iOS 上 Swift 中的自定义 SceneKit 几何图形不起作用,但等效的 Objective C 代码可以
Posted
技术标签:
【中文标题】iOS 上 Swift 中的自定义 SceneKit 几何图形不起作用,但等效的 Objective C 代码可以【英文标题】:Custom SceneKit Geometry in Swift on iOS not working but equivalent Objective C code does 【发布时间】:2014-08-21 00:15:00 【问题描述】:我热衷于采用新的 Swift 语言,因为这似乎是 Apple 开发的前进方向。 ios 8 中新的 SceneKit 支持也给我留下了深刻的印象。我想在运行时以编程方式创建自定义几何图形,但我很难让 Swift 代码正常工作。然而,Objective C 中的等效代码可以正常工作。
这可能是一个错误,或者我做错了什么。
我只是想创建和渲染一个三角形。为简单起见,此时我将忽略法线和纹理等。所以我只希望看到一个黑色的三角形。
Swift 代码(不工作)
var verts = [SCNVector3(x: 0,y: 0,z: 0),SCNVector3(x: 1,y: 0,z: 0),SCNVector3(x: 0,y: 1,z: 0)]
let src = SCNGeometrySource(vertices: &verts, count: 3)
let indexes:Int[]=[0,1,2]
let dat = NSData(bytes: indexes, length: sizeofValue(indexes))
let ele = SCNGeometryElement(data:dat, primitiveType: .Triangles, primitiveCount: 1, bytesPerIndex: sizeof(Int))
let geo = SCNGeometry(sources: [src], elements: [ele])
let nd = SCNNode(geometry: geo)
scene.rootNode.addChildNode(nd)
Objective C 代码(确实有效)
SCNVector3 verts[] = SCNVector3Make(0, 0, 0), SCNVector3Make(1, 0, 0), SCNVector3Make(0, 1, 0) ;
SCNGeometrySource *src = [SCNGeometrySource geometrySourceWithVertices:verts count:3];
int indexes[] = 0, 1, 2 ;
NSData *datIndexes = [NSData dataWithBytes:indexes length:sizeof(indexes)];
SCNGeometryElement *ele = [SCNGeometryElement geometryElementWithData:datIndexes primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:1 bytesPerIndex:sizeof(int)];
SCNGeometry *geo = [SCNGeometry geometryWithSources:@[src] elements:@[ele]];
SCNNode *nd = [SCNNode nodeWithGeometry:geo];
d
[scene.rootNode addChildNode:nd];
任何指针将不胜感激。
【问题讨论】:
Swift 代码在什么情况下不起作用?不编译?运行时错误? 抱歉,没有编译或运行时错误。它只是没有像 Objective-c 那样渲染黑脸 【参考方案1】:嗯,这两段代码并没有完全相互翻译。 C 中的 int
与 Swift 中的 Int
不同。在 Swift 中它实际上被称为 CInt
:
/// The C 'int' type.
typealias CInt = Int32
如果您将这两个匹配项更改为使用 CInt
,您之前收到的错误消息就会消失(至少对我来说是在 OS X Playground 中。但是,它仍然不会为我呈现任何内容。
我不认为sizeofValue
用于返回数组的大小。在我看来,它正在返回指针的大小:
let indexes: CInt[] = [0, 1, 2]
sizeofValue(indexes) // is 8
sizeof(CInt) // is 4
sizeof(CInt) * countElements(indexes) // is 12
// compare to other CInt[]
let empty: CInt[] = []
let large: CInt[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sizeofValue(indexes) // is 8 (your array of indices again)
sizeofValue(empty) // is 8
sizeofValue(large) // is 8
所以,对我来说,以下代码有效(我将参数放在不同的行上,以便更容易指出我的更改):
let src = SCNGeometrySource(vertices: &verts, count: 3)
let indexes: CInt[] = [0, 1, 2] // Changed to CInt
let dat = NSData(
bytes: indexes,
length: sizeof(CInt) * countElements(indexes) // Changed to size of CInt * count
)
let ele = SCNGeometryElement(
data: dat,
primitiveType: .Triangles,
primitiveCount: 1,
bytesPerIndex: sizeof(CInt) // Changed to CInt
)
let geo = SCNGeometry(sources: [src], elements: [ele])
let nd = SCNNode(geometry: geo)
scene.rootNode.addChildNode(nd)
有了这个结果:
【讨论】:
嗨,大卫,感谢您的“Swift”回复。你是绝对正确的,奇怪的是,我曾经使用 CShort 并将 NSData 长度硬编码为 6。但即使这样现在似乎也有效。我想我可能已经将唯一的 Light 从 Omni 更改为 Ambient,但这并没有帮助。 嘿大卫,我发布了一个关于顶点和 ARKit 的有趣问题(有赏金)。你介意看看吗? ***.com/questions/63662318/…我们好像没找到解决办法啊【参考方案2】:大卫的回答还不错,但是要呈现一个更完整的画面……
结构体的 Swift 数组将其数据打包在一起,就像 C 结构体数组一样,而不是 NSValue
包装的结构体的 ObjC NSArray
。还有you can pass a Swift array to (Obj)C APIs that take a CMutablePointer
or similar,所以从 Swift 数组构造 NSData
非常酷。
结构数组/结构体也是如此,只要它们最终都归结为标量类型。
即使在 ObjC 中,创建 SCNVector3
的数组也可能存在问题,因为该结构的元素类型在 32/64 位架构之间会发生变化。
sizeOfValue
对于 Beta 2 中的数组似乎没有按预期工作,所以我推荐filing a bug。
如果您在自定义结构中实现ArrayLiteralConvertible
协议,则可以简洁地声明嵌套结构值的数组。
如果您意识到这些问题,您可以在 Swift 中使用与 (Obj)C 中相同的顶点/索引缓冲区管理技巧,但有一些变化。例如,这里有一个 sn-p,它使用交错的顶点和法线数据构建自定义几何体(这有利于 iOS 设备 GPU 上的性能):
struct Float3 : ArrayLiteralConvertible
typealias Element = GLfloat
var x, y, z: GLfloat
init(arrayLiteral elements: Element...)
self.x = elements[0]
self.y = elements[1]
self.z = elements[2]
struct Vertex: ArrayLiteralConvertible
typealias Element = Float3
var position, normal: Float3
init(arrayLiteral elements: Element...)
self.position = elements[0]
self.normal = elements[1]
// This must be a var, not a let, because it gets passed to a CMutablePointer
var vertices: Vertex[] = [
[ [+0.5, -0.5, -0.5], [+1.0, +0.0, +0.0] ],
[ [+0.5, +0.5, -0.5], [+1.0, +0.0, +0.0] ],
// ... lots more vertices here!
]
let data = NSData(bytes: vertices, length: vertices.count * sizeof(Vertex))
let vertexSource = SCNGeometrySource(data: data,
semantic: SCNGeometrySourceSemanticVertex,
vectorCount: vertices.count,
floatComponents: true,
componentsPerVector: 3,
bytesPerComponent: sizeof(GLfloat),
dataOffset: 0,
dataStride: sizeof(Vertex))
let normalSource = SCNGeometrySource(data: data,
semantic: SCNGeometrySourceSemanticNormal,
vectorCount: vertices.count,
floatComponents: true,
componentsPerVector: 3,
bytesPerComponent: sizeof(GLfloat),
// no offsetof() in Swift, but Vertex has one Float3 before normal
dataOffset: sizeof(Float3),
dataStride: sizeof(Vertex))
// use a Swift Range to quickly construct a sequential index buffer
let indexData = NSData(bytes: Array<UInt8>(0..<UInt8(vertices.count)),
length: vertices.count * sizeof(UInt8))
let element = SCNGeometryElement(data: indexData,
primitiveType: .Triangles,
primitiveCount: vertices.count / 3,
bytesPerIndex: sizeof(UInt8))
let cube = SCNGeometry(sources: [vertexSource, normalSource], elements: [element])
【讨论】:
大小值已在 iOS 8 beta 3 中修复。 数组语法和语义也发生了一些变化——我会在有时间检查代码时更新这个答案。 对于let element = SCNGeometryElement(data: indexData, ..
,如果索引数据只是[0,1,2,3,4,etc],可以说let element = SCNGeometryElement(data: nil, ..
。我必须假设 bytesPerIndex
在这种用法中被忽略了。
@rickster 但是您将如何制作 INTERLEAVED indexData(由 Collada 输出)?据我在 SceneKit 中看到的,这是不可能的。【参考方案3】:
我对 swift 了解不多,但我希望您的“verts”和“indexes”版本在 swift 和 ObjC(实际上是 C)中完全不同。
在您的 C 版本中,您会得到一个 C 结构的 C 数组。在 swift 中,我假设你得到一个包装到 NSValues 中的 SCNVector3 的快速“数组”(相当于 NSArray)。因此,您使用“indexes”字节构建的 NSData 不包含像 C 中那样的浮点值的平面缓冲区。“verts”数组也是如此。
看起来像这样的 C 和 swift 互操作性问题在这里讨论:Swift use c struct
【讨论】:
以上是关于iOS 上 Swift 中的自定义 SceneKit 几何图形不起作用,但等效的 Objective C 代码可以的主要内容,如果未能解决你的问题,请参考以下文章
如何在swift ios中的自定义TableView单元格中显示多个字符串值到多个标签?