Нарисуйте линию с помощью SceneKit, используя Swift на iOS: линия нарисована, но не видна?

Продолжение моего предыдущего вопроса в Objective-C здесь (в частности, я пытаюсь перевести ответ, который я дал с помощью Objective-C, в Swift):

Рисование линии между двумя точками с помощью SceneKit

Проблема: Статистика SceneKit показывает, что линия рисуется, но не появляется на экране.

Минимальный пример:

  • Запустите новый игровой проект в Xcode, используя Swift и SceneKit.
  • Remove boilerplate:
    • let scene = SCNScene() instead of SCNScene(named: "art.scnassets/ship.dae")!
    • закомментируйте let ship = ... и ship.runAction(...)
  • Измените scnView.backgroundColor = UIColor.blackColor() на scnView.backgroundColor = UIColor.grayColor() (сначала я думал, что линии были просто черными на черном фоне, поэтому соответственно изменил цвет фона)
  • Добавьте следующий код:

    var positions: [SCNVector3] = [SCNVector3Make(0.0,0.0,0.0),SCNVector3Make(10.0,10.0,10.0)]
    
    // using Swift's Int gives an error with unsupported "SceneKit: error, C3DRendererContextBindMeshElement unsupported byte per index (8)"
    // when constructing element: SCNGeometryElement below, so switched to CInt
    var indicies: [CInt] = [0,1]
    
    // for whatever reason, the following doesn't work on iOS:
    // let vertexSource = SCNGeometrySource(vertices: &positions, count: positions.count)
    // see https://stackoverflow.com/questions/26890739/how-to-solve-scenekit-double-not-supported-error
    // so using more complex SCNGeometrySource() initializer instead
    
    let vertexData = NSData(bytes: &positions, length: positions.count)
    let vertexSource = SCNGeometrySource(data: vertexData, semantic: SCNGeometrySourceSemanticVertex,
    vectorCount: positions.count, floatComponents: true, componentsPerVector: 3,
    bytesPerComponent: sizeof(Float), dataOffset: 0, dataStride: sizeof(SCNVector3))
    
    let indexData = NSData(bytes: &indicies, length: indicies.count)
    let element = SCNGeometryElement(data: indexData, primitiveType: SCNGeometryPrimitiveType.Line,
    primitiveCount: indicies.count, bytesPerIndex: sizeof(CInt))
    
    let lines = SCNGeometry(sources: [vertexSource], elements: [element])
    
    let cellNode = SCNNode(geometry: lines)
    scene.rootNode.addChildNode(cellNode)
    

Как уже упоминалось, статистика SceneKit предполагает, что линия прорисовывается, но она не появляется, и нет ошибок компиляции или времени выполнения, что немного усложняет отслеживание.

Изменить: использование инструмента «Анализ» графического процессора в навигаторе отладки дало следующие ошибки:

Draw call exceeded element array buffer bounds

Draw call exceeded array buffer bounds

в glDrawElements(GL_LINES, 4, GL_UNSIGNED_INT, NULL) что предполагает, что что-то не так с моей конструкцией SCNGeometryElement?


person Matthew    schedule 15.01.2015    source источник


Ответы (1)


У меня не было возможности отладить код вживую, но, скорее всего, проблема в этих строках:

let vertexData = NSData(bytes: &positions, length: positions.count)
// ...
let indexData = NSData(bytes: &indicies, length: indicies.count)

Длина ваших данных — это количество записей, умноженное на размер (в байтах) каждой записи. Каждый элемент в positions представляет собой SCNVector3, что соответствует трем Float в iOS, поэтому каждая позиция равна 3 * sizeof(Float) или 12 байтам. Каждый элемент в indicies (sic) представляет собой CInt, который, как мне кажется, является 32-битным целым числом. Таким образом, ваши буферы данных намного короче, чем заявляют ваши конструкторы SCNGeometrySource и SCNGeometryElement, а это означает, что во время рендеринга SceneKit просит OpenGL совершить долгий прогулку от короткого пирса. удар > буфер.

Как правило, если вы создаете NSData из массива, вы хотите, чтобы параметр length был числом массивов, умноженным на sizeof тип элемента массива.

person rickster    schedule 15.01.2015
comment
Я могу подтвердить, что это работает, спасибо (и спасибо, что поймали мою опечатку). Я также могу подтвердить, что код теперь работает со Swift Int вместо CInt; ошибка, которую я получал раньше с Swift Int, должна была быть связана с моим неправильным использованием NSData. Из интереса, знаете ли вы, почему NSData нужно указывать длину явно, а не просто использовать весь буфер? Кроме того, знаете ли вы, почему в Objective-C можно было указать sizeof(indices) напрямую, а в Swift это невозможно ([Int] is not convertible to 'T.Type')? Еще раз спасибо за помощь. - person Matthew; 16.01.2015
comment
А, скоро я говорил о Int против CInt. Линия рисуется с помощью indices: [Int], а затем let indexData = NSData(bytes: &indices, length: sizeof(Int)*indices.count), но только если в строке let element = SCNGeometryElement(...) есть bytesPerIndex: sizeof(CInt) и она не работает с bytesPerIndex: sizeof(Int). Поскольку я считаю, что лучше быть последовательным, я буду придерживаться indices: [CInt] в своем коде, хотя indices: [Int] теперь технически работает... - person Matthew; 16.01.2015
comment
NSData должна быть задана явная длина, потому что вы передаете ему произвольный пакет байтов, и ему нужно знать, насколько велик этот пакет. В Swift NSData теоретически может вывести длину из размера массива, который вы ему дали (потому что массивы Swift знают свой собственный размер)... но в настоящее время инициализаторы Swift NSData просто импортируются из версий ObjC, которым нужно чтобы узнать длину, потому что массивы C не знают своего собственного размера. - person rickster; 16.01.2015
comment
В C sizeof — это директива компилятора, которая может принимать либо имя типа, либо значение. В Swift sizeof принимает тип, а sizeofValue принимает значение... однако sizeofValue просто возвращает размер указателя, когда вы даете ему массив, поэтому вместо этого вам следует передать sizeof(MyType) * arrayOfMyType.count, если вы хотите получить общую длину данных массива. - person rickster; 16.01.2015
comment
Кроме того, в зависимости от того, насколько велик ваш массив indices, вы можете рассмотреть меньший целочисленный тип. Вы действительно вставляете в этот буфер 2 ^ 31 индекса? Я не знаю, оптимизирует ли SceneKit это для вас перед отправкой на графический процессор. - person rickster; 16.01.2015
comment
Мой массив индексов не велик, он просто используется для рисования довольно простого каркаса, но я также могу использовать меньший целочисленный тип (нет причин не делать этого). Спасибо за совет. - person Matthew; 16.01.2015
comment
Что касается проблемы со Swift Int, я предполагаю, что это связано с тем, что на самом деле это Int64 на моем устройстве, но 8 bytesPerIndex не поддерживается SCNGeometryElement. Если бы я дал bytesPerIndex: sizeof(CInt), это технически «работало», но я полагаю, что он читал неправильные целые числа из indexData: Int, поскольку ожидал прочитать Int32s, а я на самом деле дал ему Int64s по ошибке... Теперь я все равно явно использую Int8 везде, и работает корректно. - person Matthew; 16.01.2015