Skip to content

Commit

Permalink
[orx-mesh, orx-mesh-generator, orx-obj-loader] Add decal and tangent …
Browse files Browse the repository at this point in the history
…tools
  • Loading branch information
edwinRNDR committed Sep 25, 2024
1 parent fb3bb6f commit e016891
Show file tree
Hide file tree
Showing 28 changed files with 1,072 additions and 82 deletions.
2 changes: 2 additions & 0 deletions orx-mesh-generators/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ kotlin {
api(libs.openrndr.application)
api(libs.openrndr.math)
implementation(project(":orx-shapes"))
api(project(":orx-mesh"))
}
}

Expand All @@ -20,6 +21,7 @@ kotlin {
implementation(project(":orx-mesh-generators"))
implementation(project(":orx-camera"))
implementation(project(":orx-noise"))
implementation(project(":orx-obj-loader"))
}
}
}
Expand Down
201 changes: 201 additions & 0 deletions orx-mesh-generators/src/commonMain/kotlin/decal/Decal.kt
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
})
}
Original file line number Diff line number Diff line change
@@ -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
)
}
)
}
Loading

0 comments on commit e016891

Please sign in to comment.