[orx-mesh, orx-obj-loader] Separate into commonMain and jvmMain

This commit is contained in:
Edwin Jakobs
2024-09-16 22:12:48 +02:00
parent 8bccf54447
commit ed55899230
20 changed files with 425 additions and 86 deletions

View File

@@ -25,21 +25,21 @@ vertexBuffer.saveOBJ("my/path/exported.obj")
<!-- __demos__ -->
## Demos
### DemoObjLoader01
[source code](src/demo/kotlin/DemoObjLoader01.kt)
[source code](src/jvmDemo/kotlin/DemoObjLoader01.kt)
![DemoObjLoader01Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-obj-loader/images/DemoObjLoader01Kt.png)
### DemoObjSaver01
[source code](src/demo/kotlin/DemoObjSaver01.kt)
[source code](src/jvmDemo/kotlin/DemoObjSaver01.kt)
![DemoObjSaver01Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-obj-loader/images/DemoObjSaver01Kt.png)
### DemoObjSaver02
[source code](src/demo/kotlin/DemoObjSaver02.kt)
[source code](src/jvmDemo/kotlin/DemoObjSaver02.kt)
![DemoObjSaver02Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-obj-loader/images/DemoObjSaver02Kt.png)
### DemoWireframe01
[source code](src/demo/kotlin/DemoWireframe01.kt)
[source code](src/jvmDemo/kotlin/DemoWireframe01.kt)
![DemoWireframe01Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-obj-loader/images/DemoWireframe01Kt.png)

View File

@@ -1,12 +1,22 @@
plugins {
org.openrndr.extra.convention.`kotlin-jvm`
org.openrndr.extra.convention.`kotlin-multiplatform`
}
dependencies {
implementation(libs.openrndr.application)
implementation(libs.openrndr.math)
implementation(libs.openrndr.ffmpeg)
api(project(":orx-mesh"))
demoImplementation(project(":orx-camera"))
demoImplementation(project(":orx-mesh-generators"))
}
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.openrndr.application)
implementation(libs.openrndr.math)
api(project(":orx-mesh"))
}
}
val jvmDemo by getting {
dependencies {
implementation(project(":orx-camera"))
implementation(project(":orx-mesh-generators"))
}
}
}
}

View File

