Skip to content

Instantly share code, notes, and snippets.

@GrantMeStrength
Last active March 17, 2024 12:19
Show Gist options
  • Save GrantMeStrength/62364f8a5d7ea26e2b97b37207459a10 to your computer and use it in GitHub Desktop.
Save GrantMeStrength/62364f8a5d7ea26e2b97b37207459a10 to your computer and use it in GitHub Desktop.
Drawing a line between two points in SceneKit / iOS / Swift
func line(from p1: SCNVector3, to p2: SCNVector3) -> SCNNode? {
// Draw a line between two points and return it as a node
var indices = [Int32(0), Int32(1)]
let positions = [p1, p2]
let vertexSource = SCNGeometrySource(vertices: positions)
let indexData = Data(bytes: &indices, count:MemoryLayout<Int32>.size * indices.count)
let element = SCNGeometryElement(data: indexData, primitiveType: .line, primitiveCount: 1, bytesPerIndex: MemoryLayout<Int32>.size)
let line = SCNGeometry(sources: [vertexSource], elements: [element])
let lineNode = SCNNode(geometry: line)
return lineNode
}
// Draws a (very thin) line between two points.
// Not sure how to set the color though - materials didn't seem to do much
@GrantMeStrength
Copy link
Author

GrantMeStrength commented Dec 30, 2018

    func line(from : SCNVector3, to : SCNVector3, width : Int, color : UIColor) -> SCNNode? {
        let vector = to - from,
        length = vector.length()
        
        let cylinder = SCNCylinder(radius: 1, height: CGFloat(length))
        cylinder.radialSegmentCount = width
        cylinder.firstMaterial?.diffuse.contents = color
        
        let node = SCNNode(geometry: cylinder)
        
        node.position = (to + from) / 2
        node.eulerAngles = SCNVector3Make(Float(Double.pi/2), acos((to.z-from.z)/length), atan2((to.y-from.y), (to.x-from.x) ))
        
        return node
    }

// Better, but needs work on materials to make sure it's properly visible no matter how it's rotated etc.

@GrantMeStrength
Copy link
Author

GrantMeStrength commented Jan 6, 2019

Thanks to an article here:

https://www.invasivecode.com/weblog/scenekit-tutorial-part-2/

which explains how to define geometries, I've got a MUCH MUCH MUCH better line drawing function working. Here it is.

 func generateLine( startPoint: SCNVector3, endPoint: SCNVector3) -> SCNGeometry {
        
        let vertices: [SCNVector3] = [startPoint, endPoint]
        let data = NSData(bytes: vertices, length: MemoryLayout<SCNVector3>.size * vertices.count) as Data
        
        let vertexSource = SCNGeometrySource(data: data,
                                             semantic: .vertex,
                                             vectorCount: vertices.count,
                                             usesFloatComponents: true,
                                             componentsPerVector: 3,
                                             bytesPerComponent: MemoryLayout<Float>.size,
                                             dataOffset: 0,
                                             dataStride: MemoryLayout<SCNVector3>.stride)
        
        let indices: [Int32] = [ 0, 1]
        
        let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count) as Data
        
        let element = SCNGeometryElement(data: indexData,
                                         primitiveType: .line,
                                         primitiveCount: indices.count/2,
                                         bytesPerIndex: MemoryLayout<Int32>.size)
        
        return SCNGeometry(sources: [vertexSource], elements: [element])
        
    }

and you would use it thus:

        let line = generateLine(startPoint: SCNVector3Make(1, 1, 1), endPoint: SCNVector3Make(8, 8, 8))
        let lineNode = SCNNode(geometry: line)
        lineNode.position = SCNVector3Make(15, 15, 10)
        scene.rootNode.addChildNode(lineNode)

The thickness of the line requires implementing the SCNSceneRendererDelegate, in particular:

    func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
            glLineWidth(10)
    }

As openGL is being deprecated in iOS, I'm not sure if this way of changing the line thickness is future-proof, but what is?

See the article linked to above for ideas of defining shapes - not just lines - and changing color.

@GrantMeStrength
Copy link
Author

The article referenced colors, but it didn't work as the default lightingModel value was incorrect. Here's how I added color support to the line drawing function:

func line(startPoint: SCNVector3, endPoint: SCNVector3, color : UIColor) -> SCNNode
{
    let vertices: [SCNVector3] = [startPoint, endPoint]
    let data = NSData(bytes: vertices, length: MemoryLayout<SCNVector3>.size * vertices.count) as Data
    
    let vertexSource = SCNGeometrySource(data: data,
                                         semantic: .vertex,
                                         vectorCount: vertices.count,
                                         usesFloatComponents: true,
                                         componentsPerVector: 3,
                                         bytesPerComponent: MemoryLayout<Float>.size,
                                         dataOffset: 0,
                                         dataStride: MemoryLayout<SCNVector3>.stride)
    
    
    let indices: [Int32] = [ 0, 1]
    
    let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count) as Data
    
    let element = SCNGeometryElement(data: indexData,
                                     primitiveType: .line,
                                     primitiveCount: indices.count/2,
                                     bytesPerIndex: MemoryLayout<Int32>.size)
    
    let line = SCNGeometry(sources: [vertexSource], elements: [element])
    
    line.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
    line.firstMaterial?.diffuse.contents = color
    
    let lineNode = SCNNode(geometry: line)
    return lineNode;
}

@shouhulee
Copy link

Thanks to your approach. It's very helpful.
But glLineWidth(10) can't work. It's deprecated.
Do you konw what should I do if I want to increase the line's width?

@atrbx5
Copy link

atrbx5 commented May 20, 2019

Have faced this issue few years ago and still not find a solution that fit visual requirements:
-have fixed on screen width (e.g. 2px or 3px)
-line width independant to perspective camera transformation

@Nirajpaul2
Copy link

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.002
        lineGeometry.height = CGFloat(distance)
        lineGeometry.radialSegmentCount = 5
        lineGeometry.firstMaterial!.diffuse.contents = UIColor.green

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

How to use:

 let node = lineBetweenNodes(positionA: start.position, positionB: end.position, inScene: sceneView.scene)
     sceneView.scene.rootNode.addChildNode(node)

@bialylis
Copy link

bialylis commented Mar 9, 2020

I have developed a simple project that allows drawing any width and color line through multiple points. It also supports loops and mittered (sharp) joins. https://github.com/bialylis/ThickRedLine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment