[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

@@ -1,5 +1,8 @@
package org.openrndr.extra.objloader
/**
* Compound mesh data interface
*/
interface ICompoundMeshData {
val vertexData: IVertexData
val compounds: Map<String, IMeshData>
@@ -12,6 +15,10 @@ class CompoundMeshData(
override val compounds: Map<String, MeshData>
) : ICompoundMeshData {
init {
}
override fun triangulate(): CompoundMeshData {
return CompoundMeshData(vertexData, compounds.mapValues {
it.value.triangulate()
@@ -25,6 +32,10 @@ class MutableCompoundMeshData(
) : ICompoundMeshData {
override fun triangulate(): MutableCompoundMeshData {
TODO("Not yet implemented")
return MutableCompoundMeshData(
vertexData,
compounds.mapValues {
it.value.triangulate()
}.toMutableMap())
}
}

View File

@@ -19,6 +19,6 @@ fun ICompoundMeshData.toVertexBuffer(): VertexBuffer {
return vertexBuffer
}
fun ICompoundMeshData.flattenPolygons(): Map<String, List<IPolygon>> {
return compounds.mapValues { it.value.flattenPolygons() }
fun ICompoundMeshData.toPolygons(): Map<String, List<IPolygon>> {
return compounds.mapValues { it.value.toPolygons() }
}

View File

@@ -9,12 +9,38 @@ import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.round
/**
* Indexed polygon interface
*/
interface IIndexedPolygon {
/**
* Position indices
*/
val positions: List<Int>
/**
* Texture coordinate indices, optional
*/
val textureCoords: List<Int>
/**
* Normal indices, optional
*/
val normals: List<Int>
/**
* Color indices, optional
*/
val colors: List<Int>
/**
* Tangents, optional
*/
val tangents: List<Int>
/**
* Bitangents, optional
*/
val bitangents: List<Int>
fun base(vertexData: IVertexData): Matrix44 {
@@ -30,6 +56,11 @@ interface IIndexedPolygon {
)
}
/**
* Determine if polygon is planar
* @param vertexData the vertex data
* @param eps error tolerance
*/
fun isPlanar(vertexData: IVertexData, eps: Double = 1E-2): Boolean {
fun normal(i: Int): Vector3 {
val p0 = vertexData.positions[positions[(i - 1).mod(positions.size)]]
@@ -48,6 +79,9 @@ interface IIndexedPolygon {
}
}
/**
* Determine polygon convexity
*/
fun isConvex(vertexData: IVertexData): Boolean {
val planar = base(vertexData).inversed
@@ -99,10 +133,16 @@ interface IIndexedPolygon {
return abs(round(angleSum / (2 * PI))) == 1.0
}
/**
* Convert to [IPolygon]
* @param vertexData the vertex data required to build the [IPolygon]
*/
fun toPolygon(vertexData: IVertexData): IPolygon
}
/**
* Immutable indexed polygon implementation
*/
data class IndexedPolygon(
override val positions: List<Int>,
override val textureCoords: List<Int>,
@@ -113,7 +153,7 @@ data class IndexedPolygon(
) : IIndexedPolygon {
fun tessellate(vertexData: IVertexData): List<IndexedPolygon> {
private fun tessellate(vertexData: IVertexData): List<IndexedPolygon> {
val points = vertexData.positions.slice(positions.toList())
val triangles = org.openrndr.shape.triangulate(listOf(points))
@@ -129,6 +169,11 @@ data class IndexedPolygon(
}
}
/**
* Convert to a list of triangle [IndexedPolygon]
*
* Supports non-planar and non-convex polygons
*/
fun triangulate(vertexData: IVertexData): List<IndexedPolygon> {
return when {
positions.size == 3 -> listOf(this)
@@ -180,6 +225,9 @@ data class IndexedPolygon(
}
}
/**
* Mutable indexed polygon implementation
*/
data class MutableIndexedPolygon(
override val positions: MutableList<Int>,
override val textureCoords: MutableList<Int>,

View File

@@ -2,13 +2,19 @@ package org.openrndr.extra.objloader
import kotlin.jvm.JvmRecord
/**
* Mesh data interface
*/
interface IMeshData {
val vertexData: IVertexData
val polygons: List<IIndexedPolygon>
fun triangulate(): IMeshData
fun flattenPolygons(): List<IPolygon>
fun toPolygons(): List<IPolygon>
}
/**
* Immutable mesh data implementation
*/
@JvmRecord
data class MeshData(
override val vertexData: VertexData,
@@ -18,7 +24,7 @@ data class MeshData(
return copy(polygons = polygons.flatMap { polygon -> polygon.triangulate(vertexData) })
}
override fun flattenPolygons(): List<Polygon> {
override fun toPolygons(): List<Polygon> {
return polygons.map { ip ->
ip.toPolygon(vertexData)
}
@@ -26,6 +32,9 @@ data class MeshData(
}
/**
* Mutable mesh data implementation
*/
data class MutableMeshData(
override val vertexData: MutableVertexData,
override val polygons: MutableList<IndexedPolygon>
@@ -34,8 +43,7 @@ data class MutableMeshData(
return copy(polygons = polygons.flatMap { it.triangulate(vertexData) }.toMutableList())
}
override fun flattenPolygons(): List<Polygon> {
override fun toPolygons(): List<Polygon> {
return polygons.map { it.toPolygon(vertexData) }
}
}

View File

@@ -1,5 +1,6 @@
package org.openrndr.extra.objloader
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.VertexBuffer
import org.openrndr.draw.VertexFormat
import org.openrndr.draw.vertexBuffer
@@ -13,13 +14,14 @@ internal val objVertexFormat = vertexFormat {
position(3)
normal(3)
textureCoordinate(2)
color(4)
}
/**
* Converts a [MeshData] instance into a [VertexBuffer]
*/
fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer {
val objects = triangulate().flattenPolygons()
val objects = triangulate().toPolygons()
val triangleCount = objects.size
val vertexBuffer = vertexBuffer ?: vertexBuffer(objVertexFormat, triangleCount * 3)
@@ -40,11 +42,14 @@ fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer?
} else {
write(Vector2.ZERO)
}
if (it.colors.isNotEmpty()) {
write(it.colors[i])
} else {
write(ColorRGBa.WHITE)
}
}
}
}
vertexBuffer.shadow.destroy()
return vertexBuffer
}

View File

@@ -8,6 +8,10 @@ import org.openrndr.shape.Box
import kotlin.math.max
import kotlin.math.min
/**
* 3D Polygon interface
*/
interface IPolygon {
val positions: List<Vector3>
val normals: List<Vector3>
@@ -20,7 +24,7 @@ interface IPolygon {
}
/**
* A 3D Polygon
* Immutable 3D Polygon implementation
*
* @property positions Vertex 3D positions
* @property normals Vertex 3D normals
@@ -54,6 +58,9 @@ class Polygon(
}
}
/**
* Mutable 3D Polygon implementation
*/
class MutablePolygon(
override val positions: MutableList<Vector3> = mutableListOf(),
override val normals: MutableList<Vector3> = mutableListOf(),
@@ -77,7 +84,7 @@ class MutablePolygon(
/**
* Calculates the 3D bounding box of a list of [IPolygon].
* Calculate the 3D bounding box of a list of [IPolygon].
*/
fun bounds(polygons: List<IPolygon>): Box {
var minX = Double.POSITIVE_INFINITY
@@ -101,3 +108,42 @@ fun bounds(polygons: List<IPolygon>): Box {
}
return Box(Vector3(minX, minY, minZ), maxX - minX, maxY - minY, maxZ - minZ)
}
/**
* Convert list of polygons to [MeshData]
*/
fun List<IPolygon>.toMeshData(): MeshData {
val vertexData = MutableVertexData()
for (p in this) {
vertexData.positions.addAll(p.positions)
vertexData.normals.addAll(p.normals)
vertexData.colors.addAll(p.colors)
vertexData.textureCoords.addAll(p.textureCoords)
vertexData.tangents.addAll(p.tangents)
vertexData.bitangents.addAll(p.bitangents)
}
val indexedPolygons = mutableListOf<IndexedPolygon>()
var vertexOffset = 0
for (p in this) {
val indices = (vertexOffset until vertexOffset + p.positions.size).toList()
indexedPolygons.add(
IndexedPolygon(
positions = indices,
textureCoords = if (p.textureCoords.isNotEmpty()) indices else emptyList(),
normals = if (p.normals.isNotEmpty()) indices else emptyList(),
colors = if (p.colors.isNotEmpty()) indices else emptyList(),
tangents = if (p.tangents.isNotEmpty()) indices else emptyList(),
bitangents = if (p.bitangents.isNotEmpty()) indices else emptyList()
)
)
vertexOffset += p.positions.size
}
return MeshData(vertexData.toVertexData(), indexedPolygons)
}

View File

@@ -4,7 +4,6 @@ import org.openrndr.color.ColorRGBa
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
/**
* Vertex data interface
*/
@@ -40,6 +39,9 @@ interface IVertexData {
val bitangents: List<Vector3>
}
/**
* Immutable vertex data implementation
*/
class VertexData(
override val positions: List<Vector3> = emptyList(),
override val normals: List<Vector3> = emptyList(),
@@ -47,8 +49,27 @@ class VertexData(
override val colors: List<ColorRGBa> = emptyList(),
override val tangents: List<Vector3> = emptyList(),
override val bitangents: List<Vector3> = emptyList()
) : IVertexData
) : IVertexData {
/**
* Convert to [MutableVertexData]
*/
fun toMutableVertexData(): MutableVertexData {
return MutableVertexData(
positions.toMutableList(),
normals.toMutableList(),
textureCoords.toMutableList(),
colors.toMutableList(),
tangents.toMutableList(),
bitangents.toMutableList()
)
}
}
/**
* Mutable vertex data implementation
*/
class MutableVertexData(
override val positions: MutableList<Vector3> = mutableListOf(),
override val normals: MutableList<Vector3> = mutableListOf(),
@@ -56,4 +77,19 @@ class MutableVertexData(
override val colors: MutableList<ColorRGBa> = mutableListOf(),
override val tangents: MutableList<Vector3> = mutableListOf(),
override val bitangents: MutableList<Vector3> = mutableListOf()
) : IVertexData
) : IVertexData {
/**
* Convert to [VertexData]
*/
fun toVertexData(): VertexData {
return VertexData(
positions.toList(),
normals.toList(),
textureCoords.toList(),
colors.toList(),
tangents.toList(),
bitangents.toList()
)
}
}

View File

@@ -2,12 +2,17 @@ package org.openrndr.extra.objloader
import org.openrndr.math.Vector3
/**
* Extract wireframe from mesh data
*/
fun IMeshData.wireframe(): List<List<Vector3>> {
return polygons.map { ip -> ip.toPolygon(this.vertexData).positions.toList() }
}
/**
* Extract wireframe from compound mesh data
*/
fun ICompoundMeshData.wireframe(): List<List<Vector3>> {
return compounds.values.flatMap {
it.wireframe()
}
return compounds.values.flatMap { it.wireframe() }
}

View File

@@ -0,0 +1,57 @@
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.VertexBuffer
import org.openrndr.extra.objloader.Polygon
import org.openrndr.extra.objloader.objVertexFormat
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import java.nio.ByteBuffer
import java.nio.ByteOrder
private fun ByteBuffer.getVector3(): Vector3 {
val x = getFloat()
val y = getFloat()
val z = getFloat()
return Vector3(x.toDouble(), y.toDouble(), z.toDouble())
}
private fun ByteBuffer.getVector2(): Vector2 {
val x = getFloat()
val y = getFloat()
return Vector2(x.toDouble(), y.toDouble())
}
private fun ByteBuffer.getColorRGBa(): ColorRGBa {
val r = getFloat()
val g = getFloat()
val b = getFloat()
val a = getFloat()
return ColorRGBa(r.toDouble(), g.toDouble(), b.toDouble(), a.toDouble())
}
/**
Convert vertex buffer contents to a list of [Polygon] instances
*/
fun VertexBuffer.toPolygons(vertexCount: Int = this.vertexCount): List<Polygon> {
require(vertexFormat == objVertexFormat)
val triangleCount = vertexCount / 3
val buffer = ByteBuffer.allocateDirect(this.vertexCount * vertexFormat.size)
buffer.order(ByteOrder.nativeOrder())
val polygons = mutableListOf<Polygon>()
for (t in 0 until triangleCount) {
val positions = mutableListOf<Vector3>()
val normals = mutableListOf<Vector3>()
val textureCoordinates = mutableListOf<Vector2>()
val colors = mutableListOf<ColorRGBa>()
for (v in 0 until 3) {
positions.add(buffer.getVector3())
normals.add(buffer.getVector3())
textureCoordinates.add(buffer.getVector2())
colors.add(buffer.getColorRGBa())
}
polygons.add(Polygon(positions, normals, textureCoordinates, colors))
}
return polygons
}

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 {
kotlin {
sourceSets {
val commonMain by getting {
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"))
}
}
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",