diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt index 712e9932d..61f746ae1 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt @@ -71,7 +71,7 @@ data class ContourAdjusterEdge(val contourAdjuster: ContourAdjuster, val segment contourAdjuster.selectEdge(segmentIndex()) } - private fun wrap(block: ContourEdge.() -> ContourEdge) { + internal fun wrap(block: ContourEdge.() -> ContourEdge) { val newEdge = ContourEdge(contourAdjuster.contour, segmentIndex()).block() contourAdjuster.contour = newEdge.contour contourAdjuster.updateSelection(newEdge.adjustments) diff --git a/orx-shapes/src/commonMain/kotlin/tunni/Tunni.kt b/orx-shapes/src/commonMain/kotlin/tunni/Tunni.kt new file mode 100644 index 000000000..7df6b889b --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/tunni/Tunni.kt @@ -0,0 +1,71 @@ +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.intersection + +/** + * Find the Tunni point for the [Segment] + * @since orx 0.4.5 + */ +val Segment.tunniPoint: Vector2 + get() { + val c = this.cubic + val ac = LineSegment(c.start, c.control[0]) + val bc = LineSegment(c.end, c.control[1]) + val s = intersection(ac, bc, eps = Double.POSITIVE_INFINITY) + val t = c.control[0] * 2.0 - start + c.control[1] * 2.0 - end - s + return t + } + +/** + * Find the Tunni line for the [Segment] + * @since orx 0.4.5 + */ +val Segment.tunniLine: LineSegment + get() { + val c = this.cubic + return LineSegment(c.control[0], c.control[1]) + } + +/** + * Find a new segment that has [tunniPoint] as its Tunni-point + * @since orx 0.4.5 + */ +fun Segment.withTunniPoint(tunniPoint: Vector2): Segment { + val ha = (start + tunniPoint) / 2.0 + val hb = (end + tunniPoint) / 2.0 + val hpa = ha + this.cubic.control[1] - end + val hpb = hb + this.cubic.control[0] - start + + val hahpa = LineSegment(ha, hpa) + val ac0 = LineSegment(start, this.cubic.control[0]) + + val hbhpb = LineSegment(hb, hpb) + val bc1 = LineSegment(end, this.cubic.control[1]) + + val cp0 = intersection(hahpa, ac0, Double.POSITIVE_INFINITY) + val cp1 = intersection(hbhpb, bc1, Double.POSITIVE_INFINITY) + + return if (cp0 != Vector2.INFINITY && cp1 != Vector2.INFINITY) { + copy(start = start, control = listOf(cp0, cp1), end = end) + } else this +} + +/** + * Find a segment for which [pointOnLine] lies on its Tunni-line + * @since orx 0.4.5 + */ +fun Segment.withTunniLine(pointOnLine: Vector2): Segment { + 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]) + + val cp0 = intersection(ls, ac0, Double.POSITIVE_INFINITY) + val cp1 = intersection(ls, bc1, Double.POSITIVE_INFINITY) + + return if (cp0 != Vector2.INFINITY && cp1 != Vector2.INFINITY) { + copy(start, listOf(cp0, cp1), end) + } else this +} \ No newline at end of file diff --git a/orx-shapes/src/commonMain/kotlin/tunni/TunniContourEdgeExtensions.kt b/orx-shapes/src/commonMain/kotlin/tunni/TunniContourEdgeExtensions.kt new file mode 100644 index 000000000..024ab7697 --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/tunni/TunniContourEdgeExtensions.kt @@ -0,0 +1,65 @@ +package tunni + +import org.openrndr.extra.shapes.adjust.ContourAdjusterEdge +import org.openrndr.extra.shapes.adjust.ContourEdge +import org.openrndr.extra.shapes.tunni.tunniLine +import org.openrndr.extra.shapes.tunni.tunniPoint +import org.openrndr.extra.shapes.tunni.withTunniPoint +import org.openrndr.math.Vector2 +import org.openrndr.shape.LineSegment +import org.openrndr.shape.Segment +import org.openrndr.shape.ShapeContour + + +/** + * The Tunni-point for this [ContourEdge] + * @see Segment.tunniPoint + */ +val ContourEdge.tunniPoint: Vector2 + get() = contour.segments[segmentIndex].tunniPoint + + +/** + * The Tunni-line for this [ContourEdge] + * @see Segment.tunniLine + */ +val ContourEdge.tunniLine: LineSegment + get() = contour.segments[segmentIndex].tunniLine + + +val ContourAdjusterEdge.tunniPoint get() = contourAdjuster.contour.segments[segmentIndex()].tunniPoint + +val ContourAdjusterEdge.tunniLine get() = contourAdjuster.contour.segments[segmentIndex()].tunniLine + + + +fun ContourEdge.withTunniPoint(tunniPoint: Vector2): ContourEdge { + if (contour.empty) { + return withoutAdjustments() + } else { + val segment = contour.segments[segmentIndex].withTunniPoint(tunniPoint) + val newSegments = contour.segments.map { it }.toMutableList() + newSegments[segmentIndex] = segment + return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex) + } +} +fun ContourEdge.withTunniLine(pointOnLine: Vector2): ContourEdge { + if (contour.empty) { + return withoutAdjustments() + } else { + val segment = contour.segments[segmentIndex].withTunniPoint(pointOnLine) + val newSegments = contour.segments.map { it }.toMutableList() + newSegments[segmentIndex] = segment + return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex) + } +} + +/** + * @see Segment.withTunniPoint + */ +fun ContourAdjusterEdge.withTunniPoint(tunniPoint: Vector2) = wrap { withTunniPoint(tunniPoint) } + +/** + * @see Segment.withTunniLine + */ +fun ContourAdjusterEdge.withTunniLine(pointOnLine: Vector2) = wrap { withTunniLine(pointOnLine) } \ No newline at end of file