9

I am trying to replicate this cube image shape (with permission from the original creator) using scene kit.

enter image description here

Thus far, I have the drawing code for the lines and the vertices. I cannot use an image because the background has to be transparent.

The specific I am trying to solve right now is how to edit the stroke width for the SCNGeometryPrimitiveType.Line element.

The basic way I am creating lines is like this:

private func squareVertices(length: Float) -> [SCNVector3] {
    let m = length/Float(2)

    let topLeft =       SCNVector3Make(-m-q,  m+q, m+q)
    let topRight =      SCNVector3Make( m+q,  m+q, m+q)
    let bottomLeft =    SCNVector3Make(-m-q, -m-q, m+q)
    let bottomRight =   SCNVector3Make( m+q, -m-q, m+q)

    return [topLeft, topRight, bottomLeft, bottomRight]
}

private func cubeFace() -> SCNGeometry {

    let vertices : [SCNVector3] = squareVertices(l)
    let geoSrc = SCNGeometrySource(vertices: UnsafePointer<SCNVector3>(vertices), count: vertices.count)

    // index buffer
    let idx1 : [Int32] = [0, 3]
    let data1 = NSData(bytes: idx1, length: (sizeof(Int32) * idx1.count))
    let geoElements1 = SCNGeometryElement(data: data1, primitiveType: SCNGeometryPrimitiveType.Line, primitiveCount: idx1.count, bytesPerIndex: sizeof(Int32))

    let idx2 : [Int32] = [1, 2]
    let data2 = NSData(bytes: idx2, length: (sizeof(Int32) * idx2.count))
    let geoElements2 = SCNGeometryElement(data: data2, primitiveType: SCNGeometryPrimitiveType.Line, primitiveCount: idx2.count, bytesPerIndex: sizeof(Int32))

    let geo = SCNGeometry(sources: [geoSrc], elements: [geoElements1, geoElements2])

    return geo
}

    private func setupFaceNodes() {
    // sides
    for i in 0..<4 {
        let face = SCNNode(geometry: cubeFace())
        face.rotation = SCNVector4Make(0, 1, 0, Float(i) * Float(M_PI_2))
        rootNode.addChildNode(face)
    }
    // top/bottom
    for i in [1, 3] {
        let face = SCNNode(geometry: cubeFace())
        face.rotation = SCNVector4Make(1, 0, 0, Float(i) * Float(M_PI_2))
        rootNode.addChildNode(face)
    }
}

I have something that looks like this with the correct overall shape:

enter image description here

but I can't figure out how to increase the width of the lines being drawn using SceneKit. How can I achieve this?

For those interested, here is a sample proj.

coneybeare
  • 33,113
  • 21
  • 131
  • 183

2 Answers2

9

SceneKit doesn't provide controls for this. However, SceneKit draws using OpenGL ES, which does.

When you're drawing with GL in the GL_LINES mode, the glLineWidth call changes the line width. (Watch out: the argument is in actual pixels, not UI-layout points, so you'll need a larger width than you might think if you don't want super-thin hairlines on a Retina display.)

So, where do you call that in your SceneKit app? You have a few options there. In a simple scene like yours, where you're only rendering one thing, you can just set it before the scene renders. Set a delegate for your view, then implement renderer:willRenderSceneAtTime: and call glLineWidth there.

However, OpenGL line rendering is pretty limited — if you want to customize rendering more, you'll need a different approach. Just which approach works best depends on exactly what you're going for, so here are a few ideas for you to research:

  • Make your "lines" from narrow triangle strips
  • Make them from primitive SCN geometries like boxes and cylinders
  • Keep a simple cube geometry, but use a fragment shader (or shader modifier snippet) to draw only near the edges of each polygon
rickster
  • 124,678
  • 26
  • 272
  • 326
  • "Keep a simple cube geometry, but use a fragment shader (or shader modifier snippet) to draw only near the edges of each polygon" How would you do this with a fragment shader? Isn't the problem of drawing wireframes with shaders more complicated to solve? [Wireframe shader](http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/) – tsp Nov 03 '14 at 21:39
  • It can be more complicated, but also allows for more flexibility—e.g. fuzzy/glowing edges. And depending on your needs, you don't need to pass another attribute (geometry source in SceneKit) down the pipeline for barycentric coords—if you want to highlight the edges of a quad, forget that it's actually two triangles and just look at the texcoords. (Assuming your texcoords are set up to follow the quad.) – rickster Nov 03 '14 at 22:26
  • I tried this approach, of adding the glLineWidth call in the delegate method, and it works on the simulator but has no effect on the device. Have you guys seen the same behavior? The strange thing is that if I use GLES directly instead of SceneKit I can get glLineWidth to work on the device with pretty thick lines. – eddy May 04 '16 at 18:10
  • 2
    @eddy: Is SceneKit even using the GL renderer on your device? `glLineWidth` does nothing for Metal (and there's no Metal equivalent). – rickster May 04 '16 at 19:01
  • glLineWidth also varies by device. I never had any luck with it on iOS, and switched away from it. – Wil Shipley Jun 27 '17 at 01:00
  • 1
    We can specify OpenGL in SceneKit like this `[[SCNView alloc] initWithFrame:CGRectZero options:@{SCNViewOptionPreferredRenderingAPI:@(SCNRenderingAPIOpenGLES2)}];` – CocoaBob Aug 24 '17 at 20:10
5

You can use glLineWidth to set the line width.
Make sure to include OpenGLES in your project.

Line Width 8

Here's how your cube looks like with line width set to 8.

tsp
  • 1,938
  • 18
  • 15