SceneKit – Drawing a line between two points

21,288

Solution 1

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.

Solution 2

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

    }
}

Solution 3

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

Solution 4

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

Solution 5

Here is a swift5 version:

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
}
Share:
21,288

Related videos on Youtube

Matthew
Author by

Matthew

Updated on April 30, 2022

Comments

  • Matthew
    Matthew about 2 years

    I have two points (let's call them pointA and pointB) of type SCNVector3. I want to draw a line between them. Seems like it should be easy, but can't find a way to do it.

    I see two options, both have issues:

    • Use a SCNCylinder with a small radius, with length |pointA-pointB| and then position it/rotate it.

    • Use a custom SCNGeometry but not sure how; would have to define two triangles to form a very thin rectangle perhaps?

    It seems like there should be an easier way of doing this, but I can't seem to find one.

    Edit: Using the triangle method gives me this for drawing a line between (0,0,0) and (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];
    

    But there are problems: due to the normals, you can only see this line from one side! It's invisible from the other side. Also, if "delta" is too small you can't see the line at all. As it is, it's technically a rectangle, rather than the line I was going for, which might result in small graphical glitches if I want to draw multiple joined up lines.

    • A J
      A J about 10 years
    • David Rönnqvist
      David Rönnqvist about 10 years
      Hurray! A SceneKit question!
    • Matthew
      Matthew about 10 years
      Seriously, SceneKit is so great I have no idea why more people aren't using it. This is my first ever Objective-C project/app (though my second attempt at doing it) and SceneKit is making things so easy for me given that I have absolutely no OpenGL background. Hopefully it'll get ported to iOS eventually and it'll see more use...
    • David Rönnqvist
      David Rönnqvist about 10 years
      Aw man! I didn't even have time to get home and answer the question :(
    • μολὼν.λαβέ
      μολὼν.λαβέ almost 9 years
      damn, all i want to do is draw a 3D line from point A to point B.
  • Matthew
    Matthew about 10 years
    SCNGeometryPrimitiveTypeLine was exactly what I was looking for! Thanks. Don't know how I missed it. For anyone else looking this up, I've added the new code below. The tip on a 1.0 unit wide cylinder and then scaling the node is also really useful.
  • coneybeare
    coneybeare over 9 years
    This is a 3d line. Notice the z coordinates in the positions.
  • Dodgson86
    Dodgson86 over 9 years
    Hi, i'm intrested in the "cylinder" solution or, even better, the shape solution. I'm able to create cylinders with length equal to the distance between points, but i can't orientate them. Any clue about how to get the orientation between two points?
  • rickster
    rickster over 9 years
    "Orientation between two points" is effectively the same thing as "I'm standing here and I want to look over there" — construct a look-at matrix and use that as the line node's transform. Of course, the look-at direction is along the local z-axis, and an SCNCylinder goes along the local y-axis, so you'll need an extra bit for your transform — maybe turn the cylinder's pivot so it goes along the local z-axis.
  • zumzum
    zumzum over 9 years
    How can we set the color of the line? I tried: line.firstMaterial?.diffuse.contents = UIColor.orangeColor() but didn't work.
  • TheCodingArt
    TheCodingArt over 9 years
    The above method relies on OpenGL drawing calls. I'll update the above to reflect your questions
  • TheCodingArt
    TheCodingArt over 9 years
    I've reflected changing the line width because that's what I knew off the top of my head. I'm sure there's a way to bind a color to the openGL context for line drawing though. You would just call that there.
  • Gabriel Garrett
    Gabriel Garrett almost 8 years
    You think it'd be possible to adjust line thickness from within this function?
  • Jovan Stankovic
    Jovan Stankovic almost 8 years
    Line thickness makes sense in 2D space, but not so much in 3D.You could draw multiple lines between points that are close to each other, but you would end up with something that resembles a plane, or a cylinder. Easier way is to use SCNPlane or SCNCylinder objects to achieve this.
  • Chandan Shetty SP
    Chandan Shetty SP over 7 years
    Is there any way to provide different thickness for each line, for example if i have 10 lines each of different thickness? I looked a lot, one way is to write own custom drawing logic in render node method? is there any other easy way?
  • Wil Shipley
    Wil Shipley almost 7 years
    You can adjust line thickness in OpenGL but it’s ignored on iOS (and works for only some values on macOS). I don’t think there’s a line thickness call in Metal (but I might be wrong).
  • Lim Thye Chean
    Lim Thye Chean almost 7 years
    I hope there is a function that has a thicker line though.
  • toto_tata
    toto_tata over 6 years
    How to you change the line thickness in Obj C ?
  • dev
    dev over 6 years
    glLineWidth(20) doesnt work for some reason. any insights why ? @TheCodingArt
  • khunshan
    khunshan over 6 years
    @SonuVR because iOS now uses Metal instead of OpenGL for SceneKit.
  • khunshan
    khunshan over 6 years
    How to draw multiple lines and fill them?
  • khunshan
    khunshan over 6 years
    Transforming SCNShape is nightmare.
  • Andrew Zimmer
    Andrew Zimmer over 5 years
    You can't adjust thickness in Metal, but if you are using OpenGL as a backing layer you can us glLineWidth(5.0); to set your line thickness by pixel thickness.
  • Dharman
    Dharman over 4 years
    This does not provide an answer to the question. You can search for similar questions, or refer to the related and linked questions on the right-hand side of the page to find an answer. If you have a related but different question, ask a new question, and include a link to this one to help provide context. See: Ask questions, get answers, no distractions
  • zakdances
    zakdances over 4 years
    Is it intentional that the depth of the rectangle increases as it's length increases?
  • zakdances
    zakdances over 4 years
    edit I replaced SCNNode.localUp with SCNVector3(x: 0, y: 0, z: 1) seems to make this work correctly for some reason. Not sure why.
  • Softlion
    Softlion over 3 years
    It does answer the question "Drawing a line between two points using SceneKit". So it's fine.
  • The Way
    The Way about 3 years
    Two errors in Xcode 12.3 let dir = (endPoint - startPoint).normalized(). <-- "'Binary operator '-' cannot be applied to two 'SCNVector3' operands'". AND return self / length() <-- "Binary operator '/' cannot be applied to operands of type 'SCNVector3' and 'Float'"
  • Silvering
    Silvering about 3 years