opengl SceneKit -在两点之间绘制一条线

z8dt9xmd  于 2022-11-04  发布在  其他
关注(0)|答案(8)|浏览(157)

我有两个SCNVector 3类型的点(我们称它们为pointA和pointB)。我想在它们之间画一条线。看起来应该很容易,但找不到方法。
我看到两个选项,都有问题:

  • 使用半径小、长度|A点-B点|然后将其定位/旋转。
  • 使用自定义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];

但也有问题:由于法线的原因,你只能从一边看到这条线!从另一边看不见它。另外,如果“delta”太小,你根本看不到这条线。实际上,它是一个矩形,而不是我想要的线,如果我想画多条连接起来的线,这可能会导致小的图形故障。

rmbxnbpk

rmbxnbpk1#

下面是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])

    }
}
aurhwmvo

aurhwmvo2#

有很多方法可以做到这一点。
如前所述,您的自定义几何图形方法有一些缺点。您应该可以通过为其材质赋予doubleSided属性来纠正从一侧不可见的问题。但是,您可能仍然会遇到二维几何图形的问题。
你也可以修改你的自定义几何图形来包含更多的三角形,这样你就得到了一个有三个或更多边的管状图形,而不是一个扁平的矩形。或者在你的几何图形源中只有两个点,并使用SCNGeometryPrimitiveTypeLine几何元素类型让Scene Kit在它们之间绘制一条线段。(尽管你在渲染风格上不会像着色多边形那样灵活。)
您还可以使用您提到的SCNCylinder方法(或任何其他内建基本形体)。请记住,几何图形是在它们自己的本机(也称为模型)坐标空间,场景工具包解释它相对于节点定义的坐标空间。换句话说,您可以定义一个圆柱体(或者盒子、胶囊、平面等等),所有维度的宽度都是1.0个单位,然后使用包含该几何体的SCNNode的旋转/缩放/位置或变换,使其变长、变细并在两个点之间拉伸。(还要注意,由于线条将非常细,因此可以减少所使用的内置几何体的segmentCount,因为太多细节将不可见。)
另一个选项是SCNShape类,它可以让你从二维贝塞尔曲线创建一个拉伸的三维对象。计算出正确的变换来得到一个连接两个任意点的平面听起来像是一些有趣的数学运算,但是一旦你做了,你就可以很容易地把你的点连接到你选择的任何形状的线上。

xdyibdwo

xdyibdwo3#

下面是从(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];
tvmytwxo

tvmytwxo4#

这里有一个解决方案

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

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

func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
    //Makes the lines thicker
    glLineWidth(20)
}
lo8azlld

lo8azlld5#

下面是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
}
wqlqzqxt

wqlqzqxt6#

在ViewController.cs中定义矢量点并调用Draw函数,然后在最后一行上-它只是旋转它以查看点B。

var a = someVector3;
       var b = someOtherVector3;
       nfloat cLength = (nfloat)Vector3Helper.DistanceBetweenPoints(a, b);
       var cyclinderLine = CreateGeometry.DrawCylinderBetweenPoints(a, b, cLength, 0.05f, 10);
       ARView.Scene.RootNode.Add(cyclinderLine);
       cyclinderLine.Look(b, ARView.Scene.RootNode.WorldUp, cyclinderLine.WorldUp);

创建一个静态CreateGeomery类,并将此静态方法放入其中

public static SCNNode DrawCylinderBetweenPoints(SCNVector3 a,SCNVector3 b, nfloat length, nfloat radius, int radialSegments){

         SCNNode cylinderNode;
         SCNCylinder cylinder = new SCNCylinder();
         cylinder.Radius = radius;
         cylinder.Height = length;
         cylinder.RadialSegmentCount = radialSegments;
         cylinderNode = SCNNode.FromGeometry(cylinder);
         cylinderNode.Position = Vector3Helper.GetMidpoint(a,b);

         return cylinderNode;
        }

您可能还希望将这些实用工具方法放在静态帮助器类中

public static double DistanceBetweenPoints(SCNVector3 a, SCNVector3 b)
        {
         SCNVector3 vector = new SCNVector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
         return Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z);
        }

    public static SCNVector3 GetMidpoint(SCNVector3 a, SCNVector3 b){

        float x = (a.X + b.X) / 2;
        float y = (a.Y + b.Y) / 2;
        float z = (a.Z + b.Z) / 2;

        return new SCNVector3(x, y, z);
    }

感谢我所有的Xamarin c#朋友们。

6ss1mwsb

6ss1mwsb7#

这里有一个使用三角形的解决方案,它独立于直线的方向工作。它是使用叉积来构造的,以获得垂直于直线的点。因此,你需要一个小的SCNVector3扩展,但它可能在其他情况下也会派上用场。

private func makeRect(startPoint: SCNVector3, endPoint: SCNVector3, width: Float ) -> SCNGeometry {
    let dir = (endPoint - startPoint).normalized()
    let perp = dir.cross(SCNNode.localUp) * width / 2

    let firstPoint = startPoint + perp
    let secondPoint = startPoint - perp
    let thirdPoint = endPoint + perp
    let fourthPoint = endPoint - perp
    let points = [firstPoint, secondPoint, thirdPoint, fourthPoint]

    let indices: [UInt16] = [
        1,0,2,
        1,2,3
    ]
    let geoSource = SCNGeometrySource(vertices: points)
    let geoElement = SCNGeometryElement(indices: indices, primitiveType: .triangles)

    let geo = SCNGeometry(sources: [geoSource], elements: [geoElement])
    geo.firstMaterial?.diffuse.contents = UIColor.blue.cgColor
    return geo
}

SCNVector3扩展名:

import Foundation
import SceneKit

extension SCNVector3
{
    /**
     * Returns the length (magnitude) of the vector described by the SCNVector3
     */
    func length() -> Float {
        return sqrtf(x*x + y*y + z*z)
    }

    /**
     * Normalizes the vector described by the SCNVector3 to length 1.0 and returns
     * the result as a new SCNVector3.
     */
    func normalized() -> SCNVector3 {
        return self / length()
    }

    /**
      * Calculates the cross product between two SCNVector3.
      */
    func cross(_ vector: SCNVector3) -> SCNVector3 {
        return SCNVector3(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x)
    }
}
pkmbmrz7

pkmbmrz78#

快速版本

要生成具有特定位置和方向的圆柱体形式的线,让我们在SCNGeometry扩展中实现一个cylinderLine()类方法。这里最难的部分是三角函数(用于定义圆柱体的方向)。如下所示:

import SceneKit

extension SCNGeometry {

    class func cylinderLine(from: SCNVector3, to: SCNVector3,
                                              segments: Int = 5) -> SCNNode {
        let x1 = from.x; let x2 = to.x
        let y1 = from.y; let y2 = to.y
        let z1 = from.z; let z2 = to.z

        let subExpr01 = Float((x2-x1) * (x2-x1))
        let subExpr02 = Float((y2-y1) * (y2-y1))
        let subExpr03 = Float((z2-z1) * (z2-z1))

        let distance = CGFloat(sqrtf(subExpr01 + subExpr02 + subExpr03))

        let cylinder = SCNCylinder(radius: 0.005, height: CGFloat(distance))
        cylinder.radialSegmentCount = segments
        cylinder.firstMaterial?.diffuse.contents = NSColor.systemYellow

        let lineNode = SCNNode(geometry: cylinder)

        lineNode.position = SCNVector3((x1+x2)/2, (y1+y2)/2, (z1+z2)/2)

        lineNode.eulerAngles = SCNVector3(x: CGFloat.pi / 2,
                                      y: acos((to.z-from.z)/CGFloat(distance)),
                                      z: atan2((to.y-from.y), (to.x-from.x)))
        return lineNode
    }
}

剩下的就简单了。

class ViewController: NSViewController {

    @IBOutlet var sceneView: SCNView!
    let scene = SCNScene()
    var startingPoint: SCNVector3!
    var endingPoint: SCNVector3!

    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.scene = scene
        sceneView.backgroundColor = NSColor.black
        sceneView.allowsCameraControl = true                
        self.startingPoint = SCNVector3Zero
        self.endingPoint = SCNVector3(1,1,1)
        self.lineInBetween()
    }   
    func lineInBetween() {
        self.addSphereDot(position: startingPoint)
        self.addSphereDot(position: endingPoint)
        self.addLine(start: startingPoint, end: endingPoint)
    }   
    func addSphereDot(position: SCNVector3) {
        let sphere = SCNSphere(radius: 0.03)
        sphere.firstMaterial?.diffuse.contents = NSColor.red
        let node = SCNNode(geometry: sphere)
        node.position = position
        scene.rootNode.addChildNode(node)
    }   
    func addLine(start: SCNVector3, end: SCNVector3) {
        let lineNode = SCNGeometry.cylinderLine(from: start, to: end)
        scene.rootNode.addChildNode(lineNode)
    }
}

相关问题