[orx-shapes] Improve support for open contours in adjustContour

This commit is contained in:
Edwin Jakobs
2023-10-30 07:53:40 +01:00
parent 2293d5d74e
commit 6e14b36540
8 changed files with 132 additions and 59 deletions

View File

@@ -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))
}
}

View File

@@ -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

View File

@@ -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())

View File

@@ -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 {

View File

@@ -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<SegmentOperation> = emptyList()) {
fun withoutAdjustments() : ContourVertex {
data class ContourVertex(
val contour: ShapeContour,
val segmentIndex: Int,
val adjustments: List<SegmentOperation> = 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
val refOut = if (contour.closed || segmentIndex < contour.segments.size) {
contour.segments[segmentIndex]
} else {
contour.segments.last()
}
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)
}
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(cubicSegment.control[0].transformedBy(transform), cubicSegment.control[1])
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)

View File

@@ -16,7 +16,8 @@ fun ShapeContour.Companion.fromContours(contours: List<ShapeContour>, 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))
}
}

View File

@@ -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 }

View File

@@ -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)
}
}