[orx-shapes] Add selection stacks and selection parameters
This commit is contained in:
@@ -1,23 +1,43 @@
|
|||||||
package org.openrndr.extra.shapes.adjust
|
package org.openrndr.extra.shapes.adjust
|
||||||
|
|
||||||
|
import org.openrndr.collections.pop
|
||||||
import org.openrndr.extra.shapes.vertex.ContourVertex
|
import org.openrndr.extra.shapes.vertex.ContourVertex
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts [ShapeContour] using an accessible interface.
|
* Adjusts [ShapeContour] using an accessible interface.
|
||||||
*
|
*
|
||||||
* [ContourAdjuster]
|
* [ContourAdjuster]
|
||||||
*/
|
*/
|
||||||
class ContourAdjuster(var contour: ShapeContour) {
|
class ContourAdjuster(var contour: ShapeContour) {
|
||||||
|
data class Parameters(
|
||||||
|
var selectInsertedEdges: Boolean = false,
|
||||||
|
var selectInsertedVertices: Boolean = false,
|
||||||
|
var clearSelectedEdges: Boolean = false,
|
||||||
|
var clearSelectedVertices: Boolean = false
|
||||||
|
)
|
||||||
|
var parameters = Parameters()
|
||||||
|
|
||||||
|
val parameterStack = ArrayDeque<Parameters>()
|
||||||
|
|
||||||
|
fun pushParameters() {
|
||||||
|
parameterStack.addLast(parameters.copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popParameters() {
|
||||||
|
parameters = parameterStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* selected vertex indices
|
* selected vertex indices
|
||||||
*/
|
*/
|
||||||
var vertexIndices = listOf(0)
|
var vertexSelection = listOf(0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* selected edge indices
|
* selected edge indices
|
||||||
*/
|
*/
|
||||||
var edgeIndices = listOf(0)
|
var edgeSelection = listOf(0)
|
||||||
|
|
||||||
private var vertexWorkingSet = emptyList<Int>()
|
private var vertexWorkingSet = emptyList<Int>()
|
||||||
private var edgeWorkingSet = emptyList<Int>()
|
private var edgeWorkingSet = emptyList<Int>()
|
||||||
@@ -25,17 +45,48 @@ class ContourAdjuster(var contour: ShapeContour) {
|
|||||||
private var vertexHead = emptyList<Int>()
|
private var vertexHead = emptyList<Int>()
|
||||||
private var edgeHead = emptyList<Int>()
|
private var edgeHead = emptyList<Int>()
|
||||||
|
|
||||||
|
|
||||||
|
private val vertexSelectionStack = ArrayDeque<List<Int>>()
|
||||||
|
private val edgeSelectionStack = ArrayDeque<List<Int>>()
|
||||||
|
|
||||||
|
fun pushVertexSelection() {
|
||||||
|
vertexSelectionStack.addLast(vertexSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popVertexSelection() {
|
||||||
|
vertexSelection = vertexSelectionStack.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pushEdgeSelection() {
|
||||||
|
edgeSelectionStack.addLast(edgeSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popEdgeSelection() {
|
||||||
|
edgeSelection = edgeSelectionStack.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pushSelection() {
|
||||||
|
pushEdgeSelection()
|
||||||
|
pushVertexSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popSelection() {
|
||||||
|
popEdgeSelection()
|
||||||
|
popVertexSelection()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the selected vertex
|
* the selected vertex
|
||||||
*/
|
*/
|
||||||
val vertex: ContourAdjusterVertex
|
val vertex: ContourAdjusterVertex
|
||||||
get() {
|
get() {
|
||||||
return ContourAdjusterVertex(this, { vertexIndices.first() } )
|
return vertices.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
val vertices: Sequence<ContourAdjusterVertex>
|
val vertices: Sequence<ContourAdjusterVertex>
|
||||||
get() {
|
get() {
|
||||||
vertexWorkingSet = vertexIndices
|
vertexWorkingSet = vertexSelection
|
||||||
|
applyBeforeAdjustment()
|
||||||
return sequence {
|
return sequence {
|
||||||
while (vertexWorkingSet.isNotEmpty()) {
|
while (vertexWorkingSet.isNotEmpty()) {
|
||||||
vertexHead = vertexWorkingSet.take(1)
|
vertexHead = vertexWorkingSet.take(1)
|
||||||
@@ -51,12 +102,14 @@ class ContourAdjuster(var contour: ShapeContour) {
|
|||||||
*/
|
*/
|
||||||
val edge: ContourAdjusterEdge
|
val edge: ContourAdjusterEdge
|
||||||
get() {
|
get() {
|
||||||
return ContourAdjusterEdge(this, { edgeIndices.first() })
|
return edges.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
val edges: Sequence<ContourAdjusterEdge>
|
val edges: Sequence<ContourAdjusterEdge>
|
||||||
get() {
|
get() {
|
||||||
edgeWorkingSet = edgeIndices
|
edgeWorkingSet = edgeSelection
|
||||||
|
applyBeforeAdjustment()
|
||||||
|
|
||||||
return sequence {
|
return sequence {
|
||||||
while (edgeWorkingSet.isNotEmpty()) {
|
while (edgeWorkingSet.isNotEmpty()) {
|
||||||
edgeHead = edgeWorkingSet.take(1)
|
edgeHead = edgeWorkingSet.take(1)
|
||||||
@@ -70,28 +123,28 @@ class ContourAdjuster(var contour: ShapeContour) {
|
|||||||
* select a vertex by index
|
* select a vertex by index
|
||||||
*/
|
*/
|
||||||
fun selectVertex(index: Int) {
|
fun selectVertex(index: Int) {
|
||||||
vertexIndices = listOf(index)
|
vertexSelection = listOf(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* deselect a vertex by index
|
* deselect a vertex by index
|
||||||
*/
|
*/
|
||||||
fun deselectVertex(index: Int) {
|
fun deselectVertex(index: Int) {
|
||||||
vertexIndices = vertexIndices.filter { it != index }
|
vertexSelection = vertexSelection.filter { it != index }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* select multiple vertices
|
* select multiple vertices
|
||||||
*/
|
*/
|
||||||
fun selectVertices(vararg indices: Int) {
|
fun selectVertices(vararg indices: Int) {
|
||||||
vertexIndices = indices.toList().distinct()
|
vertexSelection = indices.toList().distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* select multiple vertices using an index based [predicate]
|
* select multiple vertices using an index based [predicate]
|
||||||
*/
|
*/
|
||||||
fun selectVertices(predicate: (Int) -> Boolean) {
|
fun selectVertices(predicate: (Int) -> Boolean) {
|
||||||
vertexIndices =
|
vertexSelection =
|
||||||
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter(predicate)
|
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter(predicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +152,7 @@ class ContourAdjuster(var contour: ShapeContour) {
|
|||||||
* select multiple vertices using an index-vertex based [predicate]
|
* select multiple vertices using an index-vertex based [predicate]
|
||||||
*/
|
*/
|
||||||
fun selectVertices(predicate: (Int, ContourVertex) -> Boolean) {
|
fun selectVertices(predicate: (Int, ContourVertex) -> Boolean) {
|
||||||
vertexIndices =
|
vertexSelection =
|
||||||
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index ->
|
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index ->
|
||||||
predicate(index, ContourVertex(contour, index))
|
predicate(index, ContourVertex(contour, index))
|
||||||
}
|
}
|
||||||
@@ -116,14 +169,14 @@ class ContourAdjuster(var contour: ShapeContour) {
|
|||||||
* select multiple edges by index
|
* select multiple edges by index
|
||||||
*/
|
*/
|
||||||
fun selectEdges(vararg indices: Int) {
|
fun selectEdges(vararg indices: Int) {
|
||||||
edgeIndices = indices.toList().distinct()
|
edgeSelection = indices.toList().distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* select multiple vertices using an index based [predicate]
|
* select multiple vertices using an index based [predicate]
|
||||||
*/
|
*/
|
||||||
fun selectEdges(predicate: (Int) -> Boolean) {
|
fun selectEdges(predicate: (Int) -> Boolean) {
|
||||||
edgeIndices =
|
edgeSelection =
|
||||||
contour.segments.indices.filter(predicate)
|
contour.segments.indices.filter(predicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,32 +184,57 @@ 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) {
|
||||||
vertexIndices =
|
vertexSelection =
|
||||||
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index ->
|
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index ->
|
||||||
predicate(index, ContourEdge(contour, index))
|
predicate(index, ContourEdge(contour, index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSelection(adjustments: List<SegmentOperation>) {
|
private fun applyBeforeAdjustment() {
|
||||||
|
if (parameters.clearSelectedEdges) {
|
||||||
|
edgeSelection = emptyList()
|
||||||
|
}
|
||||||
|
if (parameters.clearSelectedVertices) {
|
||||||
|
vertexSelection = emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSelection(adjustments: List<SegmentOperation>) {
|
||||||
for (adjustment in adjustments) {
|
for (adjustment in adjustments) {
|
||||||
when (adjustment) {
|
when (adjustment) {
|
||||||
is SegmentOperation.Insert -> {
|
is SegmentOperation.Insert -> {
|
||||||
fun insert(list: List<Int>) = list.map {
|
fun insert(list: List<Int>, selectInserted: Boolean = false) =
|
||||||
if (it >= adjustment.index) {
|
if (!selectInserted) {
|
||||||
it + adjustment.amount
|
list.map {
|
||||||
|
if (it >= adjustment.index) {
|
||||||
|
it + adjustment.amount
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
it
|
(list.flatMap {
|
||||||
|
if (it >= adjustment.index) {
|
||||||
|
listOf(it + adjustment.amount) + (it + 1..it + adjustment.amount)
|
||||||
|
} else {
|
||||||
|
listOf(it)
|
||||||
|
}
|
||||||
|
} + (adjustment.index..<adjustment.index + adjustment.amount)).distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for ((i, selection) in vertexSelectionStack.withIndex()) {
|
||||||
|
vertexSelectionStack[i] = insert(selection, false)
|
||||||
}
|
}
|
||||||
vertexIndices = insert(vertexIndices)
|
for ((i, selection) in edgeSelectionStack.withIndex()) {
|
||||||
edgeIndices = insert(edgeIndices)
|
edgeSelectionStack[i] = insert(selection, false)
|
||||||
|
}
|
||||||
|
vertexSelection = insert(vertexSelection, parameters.selectInsertedVertices)
|
||||||
|
edgeSelection = insert(edgeSelection, parameters.selectInsertedEdges)
|
||||||
vertexWorkingSet = insert(vertexWorkingSet)
|
vertexWorkingSet = insert(vertexWorkingSet)
|
||||||
edgeWorkingSet = insert(edgeWorkingSet)
|
edgeWorkingSet = insert(edgeWorkingSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
is SegmentOperation.Remove -> {
|
is SegmentOperation.Remove -> {
|
||||||
|
|
||||||
fun remove(list: List<Int>) = list.mapNotNull {
|
fun remove(list: List<Int>) = list.mapNotNull {
|
||||||
if (it in adjustment.index..<adjustment.index + adjustment.amount) {
|
if (it in adjustment.index..<adjustment.index + adjustment.amount) {
|
||||||
null
|
null
|
||||||
@@ -167,8 +245,16 @@ class ContourAdjuster(var contour: ShapeContour) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: handling of vertices in open contours is wrong here
|
// TODO: handling of vertices in open contours is wrong here
|
||||||
vertexIndices = remove(vertexIndices)
|
for ((i, selection) in vertexSelectionStack.withIndex()) {
|
||||||
edgeIndices = remove(edgeIndices)
|
vertexSelectionStack[i] = remove(selection)
|
||||||
|
}
|
||||||
|
for ((i, selection) in edgeSelectionStack.withIndex()) {
|
||||||
|
edgeSelectionStack[i] = remove(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vertexSelection = remove(vertexSelection)
|
||||||
|
edgeSelection = remove(edgeSelection)
|
||||||
vertexWorkingSet = remove(vertexWorkingSet)
|
vertexWorkingSet = remove(vertexWorkingSet)
|
||||||
edgeWorkingSet = remove(edgeWorkingSet)
|
edgeWorkingSet = remove(edgeWorkingSet)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ data class ContourAdjusterEdge(val contourAdjuster: ContourAdjuster, val segment
|
|||||||
contourAdjuster.contour = newEdge.contour
|
contourAdjuster.contour = newEdge.contour
|
||||||
contourAdjuster.updateSelection(newEdge.adjustments)
|
contourAdjuster.updateSelection(newEdge.adjustments)
|
||||||
}
|
}
|
||||||
|
fun toLinear() = wrap { toLinear() }
|
||||||
|
|
||||||
fun toCubic() = wrap { toCubic() }
|
fun toCubic() = wrap { toCubic() }
|
||||||
fun splitAt(t: Double) = wrap { splitAt(t) }
|
fun splitAt(t: Double) = wrap { splitAt(t) }
|
||||||
fun moveBy(translation: Vector2, updateTangents: Boolean = true) = wrap { movedBy(translation, updateTangents) }
|
fun moveBy(translation: Vector2, updateTangents: Boolean = true) = wrap { movedBy(translation, updateTangents) }
|
||||||
|
|||||||
@@ -19,4 +19,7 @@ class ContourAdjusterVertex(val contourAdjuster: ContourAdjuster, val segmentInd
|
|||||||
fun rotate(rotationInDegrees: Double) = wrap { rotatedBy(rotationInDegrees) }
|
fun rotate(rotationInDegrees: Double) = wrap { rotatedBy(rotationInDegrees) }
|
||||||
fun scale(scaleFactor: Double) = wrap { scaledBy(scaleFactor) }
|
fun scale(scaleFactor: Double) = wrap { scaledBy(scaleFactor) }
|
||||||
|
|
||||||
|
fun rotate(rotationInDegrees: Double, anchor: Vector2) = wrap { rotatedBy(rotationInDegrees, anchor) }
|
||||||
|
fun scale(scaleFactor: Double, anchor: Vector2) = wrap { scaledBy(scaleFactor, anchor) }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import org.openrndr.shape.SegmentType
|
|||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
private fun Vector2.transformedBy(t: Matrix44) = (t * (this.xy01)).xy
|
internal fun Vector2.transformedBy(t: Matrix44) = (t * (this.xy01)).xy
|
||||||
fun <E> List<E>.update(vararg updates: Pair<Int, E>): List<E> {
|
fun <E> List<E>.update(vararg updates: Pair<Int, E>): List<E> {
|
||||||
if (updates.isEmpty()) {
|
if (updates.isEmpty()) {
|
||||||
return this
|
return this
|
||||||
@@ -24,12 +24,20 @@ fun <E> List<E>.update(vararg updates: Pair<Int, E>): List<E> {
|
|||||||
* Helper for querying and adjusting [ShapeContour].
|
* Helper for querying and adjusting [ShapeContour].
|
||||||
* * An edge embodies exactly the same thing as a [Segment][org.openrndr.shape.Segment]
|
* * An edge embodies exactly the same thing as a [Segment][org.openrndr.shape.Segment]
|
||||||
* * All edge operations are immutable and will create a new [ContourEdge] pointing to a copied and updated [ShapeContour]
|
* * All edge operations are immutable and will create a new [ContourEdge] pointing to a copied and updated [ShapeContour]
|
||||||
|
* @param contour the contour to be adjusted
|
||||||
|
* @param segmentIndex the index of the segment of the contour to be adjusted
|
||||||
|
* @param adjustments a list of [SegmentOperation] that have been applied to reach to [contour], this is used to inform [ShapeContour]
|
||||||
|
* of changes in the contour topology.
|
||||||
|
* @since 0.4.4
|
||||||
*/
|
*/
|
||||||
data class ContourEdge(
|
data class ContourEdge(
|
||||||
val contour: ShapeContour,
|
val contour: ShapeContour,
|
||||||
val segmentIndex: Int,
|
val segmentIndex: Int,
|
||||||
val adjustments: List<SegmentOperation> = emptyList()
|
val adjustments: List<SegmentOperation> = emptyList()
|
||||||
) {
|
) {
|
||||||
|
/**
|
||||||
|
* provide a copy without the list of adjustments
|
||||||
|
*/
|
||||||
fun withoutAdjustments(): ContourEdge {
|
fun withoutAdjustments(): ContourEdge {
|
||||||
return if (adjustments.isEmpty()) {
|
return if (adjustments.isEmpty()) {
|
||||||
this
|
this
|
||||||
@@ -38,9 +46,26 @@ data class ContourEdge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert the edge to a linear edge, truncating control points if those exist
|
||||||
|
*/
|
||||||
|
fun toLinear(): ContourEdge {
|
||||||
|
if (contour.segments[segmentIndex].type != SegmentType.LINEAR) {
|
||||||
|
val newSegment = contour.segments[segmentIndex].copy(control = emptyArray())
|
||||||
|
val newSegments = contour.segments
|
||||||
|
.update(segmentIndex to newSegment)
|
||||||
|
|
||||||
|
return ContourEdge(
|
||||||
|
ShapeContour.fromSegments(newSegments, contour.closed),
|
||||||
|
segmentIndex
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* convert the edge to a cubic edge
|
||||||
*/
|
*/
|
||||||
fun toCubic(): ContourEdge {
|
fun toCubic(): ContourEdge {
|
||||||
if (contour.segments[segmentIndex].type != SegmentType.CUBIC) {
|
if (contour.segments[segmentIndex].type != SegmentType.CUBIC) {
|
||||||
@@ -57,8 +82,10 @@ data class ContourEdge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* replace this edge with a point at [t]
|
||||||
|
* @param t an edge t value between 0 and 1
|
||||||
*/
|
*/
|
||||||
fun replacedWith(t: Double, updateTangents: Boolean): ContourEdge {
|
fun replacedWith(t: Double, updateTangents: Boolean): ContourEdge {
|
||||||
if (contour.empty) {
|
if (contour.empty) {
|
||||||
@@ -83,11 +110,16 @@ data class ContourEdge(
|
|||||||
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments)
|
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* subs the edge from [t0] to [t1], preserves topology unless t0 = t1
|
||||||
|
* @param t0 the start edge t-value, between 0 and 1
|
||||||
|
* @param t1 the end edge t-value, between 0 and 1
|
||||||
|
*/
|
||||||
fun subbed(t0: Double, t1: Double, updateTangents: Boolean = true): ContourEdge {
|
fun subbed(t0: Double, t1: Double, updateTangents: Boolean = true): ContourEdge {
|
||||||
if (contour.empty) {
|
if (contour.empty) {
|
||||||
return withoutAdjustments()
|
return withoutAdjustments()
|
||||||
}
|
}
|
||||||
if (abs(t0 -t1) > 1E-6) {
|
if (abs(t0 - t1) > 1E-6) {
|
||||||
val sub = contour.segments[segmentIndex].sub(t0, t1)
|
val sub = contour.segments[segmentIndex].sub(t0, t1)
|
||||||
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 =
|
val segmentOutIndex =
|
||||||
@@ -109,6 +141,10 @@ data class ContourEdge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* split the edge at [t]
|
||||||
|
* @param t an edge t value between 0 and 1, will not split when t == 0 or t == 1
|
||||||
|
*/
|
||||||
fun splitAt(t: Double): ContourEdge {
|
fun splitAt(t: Double): ContourEdge {
|
||||||
if (contour.empty) {
|
if (contour.empty) {
|
||||||
return withoutAdjustments()
|
return withoutAdjustments()
|
||||||
@@ -121,6 +157,11 @@ data class ContourEdge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apply [transform] to the edge
|
||||||
|
* @param transform a [Matrix44]
|
||||||
|
*/
|
||||||
fun transformedBy(transform: Matrix44, updateTangents: Boolean = true): ContourEdge {
|
fun transformedBy(transform: Matrix44, updateTangents: Boolean = true): ContourEdge {
|
||||||
val segment = contour.segments[segmentIndex]
|
val segment = contour.segments[segmentIndex]
|
||||||
val newSegment = segment.copy(
|
val newSegment = segment.copy(
|
||||||
@@ -133,7 +174,6 @@ data class ContourEdge(
|
|||||||
val refIn = contour.segments.getOrNull(segmentInIndex)
|
val refIn = contour.segments.getOrNull(segmentInIndex)
|
||||||
val refOut = contour.segments.getOrNull(segmentOutIndex)
|
val refOut = contour.segments.getOrNull(segmentOutIndex)
|
||||||
|
|
||||||
|
|
||||||
val newSegments = contour.segments.map { it }.toMutableList()
|
val newSegments = contour.segments.map { it }.toMutableList()
|
||||||
|
|
||||||
if (refIn != null) {
|
if (refIn != null) {
|
||||||
@@ -176,6 +216,10 @@ data class ContourEdge(
|
|||||||
|
|
||||||
fun scaledBy(scaleFactor: Double, anchorT: Double, updateTangents: Boolean = true): ContourEdge {
|
fun scaledBy(scaleFactor: Double, anchorT: Double, updateTangents: Boolean = true): ContourEdge {
|
||||||
val anchor = contour.segments[segmentIndex].position(anchorT)
|
val anchor = contour.segments[segmentIndex].position(anchorT)
|
||||||
|
return scaledBy(scaleFactor, anchor, updateTangents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scaledBy(scaleFactor: Double, anchor: Vector2, updateTangents: Boolean = true): ContourEdge {
|
||||||
return transformedBy(buildTransform {
|
return transformedBy(buildTransform {
|
||||||
translate(anchor)
|
translate(anchor)
|
||||||
scale(scaleFactor)
|
scale(scaleFactor)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package org.openrndr.extra.shapes.vertex
|
package org.openrndr.extra.shapes.vertex
|
||||||
|
|
||||||
import org.openrndr.extra.shapes.adjust.ContourAdjuster
|
import org.openrndr.extra.shapes.adjust.*
|
||||||
import org.openrndr.extra.shapes.adjust.SegmentAdjustments
|
|
||||||
import org.openrndr.extra.shapes.adjust.SegmentOperation
|
|
||||||
import org.openrndr.extra.shapes.adjust.adjust
|
|
||||||
import org.openrndr.math.Matrix44
|
import org.openrndr.math.Matrix44
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.math.transforms.buildTransform
|
import org.openrndr.math.transforms.buildTransform
|
||||||
@@ -94,7 +91,26 @@ 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)
|
||||||
|
|
||||||
|
fun rotatedBy(rotationInDegrees: Double, anchor:Vector2, updateTangents:Boolean=true): ContourVertex {
|
||||||
|
return transformedBy(buildTransform {
|
||||||
|
translate(anchor)
|
||||||
|
rotate(rotationInDegrees)
|
||||||
|
translate(-anchor)
|
||||||
|
}, updateTangents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scaledBy(scaleFactor: Double, anchor:Vector2, updateTangents:Boolean=true): ContourVertex {
|
||||||
|
return transformedBy(buildTransform {
|
||||||
|
translate(anchor)
|
||||||
|
scale(scaleFactor)
|
||||||
|
translate(-anchor)
|
||||||
|
}, updateTangents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transformedBy(transform: Matrix44, updateTangents: Boolean = true): ContourVertex {
|
||||||
if (contour.empty) {
|
if (contour.empty) {
|
||||||
return withoutAdjustments()
|
return withoutAdjustments()
|
||||||
}
|
}
|
||||||
@@ -103,10 +119,10 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a
|
|||||||
val refOut = contour.segments[segmentIndex]
|
val refOut = contour.segments[segmentIndex]
|
||||||
val refIn = if (contour.closed) contour.segments[(segmentIndex - 1).mod(contour.segments.size)] else
|
val refIn = if (contour.closed) contour.segments[(segmentIndex - 1).mod(contour.segments.size)] else
|
||||||
contour.segments.getOrNull(segmentIndex - 1)
|
contour.segments.getOrNull(segmentIndex - 1)
|
||||||
val newPosition = refOut.start + translation
|
val newPosition = refOut.start.transformedBy(transform)
|
||||||
newSegments[segmentIndex] = if (updateTangents && !refOut.linear) {
|
newSegments[segmentIndex] = if (updateTangents && !refOut.linear) {
|
||||||
val cubicSegment = refOut.cubic
|
val cubicSegment = refOut.cubic
|
||||||
val newControls = arrayOf(cubicSegment.control[0] + translation, cubicSegment.control[1])
|
val newControls = arrayOf(cubicSegment.control[0].transformedBy(transform), cubicSegment.control[1])
|
||||||
refOut.copy(start = newPosition, control = newControls)
|
refOut.copy(start = newPosition, control = newControls)
|
||||||
} else {
|
} else {
|
||||||
newSegments[segmentIndex].copy(start = newPosition)
|
newSegments[segmentIndex].copy(start = newPosition)
|
||||||
@@ -117,7 +133,7 @@ data class ContourVertex(val contour: ShapeContour, val segmentIndex: Int, val a
|
|||||||
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] + translation)
|
val newControls = arrayOf(cubicSegment.control[0], cubicSegment.control[1].transformedBy(transform))
|
||||||
newSegments[segmentIndexIn].copy(control = newControls, end = newPosition)
|
newSegments[segmentIndexIn].copy(control = newControls, end = newPosition)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package org.openrndr.extra.shapes.adjust
|
|||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment
|
||||||
|
|
||||||
sealed interface SegmentOperation {
|
sealed interface SegmentOperation {
|
||||||
class Remove(val index: Int, val amount: Int) : SegmentOperation
|
data class Remove(val index: Int, val amount: Int) : SegmentOperation
|
||||||
class Insert(val index: Int, val amount: Int) : SegmentOperation
|
data class Insert(val index: Int, val amount: Int) : SegmentOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
42
orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour06.kt
Normal file
42
orx-shapes/src/jvmDemo/kotlin/DemoAdjustContour06.kt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.shapes.adjust.adjustContour
|
||||||
|
import org.openrndr.shape.Circle
|
||||||
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
application {
|
||||||
|
configure {
|
||||||
|
width = 800
|
||||||
|
height = 800
|
||||||
|
}
|
||||||
|
program {
|
||||||
|
extend {
|
||||||
|
var contour =
|
||||||
|
Circle(drawer.bounds.center, 300.0).contour
|
||||||
|
|
||||||
|
contour = adjustContour(contour) {
|
||||||
|
parameters.clearSelectedVertices = true
|
||||||
|
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 }
|
||||||
|
for (e in edges) {
|
||||||
|
e.splitAt(splitT)
|
||||||
|
}
|
||||||
|
// as a resut of the clearSelectedVertices and selectInsertedVertices settings
|
||||||
|
// the vertex selection is set to the newly inserted vertices
|
||||||
|
for ((index, v) in vertices.withIndex()) {
|
||||||
|
v.scale(cos(seconds + i + index) * 0.5 * (1.0 / (1.0 + i)) + 1.0, drawer.bounds.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawer.stroke = ColorRGBa.RED
|
||||||
|
drawer.contour(contour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user