使用SceneKit在两点之间画一条线

Mat*_*hew 21 opengl macos cocoa objective-c scenekit

我有两个类型SCNVector3的点(我们称之为pointA和pointB).我想在它们之间画一条线.看起来应该很容易,但找不到办法.

我看到两个选项,都有问题:

使用半径非常小的SCNCylinder,长度为| pointA-pointB | 然后定位/旋转它.

使用自定义SCNGeometry但不确定如何; 或许必须定义两个三角形才能形成一个非常薄的矩形?

似乎应该有一种更简单的方法来做到这一点,但我似乎无法找到一个.

编辑:使用三角形方法给我这个在(0,0,0)和(10,10,10)之间画一条线:

CGFloat delta = 0.1;
SCNVector3 positions[] = {  SCNVector3Make(0,0,0),
    SCNVector3Make(10, 10, 10),
    SCNVector3Make(0+delta, 0+delta, 0+delta),
    SCNVector3Make(10+delta, 10+delta, 10+delta)};
int indicies[] = {
    0,2,1,
    1,2,3
};

SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:4];
NSData *indexData = [NSData dataWithBytes:indicies length:sizeof(indicies)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:2 bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:@[vertexSource] elements:@[element]];

SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
Run Code Online (Sandbox Code Playgroud)

但是有一些问题:由于法线,你只能从一边看到这条线!它从另一边看不见.此外,如果"delta"太小,则根本看不到该线.实际上,它在技术上是一个矩形,而不是我想要的线,如果我想绘制多个连接线,可能会导致小的图形故障.

Jov*_*vic 18

这是Swift中的一个简单扩展:

extension SCNGeometry {
    class func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry {
        let indices: [Int32] = [0, 1]

        let source = SCNGeometrySource(vertices: [vector1, vector2])
        let element = SCNGeometryElement(indices: indices, primitiveType: .Line)

        return SCNGeometry(sources: [source], elements: [element])

    }
}
Run Code Online (Sandbox Code Playgroud)


ric*_*ter 14

有很多方法可以做到这一点.

如上所述,您的自定义几何方法有一些缺点.你应该能够通过给予物质doubleSided属性来纠正它从一侧看不见的问题.不过,你仍然可能会遇到二维问题.

您还可以修改自定义几何体以包含更多三角形,这样您就可以获得具有三个或更多边的管形状,而不是平面矩形.或者在几何体源中只有两个点,并使用SCNGeometryPrimitiveTypeLine几何元素类型让Scene Kit在它们之间绘制线段.(尽管使用阴影多边形渲染样式时,您将无法获得足够的灵活性.)

您还可以使用SCNCylinder您提到的方法(或任何其他内置原始形状).请记住,几何是在它们自己的局部(也称为Model)坐标空间中定义的,Scene Kit相对于节点定义的坐标空间进行解释.换句话说,您可以在所有尺寸中定义一个1.0(单位宽)的圆柱体(或盒子或胶囊或平面或其他),然后使用SCNNode包含该几何体的旋转/缩放/位置或变换使其变长,变薄和在你想要的两点之间伸展.(另请注意,由于您的线条非常薄,因此可以减少segmentCount您正在使用的内置几何体的s,因为很多细节将不可见.)

另一个选项是SCNShape允许您从2DBézier路径创建拉伸3D对象的类.计算出正确的变换以获得连接两个任意点的平面听起来像一些有趣的数学,但是一旦你这样做,你可以轻松地将你的点与你选择的任何形状的线连接起来.


Mat*_*hew 9

下面的行(0,0,0)到(10,10,10)的新代码.我不确定它是否可以进一步改进.

SCNVector3 positions[] = {
    SCNVector3Make(0.0, 0.0, 0.0),
    SCNVector3Make(10.0, 10.0, 10.0)
};

int indices[] = {0, 1};

SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions
                                                                          count:2];

NSData *indexData = [NSData dataWithBytes:indices
                                   length:sizeof(indices)];

SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData
                                                            primitiveType:SCNGeometryPrimitiveTypeLine
                                                           primitiveCount:1
                                                            bytesPerIndex:sizeof(int)];

SCNGeometry *line = [SCNGeometry geometryWithSources:@[vertexSource]
                                            elements:@[element]];

SCNNode *lineNode = [SCNNode nodeWithGeometry:line];

[root addChildNode:lineNode];
Run Code Online (Sandbox Code Playgroud)


The*_*Art 9

这是一个解决方案

class func lineBetweenNodeA(nodeA: SCNNode, nodeB: SCNNode) -> SCNNode {
    let positions: [Float32] = [nodeA.position.x, nodeA.position.y, nodeA.position.z, nodeB.position.x, nodeB.position.y, nodeB.position.z]
    let positionData = NSData(bytes: positions, length: MemoryLayout<Float32>.size*positions.count)
    let indices: [Int32] = [0, 1]
    let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)

    let source = SCNGeometrySource(data: positionData as Data, semantic: SCNGeometrySource.Semantic.vertex, vectorCount: indices.count, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout<Float32>.size, dataOffset: 0, dataStride: MemoryLayout<Float32>.size * 3)
    let element = SCNGeometryElement(data: indexData as Data, primitiveType: SCNGeometryPrimitiveType.line, primitiveCount: indices.count, bytesPerIndex: MemoryLayout<Int32>.size)

    let line = SCNGeometry(sources: [source], elements: [element])
    return SCNNode(geometry: line)
}
Run Code Online (Sandbox Code Playgroud)

如果您想更新线宽或与修改绘制线的属性相关的任何内容,您将需要在SceneKit的渲染回调中使用其中一个openGL调用:

func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
    //Makes the lines thicker
    glLineWidth(20)
}
Run Code Online (Sandbox Code Playgroud)

  • 我们如何设置线条的颜色呢?我尝试过: line.firstMaterial?.diffuse.contents = UIColor.orangeColor() 但没有用。 (2认同)
  • @SonuVR因为iOS现在使用Metal而不是OpenGL用于SceneKit. (2认同)

sil*_*erz 8

这是一个 swift5 版本:

func lineBetweenNodes(positionA: SCNVector3, positionB: SCNVector3, inScene: SCNScene) -> SCNNode {
    let vector = SCNVector3(positionA.x - positionB.x, positionA.y - positionB.y, positionA.z - positionB.z)
    let distance = sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
    let midPosition = SCNVector3 (x:(positionA.x + positionB.x) / 2, y:(positionA.y + positionB.y) / 2, z:(positionA.z + positionB.z) / 2)

    let lineGeometry = SCNCylinder()
    lineGeometry.radius = 0.05
    lineGeometry.height = distance
    lineGeometry.radialSegmentCount = 5
    lineGeometry.firstMaterial!.diffuse.contents = GREEN

    let lineNode = SCNNode(geometry: lineGeometry)
    lineNode.position = midPosition
    lineNode.look (at: positionB, up: inScene.rootNode.worldUp, localFront: lineNode.worldUp)
    return lineNode
}
Run Code Online (Sandbox Code Playgroud)

  • 它确实回答了“使用 SceneKit 在两点之间画一条线”的问题。所以没关系。 (3认同)