
three.js ConvexObjectBreaker 详解凸体对象实时破碎的 API 原理与实战【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsConvexObjectBreaker 是 three.js 官方 examples/jsm 附加载具Addon中用于把凸多面体 Mesh 实时切割成碎块的几何分解类典型场景是炮弹击中石塔后碎裂飞溅的物理破碎效果。本文基于 官方 API 文档 及其完整源码 examples/jsm/misc/ConvexObjectBreaker.js系统讲解prepareBreakableObject、subdivideByImpact、cutByPlane三大核心方法的工作流程、参数细节并结合官方示例 physics_ammo_break.html 给出从准备可破碎物体到碰撞触发碎块生成的完整接入方案读完后可独立在项目中实现凸体对象的分形破碎。一、它解决什么问题凸体对象的递归切割在三维物理场景中破碎效果需要三步能力几何上把模型切成两块、把两块继续递归切小、为每个碎块保留质量/速度等物理信息。ConvexObjectBreaker 把前两步封装为一个类第三步所需的物理量则以元数据形式挂在对象的userData上。类头部的 JSDoc 注释examples/jsm/misc/ConvexObjectBreaker.js给出了完整使用约定先用prepareBreakableObject把一个 Mesh 注册为可破碎对象之后调用subdivideByImpact按撞击点分裂或cutByPlane按平面切割做实际分裂分裂产生的子对象无需再次调用prepareBreakableObject——这一点很重要源码中cutByPlane在生成碎块时会自动为新碎块调用prepareBreakableObject见下文第四节。文档同时明确了三条对象前提约束Mesh 必须拥有 BufferGeometry 和一个 materialmaterial 会被传播给所有子碎块顶点法线必须是平面的planar, not smoothed——即同一面内的三个顶点共享同一个法线。这是算法的核心假设后文会看到源码直接依赖这一假设几何体必须是凸的库不做凸性校验。可用 ConvexGeometry底层基于 examples/jsm/math/ConvexHull.js 的凸包算法平均时间复杂度 O(n log n)构造凸几何BoxGeometry、SphereGeometry等凸图元也可直接使用。还有一条文档特别强调的注意事项该类会向对象的userData中写入mass、velocity、angularVelocity、breakable等成员变量与其他会操作userData的库混用时需谨慎阅读代码避免键名冲突。二、导入与构造两个参数的实际含义ConvexObjectBreaker 属于 addon必须显式导入参考 Installation#Addonsimport { ConvexObjectBreaker } from three/addons/misc/ConvexObjectBreaker.js;new ConvexObjectBreaker( minSizeForBreak 1.4, smallDelta 0.0001 )两个构造参数在 构造函数实现 中直接存入实例属性各自控制一类数值行为minSizeForBreak默认 1.4——碎块继续破碎的最小尺寸阈值。源码中它只在一处生效cutByPlane生成碎块时计算碎块外接半径radius并用2 * radius this.minSizeForBreak作为新碎块的breakable标志examples/jsm/misc/ConvexObjectBreaker.js#L439-L461。也就是说当碎块的近似直径2 × 到质心最远顶点的距离小于该阈值时它会被标记为不可再碎递归切割就此自然终止——这是控制碎块最终数量和整体粒度的关键参数。值越大碎块越少值越小碎得越细也更耗性能。smallDelta默认 0.0001——判定点是否落在切割平面上的最大距离容差。它有两处用途cutByPlane中对每个顶点计算localPlane.distanceToPoint(p)只有d delta或d -delta时才判定顶点严格位于平面正侧或负侧落在 ±delta 带内的顶点被标记为 coplanar标记 3同时推入两侧的顶点集合examples/jsm/misc/ConvexObjectBreaker.js#L311-L358从而保证切割面两侧的碎块共享切割缝上的顶点几何上严丝合缝同一 delta 还用于共面三角面判定1 - n0.dot(n1) delta用以标记被两个共面面共享的边避免一条边被处理两次见下文第三节的 segments 机制。构造函数另外预分配了一批临时对象tempPlane1/2、tempLine1、若干Vector3以及一个numPoints × numPoints的segments布尔标记池供切割时复用以减少 GC 压力——这也意味着一个 ConvexObjectBreaker 实例的segments池大小在构造时按30 × 30顶点上限初始化超大模型需要留意。三、核心 API 逐一解析3.1 prepareBreakableObject(object, mass, velocity, angularVelocity, breakable)文档要求所有期望可破碎的对象都必须调用它。源码实现非常简洁examples/jsm/misc/ConvexObjectBreaker.js#L76-L88prepareBreakableObject( object, mass, velocity, angularVelocity, breakable ) { const userData object.userData; userData.mass mass; // 质量kg必须 0 userData.velocity velocity.clone(); userData.angularVelocity angularVelocity.clone(); userData.breakable breakable; }五个参数的角色参数类型说明objectObject3D待破碎的 Mesh几何体必须凸massnumber质量kg必须大于 0velocityVector3线速度angularVelocityVector3角速度breakableboolean是否可破碎注意velocity/angularVelocity会被clone存储而cutByPlane生成碎块时是直接引用父对象的这两个向量不 clone所以物理引擎每帧更新刚体速度后碎块继承到的就是最新速度——这是与物理库衔接时的关键行为。3.2 subdivideByImpact(object, pointOfImpact, normal, maxRadialIterations, maxRandomIterations)按撞击语义把对象切成多块想象另一物体以法线normal撞击表面点pointOfImpact碎片沿撞击点周围呈放射状分布。参数含义见 方法实现pointOfImpact撞击点世界空间normal撞击法线maxRadialIterations径向切割的最大迭代数——围绕撞击点、沿法线轴旋转的扇形切刀次数maxRandomIterations非径向随机切割的最大迭代数。算法流程递归扇形切割先用撞击点、对象位置、pointOfImpact normal三点确定初始切割平面tempPlane1内部递归函数subdivideRadial(subObject, startAngle, endAngle, numIterations)在[startAngle, endAngle]角度区间内随机取一个角度angle0.2 0.6 × Math.random()的插值绕法线轴旋转得到新的切割平面然后调用cutByPlane切出两块对每一块带着更新后的角度区间进入下一轮递归递归的终止条件碎片直接收入debris数组、不再切有两个概率性停止Math.random() numIterations * 0.05——迭代越深越早停止的概率越大使碎块大小呈现自然的随机分布深度上限numIterations maxRadialIterations maxRandomIterations最终返回碎块数组。一个值得注意的实现细节迭代次数≤ maxRadialIterations时切割平面绕撞击点旋转真正的放射切割超过该次数后切割平面改为绕当前子对象的位置旋转且角度公式切换到基于(numIterations 1)与随机数的组合——从源码结构看这是为了让深层碎片在子对象自身坐标系内继续随机细分避免放射刀全部汇聚到原撞击点。3.3 cutByPlane(object, plane, output) : number最底层的二元切割用世界空间的plane把对象切成两半。文档签名之外源码examples/jsm/misc/ConvexObjectBreaker.js#L189-L468揭示了几个文档未展开的返回契约结果写入output.object1与output.object2两个成员若平面未真正切割对象object2为nullobject1仅在内错时才会为null返回值为碎块数量0/1/2返回 0 表示内部错误如线段与平面不相交这种理论上不会发生的分支会console.error并置空输出。切割的完整步骤共面边标记双重遍历所有三角面对(i, j)若两面法线点积满足1 - n0.dot(n1) smallDelta判为共面再按顶点索引重叠找出共享边把segments[a × numPoints b]双向置 true。这一步利用了顶点法线平面化的前提——面的三个顶点法线相同取第一个顶点即可代表整面法线examples/jsm/misc/ConvexObjectBreaker.js#L236-L284。平面变换到对象局部空间几何坐标是局部空间的而传入的切割平面是世界的因此调用静态方法transformPlaneToLocalSpaceexamples/jsm/misc/ConvexObjectBreaker.js#L523-L535——取平面上的一个参考点做逆仿射变换、法线做转置逆变换再重算平面常数。逐边分类对每条未处理的边两端点按distanceToPoint与 ±delta 的比较标记为负侧(1)/正侧(2)/共面(3)共面顶点双侧各推一份若端点异侧用Plane.intersectLine求出交点交点同时加入两侧——保证切割缝顶点共享。碎块重建当某侧顶点数 4时用new ConvexGeometry( points )直接由顶点集重建凸包网格共享原material位置设为顶点集近似质心顶点算术平均再平移到object.position四元数继承父对象。源码注释坦承质量与质心都是very fast and imprecise的近似质量恒取父对象的一半userData.mass × 0.5质心取顶点平均。递归标记新碎块立即调用prepareBreakableObject(object, newMass, 原velocity, 原文角velocity, 2 × radius minSizeForBreak)——即第一节提到的子对象无需手动 prepare的实现所在同时breakable由minSizeForBreak阈值自动决定。cutByPlane的三个静态向量变换助手transformFreeVector/transformFreeVectorInverse/transformTiedVectorInverse要求输入矩阵为正交矩阵不含缩放这是使用时的隐含前提不要对可破碎对象施加非等比缩放。四、官方示例与 Ammo 物理引擎联动的实时破碎官方示例 examples/physics_ammo_break.html 演示了鼠标发射球体击中塔楼/石桥/石块/山体对象实时碎裂的完整链路是理解三个 API 如何协作的最佳范本。核心接入点如下1. 准备可破碎对象塔楼、石桥、石块用 BoxGeometry山体用 5 个顶点构造的 ConvexGeometryconst convexBreaker new ConvexObjectBreaker(); // 使用默认 minSizeForBreak / smallDelta const object new THREE.Mesh( new THREE.BoxGeometry( halfExtents.x * 2, halfExtents.y * 2, halfExtents.z * 2 ), material ); object.position.copy( pos ); object.quaternion.copy( quat ); // 注册为可破碎对象质量 1000 kg初速度/角速度为零允许破碎 convexBreaker.prepareBreakableObject( object, mass, new THREE.Vector3(), new THREE.Vector3(), true );2. 为碎块创建物理刚体示例用btConvexHullShape从碎块顶点坐标建凸包碰撞体createConvexHullPhysicsShape并通过body.setUserPointer( btVecUserData )其中btVecUserData.threeObject object把 three.js 对象回指到刚体上以便碰撞回调时找回原始 Mesh。3. 碰撞检测中触发破碎每帧遍历dispatcher的 contact manifold取施加冲量最大的接触点若冲量超过破碎阈值fractureImpulse 250且对象breakable !collided则调用const debris convexBreaker.subdivideByImpact( threeObject, // 被击中的可破碎 Mesh impactPoint, // 接触点世界坐标 impactNormal, // 接触法线 1, 1 // 1 次径向迭代 1 次随机迭代碎片较少、性能友好 ); for ( const fragment of debris ) { const vel rigidBody.getLinearVelocity(); const angVel rigidBody.getAngularVelocity(); // 碎块自动继承了父级速度向量引用这里覆盖为刚体当前速度 fragment.userData.velocity.set( vel.x(), vel.y(), vel.z() ); fragment.userData.angularVelocity.set( angVel.x(), angVel.y(), angVel.z() ); createDebrisFromBreakableObject( fragment ); // 为碎块建物理刚体并加入世界 } objectsToRemove[ numObjectsToRemove ] threeObject; // 帧末移除原对象4. 防止同帧重复破碎示例给每个对象维护userData.collided标志破碎后置 true并在updatePhysics开头清零——因为碎块刚进入物理世界就可能与撞击球再次接触不加保护会同一帧连续碎裂。这个示例同时展示了破碎链的自终止机制subdivideByImpact产生的碎片若小于minSizeForBreak其userData.breakable即为 false后续碰撞冲量再大也不会再切。五、使用要点与工程建议综合文档约束与源码实现实际接入时有以下几点值得核对几何必须凸且法线平面化。BoxGeometry、SphereGeometry、ConvexGeometry满足要求对BufferGeometry使用平滑法线的模型会破坏共面边判定与法线假设切割结果不可靠。库不校验凸性非凸输入属于未定义行为。不要对可破碎对象施加非等比缩放静态变换助手假设矩阵正交无 scale。minSizeForBreak决定碎片粒度与性能上限它同时是递归终止条件之一。破碎效果过碎时优先调大该值或减小maxRadialIterations/maxRandomIterations而不是靠物理侧优化。与物理引擎配合时注意速度向量共享碎块的userData.velocity引用父对象的同一Vector3实例物理引擎侧每帧同步速度后碎块自动看到新速度但若你手动改父对象速度会影响所有未脱离共享的碎块。userData键名占用该类固定写入mass、velocity、angularVelocity、breakable四个键与其他操作userData的库如物理引擎写physicsBody共存时避免冲突。切割平面是局部/世界空间敏感点cutByPlane内部已把平面变换到对象局部空间调用方只需传入世界空间Plane但要求对象矩阵已更新内部会调用object.updateMatrix()。六、小结与相关资源ConvexObjectBreaker 用共面边标记 平面双侧顶点分类 ConvexGeometry 凸包重建三步完成凸体二切再以概率化的递归扇形切割叠出自然碎块分布质量与尺寸阈值提供递归自终止。它与 any 刚体物理引擎的组合模式注册 → 碰撞冲量判定 → 碎块继承速度 → 重建碰撞体是 three.js 生态中实现破坏效果的标准范式。进一步阅读API 原文docs/pages/ConvexObjectBreaker.html.md完整源码examples/jsm/misc/ConvexObjectBreaker.js凸几何构造docs/pages/ConvexGeometry.html.md、examples/jsm/geometries/ConvexGeometry.js、凸包核心 examples/jsm/math/ConvexHull.js物理破碎示例examples/physics_ammo_break.html运行效果见 examples/screenshots/physics_ammo_break.jpg【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考