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