Loading 3D lines from an OBJ file

For a project I’m working on I needed to load a 3D model and a bunch of 3D line segments.

To load the model in .obj file format I used orx/orx-obj-loader at master · openrndr/orx · GitHub but that orx does not yet support loading lines so I’m sharing a way to achieve that.

To be able to query each geometry separately I exported four separate OBJ files from Blender:

  • The ground (created via photogrammetry)
  • The vertical lines (cyan)
  • One set of horizontal lines (magenta)
  • Second set of horizontal lines (magenta)

These are the functions I used to load the obj files with lines:

fun loadOBJasLinesVertexBuffer(path: String): VertexBuffer {
    val vfPos = vertexFormat { position(3) }
    val lines = loadOBJasLines(path)
    val vb = vertexBuffer(vfPos, lines.size * 2)
    vb.put {
        lines.forEach {
            write(it.position(0.0))
            write(it.position(1.0))
        }
    }
    return vb
}

fun loadOBJasLines(path: String): List<Path3D> {
    val f = File(path)
    val lines = f.readLines()
    val result = mutableListOf<Path3D>()
    val positions = mutableListOf<Vector3>()
    lines.forEach { line ->
        if (line.isNotEmpty()) {
            val tokens = line.split(Regex("[ |\t]+")).map { it.trim() }.filter { it.isNotEmpty() }
            if (tokens.isNotEmpty()) {
                when (tokens[0]) {
                    "v" -> positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())

                    "l" -> {
                        val points = tokens.drop(1).map {
                            positions[it.toInt() - 1]
                        }
                        result.add(Path3D.fromPoints(points, false))
                    }
                }
            }
        }
    }
    return result
}

Note: I fixed a small issue in openrndr to make the above code work. Until it becomes part of a release building openrndr is necessary for it to work.

Here a small program making use of the functions:

fun main() = application {
    program {
        val ground = loadOBJasVertexBuffer("data/sk-ground.obj")
        val vtrees = loadOBJasLinesVertexBuffer("data/sk-vtrees.obj")
        val htrees1 = loadOBJasLinesVertexBuffer("data/sk-htrees1.obj")
        val htrees2 = loadOBJasLinesVertexBuffer("data/sk-htrees2.obj")

        val camera = OrbitalCamera(Vector3.ONE * 5.0, Vector3.ZERO, 90.0, 0.1, 5000.0)
        val controls = OrbitalControls(camera)

        val tex = loadImage("data/tex.jpg")
        // Improve texture quality
        tex.filterMag = MagnifyingFilter.LINEAR
        tex.filterMin = MinifyingFilter.LINEAR_MIPMAP_LINEAR
        // A style to render the texture on the ground plane
        val texShader = shadeStyle {
            fragmentTransform = "x_fill = texture(p_tex, va_texCoord0);"
            parameter("tex", tex)
        }
        extend(camera)
        extend(controls)
        extend {
            drawer.clear(ColorRGBa.BLACK)

            drawer.shadeStyle = texShader
            drawer.vertexBuffer(ground, DrawPrimitive.TRIANGLES)
            drawer.shadeStyle = null

            drawer.fill = ColorRGBa.CYAN
            drawer.vertexBuffer(vtrees, DrawPrimitive.LINES)

            drawer.fill = ColorRGBa.MAGENTA
            drawer.vertexBuffer(htrees1, DrawPrimitive.LINES)
            drawer.vertexBuffer(htrees2, DrawPrimitive.LINES)
        }
    }
}

The content of the .obj file looks like this

# Blender 4.0.2
# www.blender.org
mtllib sk-vtrees.mtl
o MyBezierCurve
v 1.274006 1.001017 -6.868662
v 1.274006 2.001017 -6.868662
l 1 2
o MyBezierCurve.001
v 2.144386 1.450704 -8.414247
v 2.144386 2.450704 -8.414247
l 3 4
...

Sharing it in case it helps someone :slight_smile:

1 Like