news 2026/9/7 5:44:54

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
three.js Capsule 胶囊碰撞体积类:从 API 到 Octree 碰撞检测的完整解析

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

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

Capsule是 three.js 提供的胶囊形碰撞体积(bounding volume)类,由一段线段(startend)加上半径radius完全定义,等价于一个沿线段扫掠的球体。它常被用作角色控制器、物理近似碰撞等场景中的检测体,与 AABB(轴对齐包围盒)和包围球并列为最常用的三种包围体积之一。读完本文,你将掌握 Capsule 类 的完整 API 与默认值、intersectsBox等方法的底层实现原理,并能参照官方 FPS 示例把它接入基于 Octree 的场景碰撞检测。

需要特别说明:本文讨论的是examples/jsm/math/Capsule.js中的数学类Capsule,它不是用于渲染的几何体。three.js 中另有一个 CapsuleGeometry,用于生成可见的胶囊网格(参数为radiusheightcapSegmentsradialSegments等),二者命名相近但用途完全不同:前者是碰撞/数学对象,后者是渲染对象。

导入方式

Capsule属于 Addons(three/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

注意startend存储的是传入的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 的startendradius全部拷入当前实例并返回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同时加到startend上,相当于在 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 ) ); }

核心辅助函数 checkAABBAxis:

function 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最主要的下游消费者,其碰撞流程清晰展示了上述方法如何组合使用:

  1. 粗筛收集getCapsuleTriangles递归遍历八叉树,每访问一个子树前先用capsule.intersectsBox( subTree.box )剔除不相交的子树——这正是上一节保守测试的用途;
  2. 精确求交:对筛出的每个三角形执行triangleCapsuleIntersect,它先做三角形平面的快速排斥(两端点到平面距离与半径比较),再分别检测轴线与三角形面的包含关系、轴线与三条边的最近点对距离,返回{ normal, point, depth }形式的碰撞信息;
  3. 迭代推出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 / updatePlayer):

function 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 内部完成子树剔除。此外示例还演示了角色与球形道具的碰撞近似——把胶囊拆成startend和中点三个球体分别做球—球测试(playerSphereCollision),说明在胶囊类之外,start/end/radius三个数据也可以被当作自定义碰撞算法的输入。

小结与适用前提

  • Capsule是纯数学类,不依赖渲染器,适用于角色控制器、物理引擎桥接、拾取/阻挡等逻辑层;
  • intersectsBox是保守快速测试,只宜用于剔除与粗筛;精确碰撞应交给 Octree 的三角形求交或外部物理引擎;
  • start/end保存的是传入向量的引用,共享向量需谨慎;更新数值请使用set/copy,移动位置请使用translate
  • 需要可视化胶囊外观时,请使用独立的 CapsuleGeometry 构建网格,其构造参数(radiusheightcapSegmentsradialSegmentsheightSegments)与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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:42:01

Commit Message

Commit Message 【免费下载链接】gemini-cli An open-source AI agent that brings the power of Gemini directly into your terminal. 项目地址: https://gitcode.com/GitHub_Trending/gemi/gemini-cli [SSR Agent] Issue Fix (<issue_number>): <short_comm…

作者头像 李华
网站建设 2026/9/7 5:41:13

WinUSB上位机开发实战:从设备描述符到批量传输完整方案

简介&#xff1a;一套基于WinUSB实现上位机与USB设备通信的完整工程源码&#xff0c;面向需要在Windows下快速入门USB驱动开发与MFC界面集成的开发者。资源适配Visual Studio 2010与C环境&#xff0c;覆盖设备枚举、接口初始化、管道读写、动态插拔处理等核心环节&#xff0c;并…

作者头像 李华
网站建设 2026/9/7 5:40:49

AI驱动供应链跨岗位协同:渐进改造的落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:37:55

AI编程代理安全落地:约束、审查与反馈闭环实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华