【问题标题】:Extracting vertices from scenekit从场景包中提取顶点
【发布时间】:2013-06-19 11:39:23
【问题描述】:

我在理解场景套件几何时遇到了问题。

我有 Blender 的默认立方体,我导出为 collada (DAE),并且可以将它带入 scenekit.... 一切都很好。

现在我想查看立方体的顶点。在 DAE 中,我可以看到“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”

现在我想在 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

【问题讨论】:

  • 嗨,Darren,我想知道你是否有最终代码,也许是在 Swift 中,它只是获取几何的点位置,没有额外的数据等。也许在 Swift 中?我目前需要能够为 Face AR Geometry 缓冲区执行此操作。我希望我知道更多的代码知识,但想法是每秒 60 次获取 iPhone X 的 Face geo,并将其保存到一个高效的大缓存数据中。在 3D 中它会是 Alembic,但不确定应用程序。但无论如何,如果你有获取几何点位置的功能,我可以从那里继续。干杯。

标签: collada scenekit


【解决方案1】:

几何源

当您调用 geometrySourcesForSemantic: 时,您将返回一个 SCNGeometrySource 对象数组,在您的情况下具有给定语义的顶点数据源。

此数据可能以多种不同方式编码,并且多个来源可以使用具有不同strideoffset 的相同数据。源本身有很多属性让您能够解码data,例如

  • dataStride
  • dataOffset
  • vectorCount
  • componentsPerVector
  • bytesPerComponent

您可以使用这些组合来确定要读取data 的哪些部分并从中制作顶点。

解码

步幅告诉您应该步进多少字节才能到达下一个向量,偏移量告诉您在到达该向量的相关数据部分之前应该从该向量的开始偏移多少字节。您应该为每个向量读取的字节数是 componentsPerVector * 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:获取不同的元素。

该元素可以告诉您顶点是使用单个三角形还是三角形条带。它还告诉您每个索引的字节数(索引可能是 ints 或 shorts,这对于解码其 data 是必要的。

【讨论】:

  • 嗨,谢谢你,我正在使用“SCNGeometrySourceSemanticVertex”,所以我以为我只会从对“geometrySourcesForSemantic”的调用中收到“顶点”数据。但我得到了额外的数据(据我所知)。我还使用以下代码来确认您建议的数据类型: NSInteger vStride = [vertex dataStride]; NSInteger vOffset = [顶点数据偏移]; NSInteger vBytesPerComponent = [顶点 bytesPerComponent]; bool areVertexFloats = [顶点浮动组件];
  • @Darren 我添加了代码来解析源中的矢量数据。希望它可以帮助您从文件中读取必要的数据。
  • 嗨,谢谢,这很好,你能解释一下为什么我有 36 个向量,而在 collada 中我有 24 个顶点。我在 Apple 文档中读到 SCNGeometrySourceSemanticVertex 返回一个 3x3 向量。但是不太明白这两者的关系,re为什么scenekit中有更多的顶点。谢谢
  • 这里有一些附加信息,如果我这样做与 Normals 提供的答案相同,并将数据打包为 VBO(如果这是正确的术语)。然后当我在其他地方使用数据时,一切都搞砸了,我认为这可能只是我的打包代码,所以我从上面获取代码,即 vertexSources,然后我将数据移到其他地方(iPhone 应用程序),并启用了顶点和法线从数据来看,一切都很好(即法线也在工作)。所以这告诉我已经以某种方式包含了正常数据(这很好,解决了我的问题:)),但最好理解
  • 还有纹理坐标。对我来说,有时数据似乎都正确打包。至少我现在正在看的那个是。这意味着在内存中它是 x,y,z,w, nx,ny,nz, texcoordx,texcoordy。但是,当您指定每个语义时,它会提供步幅和偏移量以正确解决这些问题。它已经交错了。通过执行上述代码(每个语义的时间),您基本上可以对它进行去交错。然后,您可以按照您的 iOS 应用程序的 3d 引擎所需的任何安排交错重新打包它。
【解决方案2】:

如果数据不连续(矢量大小不等于步幅),这是一种扩展方法,从 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
        }
    }
}

【讨论】:

    【解决方案3】:

    斯威夫特版本

    Objective-C 版本和 this 基本相同。

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

    【讨论】:

      【解决方案4】:

      // 调用这个函数 _ = vertices(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[]
      }
      

      【讨论】:

        【解决方案5】:

        这是一个 Swift 5.3 版本,基于其他答案,它还支持与 4 不同的 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
                    }
                }
            }
        }
        

        【讨论】:

        • 这太棒了!真的很好,而且是最新的答案。如果要获取UVMap,只需交换CGPointSCNVector3,忽略最后一个vectorData[2]
        【解决方案6】:

        使用 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 上(只有苹果好做 smth 简单所以痛苦)。所以我不得不调整 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 []
        }
        

        【讨论】:

        • 这似乎工作最简单,但从我的几何来源来看,似乎有一些额外的数据,而不仅仅是顶点。我还需要做其他事情吗?谢谢!
        • 那么代码期望你的数据是一个浮点流......检查你实际上有什么
        • 好吧,我得到了这个应用程序的源代码,它为我正在做的 Face AR 执行这个“顶点位置获取”。也许有点相关,但无论如何:github.com/elishahung/FaceCaptureX?files=1
        • 这不适用于所有几何图形,它似乎没有考虑顶点的步幅和偏移量。所以几何中顶点的字节大小可能不统一。
        【解决方案7】:

        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
            }
        

        【讨论】:

        • 这是一种通用方法,因为它考虑了步幅和偏移量。这里的其他一些答案没有,所以它们可能在某些情况下有效,而不是在其他情况下。
        【解决方案8】:

        对于像我这样想从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);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-04-28
          • 2023-03-12
          • 2016-11-05
          • 1970-01-01
          • 1970-01-01
          • 2020-03-20
          • 1970-01-01
          相关资源
          最近更新 更多