[orx-shapes] Add WIP ContourAdjuster framework

This commit is contained in:
Edwin Jakobs
2023-10-23 12:27:45 +02:00
parent 7f41c263a7
commit e7f8add84e
17 changed files with 940 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
package org.openrndr.extra.shapes.adjust
import org.openrndr.extra.shapes.vertex.ContourVertex
import org.openrndr.shape.ShapeContour
/**
* Adjusts [ShapeContour] using an accessible interface.
*
* [ContourAdjuster]
*/
class ContourAdjuster(var contour: ShapeContour) {
/**
* selected vertex indices
*/
var vertexIndices = listOf(0)
/**
* selected edge indices
*/
var edgeIndices = listOf(0)
/**
* the selected vertex
*/
val vertex: ContourAdjusterVertex
get() {
return ContourAdjusterVertex(this, vertexIndices.first())
}
/**
* the selected edge
*/
val edge: ContourAdjusterEdge
get() {
return ContourAdjusterEdge(this, edgeIndices.first())
}
/**
* select a vertex by index
*/
fun selectVertex(index: Int) {
vertexIndices = listOf(index)
}
/**
* deselect a vertex by index
*/
fun deselectVertex(index: Int) {
vertexIndices = vertexIndices.filter { it != index }
}
/**
* select multiple vertices
*/
fun selectVertices(vararg indices: Int) {
vertexIndices = indices.toList().distinct()
}
/**
* select multiple vertices using an index based [predicate]
*/
fun selectVertices(predicate: (Int) -> Boolean) {
vertexIndices =
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter(predicate)
}
/**
* select multiple vertices using an index-vertex based [predicate]
*/
fun selectVertices(predicate: (Int, ContourVertex) -> Boolean) {
vertexIndices =
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index ->
predicate(index, ContourVertex(contour, index) )
}
}
/**
* select an edge by index
*/
fun selectEdge(index: Int) {
selectEdges(index)
}
/**
* select multiple edges by index
*/
fun selectEdges(vararg indices: Int) {
edgeIndices = indices.toList().distinct()
}
/**
* select multiple vertices using an index based [predicate]
*/
fun selectEdges(predicate: (Int) -> Boolean) {
edgeIndices =
contour.segments.indices.filter(predicate)
}
/**
* select multiple edges using an index-edge based [predicate]
*/
fun selectEdges(predicate: (Int, ContourEdge) -> Boolean) {
vertexIndices =
(0 until if (contour.closed) contour.segments.size else contour.segments.size + 1).filter { index ->
predicate(index, ContourEdge(contour, index) )
}
}
fun updateSelection(adjustments: List<SegmentOperation>) {
var newVertexIndices = vertexIndices
var newEdgeIndices = edgeIndices
for (adjustment in adjustments) {
when (adjustment) {
is SegmentOperation.Insert -> {
fun insert(list: List<Int>) = list.map {
if (it >= adjustment.index) {
it + adjustment.amount
} else {
it
}
}
newVertexIndices = insert(newVertexIndices)
newEdgeIndices = insert(newEdgeIndices)
}
is SegmentOperation.Remove -> {
// TODO: handling of vertices in open contours is wrong here
newVertexIndices = newVertexIndices.mapNotNull {
if (it in adjustment.index ..< adjustment.index+adjustment.amount) {
null
} else if (it > adjustment.index) {
it - adjustment.amount
} else {
it
}
}
newEdgeIndices = newEdgeIndices.mapNotNull {
if (it in adjustment.index ..< adjustment.index+adjustment.amount) {
null
} else if (it > adjustment.index) {
it - adjustment.amount
} else {
it
}
}
}
}
}
}
}
/**
* Build a contour adjuster
*/
fun adjustContour(contour: ShapeContour, adjuster: ContourAdjuster.() -> Unit): ShapeContour {
val ca = ContourAdjuster(contour)
ca.apply(adjuster)
return ca.contour
}

View File