@@ -2,56 +2,15 @@ package org.openrndr.extra.objloader
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.*
import java.io.File
import java.net.MalformedURLException
import java.net.URL
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
/**
* Loads an OBJ file as a Map of names to lists of [Polygon].
* Use this method to access the loaded OBJ data from the CPU.
*/
fun loadOBJ(fileOrUrl: String): Map<String, List<IPolygon>> {
return try {
val url = URL(fileOrUrl)
loadOBJ(url)
} catch (e: MalformedURLException) {
loadOBJ(File(fileOrUrl))
}
}
/**
* Loads an OBJ file as a [VertexBuffer].
* Use this method to render / process the loaded OBJ data using the GPU.
*/
fun loadOBJasVertexBuffer(fileOrUrl: String): VertexBuffer {
return try {
val url = URL(fileOrUrl)
loadOBJasVertexBuffer(url)
} catch (e: MalformedURLException) {
loadOBJasVertexBuffer(File(fileOrUrl))
}
}
fun loadOBJasVertexBuffer(url: URL): VertexBuffer = loadOBJasVertexBuffer(url.readText().split("\n"))
fun loadOBJasVertexBuffer(file: File): VertexBuffer = loadOBJasVertexBuffer(file.readLines())
fun loadOBJasVertexBuffer(lines: List<String>): VertexBuffer {
return loadOBJMeshData(lines).toVertexBuffer()
}
fun loadOBJ(file: File) = loadOBJ(file.readLines())
fun loadOBJEx(file: File) = loadOBJMeshData(file.readLines())
fun loadOBJ(url: URL) = loadOBJ(url.readText().split("\n"))
fun loadOBJEx(url: URL) = loadOBJMeshData(url.readText().split("\n"))
fun loadOBJ(lines: List<String>): Map<String, List<IPolygon>> = loadOBJMeshData(lines).triangulate().flattenPolygons()
fun loadOBJMeshData(file: File) = loadOBJMeshData(file.readLines())
fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
fun readObjMeshData(lines: Iterable<String>): CompoundMeshData {
val meshes = mutableMapOf<String, List<IndexedPolygon>>()
val positions = mutableListOf<Vector3>()
val normals = mutableListOf<Vector3>()
val tangents = mutableListOf<Vector3>()
val bitangents = mutableListOf<Vector3>()
val textureCoords = mutableListOf<Vector2>()
val colors = mutableListOf<ColorRGBa>()
var activeMesh = mutableListOf<IndexedPolygon>()
@@ -66,7 +25,6 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
when (tokens.size) {
3, 4 -> {
positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
colors += ColorRGBa.WHITE
}
6 -> {
@@ -88,7 +46,18 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
}
}
"vn" -> normals += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
"vn" -> {
when (tokens.size) {
3, 4 -> normals += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
9 -> {
normals += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
tangents += Vector3(tokens[4].toDouble(), tokens[5].toDouble(), tokens[6].toDouble())
bitangents += Vector3(tokens[7].toDouble(), tokens[8].toDouble(), tokens[9].toDouble())
}
}
}
"vt" -> textureCoords += Vector2(tokens[1].toDouble(), tokens[2].toDouble())
"g" -> {
activeMesh = mutableListOf()
@@ -102,7 +71,9 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
val hasPosition = (indices[0].getOrNull(0) ?: 0) != 0
val hasUV = (indices[0].getOrNull(1) ?: 0) != 0
val hasNormal = (indices[0].getOrNull(2) ?: 0) != 0
val hasColor = hasPosition
val hasColor = colors.isNotEmpty()
val hasTangents = tangents.isNotEmpty()
val hasBitangents = bitangents.isNotEmpty()
activeMesh.add(
IndexedPolygon(
@@ -110,8 +81,8 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
if (hasUV) indices.map { it[1] - 1 } else listOf(),
if (hasNormal) indices.map { it[2] - 1 } else listOf(),
if (hasColor) indices.map { it[0] - 1 } else listOf(),
emptyList(),
emptyList()
if (hasTangents) indices.map { it[2] - 1 } else listOf(),
if (hasBitangents) indices.map { it[2] - 1 } else listOf()
)
)
@@ -132,3 +103,10 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
}
)
}
fun loadOBJasVertexBuffer(lines: List<String>): VertexBuffer {
return readObjMeshData(lines).toVertexBuffer()
}
fun loadOBJ(lines: List<String>): Map<String, List<IPolygon>> = readObjMeshData(lines).triangulate().toPolygons()

View File

@@ -0,0 +1,75 @@
package org.openrndr.extra.objloader
/**
* Convert mesh data to Wavefront OBJ representation
* @param allowNonStandardBehavior if true non-standard encoding of color and tangent data is allowed
*/
fun ICompoundMeshData.toObj(allowNonStandardBehavior: Boolean = true): String {
val sb = StringBuilder()
require(compounds.values.all { it.vertexData == vertexData }) {
"compounds do not share vertex data"
}
/*
Output positions
*/
if (vertexData.colors.isEmpty()) {
for (p in vertexData.positions) {
sb.appendLine("v ${p.x} ${p.y} ${p.z}")
}
} else {
require(vertexData.positions.size == vertexData.colors.size) {
"position and color data do not align"
}
for (pc in vertexData.positions.zip(vertexData.colors)) {
sb.appendLine("v ${pc.first.x} ${pc.first.y} ${pc.first.z} ${pc.second.r} ${pc.second.g} ${pc.second.b} ${pc.second.alpha}")
}
}
/*
Output normals. Non-standard behavior where normal-tangent-bitangent is emitted as `vn`
*/
if (!allowNonStandardBehavior || vertexData.tangents.isEmpty() || vertexData.bitangents.isEmpty()) {
for (n in vertexData.normals) {
sb.appendLine("vn ${n.x} ${n.y} ${n.z}")
}
} else {
for (i in vertexData.normals.indices) {
val n = vertexData.normals[i]
val t = vertexData.tangents[i]
val b = vertexData.bitangents[i]
sb.appendLine("vn ${n.x} ${n.y} ${n.z} ${t.x} ${t.y} ${t.z} ${b.x} ${b.y} ${b.z}")
}
}
/*
Output texture coordinates
*/
for (t in vertexData.textureCoords) {
sb.appendLine("vt ${t.x} ${t.y}")
}
/*
Output compounds
*/
for (g in compounds) {
sb.appendLine("g ${g.key}")
/*
Output polygons
*/
for (p in g.value.polygons) {
sb.appendLine("f ${
(0 until p.positions.size).joinToString(" ") { i ->
listOf(
p.positions.getOrNull(i)?.plus(1)?.toString() ?: "",
p.textureCoords.getOrNull(i)?.plus(1)?.toString() ?: "",
p.normals.getOrNull(i)?.plus(1)?.toString() ?: ""
).joinToString("/")
}
}")
}
}
return sb.toString()
}

