[orx-mesh, orx-mesh-generator, orx-obj-loader] Add decal and tangent tools

This commit is contained in:
Edwin Jakobs
2024-09-25 09:51:46 +02:00
parent fb3bb6f7a6
commit e016891b1d
28 changed files with 1072 additions and 82 deletions

View File

@@ -10,6 +10,7 @@ kotlin {
api(libs.openrndr.application) api(libs.openrndr.application)
api(libs.openrndr.math) api(libs.openrndr.math)
implementation(project(":orx-shapes")) implementation(project(":orx-shapes"))
api(project(":orx-mesh"))
} }
} }
@@ -20,6 +21,7 @@ kotlin {
implementation(project(":orx-mesh-generators")) implementation(project(":orx-mesh-generators"))
implementation(project(":orx-camera")) implementation(project(":orx-camera"))
implementation(project(":orx-noise")) implementation(project(":orx-noise"))
implementation(project(":orx-obj-loader"))
} }
} }
} }

View File

@@ -0,0 +1,201 @@
package org.openrndr.extra.meshgenerators.decal
import org.openrndr.extra.mesh.*
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import kotlin.math.abs
/**
* Create a decal mesh
* @param projectorMatrix
* @param size
*/
fun IMeshData.decal(
projectorMatrix: Matrix44,
size: Vector3
): IVertexData {
require(isTriangular())
val projectorMatrixInverse = projectorMatrix.inversed
val positions = vertexData.positions.slice(polygons.flatMap { it.positions }).map {
(projectorMatrixInverse * (it.xyz1)).div
}
val normals = vertexData.normals.slice(polygons.flatMap { it.normals })
val textureCoords = vertexData.textureCoords.slice(polygons.flatMap { it.textureCoords })
val colors = vertexData.colors.slice(polygons.flatMap { it.colors })
val tangents = vertexData.tangents.slice(polygons.flatMap { it.tangents })
val bitangents = vertexData.bitangents.slice(polygons.flatMap { it.bitangents })
var decalVertices: IVertexData = VertexData(positions, textureCoords, colors, normals, tangents, bitangents)
decalVertices = decalVertices.clipToPlane(size, Vector3(1.0, 0.0, 0.0))
decalVertices = decalVertices.clipToPlane(size, Vector3(-1.0, 0.0, 0.0))
decalVertices = decalVertices.clipToPlane(size, Vector3(0.0, 1.0, 0.0))
decalVertices = decalVertices.clipToPlane(size, Vector3(0.0, -1.0, 0.0))
decalVertices = decalVertices.clipToPlane(size, Vector3(0.0, 0.0, 1.0))
decalVertices = decalVertices.clipToPlane(size, Vector3(0.0, 0.0, -1.0))
val decalMesh = MutableVertexData()
for (i in decalVertices.positions.indices) {
val v = decalVertices[i]
val w = v.copy(
position = (projectorMatrix * v.position.xyz1).div,
textureCoord = v.position.xy / size.xy + Vector2(0.5)
)
decalMesh.add(w)
}
return decalMesh
}
fun IVertexData.clipToPlane(
size: Vector3,
plane: Vector3
): IVertexData {
val outVertices = MutableVertexData()
val s = 0.5 * abs(size.dot(plane))
fun clip(
v0: Point,
v1: Point, p: Vector3, s: Double
): Point {
val d0 = v0.position.dot(p) - s;
val d1 = v1.position.dot(p) - s;
val s0 = d0 / (d0 - d1)
val v = Point(
v0.position + (v1.position - v0.position) * s0,
if (v0.textureCoord != null) {
v0.textureCoord!! + (v1.textureCoord!! - v0.textureCoord!!) * s0
} else {
null
},
if (v0.color != null) {
v0.color!! + (v1.color!! - v0.color!!) * s0
} else {
null
},
if (v0.normal != null) {
v0.normal!! + (v1.normal!! - v0.normal!!) * s0
} else {
null
},
if (v0.tangent != null) {
v0.tangent!! + (v1.tangent!! - v0.tangent!!) * s0
} else {
null
},
if (v0.bitangent != null) {
v0.bitangent!! + (v1.bitangent!! - v0.bitangent!!) * s0
} else {
null
}
)
return v
}
for (i in positions.indices step 3) {
val d1 = positions[i + 0].dot(plane) - s
val d2 = positions[i + 1].dot(plane) - s
val d3 = positions[i + 2].dot(plane) - s
val v1Out = d1 > 0
val v2Out = d2 > 0
val v3Out = d3 > 0
val total = (if (v1Out) 1 else 0) + (if (v2Out) 1 else 0) + (if (v3Out) 1 else 0)
when (total) {
0 -> {
outVertices.add(this[i])
outVertices.add(this[i + 1])
outVertices.add(this[i + 2])
}
1 -> {
if (v1Out) {
val nV1 = this[i + 1]
val nV2 = this[i + 2]
val nV3 = clip(this[i], nV1, plane, s)
val nV4 = clip(this[i], nV2, plane, s)
outVertices.add(nV1)
outVertices.add(nV2)
outVertices.add(nV3)
outVertices.add(nV4)
outVertices.add(nV3)
outVertices.add(nV2)
}
if (v2Out) {
val nV1 = this[i];
val nV2 = this[i + 2];
val nV3 = clip(this[i + 1], nV1, plane, s)
val nV4 = clip(this[i + 1], nV2, plane, s)
outVertices.add(nV3)
outVertices.add(nV2)
outVertices.add(nV1)
outVertices.add(nV2)
outVertices.add(nV3)
outVertices.add(nV4)
}
if (v3Out) {
val nV1 = this[i]
val nV2 = this[i + 1]
val nV3 = clip(this[i + 2], nV1, plane, s)
val nV4 = clip(this[i + 2], nV2, plane, s)
outVertices.add(nV1)
outVertices.add(nV2)
outVertices.add(nV3)
outVertices.add(nV4)
outVertices.add(nV3)
outVertices.add(nV2)
}
}
2 -> {
if (!v1Out) {
val nV1 = this[i]
val nV2 = clip(nV1, this[i + 1], plane, s)
val nV3 = clip(nV1, this[i + 2], plane, s)
outVertices.add(nV1)
outVertices.add(nV2)
outVertices.add(nV3)
}
if (!v2Out) {
val nV1 = this[i + 1]
val nV2 = clip(nV1, this[i + 2], plane, s)
val nV3 = clip(nV1, this[i], plane, s)
outVertices.add(nV1)
outVertices.add(nV2)
outVertices.add(nV3)
}
if (!v3Out) {
val nV1 = this[i + 2]
val nV2 = clip(nV1, this[i], plane, s)
val nV3 = clip(nV1, this[i + 1], plane, s)
outVertices.add(nV1)
outVertices.add(nV2)
outVertices.add(nV3)
}
}
else -> {
}
}
}
return outVertices
}

