warrenm / GLTFKit2

A glTF 2.0 asset loader and exporter for Objective-C and Swift.
MIT License
150 stars 26 forks source link

How to access vertex of geometry? #25

Closed tapmyads-dev1 closed 1 year ago

tapmyads-dev1 commented 1 year ago

We are trying to access all vertex positions of geometry to show Highlight point on them could you share example of how to assess geometry vertex positions

warrenm commented 1 year ago

As GLTFKit2 is a fairly low-level framework, this is tedious but ultimately straightforward. One approach is to declare an Objective-C category on the GLTFPrimitive class that returns packed position data:

@interface GLTFPrimitive (VertexPositionAccess)
- (NSData *_Nullable)copyPackedVertexPositions;
@end

which could be implemented as follows

@implementation GLTFPrimitive (VertexPositionAccess)

- (NSData *)copyPackedVertexPositions {
    GLTFAccessor *positionAccessor = self.attributes[GLTFAttributeSemanticPosition];
    if (positionAccessor == nil) {
        return nil; // No position data, because the primitive didn't contain a position accessor
    }
    const void *sourceBufferBase = positionAccessor.bufferView.buffer.data.bytes;
    const void *sourceBase = sourceBufferBase + positionAccessor.offset + positionAccessor.bufferView.offset;
    if (sourceBase == NULL) {
        return nil; // No position data, because buffer view or buffer is NULL, or because the buffer contains no data
    }
    NSInteger vertexCount = positionAccessor.count;
    NSInteger destinationStride = sizeof(float) * 3;
    NSInteger bufferLength = destinationStride * vertexCount;
    NSInteger sourceStride = positionAccessor.bufferView.stride;
    if (sourceStride == 0) {
        sourceStride = destinationStride;
    }
    if (sourceStride == destinationStride) {
        // Existing data is already packed; just return a data object that acts as a view on it
        return [NSData dataWithBytesNoCopy:(void *)sourceBase length:bufferLength freeWhenDone:NO];
    } else {
        // Existing data is *not* packed; we need to copy it element by element
        void *packedBuffer = malloc(bufferLength);
        for (int i = 0; i < vertexCount; ++i) {
            const void *sourcePtr = sourceBase + i * sourceStride;
            void *destPtr = packedBuffer + i * destinationStride;
            memcpy(destPtr, sourcePtr, destinationStride);
        }
        return [NSData dataWithBytesNoCopy:packedBuffer length:bufferLength freeWhenDone:YES];
    }
}

@end

Back in Swift, assuming you have a reference to a primitive object, you could print out all of its vertices like this:

let vertexCount = primitive.attributes[GLTFAttributeSemantic.position.rawValue]?.count ?? 0
if let positionData = primitive.copyPackedVertexPositions() {
    positionData.withUnsafeBytes { positionPtr in
        for i in 0..<vertexCount {
            let position = positionPtr.baseAddress!
                .advanced(by: MemoryLayout<Float>.stride * 3 * i)
                .assumingMemoryBound(to: Float.self)
            let x = position[0]
            let y = position[1]
            let z = position[2]
            print("\(x) \(y) \(z)")
        }
    }
}

If you need to iterate over triangles in indexed order instead of just the raw vertex list, you can write a similar extension to retrieve the index data from the primitive. This is slightly more complex, because indices can be 8, 16, or 32 bits each, but at least index data is guaranteed to be packed.

warrenm commented 1 year ago

If you've already converted a glTF asset to a SceneKit scene, you can instead retrieve the vertex source of an SCNGeometry using the sources(for:) method, then iterate its data instead, making sure to account for the stride between vertices.

warrenm commented 1 year ago

This issue will be automatically closed in 7 days if no further comment is received.

warrenm commented 1 year ago

Closing due to inactivity.

adellari commented 1 month ago

Is this still the best way to get vertex position data?