xcode iOS 上的 Swift 中的自定义 SceneKit Geometry 不起作用,但等效的 Objective C 代码可以

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24493273/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 05:09:12  来源:igfitidea点击:

Custom SceneKit Geometry in Swift on iOS not working but equivalent Objective C code does

iosobjective-cxcodeswiftscenekit

提问by BassetMan

I am keen to adopt the new Swift language as this seems to be the way forward with Apple development. I have also been impressed with the new SceneKit support in iOS 8. I would like to programatically create custom geometry at runtime but I am struggling to get the Swift code to work. Yet the equivalent code in Objective C works OK.

我热衷于采用新的 Swift 语言,因为这似乎是 Apple 开发的前进方向。iOS 8 中新的 SceneKit 支持也给我留下了深刻的印象。我想在运行时以编程方式创建自定义几何体,但我正在努力让 Swift 代码工作。然而,Objective C 中的等效代码可以正常工作。

This could be a bug, or something I am doing wrong.

这可能是一个错误,或者我做错了什么。

I am simply trying to create and render a single triangle. I will ignore the normals and textures etc at this point for simplicity. So I am only expecting to see a black triangle.

我只是想创建和渲染一个三角形。为简单起见,此时我将忽略法线和纹理等。所以我只期待看到一个黑色的三角形。

Swift Code (not working)

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 Code (which does work)

目标 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];

Any pointers would be appreciated.

任何指针将不胜感激。

采纳答案by David R?nnqvist

Well, the two pieces of code doesn't translate exactlyto one another. The intin C is not the same as Intin Swift. It's actually called CIntin Swift:

好吧,这两段代码并不能完全相互转换。该int在C是不一样Int的斯威夫特。它实际上是CInt在 Swift 中调用的:

/// The C 'int' type.
typealias CInt = Int32

If you change both occurrences to use CIntinstead, the error message that you previously got goes away (at least for me in an OS X Playground. However, it still doesn't render anything for me.

如果您将两个事件都改为使用CInt,您之前收到的错误消息就会消失(至少对我在 OS X Playground 中是这样。但是,它仍然没有为我呈现任何内容。

I don't think sizeofValueis used to return the size of an array. It looks to me like it's returning the size of the pointer:

我不认为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

So, for me the following code works (I've put the arguments on different lines to make it easier to point out my changes):

因此,对我来说,以下代码有效(我将参数放在不同的行上,以便更容易指出我的更改):

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)

With this result:

有了这个结果:

enter image description here

在此处输入图片说明

回答by rickster

David's answer is pretty good, but to present a more complete picture...

大卫的回答很好,但要呈现更完整的画面......

  • A Swift array of structs packs its data together like a C array of structs, not an ObjC NSArrayof NSValue-wrapped structs. And you can pass a Swift array to (Obj)C APIs that take a CMutablePointeror similar, so it's totally cool to construct an NSDatafrom a Swift array.

  • The same goes for arrays of / structs of structs, as long as they all get down to scalar types in the end.

  • Even in ObjC, making an array of SCNVector3can be problematic, because the element type of that struct changes between 32/64-bit architectures.

  • sizeOfValuedoesn't seem to be working as expected for arrays in Beta 2, so I'd recommend filing a bug.

  • If you implement the ArrayLiteralConvertibleprotocol in your custom structs, you can declare arrays of nested struct values concisely.

  • Swift 结构数组将其数据打包在一起,就像 C 结构数组一样,而不是封装结构NSArrayNSValueObjC。并且您可以将 Swift 数组传递给采用 aCMutablePointer或类似 的(Obj)C API,因此NSData从 Swift 数组构造 an 非常酷。

  • 结构体的 / 结构体数组也是如此,只要它们最终都归结为标量类型。

  • 即使在 ObjC 中,创建一个数组SCNVector3也可能有问题,因为该结构的元素类型在 32/64 位体系结构之间发生变化。

  • sizeOfValueBeta 2 中的数组似乎没有按预期工作,所以我建议提交一个 bug

  • 如果ArrayLiteralConvertible在自定义结构中实现协议,则可以简洁地声明嵌套结构值的数组。

If you're aware of these issues, you can use in Swift, with some variation, most of the same tricks for vertex/index buffer management that you would in (Obj)C. For example, here's a snippet that builds a custom geometry with interleaved vertex and normal data (which is good for performance on iOS device GPUs):

如果您意识到这些问题,您可以在 Swift 中使用与 (Obj)C 中相同的顶点/索引缓冲区管理技巧,但有一些变化。例如,这里有一个片段,它使用交错的顶点和法线数据构建自定义几何体(这对 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])

回答by Toyos

I don't know a lot about swift but I would expect your versions of "verts" and "indexes" to be completely different in swift and ObjC (actually C).

我对 swift 了解不多,但我希望你的“verts”和“indexes”版本在 swift 和 ObjC(实际上是 C)中完全不同。

In your C version you get a C array of C structures. In swift I assume you get a swift "array" (equivalent to NSArray) of SCNVector3 wrapped into NSValues. so the NSData you build with the "indexes" bytes doesn't contain a flat buffer of float values like in C. Same for the "verts" array.

在你的 C 版本中,你会得到一个 C 结构的 C 数组。在 swift 中,我假设你得到了一个包含在 NSValues 中的 SCNVector3 的快速“数组”(相当于 NSArray)。因此,您使用“索引”字节构建的 NSData 不包含像 C 中那样的浮点值的平面缓冲区。“verts”数组也是如此。

Looks like such C and swift interoperability issues are discussed here: Swift use c struct

看起来这里讨论了这样的 C 和 swift 互操作性问题:Swift use c struct