View File

@@ -0,0 +1,63 @@
package org.openrndr.extra.meshgenerators.normals
import org.openrndr.extra.mesh.IMeshData
import org.openrndr.extra.mesh.IndexedPolygon
import org.openrndr.extra.mesh.MeshData
import org.openrndr.extra.mesh.VertexData
import org.openrndr.math.Vector3
/**
* Estimate per-vertex normals
*/
fun IMeshData.estimateNormals(): MeshData {
val normals = MutableList(vertexData.positions.size) { Vector3.ZERO }
for (polygon in polygons) {
for (p in polygon.positions) {
normals[p] += polygon.normal(vertexData)
}
}
for (i in normals.indices) {
normals[i] = normals[i].normalized
}
return MeshData(
VertexData(
vertexData.positions,
vertexData.textureCoords,
vertexData.colors,
normals,
vertexData.tangents,
vertexData.bitangents
),
polygons.map {
IndexedPolygon(it.positions, it.textureCoords, it.colors, it.positions, it.tangents, it.bitangents)
})
}
/**
* Assign vertex normals based on face normals
*/
fun IMeshData.assignFaceNormals(): MeshData {
val normals = MutableList(polygons.size) { Vector3.ZERO }
for (i in polygons.indices) {
normals[i] = polygons[i].normal(vertexData)
}
return MeshData(
VertexData(
vertexData.positions,
vertexData.textureCoords,
vertexData.colors,
normals,
vertexData.tangents,
vertexData.bitangents
),
polygons.mapIndexed { index, it ->
IndexedPolygon(it.positions, it.textureCoords, it.colors, it.positions.map {
index
}, it.tangents, it.bitangents)
})
}

View File

@@ -0,0 +1,94 @@
package org.openrndr.extra.meshgenerators.tangents
import org.openrndr.extra.mesh.*
import org.openrndr.math.Vector3
/**
* Estimate tangents from normals and texture coordinates
* https://terathon.com/blog/tangent-space.html
*/
fun IMeshData.estimateTangents(): MeshData {
require(vertexData.textureCoords.isNotEmpty()) {
"need texture coordinates to estimate tangents"
}
require(isTriangular()) {
}
val normals = MutableList(vertexData.positions.size) { Vector3.ZERO }
val tan1 = MutableList(vertexData.positions.size) { Vector3.ZERO }
val tan2 = MutableList(vertexData.positions.size) { Vector3.ZERO }
for (polygon in polygons) {
val v1 = vertexData.positions[polygon.positions[0]]
val v2 = vertexData.positions[polygon.positions[1]]
val v3 = vertexData.positions[polygon.positions[2]]
val w1 = vertexData.textureCoords[polygon.textureCoords[0]]
val w2 = vertexData.textureCoords[polygon.textureCoords[1]]
val w3 = vertexData.textureCoords[polygon.textureCoords[2]]
val x1 = (v2.x - v1.x)
val x2 = (v3.x - v1.x)
val y1 = (v2.y - v1.y)
val y2 = (v3.y - v1.y)
val z1 = (v2.z - v1.z)
val z2 = (v3.z - v1.z)
val s1 = (w2.x - w1.x)
val s2 = (w3.x - w1.x)
val t1 = (w2.y - w1.y)
val t2 = (w3.y - w1.y)
var det = s1 * t2 - s2 * t1
if (det == 0.0) det = 1.0
val r = 1.0/ (det)
val sdir = Vector3(
(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
(t2 * z1 - t1 * z2) * r
).normalized
val tdir = Vector3(
(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
(s1 * z2 - s2 * z1) * r
).normalized
tan1[polygon.positions[0]] += sdir
tan1[polygon.positions[1]] += sdir
tan1[polygon.positions[2]] += sdir
tan2[polygon.positions[0]] += tdir
tan2[polygon.positions[1]] += tdir
tan2[polygon.positions[2]] += tdir
normals[polygon.positions[0]] += vertexData.normals[polygon.normals[0]]
normals[polygon.positions[1]] += vertexData.normals[polygon.normals[1]]
normals[polygon.positions[2]] += vertexData.normals[polygon.normals[2]]
}
for (a in 0 until vertexData.positions.size) {
normals[a] = normals[a].normalized
tan1[a] = tan1[a].normalized
tan2[a] = tan2[a].normalized
val t = tan1[a]
val n = normals[a]
tan1[a] = (t - n * n.dot(t)).normalized
val w = if ((n.cross(t)).dot(tan2[a]) < 0.0f) -1.0 else 1.0
tan2[a] = n.cross((t)).normalized * w
}
return MeshData(VertexData(vertexData.positions, vertexData.textureCoords, vertexData.colors, normals, tan1, tan2),
polygons = polygons.map {
IndexedPolygon(
it.positions,
it.textureCoords,
it.colors,
normals = it.positions,
tangents = it.positions,
bitangents = it.positions
)
}
)
}

View File

@@ -0,0 +1,55 @@
package decal
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.mesh.*
import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.meshgenerators.decal.decal
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.buildTransform
import java.io.File
/**
* Demonstrate decal generator as an object slicer
*/
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val obj = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData().triangulate()
val slices = 25
val sliceStep = 0.1
val sliceWidth = 0.14
val sliceVBs = (0 until slices).map {
val projector = buildTransform {
translate(0.0, 0.0, -1.0 + it * sliceStep)
}
val decal = obj.decal(projector, Vector3(4.0, 4.0, sliceWidth))
val vb = decal.toVertexBuffer()
vb
}
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0)
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """x_fill.rgb = v_viewNormal.rgb * 0.5 + 0.5; """
}
drawer.translate(0.0, 0.0, slices * 0.5 * 0.5)
for (i in 0 until sliceVBs.size) {
drawer.vertexBuffer(sliceVBs[i], DrawPrimitive.TRIANGLES)
drawer.translate(0.0, 0.0, -0.5)
}
}
}
}
}

View File