@@ -0,0 +1,73 @@
package org.openrndr.extra.shapes.adjust
import org.openrndr.math.Vector2
data class ContourAdjusterEdge(val contourAdjuster: ContourAdjuster, val segmentIndex: Int) {
/**
* A [ContourAdjusterVertex] interface for the start-vertex of the edge
*/
val start
get() = ContourAdjusterVertex(contourAdjuster, segmentIndex)
/**
* A [ContourAdjusterVertex] interface for the end-vertex of the edge
*/
val end
get() = ContourAdjusterVertex(contourAdjuster, (segmentIndex + 1).mod(contourAdjuster.contour.segments.size))
/**
* A link to the edge before this edge
*/
val previous: ContourAdjusterEdge?
get() = if (contourAdjuster.contour.closed) {
this.copy(segmentIndex = (segmentIndex - 1).mod(contourAdjuster.contour.segments.size))
} else {
if (segmentIndex > 0) {
this.copy(segmentIndex = segmentIndex - 1)
} else {
null
}
}
/**
* A link to the edge after this edge
*/
val next: ContourAdjusterEdge?
get() = if (contourAdjuster.contour.closed) {
this.copy(segmentIndex = (segmentIndex + 1).mod(contourAdjuster.contour.segments.size))
} else {
if (segmentIndex < contourAdjuster.contour.segments.size - 1) {
this.copy(segmentIndex = segmentIndex + 1)
} else {
null
}
}
fun select() {
contourAdjuster.selectEdge(segmentIndex)
}
private fun wrap(block: ContourEdge.() -> ContourEdge) {
val newEdge = ContourEdge(contourAdjuster.contour, segmentIndex).block()
contourAdjuster.contour = newEdge.contour
contourAdjuster.updateSelection(newEdge.adjustments)
}
fun toCubic() = wrap { toCubic() }
fun splitAt(t: Double) = wrap { splitAt(t) }
fun moveBy(translation: Vector2, updateTangents: Boolean = true) = wrap { movedBy(translation, updateTangents) }
fun rotate(rotationInDegrees: Double, anchorT: Double = 0.5, updateTangents: Boolean = true) =
wrap { rotatedBy(rotationInDegrees, anchorT, updateTangents) }
fun scale(scaleFactor: Double, anchorT: Double = 0.5, updateTangents: Boolean = true) =
wrap { scaledBy(scaleFactor, anchorT, updateTangents = true) }
fun replaceWith(t:Double, updateTangents: Boolean = true) {
wrap { replacedWith(t, updateTangents) }
}
fun sub(t0:Double, t1: Double, updateTangents: Boolean = true) {
contourAdjuster.contour =
ContourEdge(contourAdjuster.contour, segmentIndex)
.subbed(t0, t1)
.contour
}
}

View File

@@ -0,0 +1,21 @@
package org.openrndr.extra.shapes.adjust
/*
A collection of extension functions for ContourAdjuster. It is encouraged to keep the ContourAdjuster class at a minimum
size by adding extension functions here.
*/
/**
* Apply a sub to the subject contour
*/
fun ContourAdjuster.sub(t0: Double, t1: Double, updateSelection: Boolean = true) {
val oldSegments = contour.segments
contour = contour.sub(t0, t1)
val newSegments = contour.segments
// TODO: this update of the selections is not right
if (updateSelection && oldSegments.size != newSegments.size) {
selectEdges()
selectVertices()
}
}

View File

@@ -0,0 +1,20 @@
package org.openrndr.extra.shapes.adjust
import org.openrndr.extra.shapes.vertex.ContourVertex
import org.openrndr.math.Vector2
class ContourAdjusterVertex(val contourAdjuster: ContourAdjuster, val segmentIndex: Int) {
private fun wrap(block: ContourVertex.() -> ContourVertex) {
val newVertex = ContourVertex(contourAdjuster.contour, segmentIndex).block()
contourAdjuster.contour = newVertex.contour
contourAdjuster.updateSelection(newVertex.adjustments)
}
fun select() {
contourAdjuster.selectVertex(segmentIndex)
}
fun remove(updateTangents: Boolean = true) = wrap { remove(updateTangents) }
fun moveBy(translation: Vector2, updateTangents: Boolean = true) = wrap { movedBy(translation, updateTangents) }
fun rotate(rotationInDegrees: Double) = wrap { rotatedBy(rotationInDegrees) }
fun scale(scaleFactor: Double) = wrap { scaledBy(scaleFactor) }
}

View File