View File

@@ -0,0 +1,15 @@
import org.openrndr.application
import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.objloader.toObj
import java.io.File
fun main() {
application {
program {
val path = "demo-data/obj-models"
val cm = loadOBJMeshData(File("$path/suzanne/Suzanne.obj"))
println(cm.toObj())
}
}
}

View File

@@ -8,7 +8,7 @@ import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.TransformTarget
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.objloader.readObjMeshData
import org.openrndr.extra.objloader.loadOBJasVertexBuffer
import org.openrndr.extra.objloader.wireframe
import org.openrndr.math.Vector3
@@ -25,7 +25,7 @@ fun main() {
}
program {
val vb = loadOBJasVertexBuffer("orx-obj-loader/test-data/non-planar.obj")
val md = loadOBJMeshData(File("orx-obj-loader/test-data/non-planar.obj").readLines())
val md = readObjMeshData(File("orx-obj-loader/test-data/non-planar.obj").readLines())
val paths = md.wireframe().map {
Path3D.fromPoints(it, true)

View File

@@ -0,0 +1,44 @@
package org.openrndr.extra.objloader
import org.openrndr.draw.VertexBuffer
import java.io.File
import java.net.MalformedURLException
import java.net.URL
/**
* Loads an OBJ file as a Map of names to lists of [Polygon].
* Use this method to access the loaded OBJ data from the CPU.
*/
fun loadOBJ(fileOrUrl: String): Map<String, List<IPolygon>> {
return try {
val url = URL(fileOrUrl)
loadOBJ(url)
} catch (e: MalformedURLException) {
loadOBJ(File(fileOrUrl))
}
}
/**
* Loads an OBJ file as a [VertexBuffer].
* Use this method to render / process the loaded OBJ data using the GPU.
*/
fun loadOBJasVertexBuffer(fileOrUrl: String): VertexBuffer {
return try {
val url = URL(fileOrUrl)
loadOBJasVertexBuffer(url)
} catch (e: MalformedURLException) {
loadOBJasVertexBuffer(File(fileOrUrl))
}
}
fun loadOBJasVertexBuffer(url: URL): VertexBuffer = loadOBJasVertexBuffer(url.readText().split("\n"))
fun loadOBJasVertexBuffer(file: File): VertexBuffer = loadOBJasVertexBuffer(file.readLines())
fun loadOBJ(file: File) = loadOBJ(file.readLines())
fun loadOBJEx(file: File) = readObjMeshData(file.readLines())
fun loadOBJ(url: URL) = loadOBJ(url.readText().split("\n"))
fun loadOBJEx(url: URL) = readObjMeshData(url.readText().split("\n"))
fun loadOBJMeshData(file: File) = readObjMeshData(file.readLines())

View File

@@ -47,6 +47,7 @@ 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",