[orx-mesh] Split MeshData types from orx-obj-loader

This commit is contained in:
Edwin Jakobs
2024-09-13 22:41:31 +02:00
parent 3588aecd58
commit 8238207894
16 changed files with 466 additions and 172 deletions

26
orx-mesh/build.gradle.kts Normal file
View 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"))
}
}
}
}

View 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")
}
}

View 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() }
}

View File

@@ -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()
) )
} }
} }

View 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) }
}
}

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

View 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)
}

View 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

View 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()
}
}

View File

@@ -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"))
} }

View File

@@ -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)
}
}
}
}

View File

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

View File

@@ -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)
}
) )
} }

View File

@@ -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)
}

View File

@@ -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()
)

View File

@@ -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() }
}
}