@@ -0,0 +1,186 @@
package org.openrndr.extra.shapes.adjust
import org.openrndr.extra.shapes.utilities.insertPointAt
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.math.transforms.buildTransform
import org.openrndr.shape.SegmentType
import org.openrndr.shape.ShapeContour
import kotlin.math.abs
private fun Vector2.transformedBy(t: Matrix44) = (t * (this.xy01)).xy
fun <E> List<E>.update(vararg updates: Pair<Int, E>): List<E> {
if (updates.isEmpty()) {
return this
}
val result = this.toMutableList()
for ((index, value) in updates) {
result[index] = value
}
return result
}
/**
* Helper for querying and adjusting [ShapeContour].
* * 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]
*/
data class ContourEdge(
val contour: ShapeContour,
val segmentIndex: Int,
val adjustments: List<SegmentOperation> = emptyList()
) {
fun withoutAdjustments(): ContourEdge {
return if (adjustments.isEmpty()) {
this
} else {
copy(adjustments = emptyList())
}
}
/**
*
*/
fun toCubic(): ContourEdge {
if (contour.segments[segmentIndex].type != SegmentType.CUBIC) {
val newSegment = contour.segments[segmentIndex].cubic
val newSegments = contour.segments
.update(segmentIndex to newSegment)
return ContourEdge(
ShapeContour.fromSegments(newSegments, contour.closed),
segmentIndex
)
} else {
return this
}
}
/**
*
*/
fun replacedWith(t: Double, updateTangents: Boolean): ContourEdge {
if (contour.empty) {
return withoutAdjustments()
}
val point = contour.segments[segmentIndex].position(t)
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 refIn = contour.segments.getOrNull(segmentInIndex)
val refOut = contour.segments.getOrNull(segmentOutIndex)
val newSegments = contour.segments.toMutableList()
if (refIn != null) {
newSegments[segmentInIndex] = newSegments[segmentInIndex].copy(end = point)
}
if (refOut != null) {
newSegments[segmentOutIndex] = newSegments[segmentOutIndex].copy(start = point)
}
val adjustments = newSegments.adjust {
removeAt(segmentIndex)
}
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments)
}
fun subbed(t0: Double, t1: Double, updateTangents: Boolean = true): ContourEdge {
if (contour.empty) {
return withoutAdjustments()
}
if (abs(t0 -t1) > 1E-6) {
val sub = contour.segments[segmentIndex].sub(t0, t1)
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 refIn = contour.segments.getOrNull(segmentInIndex)
val refOut = contour.segments.getOrNull(segmentOutIndex)
val newSegments = contour.segments.toMutableList()
if (refIn != null) {
newSegments[segmentInIndex] = newSegments[segmentInIndex].copy(end = sub.start)
}
if (refOut != null) {
newSegments[segmentOutIndex] = newSegments[segmentOutIndex].copy(start = sub.end)
}
newSegments[segmentIndex] = sub
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex)
} else {
return replacedWith(t0, updateTangents)
}
}
fun splitAt(t: Double): ContourEdge {
if (contour.empty) {
return withoutAdjustments()
}
val newContour = contour.insertPointAt(segmentIndex, t)
if (newContour.segments.size == contour.segments.size + 1) {
return ContourEdge(newContour, segmentIndex, listOf(SegmentOperation.Insert(segmentIndex + 1, 1)))
} else {
return this.copy(adjustments = emptyList())
}
}
fun transformedBy(transform: Matrix44, updateTangents: Boolean = true): ContourEdge {
val segment = contour.segments[segmentIndex]
val newSegment = segment.copy(
start = segment.start.transformedBy(transform),
control = segment.control.map { it.transformedBy(transform) }.toTypedArray<Vector2>(),
end = segment.end.transformedBy(transform)
)
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 refIn = contour.segments.getOrNull(segmentInIndex)
val refOut = contour.segments.getOrNull(segmentOutIndex)
val newSegments = contour.segments.map { it }.toMutableList()
if (refIn != null) {
val control = if (refIn.linear || !updateTangents) {
refIn.control
} else {
refIn.cubic.control
}
control[1] = control[1].transformedBy(transform)
newSegments[segmentInIndex] = refIn.copy(end = segment.start.transformedBy(transform), control = control)
}
if (refOut != null) {
val control = if (refOut.linear || !updateTangents) {
refOut.control
} else {
refOut.cubic.control
}
control[0] = control[0].transformedBy(transform)
newSegments[segmentOutIndex] = refOut.copy(start = segment.end.transformedBy(transform))
}
newSegments[segmentIndex] = newSegment
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex)
}
fun movedBy(translation: Vector2, updateTangents: Boolean = true): ContourEdge {
return transformedBy(buildTransform {
translate(translation)
}, updateTangents)
}
fun rotatedBy(rotationInDegrees: Double, anchorT: Double, updateTangents: Boolean = true): ContourEdge {
val anchor = contour.segments[segmentIndex].position(anchorT)
return transformedBy(buildTransform {
translate(anchor)
rotate(rotationInDegrees)
translate(-anchor)
}, updateTangents)
}
fun scaledBy(scaleFactor: Double, anchorT: Double, updateTangents: Boolean = true): ContourEdge {
val anchor = contour.segments[segmentIndex].position(anchorT)
return transformedBy(buildTransform {
translate(anchor)
scale(scaleFactor)
translate(-anchor)
}, updateTangents)
}
}

