从基元创建自定义形状
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 的文本)。
SCNShape
的 init(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)
你在 sphereTransforms
和 transforms
中看到的舞蹈是因为 SceneKit 期望它的每个参数都有一个 ObjC NSArray
,并且 NSArray
s 只能包含ObjC 对象...转换是一个 SCNMatrix4
,它是一个结构,因此我们必须将它包装在 NSValue
中以将其存储在 NSArray
中。在 Swift 中,使用 SCNMatrix4
的数组很方便,然后使用 map
得到包含每个元素的 NSValue
的数组。 (当我们将 [NSValue]
传递给 SceneKit API 时,Swift 会自动桥接到引擎盖下的 NSArray
。)
这将创建一个主体,它只是立方体的圆角 — 它们之间是空的 space。根据您需要圆形立方体碰撞的情况,这可能就足够了。例如,如果您只想让圆形立方体骰子在地板上滚动,角落碰撞是唯一重要的,因为地板不会在不接触角落球体的情况下与骰子的中间碰撞。如果这就是您所需要的,那就去做吧 — 如果您的物理形状尽可能简单,您将获得最佳性能。
如果你想制作一个更精确的复合形状,边缘为圆柱体,面为三个盒子或六个平面,你可以扩展上面的例子。只需为每种形状制作形状数组,并在转换为 [NSValue]
并传递给 SceneKit 之前连接数组。 (请注意,圆柱体需要旋转和平移变换,因此请将 SCNMatrix4MakeTranslation
与 SCNMatrix4Rotate
结合使用。)
话又说回来,所有这些数学都越来越难以形象化。嵌套调用 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]))
顺便说一句,如果您在此处删除保持复合选项,您将得到一个形状,它是八个角球体的凸包网格(无论您是否还放入了边和面,因为那些位于船体内)。也就是说,它让您近似于一个圆形的立方体……角半径将不如理想化的几何形状光滑,但是根据您需要此碰撞体的用途,它可能就是您所需要的。
我正在尝试通过组合原始形状来创建自定义物理形状。目标是创建一个圆形的立方体。合适的方法似乎是 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 的文本)。
SCNShape
的 init(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)
你在 sphereTransforms
和 transforms
中看到的舞蹈是因为 SceneKit 期望它的每个参数都有一个 ObjC NSArray
,并且 NSArray
s 只能包含ObjC 对象...转换是一个 SCNMatrix4
,它是一个结构,因此我们必须将它包装在 NSValue
中以将其存储在 NSArray
中。在 Swift 中,使用 SCNMatrix4
的数组很方便,然后使用 map
得到包含每个元素的 NSValue
的数组。 (当我们将 [NSValue]
传递给 SceneKit API 时,Swift 会自动桥接到引擎盖下的 NSArray
。)
这将创建一个主体,它只是立方体的圆角 — 它们之间是空的 space。根据您需要圆形立方体碰撞的情况,这可能就足够了。例如,如果您只想让圆形立方体骰子在地板上滚动,角落碰撞是唯一重要的,因为地板不会在不接触角落球体的情况下与骰子的中间碰撞。如果这就是您所需要的,那就去做吧 — 如果您的物理形状尽可能简单,您将获得最佳性能。
如果你想制作一个更精确的复合形状,边缘为圆柱体,面为三个盒子或六个平面,你可以扩展上面的例子。只需为每种形状制作形状数组,并在转换为 [NSValue]
并传递给 SceneKit 之前连接数组。 (请注意,圆柱体需要旋转和平移变换,因此请将 SCNMatrix4MakeTranslation
与 SCNMatrix4Rotate
结合使用。)
话又说回来,所有这些数学都越来越难以形象化。嵌套调用 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]))
顺便说一句,如果您在此处删除保持复合选项,您将得到一个形状,它是八个角球体的凸包网格(无论您是否还放入了边和面,因为那些位于船体内)。也就是说,它让您近似于一个圆形的立方体……角半径将不如理想化的几何形状光滑,但是根据您需要此碰撞体的用途,它可能就是您所需要的。