'Extracting vertices from scenekit

I'm having a problem with understanding scenekit geometery.

I have the default cube from Blender, and I export as collada (DAE), and can bring it into scenekit.... all good.

Now I want to see the vertices for the cube. In the DAE I can see the following for the "Cube-mesh-positions-array",

"1 1 -1 1 -1 -1 -1 -0.9999998 -1 -0.9999997 1 -1 1 0.9999995 1 0.9999994 -1.000001 1 -1 -0.9999997 1 -1 1 1"

Now what I'd like to do in scenekit, is get the vertices back, using something like the following:

SCNGeometrySource *vertexBuffer = [[cubeNode.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex] objectAtIndex:0];

If I process the vertexBuffer (I've tried numerous methods of looking at the data), it doesn't seem correct.

Can somebody explain what "SCNGeometrySourceSemanticVertex" is giving me, and how to extract the vertex data properly? What I'd like to see is:

X = "float"
Y = "float"
Z = "float"

Also I was investigating the following class / methods, which looked promising (some good data values here), but the data from gmpe appears empty, is anybody able to explain what the data property of "SCNGeometryElement" contains?

SCNGeometryElement *gmpe = [theCurrentNode.geometry geometryElementAtIndex:0];

Thanks, assistance much appreciated,

D



Solution 1:[1]

Here is an extension method if the data isn't contiguous (the vector size isn't equal to the stride) which can be the case when the geometry is loaded from a DAE file. It also doesn't use copyByte function.

extension  SCNGeometry{


    /**
     Get the vertices (3d points coordinates) of the geometry.

     - returns: An array of SCNVector3 containing the vertices of the geometry.
     */
    func vertices() -> [SCNVector3]? {

        let sources = self.sources(for: .vertex)

        guard let source  = sources.first else{return nil}

        let stride = source.dataStride / source.bytesPerComponent
        let offset = source.dataOffset / source.bytesPerComponent
        let vectorCount = source.vectorCount

        return source.data.withUnsafeBytes { (buffer : UnsafePointer<Float>) -> [SCNVector3] in

            var result = Array<SCNVector3>()
            for i in 0...vectorCount - 1 {
                let start = i * stride + offset
                let x = buffer[start]
                let y = buffer[start + 1]
                let z = buffer[start + 2]
                result.append(SCNVector3(x, y, z))
            }
            return result
        }
    }
}

Solution 2:[2]

The Swift Version

The Objective-C version and this are essentially identical.

let planeSources = _planeNode?.geometry?.geometrySourcesForSemantic(SCNGeometrySourceSemanticVertex)
if let planeSource = planeSources?.first {
    let stride = planeSource.dataStride
    let offset = planeSource.dataOffset
    let componentsPerVector = planeSource.componentsPerVector
    let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent

    let vectors = [SCNVector3](count: planeSource.vectorCount, repeatedValue: SCNVector3Zero)
    let vertices = vectors.enumerate().map({
        (index: Int, element: SCNVector3) -> SCNVector3 in
        var vectorData = [Float](count: componentsPerVector, repeatedValue: 0)
        let byteRange = NSMakeRange(index * stride + offset, bytesPerVector)
        planeSource.data.getBytes(&vectorData, range: byteRange)
        return SCNVector3Make(vectorData[0], vectorData[1], vectorData[2])
    })

    // You have your vertices, now what?
}

Solution 3:[3]

// call this function _ = vertices(node: mySceneView.scene!.rootNode) // I have get the volume in Swift 4.2 :--- this function