@@ -0,0 +1,86 @@
package decal
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.isolated
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.mesh.toVertexBuffer
import org.openrndr.extra.meshgenerators.decal.decal
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.buildTransform
import java.io.File
import kotlin.math.PI
/**
* Demonstrate decal generation and rendering
*/
fun main() {
application {
configure {
width = 720
height = 720
}
program {
/** base object */
val obj = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj"))
.toMeshData() // convert from CompoundMeshData to MeshData
.triangulate() // convert to triangles, we need this for the decal generation steps
/** object [VertexBuffer] */
val objVB = obj.toVertexBuffer()
/** positions for the decal projectors */
val decalPositions = listOf(
Vector3(0.35, 0.245, 0.8),
Vector3(-0.35, 0.245, 0.8)
)
/** decal vertex buffers */
val decalVBs = decalPositions.map {
val projector = buildTransform {
translate(it)
}
val decal = obj.decal(projector, Vector3(2.0, 2.0, 0.5))
val vb = decal.toVertexBuffer()
vb
}
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0)
}
extend {
/* draw the base mesh */
drawer.isolated {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """x_fill.rgb = vec3(v_viewNormal * 0.5 + 0.5); """
}
drawer.vertexBuffer(objVB, DrawPrimitive.TRIANGLES)
}
/* draw the decals */
drawer.isolated {
for ((index, decal) in decalVBs.withIndex()) {
/* offset the projection transform to avoid z-fighting */
drawer.projection = buildTransform {
translate(0.0, 0.0, -1e-4)
} * drawer.projection
/* draw effects on the decal geometry */
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
float d = length(va_texCoord0.xy - vec2(0.5));
float sd = smoothstep(-0.01, 0.01, cos(p_time + d * 3.1415 * 2.0 * 10.0));
float l = max(0.0, va_normal.z);
x_fill = vec4(0.0, 0.0, 0.0, l * sd * 0.5); """
parameter("time", seconds * PI * 2 + index * PI)
}
drawer.vertexBuffer(decal, DrawPrimitive.TRIANGLES)
}
}
}
}
}
}

View File

@@ -3,7 +3,7 @@ import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.isolated import org.openrndr.draw.isolated
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.mesh.loadOBJMeshData import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.mesh.noise.uniform import org.openrndr.extra.mesh.noise.uniform
import org.openrndr.extra.meshgenerators.sphereMesh import org.openrndr.extra.meshgenerators.sphereMesh
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3

View File

@@ -18,6 +18,7 @@ kotlin {
api(libs.openrndr.shape) api(libs.openrndr.shape)
implementation(project(":orx-shapes")) implementation(project(":orx-shapes"))
implementation(project(":orx-mesh-generators")) implementation(project(":orx-mesh-generators"))
implementation(project(":orx-obj-loader"))
implementation(project(":orx-camera")) implementation(project(":orx-camera"))
implementation(project(":orx-noise")) implementation(project(":orx-noise"))
} }

View File

@@ -3,6 +3,9 @@ package org.openrndr.extra.mesh
import org.openrndr.draw.VertexBuffer import org.openrndr.draw.VertexBuffer
import org.openrndr.draw.vertexBuffer import org.openrndr.draw.vertexBuffer
/**
* Write compound mesh data to [VertexBuffer]
*/
fun ICompoundMeshData.toVertexBuffer(): VertexBuffer { fun ICompoundMeshData.toVertexBuffer(): VertexBuffer {
val triangulated = this.triangulate() val triangulated = this.triangulate()
@@ -19,6 +22,9 @@ fun ICompoundMeshData.toVertexBuffer(): VertexBuffer {
return vertexBuffer return vertexBuffer
} }
/**
* Convert compound mesh data to [IPolygon] compounds
*/
fun ICompoundMeshData.toPolygons(): Map<String, List<IPolygon>> { fun ICompoundMeshData.toPolygons(): Map<String, List<IPolygon>> {
return compounds.mapValues { it.value.toPolygons() } return compounds.mapValues { it.value.toPolygons() }
} }

View File

@@ -4,10 +4,7 @@ import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2 import org.openrndr.math.Vector2
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3
import org.openrndr.math.Vector4 import org.openrndr.math.Vector4
import kotlin.math.PI import kotlin.math.*
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.round
/** /**
* Indexed polygon interface * Indexed polygon interface
@@ -133,6 +130,15 @@ interface IIndexedPolygon {
return abs(round(angleSum / (2 * PI))) == 1.0 return abs(round(angleSum / (2 * PI))) == 1.0
} }
/**
* Evaluate polygon normal
*/
fun normal(vertexData: IVertexData) : Vector3 {
val u = vertexData.positions[positions[1]] - vertexData.positions[positions[0]]
val v = vertexData.positions[positions[2]] - vertexData.positions[positions[0]]
return u.cross(v).normalized
}
/** /**
* Convert to [IPolygon] * Convert to [IPolygon]
* @param vertexData the vertex data required to build the [IPolygon] * @param vertexData the vertex data required to build the [IPolygon]
@@ -146,12 +152,12 @@ interface IIndexedPolygon {
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>,
override val normals: List<Int>,
override val colors: List<Int>, override val colors: List<Int>,
override val normals: List<Int>,
override val tangents: List<Int>, override val tangents: List<Int>,
override val bitangents: List<Int> override val bitangents: List<Int>
) : IIndexedPolygon { ) : IIndexedPolygon {
private 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())
@@ -161,8 +167,8 @@ data class IndexedPolygon(
IndexedPolygon( IndexedPolygon(
positions.slice(it), positions.slice(it),
if (textureCoords.isNotEmpty()) textureCoords.slice(it) else listOf(), if (textureCoords.isNotEmpty()) textureCoords.slice(it) else listOf(),
if (normals.isNotEmpty()) normals.slice(it) else listOf(),
if (colors.isNotEmpty()) colors.slice(it) else listOf(), if (colors.isNotEmpty()) colors.slice(it) else listOf(),
if (normals.isNotEmpty()) normals.slice(it) else listOf(),
if (tangents.isNotEmpty()) tangents.slice(it) else listOf(), if (tangents.isNotEmpty()) tangents.slice(it) else listOf(),
if (bitangents.isNotEmpty()) bitangents.slice(it) else listOf() if (bitangents.isNotEmpty()) bitangents.slice(it) else listOf()
) )
@@ -187,16 +193,16 @@ data class IndexedPolygon(
textureCoords.getOrNull(it), textureCoords.getOrNull(it),
textureCoords.getOrNull(it + 1) textureCoords.getOrNull(it + 1)
), ),
listOfNotNull(
normals.getOrNull(0),
normals.getOrNull(it),
normals.getOrNull(it + 1)
),
listOfNotNull( listOfNotNull(
colors.getOrNull(0), colors.getOrNull(0),
colors.getOrNull(it + 1), colors.getOrNull(it + 1),
colors.getOrNull(it + 2) colors.getOrNull(it + 2)
), ),
listOfNotNull(
normals.getOrNull(0),
normals.getOrNull(it),
normals.getOrNull(it + 1)
),
listOfNotNull( listOfNotNull(
tangents.getOrNull(0), tangents.getOrNull(0),
tangents.getOrNull(it + 1), tangents.getOrNull(it + 1),
@@ -218,9 +224,39 @@ data class IndexedPolygon(
override fun toPolygon(vertexData: IVertexData): Polygon { override fun toPolygon(vertexData: IVertexData): Polygon {
return Polygon( return Polygon(
vertexData.positions.slice(positions), vertexData.positions.slice(positions),
vertexData.normals.slice(normals),
vertexData.textureCoords.slice(textureCoords), vertexData.textureCoords.slice(textureCoords),
vertexData.colors.slice(colors) vertexData.colors.slice(colors),
vertexData.normals.slice(normals),
vertexData.tangents.slice(tangents),
vertexData.bitangents.slice(bitangents)
)
}
/**
* Shift indices
* @param positions position index shift
* @param textureCoords texture coordinate index shift
* @param colors color index shift
* @param normals normal index shift
* @param tangents tangent index shift
* @param bitangents bitangent index shift
*
*/
fun shiftIndices(
positions: Int = 0,
textureCoords: Int = 0,
colors: Int = 0,
normals: Int = 0,
tangents: Int = 0,
bitangents: Int = 0
): IndexedPolygon {
return IndexedPolygon(
positions = this.positions.map { it + positions },
textureCoords = this.textureCoords.map { it + textureCoords },
colors = this.colors.map { it + colors },
normals = this.normals.map { it + normals },
tangents = this.tangents.map { it + tangents },
bitangents = this.bitangents.map { it + bitangents }
) )
} }
} }
@@ -240,9 +276,9 @@ data class MutableIndexedPolygon(
override fun toPolygon(vertexData: IVertexData): MutablePolygon { override fun toPolygon(vertexData: IVertexData): MutablePolygon {
return MutablePolygon( return MutablePolygon(
vertexData.positions.slice(positions).toMutableList(), vertexData.positions.slice(positions).toMutableList(),
vertexData.normals.slice(normals).toMutableList(),
vertexData.textureCoords.slice(textureCoords).toMutableList(), vertexData.textureCoords.slice(textureCoords).toMutableList(),
vertexData.colors.slice(colors).toMutableList() vertexData.colors.slice(colors).toMutableList(),
vertexData.normals.slice(normals).toMutableList()
) )
} }
} }

View File

@@ -0,0 +1,33 @@
package org.openrndr.extra.mesh
import org.openrndr.math.LinearType
import org.openrndr.math.Vector3
internal fun <T : LinearType<T>> bc(barycentric: Vector3, items: List<T>): T {
return (items[0] * barycentric.x) + (items[1] * barycentric.y) + (items[2] * barycentric.z)
}
/**
* Evaluate a point in triangle
* @param vertexData the vertex data to use
* @param barycentric the barycentric coordinates of the point to evaluate
*/
fun IIndexedPolygon.point(vertexData: VertexData, barycentric: Vector3): Point {
require(positions.size == 3)
val positions = vertexData.positions.slice(positions)
val colors = vertexData.colors.slice(colors)
val normals = vertexData.normals.slice(normals)
val tangents = vertexData.tangents.slice(tangents)
val bitangents = vertexData.bitangents.slice(bitangents)
val textureCoords = vertexData.textureCoords.slice(textureCoords)
return Point(
(if (positions.isNotEmpty()) bc(barycentric, positions) else null)!!,
if (textureCoords.isNotEmpty()) bc(barycentric, textureCoords) else null,
if (colors.isNotEmpty()) bc(barycentric, colors) else null,
if (normals.isNotEmpty()) bc(barycentric, normals) else null,
if (tangents.isNotEmpty()) bc(barycentric, tangents) else null,
if (bitangents.isNotEmpty()) bc(barycentric, bitangents) else null
)
}

View File

@@ -8,8 +8,26 @@ import kotlin.jvm.JvmRecord
interface IMeshData { interface IMeshData {
val vertexData: IVertexData val vertexData: IVertexData
val polygons: List<IIndexedPolygon> val polygons: List<IIndexedPolygon>
/**
* Convert mesh data to triangular mesh data
*/
fun triangulate(): IMeshData fun triangulate(): IMeshData
/**
* Convert mesh data to a list of [IPolygon]
*/
fun toPolygons(): List<IPolygon> fun toPolygons(): List<IPolygon>
/**
* Join mesh data with [other] mesh data
*/
fun join(other: IMeshData): IMeshData
fun toMeshData(): MeshData
fun toMutableMeshData() : MutableMeshData
} }
/** /**
@@ -32,6 +50,94 @@ data class MeshData(
ip.toPolygon(vertexData) ip.toPolygon(vertexData)
} }
} }
override fun join(other: IMeshData): IMeshData {
if (vertexData === other.vertexData) {
@Suppress("UNCHECKED_CAST")
return MeshData(vertexData, polygons + (other.polygons as List<IndexedPolygon>))
} else {
val positionsShift: Int
val positions = if (vertexData.positions === other.vertexData.positions) {
positionsShift = 0
vertexData.positions
} else {
positionsShift = vertexData.positions.size
vertexData.positions + other.vertexData.positions
}
val textureCoordsShift: Int
val textureCoords = if (vertexData.textureCoords === other.vertexData.textureCoords) {
textureCoordsShift = 0
vertexData.textureCoords
} else {
textureCoordsShift = vertexData.textureCoords.size
vertexData.textureCoords + other.vertexData.textureCoords
}
val colorsShift: Int
val colors = if (vertexData.colors === other.vertexData.colors) {
colorsShift = 0
vertexData.colors
} else {
colorsShift = vertexData.colors.size
vertexData.colors + other.vertexData.colors
}
val normalsShift: Int
val normals = if (vertexData.normals === other.vertexData.normals) {
normalsShift = 0
vertexData.normals
} else {
normalsShift = vertexData.normals.size
vertexData.normals + other.vertexData.normals
}
val tangentsShift: Int
val tangents = if (vertexData.tangents === other.vertexData.tangents) {
tangentsShift = 0
vertexData.tangents
} else {
tangentsShift = vertexData.tangents.size
vertexData.tangents + other.vertexData.tangents
}
val bitangentsShift: Int
val bitangents = if (vertexData.bitangents === other.vertexData.bitangents) {
bitangentsShift = 0
vertexData.bitangents
} else {
bitangentsShift = vertexData.bitangents.size
vertexData.bitangents + other.vertexData.bitangents
}
return MeshData(
VertexData(
positions = positions,
textureCoords = textureCoords,
colors = colors,
normals = normals,
tangents = tangents,
bitangents = bitangents
),
polygons + other.polygons.map {
(it as IndexedPolygon).shiftIndices(
positionsShift,
textureCoordsShift,
colorsShift,
normalsShift,
tangentsShift,
bitangentsShift
)
}
)
}
}
override fun toMeshData(): MeshData = this
override fun toMutableMeshData(): MutableMeshData {
TODO("Not yet implemented")
}
} }
@@ -53,4 +159,16 @@ data class MutableMeshData(
override fun toPolygons(): List<Polygon> { override fun toPolygons(): List<Polygon> {
return polygons.map { it.toPolygon(vertexData) } return polygons.map { it.toPolygon(vertexData) }
} }
override fun join(other: IMeshData): IMeshData {
TODO("Not yet implemented")
}
override fun toMutableMeshData(): MutableMeshData {
return this
}
override fun toMeshData(): MeshData {
TODO("Not yet implemented")
}
} }

View File

@@ -1,11 +1,8 @@
package org.openrndr.extra.mesh package org.openrndr.extra.mesh
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.VertexBuffer import org.openrndr.draw.*
import org.openrndr.draw.VertexFormat import org.openrndr.math.*
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. * The [VertexFormat] for a [VertexBuffer] with positions, normals and texture coordinates.
@@ -17,6 +14,16 @@ internal val objVertexFormat = vertexFormat {
color(4) color(4)
} }
internal val objVertexFormatTangents = vertexFormat {
position(3)
normal(3)
textureCoordinate(2)
color(4)
attribute("tangent", VertexElementType.VECTOR3_FLOAT32)
attribute("bitangent", VertexElementType.VECTOR3_FLOAT32)
}
/** /**
* Determine if [IMeshData] is triangular by checking if each polygon has exactly 3 vertices * Determine if [IMeshData] is triangular by checking if each polygon has exactly 3 vertices
*/ */
@@ -30,11 +37,15 @@ fun IMeshData.isTriangular(): Boolean {
fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer { fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer {
val objects = triangulate().toPolygons() val objects = triangulate().toPolygons()
val triangleCount = objects.size val triangleCount = objects.size
val vertexBuffer = vertexBuffer ?: vertexBuffer(objVertexFormat, triangleCount * 3)
val format = if (vertexData.tangents.isNotEmpty() && vertexData.bitangents.isNotEmpty()) {
objVertexFormatTangents
} else objVertexFormat
val vertexBuffer = vertexBuffer ?: vertexBuffer(format, triangleCount * 3)
vertexBuffer.put(elementOffset) { vertexBuffer.put(elementOffset) {
objects.forEach { objects.forEach {
for (i in it.positions.indices) { for (i in it.positions.indices) {
write(it.positions[i]) write(it.positions[i])
if (it.normals.isNotEmpty()) { if (it.normals.isNotEmpty()) {
@@ -54,9 +65,164 @@ fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer?
} else { } else {
write(ColorRGBa.WHITE) write(ColorRGBa.WHITE)
} }
if (format == objVertexFormatTangents) {
write(it.tangents[i])
write(it.bitangents[i])
}
} }
} }
} }
vertexBuffer.shadow.destroy() vertexBuffer.shadow.destroy()
return vertexBuffer return vertexBuffer
}
/**
* Weld vertices
* @param positionFractBits number of bits to use for fractional representation, negative amount to skip welding
*/
fun IMeshData.weld(
positionFractBits: Int,
textureCoordFractBits: Int = -1,
colorFractBits: Int = -1,
normalFractBits: Int = -1,
tangentFractBits: Int = -1,
bitangentFractBits: Int = -1
): MeshData {
fun MutableMap<IntVector3, Int>.quantize(v: Vector3, bits: Int): Int =
getOrPut((v * (1 shl bits).toDouble()).toInt()) { this.size }
fun MutableMap<IntVector2, Int>.quantize(v: Vector2, bits: Int): Int =
getOrPut((v * (1 shl bits).toDouble()).toInt()) { this.size }
fun MutableMap<IntVector4, Int>.quantize(v: Vector4, bits: Int): Int =
getOrPut((v * (1 shl bits).toDouble()).toInt()) { this.size }
val positionMap = mutableMapOf<IntVector3, Int>()
val textureCoordMap = mutableMapOf<IntVector2, Int>()
val colorMap = mutableMapOf<IntVector4, Int>()
val normalMap = mutableMapOf<IntVector3, Int>()
val tangentMap = mutableMapOf<IntVector3, Int>()
val bitangentMap = mutableMapOf<IntVector3, Int>()
if (positionFractBits >= 0) {
for (p in vertexData.positions) {
positionMap.quantize(p, positionFractBits)
}
}
if (textureCoordFractBits >= 0) {
for (p in vertexData.textureCoords) {
textureCoordMap.quantize(p, textureCoordFractBits)
}
}
if (colorFractBits >= 0) {
for (p in vertexData.colors) {
colorMap.quantize(p.toVector4(), colorFractBits)
}
}
if (normalFractBits >= 0) {
for (p in vertexData.normals) {
normalMap.quantize(p, normalFractBits)
}
}
if (tangentFractBits >= 0) {
for (p in vertexData.tangents) {
tangentMap.quantize(p, tangentFractBits)
}
}
if (bitangentFractBits >= 0) {
for (p in vertexData.bitangents) {
bitangentMap.quantize(p, bitangentFractBits)
}
}
val reindexedPolygons = mutableListOf<IndexedPolygon>()
for (polygon in polygons) {
val positions = if (positionFractBits >= 0) {
vertexData.positions.slice(polygon.positions).map { positionMap.quantize(it, positionFractBits) }
} else {
polygon.positions
}
val textureCoords = if (textureCoordFractBits >= 0) {
vertexData.textureCoords.slice(polygon.textureCoords)
.map { textureCoordMap.quantize(it, textureCoordFractBits) }
} else {
polygon.textureCoords
}
val colors = if (colorFractBits >= 0) {
vertexData.colors.slice(polygon.colors).map { colorMap.quantize(it.toVector4(), colorFractBits) }
} else {
polygon.colors
}
val normals = if (normalFractBits >= 0) {
vertexData.normals.slice(polygon.normals).map { normalMap.quantize(it, normalFractBits) }
} else {
polygon.normals
}
val tangents = if (tangentFractBits >= 0) {
vertexData.tangents.slice(polygon.tangents).map { tangentMap.quantize(it, tangentFractBits) }
} else {
polygon.tangents
}
val bitangents = if (bitangentFractBits >= 0) {
vertexData.bitangents.slice(polygon.bitangents).map { bitangentMap.quantize(it, bitangentFractBits) }
} else {
polygon.bitangents
}
reindexedPolygons.add(IndexedPolygon(positions, textureCoords, colors, normals, tangents, bitangents))
}
val positionByIndex = vertexData.positions.associateBy { positionMap.quantize(it, positionFractBits) }
val textureCoordByIndex =
vertexData.textureCoords.associateBy { textureCoordMap.quantize(it, textureCoordFractBits) }
val colorByIndex = vertexData.colors.associateBy { colorMap.quantize(it.toVector4(), colorFractBits) }
val normalByIndex = vertexData.normals.associateBy { normalMap.quantize(it, normalFractBits) }
val tangentByIndex = vertexData.tangents.associateBy { tangentMap.quantize(it, tangentFractBits) }
val bitangentByIndex = vertexData.bitangents.associateBy { bitangentMap.quantize(it, bitangentFractBits) }
val reindexedVertexData = VertexData(
if (positionFractBits >= 0) {
(0 until positionByIndex.size).map { positionByIndex.getValue(it) }
} else {
vertexData.positions
},
if (textureCoordFractBits >= 0) {
(0 until textureCoordByIndex.size).map { textureCoordByIndex.getValue(it) }
} else {
vertexData.textureCoords
},
if (colorFractBits >= 0) {
(0 until colorByIndex.size).map { colorByIndex.getValue(it) }
} else {
vertexData.colors
},
if (normalFractBits >= 0) {
(0 until normalByIndex.size).map { normalByIndex.getValue(it) }
} else {
vertexData.normals
},
if (tangentFractBits >= 0) {
(0 until tangentByIndex.size).map { tangentByIndex.getValue(it) }
} else {
vertexData.tangents
},
if (bitangentFractBits >= 0) {
(0 until bitangentByIndex.size).map { bitangentByIndex.getValue(it) }
} else {
vertexData.bitangents
}
)
return MeshData(reindexedVertexData, reindexedPolygons)
} }

View File

@@ -0,0 +1,23 @@
package org.openrndr.extra.mesh
import org.openrndr.color.ColorRGBa
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
/**
* Point with optional attributes
* @param position position attribute
* @param textureCoord optional texture coordinate attribute
* @param color optional color attribute
* @param normal optional normal attribute
* @param tangent optional tangent attribute
* @param bitangent optional bitangent attribute
*/
data class Point(
val position: Vector3,
val textureCoord: Vector2? = null,
val color: ColorRGBa? = null,
val normal: Vector3? = null,
val tangent: Vector3? = null,
val bitangent: Vector3? =null
)

View File

@@ -14,9 +14,9 @@ import kotlin.math.min
*/ */
interface IPolygon { interface IPolygon {
val positions: List<Vector3> val positions: List<Vector3>
val normals: List<Vector3>
val textureCoords: List<Vector2> val textureCoords: List<Vector2>
val colors: List<ColorRGBa> val colors: List<ColorRGBa>
val normals: List<Vector3>
val tangents: List<Vector3> val tangents: List<Vector3>
val bitangents: List<Vector3> val bitangents: List<Vector3>
@@ -33,14 +33,14 @@ interface IPolygon {
*/ */
class Polygon( class Polygon(
override val positions: List<Vector3> = emptyList(), override val positions: List<Vector3> = emptyList(),
override val normals: List<Vector3> = emptyList(),
override val textureCoords: List<Vector2> = emptyList(), override val textureCoords: List<Vector2> = emptyList(),
override val colors: List<ColorRGBa> = emptyList(), override val colors: List<ColorRGBa> = emptyList(),
override val normals: List<Vector3> = 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(),
) : IPolygon { ) : IPolygon {
override fun transform(t: Matrix44): Polygon { override fun transform(t: Matrix44): Polygon {
return Polygon(positions.map { (t * it.xyz1).div }, normals, textureCoords, colors, tangents, bitangents) return Polygon(positions.map { (t * it.xyz1).div }, textureCoords, colors, normals, tangents, bitangents)
} }
/** /**
@@ -49,9 +49,9 @@ class Polygon(
fun toMutablePolygon(): MutablePolygon { fun toMutablePolygon(): MutablePolygon {
return MutablePolygon( return MutablePolygon(
positions.toMutableList(), positions.toMutableList(),
normals.toMutableList(),
textureCoords.toMutableList(), textureCoords.toMutableList(),
colors.toMutableList(), colors.toMutableList(),
normals.toMutableList(),
tangents.toMutableList(), tangents.toMutableList(),
bitangents.toMutableList() bitangents.toMutableList()
) )
@@ -63,9 +63,9 @@ class Polygon(
*/ */
class MutablePolygon( class MutablePolygon(
override val positions: MutableList<Vector3> = mutableListOf(), override val positions: MutableList<Vector3> = mutableListOf(),
override val normals: MutableList<Vector3> = mutableListOf(),
override val textureCoords: MutableList<Vector2> = mutableListOf(), override val textureCoords: MutableList<Vector2> = mutableListOf(),
override val colors: MutableList<ColorRGBa> = mutableListOf(), override val colors: MutableList<ColorRGBa> = mutableListOf(),
override val normals: MutableList<Vector3> = 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()
@@ -73,9 +73,9 @@ class MutablePolygon(
override fun transform(t: Matrix44): MutablePolygon { override fun transform(t: Matrix44): MutablePolygon {
return MutablePolygon( return MutablePolygon(
positions.map { (t * it.xyz1).div }.toMutableList(), positions.map { (t * it.xyz1).div }.toMutableList(),
ArrayList(normals),
ArrayList(textureCoords), ArrayList(textureCoords),
ArrayList(colors), ArrayList(colors),
ArrayList(normals),
ArrayList(tangents), ArrayList(tangents),
ArrayList(bitangents) ArrayList(bitangents)
) )
@@ -136,8 +136,8 @@ fun List<IPolygon>.toMeshData(): MeshData {
IndexedPolygon( IndexedPolygon(
positions = indices, positions = indices,
textureCoords = if (p.textureCoords.isNotEmpty()) indices else emptyList(), textureCoords = if (p.textureCoords.isNotEmpty()) indices else emptyList(),
normals = if (p.normals.isNotEmpty()) indices else emptyList(),
colors = if (p.colors.isNotEmpty()) indices else emptyList(), colors = if (p.colors.isNotEmpty()) indices else emptyList(),
normals = if (p.normals.isNotEmpty()) indices else emptyList(),
tangents = if (p.tangents.isNotEmpty()) indices else emptyList(), tangents = if (p.tangents.isNotEmpty()) indices else emptyList(),
bitangents = if (p.bitangents.isNotEmpty()) indices else emptyList() bitangents = if (p.bitangents.isNotEmpty()) indices else emptyList()
) )

View File

@@ -13,11 +13,6 @@ interface IVertexData {
*/ */
val positions: List<Vector3> val positions: List<Vector3>
/**
* Vertex normals
*/
val normals: List<Vector3>
/** /**
* Vertex texture coordinates * Vertex texture coordinates
*/ */
@@ -28,6 +23,11 @@ interface IVertexData {
*/ */
val colors: List<ColorRGBa> val colors: List<ColorRGBa>
/**
* Vertex normals
*/
val normals: List<Vector3>
/** /**
* Vertex tangents * Vertex tangents
*/ */
@@ -37,6 +37,16 @@ interface IVertexData {
* Vertex bitangents * Vertex bitangents
*/ */
val bitangents: List<Vector3> val bitangents: List<Vector3>
/**
* Convert to [VertexData]
*/
fun toVertexData() : VertexData
/**
* Convert to [MutableVertexData]
*/
fun toMutableVertexData() : MutableVertexData
} }
/** /**
@@ -44,26 +54,23 @@ interface IVertexData {
*/ */
class VertexData( class VertexData(
override val positions: List<Vector3> = emptyList(), override val positions: List<Vector3> = emptyList(),
override val normals: List<Vector3> = emptyList(),
override val textureCoords: List<Vector2> = emptyList(), override val textureCoords: List<Vector2> = emptyList(),
override val colors: List<ColorRGBa> = emptyList(), override val colors: List<ColorRGBa> = emptyList(),
override val normals: List<Vector3> = 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 {
/** override fun toVertexData(): VertexData = this
* Convert to [MutableVertexData]
*/ override fun toMutableVertexData(): MutableVertexData = MutableVertexData(
fun toMutableVertexData(): MutableVertexData { positions.toMutableList(),
return MutableVertexData( textureCoords.toMutableList(),
positions.toMutableList(), colors.toMutableList(),
normals.toMutableList(), normals.toMutableList(),
textureCoords.toMutableList(), tangents.toMutableList(),
colors.toMutableList(), bitangents.toMutableList()
tangents.toMutableList(), )
bitangents.toMutableList()
)
}
} }
@@ -72,24 +79,54 @@ class VertexData(
*/ */
class MutableVertexData( class MutableVertexData(
override val positions: MutableList<Vector3> = mutableListOf(), override val positions: MutableList<Vector3> = mutableListOf(),
override val normals: MutableList<Vector3> = mutableListOf(),
override val textureCoords: MutableList<Vector2> = mutableListOf(), override val textureCoords: MutableList<Vector2> = mutableListOf(),
override val colors: MutableList<ColorRGBa> = mutableListOf(), override val colors: MutableList<ColorRGBa> = mutableListOf(),
override val normals: MutableList<Vector3> = 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 {
/** override fun toVertexData(): VertexData = VertexData(
* Convert to [VertexData] positions.toList(),
*/ textureCoords.toList(),
fun toVertexData(): VertexData { colors.toList(),
return VertexData( normals.toList(),
positions.toList(), tangents.toList(),
normals.toList(), bitangents.toList()
textureCoords.toList(), )
colors.toList(),
tangents.toList(), override fun toMutableVertexData(): MutableVertexData = this
bitangents.toList() }
)
} /**
* Add [point] to vertex data
*/
fun MutableVertexData.add(point: Point) {
positions.add(point.position)
point.color?.let { colors.add(it) }
point.textureCoord?.let { textureCoords.add(it) }
point.normal?.let { normals.add(it) }
point.tangent?.let { tangents.add(it) }
point.bitangent?.let { bitangents.add(it) }
}
/**
* Retrieve [Point] from vertex data
*/
operator fun IVertexData.get(
index: Int,
textureCoordsIndex: Int = index,
colorsIndex: Int = index,
normalsIndex: Int = index,
tangentsIndex: Int = index,
bitangentsIndex: Int = index
): Point {
return Point(
positions[index],
textureCoords.getOrNull(textureCoordsIndex),
colors.getOrNull(colorsIndex),
normals.getOrNull(normalsIndex),
tangents.getOrNull(tangentsIndex),
bitangents.getOrNull(bitangentsIndex)
)
} }

View File

@@ -0,0 +1,68 @@
package org.openrndr.extra.mesh
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.VertexBuffer
import org.openrndr.draw.vertexBuffer
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
/**
* Convert vertex data to [VertexBuffer]. Assumes every 3 consecutive vertices encode a triangle.
*/
fun IVertexData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer {
val triangleCount = positions.size / 3
val vertexBuffer = vertexBuffer ?: vertexBuffer(objVertexFormat, triangleCount * 3)
vertexBuffer.put(elementOffset) {
var offset = 0
for (triangle in 0 until triangleCount) {
for (i in 0 until 3) {
write(positions[offset])
if (normals.isNotEmpty()) {
write(normals[offset])
} else {
write(Vector3.ZERO)
}
if (textureCoords.isNotEmpty()) {
write(textureCoords[offset])
} else {
write(Vector2.ZERO)
}
if (colors.isNotEmpty()) {
write(colors[offset])
} else {
write(ColorRGBa.WHITE)
}
offset++
}
}
}
vertexBuffer.shadow.destroy()
return vertexBuffer
}
/**
* Convert vertex data to [MeshData]. Assumes every 3 consecutive vertices encode a triangle.
*/
fun VertexData.toMeshData(): MeshData {
val polygons = mutableListOf<IndexedPolygon>()
val triangleCount = positions.size / 3
for (t in 0 until triangleCount) {
val indices = listOf(t * 3, t * 3 + 1, t * 3 + 2)
polygons.add(
IndexedPolygon(
indices,
if (textureCoords.isNotEmpty()) indices else emptyList(),
if (colors.isNotEmpty()) indices else emptyList(),
if (normals.isNotEmpty()) indices else emptyList(),
if (tangents.isNotEmpty()) indices else emptyList(),
if (bitangents.isNotEmpty()) indices else emptyList()
)
)
}
return MeshData(this, polygons)
}

View File

@@ -9,7 +9,6 @@ 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 * Extract wireframe from compound mesh data
*/ */

View File

@@ -2,8 +2,6 @@ package org.openrndr.extra.mesh
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.VertexBuffer import org.openrndr.draw.VertexBuffer
import org.openrndr.extra.mesh.Polygon
import org.openrndr.extra.mesh.objVertexFormat
import org.openrndr.math.Vector2 import org.openrndr.math.Vector2
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3
import java.nio.ByteBuffer import java.nio.ByteBuffer
@@ -53,7 +51,7 @@ fun VertexBuffer.toPolygons(vertexCount: Int = this.vertexCount): List<Polygon>
textureCoordinates.add(buffer.getVector2()) textureCoordinates.add(buffer.getVector2())
colors.add(buffer.getColorRGBa()) colors.add(buffer.getColorRGBa())
} }
polygons.add(Polygon(positions, normals, textureCoordinates, colors)) polygons.add(Polygon(positions, textureCoordinates, colors, normals))
} }
return polygons return polygons
} }

View File

@@ -1,7 +1,8 @@
package org.openrndr.extra.mesh 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.extra.mesh.*
import org.openrndr.math.Vector2 import org.openrndr.math.Vector2
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3
@@ -79,8 +80,8 @@ fun readObjMeshData(lines: Iterable<String>): CompoundMeshData {
IndexedPolygon( IndexedPolygon(
if (hasPosition) indices.map { it[0] - 1 } else listOf(), if (hasPosition) indices.map { it[0] - 1 } else listOf(),
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 (hasColor) indices.map { it[0] - 1 } else listOf(), if (hasColor) indices.map { it[0] - 1 } else listOf(),
if (hasNormal) indices.map { it[2] - 1 } else listOf(),
if (hasTangents) indices.map { it[2] - 1 } else listOf(), if (hasTangents) indices.map { it[2] - 1 } else listOf(),
if (hasBitangents) indices.map { it[2] - 1 } else listOf() if (hasBitangents) indices.map { it[2] - 1 } else listOf()
) )
@@ -95,7 +96,7 @@ fun readObjMeshData(lines: Iterable<String>): CompoundMeshData {
} }
} }
val vertexData = VertexData(positions, normals, textureCoords, colors) val vertexData = VertexData(positions, textureCoords, colors, normals)
return CompoundMeshData( return CompoundMeshData(
vertexData, vertexData,
meshes.mapValues { meshes.mapValues {

View File

@@ -1,4 +1,6 @@
package org.openrndr.extra.mesh package org.openrndr.extra.objloader
import org.openrndr.extra.mesh.ICompoundMeshData
/** /**
* Convert mesh data to Wavefront OBJ representation * Convert mesh data to Wavefront OBJ representation

View File

@@ -1,6 +1,6 @@
import org.openrndr.application import org.openrndr.application
import org.openrndr.extra.mesh.loadOBJMeshData import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.mesh.toObj import org.openrndr.extra.objloader.toObj
import java.io.File import java.io.File
fun main() { fun main() {

View File

@@ -3,7 +3,7 @@ import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DepthTestPass import org.openrndr.draw.DepthTestPass
import org.openrndr.draw.DrawPrimitive import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle import org.openrndr.draw.shadeStyle
import org.openrndr.extra.mesh.loadOBJasVertexBuffer import org.openrndr.extra.objloader.loadOBJasVertexBuffer
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3
fun main() = application { fun main() = application {

View File

@@ -1,7 +1,7 @@
import org.openrndr.application import org.openrndr.application
import org.openrndr.draw.loadFont import org.openrndr.draw.loadFont
import org.openrndr.extra.mesh.loadOBJasVertexBuffer import org.openrndr.extra.objloader.loadOBJasVertexBuffer
import org.openrndr.extra.mesh.saveOBJ import org.openrndr.extra.objloader.saveOBJ
fun main() = application { fun main() = application {
configure { configure {

View File

@@ -2,7 +2,7 @@ import org.openrndr.application
import org.openrndr.draw.loadFont import org.openrndr.draw.loadFont
import org.openrndr.extra.meshgenerators.buildTriangleMesh import org.openrndr.extra.meshgenerators.buildTriangleMesh
import org.openrndr.extra.meshgenerators.sphere import org.openrndr.extra.meshgenerators.sphere
import org.openrndr.extra.mesh.saveOBJ import org.openrndr.extra.objloader.saveOBJ
fun main() = application { fun main() = application {
configure { configure {

View File

@@ -8,8 +8,8 @@ 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.mesh.readObjMeshData import org.openrndr.extra.objloader.readObjMeshData
import org.openrndr.extra.mesh.loadOBJasVertexBuffer import org.openrndr.extra.objloader.loadOBJasVertexBuffer
import org.openrndr.extra.mesh.wireframe import org.openrndr.extra.mesh.wireframe
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3
import org.openrndr.shape.Path3D import org.openrndr.shape.Path3D

View File

@@ -1,6 +1,7 @@
package org.openrndr.extra.mesh package org.openrndr.extra.objloader
import org.openrndr.draw.VertexBuffer import org.openrndr.draw.VertexBuffer
import org.openrndr.extra.mesh.IPolygon
import java.io.File import java.io.File
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URL

View File

@@ -1,4 +1,4 @@
package org.openrndr.extra.mesh package org.openrndr.extra.objloader
import org.openrndr.draw.VertexBuffer import org.openrndr.draw.VertexBuffer
import java.io.File import java.io.File