From af6d35c59bca8a932721e43399e0a6c9d320e23a Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Tue, 19 Mar 2024 16:31:45 +0100 Subject: [PATCH] Update for OPENRNDR segment and path generalizations --- .../commonMain/kotlin/CompositionDrawer.kt | 10 +- orx-fcurve/src/commonMain/kotlin/FCurve.kt | 32 ++-- .../orx-runway/src/demo/kotlin/DemoBASNet.kt | 52 +++---- orx-noise/src/commonMain/kotlin/ShapeNoise.kt | 2 +- orx-shapes/build.gradle.kts | 1 + .../kotlin/adjust/ContourAdjuster.kt | 4 +- .../commonMain/kotlin/adjust/ContourEdge.kt | 6 +- .../kotlin/adjust/SegmentAdjustments.kt | 12 +- .../kotlin/alphashape/AlphaShape.kt | 8 +- .../kotlin/bezierpatches/BezierPatch.kt | 36 ++--- .../kotlin/bezierpatches/BezierPatch3D.kt | 16 +- .../commonMain/kotlin/blend/ContourBlend.kt | 4 +- .../commonMain/kotlin/blend/Path3DBlend.kt | 37 +++++ .../blend/RectifiedContourExtensions.kt | 14 +- .../kotlin/blend/RectifiedPath3DExtensions.kt | 30 ++++ .../kotlin/blend/SegmentExtensions.kt | 28 +++- .../src/commonMain/kotlin/bounds/Bounds.kt | 2 +- .../kotlin/hobbycurve/HobbyCurve.kt | 4 +- .../src/commonMain/kotlin/offset/Offset.kt | 24 +-- .../kotlin/operators/BulgeContours.kt | 5 +- .../kotlin/operators/ChamferCorners.kt | 10 +- .../kotlin/rectify/Path3DExtensions.kt | 10 ++ .../kotlin/rectify/RectifiedContour.kt | 139 +++--------------- .../kotlin/rectify/RectifiedPath.kt | 104 +++++++++++++ .../kotlin/rectify/RectifiedPath3D.kt | 27 ++++ .../kotlin/rectify/ShapeContourExtensions.kt | 21 +++ .../commonMain/kotlin/splines/CatmullRom.kt | 4 +- .../src/commonMain/kotlin/tunni/Tunni.kt | 10 +- .../tunni/TunniContourEdgeExtensions.kt | 1 - .../commonMain/kotlin/utilities/FromPaths.kt | 31 ++++ .../commonMain/kotlin/utilities/SplitAt.kt | 39 ++++- .../kotlin/rectify/DemoRectifiedContour03.kt | 34 +++++ .../kotlin/rectify/DemoRectifiedPath3D01.kt | 49 ++++++ orx-svg/src/jvmMain/kotlin/SVGElement.kt | 36 ++--- orx-triangulation/build.gradle.kts | 9 +- .../src/commonTest/kotlin/TestVoronoi.kt | 1 + .../src/commonMain/kotlin/NinjaTurtle.kt | 4 +- 37 files changed, 579 insertions(+), 277 deletions(-) create mode 100644 orx-shapes/src/commonMain/kotlin/blend/Path3DBlend.kt create mode 100644 orx-shapes/src/commonMain/kotlin/blend/RectifiedPath3DExtensions.kt create mode 100644 orx-shapes/src/commonMain/kotlin/rectify/Path3DExtensions.kt create mode 100644 orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath.kt create mode 100644 orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath3D.kt create mode 100644 orx-shapes/src/commonMain/kotlin/rectify/ShapeContourExtensions.kt create mode 100644 orx-shapes/src/commonMain/kotlin/utilities/FromPaths.kt create mode 100644 orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedContour03.kt create mode 100644 orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedPath3D01.kt diff --git a/orx-composition/src/commonMain/kotlin/CompositionDrawer.kt b/orx-composition/src/commonMain/kotlin/CompositionDrawer.kt index d48b126b..132bdb20 100644 --- a/orx-composition/src/commonMain/kotlin/CompositionDrawer.kt +++ b/orx-composition/src/commonMain/kotlin/CompositionDrawer.kt @@ -570,28 +570,28 @@ class CompositionDrawer(documentBounds: CompositionDimensions = defaultCompositi c1: Vector2, end: Vector2, insert: Boolean = true - ) = segment(Segment(start, c0, c1, end), insert) + ) = segment(Segment2D(start, c0, c1, end), insert) fun segment( start: Vector2, c0: Vector2, end: Vector2, insert: Boolean = true - ) = segment(Segment(start, c0, end), insert) + ) = segment(Segment2D(start, c0, end), insert) fun segment( start: Vector2, end: Vector2, insert: Boolean = true - ) = segment(Segment(start, end), insert) + ) = segment(Segment2D(start, end), insert) fun segment( - segment: Segment, + segment: Segment2D, insert: Boolean = true ) = contour(segment.contour, insert) fun segments( - segments: List, + segments: List, insert: Boolean = true ) = segments.map { segment(it, insert) diff --git a/orx-fcurve/src/commonMain/kotlin/FCurve.kt b/orx-fcurve/src/commonMain/kotlin/FCurve.kt index f55adad2..745fd631 100644 --- a/orx-fcurve/src/commonMain/kotlin/FCurve.kt +++ b/orx-fcurve/src/commonMain/kotlin/FCurve.kt @@ -3,14 +3,14 @@ package org.openrndr.extra.fcurve import kotlinx.serialization.Serializable import org.openrndr.math.Vector2 import org.openrndr.math.transforms.buildTransform -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.ShapeContour import kotlin.math.abs /** * Find the (first) t value for a given [x] value */ -private fun Segment.tForX(x: Double): Double { +private fun Segment2D.tForX(x: Double): Double { if (linear) { return (x - start.x) / (end.x - start.x) } else { @@ -29,7 +29,7 @@ private fun Segment.tForX(x: Double): Double { /** * Find the y value for a given [x] value */ -private fun Segment.yForX(x: Double): Double { +private fun Segment2D.yForX(x: Double): Double { val t = tForX(x) return position(t).y } @@ -37,7 +37,7 @@ private fun Segment.yForX(x: Double): Double { /** * Scale tangents such that tangent lines do not overlap */ -fun Segment.scaleTangents(axis: Vector2 = Vector2.UNIT_X): Segment { +fun Segment2D.scaleTangents(axis: Vector2 = Vector2.UNIT_X): Segment2D { if (linear) { return this } else { @@ -74,7 +74,7 @@ fun Segment.scaleTangents(axis: Vector2 = Vector2.UNIT_X): Segment { * Fcurve class */ @Serializable -data class FCurve(val segments: List) { +data class FCurve(val segments: List) { /** * Reverse the fcurve @@ -105,7 +105,7 @@ data class FCurve(val segments: List) { * Create a sampler or function from the Fcurve */ fun sampler(normalized: Boolean = false): (Double) -> Double { - var cachedSegment: Segment? = null + var cachedSegment: Segment2D? = null if (!normalized) { return { t -> val r = valueWithSegment(t, cachedSegment) @@ -139,13 +139,13 @@ data class FCurve(val segments: List) { * @param segment an optional segment that can be used to speed up scanning for the relevant segment * @see valueWithSegment */ - fun value(t: Double, segment: Segment? = null): Double = valueWithSegment(t, segment).first + fun value(t: Double, segment: Segment2D? = null): Double = valueWithSegment(t, segment).first /** * Evaluate the Fcurve at [t] * @param segment an optional segment that can be used to speed up scanning for the relevant segment */ - fun valueWithSegment(t: Double, cachedSegment: Segment? = null): Pair { + fun valueWithSegment(t: Double, cachedSegment: Segment2D? = null): Pair { if (cachedSegment != null) { if (t >= cachedSegment.start.x && t < cachedSegment.end.x) { return Pair(cachedSegment.yForX(t), cachedSegment) @@ -180,7 +180,7 @@ data class FCurve(val segments: List) { * Return a list of contours that can be used to visualize the Fcurve */ fun contours(scale: Vector2 = Vector2.ONE): List { - var active = mutableListOf() + var active = mutableListOf() val result = mutableListOf() for (segment in segments) { @@ -210,7 +210,7 @@ data class FCurve(val segments: List) { * Fcurve builder */ class FCurveBuilder { - val segments = mutableListOf() + val segments = mutableListOf() var cursor = Vector2(0.0, 0.0) var path = "" @@ -222,7 +222,7 @@ class FCurveBuilder { fun lineTo(x: Double, y: Double, relative: Boolean = false) { val r = if (relative) 1.0 else 0.0 - segments.add(Segment(cursor, Vector2(x + cursor.x, y + cursor.y * r))) + segments.add(Segment2D(cursor, Vector2(x + cursor.x, y + cursor.y * r))) cursor = Vector2(cursor.x + x, cursor.y * r + y) path += "${if (relative) "l" else "L"}$x,$y" } @@ -234,7 +234,7 @@ class FCurveBuilder { ) { val r = if (relative) 1.0 else 0.0 segments.add( - Segment( + Segment2D( cursor, Vector2(cursor.x + x0, cursor.y * r + y0), Vector2(cursor.x + x, cursor.y * r + y) @@ -251,7 +251,7 @@ class FCurveBuilder { ) { val r = if (relative) 1.0 else 0.0 segments.add( - Segment( + Segment2D( cursor, Vector2(cursor.x + x0, cursor.y * r + y0), Vector2(cursor.x + x1, cursor.y * r + y1), @@ -272,7 +272,7 @@ class FCurveBuilder { val dy = outPos.y - outTangent.y val ts = x / lastDuration segments.add( - Segment( + Segment2D( cursor, Vector2(cursor.x + dx * ts, cursor.y + dy), Vector2(cursor.x + x * 0.66, cursor.y * r + y), @@ -290,7 +290,7 @@ class FCurveBuilder { val dx = cursor.x - outTangent.x val dy = cursor.y - outTangent.y segments.add( - Segment( + Segment2D( cursor, Vector2(cursor.x + dx, cursor.y + dy), Vector2(cursor.x + x1, cursor.y * r + y1), @@ -370,7 +370,7 @@ private fun evaluateFCurveCommands(parts: List): FCurve { */ return fcurve { fun dx(): Double { - val lastSegment = segments.lastOrNull() ?: Segment(Vector2.ZERO, Vector2.ZERO) + val lastSegment = segments.lastOrNull() ?: Segment2D(Vector2.ZERO, Vector2.ZERO) return lastSegment.end.x - lastSegment.start.x } diff --git a/orx-jvm/orx-runway/src/demo/kotlin/DemoBASNet.kt b/orx-jvm/orx-runway/src/demo/kotlin/DemoBASNet.kt index 7ef4c70f..38b062d7 100644 --- a/orx-jvm/orx-runway/src/demo/kotlin/DemoBASNet.kt +++ b/orx-jvm/orx-runway/src/demo/kotlin/DemoBASNet.kt @@ -1,26 +1,26 @@ -import org.openrndr.application -import org.openrndr.draw.* -import org.openrndr.extra.runway.* - -/** - * This example requires a `runway/BASNet` model to be active in Runway. - */ -fun main() = application { - configure { - width = 331 - height = 400 - } - - program { - val image = loadImage("demo-data/images/life-cover.jpg") - - val result: BASNETResult = - runwayQuery("http://localhost:8000/query", BASNETRequest(image.toData())) - - val segmentImage = ColorBuffer.fromData(result.image) - - extend { - drawer.image(segmentImage, 0.0, 0.0) - } - } -} \ No newline at end of file +//import org.openrndr.application +//import org.openrndr.draw.* +//import org.openrndr.extra.runway.* +// +///** +// * This example requires a `runway/BASNet` model to be active in Runway. +// */ +//fun main() = application { +// configure { +// width = 331 +// height = 400 +// } +// +// program { +// val image = loadImage("demo-data/images/life-cover.jpg") +// +// val result: BASNETResult = +// runwayQuery("http://localhost:8000/query", BASNETRequest(image.toData())) +// +// val segmentImage = ColorBuffer.fromData(result.image) +// +// extend { +// drawer.image(segmentImage, 0.0, 0.0) +// } +// } +//} \ No newline at end of file diff --git a/orx-noise/src/commonMain/kotlin/ShapeNoise.kt b/orx-noise/src/commonMain/kotlin/ShapeNoise.kt index 90baa7cb..2240d489 100644 --- a/orx-noise/src/commonMain/kotlin/ShapeNoise.kt +++ b/orx-noise/src/commonMain/kotlin/ShapeNoise.kt @@ -86,7 +86,7 @@ fun ShapeProvider.scatter( hg } - fun Segment.randomPoints(count: Int) = sequence { + fun Segment2D.randomPoints(count: Int) = sequence { for (i in 0 until count) { val t = random.nextDouble() yield(position(t) - normal(t).normalized * distanceToEdge) diff --git a/orx-shapes/build.gradle.kts b/orx-shapes/build.gradle.kts index f9792335..77b6631f 100644 --- a/orx-shapes/build.gradle.kts +++ b/orx-shapes/build.gradle.kts @@ -37,6 +37,7 @@ kotlin { implementation(project(":orx-triangulation")) implementation(project(":orx-shapes")) implementation(project(":orx-noise")) + implementation(project(":orx-mesh-generators")) } } } diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt index dbb258ec..a3c433ce 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt @@ -3,14 +3,14 @@ package org.openrndr.extra.shapes.adjust import org.openrndr.collections.pop import org.openrndr.extra.shapes.vertex.ContourVertex import org.openrndr.math.Vector2 -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.ShapeContour import kotlin.jvm.JvmName class ContourAdjusterStatus( val contour: ShapeContour, - val selectedSegments: List, + val selectedSegments: List, val selectedPoints: List ) diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt index 3b5405a9..56c07ef5 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt @@ -6,7 +6,7 @@ import org.openrndr.extra.shapes.utilities.insertPointAt import org.openrndr.math.Matrix44 import org.openrndr.math.Vector2 import org.openrndr.math.transforms.buildTransform -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.SegmentType import org.openrndr.shape.ShapeContour import kotlin.math.abs @@ -151,7 +151,7 @@ data class ContourEdge( removeAt(segmentIndex) if (segment.start.distanceTo(openContour.position(0.0)) > 1E-3) { - add(insertIndex, Segment(segment.start, openContour.position(0.0))) + add(insertIndex, Segment2D(segment.start, openContour.position(0.0))) insertIndex++ } for (s in openContour.segments) { @@ -159,7 +159,7 @@ data class ContourEdge( insertIndex++ } if (segment.end.distanceTo(openContour.position(1.0)) > 1E-3) { - add(insertIndex, Segment(segment.end, openContour.position(1.0))) + add(insertIndex, Segment2D(segment.end, openContour.position(1.0))) } } return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments) diff --git a/orx-shapes/src/commonMain/kotlin/adjust/SegmentAdjustments.kt b/orx-shapes/src/commonMain/kotlin/adjust/SegmentAdjustments.kt index 760b152a..167f5fde 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/SegmentAdjustments.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/SegmentAdjustments.kt @@ -1,6 +1,6 @@ package org.openrndr.extra.shapes.adjust -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D sealed interface SegmentOperation { data class Remove(val index: Int, val amount: Int) : SegmentOperation @@ -9,7 +9,7 @@ sealed interface SegmentOperation { class SegmentAdjustments( - val replacements: List>, + val replacements: List>, val operations: List ) { @@ -18,24 +18,24 @@ class SegmentAdjustments( } } -class SegmentAdjuster(val list: MutableList) { +class SegmentAdjuster(val list: MutableList) { val adjustments = mutableListOf() fun removeAt(index: Int) { list.removeAt(index) adjustments.add(SegmentOperation.Remove(index, 1)) } - fun add(segment: Segment) { + fun add(segment: Segment2D) { list.add(segment) adjustments.add(SegmentOperation.Insert(list.lastIndex, 1)) } - fun add(index: Int, segment: Segment) { + fun add(index: Int, segment: Segment2D) { list.add(index, segment) adjustments.add(SegmentOperation.Insert(index, 1)) } } -fun MutableList.adjust(block: SegmentAdjuster.() -> Unit) : List { +fun MutableList.adjust(block: SegmentAdjuster.() -> Unit) : List { val adjuster = SegmentAdjuster(this) adjuster.block() return adjuster.adjustments diff --git a/orx-shapes/src/commonMain/kotlin/alphashape/AlphaShape.kt b/orx-shapes/src/commonMain/kotlin/alphashape/AlphaShape.kt index 127b3876..3afa5d04 100644 --- a/orx-shapes/src/commonMain/kotlin/alphashape/AlphaShape.kt +++ b/orx-shapes/src/commonMain/kotlin/alphashape/AlphaShape.kt @@ -111,13 +111,13 @@ class AlphaShape(val points: List) { private fun edgesToShapeContour(edges: List>): ShapeContour { if (edges.isEmpty()) return ShapeContour.EMPTY val mapping = edges.toMap() - val segments = mutableListOf() + val segments = mutableListOf() val start = edges.first().first var current = start val left = edges.map { it.first }.toMutableSet() for (i in edges.indices) { val next = mapping[current]!! - segments.add(Segment(getVec(current), getVec(next))) + segments.add(Segment2D(getVec(current), getVec(next))) left.remove(current) current = next if (current == start) break @@ -140,11 +140,11 @@ class AlphaShape(val points: List) { while (left.isNotEmpty()) { val start = left.first() var current = start - val segments = mutableListOf() + val segments = mutableListOf() val contourPoints = mutableListOf() for (i in edges.indices) { val next = mapping[current]!! - segments.add(Segment(getVec(current), getVec(next))) + segments.add(Segment2D(getVec(current), getVec(next))) contourPoints.add(getVec(current)) left.remove(current) current = next diff --git a/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch.kt b/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch.kt index c1f17224..473d431e 100644 --- a/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch.kt +++ b/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch.kt @@ -6,7 +6,7 @@ import org.openrndr.color.ConvertibleToColorRGBa import org.openrndr.math.Matrix44 import org.openrndr.math.Vector2 import org.openrndr.shape.Rectangle -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.ShapeContour import kotlin.random.Random @@ -123,7 +123,7 @@ open class BezierPatchBase( cps[j] += points[i][j] * cs[i] } } - return ShapeContour(listOf(Segment(cps[0], cps[1], cps[2], cps[3])), false) + return ShapeContour(listOf(Segment2D(cps[0], cps[1], cps[2], cps[3])), false) } fun vertical(u: Double): ShapeContour { @@ -134,33 +134,33 @@ open class BezierPatchBase( cps[j] += points[j][i] * cs[i] } } - return ShapeContour(listOf(Segment(cps[0], cps[1], cps[2], cps[3])), false) + return ShapeContour(listOf(Segment2D(cps[0], cps[1], cps[2], cps[3])), false) } /** * Extract a sub-patch based on uv parameterization */ fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatchBase { - val c0 = Segment(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1) - val c1 = Segment(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1) - val c2 = Segment(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1) - val c3 = Segment(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1) + val c0 = Segment2D(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1) + val c1 = Segment2D(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1) + val c2 = Segment2D(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1) + val c3 = Segment2D(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1) val sub0 = bezierPatch(c0, c1, c2, c3) - val d0 = Segment(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1) - val d1 = Segment(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1) - val d2 = Segment(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1) - val d3 = Segment(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1) + val d0 = Segment2D(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1) + val d1 = Segment2D(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1) + val d2 = Segment2D(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1) + val d3 = Segment2D(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1) return fromSegments(d0, d1, d2, d3).transposed } val contour: ShapeContour = ShapeContour( listOf( - Segment(points[0][0], points[0][1], points[0][2], points[0][3]), - Segment(points[0][3], points[1][3], points[2][3], points[3][3]), - Segment(points[3][3], points[3][2], points[3][1], points[3][0]), - Segment(points[3][0], points[2][0], points[1][0], points[0][0]), + Segment2D(points[0][0], points[0][1], points[0][2], points[0][3]), + Segment2D(points[0][3], points[1][3], points[2][3], points[3][3]), + Segment2D(points[3][3], points[3][2], points[3][1], points[3][0]), + Segment2D(points[3][0], points[2][0], points[1][0], points[0][0]), ), true ) @@ -196,7 +196,7 @@ open class BezierPatchBase( } companion object { - fun fromSegments(c0: Segment, c1: Segment, c2: Segment, c3: Segment): BezierPatchBase + fun fromSegments(c0: Segment2D, c1: Segment2D, c2: Segment2D, c3: Segment2D): BezierPatchBase where C : AlgebraicColor, C : ConvertibleToColorRGBa { val c0c = c0.cubic val c1c = c1.cubic @@ -219,7 +219,7 @@ class BezierPatch(points: List>, colors: List> = e /** * Create a cubic bezier patch from 4 segments. The control points of the segments are used in row-wise fashion */ -fun bezierPatch(c0: Segment, c1: Segment, c2: Segment, c3: Segment): BezierPatch { +fun bezierPatch(c0: Segment2D, c1: Segment2D, c2: Segment2D, c3: Segment2D): BezierPatch { val c0c = c0.cubic val c1c = c1.cubic val c2c = c2.cubic @@ -289,7 +289,7 @@ fun BezierPatch.distort(shapeContour: ShapeContour, referenceRectangle: Rectangl val ns = position(s.x, s.y) val nc0 = position(c0.x, c0.y) val nc1 = position(c1.x, c1.y) - Segment(ns, nc0, nc1, ne) + Segment2D(ns, nc0, nc1, ne) } return ShapeContour(distortedSegments, shapeContour.closed, shapeContour.polarity) } diff --git a/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch3D.kt b/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch3D.kt index 8e548ca7..71ead562 100644 --- a/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch3D.kt +++ b/orx-shapes/src/commonMain/kotlin/bezierpatches/BezierPatch3D.kt @@ -140,16 +140,16 @@ open class BezierPatch3DBase( * Extract a sub-patch based on uv parameterization */ fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatch3DBase { - val c0 = Segment3D(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1) - val c1 = Segment3D(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1) - val c2 = Segment3D(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1) - val c3 = Segment3D(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1) + val c0 = Segment3D(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1) as Segment3D + val c1 = Segment3D(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1) as Segment3D + val c2 = Segment3D(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1) as Segment3D + val c3 = Segment3D(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1) as Segment3D val sub0 = bezierPatch(c0, c1, c2, c3) - val d0 = Segment3D(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1) - val d1 = Segment3D(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1) - val d2 = Segment3D(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1) - val d3 = Segment3D(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1) + val d0 = Segment3D(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1) as Segment3D + val d1 = Segment3D(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1) as Segment3D + val d2 = Segment3D(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1) as Segment3D + val d3 = Segment3D(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1) as Segment3D return fromSegments(d0, d1, d2, d3).transposed } diff --git a/orx-shapes/src/commonMain/kotlin/blend/ContourBlend.kt b/orx-shapes/src/commonMain/kotlin/blend/ContourBlend.kt index ab5eb0f2..1dc96b23 100644 --- a/orx-shapes/src/commonMain/kotlin/blend/ContourBlend.kt +++ b/orx-shapes/src/commonMain/kotlin/blend/ContourBlend.kt @@ -28,8 +28,8 @@ fun ContourBlend(a: ShapeContour, b: ShapeContour): ContourBlend { val rb = b.rectified() val sa = ra.splitForBlend(rb) val sb = rb.splitForBlend(ra) - require(sa.contour.segments.size == sb.contour.segments.size) { - "preprocessing for contours failed to produce equal number of segments. ${sa.contour.segments.size}, ${sb.contour.segments.size}" + require(sa.path.segments.size == sb.path.segments.size) { + "preprocessing for contours failed to produce equal number of segments. ${sa.path.segments.size}, ${sb.path.segments.size}" } return ContourBlend(sa, sb) } \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/blend/Path3DBlend.kt b/orx-shapes/src/commonMain/kotlin/blend/Path3DBlend.kt new file mode 100644 index 00000000..c541d8dc --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/blend/Path3DBlend.kt @@ -0,0 +1,37 @@ +package org.openrndr.extra.shapes.blend + +import org.openrndr.extra.shapes.rectify.RectifiedContour +import org.openrndr.extra.shapes.rectify.RectifiedPath3D +import org.openrndr.extra.shapes.rectify.rectified +import org.openrndr.shape.Path3D +import org.openrndr.shape.ShapeContour + +/** + * ContourBlend holds two rectified contours with an equal amount of segments + */ +class Path3DBlend(val a: RectifiedPath3D, val b: RectifiedPath3D) { + fun mix(blendFunction: (Double) -> Double): Path3D { + return a.mix(b, blendFunction) + } + + fun mix(blend: Double): Path3D { + return a.mix(b) { blend } + } +} + +/** + * Create a [ContourBlend] for contours [a] and [b] + * + * Finding the pose that minimizes the error between [a] and [b] is not part of this function's work. + * + */ +fun Path3DBlend(a: Path3D, b: Path3D): Path3DBlend { + val ra = a.rectified() + val rb = b.rectified() + val sa = ra.splitForBlend(rb) + val sb = rb.splitForBlend(ra) + require(sa.path.segments.size == sb.path.segments.size) { + "preprocessing for contours failed to produce equal number of segments. ${sa.path.segments.size}, ${sb.path.segments.size}" + } + return Path3DBlend(sa, sb) +} \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/blend/RectifiedContourExtensions.kt b/orx-shapes/src/commonMain/kotlin/blend/RectifiedContourExtensions.kt index 283fbd06..fc59a1e0 100644 --- a/orx-shapes/src/commonMain/kotlin/blend/RectifiedContourExtensions.kt +++ b/orx-shapes/src/commonMain/kotlin/blend/RectifiedContourExtensions.kt @@ -3,28 +3,28 @@ package org.openrndr.extra.shapes.blend import org.openrndr.extra.shapes.rectify.RectifiedContour import org.openrndr.extra.shapes.rectify.rectified import org.openrndr.extra.shapes.utilities.fromContours +import org.openrndr.shape.Segment2D import org.openrndr.shape.ShapeContour /** * Split for blending with [other] */ fun RectifiedContour.splitForBlend(other: RectifiedContour): RectifiedContour { - val ts = (0 until other.contour.segments.size + 1).map { it.toDouble() / other.contour.segments.size } + val ts = (0 until other.path.segments.size + 1).map { it.toDouble() / other.path.segments.size } val rts = ts.map { other.inverseRectify(it) } - return ShapeContour.fromContours(splitAt(rts), contour.closed && other.contour.closed).rectified() + return ShapeContour.fromContours(splitAt(rts), path.closed && other.path.closed).rectified() } fun RectifiedContour.mix(other: RectifiedContour, blendFunction: (Double) -> Double): ShapeContour { - val n = this.contour.segments.size.toDouble() - val segs = (this.contour.segments zip other.contour.segments).mapIndexed { index, it -> + val n = this.path.segments.size.toDouble() + val segs = (this.path.segments zip other.path.segments).mapIndexed { index, it -> val t0 = inverseRectify(index / n) val t1 = inverseRectify((index + 1 / 3.0) / n) val t2 = inverseRectify((index + 2 / 3.0) / n) val t3 = inverseRectify((index + 1) / n) - - it.first.mix(it.second, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3)) + (it.first as Segment2D).mix(it.second as Segment2D, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3)) } - return ShapeContour.fromSegments(segs, contour.closed && other.contour.closed) + return ShapeContour.fromSegments(segs, path.closed && other.path.closed) } diff --git a/orx-shapes/src/commonMain/kotlin/blend/RectifiedPath3DExtensions.kt b/orx-shapes/src/commonMain/kotlin/blend/RectifiedPath3DExtensions.kt new file mode 100644 index 00000000..92723c9b --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/blend/RectifiedPath3DExtensions.kt @@ -0,0 +1,30 @@ +package org.openrndr.extra.shapes.blend + +import org.openrndr.extra.shapes.rectify.RectifiedPath3D +import org.openrndr.extra.shapes.rectify.rectified +import org.openrndr.extra.shapes.utilities.fromPaths +import org.openrndr.shape.Path3D +import org.openrndr.shape.Segment3D + +/** + * Split for blending with [other] + */ +fun RectifiedPath3D.splitForBlend(other: RectifiedPath3D): RectifiedPath3D { + val ts = (0 until other.path.segments.size + 1).map { it.toDouble() / other.path.segments.size } + val rts = ts.map { other.inverseRectify(it) } + + return Path3D.fromPaths(splitAt(rts), path.closed && other.path.closed).rectified() +} + +fun RectifiedPath3D.mix(other: RectifiedPath3D, blendFunction: (Double) -> Double): Path3D { + val n = this.path.segments.size.toDouble() + val segs = (this.path.segments zip other.path.segments).mapIndexed { index, it -> + val t0 = inverseRectify(index / n) + val t1 = inverseRectify((index + 1 / 3.0) / n) + val t2 = inverseRectify((index + 2 / 3.0) / n) + val t3 = inverseRectify((index + 1) / n) + (it.first as Segment3D).mix(it.second as Segment3D, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3)) + } + return Path3D.fromSegments(segs, path.closed && other.path.closed) +} + diff --git a/orx-shapes/src/commonMain/kotlin/blend/SegmentExtensions.kt b/orx-shapes/src/commonMain/kotlin/blend/SegmentExtensions.kt index bfb68ffc..aead1145 100644 --- a/orx-shapes/src/commonMain/kotlin/blend/SegmentExtensions.kt +++ b/orx-shapes/src/commonMain/kotlin/blend/SegmentExtensions.kt @@ -1,7 +1,9 @@ package org.openrndr.extra.shapes.blend import org.openrndr.math.mix -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D +import org.openrndr.shape.Segment3D + /** * Cubic segment mix @@ -11,14 +13,14 @@ import org.openrndr.shape.Segment * @param f2 the mix factor for the second control point * @param f3 the mix factor for the end point */ -fun Segment.mix(other: Segment, f0: Double, f1: Double, f2: Double, f3: Double): Segment { +fun Segment2D.mix(other: Segment2D, f0: Double, f1: Double, f2: Double, f3: Double): Segment2D { val ac = this.cubic val bc = other.cubic val acc = if (ac.corner) 1.0 else 0.0 val bcc = if (bc.corner) 1.0 else 0.0 - return Segment( + return Segment2D( ac.start.mix(bc.start, f0), ac.control[0].mix(bc.control[0], f1), ac.control[1].mix(bc.control[1], f2), @@ -26,3 +28,23 @@ fun Segment.mix(other: Segment, f0: Double, f1: Double, f2: Double, f3: Double): corner = mix(acc, bcc, f0) >= 0.5 ) } + +/** + * Cubic segment mix + * @param other the segment to mix with + * @param f0 the mix factor for the start point + * @param f1 the mix factor for the first control point + * @param f2 the mix factor for the second control point + * @param f3 the mix factor for the end point + */ +fun Segment3D.mix(other: Segment3D, f0: Double, f1: Double, f2: Double, f3: Double): Segment3D { + val ac = this.cubic + val bc = other.cubic + + return Segment3D( + ac.start.mix(bc.start, f0), + ac.control[0].mix(bc.control[0], f1), + ac.control[1].mix(bc.control[1], f2), + ac.end.mix(bc.end, f3), + ) +} diff --git a/orx-shapes/src/commonMain/kotlin/bounds/Bounds.kt b/orx-shapes/src/commonMain/kotlin/bounds/Bounds.kt index af335b26..4365d744 100644 --- a/orx-shapes/src/commonMain/kotlin/bounds/Bounds.kt +++ b/orx-shapes/src/commonMain/kotlin/bounds/Bounds.kt @@ -26,7 +26,7 @@ val Iterable.bounds : Rectangle /** * Evaluates the bounds around all [Segment] instances in the [Iterable] */ -val Iterable.bounds : Rectangle +val Iterable.bounds : Rectangle @JvmName("segmentBounds") get() = map { it.bounds diff --git a/orx-shapes/src/commonMain/kotlin/hobbycurve/HobbyCurve.kt b/orx-shapes/src/commonMain/kotlin/hobbycurve/HobbyCurve.kt index f0e6a89e..89f435ba 100644 --- a/orx-shapes/src/commonMain/kotlin/hobbycurve/HobbyCurve.kt +++ b/orx-shapes/src/commonMain/kotlin/hobbycurve/HobbyCurve.kt @@ -2,7 +2,7 @@ package org.openrndr.extra.shapes.hobbycurve // Code adapted from http://weitz.de/hobby/ import org.openrndr.math.Vector2 -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.Shape import org.openrndr.shape.ShapeContour import kotlin.math.atan2 @@ -109,7 +109,7 @@ fun hobbyCurve(points: List, closed: Boolean = false, curl: Double = 0. c2s.add(points[(i+1) % m] - v2 * rho(beta[i]!!, alpha[i]) * distances[i] / 3.0) } - return ShapeContour(List(n) { Segment(points[it], c1s[it], c2s[it], points[(it+1)%m]) }, closed=closed) + return ShapeContour(List(n) { Segment2D(points[it], c1s[it], c2s[it], points[(it+1)%m]) }, closed=closed) } private fun thomas(a: Array, b: Array, c: Array, d: Array): Array { diff --git a/orx-shapes/src/commonMain/kotlin/offset/Offset.kt b/orx-shapes/src/commonMain/kotlin/offset/Offset.kt index b9237242..08c23a26 100644 --- a/orx-shapes/src/commonMain/kotlin/offset/Offset.kt +++ b/orx-shapes/src/commonMain/kotlin/offset/Offset.kt @@ -9,7 +9,7 @@ import kotlin.math.sign import kotlin.math.sqrt -private fun Segment.splitOnExtrema(): List { +private fun Segment2D.splitOnExtrema(): List { var extrema = extrema().toMutableList() if (isStraight(0.05)) { @@ -40,10 +40,10 @@ private fun Segment.splitOnExtrema(): List { } } -private fun Segment.splitToSimple(step: Double): List { +private fun Segment2D.splitToSimple(step: Double): List { var t1 = 0.0 var t2 = 0.0 - val result = mutableListOf() + val result = mutableListOf() while (t2 <= 1.0) { t2 = t1 + step while (t2 <= 1.0 + step) { @@ -72,15 +72,15 @@ private fun Segment.splitToSimple(step: Double): List { } -fun Segment.reduced(stepSize: Double = 0.01): List { +fun Segment2D.reduced(stepSize: Double = 0.01): List { val pass1 = splitOnExtrema() //return pass1 return pass1.flatMap { it.splitToSimple(stepSize) } } -fun Segment.scale(scale: Double, polarity: YPolarity) = scale(polarity) { scale } +fun Segment2D.scale(scale: Double, polarity: YPolarity) = scale(polarity) { scale } -fun Segment.scale(polarity: YPolarity, scale: (Double) -> Double): Segment { +fun Segment2D.scale(polarity: YPolarity, scale: (Double) -> Double): Segment2D { if (control.size == 1) { return cubic.scale(polarity, scale) } @@ -111,19 +111,19 @@ fun Segment.scale(polarity: YPolarity, scale: (Double) -> Double): Segment { } } -fun Segment.offset( +fun Segment2D.offset( distance: Double, stepSize: Double = 0.01, yPolarity: YPolarity = YPolarity.CW_NEGATIVE_Y -): List { +): List { return if (linear) { val n = normal(0.0, yPolarity) if (distance > 0.0) { - listOf(Segment(start + distance * n, end + distance * n)) + listOf(Segment2D(start + distance * n, end + distance * n)) } else { val d = direction() val s = distance.coerceAtMost(length / 2.0) - val candidate = Segment( + val candidate = Segment2D( start - s * d + distance * n, end + s * d + distance * n ) @@ -226,12 +226,12 @@ fun ShapeContour.offset(distance: Double, joinType: SegmentJoin = SegmentJoin.RO var final = candidateContour.removeLoops() if (postProc && !final.empty) { - val head = Segment( + val head = Segment2D( segments[0].start + segments[0].normal(0.0) .perpendicular(polarity) * 1000.0, segments[0].start ).offset(distance).firstOrNull()?.copy(end = final.segments[0].start)?.contour - val tail = Segment( + val tail = Segment2D( segments.last().end, segments.last().end - segments.last().normal(1.0) .perpendicular(polarity) * 1000.0 diff --git a/orx-shapes/src/commonMain/kotlin/operators/BulgeContours.kt b/orx-shapes/src/commonMain/kotlin/operators/BulgeContours.kt index c6ab83d8..e8bc34c3 100644 --- a/orx-shapes/src/commonMain/kotlin/operators/BulgeContours.kt +++ b/orx-shapes/src/commonMain/kotlin/operators/BulgeContours.kt @@ -1,11 +1,10 @@ package org.openrndr.extra.shapes.operators -import org.openrndr.math.mod_ -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.ShapeContour import org.openrndr.shape.contour -fun ShapeContour.bulgeSegments(distortion: (index: Int, segment: Segment) -> Double): ShapeContour { +fun ShapeContour.bulgeSegments(distortion: (index: Int, segment: Segment2D) -> Double): ShapeContour { val c = contour { moveTo(position(0.0)) var index = 0 diff --git a/orx-shapes/src/commonMain/kotlin/operators/ChamferCorners.kt b/orx-shapes/src/commonMain/kotlin/operators/ChamferCorners.kt index 9d2ef9e7..ed505fc3 100644 --- a/orx-shapes/src/commonMain/kotlin/operators/ChamferCorners.kt +++ b/orx-shapes/src/commonMain/kotlin/operators/ChamferCorners.kt @@ -7,15 +7,15 @@ import kotlin.math.abs import kotlin.math.sign import kotlin.math.sqrt -private fun Segment.linearSub(l0: Double, l1: Double): Segment { +private fun Segment2D.linearSub(l0: Double, l1: Double): Segment2D { return sub(l0 / length, l1 / length) } -private fun Segment.linearPosition(l: Double): Vector2 { +private fun Segment2D.linearPosition(l: Double): Vector2 { return position((l / length).coerceIn(0.0, 1.0)) } -private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment, s1: Segment): Double { +private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment2D, s1: Segment2D): Double { val p3 = s1.end val p2 = s0.end val p1 = s0.start @@ -36,8 +36,8 @@ private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment, s1: * @param chamfer the chamfer function to apply */ fun ShapeContour.chamferCorners( - lengths: (index: Int, left: Segment, right: Segment) -> Double, - expands: (index: Int, left: Segment, right: Segment) -> Double = { _, _, _ -> 0.0 }, + lengths: (index: Int, left: Segment2D, right: Segment2D) -> Double, + expands: (index: Int, left: Segment2D, right: Segment2D) -> Double = { _, _, _ -> 0.0 }, clip: Boolean = true, angleThreshold: Double = 180.0, chamfer: ContourBuilder.(p1: Vector2, p2: Vector2, p3: Vector2) -> Unit diff --git a/orx-shapes/src/commonMain/kotlin/rectify/Path3DExtensions.kt b/orx-shapes/src/commonMain/kotlin/rectify/Path3DExtensions.kt new file mode 100644 index 00000000..c5a653ca --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/rectify/Path3DExtensions.kt @@ -0,0 +1,10 @@ +package org.openrndr.extra.shapes.rectify + +import org.openrndr.shape.Path3D + +/** create a rectified contour + * @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance + * @param lengthScale used to compute the size of the LUT, default value is 1.0 + **/ +fun Path3D.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedPath3D = + RectifiedPath3D(this, distanceTolerance, lengthScale) \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/rectify/RectifiedContour.kt b/orx-shapes/src/commonMain/kotlin/rectify/RectifiedContour.kt index 85b1cb15..7e42403b 100644 --- a/orx-shapes/src/commonMain/kotlin/rectify/RectifiedContour.kt +++ b/orx-shapes/src/commonMain/kotlin/rectify/RectifiedContour.kt @@ -1,152 +1,53 @@ package org.openrndr.extra.shapes.rectify -import org.openrndr.extra.shapes.utilities.splitAt import org.openrndr.math.Matrix44 import org.openrndr.math.Vector2 -import org.openrndr.math.clamp -import org.openrndr.shape.Segment import org.openrndr.shape.ShapeContour import kotlin.math.floor -/** - * RectifiedContour provides an approximately uniform parameterization for [ShapeContour] - */ -class RectifiedContour(val contour: ShapeContour, distanceTolerance: Double = 0.5, lengthScale: Double = 1.0) { - val points = - contour.equidistantPositionsWithT((contour.length * lengthScale).toInt().coerceAtLeast(2), distanceTolerance) - - val intervals by lazy { - points.zipWithNext().map { - Pair(it.first.second, it.second.second) - } - } - - private fun safe(t: Double): Double { - return if (contour.closed) { - t.mod(1.0) - } else { - t.clamp(0.0, 1.0) - } - } - - /** - * computes a rectified t-value for [contour] - */ - fun rectify(t: Double): Double { - if (contour.empty) { - return 0.0 - } else { - if (t <= 0.0) { - return 0.0 - } - val fi = t * (points.size - 1.0) - val fr = fi.mod(1.0) - val i0 = fi.toInt() - val i1 = i0 + 1 - - return if (i0 >= points.size - 1) { - 1.0 - } else { - (points[i0].second * (1.0 - fr) + points[i1].second * fr) - } - } - } - - fun inverseRectify(t: Double): Double { - if (contour.empty) { - return 0.0 - } else { - if (t <= 0.0) { - return 0.0 - } else if (t >= 1.0) { - return 1.0 - } else { - val index = intervals.binarySearch { - if (t < it.first) { - 1 - } else if (t > it.second) { - -1 - } else { - 0 - } - } - val t0 = t - intervals[index].first - val dt = intervals[index].second - intervals[index].first - val f = t0 / dt - val f0 = index.toDouble() / intervals.size - val f1 = (index + 1.0) / intervals.size - - return f0 * (1.0 - f) + f1 * f - } - } - } - - fun position(t: Double): Vector2 { - return if (contour.empty) { - Vector2.INFINITY - } else { - contour.position(rectify(safe(t))) - } - } - +class RectifiedContour(contour: ShapeContour, distanceTolerance: Double = 0.5, lengthScale: Double = 1.0) : + RectifiedPath(contour, distanceTolerance, lengthScale) { fun velocity(t: Double): Vector2 { - return if (contour.empty) { + return if (path.empty) { Vector2.ZERO } else { - val (segment, st) = contour.segment(rectify(safe(t))) - contour.segments[segment].direction(st) + val (segment, st) = path.segment(rectify(safe(t))) + path.segments[segment].direction(st) } } fun normal(t: Double): Vector2 { - return if (contour.empty) { + return if (path.empty) { Vector2.UNIT_Y } else { - contour.normal(rectify(safe(t))) + (path as ShapeContour).normal(rectify(safe(t))) } } fun pose(t: Double): Matrix44 { - return if (contour.empty) { + path as ShapeContour + return if (path.empty) { Matrix44.IDENTITY } else { - contour.pose(rectify(safe(t))) + path.pose(rectify(safe(t))) } } - fun sub(t0: Double, t1: Double): ShapeContour { - if (contour.empty) { + override fun sub(t0: Double, t1: Double): ShapeContour { + path as ShapeContour + if (path.empty) { return ShapeContour.EMPTY } - return if (contour.closed) { - contour.sub(rectify(t0.mod(1.0)) + floor(t0), rectify(t1.mod(1.0)) + floor(t1)) + + return if (path.closed) { + path.sub(rectify(t0.mod(1.0)) + floor(t0), rectify(t1.mod(1.0)) + floor(t1)) } else { - contour.sub(rectify(t0), rectify(t1)) + path.sub(rectify(t0), rectify(t1)) } } - /** - * Split contour at [ascendingTs] - * @since orx 0.4.4 - */ - fun splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): List { - return contour.splitAt(ascendingTs.map { rectify(it) }, weldEpsilon) + override fun splitAt(ascendingTs: List, weldEpsilon: Double): List { + @Suppress("UNCHECKED_CAST") + return super.splitAt(ascendingTs, weldEpsilon) as List } } - -/** create a rectified contour - * @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance - * @param lengthScale used to compute the size of the LUT, default value is 1.0 - **/ -fun ShapeContour.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour { - return RectifiedContour(this, distanceTolerance, lengthScale) -} - -/** create a rectified contour - * @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance - * @param lengthScale used to compute the size of the LUT, default value is 1.0 - * - * */ -fun Segment.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour { - return RectifiedContour(this.contour, distanceTolerance, lengthScale) -} \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath.kt b/orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath.kt new file mode 100644 index 00000000..9bdbf897 --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath.kt @@ -0,0 +1,104 @@ +package org.openrndr.extra.shapes.rectify + +import org.openrndr.extra.shapes.utilities.splitAt +import org.openrndr.extra.shapes.utilities.splitAtBase +import org.openrndr.math.EuclideanVector +import org.openrndr.math.clamp +import org.openrndr.shape.Path +import org.openrndr.shape.ShapeContour + +/** + * RectifiedContour provides an approximately uniform parameterization for [ShapeContour] + */ +abstract class RectifiedPath>( + val path: Path, + distanceTolerance: Double = 0.5, + lengthScale: Double = 1.0 +) { + val points = + path.equidistantPositionsWithT((path.length * lengthScale).toInt().coerceAtLeast(2), distanceTolerance) + + val intervals by lazy { + points.zipWithNext().map { + Pair(it.first.second, it.second.second) + } + } + + internal fun safe(t: Double): Double { + return if (path.closed) { + t.mod(1.0) + } else { + t.clamp(0.0, 1.0) + } + } + + /** + * computes a rectified t-value for [path] + */ + fun rectify(t: Double): Double { + if (path.empty) { + return 0.0 + } else { + if (t <= 0.0) { + return 0.0 + } + val fi = t * (points.size - 1.0) + val fr = fi.mod(1.0) + val i0 = fi.toInt() + val i1 = i0 + 1 + + return if (i0 >= points.size - 1) { + 1.0 + } else { + (points[i0].second * (1.0 - fr) + points[i1].second * fr) + } + } + } + + fun inverseRectify(t: Double): Double { + if (path.empty) { + return 0.0 + } else { + if (t <= 0.0) { + return 0.0 + } else if (t >= 1.0) { + return 1.0 + } else { + val index = intervals.binarySearch { + if (t < it.first) { + 1 + } else if (t > it.second) { + -1 + } else { + 0 + } + } + val t0 = t - intervals[index].first + val dt = intervals[index].second - intervals[index].first + val f = t0 / dt + val f0 = index.toDouble() / intervals.size + val f1 = (index + 1.0) / intervals.size + + return f0 * (1.0 - f) + f1 * f + } + } + } + + fun position(t: Double): T { + return if (path.empty) { + path.infinity + } else { + path.position(rectify(safe(t))) + } + } + + abstract fun sub(t0: Double, t1: Double): Path + + /** + * Split contour at [ascendingTs] + * @since orx 0.4.4 + */ + open fun splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): List> { + return path.splitAtBase(ascendingTs.map { rectify(it) }, weldEpsilon) + } +} \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath3D.kt b/orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath3D.kt new file mode 100644 index 00000000..9e288d68 --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath3D.kt @@ -0,0 +1,27 @@ +package org.openrndr.extra.shapes.rectify + +import org.openrndr.math.Vector3 +import org.openrndr.shape.Path3D +import kotlin.math.floor + +class RectifiedPath3D(contour: Path3D, distanceTolerance: Double = 0.5, lengthScale: Double = 1.0) : + RectifiedPath(contour, distanceTolerance, lengthScale) { + + override fun sub(t0: Double, t1: Double): Path3D { + path as Path3D + if (path.empty) { + return Path3D(emptyList(), false) + } + + return if (path.closed) { + path.sub(rectify(t0.mod(1.0)) + floor(t0), rectify(t1.mod(1.0)) + floor(t1)) + } else { + path.sub(rectify(t0), rectify(t1)) + } + } + + override fun splitAt(ascendingTs: List, weldEpsilon: Double): List { + @Suppress("UNCHECKED_CAST") + return super.splitAt(ascendingTs, weldEpsilon) as List + } +} diff --git a/orx-shapes/src/commonMain/kotlin/rectify/ShapeContourExtensions.kt b/orx-shapes/src/commonMain/kotlin/rectify/ShapeContourExtensions.kt new file mode 100644 index 00000000..2f5d5fdb --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/rectify/ShapeContourExtensions.kt @@ -0,0 +1,21 @@ +package org.openrndr.extra.shapes.rectify + +import org.openrndr.shape.Segment2D +import org.openrndr.shape.ShapeContour + +/** create a rectified contour + * @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance + * @param lengthScale used to compute the size of the LUT, default value is 1.0 + **/ +fun ShapeContour.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour { + return RectifiedContour(this, distanceTolerance, lengthScale) +} + +/** create a rectified contour + * @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance + * @param lengthScale used to compute the size of the LUT, default value is 1.0 + * + * */ +fun Segment2D.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour { + return RectifiedContour(this.contour, distanceTolerance, lengthScale) +} \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/splines/CatmullRom.kt b/orx-shapes/src/commonMain/kotlin/splines/CatmullRom.kt index 59c7349e..e1b593da 100644 --- a/orx-shapes/src/commonMain/kotlin/splines/CatmullRom.kt +++ b/orx-shapes/src/commonMain/kotlin/splines/CatmullRom.kt @@ -287,7 +287,7 @@ fun List.catmullRom(alpha: Double = 0.5, closed: Boolean) = CatmullRomC /** Converts spline to a [Segment]. */ -fun CatmullRom2.toSegment(): Segment { +fun CatmullRom2.toSegment(): Segment2D { val d1a2 = (p1 - p0).length.pow(2 * alpha) val d2a2 = (p2 - p1).length.pow(2 * alpha) val d3a2 = (p3 - p2).length.pow(2 * alpha) @@ -300,7 +300,7 @@ fun CatmullRom2.toSegment(): Segment { val b2 = (p1 * d3a2 - p3 * d2a2 + p2 * (2 * d3a2 + 3 * d3a * d2a + d2a2)) / (3 * d3a * (d3a + d2a)) val b3 = p2 - return Segment(b0, b1, b2, b3) + return Segment2D(b0, b1, b2, b3) } diff --git a/orx-shapes/src/commonMain/kotlin/tunni/Tunni.kt b/orx-shapes/src/commonMain/kotlin/tunni/Tunni.kt index a8999ce0..af4d2563 100644 --- a/orx-shapes/src/commonMain/kotlin/tunni/Tunni.kt +++ b/orx-shapes/src/commonMain/kotlin/tunni/Tunni.kt @@ -2,14 +2,14 @@ package org.openrndr.extra.shapes.tunni import org.openrndr.math.Vector2 import org.openrndr.shape.LineSegment -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.intersection /** * Find the Tunni point for the [Segment] * @since orx 0.4.5 */ -val Segment.tunniPoint: Vector2 +val Segment2D.tunniPoint: Vector2 get() { val c = this.cubic val ac = LineSegment(c.start, c.control[0]) @@ -23,7 +23,7 @@ val Segment.tunniPoint: Vector2 * Find the Tunni line for the [Segment] * @since orx 0.4.5 */ -val Segment.tunniLine: LineSegment +val Segment2D.tunniLine: LineSegment get() { val c = this.cubic return LineSegment(c.control[0], c.control[1]) @@ -33,7 +33,7 @@ val Segment.tunniLine: LineSegment * Find a new segment that has [tunniPoint] as its Tunni-point * @since orx 0.4.5 */ -fun Segment.withTunniPoint(tunniPoint: Vector2): Segment { +fun Segment2D.withTunniPoint(tunniPoint: Vector2): Segment2D { val ha = (start + tunniPoint) / 2.0 val hb = (end + tunniPoint) / 2.0 val hpa = ha + this.cubic.control[1] - end @@ -57,7 +57,7 @@ fun Segment.withTunniPoint(tunniPoint: Vector2): Segment { * Find a segment for which [pointOnLine] lies on its Tunni-line * @since orx 0.4.5 */ -fun Segment.withTunniLine(pointOnLine: Vector2): Segment { +fun Segment2D.withTunniLine(pointOnLine: Vector2): Segment2D { val ls = LineSegment(pointOnLine, pointOnLine + this.cubic.control[0] - this.cubic.control[1]) val ac0 = LineSegment(start, this.cubic.control[0]) val bc1 = LineSegment(end, this.cubic.control[1]) diff --git a/orx-shapes/src/commonMain/kotlin/tunni/TunniContourEdgeExtensions.kt b/orx-shapes/src/commonMain/kotlin/tunni/TunniContourEdgeExtensions.kt index f6927d80..6b968174 100644 --- a/orx-shapes/src/commonMain/kotlin/tunni/TunniContourEdgeExtensions.kt +++ b/orx-shapes/src/commonMain/kotlin/tunni/TunniContourEdgeExtensions.kt @@ -4,7 +4,6 @@ import org.openrndr.extra.shapes.adjust.ContourAdjusterEdge import org.openrndr.extra.shapes.adjust.ContourEdge import org.openrndr.math.Vector2 import org.openrndr.shape.LineSegment -import org.openrndr.shape.Segment import org.openrndr.shape.ShapeContour diff --git a/orx-shapes/src/commonMain/kotlin/utilities/FromPaths.kt b/orx-shapes/src/commonMain/kotlin/utilities/FromPaths.kt new file mode 100644 index 00000000..63f57f43 --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/utilities/FromPaths.kt @@ -0,0 +1,31 @@ +package org.openrndr.extra.shapes.utilities + +import org.openrndr.shape.Path3D +import org.openrndr.shape.ShapeContour +import org.openrndr.shape.contour +import org.openrndr.shape.path3D + +/** + * Create a [Path3D] from a list of paths + */ +fun Path3D.Companion.fromPaths(contours: List, closed: Boolean, connectEpsilon:Double=1E-6) : Path3D { + @Suppress("NAME_SHADOWING") val contours = contours.filter { !it.empty } + if (contours.isEmpty()) { + return EMPTY + } + return path3D { + moveTo(contours.first().position(0.0)) + for (c in contours.windowed(2,1,true)) { + copy(c[0]) + if (c.size == 2) { + val d = c[0].position(1.0).distanceTo(c[1].position(0.0)) + if (d > connectEpsilon ) { + lineTo(c[1].position(0.0)) + } + } + } + if (closed) { + close() + } + } +} \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/utilities/SplitAt.kt b/orx-shapes/src/commonMain/kotlin/utilities/SplitAt.kt index 9f291ebd..3508c6e9 100644 --- a/orx-shapes/src/commonMain/kotlin/utilities/SplitAt.kt +++ b/orx-shapes/src/commonMain/kotlin/utilities/SplitAt.kt @@ -1,14 +1,20 @@ package org.openrndr.extra.shapes.utilities -import org.openrndr.shape.Segment -import org.openrndr.shape.ShapeContour +import org.openrndr.math.EuclideanVector +import org.openrndr.shape.* fun ShapeContour.splitAt(segmentIndex: Double, segmentT: Double): List { val t = (1.0 / segments.size) * (segmentIndex + segmentT) return splitAt(listOf(t)) } -fun ShapeContour.splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): List { +fun Path3D.splitAt(segmentIndex: Double, segmentT: Double): List { + val t = (1.0 / segments.size) * (segmentIndex + segmentT) + return splitAt(listOf(t)) +} + + +fun > Path.splitAtBase(ascendingTs: List, weldEpsilon: Double = 1E-6): List> { if (empty || ascendingTs.isEmpty()) { return listOf(this) } @@ -18,7 +24,20 @@ fun ShapeContour.splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): } } -fun Segment.splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): List { +fun ShapeContour.splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): List { + @Suppress("UNCHECKED_CAST") + return splitAtBase(ascendingTs, weldEpsilon) as List +} + +fun Path3D.splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): List { + @Suppress("UNCHECKED_CAST") + return splitAtBase(ascendingTs, weldEpsilon) as List +} + +fun > BezierSegment.splitAtBase( + ascendingTs: List, + weldEpsilon: Double = 1E-6 +): List> { if (ascendingTs.isEmpty()) { return listOf(this) } @@ -27,4 +46,16 @@ fun Segment.splitAt(ascendingTs: List, weldEpsilon: Double = 1E-6): List return ascendingTs.windowed(2, 1).map { sub(it[0], it[1]) } +} + +fun Segment2D.splitAt(ascendingTs: List, + weldEpsilon: Double = 1E-6) : List { + @Suppress("UNCHECKED_CAST") + return splitAtBase(ascendingTs, weldEpsilon) as List +} + +fun Segment3D.splitAt(ascendingTs: List, + weldEpsilon: Double = 1E-6) : List { + @Suppress("UNCHECKED_CAST") + return splitAtBase(ascendingTs, weldEpsilon) as List } \ No newline at end of file diff --git a/orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedContour03.kt b/orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedContour03.kt new file mode 100644 index 00000000..f29149ef --- /dev/null +++ b/orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedContour03.kt @@ -0,0 +1,34 @@ +package rectify + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extra.noise.scatter +import org.openrndr.extra.shapes.hobbycurve.hobbyCurve +import org.openrndr.extra.shapes.rectify.rectified +import kotlin.random.Random + +fun main() { + application { + configure { + width = 720 + height = 720 + } + program { + val points = drawer.bounds.scatter(80.0, distanceToEdge = 100.0, random = Random(0)) + val curve = hobbyCurve(points, closed = true) + val rectified = curve.rectified() + extend { + drawer.clear(ColorRGBa.BLACK) + drawer.fill = null + drawer.stroke = ColorRGBa.GRAY + drawer.contour(curve) + + val points = (0 until 100).map { + rectified.position(it/100.0) + } + drawer.circles(points, 5.0) + + } + } + } +} \ No newline at end of file diff --git a/orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedPath3D01.kt b/orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedPath3D01.kt new file mode 100644 index 00000000..d775c00c --- /dev/null +++ b/orx-shapes/src/jvmDemo/kotlin/rectify/DemoRectifiedPath3D01.kt @@ -0,0 +1,49 @@ +package rectify + +import org.openrndr.WindowMultisample +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.DrawPrimitive +import org.openrndr.draw.isolated +import org.openrndr.extra.camera.Orbital +import org.openrndr.extra.meshgenerators.sphereMesh +import org.openrndr.extra.noise.uniformRing +import org.openrndr.extra.shapes.rectify.rectified +import org.openrndr.math.Vector3 +import org.openrndr.shape.path3D + + +fun main() { + application { + configure { + width = 720 + height = 720 + multisample = WindowMultisample.SampleCount(4) + } + program { + val p = path3D { + moveTo(0.0, 0.0, 0.0) + for (i in 0 until 10) { + curveTo( + Vector3.uniformRing(0.1, 1.0)*10.0, + Vector3.uniformRing(0.1, 1.0)*10.0, + Vector3.uniformRing(0.1, 1.0)*10.0 + ) + } + } + val pr = p.rectified(0.01, 100.0) + val sphere = sphereMesh(radius = 0.1) + extend(Orbital()) + extend { + drawer.stroke = ColorRGBa.PINK + for (i in 0 until 500) { + drawer.isolated { + drawer.translate(pr.position(i/499.0)) + drawer.vertexBuffer(sphere, DrawPrimitive.TRIANGLES) + } + } + drawer.path(p) + } + } + } +} \ No newline at end of file diff --git a/orx-svg/src/jvmMain/kotlin/SVGElement.kt b/orx-svg/src/jvmMain/kotlin/SVGElement.kt index 16cd8e91..4019e747 100644 --- a/orx-svg/src/jvmMain/kotlin/SVGElement.kt +++ b/orx-svg/src/jvmMain/kotlin/SVGElement.kt @@ -141,7 +141,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { var prevQuadCtrlPoint: Vector2? = null val contours = compounds().map { compound -> - val segments = mutableListOf() + val segments = mutableListOf() var closed = false // If an argument is invalid, an error is logged, // further interpreting is stopped and compound is returned as-is. @@ -207,7 +207,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { // Following points are implicit lineto arguments segments += points.drop(1).map { - Segment(cursor, it).apply { + Segment2D(cursor, it).apply { cursor = it } } @@ -219,21 +219,21 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { // Following points are implicit lineto arguments segments += points.drop(1).map { - Segment(cursor, cursor + it).apply { + Segment2D(cursor, cursor + it).apply { cursor += it } } } "L" -> { segments += points!!.map { - Segment(cursor, it).apply { + Segment2D(cursor, it).apply { cursor = it } } } "l" -> { segments += points!!.map { - Segment(cursor, cursor + it).apply { + Segment2D(cursor, cursor + it).apply { cursor += it } } @@ -241,7 +241,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { "H" -> { segments += command.operands.map { val target = Vector2(it, cursor.y) - Segment(cursor, target).apply { + Segment2D(cursor, target).apply { cursor = target } } @@ -249,7 +249,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { "h" -> { segments += command.operands.map { val target = cursor + Vector2(it, 0.0) - Segment(cursor, target).apply { + Segment2D(cursor, target).apply { cursor = target } } @@ -257,7 +257,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { "V" -> { segments += command.operands.map { val target = Vector2(cursor.x, it) - Segment(cursor, target).apply { + Segment2D(cursor, target).apply { cursor = target } } @@ -265,7 +265,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { "v" -> { segments += command.operands.map { val target = cursor + Vector2(0.0, it) - Segment(cursor, target).apply { + Segment2D(cursor, target).apply { cursor = target } } @@ -277,7 +277,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { return@forEach } else { val (cp1, cp2, target) = it - Segment(cursor, cp1, cp2, target).also { + Segment2D(cursor, cp1, cp2, target).also { cursor = target prevCubicCtrlPoint = cp2 } @@ -291,7 +291,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { return@forEach } else { val (cp1, cp2, target) = it.map { v -> cursor + v } - Segment(cursor, cp1, cp2, target).apply { + Segment2D(cursor, cp1, cp2, target).apply { cursor = target prevCubicCtrlPoint = cp2 } @@ -306,7 +306,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { } else { val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor) val (cp2, target) = it - Segment(cursor, cp1, cp2, target).also { + Segment2D(cursor, cp1, cp2, target).also { cursor = target prevCubicCtrlPoint = cp2 } @@ -321,7 +321,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { } else { val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor) val (cp2, target) = it.map { v -> cursor + v } - Segment(cursor, cp1, cp2, target).also { + Segment2D(cursor, cp1, cp2, target).also { cursor = target prevCubicCtrlPoint = cp2 } @@ -335,7 +335,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { return@forEach } else { val (cp, target) = it - Segment(cursor, cp, target).also { + Segment2D(cursor, cp, target).also { cursor = target prevQuadCtrlPoint = cp } @@ -349,7 +349,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { return@forEach } else { val (cp, target) = it.map { v -> cursor + v } - Segment(cursor, cp, target).also { + Segment2D(cursor, cp, target).also { cursor = target prevQuadCtrlPoint = cp } @@ -359,7 +359,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { "T" -> { points!!.forEach { val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor) - Segment(cursor, cp, it).also { _ -> + Segment2D(cursor, cp, it).also { _ -> cursor = it prevQuadCtrlPoint = cp } @@ -368,7 +368,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { "t" -> { points!!.forEach { val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor) - Segment(cursor, cp, cursor + it).also { _ -> + Segment2D(cursor, cp, cursor + it).also { _ -> cursor = it prevQuadCtrlPoint = cp } @@ -376,7 +376,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) { } "Z", "z" -> { if ((cursor - anchor).length >= 0.001) { - segments += Segment(cursor, anchor) + segments += Segment2D(cursor, anchor) } cursor = anchor closed = true diff --git a/orx-triangulation/build.gradle.kts b/orx-triangulation/build.gradle.kts index 2e509c90..07b84fc8 100644 --- a/orx-triangulation/build.gradle.kts +++ b/orx-triangulation/build.gradle.kts @@ -4,7 +4,6 @@ plugins { kotlin { sourceSets { - @Suppress("UNUSED_VARIABLE") val commonMain by getting { dependencies { api(libs.openrndr.math) @@ -12,12 +11,18 @@ kotlin { implementation(project(":orx-noise")) } } + val commonTest by getting { + dependencies { + implementation(project(":orx-shapes")) + implementation(libs.openrndr.shape) + } + } - @Suppress("UNUSED_VARIABLE") val jvmDemo by getting { dependencies { implementation(project(":orx-shapes")) implementation(project(":orx-noise")) + implementation(libs.openrndr.shape) } } } diff --git a/orx-triangulation/src/commonTest/kotlin/TestVoronoi.kt b/orx-triangulation/src/commonTest/kotlin/TestVoronoi.kt index b875919d..d8d23408 100644 --- a/orx-triangulation/src/commonTest/kotlin/TestVoronoi.kt +++ b/orx-triangulation/src/commonTest/kotlin/TestVoronoi.kt @@ -1,6 +1,7 @@ import org.openrndr.extra.triangulation.Delaunay import org.openrndr.shape.Circle import org.openrndr.shape.Rectangle + import kotlin.test.Test import kotlin.test.assertTrue diff --git a/orx-turtle/src/commonMain/kotlin/NinjaTurtle.kt b/orx-turtle/src/commonMain/kotlin/NinjaTurtle.kt index 9b78b7f4..6fa4618b 100644 --- a/orx-turtle/src/commonMain/kotlin/NinjaTurtle.kt +++ b/orx-turtle/src/commonMain/kotlin/NinjaTurtle.kt @@ -3,7 +3,7 @@ package org.openrndr.extra.turtle import org.openrndr.math.Matrix44 import org.openrndr.math.Vector4 import org.openrndr.math.transforms.buildTransform -import org.openrndr.shape.Segment +import org.openrndr.shape.Segment2D import org.openrndr.shape.ShapeContour fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) { @@ -16,7 +16,7 @@ fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) { } fun Turtle.segment( - segment: Segment, + segment: Segment2D, alignTangent: Boolean = true, externalAlignTransform: Matrix44 = Matrix44.IDENTITY ): Matrix44 {