SceneKit – Drawing a line between two points
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 segmentCount
s 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
}
Related videos on Youtube
Matthew
Updated on April 30, 2022Comments
-
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 about 10 yearsThis may help you: ronnqvi.st/custom-scenekit-geometry
-
David Rönnqvist about 10 yearsHurray! A SceneKit question!
-
Matthew about 10 yearsSeriously, 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 about 10 yearsAw man! I didn't even have time to get home and answer the question :(
-
μολὼν.λαβέ almost 9 yearsdamn, all i want to do is draw a 3D line from point A to point B.
-
Matthew about 10 yearsSCNGeometryPrimitiveTypeLine 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 over 9 yearsThis is a 3d line. Notice the z coordinates in the positions.
-
Dodgson86 over 9 yearsHi, 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 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'spivot
so it goes along the local z-axis. -
zumzum over 9 yearsHow can we set the color of the line? I tried: line.firstMaterial?.diffuse.contents = UIColor.orangeColor() but didn't work.
-
TheCodingArt over 9 yearsThe above method relies on OpenGL drawing calls. I'll update the above to reflect your questions
-
TheCodingArt over 9 yearsI'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 almost 8 yearsYou think it'd be possible to adjust line thickness from within this function?
-
Jovan Stankovic almost 8 yearsLine 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 over 7 yearsIs 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 almost 7 yearsYou 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 almost 7 yearsI hope there is a function that has a thicker line though.
-
toto_tata over 6 yearsHow to you change the line thickness in Obj C ?
-
dev over 6 yearsglLineWidth(20) doesnt work for some reason. any insights why ? @TheCodingArt
-
khunshan over 6 years@SonuVR because iOS now uses Metal instead of OpenGL for SceneKit.
-
khunshan over 6 yearsHow to draw multiple lines and fill them?
-
khunshan over 6 yearsTransforming SCNShape is nightmare.
-
Andrew Zimmer over 5 yearsYou 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 over 4 yearsThis 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 over 4 yearsIs it intentional that the depth of the rectangle increases as it's length increases?
-
zakdances over 4 yearsedit I replaced
SCNNode.localUp
withSCNVector3(x: 0, y: 0, z: 1)
seems to make this work correctly for some reason. Not sure why. -
Softlion over 3 yearsIt does answer the question "Drawing a line between two points using SceneKit". So it's fine.
-
The Way about 3 yearsTwo errors in Xcode 12.3
let dir = (endPoint - startPoint).normalized()
. <-- "'Binary operator '-' cannot be applied to two 'SCNVector3' operands'". ANDreturn self / length()
<-- "Binary operator '/' cannot be applied to operands of type 'SCNVector3' and 'Float'" -
Silvering about 3 years