View File

@@ -0,0 +1,132 @@
package org.openrndr.extra.shapes.vertex
import org.openrndr.extra.shapes.adjust.ContourAdjuster
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.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 {
return if (adjustments.isEmpty()) {
this
} else {
copy(adjustments = emptyList())
}
}
val position: Vector2
get() {
return contour.segments[segmentIndex].start
}
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 newSegments = contour.segments.map { it }.toMutableList()
val refIn = newSegments.getOrNull(segmentInIndex)
val refOut = newSegments.getOrNull(segmentOutIndex)
val segment = newSegments[segmentIndex]
if (refIn != null) {
newSegments[segmentInIndex] = refIn.copy(end = segment.end)
}
val adjustments = newSegments.adjust {
removeAt(segmentIndex)
}
return ContourVertex(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments)
}
fun scaledBy(scaleFactor: Double): ContourVertex {
if (contour.empty) {
return withoutAdjustments()
}
val transform = buildTransform {
translate(position)
this.scale(scaleFactor)
translate(-position)
}
return transformTangents(transform, transform)
}
fun rotatedBy(rotationInDegrees: Double): ContourVertex {
if (contour.empty) {
return withoutAdjustments()
}
val transform = buildTransform {
translate(position)
this.rotate(rotationInDegrees)
translate(-position)
}
return transformTangents(transform, transform)
}
fun transformTangents(transformIn: Matrix44, transformOut: Matrix44 = transformIn): 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)
newSegments[segmentIndex] = run {
val cubicSegment = refOut.cubic
val newControls = arrayOf((transformOut * cubicSegment.control[0].xy01).xy, cubicSegment.control[1])
refOut.copy(control = newControls)
}
val segmentIndexIn = (segmentIndex - 1).mod(contour.segments.size)
if (refIn != null) {
newSegments[segmentIndexIn] = run {
val cubicSegment = refIn.cubic
val newControls = arrayOf(cubicSegment.control[0], (transformIn * cubicSegment.control[1].xy01).xy)
refIn.copy(control = newControls)
}
}
val newContour = ShapeContour.fromSegments(newSegments, contour.closed, contour.polarity)
return ContourVertex(newContour, segmentIndex)
}
fun movedBy(translation: Vector2, updateTangents: Boolean = true): 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 + translation
newSegments[segmentIndex] = if (updateTangents && !refOut.linear) {
val cubicSegment = refOut.cubic
val newControls = arrayOf(cubicSegment.control[0] + translation, cubicSegment.control[1])
refOut.copy(start = newPosition, control = newControls)
} else {
newSegments[segmentIndex].copy(start = newPosition)
}
val segmentIndexIn = (segmentIndex - 1).mod(contour.segments.size)
if (refIn != null) {
newSegments[segmentIndexIn] =
if (updateTangents && !refIn.linear) {
val cubicSegment = refIn.cubic
val newControls = arrayOf(cubicSegment.control[0], cubicSegment.control[1] + translation)
newSegments[segmentIndexIn].copy(control = newControls, end = newPosition)
} else {
newSegments[segmentIndexIn].copy(end = newPosition)
}
}
val newContour = ShapeContour.fromSegments(newSegments, contour.closed, contour.polarity)
return ContourVertex(newContour, segmentIndex)
}
}

View File

