-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[orx-mesh, orx-mesh-generator, orx-obj-loader] Add decal and tangent …
…tools
- Loading branch information
Showing
28 changed files
with
1,072 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
201 changes: 201 additions & 0 deletions
201
orx-mesh-generators/src/commonMain/kotlin/decal/Decal.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
63 changes: 63 additions & 0 deletions
63
orx-mesh-generators/src/commonMain/kotlin/normals/MeshDataNormals.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} |
94 changes: 94 additions & 0 deletions
94
orx-mesh-generators/src/commonMain/kotlin/tangents/MeshDataTangents.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) | ||
} | ||
) | ||
} |
Oops, something went wrong.