1

I have a binary file that contains many triples of Float values; it is actually a long array of SCNVector3 representing the vertices for a SCNGeometrySource. I read the file (excuse the omission of do-try-catch, etc) into memory:

let sceneVectors = Data(contentsOf: sceneURL)

and want to pass that in memory data as a [SCNVector3] along to SceneKit:

let sceneGeometry = SCNGeometrySource(vertices: sceneVectors)

I've been wrestling today with using the UnsafePointer mechanism to re-type the contents of Data buffer as an array of SCNVector3 to satisfy the SCNGeometrySource contract; something like this.

dataBuffer.withUnsafeBytes { 
    (vertexBuffer: UnsafePointer<SCNVector3>) -> SCNGeometry in
        « magic happens here »
        return sceneGeometry
}

My reading about this tells me that, within the closure, the buffer can be accessed as an array of SCNVector3, but when passed to SCNGeometrySource it causes Swift to report:

cannot convert value of type 'UnsafePointer<SCNVector3>' to expected argument type '[SCNVector3]'

As usual, I suspect there is a simple solution that I've not figured out yet, so I would appreciate any suggestion for resolving this.

Ramsay Consulting
  • 593
  • 1
  • 5
  • 14

1 Answers1

2

In SCNGeometrySource(vertices: sceneVectors) you have to pass an array of vectors, not a pointer:

let sceneGeometry = dataBuffer.withUnsafeBytes {
    (vertexBuffer: UnsafePointer<SCNVector3>) -> SCNGeometrySource in
    let sceneVectors = Array(UnsafeBufferPointer(start: vertexBuffer, count: dataBuffer.count/MemoryLayout<SCNVector3>.stride))
    return SCNGeometrySource(vertices: sceneVectors)
}

In Swift 3 (but not in Swift 4 anymore) SCNGeometrySource has another initializer which takes a pointer to SCNVector3 and a count:

let sceneGeometry = dataBuffer.withUnsafeBytes {
    (vertexBuffer: UnsafePointer<SCNVector3>) -> SCNGeometrySource in
    return SCNGeometrySource(vertices: vertexBuffer, count: dataBuffer.count/MemoryLayout<SCNVector3>.stride)
}

which can be shorted to

let sceneGeometry = dataBuffer.withUnsafeBytes {
    SCNGeometrySource(vertices: $0, count: dataBuffer.count/MemoryLayout<SCNVector3>.stride)
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Elegantly described, thanks. I'll note that the initializer which takes a pointer to `SCNVector3` and a count is not available in Swift 4. The error is "error: 'init(vertices:count:)' is unavailable in Swift: Use init(vertices:) instead" .. – Ramsay Consulting Jun 14 '17 at 12:24
  • I should note, for future readers, that the very first sentence of original post contains an error. I implied `SCNVector3` is a triple of `Float` when it actually is a triple of `Double`. My file actually does contains `Float` values (using 64-bit values seemed a tad excessive for SceneKit vector) so I still have some work to do, however Martin correctly answered the question I asked. – Ramsay Consulting Jun 14 '17 at 13:17
  • @RamsayConsulting: According to https://developer.apple.com/documentation/scenekit/scnvector3 it is CGFloat on the Mac and Float on iOS. – Martin R Jun 14 '17 at 13:19
  • Either way they are 64-bit values on 64-bit CPUs. My file would work out the box with your code, on a 32-bit CPU .. https://developer.apple.com/documentation/coregraphics/cgfloat – Ramsay Consulting Jun 14 '17 at 13:29
  • Of course, if I want to avoid a buffer copy, I can [must?] revert to the rather ghastly `SCNGeometrySource(data: semantic: vectorCount: usesFloatComponents: componentsPerVector: bytesPerComponent: dataOffset: dataStride: )` usage. – Ramsay Consulting Jun 14 '17 at 13:39
  • @RamsayConsulting: That sounds sensible – I admit that I answered the question but know almost nothing about SceneKit and SCNGeometrySource :) – Martin R Jun 14 '17 at 13:48