问题描述
我正在尝试使用原始形状组合创建自定义物理形状。目标是创建一个圆角的立方体。适当的方法似乎是 init(形状:变换:) ,我在这里找到了:
I'm trying to create a custom Physics shape with combining primitive shapes. The goal is to create a rounded cube. The appropriate method seems to be init(shapes:transforms:) which I found here https://developer.apple.com/library/prerelease/ios/documentation/SceneKit/Reference/SCNPhysicsShape_Class/index.html#//apple_ref/occ/clm/SCNPhysicsShape/shapeWithShapes:transforms:
我认为这可以用8个球体完成, 12个圆筒和中间的一个盒子。任何人都可以提供这样做的例子吗?
I'm thinking this could be done with 8 spheres, 12 cylinders and a box in the middle. Can anyone provide an example of doing that?
推荐答案
是的,正如您可能已经注意到的那样,从<$创建一个物理体带圆角的c $ c> SCNBox 会忽略倒角半径。实际上,几乎所有的基本几何形状(盒子,球体,圆柱体,金字塔,等)都会产生理想化形式的物理形状,而不是将它们的顶点网格直接转换为物理体。
Yes, as you may have noticed, creating a physics body from an SCNBox
with rounded corners ignores the chamfer radius. Actually, nearly all of the basic geometries (box, sphere, cylinder, pyramid, , etc) generate physics shapes that are idealized forms rather than direct conversions of their vertex meshes to physics bodies.
一般来说,这是一件好事。在理想化球体上执行碰撞检测比在近似球体的一百三十个三角形网格上(在球体中心的半径距离内测试点?)要快得多。理想化的盒子也是如此(将点转换为盒子的局部坐标系,边界内的x / y / z文本)。
Generally, this is a good thing. It's much faster to perform collision detection on an idealized sphere than on a mesh of eleventy-hundred triangles that approximates a sphere (is the point to test within radius distance of the sphere's center?). Ditto for an idealized box (convert point to box's local coordinate system, text for x/y/z within bounds).
init(形状) :转换:)
初始化器> SCNShape
是从这些理想化形状构建复杂形状的好方法。实际上, init(node:options:)
初始值设定项也是如此:如果你传递 [SCNPhysicsShapeKeepAsCompoundKey:true]
options
参数,您可以传递 SCNNode
,其中包含几何图形的子节点层次结构是原始形状,SceneKit会在创建一个物理形状之前将每个几何转换为理想化的物理形状,这是所有几何的结合。
The init(shapes:transforms:)
initializer for SCNShape
is a good way to build a complex shape from these idealized shapes. Actually, so is the init(node:options:)
initializer: If you pass [SCNPhysicsShapeKeepAsCompoundKey: true]
for the options
parameter, you can pass an SCNNode
that contains an hierarchy of child nodes whose geometries are primitive shapes, and SceneKit will convert each of those geometries to its idealized physics shape before creating a physics shape that's the union of all of them.
我将展示一个每个例子。但首先,一些共享上下文:
I'll show an example of each. But first, some shared context:
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(形状)制作它的镜头:转换:)
:
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: $0)
}
return SCNPhysicsShape(shapes: spheres, transforms: transforms)
}
cube.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: compound)
你在那里用 sphereTransforms
和变换
看到的舞蹈是因为SceneKit需要一个ObjC NSArray
为其每个参数, NSArray
s只能包含ObjC对象...转换是 SCNMatrix4
,这是一个结构,所以我们必须将它包装在 NSValue
中,以将其存储在 NSArray $ c $中C>。在Swift中,使用
包装每个元素。 (当我们将 SCNMatrix4
数组很方便,然后使用 map
来获取<$ c的数组$ c> NSValue [NSValue]
传递给SceneKit API时,Swift会自动连接到 NSArray
。)
The dance you see in there with sphereTransforms
and transforms
is because SceneKit expects an ObjC NSArray
for each of its parameters, and NSArray
s can contain only ObjC objects... a transform is an SCNMatrix4
, which is a struct, so we have to wrap it in an NSValue
to store it in an NSArray
. In Swift, it's convenient to work with an array of SCNMatrix4
, then use map
to get an array of NSValue
s wrapping each element. (And Swift automatically bridges to NSArray
under the hood when we pass our [NSValue]
to the SceneKit API.)
这会创建一个只是立方体圆角的主体 - 它们之间有空的空间。根据您需要圆角立方体碰撞的情况,这可能就足够了。例如,如果您只想在地板上制作圆角立方体骰子,则角落碰撞是唯一重要的,因为地板不会与模具中间碰撞而不会接触角落球体。如果这就是你所需要的,那么就去吧 - 如果你的物理形状尽可能简单,你就能获得最佳性能。
This creates a body that's just the rounded corners for the cube — there's empty space in between them. Depending on the situation where you need rounded-cube collisions, that may be enough. For example, if you just want to make rounded-cube dice roll on a floor, corner collisions are the only important ones, because the floor won't collide with the middle of a die without also contacting the corner spheres. If that's all you need, go for it — you get the best performance if your physics shapes are as simple as possible.
如果你想要的话为了制作更精确的复合形状,边缘有圆柱体,面部有三个盒子或六个平面,你可以扩展上面的例子。只需为每种形状制作形状变换数组,并在转换为 [NSValue]
并传递给SceneKit之前连接数组。 (请注意,圆柱体将需要旋转和平移变换,因此将 SCNMatrix4MakeTranslation
与 SCNMatrix4Rotate
结合使用。)
If you wanted to make a more accurate compound shape, with cylinders for the edges and either three boxes or six planes for the faces, you could extend the above example. Just make arrays of shapes transforms for each kind of shape, and concatenate the arrays before converting to [NSValue]
and passing to SceneKit. (Note that the cylinders will need both rotation and translation transforms, so combine SCNMatrix4MakeTranslation
with SCNMatrix4Rotate
.)
然后,所有数学都变得难以想象。嵌套调用 SCNMatrix4Whatever
进行数学运算并不是那么有趣。所以你可以改为使用节点:
Then again, all that math is getting hard to visualize. And nesting calls to SCNMatrix4Whatever
to do that math isn't so fun. So you could do it with nodes instead:
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
}
将此节点放在场景中,您可以在定位球体(和圆柱体等)时可视化结果。请注意,此节点不必实际添加到场景中(除非您将其可视化以进行调试)。一旦你得到它你想要它,用它来创建一个物理形状,并将该形状分配给你真正想在场景中绘制的其他节点:
Put this node in a scene and you can visualize the results as you position your spheres (and cylinders, etc). Notice that this node doesn't have to actually be added to your scene, though (except when you're visualizing it for debugging purposes). Once you've got it how you want it, use it to create a physics shape, and assign that shape to the other node that you actually want to draw in your scene:
cube.physicsBody = SCNPhysicsBody(type: .Dynamic,
shape: SCNPhysicsShape(node: nodeCompound,
options: [SCNPhysicsShapeKeepAsCompoundKey: true]))
顺便说一句,如果你在这里删除keep-as-compound选项,将获得一个形状,它是八个角球的凸包网格(无论你是否也将边和面放入,因为它们位于船体内)。也就是说,它可以让你得到一个圆角立方体的近似值......角半径将比理想化的几何体更不平滑,但是根据你需要这个碰撞体,你可能只需要它。
By the way, if you drop the keep-as-compound option here, you'll get a shape that's a convex hull mesh of your eight corner spheres (regardless of whether you also put edges and faces in, because those lie within the hull). That is, it gets you some approximation of a rounded cube... the corner radius will be less smooth than with the idealized geometry, but depending on what you need this collision body for, it might be all you need.
这篇关于从基元创建自定义形状的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!