如何在SceneKit中为自定义枢轴正确设置物理体
在SceneKit中为带缩放/旋转的自定义枢轴节点正确设置物理体
当节点仅设置自定义枢轴(无缩放、旋转)时,通过对物理形状应用枢轴的逆变换即可让物理体与几何对齐,但当节点同时存在缩放或旋转时,这种方法会导致物理形状错位。
例如以下错误代码会出现对齐问题:
box.scale = SCNVector3Make(1.5, 1.5, 1.5) box.pivot = SCNMatrix4MakeTranslation(1, 1, 1) // 错误:变换顺序颠倒,未处理旋转 let boxPhysicsShape = SCNPhysicsShape(geometry: box.geometry!) .scaled(by: box.scale) .transformed(by: SCNMatrix4Invert(box.pivot))
问题根源
节点的变换逻辑是先基于枢轴调整原点,再应用缩放,最后应用旋转。物理形状的变换顺序必须与节点的变换逻辑匹配,否则会出现错位。之前的错误在于颠倒了缩放与枢轴逆变换的顺序,且未对物理形状应用节点的旋转变换。
解决方案
正确的变换顺序应为:
- 对物理形状应用枢轴的逆变换,将物理形状的原点对齐到节点的枢轴位置
- 应用节点的缩放变换,让物理形状与几何的缩放比例一致
- 应用节点的旋转变换,让物理形状与几何的旋转角度一致
可以通过链式调用辅助方法实现,也可以直接构建组合变换矩阵一次性应用。
修正后的物理形状创建代码
// 获取节点的变换参数 let pivotInverse = SCNMatrix4Invert(box.pivot) let scale = box.scale let rotation = box.rotation // 直接使用节点的旋转参数,或从eulerAngles转换 // 按正确顺序变换物理形状 let boxPhysicsShape = SCNPhysicsShape(geometry: box.geometry!) .transformed(by: pivotInverse) .scaled(by: scale) .rotated(by: rotation) box.physicsBody = SCNPhysicsBody(type: .static, shape: boxPhysicsShape)
完整可运行示例
class My3DScene: SCNScene { override init() { super.init() // 设置相机 let camera = SCNCamera() let cameraNode = SCNNode() cameraNode.camera = camera cameraNode.position = SCNVector3(x: 0, y: 0, z: 50) rootNode.addChildNode(cameraNode) // 创建带变换的节点 let boxGeo = SCNBox(width: 5, height: 5, length: 1, chamferRadius: 0) boxGeo.firstMaterial?.diffuse.contents = UIColor.red let box = SCNNode(geometry: boxGeo) box.scale = SCNVector3Make(1.5, 1.5, 1.5) box.eulerAngles = SCNVector3Make(1, 2, 3) box.pivot = SCNMatrix4MakeTranslation(1, 1, 1) rootNode.addChildNode(box) // 正确创建物理形状 let pivotInverse = SCNMatrix4Invert(box.pivot) // 将欧拉角转换为SCNVector4格式的旋转参数 let rotationX = SCNMatrix4MakeRotation(box.eulerAngles.x, 1, 0, 0) let rotationY = SCNMatrix4MakeRotation(box.eulerAngles.y, 0, 1, 0) let rotationZ = SCNMatrix4MakeRotation(box.eulerAngles.z, 0, 0, 1) let rotationMatrix = SCNMatrix4Mult(SCNMatrix4Mult(rotationX, rotationY), rotationZ) let rotationVector = SCNVector4Make( rotationMatrix.m12, rotationMatrix.m13, rotationMatrix.m23, acos(rotationMatrix.m11) ) let boxPhysicsShape = SCNPhysicsShape(geometry: box.geometry!) .transformed(by: pivotInverse) .scaled(by: box.scale) .rotated(by: rotationVector) box.physicsBody = SCNPhysicsBody(type: .static, shape: boxPhysicsShape) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let scnView = SCNView(frame: self.view.frame) scnView.debugOptions = .showPhysicsShapes self.view.addSubview(scnView) let scene = My3DScene() scnView.present(scene, with: .fade(withDuration: 1), incomingPointOfView: nil) } } // 物理形状辅助扩展 public extension SCNPhysicsShape { func transformed(by transform: SCNMatrix4) -> SCNPhysicsShape { return SCNPhysicsShape( shapes: [self], transforms: [NSValue(scnMatrix4: transform)] ) } func translated(by translation: SCNVector3) -> SCNPhysicsShape { let transform = SCNMatrix4MakeTranslation(translation.x, translation.y, translation.z) return transformed(by: transform) } func scaled(by scale: SCNVector3) -> SCNPhysicsShape { let transform = SCNMatrix4MakeScale(scale.x, scale.y, scale.z) return transformed(by: transform) } func rotated(by rotation: SCNVector4) -> SCNPhysicsShape { let transform = SCNMatrix4MakeRotation(rotation.w, rotation.x, rotation.y, rotation.z) return transformed(by: transform) } }
另一种方式:直接使用组合矩阵
如果不想链式调用,可以直接构建组合变换矩阵,一次性应用到物理形状:
let pivotInverse = SCNMatrix4Invert(box.pivot) let scaleMatrix = SCNMatrix4MakeScale(box.scale.x, box.scale.y, box.scale.z) // 构建旋转矩阵 let rotationX = SCNMatrix4MakeRotation(box.eulerAngles.x, 1, 0, 0) let rotationY = SCNMatrix4MakeRotation(box.eulerAngles.y, 0, 1, 0) let rotationZ = SCNMatrix4MakeRotation(box.eulerAngles.z, 0, 0, 1) let rotationMatrix = SCNMatrix4Mult(SCNMatrix4Mult(rotationX, rotationY), rotationZ) // 组合变换矩阵:旋转 → 缩放 → 枢轴逆变换(矩阵乘法顺序从右到左) let combinedTransform = SCNMatrix4Mult(SCNMatrix4Mult(rotationMatrix, scaleMatrix), pivotInverse) let boxPhysicsShape = SCNPhysicsShape(geometry: box.geometry!) .transformed(by: combinedTransform)
内容的提问来源于stack exchange,提问作者OMGPOP
相关产品推荐
相关产品推荐

