jvmMain.OBJSaver.kt Maven / Gradle / Ivy
The newest version!
package org.openrndr.extra.objloader
import org.openrndr.draw.VertexBuffer
import java.io.File
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* An Ordered Map
* containing Vec2 / Vec3 String representations.
* Ensures no strings are duplicates by using a Map
* and maintains their order by using a List.
*/
private class UniqueCoords {
// Map pointing unique strings to their index.
// We could just use `coords.indexOf(floats)` but using a map is faster.
private val indices = mutableMapOf()
// List containing unique Strings
private val coords = mutableListOf()
/**
* Adds strings only if they are not found yet in `coords`.
* Returns the index of the received argument inside `coords`.
*/
fun add(floats: String): Int {
val index = indices[floats]
return if (index == null) {
coords.add(floats)
val newIndex = coords.size
indices[floats] = newIndex
newIndex
} else index
}
/**
* Returns a valid .obj block representing `coords`.
*/
fun toObjBlock(token: String) = coords.joinToString("\n$token ", "$token ", "\n")
}
/**
* Saves a VertexBuffer to a Wavefront OBJ file.
* Faces use indices. Vertices, normals and texture coordinates are deduplicated.
*/
fun VertexBuffer.saveOBJ(filePath: String) {
val bb = ByteBuffer.allocateDirect(vertexCount * vertexFormat.size)
bb.order(ByteOrder.nativeOrder())
read(bb)
bb.rewind()
val tokens = mapOf(
"position" to "v",
"normal" to "vn",
"texCoord0" to "vt"
)
val indexMap = tokens.values.associateWith { UniqueCoords() }
val lastIndices = tokens.values.associateWith { 0 }.toMutableMap()
val vertexIndices = mutableListOf()
// Process the ByteBuffer and populate data structures
while (bb.position() < bb.capacity()) {
vertexFormat.items.forEach { vertexElement ->
val floats = List(vertexElement.type.componentCount) {
bb.getFloat()
}.joinToString(" ")
val token = tokens[vertexElement.attribute]
if (token != null) {
lastIndices[token] = indexMap[token]!!.add(floats)
}
}
vertexIndices.add("${lastIndices["v"]}/${lastIndices["vt"]}/${lastIndices["vn"]}")
}
val f = File(filePath)
f.bufferedWriter().use { writer ->
writer.run {
// Write header
appendLine("# OPENRNDR")
appendLine("# www.openrndr.org")
appendLine("o " + f.name)
// Write v, vt, vn blocks
indexMap.forEach { (token, verts) ->
appendLine(verts.toObjBlock(token))
}
// Write faces processing three vertices at a time
vertexIndices.chunked(3) {
appendLine("f ${it[0]} ${it[1]} ${it[2]}")
}
}
}
}