[orx-mesh] Split MeshData types from orx-obj-loader
This commit is contained in:
26
orx-mesh/build.gradle.kts
Normal file
26
orx-mesh/build.gradle.kts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
plugins {
|
||||||
|
org.openrndr.extra.convention.`kotlin-multiplatform`
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
api(libs.openrndr.application)
|
||||||
|
api(libs.openrndr.math)
|
||||||
|
api(libs.openrndr.shape)
|
||||||
|
implementation(project(":orx-shapes"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val jvmDemo by getting {
|
||||||
|
dependencies {
|
||||||
|
api(libs.openrndr.shape)
|
||||||
|
implementation(project(":orx-shapes"))
|
||||||
|
implementation(project(":orx-mesh-generators"))
|
||||||
|
implementation(project(":orx-camera"))
|
||||||
|
implementation(project(":orx-noise"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
orx-mesh/src/commonMain/kotlin/CompoundMeshData.kt
Normal file
30
orx-mesh/src/commonMain/kotlin/CompoundMeshData.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
interface ICompoundMeshData {
|
||||||
|
val vertexData: IVertexData
|
||||||
|
val compounds: Map<String, IMeshData>
|
||||||
|
|
||||||
|
fun triangulate(): ICompoundMeshData
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompoundMeshData(
|
||||||
|
override val vertexData: VertexData,
|
||||||
|
override val compounds: Map<String, MeshData>
|
||||||
|
) : ICompoundMeshData {
|
||||||
|
|
||||||
|
override fun triangulate(): CompoundMeshData {
|
||||||
|
return CompoundMeshData(vertexData, compounds.mapValues {
|
||||||
|
it.value.triangulate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MutableCompoundMeshData(
|
||||||
|
override val vertexData: MutableVertexData,
|
||||||
|
override val compounds: MutableMap<String, MutableMeshData>
|
||||||
|
) : ICompoundMeshData {
|
||||||
|
|
||||||
|
override fun triangulate(): MutableCompoundMeshData {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
24
orx-mesh/src/commonMain/kotlin/CompoundMeshDataExtensions.kt
Normal file
24
orx-mesh/src/commonMain/kotlin/CompoundMeshDataExtensions.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
import org.openrndr.draw.VertexBuffer
|
||||||
|
import org.openrndr.draw.vertexBuffer
|
||||||
|
|
||||||
|
fun ICompoundMeshData.toVertexBuffer(): VertexBuffer {
|
||||||
|
val triangulated = this.triangulate()
|
||||||
|
|
||||||
|
val triangleCount = triangulated.compounds.values.sumOf { it.polygons.size }
|
||||||
|
|
||||||
|
val vertexBuffer = vertexBuffer(objVertexFormat, triangleCount * 3)
|
||||||
|
|
||||||
|
var elementOffset = 0
|
||||||
|
for (compound in compounds) {
|
||||||
|
compound.value.toVertexBuffer(elementOffset, vertexBuffer)
|
||||||
|
elementOffset += compound.value.polygons.size * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
return vertexBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ICompoundMeshData.flattenPolygons(): Map<String, List<IPolygon>> {
|
||||||
|
return compounds.mapValues { it.value.flattenPolygons() }
|
||||||
|
}
|
||||||
@@ -9,11 +9,15 @@ import kotlin.math.abs
|
|||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
data class IndexedPolygon(
|
interface IIndexedPolygon {
|
||||||
val positions: IntArray, val textureCoords: IntArray, val normals: IntArray
|
val positions: List<Int>
|
||||||
) {
|
val textureCoords: List<Int>
|
||||||
|
val normals: List<Int>
|
||||||
|
val colors: List<Int>
|
||||||
|
val tangents: List<Int>
|
||||||
|
val bitangents: List<Int>
|
||||||
|
|
||||||
fun base(vertexData: VertexData): Matrix44 {
|
fun base(vertexData: IVertexData): Matrix44 {
|
||||||
val u = (vertexData.positions[positions[1]] - vertexData.positions[positions[0]])
|
val u = (vertexData.positions[positions[1]] - vertexData.positions[positions[0]])
|
||||||
val v = (vertexData.positions[positions[positions.size - 1]] - vertexData.positions[positions[0]])
|
val v = (vertexData.positions[positions[positions.size - 1]] - vertexData.positions[positions[0]])
|
||||||
val normal = u.cross(v)
|
val normal = u.cross(v)
|
||||||
@@ -26,7 +30,7 @@ data class IndexedPolygon(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPlanar(vertexData: VertexData, 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)]]
|
||||||
val p1 = vertexData.positions[positions[(i).mod(positions.size)]]
|
val p1 = vertexData.positions[positions[(i).mod(positions.size)]]
|
||||||
@@ -44,7 +48,7 @@ data class IndexedPolygon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isConvex(vertexData: VertexData): Boolean {
|
fun isConvex(vertexData: IVertexData): Boolean {
|
||||||
val planar = base(vertexData).inversed
|
val planar = base(vertexData).inversed
|
||||||
|
|
||||||
fun p(v: Vector3): Vector2 {
|
fun p(v: Vector3): Vector2 {
|
||||||
@@ -71,7 +75,7 @@ data class IndexedPolygon(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var angle = newDirection - oldDirection
|
var angle = newDirection - oldDirection
|
||||||
if (angle <= -Math.PI)
|
if (angle <= -PI)
|
||||||
angle += PI * 2.0
|
angle += PI * 2.0
|
||||||
|
|
||||||
if (angle > PI) {
|
if (angle > PI) {
|
||||||
@@ -95,37 +99,69 @@ data class IndexedPolygon(
|
|||||||
return abs(round(angleSum / (2 * PI))) == 1.0
|
return abs(round(angleSum / (2 * PI))) == 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tessellate(vertexData: VertexData): List<IndexedPolygon> {
|
fun toPolygon(vertexData: IVertexData): IPolygon
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class IndexedPolygon(
|
||||||
|
override val positions: List<Int>,
|
||||||
|
override val textureCoords: List<Int>,
|
||||||
|
override val normals: List<Int>,
|
||||||
|
override val colors: List<Int>,
|
||||||
|
override val tangents: List<Int>,
|
||||||
|
override val bitangents: List<Int>
|
||||||
|
|
||||||
|
) : IIndexedPolygon {
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
return triangles.windowed(3, 3).map {
|
return triangles.windowed(3, 3).map {
|
||||||
IndexedPolygon(
|
IndexedPolygon(
|
||||||
positions.sliceArray(it),
|
positions.slice(it),
|
||||||
if (textureCoords.isNotEmpty()) textureCoords.sliceArray(it) else intArrayOf(),
|
if (textureCoords.isNotEmpty()) textureCoords.slice(it) else listOf(),
|
||||||
if (normals.isNotEmpty()) normals.sliceArray(it) else intArrayOf()
|
if (normals.isNotEmpty()) normals.slice(it) else listOf(),
|
||||||
|
if (colors.isNotEmpty()) colors.slice(it) else listOf(),
|
||||||
|
if (tangents.isNotEmpty()) tangents.slice(it) else listOf(),
|
||||||
|
if (bitangents.isNotEmpty()) bitangents.slice(it) else listOf()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun triangulate(vertexData: VertexData): List<IndexedPolygon> {
|
fun triangulate(vertexData: IVertexData): List<IndexedPolygon> {
|
||||||
return when {
|
return when {
|
||||||
positions.size == 3 -> listOf(this)
|
positions.size == 3 -> listOf(this)
|
||||||
isPlanar(vertexData) && isConvex(vertexData) -> {
|
isPlanar(vertexData) && isConvex(vertexData) -> {
|
||||||
val triangleCount = positions.size - 2
|
val triangleCount = positions.size - 2
|
||||||
(0 until triangleCount).map {
|
(0 until triangleCount).map {
|
||||||
IndexedPolygon(
|
IndexedPolygon(
|
||||||
intArrayOf(positions[0], positions[it + 1], positions[it + 2]),
|
listOf(positions[0], positions[it + 1], positions[it + 2]),
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
textureCoords.getOrNull(0),
|
textureCoords.getOrNull(0),
|
||||||
textureCoords.getOrNull(it),
|
textureCoords.getOrNull(it),
|
||||||
textureCoords.getOrNull(it + 1)
|
textureCoords.getOrNull(it + 1)
|
||||||
).toIntArray(),
|
),
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
normals.getOrNull(0),
|
normals.getOrNull(0),
|
||||||
normals.getOrNull(it),
|
normals.getOrNull(it),
|
||||||
normals.getOrNull(it + 1)
|
normals.getOrNull(it + 1)
|
||||||
).toIntArray(),
|
),
|
||||||
|
listOfNotNull(
|
||||||
|
colors.getOrNull(0),
|
||||||
|
colors.getOrNull(it + 1),
|
||||||
|
colors.getOrNull(it + 2)
|
||||||
|
),
|
||||||
|
listOfNotNull(
|
||||||
|
tangents.getOrNull(0),
|
||||||
|
tangents.getOrNull(it + 1),
|
||||||
|
tangents.getOrNull(it + 2)
|
||||||
|
),
|
||||||
|
listOfNotNull(
|
||||||
|
bitangents.getOrNull(0),
|
||||||
|
bitangents.getOrNull(it + 1),
|
||||||
|
bitangents.getOrNull(it + 2)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,11 +170,31 @@ data class IndexedPolygon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toPolygon(vertexData: VertexData): Polygon {
|
override fun toPolygon(vertexData: IVertexData): Polygon {
|
||||||
return Polygon(
|
return Polygon(
|
||||||
vertexData.positions.slice(positions.toList()).toTypedArray(),
|
vertexData.positions.slice(positions),
|
||||||
vertexData.normals.slice(normals.toList()).toTypedArray(),
|
vertexData.normals.slice(normals),
|
||||||
vertexData.textureCoords.slice(textureCoords.toList()).toTypedArray()
|
vertexData.textureCoords.slice(textureCoords),
|
||||||
|
vertexData.colors.slice(colors)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MutableIndexedPolygon(
|
||||||
|
override val positions: MutableList<Int>,
|
||||||
|
override val textureCoords: MutableList<Int>,
|
||||||
|
override val normals: MutableList<Int>,
|
||||||
|
override val colors: MutableList<Int>,
|
||||||
|
override val tangents: MutableList<Int>,
|
||||||
|
override val bitangents: MutableList<Int>
|
||||||
|
) : IIndexedPolygon {
|
||||||
|
|
||||||
|
override fun toPolygon(vertexData: IVertexData): MutablePolygon {
|
||||||
|
return MutablePolygon(
|
||||||
|
vertexData.positions.slice(positions).toMutableList(),
|
||||||
|
vertexData.normals.slice(normals).toMutableList(),
|
||||||
|
vertexData.textureCoords.slice(textureCoords).toMutableList(),
|
||||||
|
vertexData.colors.slice(colors).toMutableList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
41
orx-mesh/src/commonMain/kotlin/MeshData.kt
Normal file
41
orx-mesh/src/commonMain/kotlin/MeshData.kt
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmRecord
|
||||||
|
|
||||||
|
interface IMeshData {
|
||||||
|
val vertexData: IVertexData
|
||||||
|
val polygons: List<IIndexedPolygon>
|
||||||
|
fun triangulate(): IMeshData
|
||||||
|
fun flattenPolygons(): List<IPolygon>
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmRecord
|
||||||
|
data class MeshData(
|
||||||
|
override val vertexData: VertexData,
|
||||||
|
override val polygons: List<IndexedPolygon>,
|
||||||
|
) : IMeshData {
|
||||||
|
override fun triangulate(): MeshData {
|
||||||
|
return copy(polygons = polygons.flatMap { polygon -> polygon.triangulate(vertexData) })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flattenPolygons(): List<Polygon> {
|
||||||
|
return polygons.map { ip ->
|
||||||
|
ip.toPolygon(vertexData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class MutableMeshData(
|
||||||
|
override val vertexData: MutableVertexData,
|
||||||
|
override val polygons: MutableList<IndexedPolygon>
|
||||||
|
) : IMeshData {
|
||||||
|
override fun triangulate(): MutableMeshData {
|
||||||
|
return copy(polygons = polygons.flatMap { it.triangulate(vertexData) }.toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flattenPolygons(): List<Polygon> {
|
||||||
|
return polygons.map { it.toPolygon(vertexData) }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
50
orx-mesh/src/commonMain/kotlin/MeshDataExtensions.kt
Normal file
50
orx-mesh/src/commonMain/kotlin/MeshDataExtensions.kt
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
import org.openrndr.draw.VertexBuffer
|
||||||
|
import org.openrndr.draw.VertexFormat
|
||||||
|
import org.openrndr.draw.vertexBuffer
|
||||||
|
import org.openrndr.draw.vertexFormat
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [VertexFormat] for a [VertexBuffer] with positions, normals and texture coordinates.
|
||||||
|
*/
|
||||||
|
internal val objVertexFormat = vertexFormat {
|
||||||
|
position(3)
|
||||||
|
normal(3)
|
||||||
|
textureCoordinate(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a [MeshData] instance into a [VertexBuffer]
|
||||||
|
*/
|
||||||
|
fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer {
|
||||||
|
val objects = triangulate().flattenPolygons()
|
||||||
|
val triangleCount = objects.size
|
||||||
|
val vertexBuffer = vertexBuffer ?: vertexBuffer(objVertexFormat, triangleCount * 3)
|
||||||
|
|
||||||
|
vertexBuffer.put(elementOffset) {
|
||||||
|
objects.forEach {
|
||||||
|
|
||||||
|
for (i in it.positions.indices) {
|
||||||
|
write(it.positions[i])
|
||||||
|
if (it.normals.isNotEmpty()) {
|
||||||
|
write(it.normals[i])
|
||||||
|
} else {
|
||||||
|
val d0 = it.positions[2] - it.positions[0]
|
||||||
|
val d1 = it.positions[1] - it.positions[0]
|
||||||
|
write(d0.normalized.cross(d1.normalized).normalized)
|
||||||
|
}
|
||||||
|
if (it.textureCoords.isNotEmpty()) {
|
||||||
|
write(it.textureCoords[i])
|
||||||
|
} else {
|
||||||
|
write(Vector2.ZERO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vertexBuffer.shadow.destroy()
|
||||||
|
return vertexBuffer
|
||||||
|
}
|
||||||
103
orx-mesh/src/commonMain/kotlin/Polygon.kt
Normal file
103
orx-mesh/src/commonMain/kotlin/Polygon.kt
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.shape.Box
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
interface IPolygon {
|
||||||
|
val positions: List<Vector3>
|
||||||
|
val normals: List<Vector3>
|
||||||
|
val textureCoords: List<Vector2>
|
||||||
|
val colors: List<ColorRGBa>
|
||||||
|
val tangents: List<Vector3>
|
||||||
|
val bitangents: List<Vector3>
|
||||||
|
|
||||||
|
fun transform(t: Matrix44): IPolygon
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 3D Polygon
|
||||||
|
*
|
||||||
|
* @property positions Vertex 3D positions
|
||||||
|
* @property normals Vertex 3D normals
|
||||||
|
* @property textureCoords Vertex 2D texture coordinates
|
||||||
|
* @constructor Create empty 3D Polygon
|
||||||
|
*/
|
||||||
|
class Polygon(
|
||||||
|
override val positions: List<Vector3> = emptyList(),
|
||||||
|
override val normals: List<Vector3> = emptyList(),
|
||||||
|
override val textureCoords: List<Vector2> = emptyList(),
|
||||||
|
override val colors: List<ColorRGBa> = emptyList(),
|
||||||
|
override val tangents: List<Vector3> = emptyList(),
|
||||||
|
override val bitangents: List<Vector3> = emptyList(),
|
||||||
|
) : IPolygon {
|
||||||
|
override fun transform(t: Matrix44): Polygon {
|
||||||
|
return Polygon(positions.map { (t * it.xyz1).div }, normals, textureCoords, colors, tangents, bitangents)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [MutablePolygon] by copying
|
||||||
|
*/
|
||||||
|
fun toMutablePolygon(): MutablePolygon {
|
||||||
|
return MutablePolygon(
|
||||||
|
positions.toMutableList(),
|
||||||
|
normals.toMutableList(),
|
||||||
|
textureCoords.toMutableList(),
|
||||||
|
colors.toMutableList(),
|
||||||
|
tangents.toMutableList(),
|
||||||
|
bitangents.toMutableList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MutablePolygon(
|
||||||
|
override val positions: MutableList<Vector3> = mutableListOf(),
|
||||||
|
override val normals: MutableList<Vector3> = mutableListOf(),
|
||||||
|
override val textureCoords: MutableList<Vector2> = mutableListOf(),
|
||||||
|
override val colors: MutableList<ColorRGBa> = mutableListOf(),
|
||||||
|
override val tangents: MutableList<Vector3> = mutableListOf(),
|
||||||
|
override val bitangents: MutableList<Vector3> = mutableListOf()
|
||||||
|
|
||||||
|
) : IPolygon {
|
||||||
|
override fun transform(t: Matrix44): MutablePolygon {
|
||||||
|
return MutablePolygon(
|
||||||
|
positions.map { (t * it.xyz1).div }.toMutableList(),
|
||||||
|
ArrayList(normals),
|
||||||
|
ArrayList(textureCoords),
|
||||||
|
ArrayList(colors),
|
||||||
|
ArrayList(tangents),
|
||||||
|
ArrayList(bitangents)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the 3D bounding box of a list of [IPolygon].
|
||||||
|
*/
|
||||||
|
fun bounds(polygons: List<IPolygon>): Box {
|
||||||
|
var minX = Double.POSITIVE_INFINITY
|
||||||
|
var minY = Double.POSITIVE_INFINITY
|
||||||
|
var minZ = Double.POSITIVE_INFINITY
|
||||||
|
|
||||||
|
var maxX = Double.NEGATIVE_INFINITY
|
||||||
|
var maxY = Double.NEGATIVE_INFINITY
|
||||||
|
var maxZ = Double.NEGATIVE_INFINITY
|
||||||
|
|
||||||
|
polygons.forEach {
|
||||||
|
it.positions.forEach { pos ->
|
||||||
|
minX = min(minX, pos.x)
|
||||||
|
minY = min(minY, pos.y)
|
||||||
|
minZ = min(minZ, pos.z)
|
||||||
|
|
||||||
|
maxX = max(maxX, pos.x)
|
||||||
|
maxY = max(maxY, pos.y)
|
||||||
|
maxZ = max(maxZ, pos.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Box(Vector3(minX, minY, minZ), maxX - minX, maxY - minY, maxZ - minZ)
|
||||||
|
}
|
||||||
59
orx-mesh/src/commonMain/kotlin/VertexData.kt
Normal file
59
orx-mesh/src/commonMain/kotlin/VertexData.kt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertex data interface
|
||||||
|
*/
|
||||||
|
interface IVertexData {
|
||||||
|
/**
|
||||||
|
* Vertex positions
|
||||||
|
*/
|
||||||
|
val positions: List<Vector3>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertex normals
|
||||||
|
*/
|
||||||
|
val normals: List<Vector3>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertex texture coordinates
|
||||||
|
*/
|
||||||
|
val textureCoords: List<Vector2>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertex colors
|
||||||
|
*/
|
||||||
|
val colors: List<ColorRGBa>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertex tangents
|
||||||
|
*/
|
||||||
|
val tangents: List<Vector3>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertex bitangents
|
||||||
|
*/
|
||||||
|
val bitangents: List<Vector3>
|
||||||
|
}
|
||||||
|
|
||||||
|
class VertexData(
|
||||||
|
override val positions: List<Vector3> = emptyList(),
|
||||||
|
override val normals: List<Vector3> = emptyList(),
|
||||||
|
override val textureCoords: List<Vector2> = emptyList(),
|
||||||
|
override val colors: List<ColorRGBa> = emptyList(),
|
||||||
|
override val tangents: List<Vector3> = emptyList(),
|
||||||
|
override val bitangents: List<Vector3> = emptyList()
|
||||||
|
) : IVertexData
|
||||||
|
|
||||||
|
class MutableVertexData(
|
||||||
|
override val positions: MutableList<Vector3> = mutableListOf(),
|
||||||
|
override val normals: MutableList<Vector3> = mutableListOf(),
|
||||||
|
override val textureCoords: MutableList<Vector2> = mutableListOf(),
|
||||||
|
override val colors: MutableList<ColorRGBa> = mutableListOf(),
|
||||||
|
override val tangents: MutableList<Vector3> = mutableListOf(),
|
||||||
|
override val bitangents: MutableList<Vector3> = mutableListOf()
|
||||||
|
) : IVertexData
|
||||||
13
orx-mesh/src/commonMain/kotlin/Wireframe.kt
Normal file
13
orx-mesh/src/commonMain/kotlin/Wireframe.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package org.openrndr.extra.objloader
|
||||||
|
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
|
||||||
|
fun IMeshData.wireframe(): List<List<Vector3>> {
|
||||||
|
return polygons.map { ip -> ip.toPolygon(this.vertexData).positions.toList() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ICompoundMeshData.wireframe(): List<List<Vector3>> {
|
||||||
|
return compounds.values.flatMap {
|
||||||
|
it.wireframe()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ dependencies {
|
|||||||
implementation(libs.openrndr.application)
|
implementation(libs.openrndr.application)
|
||||||
implementation(libs.openrndr.math)
|
implementation(libs.openrndr.math)
|
||||||
implementation(libs.openrndr.ffmpeg)
|
implementation(libs.openrndr.ffmpeg)
|
||||||
|
api(project(":orx-mesh"))
|
||||||
demoImplementation(project(":orx-camera"))
|
demoImplementation(project(":orx-camera"))
|
||||||
demoImplementation(project(":orx-mesh-generators"))
|
demoImplementation(project(":orx-mesh-generators"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.openrndr.extra.objloader
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class MeshData(val vertexData: VertexData, val polygonGroups: Map<String, List<IndexedPolygon>>) {
|
|
||||||
fun triangulate(): MeshData {
|
|
||||||
return copy(polygonGroups = polygonGroups.mapValues {
|
|
||||||
it.value.flatMap { polygon -> polygon.triangulate(vertexData) }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun flattenPolygons(): Map<String, List<Polygon>> {
|
|
||||||
return polygonGroups.mapValues {
|
|
||||||
it.value.map { ip ->
|
|
||||||
ip.toPolygon(vertexData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package org.openrndr.extra.objloader
|
|
||||||
|
|
||||||
import org.openrndr.draw.VertexBuffer
|
|
||||||
import org.openrndr.draw.VertexFormat
|
|
||||||
import org.openrndr.draw.vertexBuffer
|
|
||||||
import org.openrndr.draw.vertexFormat
|
|
||||||
import org.openrndr.math.Vector2
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The [VertexFormat] for a [VertexBuffer] with positions, normals and texture coordinates.
|
|
||||||
*/
|
|
||||||
private val objVertexFormat = vertexFormat {
|
|
||||||
position(3)
|
|
||||||
normal(3)
|
|
||||||
textureCoordinate(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a [MeshData] instance into a [VertexBuffer]
|
|
||||||
*/
|
|
||||||
fun MeshData.toVertexBuffer() : VertexBuffer {
|
|
||||||
val objects = triangulate().flattenPolygons()
|
|
||||||
val triangleCount = objects.values.sumOf { it.size }
|
|
||||||
val vertexBuffer = vertexBuffer(objVertexFormat, triangleCount * 3)
|
|
||||||
|
|
||||||
vertexBuffer.put {
|
|
||||||
objects.entries.forEach {
|
|
||||||
it.value.forEach {
|
|
||||||
for (i in it.positions.indices) {
|
|
||||||
write(it.positions[i])
|
|
||||||
if (it.normals.isNotEmpty()) {
|
|
||||||
write(it.normals[i])
|
|
||||||
} else {
|
|
||||||
val d0 = it.positions[2] - it.positions[0]
|
|
||||||
val d1 = it.positions[1] - it.positions[0]
|
|
||||||
write(d0.normalized.cross(d1.normalized).normalized)
|
|
||||||
}
|
|
||||||
if (it.textureCoords.isNotEmpty()) {
|
|
||||||
write(it.textureCoords[i])
|
|
||||||
} else {
|
|
||||||
write(Vector2.ZERO)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vertexBuffer.shadow.destroy()
|
|
||||||
return vertexBuffer
|
|
||||||
}
|
|
||||||
@@ -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.math.*
|
import org.openrndr.math.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -10,7 +11,7 @@ import java.net.URL
|
|||||||
* Loads an OBJ file as a Map of names to lists of [Polygon].
|
* 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.
|
* Use this method to access the loaded OBJ data from the CPU.
|
||||||
*/
|
*/
|
||||||
fun loadOBJ(fileOrUrl: String): Map<String, List<Polygon>> {
|
fun loadOBJ(fileOrUrl: String): Map<String, List<IPolygon>> {
|
||||||
return try {
|
return try {
|
||||||
val url = URL(fileOrUrl)
|
val url = URL(fileOrUrl)
|
||||||
loadOBJ(url)
|
loadOBJ(url)
|
||||||
@@ -43,15 +44,16 @@ fun loadOBJEx(file: File) = loadOBJMeshData(file.readLines())
|
|||||||
fun loadOBJ(url: URL) = loadOBJ(url.readText().split("\n"))
|
fun loadOBJ(url: URL) = loadOBJ(url.readText().split("\n"))
|
||||||
fun loadOBJEx(url: URL) = loadOBJMeshData(url.readText().split("\n"))
|
fun loadOBJEx(url: URL) = loadOBJMeshData(url.readText().split("\n"))
|
||||||
|
|
||||||
fun loadOBJ(lines: List<String>): Map<String, List<Polygon>> = loadOBJMeshData(lines).triangulate().flattenPolygons()
|
fun loadOBJ(lines: List<String>): Map<String, List<IPolygon>> = loadOBJMeshData(lines).triangulate().flattenPolygons()
|
||||||
|
|
||||||
|
|
||||||
fun loadOBJMeshData(file: File) = loadOBJMeshData(file.readLines())
|
fun loadOBJMeshData(file: File) = loadOBJMeshData(file.readLines())
|
||||||
fun loadOBJMeshData(lines: List<String>): MeshData {
|
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 textureCoords = mutableListOf<Vector2>()
|
val textureCoords = mutableListOf<Vector2>()
|
||||||
|
val colors = mutableListOf<ColorRGBa>()
|
||||||
var activeMesh = mutableListOf<IndexedPolygon>()
|
var activeMesh = mutableListOf<IndexedPolygon>()
|
||||||
|
|
||||||
lines.forEach { line ->
|
lines.forEach { line ->
|
||||||
@@ -60,7 +62,32 @@ fun loadOBJMeshData(lines: List<String>): MeshData {
|
|||||||
|
|
||||||
if (tokens.isNotEmpty()) {
|
if (tokens.isNotEmpty()) {
|
||||||
when (tokens[0]) {
|
when (tokens[0]) {
|
||||||
"v" -> positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
"v" -> {
|
||||||
|
when (tokens.size) {
|
||||||
|
3, 4 -> {
|
||||||
|
positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
||||||
|
colors += ColorRGBa.WHITE
|
||||||
|
}
|
||||||
|
|
||||||
|
6 -> {
|
||||||
|
positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
||||||
|
colors += ColorRGBa(tokens[4].toDouble(), tokens[5].toDouble(), tokens[6].toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
7 -> {
|
||||||
|
positions += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
||||||
|
colors += ColorRGBa(
|
||||||
|
tokens[4].toDouble(),
|
||||||
|
tokens[5].toDouble(),
|
||||||
|
tokens[6].toDouble(),
|
||||||
|
tokens[7].toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("vertex has ${tokens.size - 1} components, loader only supports 3/4/6/7 components")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"vn" -> normals += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
"vn" -> normals += Vector3(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble())
|
||||||
"vt" -> textureCoords += Vector2(tokens[1].toDouble(), tokens[2].toDouble())
|
"vt" -> textureCoords += Vector2(tokens[1].toDouble(), tokens[2].toDouble())
|
||||||
"g" -> {
|
"g" -> {
|
||||||
@@ -75,12 +102,16 @@ fun loadOBJMeshData(lines: List<String>): MeshData {
|
|||||||
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
|
||||||
|
|
||||||
activeMesh.add(
|
activeMesh.add(
|
||||||
IndexedPolygon(
|
IndexedPolygon(
|
||||||
if (hasPosition) indices.map { it[0] - 1 }.toIntArray() else intArrayOf(),
|
if (hasPosition) indices.map { it[0] - 1 } else listOf(),
|
||||||
if (hasUV) indices.map { it[1] - 1 }.toIntArray() else intArrayOf(),
|
if (hasUV) indices.map { it[1] - 1 } else listOf(),
|
||||||
if (hasNormal) indices.map { it[2] - 1 }.toIntArray() else intArrayOf()
|
if (hasNormal) indices.map { it[2] - 1 } else listOf(),
|
||||||
|
if (hasColor) indices.map { it[0] - 1 } else listOf(),
|
||||||
|
emptyList(),
|
||||||
|
emptyList()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,8 +124,11 @@ fun loadOBJMeshData(lines: List<String>): MeshData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MeshData(
|
val vertexData = VertexData(positions, normals, textureCoords, colors)
|
||||||
VertexData(positions.toTypedArray(), normals.toTypedArray(), textureCoords.toTypedArray()),
|
return CompoundMeshData(
|
||||||
meshes
|
vertexData,
|
||||||
|
meshes.mapValues {
|
||||||
|
MeshData(vertexData, it.value)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package org.openrndr.extra.objloader
|
|
||||||
|
|
||||||
import org.openrndr.math.Matrix44
|
|
||||||
import org.openrndr.math.Vector2
|
|
||||||
import org.openrndr.math.Vector3
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A 3D Polygon
|
|
||||||
*
|
|
||||||
* @property positions Vertex 3D positions
|
|
||||||
* @property normals Vertex 3D normals
|
|
||||||
* @property textureCoords Vertex 2D texture coordinates
|
|
||||||
* @constructor Create empty 3D Polygon
|
|
||||||
*/
|
|
||||||
class Polygon(
|
|
||||||
val positions: Array<Vector3> = emptyArray(),
|
|
||||||
val normals: Array<Vector3> = emptyArray(),
|
|
||||||
val textureCoords: Array<Vector2> = emptyArray()
|
|
||||||
) {
|
|
||||||
fun transform(t: Matrix44): Polygon {
|
|
||||||
return Polygon(positions.map { (t * it.xyz1).div }.toTypedArray(), normals, textureCoords)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A 3D Box defined by an anchor point ([corner]), [width], [height] and [depth].
|
|
||||||
*/
|
|
||||||
class Box(val corner: Vector3, val width: Double, val height: Double, val depth: Double)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the 3D bounding box of a list of [Polygon].
|
|
||||||
*/
|
|
||||||
fun bounds(polygons: List<Polygon>): Box {
|
|
||||||
var minX = Double.POSITIVE_INFINITY
|
|
||||||
var minY = Double.POSITIVE_INFINITY
|
|
||||||
var minZ = Double.POSITIVE_INFINITY
|
|
||||||
|
|
||||||
var maxX = Double.NEGATIVE_INFINITY
|
|
||||||
var maxY = Double.NEGATIVE_INFINITY
|
|
||||||
var maxZ = Double.NEGATIVE_INFINITY
|
|
||||||
|
|
||||||
polygons.forEach {
|
|
||||||
it.positions.forEach { pos ->
|
|
||||||
minX = min(minX, pos.x)
|
|
||||||
minY = min(minY, pos.y)
|
|
||||||
minZ = min(minZ, pos.z)
|
|
||||||
|
|
||||||
maxX = max(maxX, pos.x)
|
|
||||||
maxY = max(maxY, pos.y)
|
|
||||||
maxZ = max(maxZ, pos.z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Box(Vector3(minX, minY, minZ), maxX - minX, maxY - minY, maxZ - minZ)
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package org.openrndr.extra.objloader
|
|
||||||
|
|
||||||
import org.openrndr.math.Vector2
|
|
||||||
import org.openrndr.math.Vector3
|
|
||||||
|
|
||||||
class VertexData(
|
|
||||||
val positions: Array<Vector3> = emptyArray(),
|
|
||||||
val normals: Array<Vector3> = emptyArray(),
|
|
||||||
val textureCoords: Array<Vector2> = emptyArray()
|
|
||||||
)
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.openrndr.extra.objloader
|
|
||||||
|
|
||||||
import org.openrndr.math.Vector3
|
|
||||||
|
|
||||||
fun MeshData.wireframe() : List<List<Vector3>> {
|
|
||||||
return polygonGroups.values.flatMap {
|
|
||||||
it.map { ip -> ip.toPolygon(this.vertexData).positions.toList() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user