Ammo.js 物体在「物理态→运动学态→物理态」切换后碰撞失效问题求助
问题描述
当物体经历「动态物理态 → 运动学态 → 动态物理态」的切换流程后,会直接掉落地面但完全无碰撞反应,穿模穿过地面,没有任何物理交互效果。我已经实现了状态切换、位置重置等相关函数,但问题依旧,想请教解决方案。
现有核心代码片段
1. 切换为运动学态函数
deactivatePhysics: function(body) { const CF_KINEMATIC_OBJECT = 2; const DISABLE_DEACTIVATION = 4; // Remove from world if already added app.matrixAmmo.dynamicsWorld.removeRigidBody(body); // Set kinematic body.setCollisionFlags(body.getCollisionFlags() | CF_KINEMATIC_OBJECT); body.setActivationState(DISABLE_DEACTIVATION); // Optional: Zero velocities const zero = new Ammo.btVector3(0, 0, 0); body.setLinearVelocity(zero); body.setAngularVelocity(zero); // Re-add to world app.matrixAmmo.dynamicsWorld.addRigidBody(body); // Manual flag for logic body.isKinematic = true; },
2. 重置物体到地面上方函数
resetBodyAboveFloor: function(body, x = 0, z = 0) { const y = 3 + Math.random(); const transform = new Ammo.btTransform(); transform.setIdentity(); transform.setOrigin(new Ammo.btVector3(x, y, z)); body.setWorldTransform(transform); body.getMotionState().setWorldTransform(transform); },
3. 切回动态物理态函数
activatePhysics: function(body) { // 1. Make it dynamic again body.setCollisionFlags(body.getCollisionFlags() & ~2); // remove kinematic body.setActivationState(1); // ACTIVE_TAG body.isKinematic = false; // 2. Reset position ABOVE the floor — force it out of collision const newY = 3 + Math.random(); // ensure it’s above the floor const transform = new Ammo.btTransform(); transform.setIdentity(); transform.setOrigin(new Ammo.btVector3(0, newY, 0)); // randomize if needed body.setWorldTransform(transform); // 3. Clear velocities body.setLinearVelocity(new Ammo.btVector3(0, 0, 0)); body.setAngularVelocity(new Ammo.btVector3(0, 0, 0)); // 4. Enable CCD (to prevent tunneling) body.setCcdMotionThreshold(1e-7); body.setCcdSweptSphereRadius(0.2); this.resetBodyAboveFloor(body); // <--- 重复调用位置重置 // 5. Reactivate it body.activate(true); },
4. 批量激活所有骰子物理态函数
activateAllDicesPhysics: function() { this.getAllDices() .filter((item) => { let test = app.matrixAmmo.getBodyByName(item.name)?.isKinematicObject(); if(test === true) { return true; } else { return false; } }) .forEach((dice) => { const body = app.matrixAmmo.getBodyByName(dice.name); if(body) { this.activatePhysics(body); // <--- 已注意传入物理体而非骰子对象 } }); },
5. 获取所有骰子函数
getAllDices: function() { return app.mainRenderBundle.filter(item => item.name.indexOf("CubePhysics") !== -1); },
问题排查与修改建议
我梳理了代码里几个可能导致碰撞失效的关键点,逐个给你分析并给出修正方案:
1. 修复activatePhysics中的重复位置重置与MotionState同步问题
你在activatePhysics里先手动设置了body.setWorldTransform(transform),随后又调用this.resetBodyAboveFloor(body)重复重置位置——更关键的是,第一次手动设置transform时没有同步MotionState。
Ammo.js中动态物体的位置必须同时同步body.setWorldTransform和getMotionState().setWorldTransform,否则物理世界无法正确识别物体的新位置,导致碰撞检测异常。建议直接复用resetBodyAboveFloor函数,不要手动重复设置:
2. 替换魔法数字,用常量保证位运算一致性
移除运动学标记时直接写~2属于魔法数字,和deactivatePhysics里的常量定义不一致,容易出错,统一用常量:
const CF_KINEMATIC_OBJECT = 2; body.setCollisionFlags(body.getCollisionFlags() & ~CF_KINEMATIC_OBJECT);
3. 状态切换时统一移除/重新添加刚体,同步碰撞组参数
deactivatePhysics中移除再添加刚体时,如果初始添加刚体时指定了碰撞组和掩码(比如addRigidBody(body, group, mask)),重新添加时必须传入相同参数,否则会导致碰撞对不被检测。
4. 调整激活时机,确保最后强制激活
body.activate(true)会强制将物体设为ACTIVE_TAG状态,建议在所有状态修改、位置重置完成后再调用。
修正后的activatePhysics函数示例
activatePhysics: function(body) { const CF_KINEMATIC_OBJECT = 2; const ACTIVE_TAG = 1; // 替换为你初始添加刚体时的碰撞组/掩码 const COLLISION_GROUP_OBJECT = 1; const COLLISION_MASK_OBJECT = -1; // 1. 先从物理世界移除刚体,避免状态切换时的同步冲突 app.matrixAmmo.dynamicsWorld.removeRigidBody(body); // 2. 恢复动态物理态 body.setCollisionFlags(body.getCollisionFlags() & ~CF_KINEMATIC_OBJECT); body.setActivationState(ACTIVE_TAG); body.isKinematic = false; // 3. 重置位置到地面上方,复用封装好的函数保证MotionState同步 this.resetBodyAboveFloor(body, 0, 0); // 4. 清空速度 const zero = new Ammo.btVector3(0, 0, 0); body.setLinearVelocity(zero); body.setAngularVelocity(zero); // 5. 启用CCD防止穿模 body.setCcdMotionThreshold(1e-7); body.setCcdSweptSphereRadius(0.2); // 6. 重新添加刚体到物理世界,同步碰撞组参数 app.matrixAmmo.dynamicsWorld.addRigidBody(body, COLLISION_GROUP_OBJECT, COLLISION_MASK_OBJECT); // 7. 最后强制激活物体 body.activate(true); },
额外注意事项
- 确保地面碰撞体是静态态(
CF_STATIC_OBJECT),且碰撞组与动态物体的掩码匹配; - 检查
resetBodyAboveFloor中的y = 3 + Math.random()是否真的高于地面(如果地面y坐标非0或物体本身有高度,需调整该值避免初始重叠); - 注意销毁Ammo.js创建的
btTransform、btVector3等C++对象(用Ammo.destroy(transform)),避免内存泄漏导致的物理异常。
试试这些修改,应该能解决碰撞失效的问题!
内容来源于stack exchange

