Improve mesh generator (#301)
Co-authored-by: Edwin Jakobs <edwin@rndr.studio>
This commit is contained in:
@@ -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
|
||||
` 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)
|
||||
|
||||

|
||||
|
||||
### DemoBox
|
||||
[source code](src/demo/kotlin/DemoBox.kt)
|
||||
[source code](src/jvmDemo/kotlin/DemoBox.kt)
|
||||
|
||||

|
||||
|
||||
### DemoComplex01
|
||||
[source code](src/demo/kotlin/DemoComplex01.kt)
|
||||
[source code](src/jvmDemo/kotlin/DemoComplex01.kt)
|
||||
|
||||

|
||||
|
||||
### DemoComplex02
|
||||
[source code](src/demo/kotlin/DemoComplex02.kt)
|
||||
[source code](src/jvmDemo/kotlin/DemoComplex02.kt)
|
||||
|
||||

|
||||
|
||||
### DemoComplex03
|
||||
[source code](src/demo/kotlin/DemoComplex03.kt)
|
||||
[source code](src/jvmDemo/kotlin/DemoComplex03.kt)
|
||||
|
||||

|
||||
|
||||
### DemoComplex04
|
||||
[source code](src/demo/kotlin/DemoComplex04.kt)
|
||||
[source code](src/jvmDemo/kotlin/DemoComplex04.kt)
|
||||
|
||||

|
||||
|
||||
### DemoComplex05
|
||||
[source code](src/demo/kotlin/DemoComplex05.kt)
|
||||
[source code](src/jvmDemo/kotlin/DemoComplex05.kt)
|
||||
|
||||

|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
114
orx-mesh-generators/src/commonMain/kotlin/Box.kt
Normal file
114
orx-mesh-generators/src/commonMain/kotlin/Box.kt
Normal 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
|
||||
)
|
||||
}
|
||||
@@ -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]
|
||||
141
orx-mesh-generators/src/commonMain/kotlin/Cylinder.kt
Normal file
141
orx-mesh-generators/src/commonMain/kotlin/Cylinder.kt
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
445
orx-mesh-generators/src/commonMain/kotlin/Extrusion.kt
Normal file
445
orx-mesh-generators/src/commonMain/kotlin/Extrusion.kt
Normal 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
|
||||
)
|
||||
60
orx-mesh-generators/src/commonMain/kotlin/Frames.kt
Normal file
60
orx-mesh-generators/src/commonMain/kotlin/Frames.kt
Normal 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
|
||||
}
|
||||
280
orx-mesh-generators/src/commonMain/kotlin/MeshGenerators.kt
Normal file
280
orx-mesh-generators/src/commonMain/kotlin/MeshGenerators.kt
Normal 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
|
||||
}
|
||||
}
|
||||
137
orx-mesh-generators/src/commonMain/kotlin/Plane.kt
Normal file
137
orx-mesh-generators/src/commonMain/kotlin/Plane.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
127
orx-mesh-generators/src/commonMain/kotlin/Sphere.kt
Normal file
127
orx-mesh-generators/src/commonMain/kotlin/Sphere.kt
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
634
orx-mesh-generators/src/commonMain/kotlin/TriangleMeshBuilder.kt
Normal file
634
orx-mesh-generators/src/commonMain/kotlin/TriangleMeshBuilder.kt
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
67
orx-mesh-generators/src/jvmDemo/kotlin/DemoComplex02.kt
Normal file
67
orx-mesh-generators/src/jvmDemo/kotlin/DemoComplex02.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
83
orx-mesh-generators/src/jvmDemo/kotlin/DemoComplex03.kt
Normal file
83
orx-mesh-generators/src/jvmDemo/kotlin/DemoComplex03.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
orx-mesh-generators/src/jvmDemo/kotlin/DemoComplex04.kt
Normal file
104
orx-mesh-generators/src/jvmDemo/kotlin/DemoComplex04.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
67
orx-mesh-generators/src/jvmDemo/kotlin/DemoExtrude01.kt
Normal file
67
orx-mesh-generators/src/jvmDemo/kotlin/DemoExtrude01.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
orx-mesh-generators/src/jvmDemo/kotlin/DemoExtrude02.kt
Normal file
71
orx-mesh-generators/src/jvmDemo/kotlin/DemoExtrude02.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
orx-mesh-generators/src/jvmDemo/kotlin/DemoExtrude03.kt
Normal file
96
orx-mesh-generators/src/jvmDemo/kotlin/DemoExtrude03.kt
Normal 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)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user