Извлечение вершин из сцены

У меня проблема с пониманием геометрии сцены.

У меня есть куб по умолчанию от Blender, и я экспортирую его как коллада (DAE), и могу перенести его в набор сценариев.... все хорошо.

Теперь я хочу увидеть вершины для куба. В DAE я вижу следующее для "Cube-mesh-позиции-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"

Теперь, что я хотел бы сделать в scenekit, это вернуть вершины, используя что-то вроде следующего:

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

Если я обработаю vertexBuffer (я пробовал многочисленные методы просмотра данных), это не будет правильным.

Может кто-нибудь объяснить, что мне дает SCNGeometrySourceSemanticVertex и как правильно извлечь данные вершины? То, что я хотел бы видеть это:

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

Также я исследовал следующий класс / методы, которые выглядели многообещающе (некоторые хорошие значения данных здесь), но данные из gmpe кажутся пустыми, кто-нибудь может объяснить, что содержит свойство данных в "SCNGeometryElement"?

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

Спасибо, помощь высоко ценится,

D

9 ответов

Решение

Источник геометрии

Когда вы звоните geometrySourcesForSemantic: вам возвращают массив SCNGeometrySource объекты с заданной семантикой в ​​вашем случае источники данных вершин).

Эти данные могли быть закодированы разными способами, и несколько источников могут использовать одни и те же данные с разными stride а также offset, Сам источник имеет множество свойств, чтобы вы могли декодировать data как например

  • dataStride
  • dataOffset
  • vectorCount
  • componentsPerVector
  • bytesPerComponent

Вы можете использовать их комбинации, чтобы выяснить, какие части data читать и делать из них вершины.

расшифровка

Шаг показывает, сколько байтов нужно выполнить, чтобы перейти к следующему вектору, а смещение показывает, сколько байтов смещено от начала этого вектора, которое вы должны сместить, прежде чем перейти к соответствующим частям данных для этого вектора. Число байтов, которые вы должны прочитать для каждого вектора, это componentPerVector * bytesPerComponent

Код для считывания всех вершин для одного источника геометрии будет выглядеть примерно так

// Get the vertex sources
NSArray *vertexSources = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];

// Get the first source
SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources

NSInteger stride = vertexSource.dataStride; // in bytes
NSInteger offset = vertexSource.dataOffset; // in bytes

NSInteger componentsPerVector = vertexSource.componentsPerVector;
NSInteger bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
NSInteger vectorCount = vertexSource.vectorCount;

SCNVector3 vertices[vectorCount]; // A new array for vertices

// for each vector, read the bytes
for (NSInteger i=0; i<vectorCount; i++) {

    // Assuming that bytes per component is 4 (a float)
    // If it was 8 then it would be a double (aka CGFloat)
    float vectorData[componentsPerVector];

    // The range of bytes for this vector
    NSRange byteRange = NSMakeRange(i*stride + offset, // Start at current stride + offset
                                    bytesPerVector);   // and read the lenght of one vector

    // Read into the vector data buffer
    [vertexSource.data getBytes:&vectorData range:byteRange];

    // At this point you can read the data from the float array
    float x = vectorData[0];
    float y = vectorData[1];
    float z = vectorData[2];

    // ... Maybe even save it as an SCNVector3 for later use ...
    vertices[i] = SCNVector3Make(x, y, z);

    // ... or just log it 
    NSLog(@"x:%f, y:%f, z:%f", x, y, z);
}

Элемент геометрии

Это даст вам все вершины, но не расскажет, как они используются для построения геометрии. Для этого вам нужен элемент геометрии, который управляет индексами для вершин.

Вы можете получить количество геометрических элементов для части геометрии из geometryElementCount имущество. Тогда вы можете получить различные элементы, используя geometryElementAtIndex:,

Элемент может сказать вам, используются ли вершины отдельными треугольниками или полосой треугольника. Он также говорит вам байтов на индекс (индексы, возможно, были intс или shortы, которые будут необходимы для декодирования его data,

Вот метод расширения, если данные не являются смежными (размер вектора не равен шагу), который может иметь место при загрузке геометрии из файла DAE. Он также не использует функцию copyByte.

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
        }
    }
}

Версия Swift

Версия Objective-C и это, по сути, идентичны.

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?
}

Вот версия Swift 5.3, основанная на других ответах, и которая также поддерживает bytesPerComponent отличается от (не проверено на размер отличается от 4 хотя):

      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
            }
        }
    }
}

С swift 3.1 вы можете извлекать вершины из SCNGeometry намного быстрее и короче:

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 []
}

... Сегодня я заметил, что на OSX это не будет работать правильно. Это происходит потому, что на iOS сборка SCNVector3 с Float и на osx CGFloat (только яблоко хорошо делает что-то простое, так мучительно). Поэтому мне пришлось настроить код для OSX, но это не сработает так же быстро, как на 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 []
}

Хорошо, вот еще одна версия Swift 5.5, основанная на ответе user3861865

      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

    }
    
}
}

Чтобы использовать его, вы просто создаете стандартную форму, из которой вы можете извлечь вершину и перестроить индекс.

      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)

Расширение createGeometry можно найти здесь .

Рисует это...

// вызвать эту функцию _ = вершины (node: mySceneView.scene!.rootNode) // У меня есть объем в Swift 4.2:--- эта функция

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[]
}

Для таких, как я, хочу извлечь данные лица из SCNGeometryElement,

Обратите внимание, я считаю только примитивный тип треугольником, а размер индекса равен 2 или 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);
    }
}

Версия Swift 3:

    // `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
    }
Другие вопросы по тегам