From c4e036110092bea928a532beee8067a2f0ff0cc3 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Wed, 16 Jan 2019 18:33:39 +0100 Subject: [PATCH] Improve orx-mesh-generators Add GeneratorBuffer Add Cap Fix triangle winding --- README.md | 6 +- build.gradle | 2 +- orx-mesh-generators/src/main/kotlin/Cap.kt | 101 ++++++++++ .../src/main/kotlin/Cylinder.kt | 82 +++++++++ .../src/main/kotlin/GeneratorBuffer.kt | 172 ++++++++++++++++++ .../src/main/kotlin/MeshGenerators.kt | 7 +- orx-mesh-generators/src/main/kotlin/Sphere.kt | 34 +++- 7 files changed, 393 insertions(+), 11 deletions(-) create mode 100644 orx-mesh-generators/src/main/kotlin/Cap.kt create mode 100644 orx-mesh-generators/src/main/kotlin/Cylinder.kt create mode 100644 orx-mesh-generators/src/main/kotlin/GeneratorBuffer.kt diff --git a/README.md b/README.md index 67708fac..24f5da97 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A growing library of assorted data structures, algorithms and utilities. - [`orx-obj-loader`](orx-obj-loader/README.md), simple Wavefront .obj mesh loader ## Usage -ORX 0.0.18 is built against OPENRNDR 0.3.32, make sure you use this version in your project. Because OPENRNDR's API is pre 1.0 it tends to change from time to time. +ORX 0.0.19 is built against OPENRNDR 0.3.32, make sure you use this version in your project. Because OPENRNDR's API is pre 1.0 it tends to change from time to time. The easiest way to add ORX to your project is through the use of Jitpack. [Jitpack](http://jitpack.io) is a service that pulls Gradle based libraries from Github, builds them and serves the jar files. @@ -30,13 +30,13 @@ repositories { You can then add any of the ORX artefacts to your `dependencies {}`: ``` dependencies { - compile 'com.github.openrndr.orx::v0.0.18' + compile 'com.github.openrndr.orx::v0.0.19' } ``` For example if you want to use the `orx-no-clear` artifact one would use: ``` dependencies { - compile 'com.github.openrndr.orx:orx-no-clear:v0.0.18' + compile 'com.github.openrndr.orx:orx-no-clear:v0.0.19' } ``` diff --git a/build.gradle b/build.gradle index 7e5d964c..fe20fff4 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { allprojects { group 'org.openrndr.extra' - version '0.0.18' + version '0.0.19' } repositories { diff --git a/orx-mesh-generators/src/main/kotlin/Cap.kt b/orx-mesh-generators/src/main/kotlin/Cap.kt new file mode 100644 index 00000000..26ce263d --- /dev/null +++ b/orx-mesh-generators/src/main/kotlin/Cap.kt @@ -0,0 +1,101 @@ +package org.openrndr.extras.meshgenerators + +import org.openrndr.math.Vector2 +import org.openrndr.math.Vector3 +import org.openrndr.math.transforms.rotateY + +fun generateCap(sides: Int, radius: Double, enveloppe: List = listOf(Vector2(0.0, 0.0), Vector2(1.0, 0.0)), writer: VertexWriter) { + val maxX = enveloppe.maxBy { it.x } ?: Vector2(1.0, 0.0) + val a = maxX.x + + val cleanEnveloppe = enveloppe.map { Vector2((it.x / a) * radius, it.y) } + + val normals2D = enveloppe.zipWithNext().map { + val d = it.second - it.first + d.normalized.perpendicular + } + + val basePositions = cleanEnveloppe.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) { + val r0 = rotateY(360.0 / sides * side) + val r1 = rotateY(360.0 / sides * (side + 1)) + + val v0 = basePositions.map { (r0 * it.xyz0).xyz } + val v1 = basePositions.map { (r1 * it.xyz0).xyz } + val n0 = baseNormals.map { (r0 * it.xyz0).xyz } + val n1 = baseNormals.map { (r1 * it.xyz0).xyz } + + for (segment in 0 until basePositions.size - 1) { + + val p00 = v0[segment] + val p01 = v0[segment+1] + val p10 = v1[segment] + val p11 = v1[segment+1] + + val nn0 = n0[segment] + val nn1 = n1[segment] + + writer(p00, nn0, Vector2.ZERO) + writer(p01, nn0, Vector2.ZERO) + writer(p11, nn1, Vector2.ZERO) + + writer(p11, nn1, Vector2.ZERO) + writer(p10, nn1, Vector2.ZERO) + writer(p00, nn0, Vector2.ZERO) + } + } +} + +fun generateRevolve(sides: Int, length: Double, enveloppe: List = listOf(Vector2(1.0, 0.0), Vector2(1.0, 1.0)), writer: VertexWriter) { + val maxY = enveloppe.maxBy { 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 normals2D = enveloppe.zipWithNext().map { + val d = it.second - it.first + d.normalized.perpendicular * Vector2(1.0, -1.0) + + } + + val extended = listOf(normals2D[0]) + normals2D + normals2D[normals2D.size-1] + +// extended.zipW + + println(normals2D.joinToString(", ")) + + val basePositions = cleanEnveloppe.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) { + val r0 = rotateY(360.0 / sides * side) + val r1 = rotateY(360.0 / sides * (side + 1)) + + val v0 = basePositions.map { (r0 * it.xyz0).xyz } + val v1 = basePositions.map { (r1 * it.xyz0).xyz } + val n0 = baseNormals.map { (r0 * it.xyz0).xyz } + val n1 = baseNormals.map { (r1 * it.xyz0).xyz } + + for (segment in 0 until basePositions.size - 1) { + + val p00 = v0[segment] + val p01 = v0[segment+1] + val p10 = v1[segment] + val p11 = v1[segment+1] + + val nn0 = n0[segment] + val nn1 = n1[segment] + + writer(p00, nn0, Vector2.ZERO) + writer(p10, nn1, Vector2.ZERO) + writer(p11, nn1, Vector2.ZERO) + + writer(p11, nn1, Vector2.ZERO) + writer(p01, nn0, Vector2.ZERO) + + writer(p00, nn0, Vector2.ZERO) + } + } +} \ No newline at end of file diff --git a/orx-mesh-generators/src/main/kotlin/Cylinder.kt b/orx-mesh-generators/src/main/kotlin/Cylinder.kt new file mode 100644 index 00000000..89d1f281 --- /dev/null +++ b/orx-mesh-generators/src/main/kotlin/Cylinder.kt @@ -0,0 +1,82 @@ +package org.openrndr.extras.meshgenerators + +import org.openrndr.draw.VertexBuffer +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 = (rotateZ(side * ddeg) * baseNormal.xyz0).xyz.normalized * invertFactor + val n1 = (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)) + } + } + } +} \ No newline at end of file diff --git a/orx-mesh-generators/src/main/kotlin/GeneratorBuffer.kt b/orx-mesh-generators/src/main/kotlin/GeneratorBuffer.kt new file mode 100644 index 00000000..4b2b3938 --- /dev/null +++ b/orx-mesh-generators/src/main/kotlin/GeneratorBuffer.kt @@ -0,0 +1,172 @@ +package org.openrndr.extras.meshgenerators + +import org.openrndr.draw.VertexBuffer +import org.openrndr.draw.vertexBuffer +import org.openrndr.draw.vertexFormat +import org.openrndr.math.Matrix44 +import org.openrndr.math.Vector2 +import org.openrndr.math.Vector3 +import org.openrndr.math.transforms.rotate +import org.openrndr.math.transforms.transform +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() + + 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) { + data = data.map { + VertexData((m * (it.position.xyz1)).xyz, (m * (it.normal.xyz0)).xyz, it.texCoord) + }.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 = 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.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) { + generateCap(sides, radius, enveloppe, this::write) +} + +fun GeneratorBuffer.revolve(sides:Int, length:Double, enveloppe: List) { + generateRevolve(sides, length, enveloppe, this::write) +} + +fun GeneratorBuffer.extrudeShape(shape: Shape, length: Double, scale: Double = 1.0, distanceTolerance: Double = 0.5) { + extrudeShape(shape, -length / 2.0, length / 2.0, scale, scale, true, true, distanceTolerance, false, this::write) +} + +fun meshGenerator(builder: GeneratorBuffer.() -> Unit): VertexBuffer { + val gb = GeneratorBuffer() + gb.builder() + + val vb = vertexBuffer(vertexFormat { + position(3) + normal(3) + textureCoordinate(2) + }, 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) +} + +fun main(args: Array) { + val gb = generator { + box(20.0, 20.0, 20.0) + group { + box(40.0, 40.0, 40.0) + transform(transform { + translate(0.0, 20.0, 0.0) + }) + } + } +} \ No newline at end of file diff --git a/orx-mesh-generators/src/main/kotlin/MeshGenerators.kt b/orx-mesh-generators/src/main/kotlin/MeshGenerators.kt index 1735e2e6..84fc726d 100644 --- a/orx-mesh-generators/src/main/kotlin/MeshGenerators.kt +++ b/orx-mesh-generators/src/main/kotlin/MeshGenerators.kt @@ -65,7 +65,7 @@ fun extrudeShape(shape: Shape, val negativeNormal = normal * -1.0 if (frontCap) { - baseTriangles.forEach { + baseTriangles.reversed().forEach { writer((it*frontScale).vector3(z = front), normal, Vector2.ZERO) } } @@ -79,24 +79,19 @@ fun extrudeShape(shape: Shape, shape.contours.forEach { val points = it.adaptivePositions(distanceTolerance) - - val normals = (0 until points.size).map { (points[mod(it + 1, points.size)] - points[mod(it - 1, points.size)]).safeNormalized * -flip } - val forward = Vector3(0.0, 0.0, depth) val base = Vector3(0.0, 0.0, front) (points zip normals).zipWithNext().forEach { (left, right) -> - 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 lnormal = (frontLeft - backLeft).normalized.cross(left.second.xy0) val rnormal = (frontRight - backRight).normalized.cross(right.second.xy0) diff --git a/orx-mesh-generators/src/main/kotlin/Sphere.kt b/orx-mesh-generators/src/main/kotlin/Sphere.kt index f2735343..20ef6f1d 100644 --- a/orx-mesh-generators/src/main/kotlin/Sphere.kt +++ b/orx-mesh-generators/src/main/kotlin/Sphere.kt @@ -13,8 +13,8 @@ fun sphereMesh(sides: Int = 16, segments: Int = 16, radius: Double = 1.0, invert return vb } -fun generateSphere(sides: Int, segments: Int, radius: Double = 1.0, invert: Boolean = false, writer: VertexWriter) { +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) { @@ -42,6 +42,38 @@ fun generateSphere(sides: Int, segments: Int, radius: Double = 1.0, invert: Bool writer(st01.cartesian, st01.cartesian.normalized * inverter, Vector2(st01.phi / phiMax, st01.theta / thetaMax)) writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.phi / phiMax, st11.theta / thetaMax)) + writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.phi / phiMax, st11.theta / thetaMax)) + writer(st10.cartesian, st10.cartesian.normalized * inverter, Vector2(st10.phi / phiMax, st10.theta / thetaMax)) + writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.phi / phiMax, st00.theta / thetaMax)) + } + } + } + } +} + +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(radius, s * Math.PI * 2.0 / sides, t * Math.PI*0.5 / segments) + val st01 = Spherical(radius, s * Math.PI * 2.0 / sides, (t + 1) * Math.PI*0.5 / segments) + val st10 = Spherical(radius, (s + 1) * Math.PI * 2.0 / sides, t * Math.PI*0.5 / segments) + val st11 = Spherical(radius, (s + 1) * Math.PI * 2.0 / sides, (t + 1) * Math.PI*0.5 / segments) + + val thetaMax = Math.PI * 0.5 + val phiMax = Math.PI * 2.0 + + when (t) { + 0 -> { + writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.phi / phiMax, st00.theta / thetaMax)) + writer(st01.cartesian, st01.cartesian.normalized * inverter, Vector2(st01.phi / phiMax, st01.theta / thetaMax)) + writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.phi / phiMax, st11.theta / thetaMax)) + } + else -> { + writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.phi / phiMax, st00.theta / thetaMax)) + writer(st01.cartesian, st01.cartesian.normalized * inverter, Vector2(st01.phi / phiMax, st01.theta / thetaMax)) + writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.phi / phiMax, st11.theta / thetaMax)) + writer(st11.cartesian, st11.cartesian.normalized * inverter, Vector2(st11.phi / phiMax, st11.theta / thetaMax)) writer(st10.cartesian, st10.cartesian.normalized * inverter, Vector2(st10.phi / phiMax, st10.theta / thetaMax)) writer(st00.cartesian, st00.cartesian.normalized * inverter, Vector2(st00.phi / phiMax, st00.theta / thetaMax))