Haxe/OpenFL:如何将等长盒体-球体碰撞代码适配为动态尺寸?
解决非正方体盒体与球体的碰撞检测问题
你的代码核心问题在于把非正方体盒体错误地近似成了球体(用最大半边长作为等效半径),同时碰撞点的计算逻辑没有考虑盒体各轴尺寸差异。以下是具体修改方案:
关键错误分析
- 碰撞判断逻辑错误:原代码用
MathUtils.max(box.size.x*0.5, ...)把盒体当成球体处理,只有正方体时才正确,非正方体下会导致碰撞判断提前或延迟。 - 碰撞点计算错误:
bDeep的计算直接使用盒体整体边长缩放偏移,不符合非正方体盒体的表面碰撞逻辑。
具体修改步骤
1. 修正碰撞判断逻辑
删除错误的combinedRadius计算,直接比较球体中心到盒体最近点的距离与缩放后的球体半径:
// 替换原碰撞判断代码段 final scaledSphereRadius:Float = sphere.radius * sphereTransform.scale; final scaledSphereRadiusSquared:Float = scaledSphereRadius * scaledSphereRadius; // 判断球体是否侵入盒体:最近点距离平方 <= 球体半径平方 if (distanceSquared > scaledSphereRadiusSquared) { // 无碰撞 return new PhysicsCollisionPoints(null, null, null, 0, false); }
2. 修正碰撞深度计算
基于正确的球体半径和实际距离计算碰撞深度:
// 替换原collisionDepth计算 final collisionDepth:Float = scaledSphereRadius - distance;
3. 修正盒体碰撞点(bDeep)计算
盒体的碰撞点就是世界空间中的最近点,若要获取侵入后的内部点,需基于碰撞深度偏移:
// 替换原bDeep计算 final bDeep:Vector3D = new Vector3D( closestPointOnBox.x - (normal.x * collisionDepth), closestPointOnBox.y - (normal.y * collisionDepth), closestPointOnBox.z - (normal.z * collisionDepth) );
4. 进阶优化:支持旋转的非正方体盒体(可选)
原代码计算boxMin/boxMax的方式仅适用于无旋转的盒体,若要支持带旋转的盒体,需将球体中心转换到盒体局部空间计算最近点:
// 替换原boxMin/boxMax及closestPointOnBox的计算 // 将球体中心转换到盒体局部空间 final boxInverseTransform:Matrix3D = boxTransform.transform.clone(); boxInverseTransform.invert(); final sphereCenterLocal:Vector3D = boxInverseTransform.transformVector(sphereCenter.subtract(boxCenter)); // 局部空间中盒体的半尺寸(轴对齐) final boxHalfSizeLocal:Vector3D = new Vector3D( box.size.x * 0.5, box.size.y * 0.5, box.size.z * 0.5 ); final boxMinLocal:Vector3D = boxHalfSizeLocal.multiply(-1); final boxMaxLocal:Vector3D = boxHalfSizeLocal; // 局部空间内的最近点 final closestPointLocal:Vector3D = new Vector3D( MathUtils.clamp(sphereCenterLocal.x, boxMinLocal.x, boxMaxLocal.x), MathUtils.clamp(sphereCenterLocal.y, boxMinLocal.y, boxMaxLocal.y), MathUtils.clamp(sphereCenterLocal.z, boxMinLocal.z, boxMaxLocal.z) ); // 转换回世界空间 final closestPointOnBox:Vector3D = boxTransform.transform.transformVector(closestPointLocal).add(boxCenter);
修改后的完整代码
public static function TestCollisionBetweenSphereBox(a:PhysicsCollider, ta:PhysicsTransform, b:PhysicsCollider, tb:PhysicsTransform):PhysicsCollisionPoints { final sphere:PhysicsSphereCollider = cast(a, PhysicsSphereCollider); final sphereTransform = ta; final box:PhysicsBoxCollider = cast(b, PhysicsBoxCollider); final boxTransform = tb; trace("testing collision between sphere", sphere, "and box", box); trace("transform", ta, tb); // Transform to World Space final sphereCenter:Vector3D = new Vector3D( sphere.center.x + ta.position.x, sphere.center.y + ta.position.y, sphere.center.z + ta.position.z ); final boxCenter:Vector3D = new Vector3D( box.center.x + tb.position.x, box.center.y + tb.position.y, box.center.z + tb.position.z ); trace("sphereCenter", sphereCenter); trace("boxCenter", boxCenter); // 进阶优化:支持旋转的盒体,转换到局部空间计算最近点 final boxInverseTransform:Matrix3D = boxTransform.transform.clone(); boxInverseTransform.invert(); final sphereCenterLocal:Vector3D = boxInverseTransform.transformVector(sphereCenter.subtract(boxCenter)); final boxHalfSizeLocal:Vector3D = new Vector3D( box.size.x * 0.5, box.size.y * 0.5, box.size.z * 0.5 ); final boxMinLocal:Vector3D = boxHalfSizeLocal.multiply(-1); final boxMaxLocal:Vector3D = boxHalfSizeLocal; final closestPointLocal:Vector3D = new Vector3D( MathUtils.clamp(sphereCenterLocal.x, boxMinLocal.x, boxMaxLocal.x), MathUtils.clamp(sphereCenterLocal.y, boxMinLocal.y, boxMaxLocal.y), MathUtils.clamp(sphereCenterLocal.z, boxMinLocal.z, boxMaxLocal.z) ); final closestPointOnBox:Vector3D = boxTransform.transform.transformVector(closestPointLocal).add(boxCenter); trace("closest point", closestPointOnBox); // Check for Intersection final sphereToBox:Vector3D = closestPointOnBox.subtract(sphereCenter); final distanceSquared:Float = sphereToBox.lengthSquared; final scaledSphereRadius:Float = sphere.radius * sphereTransform.scale; final scaledSphereRadiusSquared:Float = scaledSphereRadius * scaledSphereRadius; trace("sphereToBox",sphereToBox); trace("distanceSquared", distanceSquared); trace("scaledSphereRadius", scaledSphereRadius); trace("scaledSphereRadius^2", scaledSphereRadiusSquared); if (distanceSquared > scaledSphereRadiusSquared) { // No collision return new PhysicsCollisionPoints(null, null, null, 0, false); } // Calculate Collision Points final distance:Float = Math.sqrt(distanceSquared); final normal:Vector3D = MathUtils.normalizeVector(sphereToBox); final collisionDepth:Float = scaledSphereRadius - distance; final aDeep:Vector3D = new Vector3D( sphereCenter.x + (normal.x * scaledSphereRadius), sphereCenter.y + (normal.y * scaledSphereRadius), sphereCenter.z + (normal.z * scaledSphereRadius) ); final bDeep:Vector3D = new Vector3D( closestPointOnBox.x - (normal.x * collisionDepth), closestPointOnBox.y - (normal.y * collisionDepth), closestPointOnBox.z - (normal.z * collisionDepth) ); final ret = new PhysicsCollisionPoints(aDeep, bDeep, normal, collisionDepth, true); trace("distance", distance); trace("normal", normal); trace("collisionDepth", collisionDepth); trace("aDeep", aDeep); trace("bDeep", bDeep); return ret; }
内容的提问来源于stack exchange,提问作者user23323208
相关产品推荐
相关产品推荐

