[orx-mesh, orx-mesh-generator, orx-obj-loader] Add decal and tangent tools
This commit is contained in:
@@ -10,6 +10,7 @@ kotlin {
|
||||
api(libs.openrndr.application)
|
||||
api(libs.openrndr.math)
|
||||
implementation(project(":orx-shapes"))
|
||||
api(project(":orx-mesh"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +21,7 @@ kotlin {
|
||||
implementation(project(":orx-mesh-generators"))
|
||||
implementation(project(":orx-camera"))
|
||||
implementation(project(":orx-noise"))
|
||||
implementation(project(":orx-obj-loader"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
201
orx-mesh-generators/src/commonMain/kotlin/decal/Decal.kt
Normal file
201
orx-mesh-generators/src/commonMain/kotlin/decal/Decal.kt
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
55
orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal01.kt
Normal file
55
orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal01.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal02.kt
Normal file
86
orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal02.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ 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.mesh.loadOBJMeshData
|
||||
import org.openrndr.extra.objloader.loadOBJMeshData
|
||||
import org.openrndr.extra.mesh.noise.uniform
|
||||
import org.openrndr.extra.meshgenerators.sphereMesh
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
@@ -18,6 +18,7 @@ kotlin {
|
||||
api(libs.openrndr.shape)
|
||||
implementation(project(":orx-shapes"))
|
||||
implementation(project(":orx-mesh-generators"))
|
||||
implementation(project(":orx-obj-loader"))
|
||||
implementation(project(":orx-camera"))
|
||||
implementation(project(":orx-noise"))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.openrndr.extra.mesh
|
||||
import org.openrndr.draw.VertexBuffer
|
||||
import org.openrndr.draw.vertexBuffer
|
||||
|
||||
/**
|
||||
* Write compound mesh data to [VertexBuffer]
|
||||
*/
|
||||
fun ICompoundMeshData.toVertexBuffer(): VertexBuffer {
|
||||
val triangulated = this.triangulate()
|
||||
|
||||
@@ -19,6 +22,9 @@ fun ICompoundMeshData.toVertexBuffer(): VertexBuffer {
|
||||
return vertexBuffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert compound mesh data to [IPolygon] compounds
|
||||
*/
|
||||
fun ICompoundMeshData.toPolygons(): Map<String, List<IPolygon>> {
|
||||
return compounds.mapValues { it.value.toPolygons() }
|
||||
}
|
||||
@@ -4,10 +4,7 @@ import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.math.Vector4
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.round
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
* Indexed polygon interface
|
||||
@@ -133,6 +130,15 @@ interface IIndexedPolygon {
|
||||
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]
|
||||
* @param vertexData the vertex data required to build the [IPolygon]
|
||||
@@ -146,12 +152,12 @@ interface IIndexedPolygon {
|
||||
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 normals: List<Int>,
|
||||
override val tangents: List<Int>,
|
||||
override val bitangents: List<Int>
|
||||
|
||||
) : IIndexedPolygon {
|
||||
) : IIndexedPolygon {
|
||||
|
||||
private fun tessellate(vertexData: IVertexData): List<IndexedPolygon> {
|
||||
val points = vertexData.positions.slice(positions.toList())
|
||||
@@ -161,8 +167,8 @@ data class IndexedPolygon(
|
||||
IndexedPolygon(
|
||||
positions.slice(it),
|
||||
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 (normals.isNotEmpty()) normals.slice(it) else listOf(),
|
||||
if (tangents.isNotEmpty()) tangents.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 + 1)
|
||||
),
|
||||
listOfNotNull(
|
||||
normals.getOrNull(0),
|
||||
normals.getOrNull(it),
|
||||
normals.getOrNull(it + 1)
|
||||
),
|
||||
listOfNotNull(
|
||||
colors.getOrNull(0),
|
||||
colors.getOrNull(it + 1),
|
||||
colors.getOrNull(it + 2)
|
||||
),
|
||||
listOfNotNull(
|
||||
normals.getOrNull(0),
|
||||
normals.getOrNull(it),
|
||||
normals.getOrNull(it + 1)
|
||||
),
|
||||
listOfNotNull(
|
||||
tangents.getOrNull(0),
|
||||
tangents.getOrNull(it + 1),
|
||||
@@ -218,9 +224,39 @@ data class IndexedPolygon(
|
||||
override fun toPolygon(vertexData: IVertexData): Polygon {
|
||||
return Polygon(
|
||||
vertexData.positions.slice(positions),
|
||||
vertexData.normals.slice(normals),
|
||||
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 {
|
||||
return MutablePolygon(
|
||||
vertexData.positions.slice(positions).toMutableList(),
|
||||
vertexData.normals.slice(normals).toMutableList(),
|
||||
vertexData.textureCoords.slice(textureCoords).toMutableList(),
|
||||
vertexData.colors.slice(colors).toMutableList()
|
||||
vertexData.colors.slice(colors).toMutableList(),
|
||||
vertexData.normals.slice(normals).toMutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
33
orx-mesh/src/commonMain/kotlin/IndexedPolygonExtensions.kt
Normal file
33
orx-mesh/src/commonMain/kotlin/IndexedPolygonExtensions.kt
Normal 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
|
||||
)
|
||||
}
|
||||
@@ -8,8 +8,26 @@ import kotlin.jvm.JvmRecord
|
||||
interface IMeshData {
|
||||
val vertexData: IVertexData
|
||||
val polygons: List<IIndexedPolygon>
|
||||
|
||||
/**
|
||||
* Convert mesh data to triangular mesh data
|
||||
*/
|
||||
fun triangulate(): IMeshData
|
||||
|
||||
/**
|
||||
* Convert mesh data to a list of [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)
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
package org.openrndr.extra.mesh
|
||||
|
||||
import org.openrndr.color.ColorRGBa
|
||||
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
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.*
|
||||
|
||||
/**
|
||||
* The [VertexFormat] for a [VertexBuffer] with positions, normals and texture coordinates.
|
||||
@@ -17,6 +14,16 @@ internal val objVertexFormat = vertexFormat {
|
||||
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
|
||||
*/
|
||||
@@ -30,11 +37,15 @@ fun IMeshData.isTriangular(): Boolean {
|
||||
fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer? = null): VertexBuffer {
|
||||
val objects = triangulate().toPolygons()
|
||||
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) {
|
||||
objects.forEach {
|
||||
|
||||
for (i in it.positions.indices) {
|
||||
write(it.positions[i])
|
||||
if (it.normals.isNotEmpty()) {
|
||||
@@ -54,9 +65,164 @@ fun IMeshData.toVertexBuffer(elementOffset: Int = 0, vertexBuffer: VertexBuffer?
|
||||
} else {
|
||||
write(ColorRGBa.WHITE)
|
||||
}
|
||||
if (format == objVertexFormatTangents) {
|
||||
write(it.tangents[i])
|
||||
write(it.bitangents[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vertexBuffer.shadow.destroy()
|
||||
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)
|
||||
}
|
||||
23
orx-mesh/src/commonMain/kotlin/Point.kt
Normal file
23
orx-mesh/src/commonMain/kotlin/Point.kt
Normal 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
|
||||
)
|
||||
@@ -14,9 +14,9 @@ import kotlin.math.min
|
||||
*/
|
||||
interface IPolygon {
|
||||
val positions: List<Vector3>
|
||||
val normals: List<Vector3>
|
||||
val textureCoords: List<Vector2>
|
||||
val colors: List<ColorRGBa>
|
||||
val normals: List<Vector3>
|
||||
val tangents: List<Vector3>
|
||||
val bitangents: List<Vector3>
|
||||
|
||||
@@ -33,14 +33,14 @@ interface IPolygon {
|
||||
*/
|
||||
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 normals: List<Vector3> = 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)
|
||||
return Polygon(positions.map { (t * it.xyz1).div }, textureCoords, colors, normals, tangents, bitangents)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +49,9 @@ class Polygon(
|
||||
fun toMutablePolygon(): MutablePolygon {
|
||||
return MutablePolygon(
|
||||
positions.toMutableList(),
|
||||
normals.toMutableList(),
|
||||
textureCoords.toMutableList(),
|
||||
colors.toMutableList(),
|
||||
normals.toMutableList(),
|
||||
tangents.toMutableList(),
|
||||
bitangents.toMutableList()
|
||||
)
|
||||
@@ -63,9 +63,9 @@ class Polygon(
|
||||
*/
|
||||
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 normals: MutableList<Vector3> = mutableListOf(),
|
||||
override val tangents: MutableList<Vector3> = mutableListOf(),
|
||||
override val bitangents: MutableList<Vector3> = mutableListOf()
|
||||
|
||||
@@ -73,9 +73,9 @@ class MutablePolygon(
|
||||
override fun transform(t: Matrix44): MutablePolygon {
|
||||
return MutablePolygon(
|
||||
positions.map { (t * it.xyz1).div }.toMutableList(),
|
||||
ArrayList(normals),
|
||||
ArrayList(textureCoords),
|
||||
ArrayList(colors),
|
||||
ArrayList(normals),
|
||||
ArrayList(tangents),
|
||||
ArrayList(bitangents)
|
||||
)
|
||||
@@ -136,8 +136,8 @@ fun List<IPolygon>.toMeshData(): MeshData {
|
||||
IndexedPolygon(
|
||||
positions = indices,
|
||||
textureCoords = if (p.textureCoords.isNotEmpty()) indices else emptyList(),
|
||||
normals = if (p.normals.isNotEmpty()) indices else emptyList(),
|
||||
colors = if (p.colors.isNotEmpty()) indices else emptyList(),
|
||||
normals = if (p.normals.isNotEmpty()) indices else emptyList(),
|
||||
tangents = if (p.tangents.isNotEmpty()) indices else emptyList(),
|
||||
bitangents = if (p.bitangents.isNotEmpty()) indices else emptyList()
|
||||
)
|
||||
|
||||
@@ -13,11 +13,6 @@ interface IVertexData {
|
||||
*/
|
||||
val positions: List<Vector3>
|
||||
|
||||
/**
|
||||
* Vertex normals
|
||||
*/
|
||||
val normals: List<Vector3>
|
||||
|
||||
/**
|
||||
* Vertex texture coordinates
|
||||
*/
|
||||
@@ -28,6 +23,11 @@ interface IVertexData {
|
||||
*/
|
||||
val colors: List<ColorRGBa>
|
||||
|
||||
/**
|
||||
* Vertex normals
|
||||
*/
|
||||
val normals: List<Vector3>
|
||||
|
||||
/**
|
||||
* Vertex tangents
|
||||
*/
|
||||
@@ -37,6 +37,16 @@ interface IVertexData {
|
||||
* Vertex bitangents
|
||||
*/
|
||||
val bitangents: List<Vector3>
|
||||
|
||||
/**
|
||||
* Convert to [VertexData]
|
||||
*/
|
||||
fun toVertexData() : VertexData
|
||||
|
||||
/**
|
||||
* Convert to [MutableVertexData]
|
||||
*/
|
||||
fun toMutableVertexData() : MutableVertexData
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,26 +54,23 @@ interface IVertexData {
|
||||
*/
|
||||
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 normals: List<Vector3> = emptyList(),
|
||||
override val tangents: List<Vector3> = emptyList(),
|
||||
override val bitangents: List<Vector3> = emptyList()
|
||||
) : IVertexData {
|
||||
|
||||
/**
|
||||
* Convert to [MutableVertexData]
|
||||
*/
|
||||
fun toMutableVertexData(): MutableVertexData {
|
||||
return MutableVertexData(
|
||||
positions.toMutableList(),
|
||||
normals.toMutableList(),
|
||||
textureCoords.toMutableList(),
|
||||
colors.toMutableList(),
|
||||
tangents.toMutableList(),
|
||||
bitangents.toMutableList()
|
||||
)
|
||||
}
|
||||
override fun toVertexData(): VertexData = this
|
||||
|
||||
override fun toMutableVertexData(): MutableVertexData = MutableVertexData(
|
||||
positions.toMutableList(),
|
||||
textureCoords.toMutableList(),
|
||||
colors.toMutableList(),
|
||||
normals.toMutableList(),
|
||||
tangents.toMutableList(),
|
||||
bitangents.toMutableList()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,24 +79,54 @@ class VertexData(
|
||||
*/
|
||||
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 normals: MutableList<Vector3> = mutableListOf(),
|
||||
override val tangents: MutableList<Vector3> = mutableListOf(),
|
||||
override val bitangents: MutableList<Vector3> = mutableListOf()
|
||||
) : IVertexData {
|
||||
|
||||
/**
|
||||
* Convert to [VertexData]
|
||||
*/
|
||||
fun toVertexData(): VertexData {
|
||||
return VertexData(
|
||||
positions.toList(),
|
||||
normals.toList(),
|
||||
textureCoords.toList(),
|
||||
colors.toList(),
|
||||
tangents.toList(),
|
||||
bitangents.toList()
|
||||
)
|
||||
}
|
||||
override fun toVertexData(): VertexData = VertexData(
|
||||
positions.toList(),
|
||||
textureCoords.toList(),
|
||||
colors.toList(),
|
||||
normals.toList(),
|
||||
tangents.toList(),
|
||||
bitangents.toList()
|
||||
)
|
||||
|
||||
override fun toMutableVertexData(): MutableVertexData = this
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
)
|
||||
}
|
||||
68
orx-mesh/src/commonMain/kotlin/VertexDataExtensions.kt
Normal file
68
orx-mesh/src/commonMain/kotlin/VertexDataExtensions.kt
Normal 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)
|
||||
}
|
||||
@@ -9,7 +9,6 @@ fun IMeshData.wireframe(): List<List<Vector3>> {
|
||||
return polygons.map { ip -> ip.toPolygon(this.vertexData).positions.toList() }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract wireframe from compound mesh data
|
||||
*/
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.openrndr.extra.mesh
|
||||
|
||||
import org.openrndr.color.ColorRGBa
|
||||
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.Vector3
|
||||
import java.nio.ByteBuffer
|
||||
@@ -53,7 +51,7 @@ fun VertexBuffer.toPolygons(vertexCount: Int = this.vertexCount): List<Polygon>
|
||||
textureCoordinates.add(buffer.getVector2())
|
||||
colors.add(buffer.getColorRGBa())
|
||||
}
|
||||
polygons.add(Polygon(positions, normals, textureCoordinates, colors))
|
||||
polygons.add(Polygon(positions, textureCoordinates, colors, normals))
|
||||
}
|
||||
return polygons
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.openrndr.extra.mesh
|
||||
package org.openrndr.extra.objloader
|
||||
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.VertexBuffer
|
||||
import org.openrndr.extra.mesh.*
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
@@ -79,8 +80,8 @@ fun readObjMeshData(lines: Iterable<String>): CompoundMeshData {
|
||||
IndexedPolygon(
|
||||
if (hasPosition) indices.map { it[0] - 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 (hasNormal) 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()
|
||||
)
|
||||
@@ -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(
|
||||
vertexData,
|
||||
meshes.mapValues {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.extra.mesh.loadOBJMeshData
|
||||
import org.openrndr.extra.mesh.toObj
|
||||
import org.openrndr.extra.objloader.loadOBJMeshData
|
||||
import org.openrndr.extra.objloader.toObj
|
||||
import java.io.File
|
||||
|
||||
fun main() {
|
||||
|
||||
@@ -3,7 +3,7 @@ import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.DepthTestPass
|
||||
import org.openrndr.draw.DrawPrimitive
|
||||
import org.openrndr.draw.shadeStyle
|
||||
import org.openrndr.extra.mesh.loadOBJasVertexBuffer
|
||||
import org.openrndr.extra.objloader.loadOBJasVertexBuffer
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
fun main() = application {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.draw.loadFont
|
||||
import org.openrndr.extra.mesh.loadOBJasVertexBuffer
|
||||
import org.openrndr.extra.mesh.saveOBJ
|
||||
import org.openrndr.extra.objloader.loadOBJasVertexBuffer
|
||||
import org.openrndr.extra.objloader.saveOBJ
|
||||
|
||||
fun main() = application {
|
||||
configure {
|
||||
|
||||
@@ -2,7 +2,7 @@ import org.openrndr.application
|
||||
import org.openrndr.draw.loadFont
|
||||
import org.openrndr.extra.meshgenerators.buildTriangleMesh
|
||||
import org.openrndr.extra.meshgenerators.sphere
|
||||
import org.openrndr.extra.mesh.saveOBJ
|
||||
import org.openrndr.extra.objloader.saveOBJ
|
||||
|
||||
fun main() = application {
|
||||
configure {
|
||||
|
||||
@@ -8,8 +8,8 @@ import org.openrndr.draw.DrawPrimitive
|
||||
import org.openrndr.draw.TransformTarget
|
||||
import org.openrndr.draw.shadeStyle
|
||||
import org.openrndr.extra.camera.Orbital
|
||||
import org.openrndr.extra.mesh.readObjMeshData
|
||||
import org.openrndr.extra.mesh.loadOBJasVertexBuffer
|
||||
import org.openrndr.extra.objloader.readObjMeshData
|
||||
import org.openrndr.extra.objloader.loadOBJasVertexBuffer
|
||||
import org.openrndr.extra.mesh.wireframe
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.shape.Path3D
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.openrndr.extra.mesh
|
||||
package org.openrndr.extra.objloader
|
||||
|
||||
import org.openrndr.draw.VertexBuffer
|
||||
import org.openrndr.extra.mesh.IPolygon
|
||||
import java.io.File
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.openrndr.extra.mesh
|
||||
package org.openrndr.extra.objloader
|
||||
|
||||
import org.openrndr.draw.VertexBuffer
|
||||
import java.io.File
|
||||
|
||||
Reference in New Issue
Block a user