func vertices(node:SCNNode) -> [SCNVector3] {

    let planeSources1 = node.childNodes.first?.geometry

    let planeSources = planeSources1?.sources(for: SCNGeometrySource.Semantic.vertex)
    if let planeSource = planeSources?.first {

        let stride = planeSource.dataStride
        let offset = planeSource.dataOffset
        let componentsPerVector = planeSource.componentsPerVector
        let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent

        let vectors = [SCNVector3](repeating: SCNVector3Zero, count: planeSource.vectorCount)
        let vertices = vectors.enumerated().map({
            (index: Int, element: SCNVector3) -> SCNVector3 in
            let vectorData = UnsafeMutablePointer<Float>.allocate(capacity: componentsPerVector)
            let nsByteRange = NSMakeRange(index * stride + offset, bytesPerVector)
            let byteRange = Range(nsByteRange)

            let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
            planeSource.data.copyBytes(to: buffer, from: byteRange)
            return SCNVector3Make(buffer[0], buffer[1], buffer[2])
        })
        var totalVolume = Float()
        var x1 = Float(),x2 = Float(),x3 = Float(),y1 = Float(),y2 = Float(),y3 = Float(),z1 = Float(),z2 = Float(),z3 = Float()
        var i = 0
        while i < vertices.count{

                x1 = vertices[i].x;
                y1 = vertices[i].y;
                z1 = vertices[i].z;

                x2 = vertices[i + 1].x;
                y2 = vertices[i + 1].y;
                z2 = vertices[i + 1].z;

                x3 = vertices[i + 2].x;
                y3 = vertices[i + 2].y;
                z3 = vertices[i + 2].z;

                totalVolume +=
                    (-x3 * y2 * z1 +
                        x2 * y3 * z1 +
                        x3 * y1 * z2 -
                        x1 * y3 * z2 -
                        x2 * y1 * z3 +
                        x1 * y2 * z3);

                        i = i + 3
        }
        totalVolume = totalVolume / 6;
        volume = "\(totalVolume)"
        print("Volume Volume Volume Volume Volume Volume Volume :\(totalVolume)")
        lbl_valume.text = "\(clean(String(totalVolume))) cubic mm"
    }
    return[]
}

Solution 4:[4]

Here's a Swift 5.3 version, based on the other answers, and that also supports a bytesPerComponent different from 4 (untested for size different from 4 though):

extension SCNGeometrySource {
    var vertices: [SCNVector3] {
        let stride = self.dataStride
        let offset = self.dataOffset
        let componentsPerVector = self.componentsPerVector
        let bytesPerVector = componentsPerVector * self.bytesPerComponent

        func vectorFromData<FloatingPoint: BinaryFloatingPoint>(_ float: FloatingPoint.Type, index: Int) -> SCNVector3 {
            assert(bytesPerComponent == MemoryLayout<FloatingPoint>.size)
            let vectorData = UnsafeMutablePointer<FloatingPoint>.allocate(capacity: componentsPerVector)
            let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
            let rangeStart = index * stride + offset
            self.data.copyBytes(to: buffer, from: rangeStart..<(rangeStart + bytesPerVector))
            return SCNVector3(
                CGFloat.NativeType(vectorData[0]),
                CGFloat.NativeType(vectorData[1]),
                CGFloat.NativeType(vectorData[2])
            )
        }

        let vectors = [SCNVector3](repeating: SCNVector3Zero, count: self.vectorCount)
        return vectors.indices.map { index -> SCNVector3 in
            switch bytesPerComponent {
            case 4:
                return vectorFromData(Float32.self, index: index)
            case 8:
                return vectorFromData(Float64.self, index: index)
            case 16:
                return vectorFromData(Float80.self, index: index)
            default:
                return SCNVector3Zero
            }
        }
    }
}

Solution 5:[5]

With swift 3.1 you can extract vertices from SCNGeometry in a much faster and shorter way:

func vertices(node:SCNNode) -> [SCNVector3] {
    let vertexSources = node.geometry?.getGeometrySources(for: SCNGeometrySource.Semantic.vertex)
    if let vertexSource = vertexSources?.first {
        let count = vertexSource.data.count / MemoryLayout<SCNVector3>.size
        return vertexSource.data.withUnsafeBytes {
            [SCNVector3](UnsafeBufferPointer<SCNVector3>(start: $0, count: count))
        }
    }
    return []
}

... Today i've noted that on osx this not going to work correct. This happens because on iOS SCNVector3 build with Float and on osx CGFloat (only apple good do smth simple so suffering). So I had to tweak the code for osx but this not gonna work as fast as on iOS.

func vertices() -> [SCNVector3] {

        let vertexSources = sources(for: SCNGeometrySource.Semantic.vertex)


        if let vertexSource = vertexSources.first {

        let count = vertexSource.vectorCount * 3
        let values = vertexSource.data.withUnsafeBytes {
            [Float](UnsafeBufferPointer<Float>(start: $0, count: count))
        }

        var vectors = [SCNVector3]()
        for i in 0..<vertexSource.vectorCount {
            let offset = i * 3
            vectors.append(SCNVector3Make(
                CGFloat(values[offset]),
                CGFloat(values[offset + 1]),
                CGFloat(values[offset + 2])
            ))
        }

        return vectors
    }
    return []
}

Solution 6:[6]

