[orx-shapes] Improve support for open contours in adjustContour
This commit is contained in:
@@ -184,8 +184,8 @@ class ContourAdjuster(var contour: ShapeContour) {
|
|||||||
* select multiple edges using an index-edge based [predicate]
|
* select multiple edges using an index-edge based [predicate]
|
||||||
*/
|
*/
|
||||||
fun selectEdges(predicate: (Int, ContourEdge) -> Boolean) {
|
fun selectEdges(predicate: (Int, ContourEdge) -> Boolean) {
|
||||||
vertexSelection =
|
edgeSelection =
|
||||||
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index ->
|
(contour.segments.indices).filter { index ->
|
||||||
predicate(index, ContourEdge(contour, index))
|
predicate(index, ContourEdge(contour, index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ data class ContourAdjusterEdge(val contourAdjuster: ContourAdjuster, val segment
|
|||||||
return contourAdjuster.contour.segments[segmentIndex()].normal(t)
|
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
|
* A [ContourAdjusterVertex] interface for the start-vertex of the edge
|
||||||
|
|||||||
@@ -10,10 +10,34 @@ class ContourAdjusterVertex(val contourAdjuster: ContourAdjuster, val segmentInd
|
|||||||
contourAdjuster.updateSelection(newVertex.adjustments)
|
contourAdjuster.updateSelection(newVertex.adjustments)
|
||||||
}
|
}
|
||||||
|
|
||||||
val position: Vector2
|
val previous: ContourAdjusterVertex?
|
||||||
get() {
|
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() {
|
fun select() {
|
||||||
contourAdjuster.selectVertex(segmentIndex())
|
contourAdjuster.selectVertex(segmentIndex())
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ data class ContourEdge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val length: Double
|
||||||
|
get() {
|
||||||
|
return contour.segments[segmentIndex].length
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* replace this edge with a point at [t]
|
* replace this edge with a point at [t]
|
||||||
@@ -124,7 +129,8 @@ data class ContourEdge(
|
|||||||
}.windowed(2, 1).map {
|
}.windowed(2, 1).map {
|
||||||
r.sub(it[0], it[1])
|
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 {
|
fun replacedWith(openContour: ShapeContour): ContourEdge {
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import org.openrndr.math.Vector2
|
|||||||
import org.openrndr.math.transforms.buildTransform
|
import org.openrndr.math.transforms.buildTransform
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
|
|
||||||
data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val adjustments: List<SegmentOperation> = emptyList()) {
|
data class ContourVertex(
|
||||||
fun withoutAdjustments() : ContourVertex {
|
val contour: ShapeContour,
|
||||||
|
val segmentIndex: Int,
|
||||||
|
val adjustments: List<SegmentOperation> = emptyList()
|
||||||
|
) {
|
||||||
|
fun withoutAdjustments(): ContourVertex {
|
||||||
return if (adjustments.isEmpty()) {
|
return if (adjustments.isEmpty()) {
|
||||||
this
|
this
|
||||||
} else {
|
} else {
|
||||||
@@ -15,17 +19,40 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val position: Vector2
|
val normal: Vector2
|
||||||
get() {
|
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) {
|
if (contour.empty) {
|
||||||
return withoutAdjustments()
|
return withoutAdjustments()
|
||||||
}
|
}
|
||||||
val segmentInIndex = 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 segmentOutIndex = if (contour.closed) (segmentIndex + 1).mod(contour.segments.size) else segmentIndex + 1
|
||||||
val newSegments = contour.segments.map { it }.toMutableList()
|
val newSegments = contour.segments.map { it }.toMutableList()
|
||||||
val refIn = newSegments.getOrNull(segmentInIndex)
|
val refIn = newSegments.getOrNull(segmentInIndex)
|
||||||
val refOut = newSegments.getOrNull(segmentOutIndex)
|
val refOut = newSegments.getOrNull(segmentOutIndex)
|
||||||
@@ -58,7 +85,7 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a
|
|||||||
}
|
}
|
||||||
val transform = buildTransform {
|
val transform = buildTransform {
|
||||||
translate(position)
|
translate(position)
|
||||||
this.rotate(rotationInDegrees)
|
this@buildTransform.rotate(rotationInDegrees)
|
||||||
translate(-position)
|
translate(-position)
|
||||||
}
|
}
|
||||||
return transformTangents(transform, transform)
|
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 =
|
fun movedBy(translation: Vector2, updateTangents: Boolean = true): ContourVertex =
|
||||||
transformedBy(buildTransform { translate(translation) }, updateTangents)
|
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 {
|
return transformedBy(buildTransform {
|
||||||
translate(anchor)
|
translate(anchor)
|
||||||
rotate(rotationInDegrees)
|
rotate(rotationInDegrees)
|
||||||
@@ -102,7 +129,7 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a
|
|||||||
}, updateTangents)
|
}, updateTangents)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scaledBy(scaleFactor: Double, anchor:Vector2, updateTangents:Boolean=true): ContourVertex {
|
fun scaledBy(scaleFactor: Double, anchor: Vector2, updateTangents: Boolean = true): ContourVertex {
|
||||||
return transformedBy(buildTransform {
|
return transformedBy(buildTransform {
|
||||||
translate(anchor)
|
translate(anchor)
|
||||||
scale(scaleFactor)
|
scale(scaleFactor)
|
||||||
@@ -110,36 +137,65 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a
|
|||||||
}, updateTangents)
|
}, 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) {
|
if (contour.empty) {
|
||||||
return withoutAdjustments()
|
return withoutAdjustments()
|
||||||
}
|
}
|
||||||
|
|
||||||
val newSegments = contour.segments.map { it }.toMutableList()
|
val newSegments = contour.segments.map { it }.toMutableList()
|
||||||
val refOut = contour.segments[segmentIndex]
|
val refOut = if (contour.closed || segmentIndex < contour.segments.size) {
|
||||||
val refIn = if (contour.closed) contour.segments[(segmentIndex - 1).mod(contour.segments.size)] else
|
contour.segments[segmentIndex]
|
||||||
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)
|
|
||||||
} else {
|
} 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)
|
val segmentIndexIn = (segmentIndex - 1).mod(contour.segments.size)
|
||||||
if (refIn != null) {
|
if (refIn != null && (contour.closed || segmentIndex < contour.segments.size)) {
|
||||||
newSegments[segmentIndexIn] =
|
newSegments[segmentIndexIn] =
|
||||||
if (updateTangents && !refIn.linear) {
|
if (updateTangents && !refIn.linear) {
|
||||||
val cubicSegment = refIn.cubic
|
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)
|
newSegments[segmentIndexIn].copy(control = newControls, end = newPosition)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
newSegments[segmentIndexIn].copy(end = newPosition)
|
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)
|
val newContour = ShapeContour.fromSegments(newSegments, contour.closed, contour.polarity)
|
||||||
|
|
||||||
return ContourVertex(newContour, segmentIndex)
|
return ContourVertex(newContour, segmentIndex)
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ fun ShapeContour.Companion.fromContours(contours: List<ShapeContour>, closed: Bo
|
|||||||
for (c in contours.windowed(2,1,true)) {
|
for (c in contours.windowed(2,1,true)) {
|
||||||
copy(c[0])
|
copy(c[0])
|
||||||
if (c.size == 2) {
|
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))
|
lineTo(c[1].position(0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ fun main() {
|
|||||||
parameters.selectInsertedVertices = true
|
parameters.selectInsertedVertices = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (i in 0 until 4) {
|
for (i in 0 until 4) {
|
||||||
val splitT = cos(seconds + i * Math.PI*0.5)*0.2+0.5
|
val splitT = cos(seconds + i * Math.PI*0.5)*0.2+0.5
|
||||||
selectEdges { it -> true }
|
selectEdges { it -> true }
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ import org.openrndr.application
|
|||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.extra.shapes.adjust.adjustContour
|
import org.openrndr.extra.shapes.adjust.adjustContour
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.shape.Circle
|
|
||||||
import org.openrndr.shape.contour
|
import org.openrndr.shape.contour
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.sin
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
@@ -15,38 +13,22 @@ fun main() {
|
|||||||
}
|
}
|
||||||
program {
|
program {
|
||||||
extend {
|
extend {
|
||||||
var contour =
|
var contour = contour {
|
||||||
Circle(drawer.bounds.center, 300.0).contour
|
moveTo(drawer.bounds.center - Vector2(300.0, 0.0))
|
||||||
|
lineTo(drawer.bounds.center + Vector2(300.0, 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
contour = adjustContour(contour) {
|
contour = adjustContour(contour) {
|
||||||
selectEdges(0, 2)
|
selectEdge(0)
|
||||||
|
edge.splitIn(128)
|
||||||
|
val tr = cos(seconds) * 0.5 + 0.5
|
||||||
|
|
||||||
for (e in edges) {
|
selectVertices { i, v -> v.t >= tr }
|
||||||
e.replaceWith(contour {
|
val anchor = contour.position(tr)
|
||||||
moveTo(e.startPosition)
|
|
||||||
lineTo(e.position(0.5) + e.normal(0.5) * cos(seconds) * 150.0)
|
|
||||||
lineTo(e.endPosition)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
selectEdges(0, 1)
|
|
||||||
|
|
||||||
for (e in edges) {
|
for (v in vertices) {
|
||||||
e.replaceWith(contour {
|
v.rotate((v.t - tr) * 2000.0, anchor)
|
||||||
moveTo(e.startPosition)
|
v.scale(0.05, anchor)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user