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

如何实现适配旋转物体的碰撞检测?

旋转物体的同步碰撞盒实现方案

你当前用的THREE.Box3是轴对齐包围盒(AABB),它的特性就是始终和世界坐标系的X/Y/Z轴平行,无论物体怎么旋转,它都会重新计算顶点的轴对齐边界,所以无法跟随物体旋转。要实现随物体同步旋转的碰撞盒,必须使用定向包围盒(OBB),核心是用**分离轴定理(SAT)**做碰撞检测。

以下是无需额外库的实现步骤:

1. 实现OBB参数提取函数

先写一个函数,从物体中提取OBB的核心参数:中心位置、三个旋转后的轴向量、半尺寸(考虑缩放):

function getOBB(obj) {
    // 获取物体的世界变换矩阵
    const worldMatrix = obj.matrixWorld.clone();
    
    // 提取物体世界空间的中心位置
    const center = new THREE.Vector3();
    worldMatrix.decompose(center, new THREE.Quaternion(), new THREE.Vector3());
    
    // 提取旋转后的三个轴向量(已归一化)
    const axes = [
        new THREE.Vector3(worldMatrix.elements[0], worldMatrix.elements[1], worldMatrix.elements[2]).normalize(),
        new THREE.Vector3(worldMatrix.elements[4], worldMatrix.elements[5], worldMatrix.elements[6]).normalize(),
        new THREE.Vector3(worldMatrix.elements[8], worldMatrix.elements[9], worldMatrix.elements[10]).normalize()
    ];
    
    // 计算物体局部空间的半尺寸,再叠加缩放
    const localBox = new THREE.Box3().setFromObject(obj);
    const localHalfSize = localBox.getSize(new THREE.Vector3()).multiplyScalar(0.5);
    const halfExtents = new THREE.Vector3(
        localHalfSize.x * obj.scale.x,
        localHalfSize.y * obj.scale.y,
        localHalfSize.z * obj.scale.z
    );
    
    return { center, axes, halfExtents };
}

2. 实现OBB碰撞检测(分离轴定理)

用SAT检测两个OBB是否碰撞,同时计算最小分离向量(用于碰撞响应):

function checkOBBCollision(obbA, obbB) {
    const axes = [
        // OBB A的三个轴
        obbA.axes[0], obbA.axes[1], obbA.axes[2],
        // OBB B的三个轴
        obbB.axes[0], obbB.axes[1], obbB.axes[2],
        // 两两轴的叉乘
        obbA.axes[0].clone().cross(obbB.axes[0]),
        obbA.axes[0].clone().cross(obbB.axes[1]),
        obbA.axes[0].clone().cross(obbB.axes[2]),
        obbA.axes[1].clone().cross(obbB.axes[0]),
        obbA.axes[1].clone().cross(obbB.axes[1]),
        obbA.axes[1].clone().cross(obbB.axes[2]),
        obbA.axes[2].clone().cross(obbB.axes[0]),
        obbA.axes[2].clone().cross(obbB.axes[1]),
        obbA.axes[2].clone().cross(obbB.axes[2])
    ];
    
    let minOverlap = Infinity;
    let collisionAxis = null;
    
    // 遍历所有分离轴
    for (const axis of axes) {
        // 跳过零向量轴(叉乘可能出现)
        if (axis.lengthSq() < 0.0001) continue;
        axis.normalize();
        
        // 计算两个OBB在当前轴上的投影区间
        const projA = projectOBB(obbA, axis);
        const projB = projectOBB(obbB, axis);
        
        // 检查是否分离
        const overlap = getIntervalOverlap(projA, projB);
        if (overlap <= 0) {
            // 存在分离轴,无碰撞
            return { isColliding: false };
        }
        
        // 记录最小的重叠量和对应轴
        if (overlap < minOverlap) {
            minOverlap = overlap;
            collisionAxis = axis;
        }
    }
    
    // 修正轴方向(确保从B指向A,方便后续响应)
    const delta = obbA.center.clone().sub(obbB.center);
    if (delta.dot(collisionAxis) < 0) {
        collisionAxis.multiplyScalar(-1);
    }
    
    return {
        isColliding: true,
        overlap: minOverlap,
        axis: collisionAxis
    };
}

// 辅助函数:计算OBB在指定轴上的投影区间
function projectOBB(obb, axis) {
    const extent = obb.halfExtents.x * Math.abs(obb.axes[0].dot(axis)) +
                   obb.halfExtents.y * Math.abs(obb.axes[1].dot(axis)) +
                   obb.halfExtents.z * Math.abs(obb.axes[2].dot(axis));
    const centerProj = obb.center.dot(axis);
    return { min: centerProj - extent, max: centerProj + extent };
}

// 辅助函数:计算两个投影区间的重叠量
function getIntervalOverlap(projA, projB) {
    const overlapStart = Math.max(projA.min, projB.min);
    const overlapEnd = Math.min(projA.max, projB.max);
    return overlapEnd - overlapStart;
}

3. 修改碰撞检测主函数

替换原来的AABB逻辑,用OBB检测并处理碰撞响应:

function checkCollisions() {
    // 获取玩家的OBB参数
    const playerOBB = getOBB(player);
    const nearbyObjects = scene.children.filter(obj => obj !== player && obj.isMesh && obj.name !== "deco");

    nearbyObjects.forEach(obj => {
        const objOBB = getOBB(obj);
        const collisionResult = checkOBBCollision(playerOBB, objOBB);
        
        if (collisionResult.isColliding) {
            // 应用碰撞位移:将玩家沿碰撞轴方向推开重叠量的80%(和你原来的系数一致)
            player.position.add(collisionResult.axis.clone().multiplyScalar(collisionResult.overlap * 0.8));
            
            // 处理速度衰减:将玩家速度沿碰撞轴的分量衰减
            const velocityAlongAxis = playerVelocity.dot(collisionResult.axis);
            if (velocityAlongAxis < 0) {
                playerVelocity.sub(collisionResult.axis.clone().multiplyScalar(velocityAlongAxis * (1 - 0.2)));
            }
            
            // 特殊处理跳跃状态(如果碰撞轴是Y轴向上,判定为踩在物体上)
            if (Math.abs(collisionResult.axis.y - 1) < 0.1) {
                isJumping = false;
                playerVelocity.y = Math.max(playerVelocity.y, 0);
            }
        }
    });
}

关键说明

  • OBB的碰撞响应不再依赖世界轴的X/Y/Z,而是基于碰撞发生的实际法线轴,所以旋转后的物体碰撞会更准确。
  • 如果你玩家不需要旋转,可以把玩家的OBB换成AABB,只对障碍物用OBB,这样能稍微优化性能,但统一用OBB兼容性更好。
  • 代码中保留了你原来的碰撞响应系数(0.8位移、0.2速度衰减),可以根据需求调整。

内容的提问来源于stack exchange,提问作者Kashu Kutosza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:34:58