diff --git a/orx-shapes/src/demo/kotlin/DemoBezierPatch04.kt b/orx-shapes/src/demo/kotlin/DemoBezierPatch04.kt new file mode 100644 index 00000000..45d192c9 --- /dev/null +++ b/orx-shapes/src/demo/kotlin/DemoBezierPatch04.kt @@ -0,0 +1,42 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extensions.Screenshots +import org.openrndr.extensions.SingleScreenshot +import org.openrndr.extra.shapes.bezierPatch +import org.openrndr.shape.Circle + +fun main() { + application { + configure { + width = 800 + height = 800 + } + program { + if (System.getProperty("takeScreenshot") == "true") { + extend(SingleScreenshot()) { + this.outputFile = System.getProperty("screenshotPath") + } + } + extend { + drawer.clear(ColorRGBa.PINK) + val bp = bezierPatch(Circle(width / 2.0, height / 2.0, 350.0).contour) + + for (i in 0..50) { + drawer.stroke = ColorRGBa.BLACK.opacify(1.0) + } + + for (j in 1 until 50 step 2) { + for (i in 1 until 50 step 2) { + val p = bp.position(i / 50.0, j / 50.0) + val g2 = bp.gradient(i / 50.0, j / 50.0).normalized + val g = g2.perpendicular() + drawer.lineSegment(p, p + g2 * 10.0) + drawer.lineSegment(p, p - g2 * 10.0) + drawer.lineSegment(p, p + g * 10.0) + drawer.lineSegment(p, p - g * 10.0) + } + } + } + } + } +} \ No newline at end of file diff --git a/orx-shapes/src/main/kotlin/BezierPatch.kt b/orx-shapes/src/main/kotlin/BezierPatch.kt index edeaf2d1..c0878edf 100644 --- a/orx-shapes/src/main/kotlin/BezierPatch.kt +++ b/orx-shapes/src/main/kotlin/BezierPatch.kt @@ -1,5 +1,6 @@ package org.openrndr.extra.shapes +import org.openrndr.math.Matrix44 import org.openrndr.math.Vector2 import org.openrndr.shape.Rectangle import org.openrndr.shape.Segment @@ -24,7 +25,18 @@ class BezierPatch(val points: List>) { ) ) - private fun coeffs(t: Double): DoubleArray { + fun transform(transform: Matrix44) = BezierPatch(points.map { r -> + r.map { (transform * it.xy01).div.xy } + }) + + private fun coeffs2(t: Double): DoubleArray { + val it = 1.0 - t + val it2 = it * it + val t2 = t * t + return doubleArrayOf(it2, 2 * it * t, t2) + } + + private fun coeffs3(t: Double): DoubleArray { val it = 1.0 - t val it2 = it * it val it3 = it2 * it @@ -39,8 +51,8 @@ class BezierPatch(val points: List>) { * @param v a value between 0 and 1 */ fun position(u: Double, v: Double): Vector2 { - val csu = coeffs(u) - val csv = coeffs(v) + val csu = coeffs3(u) + val csv = coeffs3(v) var result = Vector2.ZERO for (j in 0 until 4) { for (i in 0 until 4) { @@ -50,6 +62,37 @@ class BezierPatch(val points: List>) { return result } + /** + * Return a gradient vector on the patch by using its u,v parameterization + * @param u a value between 0 and 1 + * @param v a value between 0 and 1 + */ + fun gradient(u: Double, v: Double): Vector2 { + val f0 = List(4) { MutableList(3) { Vector2.ZERO } } + for (j in 0 until 4) { + for (i in 0 until 3) { + f0[j][i] = points[j][i+1] - points[j][i] + } + } + + val f1 = List(3) { MutableList(3) { Vector2.ZERO } } + for (j in 0 until 3) { + for (i in 0 until 3) { + f1[j][i] = f0[j+1][i] - f0[j][i] + } + } + + val csu = coeffs2(u) + val csv = coeffs2(v) + var result = Vector2.ZERO + for (j in 0 until 3) { + for (i in 0 until 3) { + result += f1[j][i] * csu[i] * csv[j] + } + } + return result + } + /** * Generate a random point on the path * @return a point that is uniformly distributed in uv space @@ -57,7 +100,7 @@ class BezierPatch(val points: List>) { fun randomPoint(random: Random = Random.Default) = position(random.nextDouble(), random.nextDouble()) fun horizontal(v: Double): ShapeContour { - val cs = coeffs(v) + val cs = coeffs3(v) val cps = Array(4) { Vector2.ZERO } for (j in 0 until 4) { for (i in 0 until 4) { @@ -68,7 +111,7 @@ class BezierPatch(val points: List>) { } fun vertical(u: Double): ShapeContour { - val cs = coeffs(u) + val cs = coeffs3(u) val cps = Array(4) { Vector2.ZERO } for (j in 0 until 4) { for (i in 0 until 4) { @@ -139,7 +182,7 @@ fun bezierPatch(shapeContour: ShapeContour, alpha: Double = 1.0 / 3.0): BezierPa listOf(c3.control[1], x00, x01, c1.control[0]), listOf(c3.control[0], x10, x11, c1.control[1]), listOf(c2.end, c2.control[1], c2.control[0], c2.start), - ) + ) return BezierPatch(cps) } @@ -157,7 +200,7 @@ fun bezierPatch(corners: List, alpha: Double = 1.0 / 3.0): BezierPatch /** * Distort a shape contour - */ + */ fun BezierPatch.distort(shapeContour: ShapeContour, referenceRectangle: Rectangle = shapeContour.bounds): ShapeContour { val distortedSegments = shapeContour.segments.map { val c = it.cubic