You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ammo.js 物体在「物理态→运动学态→物理态」切换后碰撞失效问题求助

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 09:44:38