Add new extrusion types, refactor code. (#323)

This commit is contained in:
Abe Pazos
2023-08-25 21:02:23 +02:00
committed by GitHub
parent 4b42cfc1fc
commit be07a8fc4c
7 changed files with 674 additions and 92 deletions

View File

@@ -10,34 +10,6 @@ import org.openrndr.shape.Shape
import org.openrndr.shape.ShapeContour
import org.openrndr.shape.Triangle
/**
* Writes two triangles to [writer] representing
* the quad formed by four vertices.
*
* @param v00 vertex (0, 0)
* @param v01 vertex (0, 1)
* @param v10 vertex (1, 0)
* @param v11 vertex (1, 1)
* @param faceNormal the face normal
* @param writer the vertex writer function
*/
fun quadToTris(
v00: Vector3,
v01: Vector3,
v10: Vector3,
v11: Vector3,
faceNormal: Vector3,
writer: VertexWriter
) {
writer(v11, faceNormal, Vector2.ZERO)
writer(v01, faceNormal, Vector2.ZERO)
writer(v00, faceNormal, Vector2.ZERO)
writer(v00, faceNormal, Vector2.ZERO)
writer(v10, faceNormal, Vector2.ZERO)
writer(v11, faceNormal, Vector2.ZERO)
}
/**
* Writes quads to [writer] creating a surface that connects two
* displaced instances of [linearContour]. The positions and orientations
@@ -49,7 +21,7 @@ fun quadToTris(
* @param writer the vertex writer function
*/
fun contourSegment(
linearContour: List<Vector3>,
linearContour: List<Vector2>,
frame0: Matrix44,
frame1: Matrix44,
writer: VertexWriter
@@ -58,42 +30,16 @@ fun contourSegment(
val v0 = linearContour[i]
val v1 = linearContour[(i + 1).mod(linearContour.size)]
val v00 = (frame0 * v0.xyz1).xyz
val v01 = (frame0 * v1.xyz1).xyz
val v00 = (frame0 * v0.xy01).xyz
val v01 = (frame0 * v1.xy01).xyz
val v10 = (frame1 * v0.xyz1).xyz
val v11 = (frame1 * v1.xyz1).xyz
val v10 = (frame1 * v0.xy01).xyz
val v11 = (frame1 * v1.xy01).xyz
val faceNormal = ((v10 - v00).normalized cross (v01 - v00).normalized).normalized
quadToTris(v00, v01, v10, v11, faceNormal, writer)
}
}
/**
* Writes a list of triangles transformed by the [frame]
* transformation matrix into [writer].
*
* @param triangulation the list of triangles to write
* @param frame a transformation matrix to apply to each triangle
* @param flipNormals generates inside-out geometry if true
* @param writer the vertex writer function
*/
fun triangulationWithFrame(
triangulation: List<Triangle>,
frame: Matrix44,
flipNormals: Boolean = true,
writer: VertexWriter
) {
val normalFrame = normalMatrix(frame)
val normalScale = if (!flipNormals) -1.0 else 1.0
val normal = ((normalFrame * Vector4(0.0, 0.0, normalScale, 0.0)).xyz)
for (triangle in triangulation) {
val t = if (!flipNormals) triangle else Triangle(triangle.x3, triangle.x2, triangle.x1)
writer((frame * t.x1.xy01).xyz, normal, Vector2.ZERO)
writer((frame * t.x2.xy01).xyz, normal, Vector2.ZERO)
writer((frame * t.x3.xy01).xyz, normal, Vector2.ZERO)
}
}
/**
* Extrude a [contour] along a [path] specifying the number of steps.
*
@@ -129,7 +75,7 @@ fun extrudeContourSteps(
writer: VertexWriter
) {
val linearContour = contour.sampleLinear(contourDistanceTolerance)
val linearContourPoints = linearContour.adaptivePositions().map { it.xy0 }
val linearContourPoints = linearContour.adaptivePositions()
val finalFrames = if (path.closed) frames + frames.first() else frames
// First add caps
@@ -141,36 +87,6 @@ fun extrudeContourSteps(
}
}
/**
* Adds caps to an extruded shape
*
* @param linearShape the cross-section of the mesh
* @param path the 3D path
* @param startCap adds a start cap if set to true
* @param endCap adds an end cap if set to true
* @param frames a list of matrices holding the transformation matrices along
* the path
* @param writer the vertex writer function
*/
private fun extrudeCaps(
linearShape: Shape,
path: Path3D,
startCap: Boolean,
endCap: Boolean,
frames: List<Matrix44>,
writer: VertexWriter
) {
if ((startCap || endCap) && !path.closed) {
val capTriangles = linearShape.triangulation
if (startCap) {
triangulationWithFrame(capTriangles, frames.first(), false, writer)
}
if (endCap) {
triangulationWithFrame(capTriangles, frames.last(), true, writer)
}
}
}
/**
* Extrude a [contour] along a [path]. The number of resulting steps
* along the path depends on the tolerance values.
@@ -202,7 +118,7 @@ fun extrudeContourAdaptive(
writer: VertexWriter
) {
val linearContour = contour.sampleLinear(contourDistanceTolerance)
val linearContourPoints = linearContour.adaptivePositions().map { it.xy0 }
val linearContourPoints = linearContour.adaptivePositions()
val finalFrames = if (path.closed) frames + frames.first() else frames
// First add caps
@@ -442,4 +358,4 @@ fun TriangleMeshBuilder.extrudeContourAdaptive(
contourDistanceTolerance,
pathDistanceTolerance,
writer = this::write
)
)

View File

@@ -0,0 +1,111 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector3
import org.openrndr.shape.Path3D
import org.openrndr.shape.ShapeContour
/**
* Extrude a [contour] along a [path] specifying the number of steps.
*
* @param contour the cross-section of the mesh
* @param path the 3D path
* @param stepCount the number of steps along the [path]
* @param up0 the initial up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [contour]. Lower tolerance results in higher precision.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision.
* @param steps the resulting positions in the path
* @param frames a list of matrices holding the transformation matrices along
* the path
* @param startCap adds a start cap if set to true
* @param endCap adds an end cap if set to true
* @param writer the vertex writer function
*/
fun extrudeContourStepsMorphed(
contour: (Double) -> ShapeContour,
path: Path3D,
stepCount: Int,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
steps: List<Vector3> = path.equidistantPositions(
stepCount,
pathDistanceTolerance
),
frames: List<Matrix44> = steps.frames(up0),
startCap: Boolean = true,
endCap: Boolean = true,
writer: VertexWriter
) {
val finalFrames = if (path.closed) frames + frames.first() else frames
val crossSections = List(finalFrames.size) {
val t = it / (finalFrames.size - 1.0)
val linearContour = contour(t).sampleLinear(contourDistanceTolerance)
linearContour.adaptivePositions()
}
// Add caps
if (!path.closed) {
if (startCap) {
triangulationWithFrame(
ShapeContour.fromPoints(crossSections.first(), true).shape.triangulation,
finalFrames.first(), false, writer
)
}
if (endCap) {
triangulationWithFrame(
ShapeContour.fromPoints(crossSections.last(), true).shape.triangulation,
finalFrames.last(), false, writer
)
}
}
// Add sides
var i = 0
(finalFrames zip crossSections).windowed(2, 1).forEach {
contourSegment(
it[0].second, it[1].second,
it[0].first, it[1].first,
i.toDouble() / crossSections.size,
(++i).toDouble() / crossSections.size,
writer)
}
}
/**
* Extrude a [contour] along a [path] specifying the number of steps.
*
* @param contour the cross-section of the mesh
* @param path the 3D path
* @param stepCount the number of steps along the [path]
* @param up0 the initial up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [contour]. Lower tolerance results in higher precision.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision.
*/
fun TriangleMeshBuilder.extrudeContourStepsMorphed(
contour: (Double) -> ShapeContour,
path: Path3D,
stepCount: Int,
up0: Vector3,
startCap: Boolean = true,
endCap: Boolean = true,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5
) = extrudeContourStepsMorphed(
contour,
path,
stepCount,
up0,
startCap = startCap,
endCap = endCap,
contourDistanceTolerance = contourDistanceTolerance,
pathDistanceTolerance = pathDistanceTolerance,
writer = this::write
)

View File

@@ -0,0 +1,116 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector3
import org.openrndr.shape.Path3D
import org.openrndr.shape.ShapeContour
/**
* Extrude a [contour] along a [path] specifying the number of steps.
* The [scale] argument can be used to make variable width shapes.
* For example `scale = { t: Double -> 0.5 - 0.5 * cos(t * 2 * PI) }`
* produces an extruded shape that begins and ends with hairline thickness.
*
* @param contour the cross-section of the mesh
* @param path the 3D path
* @param stepCount the number of steps along the [path]
* @param up0 the initial up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [contour]. Lower tolerance results in higher precision.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision.
* @param steps the resulting positions in the path
* @param frames a list of matrices holding the transformation matrices along
* the path
* @param startCap adds a start cap if set to true
* @param endCap adds an end cap if set to true
* @param scale A function that takes a curve `t` value and returns
* a scaling factor for [contour] at that point.
* @param writer the vertex writer function
*/
fun extrudeContourStepsScaled(
contour: ShapeContour,
path: Path3D,
stepCount: Int,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
steps: List<Vector3> = path.equidistantPositions(
stepCount,
pathDistanceTolerance
),
frames: List<Matrix44> = steps.frames(up0),
startCap: Boolean = true,
endCap: Boolean = true,
scale: (Double) -> Double = { _ -> 1.0 },
writer: VertexWriter
) {
val finalFrames = if (path.closed) frames + frames.first() else frames
val linearContour = contour.sampleLinear(contourDistanceTolerance)
val linearContourPoints2D = linearContour.adaptivePositions()
val crossSections = List(finalFrames.size) {
val t = it / (finalFrames.size - 1.0)
linearContourPoints2D.map { p -> p * scale(t) }
}
if (!path.closed) {
if (startCap) {
triangulationWithFrame(
ShapeContour.fromPoints(crossSections.first(), true).shape.triangulation,
finalFrames.first(), false, writer
)
}
if (endCap) {
triangulationWithFrame(
ShapeContour.fromPoints(crossSections.last(), true).shape.triangulation,
finalFrames.last(), false, writer
)
}
}
// Then add sides
(finalFrames zip crossSections).windowed(2, 1).forEach {
contourSegment(it[0].second, it[1].second, it[0].first, it[1].first, writer)
}
}
/**
* Extrude a [contour] along a [path] specifying the number of steps.
* The [scale] argument can be used to make variable width shapes.
* For example `scale = { t: Double -> 0.5 - 0.5 * cos(t * 2 * PI) }`
* produces an extruded shape that begins and ends with hairline thickness.
*
* @param contour the cross-section of the mesh
* @param path the 3D path
* @param stepCount the number of steps along the [path]
* @param up0 the initial up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [contour]. Lower tolerance results in higher precision.
* @param pathDistanceTolerance precision for calculating steps along
* @param scale A function that converts `t` into a radius
* [path]. Lower tolerance results in higher precision.
*/
fun TriangleMeshBuilder.extrudeContourStepsScaled(
contour: ShapeContour,
path: Path3D,
stepCount: Int,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
startCap: Boolean = true,
endCap: Boolean = true,
scale: (Double) -> Double = { _ -> 1.0 }
) = extrudeContourStepsScaled(
contour,
path,
stepCount,
up0,
contourDistanceTolerance,
pathDistanceTolerance,
startCap = startCap,
endCap = endCap,
scale = scale,
writer = this::write
)

View File

@@ -0,0 +1,112 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.Vector4
import org.openrndr.math.transforms.normalMatrix
import org.openrndr.shape.Path3D
import org.openrndr.shape.Shape
import org.openrndr.shape.Triangle
/**
* Writes two triangles to [writer] representing
* the quad formed by four vertices.
*
* @param v00 vertex (0, 0)
* @param v01 vertex (0, 1)
* @param v10 vertex (1, 0)
* @param v11 vertex (1, 1)
* @param faceNormal the face normal
* @param writer the vertex writer function
*/
fun quadToTris(
v00: Vector3,
v01: Vector3,
v10: Vector3,
v11: Vector3,
faceNormal: Vector3,
writer: VertexWriter
) {
writeTri(v11, v01, v00, Vector2.ZERO, Vector2.ZERO, Vector2.ZERO, faceNormal, writer)
writeTri(v00, v10, v11, Vector2.ZERO, Vector2.ZERO, Vector2.ZERO, faceNormal, writer)
}
/**
* Writes a triangle to [writer].
*
* @param v0 vertex 0
* @param v1 vertex 1
* @param v2 vertex 2
* @param tc0 texture coordinate 0
* @param tc1 texture coordinate 1
* @param tc2 texture coordinate 2
* @param faceNormal the face normal
* @param writer the vertex writer function
*/
fun writeTri(
v0: Vector3, v1: Vector3, v2: Vector3,
tc0: Vector2, tc1: Vector2, tc2: Vector2,
faceNormal: Vector3,
writer: VertexWriter
) {
writer(v0, faceNormal, tc0)
writer(v1, faceNormal, tc1)
writer(v2, faceNormal, tc2)
}
/**
* Writes a list of triangles transformed by the [frame]
* transformation matrix into [writer].
*
* @param triangulation the list of triangles to write
* @param frame a transformation matrix to apply to each triangle
* @param flipNormals generates inside-out geometry if true
* @param writer the vertex writer function
*/
fun triangulationWithFrame(
triangulation: List<Triangle>,
frame: Matrix44,
flipNormals: Boolean = true,
writer: VertexWriter
) {
val normalFrame = normalMatrix(frame)
val normalScale = if (!flipNormals) -1.0 else 1.0
val normal = ((normalFrame * Vector4(0.0, 0.0, normalScale, 0.0)).xyz)
for (triangle in triangulation) {
val t = if (!flipNormals) triangle else Triangle(triangle.x3, triangle.x2, triangle.x1)
writer((frame * t.x1.xy01).xyz, normal, Vector2.ZERO)
writer((frame * t.x2.xy01).xyz, normal, Vector2.ZERO)
writer((frame * t.x3.xy01).xyz, normal, Vector2.ZERO)
}
}
/**
* Adds caps to an extruded shape
*
* @param linearShape the cross-section of the mesh
* @param path the 3D path
* @param startCap adds a start cap if set to true
* @param endCap adds an end cap if set to true
* @param frames a list of matrices holding the transformation matrices along
* the path
* @param writer the vertex writer function
*/
fun extrudeCaps(
linearShape: Shape,
path: Path3D,
startCap: Boolean,
endCap: Boolean,
frames: List<Matrix44>,
writer: VertexWriter
) {
if ((startCap || endCap) && !path.closed) {
val capTriangles = linearShape.triangulation
if (startCap) {
triangulationWithFrame(capTriangles, frames.first(), false, writer)
}
if (endCap) {
triangulationWithFrame(capTriangles, frames.last(), true, writer)
}
}
}

View File

@@ -0,0 +1,108 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
/**
* Writes quads to [writer] creating a surface that connects
* [linearContour0] with [linearContour1].
* The positions and orientations of the two contours
* are defined by the [frame0] and [frame1] matrices.
*
* @param linearContour0 the first cross-section
* @param linearContour1 the second cross-section
* @param frame0 a transformation matrix with the pose of [linearContour0]
* @param frame1 a transformation matrix with the pose of [linearContour1]
* @param writer the vertex writer function
*/
fun contourSegment(
linearContour0: List<Vector2>,
linearContour1: List<Vector2>,
frame0: Matrix44,
frame1: Matrix44,
writer: VertexWriter
) {
for (i in linearContour0.indices) {
val v0a = linearContour0[i]
val v1a = linearContour0[(i + 1).mod(linearContour0.size)]
val v0b = linearContour1[i]
val v1b = linearContour1[(i + 1).mod(linearContour1.size)]
val v00a = (frame0 * v0a.xy01).xyz
val v01a = (frame0 * v1a.xy01).xyz
val v10b = (frame1 * v0b.xy01).xyz
val v11b = (frame1 * v1b.xy01).xyz
val faceNormal = ((v10b - v00a).normalized cross (v01a - v00a).normalized).normalized
quadToTris(v00a, v01a, v10b, v11b, faceNormal, writer)
}
}
/**
* Contour segment
*
* @param linearContour0
* @param linearContour1
* @param frame0
* @param frame1
* @param v0
* @param v1
* @param writer
*/
fun contourSegment(
linearContour0: List<Vector2>,
linearContour1: List<Vector2>,
frame0: Matrix44,
frame1: Matrix44,
v0: Double,
v1: Double,
writer: VertexWriter
) {
var i0 = 0
var i1 = 0
do {
val flipNormal = false
val vCurr0 = (frame0 * linearContour0[i0 % linearContour0.size].xy01).xyz
val vCurr1 = (frame1 * linearContour1[i1 % linearContour1.size].xy01).xyz
val vNext0 = (frame0 * linearContour0[(i0 + 1) % linearContour0.size].xy01).xyz
val vNext1 = (frame1 * linearContour1[(i1 + 1) % linearContour1.size].xy01).xyz
val tCurr0 = i0.toDouble() / linearContour0.size
val tCurr1 = i1.toDouble() / linearContour1.size
val tNext0 = (i0 + 1.0) / linearContour0.size
val tNext1 = (i1 + 1.0) / linearContour1.size
val uvCurr0 = Vector2(tCurr0, v0)
val uvCurr1 = Vector2(tCurr1, v1)
val vNext: Vector3
val uvNext: Vector2
if (tNext0 < tNext1 || (tNext0 == tNext1 && tCurr0 < tCurr1)) {
++i0
vNext = vNext0
uvNext = Vector2(i0.toDouble() / linearContour0.size, v0)
} else {
++i1
vNext = vNext1
uvNext = Vector2(i1.toDouble() / linearContour1.size, v1)
}
val faceNormal = if (flipNormal)
(vNext - vCurr1).cross(vCurr0 - vCurr1)
else
(vNext - vCurr0).cross(vCurr1 - vCurr0)
writeTri(
vCurr0, vNext, vCurr1,
uvCurr0, uvNext, uvCurr1,
faceNormal.normalized,
writer
)
} while (i0 < linearContour0.size || i1 < linearContour1.size)
}