@@ -0,0 +1,38 @@
package org.openrndr.extra.shapes.adjust
import org.openrndr.shape.Segment
sealed interface SegmentOperation {
class Remove(val index: Int, val amount: Int) : SegmentOperation
class Insert(val index: Int, val amount: Int) : SegmentOperation
}
class SegmentAdjustments(
val replacements: List<Triple<Int, Segment, Segment>>,
val operations: List<SegmentOperation>
) {
companion object {
val EMPTY = SegmentAdjustments(emptyList(), emptyList())
}
}
class SegmentAdjuster(val list: MutableList<Segment>) {
val adjustments = mutableListOf<SegmentOperation>()
fun removeAt(index: Int) {
list.removeAt(index)
adjustments.add(SegmentOperation.Remove(index, 1))
}
fun add(segment: Segment) {
list.add(segment)
adjustments.add(SegmentOperation.Insert(list.lastIndex, 1))
}
}
fun MutableList<Segment>.adjust(block: SegmentAdjuster.() -> Unit) : List<SegmentOperation> {
val adjuster = SegmentAdjuster(this)
adjuster.block()
return adjuster.adjustments
}

View File

@@ -0,0 +1,28 @@
package org.openrndr.extra.shapes.utilities
import org.openrndr.shape.ShapeContour
import org.openrndr.shape.contour
/**
* Create a contour from a list of contours
*/
fun ShapeContour.Companion.fromContours(contours: List<ShapeContour>, closed: Boolean, connectEpsilon:Double=1E-6) : ShapeContour {
val contours = contours.filter { !it.empty }
if (contours.isEmpty()) {
return EMPTY
}
return contour {
moveTo(contours.first().position(0.0))
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 ) {
lineTo(c[1].position(0.0))
}
}
}
if (closed) {
close()
}
}
}

View File

@@ -0,0 +1,35 @@
package org.openrndr.extra.shapes.utilities
import org.openrndr.shape.ShapeContour
/**
* Insert point at [t]
* @param ascendingTs a list of ascending T values
* @param weldEpsilon minimum distance between T values
*/
fun ShapeContour.insertPointAt(t: Double, weldEpsilon: Double = 1E-6): ShapeContour {
val splitContours = splitAt(listOf(t), weldEpsilon)
return ShapeContour.fromContours(splitContours, closed)
}
/**
* Insert point at [segmentIndex], [segmentT]
* @param weldEpsilon minimum distance between T values
*/
fun ShapeContour.insertPointAt(segmentIndex: Int, segmentT: Double, weldEpsilon: Double = 1E-6): ShapeContour {
val t = (1.0 / segments.size) * (segmentIndex + segmentT)
val splitContours = splitAt(listOf(t), weldEpsilon)
return ShapeContour.fromContours(splitContours, closed)
}
/**
* Insert points at [ascendingTs]
* @param ascendingTs a list of ascending T values
* @param weldEpsilon minimum distance between T values
*/
fun ShapeContour.insertPointsAt(ascendingTs: List<Double>, weldEpsilon:Double = 1E-6) : ShapeContour {
val splitContours = splitAt(ascendingTs, weldEpsilon)
return ShapeContour.fromContours(splitContours, closed)
}

View File

@@ -0,0 +1,30 @@
package org.openrndr.extra.shapes.utilities
import org.openrndr.shape.Segment
import org.openrndr.shape.ShapeContour
fun ShapeContour.splitAt(segmentIndex: Double, segmentT: Double): List<ShapeContour> {
val t = (1.0 / segments.size) * (segmentIndex + segmentT)
return splitAt(listOf(t))
}
fun ShapeContour.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<ShapeContour> {
if (empty || ascendingTs.isEmpty()) {
return listOf(this)
}
@Suppress("NAME_SHADOWING") val ascendingTs = (listOf(0.0) + ascendingTs + listOf(1.0)).weldAscending(weldEpsilon)
return ascendingTs.windowed(2, 1).map {
sub(it[0], it[1])
}
}
fun Segment.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<Segment> {
if (ascendingTs.isEmpty()) {
return listOf(this)
}
@Suppress("NAME_SHADOWING") val ascendingTs = (listOf(0.0) + ascendingTs + listOf(1.0)).weldAscending(weldEpsilon)
return ascendingTs.windowed(2, 1).map {
sub(it[0], it[1])
}
}

View File

@@ -0,0 +1,21 @@
package org.openrndr.extra.shapes.utilities
/**
* Weld values if their distance is less than [epsilon]
*/
fun List<Double>.weldAscending(epsilon: Double = 1E-6): List<Double> {
return if (size <= 1) {
this
} else {
val result = mutableListOf(first())
var lastPassed = first()
for (i in 1 until size) {
require(this[i] >= lastPassed) { "input list is not in ascending order" }
if (this[i] - lastPassed > epsilon) {
result.add(this[i])
lastPassed = this[i]
}
}
result
}
}

