Improve mesh generator (#301)

Co-authored-by: Edwin Jakobs <edwin@rndr.studio>
This commit is contained in:
Abe Pazos
2023-04-19 10:34:55 +02:00
committed by GitHub
parent a8c2f7217c
commit 03bf971ff5
31 changed files with 2639 additions and 1164 deletions

View File

@@ -1,95 +1,121 @@
# orx-mesh-generators
Generates 3D meshes: sphere, box, cylinder, plane, dodecahedron.
Generates various types of 3D meshes.
##### usage
## Simple meshes
```kotlin
// To create simple meshes
val sphere = sphereMesh(32, 32, 4.0)
val unitSphere = sphereMesh()
val cube = boxMesh()
val box = boxMesh(2.0, 4.0, 2.0)
val cylinder = cylinderMesh(radius = 0.5, length = 1.0, center = true)
val dodecahedron = dodecahedronMesh(0.5)
val plane = planeMesh(Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_Y)
val disk = capMesh(sides = 15, radius = 0.5)
val tube = revolveMesh(sides = 15, length = 1.0)
...
drawer.vertexBuffer(sphere, DrawPrimitive.TRIANGLES)
drawer.vertexBuffer(unitSphere, DrawPrimitive.TRIANGLES)
drawer.vertexBuffer(cube, DrawPrimitive.TRIANGLES)
drawer.vertexBuffer(box, DrawPrimitive.TRIANGLES)
// To draw the generated meshes
drawer.vertexBuffer(dodecahedron, DrawPrimitive.TRIANGLES)
```
## API
## Complex triangular mesh generation
`orx-mesh-generators` comes with `buildTriangleMesh`, which
implements a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)
to construct 3D shapes.
To create shapes we can call methods like `box()`, `sphere()`,
`cylinder()`, `dodecahedron()`, `plane()`, `revolve()`,
`taperedCylinder()`, `hemisphere()` and `cap()`.
```kotlin
fun sphereMesh(
sides: Int = 16,
segments: Int = 16,
radius: Double = 1.0,
invert: Boolean = false): VertexBuffer
fun groundPlaneMesh(
width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int): VertexBuffer
fun boxMesh(
width: Double = 1.0,
height: Double = 1.0,
depth: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1,
depthSegments: Int = 1,
invert: Boolean = false): VertexBuffer
// Create a rotated box
val mesh = buildTriangleMesh {
rotate(Vector3.UNIT_Z, 45.0)
box()
}
```
<!-- __demos__ >
# Demos
[DemoBoxKt](src/demo/kotlin/DemoBoxKt.kt
![DemoBoxKt](https://github.com/openrndr/orx/blob/media/orx-mesh-generators/images/DemoBoxKt.png
[DemoComplex01Kt](src/demo/kotlin/DemoComplex01Kt.kt
![DemoComplex01Kt](https://github.com/openrndr/orx/blob/media/orx-mesh-generators/images/DemoComplex01Kt.png
[DemoComplex02Kt](src/demo/kotlin/DemoComplex02Kt.kt
![DemoComplex02Kt](https://github.com/openrndr/orx/blob/media/orx-mesh-generators/images/DemoComplex02Kt.png
[DemoComplex03Kt](src/demo/kotlin/DemoComplex03Kt.kt
![DemoComplex03Kt](https://github.com/openrndr/orx/blob/media/orx-mesh-generators/images/DemoComplex03Kt.png
[DemoComplex04Kt](src/demo/kotlin/DemoComplex04Kt.kt
![DemoComplex04Kt](https://github.com/openrndr/orx/blob/media/orx-mesh-generators/images/DemoComplex04Kt.png
[DemoComplex05Kt](src/demo/kotlin/DemoComplex05Kt.kt
![DemoComplex05Kt](https://github.com/openrndr/orx/blob/media/orx-mesh-generators/images/DemoComplex05Kt.png
We can also use methods like `translate()` and `rotate()` to create
more complex compositions. The `color` property sets the color of
the next mesh.
```kotlin
// Create a ring of boxes of various colors
val mesh = buildTriangleMesh {
repeat(12) {
// Take a small step
translate(2.0, 0.0, 0.0)
// Turn 30 degrees
rotate(Vector3.UNIT_Y, 30.0)
// Set a color
color = rgb(it / 11.0, 1.0, 1.0 - it / 11.0)
// Add a colored box
box(1.0, 1.0, 1.0)
}
}
```
`isolated { ... }` can be used to encapsulate transformations and
avoid them accumulating to unpredictable values.
```kotlin
val mesh = buildTriangleMesh {
repeat(10) { x ->
repeat(10) { y ->
isolated {
translate(x * 1.0, y * 1.0, 0.0)
sphere(8, 8, 0.1)
}
}
}
}
```
Other available methods are:
- `grid()`: creates a tri-dimensional grid of meshes.
- `extrudeShape()`: gives depth to 2D `Shape`.
- `twist()`: post-processing effect to twist a mesh around an axis.
- `extrudeContourSteps()`: uses Parallel Transport Frames to extrude a contour along a 3D path.
The [demo folder](src/jvmDemo/kotlin) contains examples using these methods.
Check out the [source code](src/commonMain/kotlin) to learn about function arguments.
<!-- __demos__ -->
## Demos
### DemoAll
[source code](src/demo/kotlin/DemoAll.kt)
[source code](src/jvmDemo/kotlin/DemoAll.kt)
![DemoAllKt](https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/DemoAllKt.png)
### DemoBox
[source code](src/demo/kotlin/DemoBox.kt)
[source code](src/jvmDemo/kotlin/DemoBox.kt)
![DemoBoxKt](https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/DemoBoxKt.png)
### DemoComplex01
[source code](src/demo/kotlin/DemoComplex01.kt)
[source code](src/jvmDemo/kotlin/DemoComplex01.kt)
![DemoComplex01Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/DemoComplex01Kt.png)
### DemoComplex02
[source code](src/demo/kotlin/DemoComplex02.kt)
[source code](src/jvmDemo/kotlin/DemoComplex02.kt)
![DemoComplex02Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/DemoComplex02Kt.png)
### DemoComplex03
[source code](src/demo/kotlin/DemoComplex03.kt)
[source code](src/jvmDemo/kotlin/DemoComplex03.kt)
![DemoComplex03Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/DemoComplex03Kt.png)
### DemoComplex04
[source code](src/demo/kotlin/DemoComplex04.kt)
[source code](src/jvmDemo/kotlin/DemoComplex04.kt)
![DemoComplex04Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/DemoComplex04Kt.png)
### DemoComplex05
[source code](src/demo/kotlin/DemoComplex05.kt)
[source code](src/jvmDemo/kotlin/DemoComplex05.kt)
![DemoComplex05Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/DemoComplex05Kt.png)

View File

@@ -1,11 +1,24 @@
plugins {
org.openrndr.extra.convention.`kotlin-jvm`
org.openrndr.extra.convention.`kotlin-multiplatform`
}
dependencies {
implementation(libs.openrndr.application)
implementation(libs.openrndr.math)
demoImplementation(project(":orx-shapes"))
demoImplementation(project(":orx-mesh-generators"))
demoImplementation(project(":orx-camera"))
}
kotlin {
sourceSets {
@Suppress("UNUSED_VARIABLE")
val commonMain by getting {
dependencies {
api(libs.openrndr.application)
api(libs.openrndr.math)
}
}
@Suppress("UNUSED_VARIABLE")
val jvmDemo by getting {
dependencies {
implementation(project(":orx-shapes"))
implementation(project(":orx-mesh-generators"))
implementation(project(":orx-camera"))
}
}
}
}

View File

@@ -0,0 +1,114 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Vector3
/**
* Returns a Box mesh
*
* @param width the width of the box
* @param height the height of the box
* @param depth the depth of the box
* @param widthSegments the number of segments along the x-axis
* @param heightSegments the number of segments along the z-axis
* @param depthSegments the number of segments along the y-axis
* @param flipNormals generates inside-out geometry if true
* @return A vertex buffer containing the triangles to render the 3D shape
*/
fun boxMesh(
width: Double = 1.0,
height: Double = 1.0,
depth: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1,
depthSegments: Int = 1,
flipNormals: Boolean = false
): VertexBuffer {
val vb = meshVertexBuffer(
widthSegments * heightSegments * 6 * 2 +
widthSegments * depthSegments * 6 * 2 +
heightSegments * depthSegments * 6 * 2
)
vb.put {
generateBox(
width, height, depth,
widthSegments, heightSegments, depthSegments,
flipNormals, bufferWriter(this)
)
}
return vb
}
/**
* Generate a box
*
* @param width the width of the box
* @param height the height of the box
* @param depth the depth of the box
* @param widthSegments the number of segments along the x-axis
* @param heightSegments the number of segments along the z-axis
* @param depthSegments the number of segments along the y-axis
* @param flipNormals generates inside-out geometry if true
* @param writer the vertex writer function
*/
fun generateBox(
width: Double = 1.0,
height: Double = 1.0,
depth: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1,
depthSegments: Int = 1,
flipNormals: Boolean = false,
writer: VertexWriter
) {
val sign = if (flipNormals) -1.0 else 1.0
// +x -- ZY
generatePlane(
Vector3(width / 2.0 * sign, 0.0, 0.0),
Vector3.UNIT_Z, Vector3.UNIT_Y, Vector3.UNIT_X,
-depth, -height,
depthSegments, heightSegments, writer
)
// -x -- ZY
generatePlane(
Vector3(-width / 2.0 * sign, 0.0, 0.0),
Vector3.UNIT_Z, Vector3.UNIT_Y, -Vector3.UNIT_X,
-depth, height,
depthSegments, heightSegments, writer
)
// +y -- XZ
generatePlane(
Vector3(0.0, height / 2.0 * sign, 0.0),
Vector3.UNIT_X, Vector3.UNIT_Z, Vector3.UNIT_Y,
width, depth,
widthSegments, depthSegments, writer
)
// -y -- XZ
generatePlane(
Vector3(0.0, -height / 2.0 * sign, 0.0),
Vector3.UNIT_X, Vector3.UNIT_Z, -Vector3.UNIT_Y,
width, -depth,
widthSegments, depthSegments, writer
)
// +z -- XY
generatePlane(
Vector3(0.0, 0.0, depth / 2.0 * sign),
Vector3.UNIT_X, Vector3.UNIT_Y, Vector3.UNIT_Z,
-width, height,
widthSegments, heightSegments, writer
)
// -z -- XY
generatePlane(
Vector3(0.0, 0.0, -depth / 2.0 * sign),
Vector3.UNIT_X, Vector3.UNIT_Y, -Vector3.UNIT_Z,
width, height,
widthSegments, heightSegments, writer
)
}

View File

@@ -1,49 +1,63 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.*
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.YPolarity
import org.openrndr.math.transforms.rotateY
/**
* A shape created by rotating an envelope around a vertical axis.
* The default envelope is a horizontal line which produces a flat round disk.
* By providing a more complex envelop one can create curved shapes like a bowl.
*
* @param sides the angular resolution of the cap
* @param radius the radius of the cap
* @param envelope a list of points defining the profile of the cap. The default envelope is a horizontal line which produces a flat round disk. By providing a more complex envelope one can create curved shapes like a bowl.
* @return A vertex buffer containing the triangles to render the 3D shape
*/
fun capMesh(
sides: Int,
radius: Double,
enveloppe: List<Vector2> = listOf(
envelope: List<Vector2> = listOf(
Vector2(0.0, 0.0),
Vector2(1.0, 0.0)
)
): VertexBuffer {
val vb = meshVertexBuffer(6 * sides * (enveloppe.size - 1))
val vb = meshVertexBuffer(6 * sides * (envelope.size - 1))
vb.put {
generateCap(sides, radius, enveloppe, bufferWriter(this))
generateCap(sides, radius, envelope, bufferWriter(this))
}
return vb
}
/**
* Generate a shape by rotating an envelope around a vertical axis.
*
* @param sides the angular resolution of the cap
* @param radius the radius of the cap
* @param envelope a list of points defining the profile of the cap. The default envelope is a horizontal line which produces a flat round disk. By providing a more complex envelope one can create curved shapes like a bowl.
* @param writer the vertex writer function
*/
fun generateCap(
sides: Int,
radius: Double,
enveloppe: List<Vector2> = listOf(
envelope: List<Vector2> = listOf(
Vector2(0.0, 0.0),
Vector2(1.0, 0.0)
),
writer: VertexWriter
) {
val maxX = enveloppe.maxByOrNull { it.x } ?: Vector2(1.0, 0.0)
val maxX = envelope.maxByOrNull { it.x } ?: Vector2(1.0, 0.0)
val a = maxX.x
val cleanEnveloppe = enveloppe.map { Vector2((it.x / a) * radius, it.y) }
val cleanEnvelope = envelope.map { Vector2((it.x / a) * radius, it.y) }
val normals2D = enveloppe.zipWithNext().map {
val normals2D = envelope.zipWithNext().map {
val d = it.second - it.first
d.normalized.perpendicular(YPolarity.CCW_POSITIVE_Y)
}
val basePositions = cleanEnveloppe.map { Vector3(it.x, it.y, 0.0) }
val basePositions = cleanEnvelope.map { Vector3(it.x, it.y, 0.0) }
val baseNormals = normals2D.map { Vector3(it.x, it.y, 0.0) }
for (side in 0 until sides) {
@@ -58,9 +72,9 @@ fun generateCap(
for (segment in 0 until basePositions.size - 1) {
val p00 = v0[segment]
val p01 = v0[segment+1]
val p01 = v0[segment + 1]
val p10 = v1[segment]
val p11 = v1[segment+1]
val p11 = v1[segment + 1]
val nn0 = n0[segment]
val nn1 = n1[segment]
@@ -78,43 +92,55 @@ fun generateCap(
/**
* A shape created by rotating an envelope around a vertical axis.
* The default envelope is a vertical line which produces a hollow cylinder.
*
* @param sides the angular resolution of the cap
* @param length the length of the shape. A multiplier for the y component of the envelope
* @param envelope a list of points defining the profile of the shape. The default envelope is a vertical line which produces a hollow cylinder.
* @return A vertex buffer containing the triangles to render the 3D shape
*/
fun revolveMesh(
sides: Int,
length: Double,
enveloppe: List<Vector2> = listOf(
envelope: List<Vector2> = listOf(
Vector2(1.0, 0.0),
Vector2(1.0, 1.0)
)
): VertexBuffer {
val vb = meshVertexBuffer(6 * sides * (enveloppe.size - 1))
val vb = meshVertexBuffer(6 * sides * (envelope.size - 1))
vb.put {
generateRevolve(sides, length, enveloppe, bufferWriter(this))
generateRevolve(sides, length, envelope, bufferWriter(this))
}
return vb
}
/**
* Generate a shape by rotating an envelope around a vertical axis.
*
* @param sides the angular resolution of the cap
* @param length the length of the shape. A multiplier for the y component of the envelope
* @param envelope a list of points defining the profile of the shape. The default envelope is a vertical line which produces a hollow cylinder.
* @param writer the vertex writer function
*/
fun generateRevolve(
sides: Int,
length: Double,
enveloppe: List<Vector2> = listOf(
envelope: List<Vector2> = listOf(
Vector2(1.0, 0.0),
Vector2(1.0, 1.0)
),
writer: VertexWriter
) {
val maxY = enveloppe.maxByOrNull { it.y } ?: Vector2(0.0, 1.0)
val maxY = envelope.maxByOrNull { it.y } ?: Vector2(0.0, 1.0)
val a = maxY.y
val cleanEnveloppe = enveloppe.map { Vector2((it.x), (it.y/a - 0.5) * length ) }
val cleanEnvelope = envelope.map { Vector2((it.x), (it.y / a - 0.5) * length) }
val normals2D = enveloppe.zipWithNext().map {
val normals2D = envelope.zipWithNext().map {
val d = it.second - it.first
d.normalized.perpendicular() * Vector2(1.0, -1.0)
}
val basePositions = cleanEnveloppe.map { Vector3(it.x, it.y, 0.0) }
val basePositions = cleanEnvelope.map { Vector3(it.x, it.y, 0.0) }
val baseNormals = normals2D.map { Vector3(it.x, it.y, 0.0) }
for (side in 0 until sides) {
@@ -129,9 +155,9 @@ fun generateRevolve(
for (segment in 0 until basePositions.size - 1) {
val p00 = v0[segment]
val p01 = v0[segment+1]
val p01 = v0[segment + 1]
val p10 = v1[segment]
val p11 = v1[segment+1]
val p11 = v1[segment + 1]
val nn0 = n0[segment]
val nn1 = n1[segment]

View File

@@ -0,0 +1,141 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.mix
import org.openrndr.math.transforms.rotateZ
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
/**
* Creates a cylinder along the z-axis
*
* @param sides the number of sides of the cylinder
* @param segments the number of segments along the z-axis
* @param radius the radius of the cylinder
* @param length the length of the cylinder
* @param flipNormals generates inside-out geometry if true
* @param center center the cylinder on the z-plane
* @return A vertex buffer containing the triangles to render the 3D shape
*/
fun cylinderMesh(
sides: Int = 16,
segments: Int = 16,
radius: Double = 1.0,
length: Double,
flipNormals: Boolean = false,
center: Boolean = false,
): VertexBuffer {
val vertexCount = 6 * sides * segments
val vb = meshVertexBuffer(vertexCount)
vb.put {
generateCylinder(sides, segments, radius, length, flipNormals, center, bufferWriter(this))
}
return vb
}
/**
* Generate a cylinder along the z-axis
* @param sides the number of sides of the cylinder
* @param segments the number of segments along the z-axis
* @param radius the radius of the cylinder
* @param length the length of the cylinder
* @param flipNormals generates inside-out geometry if true
* @param center centers the cylinder on the z-plane if true
* @param writer the vertex writer function
*/
fun generateCylinder(
sides: Int,
segments: Int,
radius: Double,
length: Double,
flipNormals: Boolean = false,
center: Boolean = false,
writer: VertexWriter
) = generateTaperedCylinder(sides, segments, radius, radius, length, flipNormals, center, writer)
/**
* Generate a tapered cylinder along the z-axis
* @param sides the number of sides of the tapered cylinder
* @param segments the number of segments along the z-axis
* @param radiusStart the start radius of the tapered cylinder
* @param radiusEnd the end radius of the tapered cylinder
* @param length the length of the tapered cylinder
* @param flipNormals generates inside-out geometry if true
* @param center centers the cylinder on the z-plane if true
* @param writer the vertex writer function
*/
fun generateTaperedCylinder(
sides: Int,
segments: Int,
radiusStart: Double,
radiusEnd: Double,
length: Double,
flipNormals: Boolean = false,
center: Boolean = false,
writer: VertexWriter
) {
val dphi = (PI * 2) / sides
val ddeg = (360.0) / sides
val invertFactor = if (flipNormals) 1.0 else -1.0
val dr = radiusEnd - radiusStart
val baseNormal = Vector2(length, dr).normalized.perpendicular().let { Vector3(x = it.y, y = 0.0, z = it.x) }
val zOffset = if (center) -length / 2.0 else 0.0
for (segment in 0 until segments) {
val radius0 = mix(radiusStart, radiusEnd, segment * 1.0 / segments)
val radius1 = mix(radiusStart, radiusEnd, (segment + 1) * 1.0 / segments)
val z0 = (length / segments) * segment + zOffset
val z1 = (length / segments) * (segment + 1) + zOffset
for (side in 0 until sides) {
val x00 = cos(side * dphi) * radius0
val x10 = cos(side * dphi + dphi) * radius0
val y00 = sin(side * dphi) * radius0
val y10 = sin(side * dphi + dphi) * radius0
val x01 = cos(side * dphi) * radius1
val x11 = cos(side * dphi + dphi) * radius1
val y01 = sin(side * dphi) * radius1
val y11 = sin(side * dphi + dphi) * radius1
val u0 = (segment + 0.0) / segments
val u1 = (segment + 1.0) / segments
val v0 = (side + 0.0) / sides
val v1 = (side + 1.0) / sides
val n0 = (Matrix44.rotateZ(side * ddeg) * baseNormal.xyz0).xyz.normalized * invertFactor
val n1 = (Matrix44.rotateZ((side + 1) * ddeg) * baseNormal.xyz0).xyz.normalized * invertFactor
if (flipNormals) {
writer(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
writer(Vector3(x10, y10, z0), n1, Vector2(u0, v1))
writer(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
writer(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
writer(Vector3(x01, y01, z1), n0, Vector2(u1, v0))
writer(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
} else {
writer(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
writer(Vector3(x01, y01, z1), n0, Vector2(u1, v0))
writer(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
writer(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
writer(Vector3(x10, y10, z0), n1, Vector2(u0, v1))
writer(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
}
}
}
}

View File

@@ -8,12 +8,15 @@ import kotlin.math.sqrt
// Based on
// https://github.com/mrdoob/three.js/blob/master/src/geometries/DodecahedronGeometry.js
// Create:
// val dode = dodecahedronMesh(400.0)
// Draw:
// drawer.vertexBuffer(dode, DrawPrimitive.TRIANGLES)
fun dodecahedronMesh(radius: Double = 1.0): VertexBuffer {
/**
* A dodecahedron mesh
*
* @param radius the radius of the dodecahedron
* @return A vertex buffer containing the triangles to render the 3D shape
*/
fun dodecahedronMesh(
radius: Double = 1.0
): VertexBuffer {
val vb = meshVertexBuffer(12 * 3 * 3)
vb.put {
generateDodecahedron(radius, bufferWriter(this))
@@ -21,10 +24,19 @@ fun dodecahedronMesh(radius: Double = 1.0): VertexBuffer {
return vb
}
fun generateDodecahedron(radius: Double = 1.0, writer: VertexWriter) {
/**
* Generate dodecahedron mesh
*
* @param radius the radius of the dodecahedron
* @param writer the vertex writer function
*/
fun generateDodecahedron(
radius: Double = 1.0,
writer: VertexWriter
) {
val t = (1.0 + sqrt(5.0)) / 2;
val r = 1 / t;
val t = (1.0 + sqrt(5.0)) / 2
val r = 1 / t
val vertices = listOf(
// (±1, ±1, ±1)
@@ -74,7 +86,7 @@ fun generateDodecahedron(radius: Double = 1.0, writer: VertexWriter) {
vertices[i * 3 + 1],
vertices[i * 3 + 2]) * radius
}
val up = (tri[1] - tri[0]).cross(tri[2] - tri[0]).normalized;
val up = (tri[1] - tri[0]).cross(tri[2] - tri[0]).normalized
writer(tri[0], up, uv)
writer(tri[1], up, uv)
writer(tri[2], up, uv)

View File

@@ -0,0 +1,445 @@
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.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
* of the two contours are defined by the [frame0] and [frame1] matrices.
*
* @param linearContour the cross-section of the surface to create
* @param frame0 a transformation matrix that defines an initial position
* @param frame1 a transformation matrix that defines a final position
* @param writer the vertex writer function
*/
fun contourSegment(
linearContour: List<Vector3>,
frame0: Matrix44,
frame1: Matrix44,
writer: VertexWriter
) {
for (i in linearContour.indices) {
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 v10 = (frame1 * v0.xyz1).xyz
val v11 = (frame1 * v1.xyz1).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.
*
* @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 extrudeContourSteps(
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,
writer: VertexWriter
) {
val linearContour = contour.sampleLinear(contourDistanceTolerance)
val linearContourPoints = linearContour.adaptivePositions().map { it.xy0 }
val finalFrames = if (path.closed) frames + frames.first() else frames
// First add caps
extrudeCaps(linearContour.shape, path, startCap, endCap, frames, writer)
// Then add sides
finalFrames.windowed(2, 1).forEach {
contourSegment(linearContourPoints, it[0], it[1], writer)
}
}
/**
* 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.
*
* @param contour the cross-section of the mesh
* @param path the 3D path
* @param up0 the initial up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [contour]. Lower tolerance results in higher precision and step count.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision and step count.
* @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 extrudeContourAdaptive(
contour: ShapeContour,
path: Path3D,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
steps: List<Vector3> = path.adaptivePositions(pathDistanceTolerance),
frames: List<Matrix44> = steps.frames(up0),
startCap: Boolean = true,
endCap: Boolean = true,
writer: VertexWriter
) {
val linearContour = contour.sampleLinear(contourDistanceTolerance)
val linearContourPoints = linearContour.adaptivePositions().map { it.xy0 }
val finalFrames = if (path.closed) frames + frames.first() else frames
// First add caps
extrudeCaps(linearContour.shape, path, startCap, endCap, finalFrames, writer)
// Then add sides
finalFrames.windowed(2, 1).forEach {
contourSegment(linearContourPoints, it[0], it[1], writer)
}
}
/**
* Extrude a [shape] along a [path] specifying the number of steps.
*
* @param shape 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
* [shape]. 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 extrudeShapeSteps(
shape: Shape,
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,
endCap: Boolean,
writer: VertexWriter
) {
val linearShape = Shape(shape.contours.map { it.contour.sampleLinear(contourDistanceTolerance) })
// First add caps
extrudeCaps(linearShape, path, startCap, endCap, frames, writer)
// Then add sides
for (contour in linearShape.contours) {
extrudeContourSteps(
contour,
path,
stepCount,
up0,
contourDistanceTolerance,
pathDistanceTolerance,
steps,
frames,
startCap = false,
endCap = false,
writer
)
}
}
/**
* Extrude a [shape] along a [path]. The number of resulting steps
* along the path depends on the tolerance values.
*
* @param shape the cross-section of the mesh
* @param path the 3D path
* @param up0 the initial up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [shape]. Lower tolerance results in higher precision and step count.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision and step count.
* @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 extrudeShapeAdaptive(
shape: Shape,
path: Path3D,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
steps: List<Vector3> = path.adaptivePositions(pathDistanceTolerance),
frames: List<Matrix44> = steps.frames(up0),
startCap: Boolean,
endCap: Boolean,
writer: VertexWriter
) {
val linearShape = Shape(shape.contours.map { it.contour.sampleLinear(contourDistanceTolerance) })
// First add caps
extrudeCaps(linearShape, path, startCap, endCap, frames, writer)
// Then add sides
for (contour in linearShape.contours) {
extrudeContourAdaptive(
contour,
path,
up0,
contourDistanceTolerance,
pathDistanceTolerance,
steps,
frames,
startCap,
endCap,
writer
)
}
}
/**
* Extrude a [shape] along a [path] specifying the number of steps.
*
* @param shape the cross-section of the mesh
* @param path the 3D path
* @param stepCount the number of steps along the [path]
* @param up0 the up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [shape]. Lower tolerance results in higher precision.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision.
* @param startCap adds a start cap if set to true
* @param endCap adds an end cap if set to true
*/
fun TriangleMeshBuilder.extrudeShapeSteps(
shape: Shape,
path: Path3D,
stepCount: Int,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
startCap: Boolean = true,
endCap: Boolean = true,
) = extrudeShapeSteps(
shape,
path,
stepCount,
up0,
contourDistanceTolerance,
pathDistanceTolerance,
startCap = startCap,
endCap = endCap,
writer = this::write
)
/**
* Extrude a [shape] along a [path]. The number of resulting steps
* along the path depends on the tolerance values.
*
* @param shape the cross-section of the mesh
* @param path the 3D path
* @param up0 the initial up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [shape]. Lower tolerance results in higher precision and step count.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision and step count.
* @param startCap adds a start cap if set to true
* @param endCap adds an end cap if set to true
*/
fun TriangleMeshBuilder.extrudeShapeAdaptive(
shape: Shape,
path: Path3D,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
startCap: Boolean = true,
endCap: Boolean = true
) = extrudeShapeAdaptive(
shape,
path,
up0,
contourDistanceTolerance,
pathDistanceTolerance,
startCap = startCap,
endCap = endCap,
writer = this::write
)
/**
* 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.extrudeContourSteps(
contour: ShapeContour,
path: Path3D,
stepCount: Int,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5,
) = extrudeContourSteps(
contour,
path,
stepCount,
up0,
contourDistanceTolerance,
pathDistanceTolerance,
writer = this::write
)
/**
* Extrude a [contour] along a [path]. The number of resulting steps
* along the path depends on the tolerance values.
*
* @param contour the cross-section of the shape
* @param path the 3D path
* @param up0 the up-vector
* @param contourDistanceTolerance precision for calculating steps along
* [contour]. Lower tolerance results in higher precision and step count.
* @param pathDistanceTolerance precision for calculating steps along
* [path]. Lower tolerance results in higher precision and step count.
*/
fun TriangleMeshBuilder.extrudeContourAdaptive(
contour: ShapeContour,
path: Path3D,
up0: Vector3,
contourDistanceTolerance: Double = 0.5,
pathDistanceTolerance: Double = 0.5
) = extrudeContourAdaptive(
contour,
path,
up0,
contourDistanceTolerance,
pathDistanceTolerance,
writer = this::write
)

View File

@@ -0,0 +1,60 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector3
import org.openrndr.math.Vector4
import org.openrndr.math.transforms.buildTransform
/**
* Calculate frames (pose matrices) using parallel transport
* @param up0 initial up vector, should not be collinear with `this[1] - this[0]`
*/
fun List<Vector3>.frames(up0: Vector3): List<Matrix44> {
val result = mutableListOf<Matrix44>()
if (this.isEmpty()) {
return emptyList()
}
if (this.size == 1) {
return listOf(Matrix44.IDENTITY)
}
var up = up0.normalized
run {
val current = this[0]
val next = this[1]
val forward = (next - current).normalized
val right = (forward cross up).normalized
up = ((right cross forward)).normalized
result.add(Matrix44.fromColumnVectors(right.xyz0, up.xyz0, forward.xyz0, current.xyz1))
}
require(up.length > 0.0) { "initial `up.length` is zero in .frames()" }
for (i in 1 until size - 1) {
val prev = this[i - 1]
val current = this[i]
val next = this[i + 1]
val f1 = (next - current).normalized
val f0 = (current - prev).normalized
val forward = (f0 + f1).normalized
require(forward.length > 0.0) { "`forward.length` is zero in .frames()" }
val right = (forward cross up).normalized
up = ((right cross forward)).normalized
require(up.length > 0.0) { "`up.length` is zero in .frames()" }
require(right.length > 0.0) { "`right.length` is zero in .frames()" }
//val m = Matrix44.fromColumnVectors(right.xyz0, up.xyz0, forward.xyz0, current.xyz1)
val m = buildTransform {
translate(current)
multiply(Matrix44.fromColumnVectors(right.xyz0, up.xyz0, forward.xyz0, Vector4.UNIT_W))
}
result.add(m)
}
return result
}

View File

@@ -0,0 +1,280 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.BufferWriter
import org.openrndr.draw.VertexBuffer
import org.openrndr.draw.vertexBuffer
import org.openrndr.draw.vertexFormat
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.mod
import org.openrndr.shape.Shape
import org.openrndr.shape.triangulate
/**
* Vertex writer function interface
*/
typealias VertexWriter = (position: Vector3, normal: Vector3, texCoord: Vector2) -> Unit
/**
* create a [VertexWriter] that writes into a [java.nio.ByteBuffer] through [BufferWriter]
*/
fun bufferWriter(bw: BufferWriter): VertexWriter {
return { p, n, t ->
bw.write(p)
bw.write(n)
bw.write(t)
}
}
/**
* Creates a [VertexBuffer] that is suited for holding meshes.
* Each vertex contains:
* - `position` (vec3)
* - `normal` (vec3)
* - `textureCoordinate` (vec2)
*/
fun meshVertexBuffer(size: Int): VertexBuffer {
return vertexBuffer(vertexFormat {
position(3)
normal(3)
textureCoordinate(2)
}, size)
}
/**
* Creates a [VertexBuffer] that is suited for holding meshes.
* Each vertex contains:
* - `position` (vec3)
* - `normal` (vec3)
* - `textureCoordinate` (vec2)
* - `color` (vec4)
*/
fun meshVertexBufferWithColor(size: Int): VertexBuffer {
return vertexBuffer(vertexFormat {
position(3)
normal(3)
textureCoordinate(2)
color(4)
}, size)
}
@Deprecated("binary compatibility only")
fun extrudeShape(
shape: Shape,
front: Double,
back: Double,
distanceTolerance: Double = 0.5,
writer: VertexWriter
) {
extrudeShape(
shape,
front,
back,
distanceTolerance = distanceTolerance,
flipNormals = false,
writer = writer
)
}
/**
* Extrudes a [Shape] from its triangulations
*
* @param baseTriangles triangle vertices for the caps
* @param contours contour vertices for the sides
* @param front the `z` position of the front
* @param back the `z` position of the back
* @param frontScale scale factor for the front cap
* @param backScale scale factor for the back cap
* @param frontCap add a front cap if true
* @param backCap add a back cap if true
* @param sides add the sides if true
* @param flipNormals generates inside-out geometry if true
* @param writer the vertex writer function
*/
fun extrudeShape(
baseTriangles: List<Vector2>,
contours: List<List<Vector2>>,
front: Double,
back: Double,
frontScale: Double = 1.0,
backScale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
flipNormals: Boolean = false,
writer: VertexWriter
) {
val depth = back - front
val flip = if (flipNormals) 1.0 else -1.0
run {
val normal = Vector3(0.0, 0.0, depth).normalized * flip
val negativeNormal = normal * -1.0
if (frontCap) {
baseTriangles.reversed().forEach {
writer(
(it * frontScale).vector3(z = front),
normal,
Vector2.ZERO
)
}
}
if (backCap) {
baseTriangles.forEach {
writer(
(it * backScale).vector3(z = back),
negativeNormal,
Vector2.ZERO
)
}
}
}
if (sides) {
contours.forEach {
val points = it
val normals = (points.indices).map { index ->
val a = mod(index + 1, points.size)
val b = mod(index - 1, points.size)
(points[a] - points[b]).safeNormalized * -flip
}
val forward = Vector3(0.0, 0.0, depth)
val base = Vector3(0.0, 0.0, front)
var offset = 0.0
(points zip normals).zipWithNext().forEach { (left, right) ->
val width = right.first.distanceTo(left.first)
val frontRight = (right.first * frontScale).xy0 + base
val frontLeft = (left.first * frontScale).xy0 + base
val backRight = (right.first * backScale).xy0 + base + forward
val backLeft = (left.first * backScale).xy0 + base + forward
val height = frontRight.distanceTo(backRight)
val backRightUV = Vector2(offset + width, 0.0)
val backLeftUV = Vector2(offset, 0.0)
val frontLeftUV = Vector2(offset, height)
val frontRightUV = Vector2(offset + width, height)
val lnormal =
(frontLeft - backLeft).normalized.cross(left.second.xy0)
val rnormal =
(frontRight - backRight).normalized.cross(right.second.xy0)
writer(frontLeft, lnormal, frontLeftUV)
writer(frontRight, rnormal, frontRightUV)
writer(backRight, rnormal, backRightUV)
writer(backRight, rnormal, backRightUV)
writer(backLeft, lnormal, backLeftUV)
writer(frontLeft, lnormal, frontLeftUV)
offset += width
}
}
}
}
/**
* Extrudes a [shape] by triangulating it and creating side- and cap geometry.
* @param front the `z` position of the front
* @param back the `z` position of the back
* @param frontScale scale factor for the front cap
* @param backScale scale factor for the back cap
* @param frontCap add a front cap if true
* @param backCap add a back cap if true
* @param sides add the sides if true
* @param distanceTolerance
* @param flipNormals generates inside-out geometry if true
* @param writer the vertex writer function
*/
fun extrudeShape(
shape: Shape,
front: Double,
back: Double,
frontScale: Double = 1.0,
backScale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
distanceTolerance: Double = 0.5,
flipNormals: Boolean = false,
writer: VertexWriter
) {
val baseTriangles = triangulate(shape, distanceTolerance)
val points = shape.contours.map { it.adaptivePositions(distanceTolerance) }
extrudeShape(
baseTriangles = baseTriangles,
contours = points,
front = front,
back = back,
frontScale = frontScale,
backScale = backScale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
flipNormals = flipNormals,
writer = writer
)
}
/**
* Extrudes all [shapes]. Uses [writer] to write the resulting
* 3D meshes. The arguments are passed unmodified to [extrudeShape].
*/
fun extrudeShapes(
shapes: List<Shape>,
front: Double,
back: Double,
frontScale: Double = 1.0,
backScale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
distanceTolerance: Double = 0.5,
flipNormals: Boolean = false,
writer: VertexWriter
) {
shapes.forEach {
extrudeShape(
shape = it,
front = front,
back = back,
frontScale = frontScale,
backScale = backScale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
distanceTolerance = distanceTolerance,
flipNormals = flipNormals,
writer = writer
)
}
}
/**
* Return a normalized [Vector2], or [Vector2.ZERO] if the vector is too small
*/
private val Vector2.safeNormalized: Vector2
get() {
return if (length > 0.0001) {
normalized
} else {
Vector2.ZERO
}
}

View File

@@ -0,0 +1,137 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.shape.Rectangle
/**
* Generate a finite plane centered at [center], using the [right], [forward]
* and [up] vectors for its orientation.
* [width] and [height] specify the dimensions of the plane.
* [widthSegments] and [heightSegments] control the plane's number of
* segments.
* @return A vertex buffer containing the triangles to render the 3D shape.
*/
fun planeMesh(
center: Vector3,
right: Vector3,
forward: Vector3,
up: Vector3 = forward.cross(right).normalized,
width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1
): VertexBuffer {
val vertexCount = (widthSegments * heightSegments) * 6
val vb = meshVertexBuffer(vertexCount)
vb.put {
generatePlane(
center, right, forward, up,
width, height, widthSegments, heightSegments, bufferWriter(this)
)
}
return vb
}
/**
* Converts a [Rectangle] to a [VertexBuffer] 2D mesh matching its location and
* dimensions. [resolution] specifies the size in pixels of the triangles in
* the mesh.
* @return A vertex buffer containing the triangles to render the 3D shape.
*/
fun Rectangle.toMesh(
resolution: Double = 2.0
) = planeMesh(
center.xy0, Vector3.UNIT_X, Vector3.UNIT_Y, Vector3.UNIT_Z,
width, height,
(width / resolution).toInt(),
(height / resolution).toInt()
)
/**
* Generates a finite plane with its center at (0,0,0) and spanning the
* xz-plane.
* @return A vertex buffer containing the triangles to render the 3D shape.
*/
fun groundPlaneMesh(
width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1
) = planeMesh(
Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_Z, Vector3.UNIT_Y,
width, height, widthSegments, heightSegments
)
/**
* Generates a finite plane with its center at (0,0,0) and spanning the xy-plane
* @return A vertex buffer containing the triangles to render the 3D shape.
*/
fun wallPlaneMesh(
width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1
) = planeMesh(
Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_Y, Vector3.UNIT_Z,
width, height, widthSegments, heightSegments
)
/**
* Generate plane centered at [center], using the [right], [forward] and [up]
* vectors for its orientation.
* [width] and [height] specify the dimensions of the plane.
* [widthSegments] and [heightSegments] control the plane's number of
* segments.
*
* @param writer the vertex writer function
*/
fun generatePlane(
center: Vector3,
right: Vector3,
forward: Vector3,
up: Vector3 = forward.cross(right).normalized,
width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1,
writer: VertexWriter
) {
val forwardStep = forward.normalized * (height / heightSegments)
val rightStep = right.normalized * (width / widthSegments)
val corner = center -
forward.normalized * (height * 0.5) -
right.normalized * (width * 0.5)
val step = Vector2(1.0 / widthSegments, 1.0 / heightSegments)
for (v in 0 until heightSegments) {
for (u in 0 until widthSegments) {
val uv00 = Vector2(u + 0.0, v + 0.0) * step
val uv01 = Vector2(u + 0.0, v + 1.0) * step
val uv10 = Vector2(u + 1.0, v + 0.0) * step
val uv11 = Vector2(u + 1.0, v + 1.0) * step
val c00 = corner +
forwardStep * v.toDouble() + rightStep * u.toDouble()
val c01 = corner +
forwardStep * (v + 1).toDouble() + rightStep * u.toDouble()
val c10 = corner +
forwardStep * v.toDouble() + rightStep * (u + 1).toDouble()
val c11 = corner +
forwardStep * (v + 1).toDouble() + rightStep * (u + 1).toDouble()
writer(c11, up, uv00)
writer(c10, up, uv10)
writer(c00, up, uv11)
writer(c00, up, uv11)
writer(c01, up, uv01)
writer(c11, up, uv00)
}
}
}

View File

@@ -0,0 +1,127 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Spherical
import org.openrndr.math.Vector2
import kotlin.math.max
/**
* Returns a sphere mesh
*
* @param sides The number of steps around its axis.
* @param segments The number of steps from pole to pole.
* @param radius The radius of the sphere.
* @param flipNormals Create an inside-out shape if true.
*/
fun sphereMesh(
sides: Int = 16,
segments: Int = 16,
radius: Double = 1.0,
flipNormals: Boolean = false
): VertexBuffer {
val vertexCount = 2 * sides * 3 + max(0, (segments - 2)) * sides * 6
val vb = meshVertexBuffer(vertexCount)
vb.put {
generateSphere(sides, segments, radius, flipNormals, bufferWriter(this))
}
return vb
}
/**
* Generate sphere centered at the origin.
*
* @param sides The number of steps around its axis.
* @param segments The number of steps from pole to pole.
* @param radius The radius of the sphere.
* @param flipNormals Create an inside-out shape if true.
* @param writer The vertex writer function
*/
fun generateSphere(
sides: Int,
segments: Int,
radius: Double = 1.0,
flipNormals: Boolean = false,
writer: VertexWriter
) {
val invertFactor = if (flipNormals) -1.0 else 1.0
for (t in 0 until segments) {
for (s in 0 until sides) {
val st00 = Spherical(s * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st01 = Spherical(s * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val st10 = Spherical((s + 1) * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st11 = Spherical((s + 1) * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val thetaMax = 180.0 * 2.0
val phiMax = 180.0
when (t) {
0 -> {
writer(st00.cartesian, st00.cartesian.normalized * invertFactor, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * invertFactor, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * invertFactor, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
}
segments - 1 -> {
writer(st11.cartesian, st11.cartesian.normalized * invertFactor, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st10.cartesian, st10.cartesian.normalized * invertFactor, Vector2(st10.theta / thetaMax + 0.5, 1.0 - st10.phi / phiMax))
writer(st00.cartesian, st00.cartesian.normalized * invertFactor, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
}
else -> {
writer(st00.cartesian, st00.cartesian.normalized * invertFactor, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * invertFactor, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * invertFactor, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * invertFactor, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st10.cartesian, st10.cartesian.normalized * invertFactor, Vector2(st10.theta / thetaMax + 0.5, 1.0 - st10.phi / phiMax))
writer(st00.cartesian, st00.cartesian.normalized * invertFactor, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
}
}
}
}
}
/**
* Generate hemisphere centered at the origin.
*
* @param sides The number of steps around its axis.
* @param segments The number of steps from pole to pole.
* @param radius The radius of the sphere.
* @param flipNormals Create an inside-out shape if true.
* @param writer The vertex writer function
*/
fun generateHemisphere(
sides: Int,
segments: Int,
radius: Double = 1.0,
flipNormals: Boolean = false,
writer: VertexWriter
) {
val invertFactor = if (flipNormals) -1.0 else 1.0
for (t in 0 until segments) {
for (s in 0 until sides) {
val st00 = Spherical(s * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st01 = Spherical(s * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val st10 = Spherical((s + 1) * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st11 = Spherical((s + 1) * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val thetaMax = 180.0 * 2.0
val phiMax = 180.0 * 0.5
when (t) {
0 -> {
writer(st00.cartesian, st00.cartesian.normalized * invertFactor, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * invertFactor, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * invertFactor, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
}
else -> {
writer(st00.cartesian, st00.cartesian.normalized * invertFactor, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * invertFactor, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * invertFactor, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * invertFactor, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st10.cartesian, st10.cartesian.normalized * invertFactor, Vector2(st10.theta / thetaMax + 0.5, 1.0 - st10.phi / phiMax))
writer(st00.cartesian, st00.cartesian.normalized * invertFactor, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
}
}
}
}
}

View File

@@ -0,0 +1,634 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.collections.pop
import org.openrndr.collections.push
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.buildTransform
import org.openrndr.math.transforms.normalMatrix
import org.openrndr.math.transforms.rotate
import org.openrndr.shape.Shape
import org.openrndr.utils.buffer.MPPBuffer
/**
* A class that provides a simple Domain Specific Language
* to construct and deform triangle-based 3D meshes.
*
*/
class TriangleMeshBuilder {
var color = ColorRGBa.WHITE
var transform = Matrix44.IDENTITY
set(value) {
field = value
normalTransform = normalMatrix(value)
}
var normalTransform: Matrix44 = Matrix44.IDENTITY
private set
private val transformStack = ArrayDeque<Matrix44>()
/**
* Applies a three-dimensional translation to the [transform] matrix.
* Affects meshes added afterward.
*/
fun translate(x: Double, y: Double, z: Double) {
transform *= buildTransform {
translate(x, y, z)
}
}
/**
* Applies a rotation over an arbitrary axis to the [transform] matrix.
* Affects meshes added afterward.
* @param axis the axis to rotate over, will be normalized
* @param degrees the rotation in degrees
*/
fun rotate(axis: Vector3, degrees: Double) {
transform *= buildTransform {
rotate(axis, degrees)
}
}
/**
* Push the active [transform] matrix on the transform state stack.
*/
fun pushTransform() {
transformStack.push(transform)
}
/**
* Pop the active [transform] matrix from the transform state stack.
*/
fun popTransform() {
transform = transformStack.pop()
}
/**
* Pushes the [transform] matrix, calls [function] and pops.
* @param function the function that is called in the isolation
*/
fun isolated(function: TriangleMeshBuilder.() -> Unit) {
pushTransform()
function()
popTransform()
}
/**
* A container class for vertex [position], [normal], [texCoord] and
* [color].
*/
class VertexData(
val position: Vector3,
val normal: Vector3,
val texCoord: Vector2,
val color: ColorRGBa
) {
/**
* Return a new vertex with the position transformed with [transform]
* and the normal transformed with [normalTransform]. Used to
* translate, rotate or scale vertices.
*/
fun transform(
transform: Matrix44,
normalTransform: Matrix44
) = VertexData(
(transform * position.xyz1).xyz,
(normalTransform * normal.xyz0).xyz,
texCoord,
color
)
}
/**
* Vertex storage
*/
var data = mutableListOf<VertexData>()
/**
* Write new vertex data into [data]. The current [color] is used for the
* vertex.
*/
fun write(position: Vector3, normal: Vector3, texCoord: Vector2) {
data.add(
VertexData(position, normal, texCoord, color).transform(
transform,
normalTransform
)
)
}
/**
* Append [other] data into [data], combining the two meshes.
*/
fun concat(other: TriangleMeshBuilder) {
data.addAll(other.data)
}
/**
* Returns a [MPPBuffer] representation of [data] used for rendering.
*/
fun toByteBuffer(): MPPBuffer {
//val bb = ByteBuffer.allocateDirect(data.size * (3 * 4 + 3 * 4 + 2 * 4 + 4 * 4))
val bb = MPPBuffer.allocate(data.size * (3 * 4 + 3 * 4 + 2 * 4 + 4 * 4))
//bb.order(ByteOrder.nativeOrder())
bb.rewind()
for (d in data) {
bb.putFloat(d.position.x.toFloat())
bb.putFloat(d.position.y.toFloat())
bb.putFloat(d.position.z.toFloat())
bb.putFloat(d.normal.x.toFloat())
bb.putFloat(d.normal.y.toFloat())
bb.putFloat(d.normal.z.toFloat())
bb.putFloat(d.texCoord.x.toFloat())
bb.putFloat(d.texCoord.y.toFloat())
bb.putFloat(d.color.r.toFloat())
bb.putFloat(d.color.g.toFloat())
bb.putFloat(d.color.b.toFloat())
bb.putFloat(d.color.alpha.toFloat())
}
bb.rewind()
return bb
}
}
/**
* Add a sphere mesh
*
* @param sides The number of steps around its axis.
* @param segments The number of steps from pole to pole.
* @param radius The radius of the sphere.
* @param flipNormals Create an inside-out shape if true.
*/
fun TriangleMeshBuilder.sphere(
sides: Int,
segments: Int,
radius: Double,
flipNormals: Boolean = false
) {
generateSphere(sides, segments, radius, flipNormals, this::write)
}
/**
* Add a hemisphere
*
* @param sides The number of steps around its axis.
* @param segments The number of steps from pole to pole.
* @param radius The radius of the sphere.
* @param flipNormals Create an inside-out shape if true.
*/
fun TriangleMeshBuilder.hemisphere(
sides: Int,
segments: Int,
radius: Double,
flipNormals: Boolean = false
) {
generateHemisphere(sides, segments, radius, flipNormals, this::write)
}
/**
* Used by the [grid] methods. Specifies how the UV or UVW
* coordinates the user function receives are scaled.
*/
enum class GridCoordinates {
/**
* The coordinates are the cell location index as Double.
*/
INDEX,
/**
* The coordinates with the cell's location are normalized
* to the 0.0 ~ 1.0 range.
*/
UNIPOLAR,
/**
* The coordinates with the cell's location are normalized
* to the -1.0 ~ 1.0 range.
*/
BIPOLAR,
}
/**
* Create a 2D grid of [width] x [height] 3D elements.
* The [builder] function will get called with the `u` and `v`
* coordinates of each grid cell, so you have an opportunity to add meshes
* to the scene using those coordinates. The coordinate values will be scaled
* according to [coordinates]. Use:
* - [GridCoordinates.INDEX] to get UV cell indices as [Double]s.
* - [GridCoordinates.BIPOLAR] to get values between -1.0 and 1.0
* - [GridCoordinates.UNIPOLAR] to get values between 0.0 and 1.0
*/
fun TriangleMeshBuilder.grid(
width: Int,
height: Int,
coordinates: GridCoordinates = GridCoordinates.BIPOLAR,
builder: TriangleMeshBuilder.(u: Double, v: Double) -> Unit
) {
for (v in 0 until height) {
for (u in 0 until width) {
group {
when (coordinates) {
GridCoordinates.INDEX -> this.builder(u * 1.0, v * 1.0)
GridCoordinates.BIPOLAR -> this.builder(
2 * u / (width - 1.0) - 1,
2 * v / (height - 1.0) - 1
)
GridCoordinates.UNIPOLAR -> this.builder(
u / (width - 1.0),
v / (height - 1.0)
)
}
}
}
}
}
/**
* Create a 3D grid of [width] x [height] x [depth] 3D elements.
* The [builder] function will get called with the `u`, `v` and `w`
* coordinates of each grid cell, so you have an opportunity to add meshes
* to the scene using those coordinates. The coordinate values will be scaled
* according to [coordinates]. Use:
* - [GridCoordinates.INDEX] to get the UVW cell indices as [Double]s.
* - [GridCoordinates.BIPOLAR] to get values between -1.0 and 1.0
* - [GridCoordinates.UNIPOLAR] to get values between 0.0 and 1.0
*/
fun TriangleMeshBuilder.grid(
width: Int,
height: Int,
depth: Int,
coordinates: GridCoordinates = GridCoordinates.BIPOLAR,
builder: TriangleMeshBuilder.(u: Double, v: Double, w: Double) -> Unit
) {
for (w in 0 until depth) {
for (v in 0 until height) {
for (u in 0 until width) {
group {
when (coordinates) {
GridCoordinates.INDEX -> this.builder(
u * 1.0,
v * 1.0,
w * 1.0
)
GridCoordinates.BIPOLAR -> this.builder(
2 * u / (width - 1.0) - 1,
2 * v / (height - 1.0) - 1,
2 * w / (depth - 1.0) - 1
)
GridCoordinates.UNIPOLAR -> this.builder(
u / (width - 1.0),
v / (height - 1.0),
w / (depth - 1.0)
)
}
}
}
}
}
}
/**
* Twists a 3D mesh around an axis that starts at [Vector3.ZERO] and ends
* at [axis]. [degreesPerUnit] controls the amount of twist. [start] is
* currently unused.
*/
fun TriangleMeshBuilder.twist(
degreesPerUnit: Double,
start: Double,
axis: Vector3 = Vector3.UNIT_Y
) {
data = data.map {
val p = it.position.projectedOn(axis)
val t = when {
axis.x != 0.0 -> p.x / axis.x
axis.y != 0.0 -> p.y / axis.y
axis.z != 0.0 -> p.z / axis.z
else -> throw IllegalArgumentException("0 axis")
}
val r = Matrix44.rotate(axis, t * degreesPerUnit)
TriangleMeshBuilder.VertexData(
(r * it.position.xyz1).xyz,
(r * it.normal.xyz0).xyz,
it.texCoord,
this@twist.color
)
}.toMutableList()
}
/**
* Generate a box of size [width], [height] and [depth].
* Specify the number of segments with [widthSegments], [heightSegments] and
* [depthSegments]. Use [flipNormals] for an inside-out shape.
*/
fun TriangleMeshBuilder.box(
width: Double,
height: Double,
depth: Double,
widthSegments: Int = 1,
heightSegments: Int = 1,
depthSegments: Int = 1,
flipNormals: Boolean = false
) {
generateBox(
width,
height,
depth,
widthSegments,
heightSegments,
depthSegments,
flipNormals,
this::write
)
}
/**
* Generate a cylinder
*
* @param sides the number of sides of the cylinder
* @param segments the number of segments along the z-axis
* @param radius the radius of the cylinder
* @param length the length of the cylinder
* @param flipNormals generates inside-out geometry if true
* @param center center the cylinder on the z-plane
*/
fun TriangleMeshBuilder.cylinder(
sides: Int,
segments: Int,
radius: Double,
length: Double,
flipNormals: Boolean = false,
center: Boolean = false
) {
generateCylinder(
sides,
segments,
radius,
length,
flipNormals,
center,
this::write
)
}
/**
* Generate dodecahedron mesh
*
* @param radius the radius of the dodecahedron
*/
fun TriangleMeshBuilder.dodecahedron(radius: Double) {
generateDodecahedron(radius, this::write)
}
/**
* Generate a tapered cylinder along the z-axis
*
* @param sides the number of sides of the tapered cylinder
* @param segments the number of segments along the z-axis
* @param startRadius the start radius of the tapered cylinder
* @param endRadius the end radius of the tapered cylinder
* @param length the length of the tapered cylinder
* @param flipNormals generates inside-out geometry if true
* @param center centers the cylinder on the z-plane if true
*/
fun TriangleMeshBuilder.taperedCylinder(
sides: Int,
segments: Int,
startRadius: Double,
endRadius: Double,
length: Double,
flipNormals: Boolean = false,
center: Boolean = false
) {
generateTaperedCylinder(
sides,
segments,
startRadius,
endRadius,
length,
flipNormals,
center,
this::write
)
}
/**
* Generate a shape by rotating an envelope around a vertical axis.
*
* @param sides the angular resolution of the cap
* @param radius the radius of the cap
* @param envelope a list of points defining the profile of the cap.
* The default envelope is a horizontal line which produces a flat round disk.
* By providing a more complex envelope one can create curved shapes like a bowl.
*/
fun TriangleMeshBuilder.cap(
sides: Int,
radius: Double,
envelope: List<Vector2>
) {
generateCap(sides, radius, envelope, this::write)
}
/**
* Generate a shape by rotating an envelope around a vertical axis.
*
* @param sides the angular resolution of the cap
* @param length the length of the shape. A multiplier for the y component of the envelope
* @param envelope a list of points defining the profile of the shape.
* The default envelope is a vertical line which produces a hollow cylinder.
*/
fun TriangleMeshBuilder.revolve(
sides: Int,
length: Double,
envelope: List<Vector2>
) {
generateRevolve(sides, length, envelope, this::write)
}
/**
* Generate plane centered at [center], using the [right], [forward] and [up]
* vectors for its orientation.
* [width] and [height] specify the dimensions of the plane.
* [widthSegments] and [heightSegments] control the plane's number of
* segments.
*/
fun TriangleMeshBuilder.plane(
center: Vector3,
right: Vector3,
forward: Vector3,
up: Vector3,
width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1
) {
generatePlane(
center,
right,
forward,
up,
width,
height,
widthSegments,
heightSegments,
this::write
)
}
/**
* Extrudes a [Shape] from its triangulations
*
* @param baseTriangles triangle vertices for the caps
* @param contours contour vertices for the sides
* @param length the length of the extrusion
* @param scale scale factor for the caps
* @param frontCap add a front cap if true
* @param backCap add a back cap if true
* @param sides add the sides if true
*/
fun TriangleMeshBuilder.extrudeShape(
baseTriangles: List<Vector2>,
contours: List<List<Vector2>>,
length: Double,
scale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true
) {
extrudeShape(
baseTriangles = baseTriangles,
contours = contours,
front = -length / 2.0,
back = length / 2.0,
frontScale = scale,
backScale = scale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
flipNormals = false,
writer = this::write
)
}
/**
* Extrudes a [Shape]
*
* @param shape the [Shape] to extrude
* @param length length of the extrusion
* @param scale scale factor of the caps
* @param frontCap add a front cap if true
* @param backCap add a back cap if true
* @param sides add the sides if true
* @param distanceTolerance controls how many segments will be created. Lower
* values result in higher vertex counts.
*/
fun TriangleMeshBuilder.extrudeShape(
shape: Shape,
length: Double,
scale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
distanceTolerance: Double = 0.5
) {
extrudeShape(
shape = shape,
front = -length / 2.0,
back = length / 2.0,
frontScale = scale,
backScale = scale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
distanceTolerance = distanceTolerance,
flipNormals = false,
writer = this::write
)
}
/**
* Extrudes a list of [Shape]
*
* @param shapes The [Shape]s to extrude
* @param length length of the extrusion
* @param scale scale factor of the caps
* @param distanceTolerance controls how many segments will be created. Lower
* values result in higher vertex counts.
*/
fun TriangleMeshBuilder.extrudeShapes(
shapes: List<Shape>,
length: Double,
scale: Double = 1.0,
distanceTolerance: Double = 0.5
) {
extrudeShapes(
shapes = shapes,
front = -length / 2.0,
back = length / 2.0,
frontScale = scale,
backScale = scale,
frontCap = true,
backCap = true,
sides = true,
distanceTolerance = distanceTolerance,
flipNormals = false,
writer = this::write
)
}
/**
* Creates a triangle mesh builder
*
* @param vertexBuffer The optional [VertexBuffer] into which to write data.
* If not provided one is created.
* @param builder A user function that adds 3D meshes to the [vertexBuffer]
* @return The populated [VertexBuffer]
*/
fun buildTriangleMesh(
vertexBuffer: VertexBuffer? = null,
builder: TriangleMeshBuilder.() -> Unit
): VertexBuffer {
val gb = TriangleMeshBuilder()
gb.builder()
val vb = vertexBuffer ?: meshVertexBufferWithColor(gb.data.size)
val bb = gb.toByteBuffer()
bb.rewind()
vb.write(bb)
return vb
}
//fun generator(
// builder: TriangleMeshBuilder.() -> Unit
//): TriangleMeshBuilder {
// val gb = TriangleMeshBuilder()
// gb.builder()
// return gb
//}
/**
* Creates a group. Can be used to avoid leaking mesh properties like `color`
* and `transform` into following meshes or groups.
*
* @param builder A user function that adds 3D meshes to the [vertexBuffer]
* @see [TriangleMeshBuilder.isolated]
*/
fun TriangleMeshBuilder.group(
builder: TriangleMeshBuilder.() -> Unit
) {
val gb = TriangleMeshBuilder()
gb.builder()
this.concat(gb)
}

View File

@@ -1,73 +0,0 @@
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.*
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
fun main() {
application {
program {
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
extend(Orbital()) {
this.eye = Vector3(0.0, 10.0, 20.0)
this.lookAt = Vector3(0.0, 5.0, 0.0)
}
val m = meshGenerator {
group {
hemisphere(32, 16, 5.0)
transform(transform {
translate(0.0, 12.0, 0.0)
})
}
group {
cylinder(32, 1, 5.0, 6.0)
transform(transform {
translate(0.0, 9.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
})
}
group {
hemisphere(32, 16, 5.0)
transform(transform {
translate(0.0, 6.0, 0.0)
rotate(Vector3.UNIT_X, 180.0)
})
}
group {
val legCount = 12
val baseRadius = 3.0
val legRadius = 0.05
val legLength = 4.0
for (i in 0 until legCount) {
group {
val dphi = 360.0 / legCount
cylinder(32, 1, legRadius, legLength)
transform(transform {
rotate(Vector3.UNIT_Y, dphi * i)
translate(baseRadius, 0.0, 0.0)
rotate(Vector3.UNIT_Z, -15.0)
translate(0.0, legLength/2.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
})
}
}
}
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -1,94 +0,0 @@
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.*
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
fun main() {
application {
program {
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
extend(Orbital()) {
this.eye = Vector3(0.0, 10.0, 20.0)
this.lookAt = Vector3(0.0, 5.0, 0.0)
}
val m = meshGenerator {
group {
hemisphere(32, 16, 5.0)
transform(transform {
translate(0.0, 12.0, 0.0)
})
}
val ridges = 5
val midLength = 6.0
val ridgeLength = midLength / ridges
val ridgeRadius = 5.5
for (r in 0 until ridges) {
group {
taperedCylinder(32, 1, 5.0, ridgeRadius, ridgeLength/ 2.0)
transform(transform {
translate(0.0,
ridgeLength/4.0 + r * ridgeLength + 6.0,
0.0)
rotate(Vector3.UNIT_X, 270.0)
})
}
group {
taperedCylinder(32, 1, ridgeRadius, 5.0, ridgeLength/2.0)
transform(transform {
translate(0.0,
ridgeLength/4.0 + ridgeLength/2.0 + r * ridgeLength + 6.0,
0.0)
rotate(Vector3.UNIT_X, 270.0)
})
}
}
group {
hemisphere(32, 16, 5.0)
transform(transform {
translate(0.0, 6.0, 0.0)
rotate(Vector3.UNIT_X, 180.0)
})
}
group {
val legCount = 12
val baseRadius = 3.0
val legRadius = 0.05
val legLength = 4.0
for (i in 0 until legCount) {
group {
val dphi = 360.0 / legCount
cylinder(32, 1, legRadius, legLength)
transform(transform {
rotate(Vector3.UNIT_Y, dphi * i)
translate(baseRadius, 0.0, 0.0)
rotate(Vector3.UNIT_Z, -15.0)
translate(0.0, legLength/2.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
})
}
}
}
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -1,119 +0,0 @@
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.*
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
fun main() {
application {
program {
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
extend(Orbital()) {
this.eye = Vector3(0.0, 15.0, 15.0)
}
val m = meshGenerator {
val sides = 12
group {
cap(sides, 5.0, listOf(
Vector2(0.0, 1.0),
Vector2(0.5, 1.0),
Vector2(0.5, 0.5),
Vector2(0.9, 0.5),
Vector2(1.0, 0.0))
)
transform(transform {
translate(0.0, 12.0, 0.0)
})
}
val ridges = 5
val midLength = 6.0
val ridgeLength = midLength / ridges
val ridgeRadius = 5.5
for (r in 0 until ridges) {
group {
taperedCylinder(sides, 1, 5.0, ridgeRadius, ridgeLength / 3.0)
transform(transform {
translate(
0.0,
ridgeLength / 6.0 + r * ridgeLength + 6.0,
0.0
)
rotate(Vector3.UNIT_X, 270.0)
})
}
group {
taperedCylinder(sides, 1, ridgeRadius, ridgeRadius, ridgeLength / 3.0)
transform(transform {
translate(
0.0,
ridgeLength / 6.0 + ridgeLength / 3.0 + r * ridgeLength + 6.0,
0.0
)
rotate(Vector3.UNIT_X, 270.0)
})
}
group {
taperedCylinder(sides, 1, ridgeRadius, 5.0, ridgeLength / 3.0)
transform(transform {
translate(
0.0,
ridgeLength / 6.0 + 2 * ridgeLength / 3.0 + r * ridgeLength + 6.0,
0.0
)
rotate(Vector3.UNIT_X, 270.0)
})
}
}
group {
cap(sides, 5.0, listOf(Vector2(0.0, 0.0), Vector2(1.0, 0.0)))
transform(transform {
translate(0.0, 6.0, 0.0)
rotate(Vector3.UNIT_X, 180.0)
})
}
group {
val legCount = 12
val baseRadius = 4.5
val legRadius = 0.05
val legLength = 7.0
for (i in 0 until legCount) {
group {
val dphi = 360.0 / legCount
cylinder(sides, 1, legRadius, legLength)
transform(transform {
rotate(Vector3.UNIT_Y, dphi * i)
translate(baseRadius, 0.0, 0.0)
//rotate(Vector3.UNIT_Z, -15.0)
translate(0.0, legLength / 2.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
})
}
}
}
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -1,3 +1,4 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
@@ -9,12 +10,15 @@ import org.openrndr.shape.Rectangle
fun main() {
application {
configure {
multisample = WindowMultisample.SampleCount(8)
}
program {
val meshes = listOf(
boxMesh(1.0, 1.0, 1.0),
sphereMesh(radius = 0.5),
dodecahedronMesh(0.5),
cylinderMesh(radius = 0.5, length = 1.0),
cylinderMesh(radius = 0.5, length = 1.0, center = true),
planeMesh(Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_Y),
capMesh(
15, 0.5,

View File

@@ -1,15 +1,19 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.CullTestPass
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.colorBuffer
import org.openrndr.draw.shadeStyle
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.boxMesh
import org.openrndr.math.Vector3
fun main() {
application {
configure {
multisample = WindowMultisample.SampleCount(8)
}
program {
val box = boxMesh(1.0, 1.0, 1.0)
@@ -22,11 +26,6 @@ fun main() {
}
s.upload()
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
extend(Orbital()) {
eye = Vector3(1.0, 1.0, 1.0)
}
@@ -38,6 +37,7 @@ fun main() {
""".trimIndent()
parameter("texture", texture)
}
drawer.drawStyle.cullTestPass = CullTestPass.FRONT
drawer.vertexBuffer(box, DrawPrimitive.TRIANGLES)
}
}

View File

@@ -1,32 +1,32 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.box
import org.openrndr.extra.meshgenerators.group
import org.openrndr.extra.meshgenerators.meshGenerator
import org.openrndr.extra.meshgenerators.buildTriangleMesh
import org.openrndr.extra.meshgenerators.sphere
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
val m = meshGenerator {
val m = buildTriangleMesh {
color = ColorRGBa.PINK
sphere(32, 32, 1.0)
group {
box(4.0, 4.0, 4.0)
transform(transform {
translate(0.0, -2.0, 0.0)
})
}
}
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
color = ColorRGBa.WHITE
translate(0.0, -2.0, 0.0)
box(4.0, 4.0, 4.0)
}
extend(Orbital()) {
this.eye = Vector3(0.0, 3.0, 7.0)
this.lookAt = Vector3(0.0, 2.0, 0.0)
@@ -35,6 +35,7 @@ fun main() {
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill = va_color;
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}

View File

@@ -0,0 +1,67 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.buildTriangleMesh
import org.openrndr.extra.meshgenerators.cylinder
import org.openrndr.extra.meshgenerators.hemisphere
import org.openrndr.math.Vector3
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
extend(Orbital()) {
this.eye = Vector3(0.0, 10.0, 20.0)
this.lookAt = Vector3(0.0, 5.0, 0.0)
}
val m = buildTriangleMesh {
isolated {
translate(0.0, 12.0, 0.0)
hemisphere(32, 16, 5.0)
}
isolated {
translate(0.0, 9.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
cylinder(32, 1, 5.0, 6.0, center = true)
}
isolated {
translate(0.0, 6.0, 0.0)
rotate(Vector3.UNIT_X, 180.0)
hemisphere(32, 16, 5.0)
}
isolated {
val legCount = 12
val baseRadius = 3.0
val legRadius = 0.05
val legLength = 4.0
for (i in 0 until legCount) {
isolated {
val dphi = 360.0 / legCount
rotate(Vector3.UNIT_Y, dphi * i)
translate(baseRadius, 0.0, 0.0)
rotate(Vector3.UNIT_Z, -15.0)
translate(0.0, legLength / 2.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
cylinder(32, 1, legRadius, legLength, center = true)
}
}
}
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -0,0 +1,83 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.*
import org.openrndr.math.Vector3
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
extend(Orbital()) {
this.eye = Vector3(0.0, 10.0, 20.0)
this.lookAt = Vector3(0.0, 5.0, 0.0)
}
val m = buildTriangleMesh {
isolated {
translate(0.0, 12.0, 0.0)
hemisphere(32, 16, 5.0)
}
val ridges = 5
val midLength = 6.0
val ridgeLength = midLength / ridges
val ridgeRadius = 5.5
for (r in 0 until ridges) {
isolated {
translate(0.0,
ridgeLength/4.0 + r * ridgeLength + 6.0,
0.0)
rotate(Vector3.UNIT_X, 270.0)
taperedCylinder(32, 1, 5.0, ridgeRadius, ridgeLength/ 2.0, center = true)
}
isolated {
translate(0.0,
ridgeLength/4.0 + ridgeLength/2.0 + r * ridgeLength + 6.0,
0.0)
rotate(Vector3.UNIT_X, 270.0)
taperedCylinder(32, 1, ridgeRadius, 5.0, ridgeLength/2.0, center = true)
}
}
isolated {
translate(0.0, 6.0, 0.0)
rotate(Vector3.UNIT_X, 180.0)
hemisphere(32, 16, 5.0)
}
isolated {
val legCount = 12
val baseRadius = 3.0
val legRadius = 0.05
val legLength = 4.0
for (i in 0 until legCount) {
isolated {
val dphi = 360.0 / legCount
rotate(Vector3.UNIT_Y, dphi * i)
translate(baseRadius, 0.0, 0.0)
rotate(Vector3.UNIT_Z, -15.0)
translate(0.0, legLength/2.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
cylinder(32, 1, legRadius, legLength, center = true)
}
}
}
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -0,0 +1,104 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.*
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
extend(Orbital()) {
this.eye = Vector3(0.0, 15.0, 15.0)
}
val m = buildTriangleMesh {
val sides = 12
isolated {
translate(0.0, 12.0, 0.0)
cap(sides, 5.0, listOf(
Vector2(0.0, 1.0),
Vector2(0.5, 1.0),
Vector2(0.5, 0.5),
Vector2(0.9, 0.5),
Vector2(1.0, 0.0))
)
}
val ridges = 5
val midLength = 6.0
val ridgeLength = midLength / ridges
val ridgeRadius = 5.5
for (r in 0 until ridges) {
isolated {
translate(
0.0,
ridgeLength / 6.0 + r * ridgeLength + 6.0,
0.0
)
rotate(Vector3.UNIT_X, 270.0)
taperedCylinder(sides, 1, 5.0, ridgeRadius, ridgeLength / 3.0, center = true)
}
isolated {
translate(
0.0,
ridgeLength / 6.0 + ridgeLength / 3.0 + r * ridgeLength + 6.0,
0.0
)
rotate(Vector3.UNIT_X, 270.0)
taperedCylinder(sides, 1, ridgeRadius, ridgeRadius, ridgeLength / 3.0, center = true)
}
isolated {
translate(
0.0,
ridgeLength / 6.0 + 2 * ridgeLength / 3.0 + r * ridgeLength + 6.0,
0.0
)
rotate(Vector3.UNIT_X, 270.0)
taperedCylinder(sides, 1, ridgeRadius, 5.0, ridgeLength / 3.0, center = true)
}
}
isolated {
translate(0.0, 6.0, 0.0)
rotate(Vector3.UNIT_X, 180.0)
cap(sides, 5.0, listOf(Vector2(0.0, 0.0), Vector2(1.0, 0.0)))
}
isolated {
val legCount = 12
val baseRadius = 4.5
val legRadius = 0.05
val legLength = 7.0
for (i in 0 until legCount) {
isolated {
val dphi = 360.0 / legCount
rotate(Vector3.UNIT_Y, dphi * i)
translate(baseRadius, 0.0, 0.0)
translate(0.0, legLength / 2.0, 0.0)
rotate(Vector3.UNIT_X, 90.0)
cylinder(sides, 1, legRadius, legLength, center = true)
}
}
}
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -1,30 +1,30 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.draw.CullTestPass
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.*
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
import org.openrndr.shape.Circle
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
extend(Orbital()) {
this.eye = Vector3(0.0, 30.0, 50.0)
}
val m = meshGenerator {
val m = buildTriangleMesh {
grid(5,5, 5) { u, v, w ->
extrudeShape(Circle(0.0, 0.0, 50.0).shape, 4.0, scale = 0.1)
transform(transform{ translate(u*20.0, v*20.0, w * 20.0)} )
isolated {
translate(u * 20.0, v * 20.0, w * 20.0)
extrudeShape(Circle(0.0, 0.0, 50.0).shape, 4.0, scale = 0.1)
}
}
twist(360.0/200.0, 0.0)
twist(360.0/200.0, 0.0, Vector3.UNIT_X)
@@ -37,6 +37,7 @@ fun main() {
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.drawStyle.cullTestPass = CullTestPass.FRONT
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}

View File

@@ -0,0 +1,67 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.buildTriangleMesh
import org.openrndr.extra.meshgenerators.extrudeContourSteps
import org.openrndr.math.Vector3
import org.openrndr.math.catmullRom
import org.openrndr.shape.Circle
import org.openrndr.shape.toPath3D
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
val m = buildTriangleMesh {
color = ColorRGBa.PINK
val path = listOf(
Vector3(0.0, 0.0, 0.0),
Vector3(-2.0, 2.0, 2.0),
Vector3(2.0, -4.0, 4.0),
Vector3(0.0, 0.0, 8.0)
).catmullRom(0.5, closed = false).toPath3D()
translate(-1.0, 0.0, 0.0)
for (i in 0 until 3) {
extrudeContourSteps(
Circle(0.0, 0.0, 0.5).contour,
path,
160,
Vector3.UNIT_Y,
contourDistanceTolerance = 0.02,
pathDistanceTolerance = 0.001
)
translate(1.0, 0.0, 0.0)
}
}
extend(Orbital()) {
this.eye = Vector3(0.0, 3.0, 7.0)
this.lookAt = Vector3(0.0, 2.0, 0.0)
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill = va_color;
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -0,0 +1,71 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.buildTriangleMesh
import org.openrndr.extra.meshgenerators.extrudeShapeSteps
import org.openrndr.math.Vector3
import org.openrndr.math.catmullRom
import org.openrndr.shape.Circle
import org.openrndr.shape.Shape
import org.openrndr.shape.toPath3D
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
val m = buildTriangleMesh {
color = ColorRGBa.PINK
val path = listOf(
Vector3(0.0, 0.0, 0.0),
Vector3(-2.0, 2.0, 2.0),
Vector3(2.0, -4.0, 4.0),
Vector3(0.0, 0.0, 8.0)
).catmullRom(0.5, closed = false).toPath3D()
translate(-5.0, 0.0, 0.0)
val ring = Shape(listOf(Circle(0.0, 0.0, 0.5).contour, Circle(0.0, 0.0, 0.25).contour.reversed))
for (i in 0 until 5) {
extrudeShapeSteps(
ring,
path,
160,
Vector3.UNIT_Y,
contourDistanceTolerance = 0.02,
pathDistanceTolerance = 0.001
)
translate(2.0, 0.0, 0.0)
}
}
extend(Orbital()) {
this.eye = Vector3(0.0, 3.0, 7.0)
this.lookAt = Vector3(0.0, 2.0, 0.0)
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill = va_color;
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -0,0 +1,96 @@
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.buildTriangleMesh
import org.openrndr.extra.meshgenerators.extrudeContourAdaptive
import org.openrndr.math.Polar
import org.openrndr.math.Vector3
import org.openrndr.math.asDegrees
import org.openrndr.math.asRadians
import org.openrndr.shape.Circle
import org.openrndr.shape.Path3D
import kotlin.math.PI
import kotlin.math.exp
fun main() {
application {
configure {
width = 800
height = 800
multisample = WindowMultisample.SampleCount(8)
}
program {
fun spiralPath(a: Double, k: Double, cycles: Double, steps: Int, direction:Double = 1.0): Path3D {
val points = (0 until steps).map {
val theta = ((PI * 2.0 * cycles) / steps) * it
val radius = a * exp(k * theta)
val c = Polar(theta.asDegrees, radius).cartesian
c.xy0
}
return Path3D.fromPoints(points, false)
}
val spiral = buildTriangleMesh {
for (i in -1..1 step 2) {
val p = spiralPath(0.2 * i, 0.25, 4.0, 400)
extrudeContourAdaptive(
Circle(0.0, 0.0, 0.1).contour,
p,
Vector3.UNIT_Z,
contourDistanceTolerance = 0.02,
pathDistanceTolerance = 0.001
)
}
isolated {
color = ColorRGBa.YELLOW
rotate(Vector3.UNIT_X, 90.0)
//rotate(Vector3.UNIT_Y, 45.0)
for (j in 0 until 1) {
for (i in -1..1 step 2) {
val rotationDegrees = j * 180.0 / 1.0
val rotation = rotationDegrees.asRadians
val scale = exp(rotation * 0.25)
val p = spiralPath(0.2 * i * scale, 0.25, 4.0, 400)
extrudeContourAdaptive(
Circle(0.0, 0.0, 0.1).contour,
p,
Vector3.UNIT_Z,
contourDistanceTolerance = 0.02,
pathDistanceTolerance = 0.001
)
}
rotate(Vector3.UNIT_Y, 180.0 / 1.0)
}
}
}
extend(Orbital())
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
x_fill = va_color;
x_fill.rgb *= v_viewNormal.z;
""".trimIndent()
}
drawer.rotate(Vector3.UNIT_X, seconds*20.0)
drawer.vertexBuffer(spiral, DrawPrimitive.TRIANGLES)
}
}
}
}

View File

@@ -1,61 +0,0 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Vector3
fun boxMesh(width: Double = 1.0, height: Double = 1.0, depth: Double = 1.0,
widthSegments: Int = 1, heightSegments: Int = 1, depthSegments: Int = 1,
invert: Boolean = false): VertexBuffer {
val vb = meshVertexBuffer(widthSegments * heightSegments * 6 * 2 +
widthSegments * depthSegments * 6 * 2 +
heightSegments * depthSegments * 6 * 2)
vb.put {
generateBox(width, height, depth,
widthSegments, heightSegments, depthSegments,
invert, bufferWriter(this))
}
return vb
}
fun generateBox(width: Double = 1.0, height: Double = 1.0, depth: Double = 1.0,
widthSegments: Int = 1, heightSegments: Int = 1, depthSegments: Int = 1,
invert: Boolean = false,
writer: VertexWriter) {
val sign = if (invert) -1.0 else 1.0
// +x -- ZY
generatePlane(Vector3(width / 2.0 * sign, 0.0, 0.0),
Vector3.UNIT_Z, Vector3.UNIT_Y, Vector3.UNIT_X,
-depth, -height,
depthSegments, heightSegments, writer)
// -x -- ZY
generatePlane(Vector3(-width / 2.0 * sign, 0.0, 0.0),
Vector3.UNIT_Z, Vector3.UNIT_Y, -Vector3.UNIT_X,
-depth, height,
depthSegments, heightSegments, writer)
// +y -- XZ
generatePlane(Vector3(0.0, height / 2.0 * sign, 0.0),
Vector3.UNIT_X, Vector3.UNIT_Z, Vector3.UNIT_Y,
width, depth,
widthSegments, depthSegments, writer)
// -y -- XZ
generatePlane(Vector3(0.0, -height / 2.0 * sign, 0.0),
Vector3.UNIT_X, Vector3.UNIT_Z, -Vector3.UNIT_Y,
width, -depth,
widthSegments, depthSegments, writer)
// +z -- XY
generatePlane(Vector3(0.0, 0.0, depth / 2.0 * sign),
Vector3.UNIT_X, Vector3.UNIT_Y, Vector3.UNIT_Z,
-width, height,
widthSegments, heightSegments, writer)
// -z -- XY
generatePlane(Vector3(0.0, 0.0, -depth / 2.0 * sign),
Vector3.UNIT_X, Vector3.UNIT_Y, -Vector3.UNIT_Z,
width, height,
widthSegments, heightSegments, writer)
}

View File

@@ -1,83 +0,0 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.mix
import org.openrndr.math.transforms.rotateZ
fun cylinderMesh(sides: Int = 16, segments: Int = 16, radius: Double = 1.0, length: Double, invert: Boolean = false): VertexBuffer {
val vertexCount = 6 * sides * segments
val vb = meshVertexBuffer(vertexCount)
vb.put {
generateCylinder(sides, segments, radius, length, invert, bufferWriter(this))
}
return vb
}
fun generateCylinder(sides: Int, segments: Int, radius: Double, length: Double, invert: Boolean = false, vertexWriter: VertexWriter) {
return generateTaperedCylinder(sides, segments, radius, radius, length, invert, vertexWriter)
}
fun generateTaperedCylinder(sides: Int, segments: Int, radiusStart: Double, radiusEnd:Double, length: Double, invert: Boolean = false, vertexWriter: VertexWriter) {
val dphi = (Math.PI * 2) / sides
val ddeg = (360.0) / sides
val invertFactor = if (invert) 1.0 else -1.0
val dr = radiusEnd - radiusStart
val baseNormal = Vector2(length, dr).normalized.perpendicular().let { Vector3(x=it.y, y=0.0, z=it.x)}
//val baseNormal = Vector3(1.0, 0.0, 0.0)
for (segment in 0 until segments) {
val radius0 = mix(radiusStart, radiusEnd, segment*1.0/segments)
val radius1 = mix(radiusStart, radiusEnd, (segment+1)*1.0/segments)
val z0 = (length / segments) * segment - length/2.0
val z1 = (length / segments) * (segment + 1) - length/2.0
for (side in 0 until sides) {
val x00 = Math.cos(side * dphi) * radius0
val x10 = Math.cos(side * dphi + dphi) * radius0
val y00 = Math.sin(side * dphi) * radius0
val y10 = Math.sin(side * dphi + dphi) * radius0
val x01 = Math.cos(side * dphi) * radius1
val x11 = Math.cos(side * dphi + dphi) * radius1
val y01 = Math.sin(side * dphi) * radius1
val y11 = Math.sin(side * dphi + dphi) * radius1
val u0 = (segment + 0.0) / segments
val u1 = (segment + 1.0) / segments
val v0 = (side + 0.0) / sides
val v1 = (side + 1.0) / sides
val n0 = (Matrix44.rotateZ(side * ddeg) * baseNormal.xyz0).xyz.normalized * invertFactor
val n1 = (Matrix44.rotateZ((side+1) * ddeg) * baseNormal.xyz0).xyz.normalized * invertFactor
if (invert) {
vertexWriter(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
vertexWriter(Vector3(x10, y10, z0), n1, Vector2(u0, v1))
vertexWriter(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
vertexWriter(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
vertexWriter(Vector3(x01, y01, z1), n0, Vector2(u1, v0))
vertexWriter(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
} else {
vertexWriter(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
vertexWriter(Vector3(x01, y01, z1), n0, Vector2(u1, v0))
vertexWriter(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
vertexWriter(Vector3(x11, y11, z1), n1, Vector2(u1, v1))
vertexWriter(Vector3(x10, y10, z0), n1, Vector2(u0, v1))
vertexWriter(Vector3(x00, y00, z0), n0, Vector2(u0, v0))
}
}
}
}

View File

@@ -1,230 +0,0 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.normalMatrix
import org.openrndr.math.transforms.rotate
import org.openrndr.shape.Shape
import java.nio.ByteBuffer
import java.nio.ByteOrder
class GeneratorBuffer {
class VertexData(val position: Vector3, val normal: Vector3, val texCoord: Vector2)
var data = mutableListOf<VertexData>()
fun write(position: Vector3, normal: Vector3, texCoord: Vector2) {
data.add(VertexData(position, normal, texCoord))
}
fun concat(other: GeneratorBuffer) {
data.addAll(other.data)
}
fun transform(m: Matrix44) {
val nm = normalMatrix(m)
data = data.map {
VertexData((m * (it.position.xyz1)).xyz, (nm * (it.normal.xyz0)).xyz, it.texCoord)
}.toMutableList()
}
fun transformUV(m: Matrix44) {
data = data.map {
VertexData(it.position, it.normal, (m * (it.texCoord.xy01)).xy)
}.toMutableList()
}
fun toByteBuffer(): ByteBuffer {
val bb = ByteBuffer.allocateDirect(data.size * (3 * 4 + 3 * 4 + 2 * 4))
bb.order(ByteOrder.nativeOrder())
bb.rewind()
for (d in data) {
bb.putFloat(d.position.x.toFloat())
bb.putFloat(d.position.y.toFloat())
bb.putFloat(d.position.z.toFloat())
bb.putFloat(d.normal.x.toFloat())
bb.putFloat(d.normal.y.toFloat())
bb.putFloat(d.normal.z.toFloat())
bb.putFloat(d.texCoord.x.toFloat())
bb.putFloat(d.texCoord.y.toFloat())
}
return bb
}
}
fun GeneratorBuffer.sphere(sides: Int, segments: Int, radius: Double, invert: Boolean = false) {
generateSphere(sides, segments, radius, invert, this::write)
}
fun GeneratorBuffer.hemisphere(sides: Int, segments: Int, radius: Double, invert: Boolean = false) {
generateHemisphere(sides, segments, radius, invert, this::write)
}
enum class GridCoordinates {
INDEX,
UNIPOLAR,
BIPOLAR,
}
fun GeneratorBuffer.grid(width: Int, height: Int, coordinates: GridCoordinates = GridCoordinates.BIPOLAR, builder: GeneratorBuffer.(u: Double, v: Double) -> Unit) {
for (v in 0 until height) {
for (u in 0 until width) {
group {
when (coordinates) {
GridCoordinates.INDEX -> this.builder(u * 1.0, v * 1.0)
GridCoordinates.BIPOLAR -> this.builder(2 * u / (width - 1.0) - 1,
2 * v / (height - 1.0) - 1)
GridCoordinates.UNIPOLAR -> this.builder(u / (width - 1.0), v / (height - 1.0))
}
}
}
}
}
fun GeneratorBuffer.twist(degreesPerUnit: Double, start: Double, axis: Vector3 = Vector3.UNIT_Y) {
data = data.map {
val p = it.position.projectedOn(axis)
val t = if (axis.x != 0.0) p.x / axis.x else if (axis.y != 0.0) p.y / axis.y else if (axis.z != 0.0) p.z / axis.z else
throw IllegalArgumentException("0 axis")
val r = Matrix44.rotate(axis, t * degreesPerUnit)
GeneratorBuffer.VertexData((r * it.position.xyz1).xyz, (r * it.normal.xyz0).xyz, it.texCoord)
}.toMutableList()
}
fun GeneratorBuffer.grid(width: Int, height: Int, depth: Int, coordinates: GridCoordinates = GridCoordinates.BIPOLAR, builder: GeneratorBuffer.(u: Double, v: Double, w: Double) -> Unit) {
for (w in 0 until depth) {
for (v in 0 until height) {
for (u in 0 until width) {
group {
when (coordinates) {
GridCoordinates.INDEX -> this.builder(u * 1.0, v * 1.0, w * 1.0)
GridCoordinates.BIPOLAR -> this.builder(2 * u / (width - 1.0) - 1,
2 * v / (height - 1.0) - 1, 2 * w / (depth - 1.0) - 1)
GridCoordinates.UNIPOLAR -> this.builder(u / (width - 1.0), v / (height - 1.0), w / (depth - 1.0))
}
}
}
}
}
}
fun GeneratorBuffer.box(width: Double, height: Double, depth: Double, widthSegments: Int = 1, heightSegments: Int = 1, depthSegments: Int = 1, invert: Boolean = false) {
generateBox(width, height, depth, widthSegments, heightSegments, depthSegments, invert, this::write)
}
fun GeneratorBuffer.cylinder(sides: Int, segments: Int, radius: Double, length: Double, invert: Boolean = false) {
generateCylinder(sides, segments, radius, length, invert, this::write)
}
fun GeneratorBuffer.dodecahedron(radius: Double) {
generateDodecahedron(radius, this::write)
}
fun GeneratorBuffer.taperedCylinder(sides: Int, segments: Int, startRadius: Double, endRadius: Double, length: Double, invert: Boolean = false) {
generateTaperedCylinder(sides, segments, startRadius, endRadius, length, invert, this::write)
}
fun GeneratorBuffer.cap(sides: Int, radius: Double, enveloppe: List<Vector2>) {
generateCap(sides, radius, enveloppe, this::write)
}
fun GeneratorBuffer.revolve(sides: Int, length: Double, enveloppe: List<Vector2>) {
generateRevolve(sides, length, enveloppe, this::write)
}
fun GeneratorBuffer.plane(center: Vector3, right: Vector3, forward: Vector3, up: Vector3, width: Double = 1.0, height: Double = 1.0,
widthSegments: Int = 1, heightSegments: Int= 1) =
generatePlane(center, right, forward, up, width, height, widthSegments, heightSegments, this::write)
fun GeneratorBuffer.extrudeShape(
baseTriangles: List<Vector2>,
contours: List<List<Vector2>>,
length: Double,
scale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true
) {
extrudeShape(
baseTriangles = baseTriangles,
contours = contours,
front = -length / 2.0,
back = length / 2.0,
frontScale = scale,
backScale = scale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
flipNormals = false,
writer = this::write
)
}
fun GeneratorBuffer.extrudeShape(
shape: Shape,
length: Double,
scale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
distanceTolerance: Double = 0.5
) {
extrudeShape(
shape = shape,
front = -length / 2.0,
back = length / 2.0,
frontScale = scale,
backScale = scale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
distanceTolerance = distanceTolerance,
flipNormals = false,
writer = this::write
)
}
fun GeneratorBuffer.extrudeShapes(shapes: List<Shape>, length: Double, scale: Double = 1.0, distanceTolerance: Double = 0.5) {
extrudeShapes(
shapes = shapes,
front = -length / 2.0,
back = length / 2.0,
frontScale = scale,
backScale = scale,
frontCap = true,
backCap = true,
sides = true,
distanceTolerance = distanceTolerance,
flipNormals = false,
writer = this::write
)
}
fun meshGenerator(vertexBuffer: VertexBuffer? = null, builder: GeneratorBuffer.() -> Unit): VertexBuffer {
val gb = GeneratorBuffer()
gb.builder()
val vb = vertexBuffer ?: meshVertexBuffer(gb.data.size)
val bb = gb.toByteBuffer()
bb.rewind()
vb.write(bb)
return vb
}
fun generator(builder: GeneratorBuffer.() -> Unit): GeneratorBuffer {
val gb = GeneratorBuffer()
gb.builder()
return gb
}
fun GeneratorBuffer.group(builder: GeneratorBuffer.() -> Unit) {
val gb = GeneratorBuffer()
gb.builder()
this.concat(gb)
}

View File

@@ -1,193 +0,0 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.BufferWriter
import org.openrndr.draw.VertexBuffer
import org.openrndr.draw.vertexBuffer
import org.openrndr.draw.vertexFormat
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.mod
import org.openrndr.shape.Circle
import org.openrndr.shape.Shape
import org.openrndr.shape.triangulate
/**
* Vertex writer function interface
*/
typealias VertexWriter = (position: Vector3, normal: Vector3, texCoord: Vector2) -> Unit
/**
* create a [VertexWriter] that writes into a [java.nio.ByteBuffer] through [BufferWriter]
*/
fun bufferWriter(bw: BufferWriter): VertexWriter {
return { p, n, t ->
bw.write(p)
bw.write(n)
bw.write(t)
}
}
/**
* creates a [VertexBuffer] that is suited for holding meshes
*/
fun meshVertexBuffer(size: Int): VertexBuffer {
return vertexBuffer(vertexFormat {
position(3)
normal(3)
textureCoordinate(2)
}, size)
}
@Deprecated("binary compatibility only")
fun extrudeShape(shape: Shape, front: Double, back: Double, distanceTolerance: Double = 0.5, writer: VertexWriter) {
extrudeShape(shape, front, back, distanceTolerance = distanceTolerance, flipNormals = false, writer = writer)
}
/**
* extrudes a [shape] from its triangulations
*/
fun extrudeShape(baseTriangles: List<Vector2>, contours: List<List<Vector2>>, front: Double,
back: Double,
frontScale: Double = 1.0,
backScale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
flipNormals: Boolean = false, writer: VertexWriter) {
val depth = back - front
val flip = if (flipNormals) 1.0 else -1.0
run {
val normal = Vector3(0.0, 0.0, depth).normalized * flip
val negativeNormal = normal * -1.0
if (frontCap) {
baseTriangles.reversed().forEach {
writer((it * frontScale).vector3(z = front), normal, Vector2.ZERO)
}
}
if (backCap) {
baseTriangles.forEach {
writer((it * backScale).vector3(z = back), negativeNormal, Vector2.ZERO)
}
}
}
if (sides) {
contours.forEach {
val points = it
val normals = (points.indices).map { index ->
(points[mod(index + 1, points.size)] - points[mod(index - 1, points.size)]).safeNormalized * -flip
}
val forward = Vector3(0.0, 0.0, depth)
val base = Vector3(0.0, 0.0, front)
var offset = 0.0
(points zip normals).zipWithNext().forEach { (left, right) ->
val width = right.first.distanceTo(left.first)
val frontRight = (right.first * frontScale).xy0 + base
val frontLeft = (left.first * frontScale).xy0 + base
val backRight = (right.first * backScale).xy0 + base + forward
val backLeft = (left.first * backScale).xy0 + base + forward
val height = frontRight.distanceTo(backRight)
val backRightUV = Vector2(offset + width, 0.0)
val backLeftUV = Vector2(offset, 0.0)
val frontLeftUV = Vector2(offset, height)
val frontRightUV = Vector2(offset + width, height)
val lnormal = (frontLeft - backLeft).normalized.cross(left.second.xy0)
val rnormal = (frontRight - backRight).normalized.cross(right.second.xy0)
writer(frontLeft, lnormal, frontLeftUV)
writer(frontRight, rnormal, frontRightUV)
writer(backRight, rnormal, backRightUV)
writer(backRight, rnormal, backRightUV)
writer(backLeft, lnormal, backLeftUV)
writer(frontLeft, lnormal, frontLeftUV)
offset += width
}
}
}
}
/**
* extrudes a [shape] by triangulating it and creating side- and cap geometry
*/
fun extrudeShape(shape: Shape,
front: Double,
back: Double,
frontScale: Double = 1.0,
backScale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
distanceTolerance: Double = 0.5,
flipNormals: Boolean = false, writer: VertexWriter) {
val baseTriangles = triangulate(shape, distanceTolerance)
val points = shape.contours.map { it.adaptivePositions(distanceTolerance) }
extrudeShape(
baseTriangles = baseTriangles,
contours = points,
front = front,
back = back,
frontScale = frontScale,
backScale = backScale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
flipNormals = flipNormals,
writer = writer
)
}
fun extrudeShapes(shapes: List<Shape>,
front: Double,
back: Double,
frontScale: Double = 1.0,
backScale: Double = 1.0,
frontCap: Boolean = true,
backCap: Boolean = true,
sides: Boolean = true,
distanceTolerance: Double = 0.5,
flipNormals: Boolean = false, writer: VertexWriter) {
shapes.forEach {
extrudeShape(
shape = it,
front = front,
back = back,
frontScale = frontScale,
backScale = backScale,
frontCap = frontCap,
backCap = backCap,
sides = sides,
distanceTolerance = distanceTolerance,
flipNormals = flipNormals,
writer = writer
)
}
}
private val Vector2.safeNormalized: Vector2
get() {
return if (length > 0.0001) {
normalized
} else {
Vector2.ZERO
}
}

View File

@@ -1,97 +0,0 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.shape.Rectangle
fun planeMesh(center: Vector3,
right: Vector3,
forward: Vector3,
up: Vector3 = forward.cross(right).normalized,
width: Double = 1.0, height: Double = 1.0,
widthSegments: Int = 1, heightSegments: Int = 1): VertexBuffer {
val vertexCount = (widthSegments * heightSegments) * 6
val vb = meshVertexBuffer(vertexCount)
vb.put {
generatePlane(center, right, forward, up,
width, height, widthSegments, heightSegments, bufferWriter(this))
}
return vb
}
/**
* Converts a [Rectangle] to a [VertexBuffer] 2D mesh matching its location and
* dimensions. [resolution] specifies the size in pixels of the triangles in
* the mesh.
*/
fun Rectangle.toMesh(resolution: Double = 2.0) = planeMesh(
center.xy0, Vector3.UNIT_X, Vector3.UNIT_Y, Vector3.UNIT_Z,
width, height,
(width / resolution).toInt(),
(height / resolution).toInt()
)
/**
* generates a finite plane with its center at (0,0,0) and spanning the xz-plane
*/
fun groundPlaneMesh(width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1): VertexBuffer {
return planeMesh(Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_Z, Vector3.UNIT_Y,
width, height, widthSegments, heightSegments)
}
/**
* generates a finite plane with its center at (0,0,0) and spanning the xy-plane
*/
fun wallPlaneMesh(width: Double = 1.0,
height: Double = 1.0,
widthSegments: Int = 1,
heightSegments: Int = 1): VertexBuffer {
return planeMesh(Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_Y, Vector3.UNIT_Z,
width, height, widthSegments, heightSegments)
}
fun generatePlane(center: Vector3,
right: Vector3,
forward: Vector3,
up: Vector3 = forward.cross(right).normalized,
width: Double = 1.0, height: Double = 1.0,
widthSegments: Int = 1, heightSegments: Int = 2,
writer: VertexWriter) {
val forwardStep = forward.normalized * (height / heightSegments)
val rightStep = right.normalized * (width / widthSegments)
val corner = center - forward.normalized * (height*0.5) - right.normalized * (width * 0.5)
val step = Vector2(1.0 / widthSegments, 1.0 / heightSegments)
for (v in 0 until heightSegments) {
for (u in 0 until widthSegments) {
val uv00 = Vector2(u + 0.0, v + 0.0) * step
val uv01 = Vector2(u + 0.0, v + 1.0) * step
val uv10 = Vector2(u + 1.0, v + 0.0) * step
val uv11 = Vector2(u + 1.0, v + 1.0) * step
val c00 = corner + forwardStep * v.toDouble() + rightStep * u.toDouble()
val c01 = corner + forwardStep * (v + 1).toDouble() + rightStep * u.toDouble()
val c10 = corner + forwardStep * v.toDouble() + rightStep * (u + 1).toDouble()
val c11 = corner + forwardStep * (v + 1).toDouble() + rightStep * (u + 1).toDouble()
writer(c11, up, uv00)
writer(c10, up, uv10)
writer(c00, up, uv11)
writer(c00, up, uv11)
writer(c01, up, uv01)
writer(c11, up, uv00)
}
}
}

View File

@@ -1,84 +0,0 @@
package org.openrndr.extra.meshgenerators
import org.openrndr.draw.VertexBuffer
import org.openrndr.math.Spherical
import org.openrndr.math.Vector2
fun sphereMesh(sides: Int = 16, segments: Int = 16, radius: Double = 1.0, invert: Boolean = false): VertexBuffer {
val vertexCount = 2 * sides * 3 + Math.max(0, (segments - 2)) * sides * 6
val vb = meshVertexBuffer(vertexCount)
vb.put {
generateSphere(sides, segments, radius, invert, bufferWriter(this))
}
return vb
}
fun generateSphere(sides: Int, segments: Int, radius: Double = 1.0, invert: Boolean = false, writer: VertexWriter) {
val inverter = if (invert) -1.0 else 1.0
for (t in 0 until segments) {
for (s in 0 until sides) {
val st00 = Spherical(s * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st01 = Spherical(s * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val st10 = Spherical((s + 1) * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st11 = Spherical((s + 1) * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val thetaMax = 180.0 * 2.0
val phiMax = 180.0
when (t) {
0 -> {
writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * inverter, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
}
segments - 1 -> {
writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st10.cartesian, st10.cartesian.normalized * inverter, Vector2(st10.theta / thetaMax + 0.5, 1.0 - st10.phi / phiMax))
writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
}
else -> {
writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * inverter, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st10.cartesian, st10.cartesian.normalized * inverter, Vector2(st10.theta / thetaMax + 0.5, 1.0 - st10.phi / phiMax))
writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
}
}
}
}
}
fun generateHemisphere(sides: Int, segments: Int, radius: Double = 1.0, invert: Boolean = false, writer: VertexWriter) {
val inverter = if (invert) -1.0 else 1.0
for (t in 0 until segments) {
for (s in 0 until sides) {
val st00 = Spherical(s * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st01 = Spherical(s * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val st10 = Spherical((s + 1) * 180.0 * 2.0 / sides, t * 180.0 / segments, radius)
val st11 = Spherical((s + 1) * 180.0 * 2.0 / sides, (t + 1) * 180.0 / segments, radius)
val thetaMax = 180.0 * 2.0
val phiMax = 180.0 * 0.5
when (t) {
0 -> {
writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * inverter, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
}
else -> {
writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
writer(st01.cartesian, st01.cartesian.normalized * inverter, Vector2(st01.theta / thetaMax + 0.5, 1.0 - st01.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.theta / thetaMax + 0.5, 1.0 - st11.phi / phiMax))
writer(st10.cartesian, st10.cartesian.normalized * inverter, Vector2(st10.theta / thetaMax + 0.5, 1.0 - st10.phi / phiMax))
writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.theta / thetaMax + 0.5, 1.0 - st00.phi / phiMax))
}
}
}
}
}