[orx-mesh, orx-obj-loader] Separate into commonMain and jvmMain
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
package org.openrndr.extra.objloader
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compound mesh data interface
|
||||||
|
*/
|
||||||
interface ICompoundMeshData {
|
interface ICompoundMeshData {
|
||||||
val vertexData: IVertexData
|
val vertexData: IVertexData
|
||||||
val compounds: Map<String, IMeshData>
|
val compounds: Map<String, IMeshData>
|
||||||
@@ -12,6 +15,10 @@ class CompoundMeshData(
|
|||||||
override val compounds: Map<String, MeshData>
|
override val compounds: Map<String, MeshData>
|
||||||
) : ICompoundMeshData {
|
) : ICompoundMeshData {
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun triangulate(): CompoundMeshData {
|
override fun triangulate(): CompoundMeshData {
|
||||||
return CompoundMeshData(vertexData, compounds.mapValues {
|
return CompoundMeshData(vertexData, compounds.mapValues {
|
||||||
it.value.triangulate()
|
it.value.triangulate()
|
||||||
@@ -25,6 +32,10 @@ class MutableCompoundMeshData(
|
|||||||
) : ICompoundMeshData {
|
) : ICompoundMeshData {
|
||||||
|
|
||||||
override fun triangulate(): MutableCompoundMeshData {
|
override fun triangulate(): MutableCompoundMeshData {
|
||||||
TODO("Not yet implemented")
|
return MutableCompoundMeshData(
|
||||||
|
vertexData,
|
||||||
|
compounds.mapValues {
|
||||||
|
it.value.triangulate()
|
||||||
|
}.toMutableMap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,6 @@ fun ICompoundMeshData.toVertexBuffer(): VertexBuffer {
|
|||||||
return vertexBuffer
|
return vertexBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ICompoundMeshData.flattenPolygons(): Map<String, List<IPolygon>> {
|
fun ICompoundMeshData.toPolygons(): Map<String, List<IPolygon>> {
|
||||||
return compounds.mapValues { it.value.flattenPolygons() }
|
return compounds.mapValues { it.value.toPolygons() }
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,38 @@ import kotlin.math.abs
|
|||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexed polygon interface
|
||||||
|
*/
|
||||||
interface IIndexedPolygon {
|
interface IIndexedPolygon {
|
||||||
|
/**
|
||||||
|
* Position indices
|
||||||
|
*/
|
||||||
val positions: List<Int>
|
val positions: List<Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Texture coordinate indices, optional
|
||||||
|
*/
|
||||||
val textureCoords: List<Int>
|
val textureCoords: List<Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal indices, optional
|
||||||
|
*/
|
||||||
val normals: List<Int>
|
val normals: List<Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color indices, optional
|
||||||
|
*/
|
||||||
val colors: List<Int>
|
val colors: List<Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tangents, optional
|
||||||
|
*/
|
||||||
val tangents: List<Int>
|
val tangents: List<Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitangents, optional
|
||||||
|
*/
|
||||||
val bitangents: List<Int>
|
val bitangents: List<Int>
|
||||||
|
|
||||||
fun base(vertexData: IVertexData): Matrix44 {
|
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 isPlanar(vertexData: IVertexData, eps: Double = 1E-2): Boolean {
|
||||||
fun normal(i: Int): Vector3 {
|
fun normal(i: Int): Vector3 {
|
||||||
val p0 = vertexData.positions[positions[(i - 1).mod(positions.size)]]
|
val p0 = vertexData.positions[positions[(i - 1).mod(positions.size)]]
|
||||||
@@ -48,6 +79,9 @@ interface IIndexedPolygon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine polygon convexity
|
||||||
|
*/
|
||||||
fun isConvex(vertexData: IVertexData): Boolean {
|
fun isConvex(vertexData: IVertexData): Boolean {
|
||||||
val planar = base(vertexData).inversed
|
val planar = base(vertexData).inversed
|
||||||
|
|
||||||
@@ -99,10 +133,16 @@ interface IIndexedPolygon {
|
|||||||
return abs(round(angleSum / (2 * PI))) == 1.0
|
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
|
fun toPolygon(vertexData: IVertexData): IPolygon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable indexed polygon implementation
|
||||||
|
*/
|
||||||
data class IndexedPolygon(
|
data class IndexedPolygon(
|
||||||
override val positions: List<Int>,
|
override val positions: List<Int>,
|
||||||
override val textureCoords: List<Int>,
|
override val textureCoords: List<Int>,
|
||||||
@@ -113,7 +153,7 @@ data class IndexedPolygon(
|
|||||||
|
|
||||||
) : IIndexedPolygon {
|
) : IIndexedPolygon {
|
||||||
|
|
||||||
fun tessellate(vertexData: IVertexData): List<IndexedPolygon> {
|
private fun tessellate(vertexData: IVertexData): List<IndexedPolygon> {
|
||||||
val points = vertexData.positions.slice(positions.toList())
|
val points = vertexData.positions.slice(positions.toList())
|
||||||
val triangles = org.openrndr.shape.triangulate(listOf(points))
|
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> {
|
fun triangulate(vertexData: IVertexData): List<IndexedPolygon> {
|
||||||
return when {
|
return when {
|
||||||
positions.size == 3 -> listOf(this)
|
positions.size == 3 -> listOf(this)
|
||||||
@@ -180,6 +225,9 @@ data class IndexedPolygon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable indexed polygon implementation
|
||||||
|
*/
|
||||||
data class MutableIndexedPolygon(
|
data class MutableIndexedPolygon(
|
||||||
override val positions: MutableList<Int>,
|
override val positions: MutableList<Int>,
|
||||||
override val textureCoords: MutableList<Int>,
|
override val textureCoords: MutableList<Int>,
|
||||||
|
|||||||
@@ -2,13 +2,19 @@ package org.openrndr.extra.objloader
|
|||||||
|
|
||||||
import kotlin.jvm.JvmRecord
|
import kotlin.jvm.JvmRecord
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mesh data interface
|
||||||
|
*/
|
||||||
interface IMeshData {
|
interface IMeshData {
|
||||||
val vertexData: IVertexData
|
val vertexData: IVertexData
|
||||||
val polygons: List<IIndexedPolygon>
|
val polygons: List<IIndexedPolygon>
|
||||||
fun triangulate(): IMeshData
|
fun triangulate(): IMeshData
|
||||||
fun flattenPolygons(): List<IPolygon>
|
fun toPolygons(): List<IPolygon>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable mesh data implementation
|
||||||
|
*/
|
||||||
@JvmRecord
|
@JvmRecord
|
||||||
data class MeshData(
|
data class MeshData(
|
||||||
override val vertexData: VertexData,
|
override val vertexData: VertexData,
|
||||||
@@ -18,7 +24,7 @@ data class MeshData(
|
|||||||
return copy(polygons = polygons.flatMap { polygon -> polygon.triangulate(vertexData) })
|
return copy(polygons = polygons.flatMap { polygon -> polygon.triangulate(vertexData) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun flattenPolygons(): List<Polygon> {
|
override fun toPolygons(): List<Polygon> {
|
||||||
return polygons.map { ip ->
|
return polygons.map { ip ->
|
||||||
ip.toPolygon(vertexData)
|
ip.toPolygon(vertexData)
|
||||||
}
|
}
|
||||||
@@ -26,6 +32,9 @@ data class MeshData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable mesh data implementation
|
||||||
|
*/
|
||||||
data class MutableMeshData(
|
data class MutableMeshData(
|
||||||
override val vertexData: MutableVertexData,
|
override val vertexData: MutableVertexData,
|
||||||
override val polygons: MutableList<IndexedPolygon>
|
override val polygons: MutableList<IndexedPolygon>
|
||||||
@@ -34,8 +43,7 @@ data class MutableMeshData(
|
|||||||
return copy(polygons = polygons.flatMap { it.triangulate(vertexData) }.toMutableList())
|
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) }
|
return polygons.map { it.toPolygon(vertexData) }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.openrndr.extra.objloader
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.draw.VertexBuffer
|
import org.openrndr.draw.VertexBuffer
|
||||||
import org.openrndr.draw.VertexFormat
|
import org.openrndr.draw.VertexFormat
|
||||||
import org.openrndr.draw.vertexBuffer
|
import org.openrndr.draw.vertexBuffer
|
||||||
@@ -13,13 +14,14 @@ internal val objVertexFormat = vertexFormat {
|
|||||||
position(3)
|
position(3)
|
||||||
normal(3)
|
normal(3)
|
||||||
textureCoordinate(2)
|
textureCoordinate(2)
|
||||||
|
color(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a [MeshData] instance into a [VertexBuffer]
|
* Converts a [MeshData] instance into a [VertexBuffer]
|
||||||
*/
|
*/
|
||||||
fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer {
|
fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer {
|
||||||
val objects = triangulate().flattenPolygons()
|
val objects = triangulate().toPolygons()
|
||||||
val triangleCount = objects.size
|
val triangleCount = objects.size
|
||||||
val vertexBuffer = vertexBuffer ?: vertexBuffer(objVertexFormat, triangleCount * 3)
|
val vertexBuffer = vertexBuffer ?: vertexBuffer(objVertexFormat, triangleCount * 3)
|
||||||
|
|
||||||
@@ -40,11 +42,14 @@ fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer?
|
|||||||
} else {
|
} else {
|
||||||
write(Vector2.ZERO)
|
write(Vector2.ZERO)
|
||||||
}
|
}
|
||||||
|
if (it.colors.isNotEmpty()) {
|
||||||
|
write(it.colors[i])
|
||||||
|
} else {
|
||||||
|
write(ColorRGBa.WHITE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
vertexBuffer.shadow.destroy()
|
vertexBuffer.shadow.destroy()
|
||||||
return vertexBuffer
|
return vertexBuffer
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,10 @@ import org.openrndr.shape.Box
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3D Polygon interface
|
||||||
|
*/
|
||||||
interface IPolygon {
|
interface IPolygon {
|
||||||
val positions: List<Vector3>
|
val positions: List<Vector3>
|
||||||
val normals: 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 positions Vertex 3D positions
|
||||||
* @property normals Vertex 3D normals
|
* @property normals Vertex 3D normals
|
||||||
@@ -54,6 +58,9 @@ class Polygon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable 3D Polygon implementation
|
||||||
|
*/
|
||||||
class MutablePolygon(
|
class MutablePolygon(
|
||||||
override val positions: MutableList<Vector3> = mutableListOf(),
|
override val positions: MutableList<Vector3> = mutableListOf(),
|
||||||
override val normals: 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 {
|
fun bounds(polygons: List<IPolygon>): Box {
|
||||||
var minX = Double.POSITIVE_INFINITY
|
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)
|
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)
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import org.openrndr.color.ColorRGBa
|
|||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.math.Vector3
|
import org.openrndr.math.Vector3
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vertex data interface
|
* Vertex data interface
|
||||||
*/
|
*/
|
||||||
@@ -40,6 +39,9 @@ interface IVertexData {
|
|||||||
val bitangents: List<Vector3>
|
val bitangents: List<Vector3>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable vertex data implementation
|
||||||
|
*/
|
||||||
class VertexData(
|
class VertexData(
|
||||||
override val positions: List<Vector3> = emptyList(),
|
override val positions: List<Vector3> = emptyList(),
|
||||||
override val normals: List<Vector3> = emptyList(),
|
override val normals: List<Vector3> = emptyList(),
|
||||||
@@ -47,8 +49,27 @@ class VertexData(
|
|||||||
override val colors: List<ColorRGBa> = emptyList(),
|
override val colors: List<ColorRGBa> = emptyList(),
|
||||||
override val tangents: List<Vector3> = emptyList(),
|
override val tangents: List<Vector3> = emptyList(),
|
||||||
override val bitangents: 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(
|
class MutableVertexData(
|
||||||
override val positions: MutableList<Vector3> = mutableListOf(),
|
override val positions: MutableList<Vector3> = mutableListOf(),
|
||||||
override val normals: MutableList<Vector3> = mutableListOf(),
|
override val normals: MutableList<Vector3> = mutableListOf(),
|
||||||
@@ -56,4 +77,19 @@ class MutableVertexData(
|
|||||||
override val colors: MutableList<ColorRGBa> = mutableListOf(),
|
override val colors: MutableList<ColorRGBa> = mutableListOf(),
|
||||||
override val tangents: MutableList<Vector3> = mutableListOf(),
|
override val tangents: MutableList<Vector3> = mutableListOf(),
|
||||||
override val bitangents: 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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,17 @@ package org.openrndr.extra.objloader
|
|||||||
|
|
||||||
import org.openrndr.math.Vector3
|
import org.openrndr.math.Vector3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract wireframe from mesh data
|
||||||
|
*/
|
||||||
fun IMeshData.wireframe(): List<List<Vector3>> {
|
fun IMeshData.wireframe(): List<List<Vector3>> {
|
||||||
return polygons.map { ip -> ip.toPolygon(this.vertexData).positions.toList() }
|
return polygons.map { ip -> ip.toPolygon(this.vertexData).positions.toList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract wireframe from compound mesh data
|
||||||
|
*/
|
||||||
fun ICompoundMeshData.wireframe(): List<List<Vector3>> {
|
fun ICompoundMeshData.wireframe(): List<List<Vector3>> {
|
||||||
return compounds.values.flatMap {
|
return compounds.values.flatMap { it.wireframe() }
|
||||||
it.wireframe()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
57
orx-mesh/src/jvmMain/kotlin/VertexBufferExtensions.kt
Normal file
57
orx-mesh/src/jvmMain/kotlin/VertexBufferExtensions.kt
Normal 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
|
||||||
|
}
|
||||||
@@ -25,21 +25,21 @@ vertexBuffer.saveOBJ("my/path/exported.obj")
|
|||||||
<!-- __demos__ -->
|
<!-- __demos__ -->
|
||||||
## Demos
|
## Demos
|
||||||
### DemoObjLoader01
|
### DemoObjLoader01
|
||||||
[source code](src/demo/kotlin/DemoObjLoader01.kt)
|
[source code](src/jvmDemo/kotlin/DemoObjLoader01.kt)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### DemoObjSaver01
|
### DemoObjSaver01
|
||||||
[source code](src/demo/kotlin/DemoObjSaver01.kt)
|
[source code](src/jvmDemo/kotlin/DemoObjSaver01.kt)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### DemoObjSaver02
|
### DemoObjSaver02
|
||||||
[source code](src/demo/kotlin/DemoObjSaver02.kt)
|
[source code](src/jvmDemo/kotlin/DemoObjSaver02.kt)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### DemoWireframe01
|
### DemoWireframe01
|
||||||
[source code](src/demo/kotlin/DemoWireframe01.kt)
|
[source code](src/jvmDemo/kotlin/DemoWireframe01.kt)
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
plugins {
|
plugins {
|
||||||
org.openrndr.extra.convention.`kotlin-jvm`
|
org.openrndr.extra.convention.`kotlin-multiplatform`
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
kotlin {
|
||||||
implementation(libs.openrndr.application)
|
sourceSets {
|
||||||
implementation(libs.openrndr.math)
|
val commonMain by getting {
|
||||||
implementation(libs.openrndr.ffmpeg)
|
dependencies {
|
||||||
api(project(":orx-mesh"))
|
implementation(libs.openrndr.application)
|
||||||
demoImplementation(project(":orx-camera"))
|
implementation(libs.openrndr.math)
|
||||||
demoImplementation(project(":orx-mesh-generators"))
|
api(project(":orx-mesh"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val jvmDemo by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":orx-camera"))
|
||||||
|
implementation(project(":orx-mesh-generators"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,56 +2,15 @@ package org.openrndr.extra.objloader
|
|||||||
|
|
||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.draw.VertexBuffer
|
import org.openrndr.draw.VertexBuffer
|
||||||
import org.openrndr.math.*
|
import org.openrndr.math.Vector2
|
||||||
import java.io.File
|
import org.openrndr.math.Vector3
|
||||||
import java.net.MalformedURLException
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
/**
|
fun readObjMeshData(lines: Iterable<String>): CompoundMeshData {
|
||||||
* 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 {
|
|
||||||
val meshes = mutableMapOf<String, List<IndexedPolygon>>()
|
val meshes = mutableMapOf<String, List<IndexedPolygon>>()
|
||||||
val positions = mutableListOf<Vector3>()
|
val positions = mutableListOf<Vector3>()
|
||||||
val normals = mutableListOf<Vector3>()
|
val normals = mutableListOf<Vector3>()
|
||||||
|
val tangents = mutableListOf<Vector3>()
|
||||||
|
val bitangents = mutableListOf<Vector3>()
|
||||||
val textureCoords = mutableListOf<Vector2>()
|
val textureCoords = mutableListOf<Vector2>()
|
||||||
val colors = mutableListOf<ColorRGBa>()
|
val colors = mutableListOf<ColorRGBa>()
|
||||||
var activeMesh = mutableListOf<IndexedPolygon>()
|
var activeMesh = mutableListOf<IndexedPolygon>()
|
||||||
@@ -66,7 +25,6 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
|
|||||||
when (tokens.size) {
|
when (tokens.size) {
|
||||||
3, 4 -> {
|
3, 4 -> {
|
||||||
positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
||||||
colors += ColorRGBa.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
6 -> {
|
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())
|
"vt" -> textureCoords += Vector2(tokens[1].toDouble(), tokens[2].toDouble())
|
||||||
"g" -> {
|
"g" -> {
|
||||||
activeMesh = mutableListOf()
|
activeMesh = mutableListOf()
|
||||||
@@ -102,7 +71,9 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
|
|||||||
val hasPosition = (indices[0].getOrNull(0) ?: 0) != 0
|
val hasPosition = (indices[0].getOrNull(0) ?: 0) != 0
|
||||||
val hasUV = (indices[0].getOrNull(1) ?: 0) != 0
|
val hasUV = (indices[0].getOrNull(1) ?: 0) != 0
|
||||||
val hasNormal = (indices[0].getOrNull(2) ?: 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(
|
activeMesh.add(
|
||||||
IndexedPolygon(
|
IndexedPolygon(
|
||||||
@@ -110,8 +81,8 @@ fun loadOBJMeshData(lines: List<String>): CompoundMeshData {
|
|||||||
if (hasUV) indices.map { it[1] - 1 } else listOf(),
|
if (hasUV) indices.map { it[1] - 1 } else listOf(),
|
||||||
if (hasNormal) indices.map { it[2] - 1 } else listOf(),
|
if (hasNormal) indices.map { it[2] - 1 } else listOf(),
|
||||||
if (hasColor) indices.map { it[0] - 1 } else listOf(),
|
if (hasColor) indices.map { it[0] - 1 } else listOf(),
|
||||||
emptyList(),
|
if (hasTangents) indices.map { it[2] - 1 } else listOf(),
|
||||||
emptyList()
|
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()
|
||||||
|
|
||||||
75
orx-obj-loader/src/commonMain/kotlin/ObjWriter.kt
Normal file
75
orx-obj-loader/src/commonMain/kotlin/ObjWriter.kt
Normal 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()
|
||||||
|
}
|
||||||
15
orx-obj-loader/src/jvmDemo/kotlin/DemoObjCompoundRW01.kt
Normal file
15
orx-obj-loader/src/jvmDemo/kotlin/DemoObjCompoundRW01.kt
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import org.openrndr.draw.DrawPrimitive
|
|||||||
import org.openrndr.draw.TransformTarget
|
import org.openrndr.draw.TransformTarget
|
||||||
import org.openrndr.draw.shadeStyle
|
import org.openrndr.draw.shadeStyle
|
||||||
import org.openrndr.extra.camera.Orbital
|
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.loadOBJasVertexBuffer
|
||||||
import org.openrndr.extra.objloader.wireframe
|
import org.openrndr.extra.objloader.wireframe
|
||||||
import org.openrndr.math.Vector3
|
import org.openrndr.math.Vector3
|
||||||
@@ -25,7 +25,7 @@ fun main() {
|
|||||||
}
|
}
|
||||||
program {
|
program {
|
||||||
val vb = loadOBJasVertexBuffer("orx-obj-loader/test-data/non-planar.obj")
|
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 {
|
val paths = md.wireframe().map {
|
||||||
Path3D.fromPoints(it, true)
|
Path3D.fromPoints(it, true)
|
||||||
44
orx-obj-loader/src/jvmMain/kotlin/OBJLoader.kt
Normal file
44
orx-obj-loader/src/jvmMain/kotlin/OBJLoader.kt
Normal 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())
|
||||||
@@ -47,6 +47,7 @@ fun VertexBuffer.saveOBJ(filePath: String) {
|
|||||||
val bb = ByteBuffer.allocateDirect(vertexCount * vertexFormat.size)
|
val bb = ByteBuffer.allocateDirect(vertexCount * vertexFormat.size)
|
||||||
bb.order(ByteOrder.nativeOrder())
|
bb.order(ByteOrder.nativeOrder())
|
||||||
read(bb)
|
read(bb)
|
||||||
|
bb.rewind()
|
||||||
|
|
||||||
val tokens = mapOf(
|
val tokens = mapOf(
|
||||||
"position" to "v",
|
"position" to "v",
|
||||||
Reference in New Issue
Block a user