从基元创建自定义形状

Creating custom shapes from primitives

我正在尝试通过组合原始形状来创建自定义物理形状。目标是创建一个圆形的立方体。合适的方法似乎是 init(shapes:transforms:) 我在这里找到 https://developer.apple.com/library/prerelease/ios/documentation/SceneKit/Reference/SCNPhysicsShape_Class/index.html#//apple_ref/occ/clm/SCNPhysicsShape/shapeWithShapes:transforms:

我认为这可以用 8 个球体、12 个圆柱体和中间的一个盒子来完成。任何人都可以提供一个这样做的例子吗?

是的,您可能已经注意到,从 SCNBox 圆角创建物理体会忽略倒角半径。实际上,几乎所有的基本几何体(长方体、球体、圆柱体、金字塔、茶壶 等)都会生成理想化的物理形状,而不是将它们的顶点网格直接转换为物理体。

总的来说,这是一件好事。在理想化的球体上执行碰撞检测比在近似球体的 1100 个三角形网格上执行碰撞检测要快得多(要测试的点是否在球体中心的半径距离内?)。理想化框的同上(将点转换为框的局部坐标系,范围内 x/y/z 的文本)。

SCNShapeinit(shapes:transforms:) 初始值设定项是从这些理想化形状构建复杂形状的好方法。实际上,init(node:options:) 初始值设定项也是如此:如果为 options 参数传递 [SCNPhysicsShapeKeepAsCompoundKey: true],则可以传递一个 SCNNode,其中包含子节点的层次结构,其几何形状是原始形状,SceneKit 会将这些几何形状中的每一个转换为其理想化的物理形状,然后再创建一个所有这些几何形状的联合体。

我将分别展示一个例子。但首先,一些共享上下文:

let side: CGFloat = 1 // one side of the cube
let radius: CGFloat = side / 4 // the corner radius
// the visual (but not physical) cube
let cube = SCNNode(geometry: SCNBox(width: side, height: side, length: side, chamferRadius: radius))

这是使用 init(shapes:transforms:) 制作它的镜头:

var compound: SCNPhysicsShape {
    let sphereShape = SCNPhysicsShape(geometry: SCNSphere(radius: radius), options: nil)

    let spheres = [SCNPhysicsShape](count: 8, repeatedValue: sphereShape)
    let sphereTransforms = [
        SCNMatrix4MakeTranslation( radius,  radius,  radius),
        SCNMatrix4MakeTranslation(-radius,  radius,  radius),
        SCNMatrix4MakeTranslation(-radius, -radius,  radius),
        SCNMatrix4MakeTranslation(-radius, -radius, -radius),
        SCNMatrix4MakeTranslation( radius, -radius, -radius),
        SCNMatrix4MakeTranslation( radius,  radius, -radius),
        SCNMatrix4MakeTranslation(-radius,  radius, -radius),
        SCNMatrix4MakeTranslation( radius, -radius,  radius),
    ]
    let transforms = sphereTransforms.map {
        NSValue(SCNMatrix4: [=11=])
    }
    return SCNPhysicsShape(shapes: spheres, transforms: transforms)
}
cube.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: compound)

你在 sphereTransformstransforms 中看到的舞蹈是因为 SceneKit 期望它的每个参数都有一个 ObjC NSArray,并且 NSArrays 只能包含ObjC 对象...转换是一个 SCNMatrix4,它是一个结构,因此我们必须将它包装在 NSValue 中以将其存储在 NSArray 中。在 Swift 中,使用 SCNMatrix4 的数组很方便,然后使用 map 得到包含每个元素的 NSValue 的数组。 (当我们将 [NSValue] 传递给 SceneKit API 时,Swift 会自动桥接到引擎盖下的 NSArray。)

这将创建一个主体,它只是立方体的圆角 — 它们之间是空的 space。根据您需要圆形立方体碰撞的情况,这可能就足够了。例如,如果您只想让圆形立方体骰子在地板上滚动,角落碰撞是唯一重要的,因为地板不会在不接触角落球体的情况下与骰子的中间碰撞。如果这就是您所需要的,那就去做吧 — 如果您的物理形状尽可能简单,您将获得最佳性能。

如果你想制作一个更精确的复合形状,边缘为圆柱体,面为三个盒子或六个平面,你可以扩展上面的例子。只需为每种形状制作形状数组,并在转换为 [NSValue] 并传递给 SceneKit 之前连接数组。 (请注意,圆柱体需要旋转和平移变换,因此请将 SCNMatrix4MakeTranslationSCNMatrix4Rotate 结合使用。)

话又说回来,所有这些数学都越来越难以形象化。嵌套调用 SCNMatrix4Whatever 来进行计算并不是那么有趣。所以你可以用节点代替:

var nodeCompound: SCNNode  {
    // a node to hold the compound geometry
    let parent = SCNNode()

    // one node with a sphere
    let sphere = SCNNode(geometry: SCNSphere(radius: radius))
    // inner func to clone the sphere to a specific position
    func corner(x x: CGFloat, y: CGFloat, z: CGFloat) -> SCNNode {
        let node = sphere.clone()
        node.position = SCNVector3(x: x, y: y, z: z)
        return node
    }

    // clone the sphere to each corner as child nodes
    parent.addChildNode(corner(x:  radius, y:  radius, z:  radius))
    parent.addChildNode(corner(x: -radius, y:  radius, z:  radius))
    parent.addChildNode(corner(x: -radius, y: -radius, z:  radius))
    parent.addChildNode(corner(x: -radius, y: -radius, z: -radius))
    parent.addChildNode(corner(x:  radius, y: -radius, z: -radius))
    parent.addChildNode(corner(x:  radius, y:  radius, z: -radius))
    parent.addChildNode(corner(x: -radius, y:  radius, z: -radius))
    parent.addChildNode(corner(x:  radius, y: -radius, z:  radius))

    return parent
}

将此节点放入场景中,您可以在定位球体(和圆柱体等)时可视化结果。请注意,这个节点不必实际添加到您的场景中(除非您出于调试目的将其可视化)。一旦你按照你想要的方式获得它,用它来创建一个物理形状,并将该形状分配给你实际想要在场景中绘制的 other 节点:

cube.physicsBody = SCNPhysicsBody(type: .Dynamic, 
    shape: SCNPhysicsShape(node: nodeCompound, 
        options: [SCNPhysicsShapeKeepAsCompoundKey: true]))

顺便说一句,如果您在此处删除保持复合选项,您将得到一个形状,它是八个角球体的凸包网格(无论您是否还放入了边和面,因为那些位于船体内)。也就是说,它让您近似于一个圆形的立方体……角半径将不如理想化的几何形状光滑,但是根据您需要此碰撞体的用途,它可能就是您所需要的。