The Swift 3 version:

    // `plane` is some kind of `SCNGeometry`

    let planeSources = plane.geometry.sources(for: SCNGeometrySource.Semantic.vertex)
    if let planeSource = planeSources.first {
        let stride = planeSource.dataStride
        let offset = planeSource.dataOffset
        let componentsPerVector = planeSource.componentsPerVector
        let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent

        let vectors = [SCNVector3](repeating: SCNVector3Zero, count: planeSource.vectorCount)
        let vertices = vectors.enumerated().map({
            (index: Int, element: SCNVector3) -> SCNVector3 in

            let vectorData = UnsafeMutablePointer<Float>.allocate(capacity: componentsPerVector)
            let nsByteRange = NSMakeRange(index * stride + offset, bytesPerVector)
            let byteRange = Range(nsByteRange)

            let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
                planeSource.data.copyBytes(to: buffer, from: byteRange)
            let vector = SCNVector3Make(buffer[0], buffer[1], buffer[2])
        })

        // Use `vertices` here: vertices[0].x, vertices[0].y, vertices[0].z
    }

Solution 7:[7]

For someone like me want to extract data of face from SCNGeometryElement.

Notice I only consider primtive type is triangle and index size is 2 or 4.

void extractInfoFromGeoElement(NSString* scenePath){
    NSURL *url = [NSURL fileURLWithPath:scenePath];
    SCNScene *scene = [SCNScene sceneWithURL:url options:nil error:nil];
    SCNGeometry *geo = scene.rootNode.childNodes.firstObject.geometry;
    SCNGeometryElement *elem = geo.geometryElements.firstObject;
    NSInteger componentOfPrimitive = (elem.primitiveType == SCNGeometryPrimitiveTypeTriangles) ? 3 : 0;
    if (!componentOfPrimitive) {//TODO: Code deals with triangle primitive only
        return;
    }
    for (int i=0; i<elem.primitiveCount; i++) {
        void *idxsPtr = NULL;
        int stride = 3*i;
        if (elem.bytesPerIndex == 2) {
            short *idxsShort = malloc(sizeof(short)*3);
            idxsPtr = idxsShort;
        }else if (elem.bytesPerIndex == 4){
            int *idxsInt = malloc(sizeof(int)*3);
            idxsPtr = idxsInt;
        }else{
            NSLog(@"unknow index type");
            return;
        }
        [elem.data getBytes:idxsPtr range:NSMakeRange(stride*elem.bytesPerIndex, elem.bytesPerIndex*3)];
        if (elem.bytesPerIndex == 2) {
            NSLog(@"triangle %d : %d, %d, %d\n",i,*(short*)idxsPtr,*((short*)idxsPtr+1),*((short*)idxsPtr+2));
        }else{
            NSLog(@"triangle %d : %d, %d, %d\n",i,*(int*)idxsPtr,*((int*)idxsPtr+1),*((int*)idxsPtr+2));
        }
        //Free
        free(idxsPtr);
    }
}

Solution 8:[8]

OK, here is another Swift 5.5 version based on Oliver's answer.

extension  SCNGeometry{


/**
 Get the vertices (3d points coordinates) of the geometry.

 - returns: An array of SCNVector3 containing the vertices of the geometry.
 */
func vertices() -> [SCNVector3]? {

    let sources = self.sources(for: .vertex)

    guard let source  = sources.first else{return nil}

    let stride = source.dataStride / source.bytesPerComponent
    let offset = source.dataOffset / source.bytesPerComponent
    let vectorCount = source.vectorCount

    return source.data.withUnsafeBytes { dataBytes in
                let buffer: UnsafePointer<Float> = dataBytes.baseAddress!.assumingMemoryBound(to: Float.self)
        var result = Array<SCNVector3>()
        for i in 0...vectorCount - 1 {
            let start = i * stride + offset
            let x = buffer[start] 
            let y = buffer[start + 1] 
            let z = buffer[start + 2] 
            result.append(SCNVector3(x, y, z))
        }
        return result

    }
    
}
}

To use it you simply create a standard shape from which you can extract the vertex and rebuild the index.

let g = SCNSphere(radius: 1)

    
let newNode = SCNNode(geometry: g)

    let vectors = newNode.geometry?.vertices()
    var indices:[Int32] = []
    
    for i in stride(from: 0, to: vectors!.count, by: 1) {
        indices.append(Int32(i))
        indices.append(Int32(i+1))
    }
    
    return self.createGeometry(
            vertices:vectors!, indices: indices,
                primitiveType: SCNGeometryPrimitiveType.line)

The createGeometry extension can be found here

It draws this...

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 oliver
Solution 2 Cameron Lowell Palmer
Solution 3
Solution 4 Stéphane Copin
Solution 5
Solution 6 tom
Solution 7
Solution 8 user3069232