diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt index 10d74cde..1062482f 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjuster.kt @@ -184,8 +184,8 @@ class ContourAdjuster(var contour: ShapeContour) { * select multiple edges using an index-edge based [predicate] */ fun selectEdges(predicate: (Int, ContourEdge) -> Boolean) { - vertexSelection = - (0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index -> + edgeSelection = + (contour.segments.indices).filter { index -> predicate(index, ContourEdge(contour, index)) } } diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt index 4a57f2a1..712e9932 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterEdge.kt @@ -19,6 +19,11 @@ data class ContourAdjusterEdge(val contourAdjuster: ContourAdjuster, val segment return contourAdjuster.contour.segments[segmentIndex()].normal(t) } + val length: Double + get() { + return contourAdjuster.contour.segments[segmentIndex()].length + } + /** * A [ContourAdjusterVertex] interface for the start-vertex of the edge diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterVertex.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterVertex.kt index b2516b6b..5abd2c4b 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterVertex.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourAdjusterVertex.kt @@ -10,10 +10,34 @@ class ContourAdjusterVertex(val contourAdjuster: ContourAdjuster, val segmentInd contourAdjuster.updateSelection(newVertex.adjustments) } - val position: Vector2 + val previous: ContourAdjusterVertex? get() { - return contourAdjuster.contour.segments[segmentIndex()].start + return if (contourAdjuster.contour.closed || segmentIndex() > 0) { + ContourAdjusterVertex(contourAdjuster, { (segmentIndex() - 1).mod(contourAdjuster.contour.segments.size) }) + } else { + null + } } + val next: ContourAdjusterVertex? + get() { + return if (contourAdjuster.contour.closed || segmentIndex() < contourAdjuster.contour.segments.size-1) { + ContourAdjusterVertex(contourAdjuster, { (segmentIndex() + 1).mod(contourAdjuster.contour.segments.size) }) + } else { + null + } + } + + + val t: Double + get() = ContourVertex(contourAdjuster.contour, segmentIndex(), emptyList()).t + + + val position: Vector2 + get() = ContourVertex(contourAdjuster.contour, segmentIndex(), emptyList()).position + + val normal: Vector2 + get() = ContourVertex(contourAdjuster.contour, segmentIndex(), emptyList()).normal + fun select() { contourAdjuster.selectVertex(segmentIndex()) diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt index c2db5cb6..cfa4e397 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourEdge.kt @@ -85,6 +85,11 @@ data class ContourEdge( } } + val length: Double + get() { + return contour.segments[segmentIndex].length + } + /** * replace this edge with a point at [t] @@ -124,7 +129,8 @@ data class ContourEdge( }.windowed(2, 1).map { r.sub(it[0], it[1]) } - return replacedWith(ShapeContour.fromContours(newSegments, false)) + require(newSegments.size == parts) + return replacedWith(ShapeContour.fromContours(newSegments, false, 1.0)) } fun replacedWith(openContour: ShapeContour): ContourEdge { diff --git a/orx-shapes/src/commonMain/kotlin/adjust/ContourVertex.kt b/orx-shapes/src/commonMain/kotlin/adjust/ContourVertex.kt index 3c87a297..f1610324 100644 --- a/orx-shapes/src/commonMain/kotlin/adjust/ContourVertex.kt +++ b/orx-shapes/src/commonMain/kotlin/adjust/ContourVertex.kt @@ -6,8 +6,12 @@ import org.openrndr.math.Vector2 import org.openrndr.math.transforms.buildTransform import org.openrndr.shape.ShapeContour -data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val adjustments: List = emptyList()) { - fun withoutAdjustments() : ContourVertex { +data class ContourVertex( + val contour: ShapeContour, + val segmentIndex: Int, + val adjustments: List = emptyList() +) { + fun withoutAdjustments(): ContourVertex { return if (adjustments.isEmpty()) { this } else { @@ -15,17 +19,40 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a } } - val position: Vector2 + val normal: Vector2 get() { - return contour.segments[segmentIndex].start + return if (contour.closed || segmentIndex > 0 || segmentIndex < contour.segments.size) { + val segmentIn = contour.segments[(segmentIndex - 1).mod(contour.segments.size)] + val normalIn = segmentIn.normal(1.0) + val normalOut = contour.segments[segmentIndex].normal(0.0) + (normalIn + normalOut).normalized + } else if (segmentIndex == 0) { + contour.normal(0.0) + } else if (segmentIndex == contour.segments.size) { + contour.normal(1.0) + } else { + error("segmentIndex out of bounds ${segmentIndex} >= ${contour.segments.size}") + } } - fun remove(updateTangents: Boolean = true) : ContourVertex { + val t: Double + get() = segmentIndex.toDouble() / contour.segments.size + + val position: Vector2 + get() { + return if (contour.closed || segmentIndex < contour.segments.size) { + contour.segments[segmentIndex].start + } else { + contour.segments[segmentIndex - 1].end + } + } + + fun remove(updateTangents: Boolean = true): ContourVertex { if (contour.empty) { return withoutAdjustments() } - val segmentInIndex = if (contour.closed) (segmentIndex-1).mod(contour.segments.size) else segmentIndex-1 - val segmentOutIndex = if (contour.closed) (segmentIndex+1).mod(contour.segments.size) else segmentIndex+1 + val segmentInIndex = if (contour.closed) (segmentIndex - 1).mod(contour.segments.size) else segmentIndex - 1 + val segmentOutIndex = if (contour.closed) (segmentIndex + 1).mod(contour.segments.size) else segmentIndex + 1 val newSegments = contour.segments.map { it }.toMutableList() val refIn = newSegments.getOrNull(segmentInIndex) val refOut = newSegments.getOrNull(segmentOutIndex) @@ -58,7 +85,7 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a } val transform = buildTransform { translate(position) - this.rotate(rotationInDegrees) + this@buildTransform.rotate(rotationInDegrees) translate(-position) } return transformTangents(transform, transform) @@ -94,7 +121,7 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a fun movedBy(translation: Vector2, updateTangents: Boolean = true): ContourVertex = transformedBy(buildTransform { translate(translation) }, updateTangents) - fun rotatedBy(rotationInDegrees: Double, anchor:Vector2, updateTangents:Boolean=true): ContourVertex { + fun rotatedBy(rotationInDegrees: Double, anchor: Vector2, updateTangents: Boolean = true): ContourVertex { return transformedBy(buildTransform { translate(anchor) rotate(rotationInDegrees) @@ -102,7 +129,7 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a }, updateTangents) } - fun scaledBy(scaleFactor: Double, anchor:Vector2, updateTangents:Boolean=true): ContourVertex { + fun scaledBy(scaleFactor: Double, anchor: Vector2, updateTangents: Boolean = true): ContourVertex { return transformedBy(buildTransform { translate(anchor) scale(scaleFactor) @@ -110,36 +137,65 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a }, updateTangents) } - fun transformedBy(transform: Matrix44, updateTangents: Boolean = true): ContourVertex { + fun transformedBy(transform: Matrix44, updateTangents: Boolean): ContourVertex { + return transformedBy(updateTangents) { v -> v.transformedBy(transform) } + } + + fun transformedBy(updateTangents: Boolean = true, transform: (Vector2) -> Vector2): ContourVertex { if (contour.empty) { return withoutAdjustments() } val newSegments = contour.segments.map { it }.toMutableList() - val refOut = contour.segments[segmentIndex] - val refIn = if (contour.closed) contour.segments[(segmentIndex - 1).mod(contour.segments.size)] else - contour.segments.getOrNull(segmentIndex - 1) - val newPosition = refOut.start.transformedBy(transform) - newSegments[segmentIndex] = if (updateTangents && !refOut.linear) { - val cubicSegment = refOut.cubic - val newControls = arrayOf(cubicSegment.control[0].transformedBy(transform), cubicSegment.control[1]) - refOut.copy(start = newPosition, control = newControls) + val refOut = if (contour.closed || segmentIndex < contour.segments.size) { + contour.segments[segmentIndex] } else { - newSegments[segmentIndex].copy(start = newPosition) + contour.segments.last() + } + val refIn = if (contour.closed) { + contour.segments[(segmentIndex - 1).mod(contour.segments.size)] + } else { + contour.segments.getOrNull(segmentIndex - 1) + } + val newPosition = if (contour.closed || segmentIndex < contour.segments.size) { + transform(refOut.start) + } else { + transform(refOut.end) + } + + if (contour.closed || segmentIndex< contour.segments.size) { + newSegments[segmentIndex] = if (updateTangents && !refOut.linear) { + val cubicSegment = refOut.cubic + val newControls = arrayOf(transform(cubicSegment.control[0]), cubicSegment.control[1]) + refOut.copy(start = newPosition, control = newControls) + } else { + newSegments[segmentIndex].copy(start = newPosition) + } + } else { + newSegments[segmentIndex-1] = if (updateTangents && !refOut.linear) { + val cubicSegment = refOut.cubic + val newControls = arrayOf(cubicSegment.control[0], transform(cubicSegment.control[1])) + refOut.copy(end = newPosition, control = newControls) + } else { + newSegments[segmentIndex-1].copy(end = newPosition) + } } val segmentIndexIn = (segmentIndex - 1).mod(contour.segments.size) - if (refIn != null) { + if (refIn != null && (contour.closed || segmentIndex < contour.segments.size)) { newSegments[segmentIndexIn] = if (updateTangents && !refIn.linear) { val cubicSegment = refIn.cubic - val newControls = arrayOf(cubicSegment.control[0], cubicSegment.control[1].transformedBy(transform)) + val newControls = arrayOf(cubicSegment.control[0], transform(cubicSegment.control[1])) newSegments[segmentIndexIn].copy(control = newControls, end = newPosition) } else { newSegments[segmentIndexIn].copy(end = newPosition) } } + for (s in newSegments.windowed(2, 1)) { + require(s[0].end.distanceTo(s[1].start) < 1E-3) + } val newContour = ShapeContour.fromSegments(newSegments, contour.closed, contour.polarity) return ContourVertex(newContour, segmentIndex) diff --git a/orx-shapes/src/commonMain/kotlin/utilities/FromContours.kt b/orx-shapes/src/commonMain/kotlin/utilities/FromContours.kt index b7306360..3e7d71ba 100644 --- a/orx-shapes/src/commonMain/kotlin/utilities/FromContours.kt +++ b/orx-shapes/src/commonMain/kotlin/utilities/FromContours.kt @@ -16,7 +16,8 @@ fun ShapeContour.Companion.fromContours(contours: List, closed: Bo for (c in contours.windowed(2,1,true)) { copy(c[0]) if (c.size == 2) { - if (c[0].position(1.0).distanceTo(c[1].position(0.0)) > connectEpsilon ) { + val d = c[0].position(1.0).distanceTo(c[1].position(0.0)) + if (d > connectEpsilon ) { lineTo(c[1].position(0.0)) } } diff --git a/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour06.kt b/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour06.kt index cc1b9dc6..dfc2155b 100644 --- a/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour06.kt +++ b/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour06.kt @@ -20,7 +20,6 @@ fun main() { parameters.selectInsertedVertices = true - for (i in 0 until 4) { val splitT = cos(seconds + i * Math.PI*0.5)*0.2+0.5 selectEdges { it -> true } diff --git a/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour07.kt b/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour07.kt index 9a983456..ae0a1c01 100644 --- a/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour07.kt +++ b/orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour07.kt @@ -2,10 +2,8 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa import org.openrndr.extra.shapes.adjust.adjustContour import org.openrndr.math.Vector2 -import org.openrndr.shape.Circle import org.openrndr.shape.contour import kotlin.math.cos -import kotlin.math.sin fun main() { application { @@ -15,38 +13,22 @@ fun main() { } program { extend { - var contour = - Circle(drawer.bounds.center, 300.0).contour + var contour = contour { + moveTo(drawer.bounds.center - Vector2(300.0, 0.0)) + lineTo(drawer.bounds.center + Vector2(300.0, 0.0)) + } contour = adjustContour(contour) { - selectEdges(0, 2) + selectEdge(0) + edge.splitIn(128) + val tr = cos(seconds) * 0.5 + 0.5 - for (e in edges) { - e.replaceWith(contour { - moveTo(e.startPosition) - lineTo(e.position(0.5) + e.normal(0.5) * cos(seconds) * 150.0) - lineTo(e.endPosition) - }) - } - selectEdges(0, 1) + selectVertices { i, v -> v.t >= tr } + val anchor = contour.position(tr) - for (e in edges) { - e.replaceWith(contour { - moveTo(e.startPosition) - val t = 0.5 - lineTo(e.position(t) + e.normal(t) * cos(seconds) * 50.0) - lineTo(e.endPosition) - }) - } - - selectEdges(0, 1) - for (e in edges) { - e.replaceWith(contour { - moveTo(e.startPosition) - val t = 0.5 - lineTo(e.position(t) + e.normal(t) * sin(seconds) * 50.0) - lineTo(e.endPosition) - }) + for (v in vertices) { + v.rotate((v.t - tr) * 2000.0, anchor) + v.scale(0.05, anchor) } }