资讯动态

three.js Capsule 胶囊碰撞体积类:从 API 到 Octree 碰撞检测的完整解析

发布时间:2026/9/7 5:44:55 来源:尧图企业网站定制
three.js Capsule 胶囊碰撞体积类从 API 到 Octree 碰撞检测的完整解析【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsCapsule是 three.js 提供的胶囊形碰撞体积bounding volume类由一段线段start到end加上半径radius完全定义等价于一个沿线段扫掠的球体。它常被用作角色控制器、物理近似碰撞等场景中的检测体与 AABB轴对齐包围盒和包围球并列为最常用的三种包围体积之一。读完本文你将掌握 Capsule 类 的完整 API 与默认值、intersectsBox等方法的底层实现原理并能参照官方 FPS 示例把它接入基于 Octree 的场景碰撞检测。需要特别说明本文讨论的是examples/jsm/math/Capsule.js中的数学类Capsule它不是用于渲染的几何体。three.js 中另有一个 CapsuleGeometry用于生成可见的胶囊网格参数为radius、height、capSegments、radialSegments等二者命名相近但用途完全不同前者是碰撞/数学对象后者是渲染对象。导入方式Capsule属于 Addonsthree/addons命名空间下的扩展模块不在three核心包内必须显式导入import { Capsule } from three/addons/math/Capsule.js;该文件在源码中以具名导出方式提供见 examples/jsm/math/Capsule.js 的export { Capsule }并被 Addons.js 统一聚合导出因此也可以从three/addons总入口按需引入。构造函数与属性new Capsule( start, end, radius )三个参数在源码中都有默认值见 构造函数定义参数类型含义默认值startVector3胶囊线段起点new Vector3(0, 0, 0)endVector3胶囊线段终点new Vector3(0, 1, 0)radiusnumber胶囊半径1即不带参数直接new Capsule()时会得到一个起点在原点、终点在(0, 1, 0)、半径为 1 的竖直胶囊。属性三个属性均为公共成员可直接读写.start : Vector3— 起点向量.end : Vector3— 终点向量.radius : number— 半径默认1。注意start和end存储的是传入的Vector3对象引用本身构造器直接this.start start赋值见 源码。如果多个 Capsule 实例共用同一个向量对象修改其中一个会互相影响需要独立副本时应先clone()或使用set()/copy()。方法详解.set( start, end, radius )将胶囊各分量设置为给定值。源码实现set 方法set( start, end, radius ) { this.start.copy( start ); this.end.copy( end ); this.radius radius; return this; }这里的关键点是它只复制传入向量上的数值不会替换this.start/this.end的引用官方文档原文也提示 this method only copies the values from the given objects。因此set()可以安全地用任意临时向量更新一个已有实例而不会改变实例内部持有的向量对象。返回值是this支持链式调用。.copy( capsule ) / .clone()copy把一个 Capsule 的start、end、radius全部拷入当前实例并返回thisclone则通过new this.constructor().copy( this )创建新实例源码。这两个方法同样采用拷贝数值、保留自身引用的语义。.getCenter( target )返回胶囊中心点即起点与终点的中点结果写入传入的target向量getCenter( target ) { return target.copy( this.end ).add( this.start ).multiplyScalar( 0.5 ); }该写法完全复用target不产生中间临时对象适合放在每帧执行的循环里调用。.translate( v )把偏移向量v同时加到start和end上相当于在 3D 空间中平移整个胶囊源码。这是碰撞响应中推动胶囊的主要手段官方 FPS 示例中每帧用它应用位移、并沿法线推出穿透深度见下文实战部分。.intersectsBox( box )判断给定Box3包围盒是否与胶囊相交返回布尔值。这是整个类中最有实现含量的方法也是它接入 Octree 加速结构的基础。intersectsBox 的底层实现三平面投影的保守相交测试从源码结构看intersectsBox并不是精确的胶囊—包围盒几何求交而是一个保守conservative快速测试intersectsBox( box ) { return ( checkAABBAxis( this.start.x, this.start.y, this.end.x, this.end.y, box.min.x, box.max.x, box.min.y, box.max.y, this.radius ) checkAABBAxis( this.start.x, this.start.z, this.end.x, this.end.z, box.min.x, box.max.x, box.min.z, box.max.z, this.radius ) checkAABBAxis( this.start.y, this.start.z, this.end.y, this.end.z, box.min.y, box.max.y, box.min.z, box.max.z, this.radius ) ); }核心辅助函数 checkAABBAxisfunction checkAABBAxis( p1x, p1y, p2x, p2y, minx, maxx, miny, maxy, radius ) { return ( ( minx - p1x radius || minx - p2x radius ) ( p1x - maxx radius || p2x - maxx radius ) ( miny - p1y radius || miny - p2y radius ) ( p1y - maxy radius || p2y - maxy radius ) ); }其原理是把胶囊的轴线段投影到XY、XZ、YZ 三个坐标平面在每个 2D 平面上判断线段两端点之一是否落在包围盒对应边界的radius距离之内。只要有一个投影平面上线段完全位于盒子某条边的外侧且超出半径即可断言不相交分离轴思想三个平面都不分离时返回true。这种设计带来两个实践含义只会多报不会漏报它用于剔除确定不相交的分支是安全的但返回true只表示可能相交。因此它天然适合作为包围体粗筛broad phase后续需要精确结果时应再与三角形等细节几何做细筛实现非常廉价整个测试只有加减法与比较没有任何开方、叉乘或内存分配这正是它能在 Octree 递归遍历中被高频调用的原因。与 Octree 的协作场景碰撞检测的完整调用链three.js 的 Octree 扩展 是Capsule最主要的下游消费者其碰撞流程清晰展示了上述方法如何组合使用粗筛收集getCapsuleTriangles递归遍历八叉树每访问一个子树前先用capsule.intersectsBox( subTree.box )剔除不相交的子树——这正是上一节保守测试的用途精确求交对筛出的每个三角形执行triangleCapsuleIntersect它先做三角形平面的快速排斥两端点到平面距离与半径比较再分别检测轴线与三角形面的包含关系、轴线与三条边的最近点对距离返回{ normal, point, depth }形式的碰撞信息迭代推出capsuleIntersect用_capsule.copy( capsule )在副本上循环调用求交每命中一次就_capsule.translate( result.normal.multiplyScalar( result.depth ) )沿法线推出穿透深度直到不再相交最后返回整体法线与总深度。也就是说Capsule提供定义 平移 包围盒粗筛Octree 负责空间索引与精确三角形碰撞两者配合就构成了一套完整的胶囊—三角网格碰撞方案。实战官方 FPS 示例中的玩家碰撞体examples/games_fps.html 提供了一个完整的可运行参考。其玩家碰撞体定义如下第 121 行const playerCollider new Capsule( new THREE.Vector3( 0, 0.35, 0 ), new THREE.Vector3( 0, 1, 0 ), 0.35 );即从脚底上方 0.35 到头部 1.0 的竖直线段、半径 0.35 的胶囊——注意线段端点距离0.65小于两半球的直径因此两端的半球会有重叠胶囊总高度为0.65 2 × 0.35 1.35恰好覆盖角色全身。每帧的更新与碰撞逻辑playerCollisions / updatePlayerfunction playerCollisions() { const result worldOctree.capsuleIntersect( playerCollider ); playerOnFloor false; if ( result ) { // 法线接近竖直 0.15视为可站立的地面 playerOnFloor result.normal.y 0.15; if ( ! playerOnFloor ) { // 墙/障碍去掉沿法线的速度分量滑动而非停下 playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity ) ); } if ( result.depth 1e-10 ) { // 沿法线推出穿透深度 playerCollider.translate( result.normal.multiplyScalar( result.depth ) ); } } } function updatePlayer( deltaTime ) { // 重力、阻尼…… const deltaPosition playerVelocity.clone().multiplyScalar( deltaTime ); playerCollider.translate( deltaPosition ); // 应用本帧位移 playerCollisions(); camera.position.copy( playerCollider.end ); // 相机跟随胶囊顶端头部 }这段代码集中体现了Capsule三类 API 的典型用法translate驱动运动与碰撞响应、end作为相机锚点、intersectsBox在 Octree 内部完成子树剔除。此外示例还演示了角色与球形道具的碰撞近似——把胶囊拆成start、end和中点三个球体分别做球—球测试playerSphereCollision说明在胶囊类之外start/end/radius三个数据也可以被当作自定义碰撞算法的输入。小结与适用前提Capsule是纯数学类不依赖渲染器适用于角色控制器、物理引擎桥接、拾取/阻挡等逻辑层intersectsBox是保守快速测试只宜用于剔除与粗筛精确碰撞应交给 Octree 的三角形求交或外部物理引擎start/end保存的是传入向量的引用共享向量需谨慎更新数值请使用set/copy移动位置请使用translate需要可视化胶囊外观时请使用独立的 CapsuleGeometry 构建网格其构造参数radius、height、capSegments、radialSegments、heightSegments与Capsule类无关。相关文档与源码位置docs/pages/Capsule.html.md官方 API 文档、examples/jsm/math/Capsule.js类实现、examples/jsm/math/Octree.js碰撞调用方、examples/games_fps.html完整实战示例。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价