View File

@@ -0,0 +1,33 @@
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 kotlin.math.cos
import kotlin.math.sin
fun main() {
application {
configure {
width = 800
height = 800
}
program {
extend {
var contour = Circle(drawer.bounds.center, 300.0).contour
contour = adjustContour(contour) {
selectVertex(0)
vertex.moveBy(Vector2(cos(seconds) * 40.0, sin(seconds * 0.43) * 40.0))
selectVertex(2)
vertex.rotate(seconds * 45.0)
selectVertex(1)
vertex.scale(cos(seconds*0.943)*2.0)
}
drawer.stroke = ColorRGBa.RED
drawer.contour(contour)
}
}
}
}

View File

@@ -0,0 +1,32 @@
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 kotlin.math.cos
import kotlin.math.sin
fun main() {
application {
configure {
width = 800
height = 800
}
program {
extend {
var contour = Circle(drawer.bounds.center, 300.0).contour
contour = adjustContour(contour) {
selectVertex(0)
vertex.remove()
selectVertex(0)
vertex.moveBy(Vector2(cos(seconds) * 40.0, sin(seconds * 0.43) * 40.0))
vertex.scale(cos(seconds*2.0)*2.0)
}
drawer.stroke = ColorRGBa.RED
drawer.contour(contour)
}
}
}
}

View File

@@ -0,0 +1,42 @@
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 kotlin.math.cos
import kotlin.math.sin
fun main() {
application {
configure {
width = 800
height = 800
}
program {
extend {
var contour = drawer.bounds.scaledBy(0.5, 0.5, 0.5).contour
contour = adjustContour(contour) {
for (i in 0 until 4) {
selectEdge(i)
edge.toCubic()
}
selectEdge(0)
edge.scale(0.5, 0.5)
edge.rotate(cos(seconds*0.5)*30.0)
selectEdge(1)
edge.toCubic()
edge.splitAt(0.5)
edge.moveBy(Vector2(cos(seconds*10.0) * 40.0, 0.0))
//edge.next?.select()
selectEdge(3)
edge.moveBy(Vector2(0.0, sin(seconds*10.0) * 40.0))
}
drawer.stroke = ColorRGBa.RED
drawer.contour(contour)
}
}
}
}

View File

@@ -0,0 +1,31 @@
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 kotlin.math.cos
import kotlin.math.sin
fun main() {
application {
configure {
width = 800
height = 800
}
program {
extend {
var contour = if (seconds.mod(2.0) < 1.0) {
drawer.bounds.scaledBy(0.5, 0.5, 0.5).contour
} else {
Circle(drawer.bounds.center, 300.0).contour
}
contour = adjustContour(contour) {
selectEdge(0)
edge.replaceWith(cos(seconds) * 0.5 + 0.5)
}
drawer.stroke = ColorRGBa.RED
drawer.contour(contour)
}
}
}
}

View File

@@ -0,0 +1,31 @@
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 kotlin.math.cos
import kotlin.math.sin
fun main() {
application {
configure {
width = 800
height = 800
}
program {
extend {
var contour =
Circle(drawer.bounds.center, 300.0).contour
contour = adjustContour(contour) {
for (i in 0 until 4) {
selectEdge(i)
edge.sub(0.2, 0.8)
}
}
drawer.stroke = ColorRGBa.RED
drawer.contour(contour)
}
}
}
}

View File

@@ -0,0 +1,28 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.color.presets.MEDIUM_PURPLE
import org.openrndr.extra.shapes.utilities.splitAt
import org.openrndr.shape.Circle
fun main() = application {
configure {
width = 800
height = 800
}
program {
val c = Circle(drawer.bounds.center, 300.0).contour
val cs = c.splitAt(listOf(1.0/3.0, 2.0/3.0))
extend {
drawer.strokeWeight = 5.0
drawer.stroke = ColorRGBa.PINK
drawer.contour(cs[0])
drawer.stroke = ColorRGBa.MEDIUM_PURPLE
drawer.contour(cs[1])
drawer.stroke = ColorRGBa.RED
drawer.contour(cs[2])
}
}
}