如何正确设置物理体以适应 SceneKit 中的自定义枢轴

问题描述 投票:0回答:1

我需要设置节点的物理体。当我有一个自定义枢轴但没有缩放或旋转时,我可以简单地通过枢轴的反转来变换形状,如下所示:

    let boxGeo = SCNBox(width: 5, height: 5, length: 1, chamferRadius: 0)
    let box = SCNNode(geometry: boxGeo)
    rootNode.addChildNode(box)

    // i have a custom pivot here: 
    box.pivot = SCNMatrix4MakeTranslation(1, 1, 1)
    
    // i can simply transform the shape by inverse of pivot:
    let boxPhysicsShape = SCNPhysicsShape(geometry: box.geometry!)
      .transformed(by: SCNMatrix4Invert(box.pivot))
    box.physicsBody = SCNPhysicsBody(type: .static, shape: boxPhysicsShape)

请注意,

transformed(by:)
是我在下面提供的辅助函数:


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)
  }
  
}

这个效果很好。但是,当节点缩放时,这将不再起作用:

    // ...
    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))

如果打开

showPhysicsShapes
调试选项,物理形状会错位。

与缩放类似,当涉及旋转时,我也无法对齐物理形状。

这是您可以使用的最小重现代码:

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 boxPhysicsShape = SCNPhysicsShape(geometry: box.geometry!)
      // no need to rotate, shape is rotated by node automatically
      .scaled(by: box.scale)
      .transformed(by: SCNMatrix4Invert(box.pivot))
    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)
  }
  
}
ios swift scenekit
1个回答
0
投票

您遇到一个问题,即缩放和旋转节点时物理形状未正确对齐。这是因为 SCNPhysicsShape 在创建物理体的形状时不会自动考虑节点的缩放、旋转和枢轴。您当前的方法适用于未变换的形状,但应用缩放或旋转后,这会导致未对齐。

为了解决这个问题,您需要将节点的完整变换(包括缩放、旋转和枢轴)应用到物理形状。您需要应用包括缩放、旋转和枢轴调整的完整变换矩阵,而不仅仅是缩放和平移物理形状。

解决方案:

  1. 计算节点的完整变换:这包括其缩放、旋转和枢轴。

  2. 使用此完整变换来变换物理形状。

检查下面更新的代码。

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)
        
        // Set scale, rotation, and pivot
        box.scale = SCNVector3Make(1.5, 1.5, 1.5)
        box.eulerAngles = SCNVector3Make(1, 2, 3)
        box.pivot = SCNMatrix4MakeTranslation(1, 1, 1)
        
        rootNode.addChildNode(box)
        
        // Create the physics shape and transform it with the node's full transformation
        let boxPhysicsShape = SCNPhysicsShape(geometry: box.geometry!)
        
        // Apply the full transform: scale, rotation, and pivot
        let nodeTransformation = box.worldTransform
        
        // Apply the full transformation to the physics shape
        let transformedShape = boxPhysicsShape.transformed(by: nodeTransformation)
        
        // Set the physics body
        box.physicsBody = SCNPhysicsBody(type: .static, shape: transformedShape)
    }
    
    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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.