[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
}