Drawing a line between two points using SceneKit

懵懂的女人 提交于 2019-11-27 18:50:17

There are lots of ways to do this.

As noted, your custom geometry approach has some disadvantages. You should be able to correct the problem of it being invisible from one side by giving its material the doubleSided property. You still may have issues with it being two-dimensional, though.

You could also modify your custom geometry to include more triangles, so you get a tube shape with three or more sides instead of a flat rectangle. Or just have two points in your geometry source, and use the SCNGeometryPrimitiveTypeLine geometry element type to have Scene Kit draw a line segment between them. (Though you won't get as much flexibility in rendering styles with line drawing as with shaded polygons.)

You can also use the SCNCylinder approach you mentioned (or any of the other built-in primitive shapes). Remember that geometries are defined in their own local (aka Model) coordinate space, which Scene Kit interprets relative to the coordinate space defined by a node. In other words, you can define a cylinder (or box or capsule or plane or whatever) that's 1.0 units wide in all dimensions, then use the rotation/scale/position or transform of the SCNNode containing that geometry to make it long, thin, and stretching between the two points you want. (Also note that since your line is going to be pretty thin, you can reduce the segmentCounts of whichever built-in geometry you're using, because that much detail won't be visible.)

Yet another option is the SCNShape class that lets you create an extruded 3D object from a 2D Bézier path. Working out the right transform to get a plane connecting two arbitrary points sounds like some fun math, but once you do it you could easily connect your points with any shape of line you choose.

Jovan Stankovic

Here's a simple extension in 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])

    }
}
Matthew

New code for a line from (0, 0, 0) to (10, 10, 10) below. I'm not sure if it could be improved further.

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];

Here's one solution

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

if you would like to update the line width or anything related to modifying properties of the drawn line, you'll want to use one of the openGL calls in SceneKit's rendering callback:

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

So inside your ViewController.cs define your vector points and call a Draw function, then on the last line there - it's just rotating it to look at point 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);

Create a static CreateGeomery class and put this static method in there

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

you may also want these utility methods in a static helper class

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

For all my Xamarin c# homies out there.

Here's a solution using triangles that works independent of the direction of the line. It's constructed using the cross product to get points perpendicular to the line. So you'll need a small SCNVector3 extension, but it'll probably come in handy in other cases, too.

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 extension:

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)
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!