Пользовательская геометрия SceneKit в Swift на iOS не работает, но эквивалент Objective C code does

Я очень хочу принять новый язык Swift, поскольку, похоже, это путь продвижения Apple. Я также был впечатлен новой поддержкой SceneKit в iOS 8. Я хотел бы программно создавать пользовательскую геометрию во время выполнения, но я изо всех сил пытаюсь заставить код Swift работать. Однако эквивалентный код в Objective C работает нормально.

Это может быть ошибка, или что-то, что я делаю неправильно.

Я просто пытаюсь создать и отобразить один треугольник. Для этого я буду игнорировать нормали и текстуры и т.д. Поэтому я ожидаю увидеть только черный треугольник.

Swift Code (не работает)

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

Любые указатели будут оценены.

Ответ 1

Ну, две части кода не переводятся точно друг к другу. int в C не совпадает с int в Swift. Фактически это называется CInt в Swift:

/// 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)

С помощью этого результата:

enter image description here

Ответ 2

Ответ Дэвида довольно хорош, но чтобы представить более полную картину...

  • Сводный массив structs объединяет свои данные как массив C структур, а не ObjC NSArray из NSValue -обломанных структур. И вы можете передать массив Swift в (Obj) C API, которые принимают CMutablePointer или аналогичные, поэтому полностью охлаждать конструкцию NSData из массива Swift.

  • То же самое относится к массивам /structs структур, если в конце концов они все возвращаются к скалярным типам.

  • Даже в ObjC создание массива SCNVector3 может быть проблематичным, поскольку тип элемента этой структуры изменяется между 32/64-разрядными архитектурами.

  • sizeOfValue, похоже, не работает как ожидалось для массивов в бета-версии 2, поэтому я рекомендую подавать ошибку.

  • Если вы реализуете протокол ArrayLiteralConvertible в своих настраиваемых структурах, вы можете кратко объявить массивы вложенных структурных значений.

Если вы знаете об этих проблемах, вы можете использовать в Swift, с некоторыми вариациями, большинство из тех же трюков для управления буфером вершин/указателей, что и в (Obj) C. Например, здесь приведен фрагмент, который создает пользовательскую геометрию с чередующимися вершинами и нормальными данными (что хорошо для производительности на графических устройствах устройств iOS):

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])

Ответ 3

Я не знаю много о быстром, но я бы ожидал, что ваши версии "verts" и "indexes" будут полностью разными в swift и ObjC (на самом деле C).

В вашей версии C вы получаете C-массив из C-структур. В swift я предполагаю, что вы получаете быстрый "массив" (эквивалентный NSArray) SCNVector3, завернутый в NSValues. поэтому NSData, которые вы создаете с байтами "индексов", не содержит плоского буфера значений float, как в C. То же самое для массива "verts".

Похоже, что такие C и проблемы с оперативной совместимостью обсуждаются здесь: Swift use c struct