Revise chamfer operators, add bulge operator
This commit is contained in:
41
orx-shapes/src/main/kotlin/operators/BulgeContours.kt
Normal file
41
orx-shapes/src/main/kotlin/operators/BulgeContours.kt
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package org.openrndr.extra.shapes.operators
|
||||||
|
|
||||||
|
import org.openrndr.math.mod_
|
||||||
|
import org.openrndr.shape.Segment
|
||||||
|
import org.openrndr.shape.ShapeContour
|
||||||
|
import org.openrndr.shape.contour
|
||||||
|
|
||||||
|
fun ShapeContour.bulgeSegments(distortion: (index: Int, segment: Segment) -> Double): ShapeContour {
|
||||||
|
val c = contour {
|
||||||
|
moveTo(position(0.0))
|
||||||
|
var index = 0
|
||||||
|
for (segment in this@bulgeSegments.segments) {
|
||||||
|
when {
|
||||||
|
segment.linear -> {
|
||||||
|
val q = segment.quadratic
|
||||||
|
|
||||||
|
val d = distortion(index, segment)
|
||||||
|
|
||||||
|
curveTo(q.control[0] + segment.normal(0.5) * d, q.end)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
segment.control.size == 2 -> {
|
||||||
|
curveTo(segment.control[0], segment.control[1], segment.end)
|
||||||
|
}
|
||||||
|
segment.control.size == 1 -> {
|
||||||
|
curveTo(segment.control[0], segment.end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this@bulgeSegments.closed) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ShapeContour.bulgeSegments(distortion: Double) =
|
||||||
|
bulgeSegments { _, _ -> distortion }
|
||||||
|
|
||||||
|
fun ShapeContour.bulgeSegments(distortion: List<Double>) =
|
||||||
|
bulgeSegments { index, _ -> distortion[index.mod_(distortion.size)] }
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.openrndr.extra.shapes.operators
|
package org.openrndr.extra.shapes.operators
|
||||||
|
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.mod_
|
||||||
import org.openrndr.shape.*
|
import org.openrndr.shape.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
@@ -35,42 +36,66 @@ private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment, s1:
|
|||||||
* @param chamfer the chamfer function to apply
|
* @param chamfer the chamfer function to apply
|
||||||
*/
|
*/
|
||||||
fun ShapeContour.chamferCorners(
|
fun ShapeContour.chamferCorners(
|
||||||
leftLength: Double,
|
lengths: (index: Int, left: Segment, right: Segment) -> Double,
|
||||||
rightLength: Double = leftLength,
|
expands: (index: Int, left: Segment, right: Segment) -> Double = { _, _, _ -> 0.0 },
|
||||||
clip: Boolean = true,
|
clip: Boolean = true,
|
||||||
angleThreshold: Double = 180.0,
|
angleThreshold: Double = 180.0,
|
||||||
chamfer: ContourBuilder.(p1: Vector2, p2: Vector2, p3: Vector2) -> Unit
|
chamfer: ContourBuilder.(p1: Vector2, p2: Vector2, p3: Vector2) -> Unit
|
||||||
) = contour {
|
): ShapeContour {
|
||||||
|
|
||||||
|
if (segments.size <= 1) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
return contour {
|
||||||
val sourceSegments = if (closed) {
|
val sourceSegments = if (closed) {
|
||||||
(this@chamferCorners.segments + this@chamferCorners.segments.first())
|
(this@chamferCorners.segments + this@chamferCorners.segments.first())
|
||||||
} else {
|
} else {
|
||||||
this@chamferCorners.segments
|
this@chamferCorners.segments
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prelude
|
var lengthIndex = sourceSegments.size - 1
|
||||||
if ((this@chamferCorners).closed && sourceSegments[sourceSegments.size - 2].linear && sourceSegments.first().linear) {
|
|
||||||
val length = pickLength(leftLength, rightLength, sourceSegments.last(), sourceSegments.first())
|
|
||||||
if (clip || length <= sourceSegments[0].length / 2) {
|
sourceSegments.first().let {
|
||||||
moveTo(sourceSegments[0].linearPosition(length))
|
if (it.control.size == 1) {
|
||||||
} else {
|
|
||||||
moveTo(sourceSegments[0].position(0.0))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
moveTo(position(0.0))
|
moveTo(position(0.0))
|
||||||
}
|
}
|
||||||
|
if (it.control.size == 2) {
|
||||||
|
moveTo(position(0.0))
|
||||||
|
}
|
||||||
|
if (it.linear) {
|
||||||
|
if (!this@chamferCorners.closed)
|
||||||
|
moveTo(position(0.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lengthIndex = 0
|
||||||
for ((s0, s1) in sourceSegments.zipWithNext()) {
|
for ((s0, s1) in sourceSegments.zipWithNext()) {
|
||||||
|
lengthIndex++
|
||||||
if (s0.control.size == 1) {
|
if (s0.control.size == 1) {
|
||||||
curveTo(s0.control[0], s0.end)
|
moveOrCurveTo(s0.control[0], s0.end)
|
||||||
} else if (s0.control.size == 2) {
|
} else if (s0.control.size == 2) {
|
||||||
curveTo(s0.control[0], s0.control[1], s0.end)
|
moveOrCurveTo(s0.control[0], s0.control[1], s0.end)
|
||||||
} else if (s0.linear) {
|
} else if (s0.linear) {
|
||||||
val length = pickLength(leftLength, rightLength, s0, s1)
|
|
||||||
|
val length = lengths(lengthIndex, s0, s1)
|
||||||
|
|
||||||
if (s0.linear && s1.linear && (clip || (length <= s0.length / 2 && length <= s1.length / 2))) {
|
if (s0.linear && s1.linear && (clip || (length <= s0.length / 2 && length <= s1.length / 2))) {
|
||||||
|
|
||||||
|
val expand = expands(lengthIndex, s0, s1)
|
||||||
|
|
||||||
val p0 = s0.linearPosition(s0.length - length)
|
val p0 = s0.linearPosition(s0.length - length)
|
||||||
val p1 = s1.linearPosition(length)
|
val p1 = s1.linearPosition(length)
|
||||||
lineTo(p0)
|
|
||||||
chamfer(p0, s0.end, p1)
|
val d = p1 - p0
|
||||||
|
|
||||||
|
val q0 = p0 - d * expand
|
||||||
|
val q1 = p1 + d * expand
|
||||||
|
|
||||||
|
moveOrLineTo(q0)
|
||||||
|
chamfer(q0, s0.end, q1)
|
||||||
} else {
|
} else {
|
||||||
lineTo(s0.end)
|
lineTo(s0.end)
|
||||||
}
|
}
|
||||||
@@ -84,12 +109,8 @@ fun ShapeContour.chamferCorners(
|
|||||||
val last = sourceSegments.last()
|
val last = sourceSegments.last()
|
||||||
when {
|
when {
|
||||||
last.linear -> {
|
last.linear -> {
|
||||||
if (clip || length <= last.length / 2) {
|
|
||||||
lineTo(last.linearPosition(length))
|
|
||||||
} else {
|
|
||||||
lineTo(last.end)
|
lineTo(last.end)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
last.control.size == 1 -> {
|
last.control.size == 1 -> {
|
||||||
curveTo(last.control[0], last.end)
|
curveTo(last.control[0], last.end)
|
||||||
}
|
}
|
||||||
@@ -99,28 +120,53 @@ fun ShapeContour.chamferCorners(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun ShapeContour.bevelCorners(length: Double, angleThreshold: Double = 180.0): ShapeContour =
|
fun ShapeContour.bevelCorners(length: Double, angleThreshold: Double = 180.0): ShapeContour =
|
||||||
chamferCorners(length, length, angleThreshold = angleThreshold) { _, _, p3 ->
|
chamferCorners({ _, _, _ -> length }, angleThreshold = angleThreshold) { _, _, p3 ->
|
||||||
lineTo(p3)
|
lineTo(p3)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ShapeContour.roundCorners(length: Double, angleThreshold: Double = 180.0): ShapeContour =
|
fun ShapeContour.roundCorners(length: Double, angleThreshold: Double = 180.0): ShapeContour =
|
||||||
chamferCorners(length, length, angleThreshold = angleThreshold) { _, p2, p3 ->
|
chamferCorners({ _, _, _ -> length }, angleThreshold = angleThreshold) { _, p2, p3 ->
|
||||||
curveTo(p2, p3)
|
curveTo(p2, p3)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ShapeContour.arcCorners(leftLength: Double, rightLength: Double = leftLength,
|
fun ShapeContour.arcCorners(lengths: List<Double>,
|
||||||
leftScale: Double = 1.0, rightScale: Double = leftScale,
|
expands: List<Double> = listOf(0.0),
|
||||||
leftLargeArc : Boolean = false, rightLargeArc : Boolean = leftLargeArc,
|
scales: List<Double> = listOf(1.0),
|
||||||
angleThreshold: Double = 180.0): ShapeContour =
|
largeArcs: List<Boolean> = mutableListOf(false),
|
||||||
chamferCorners(abs(leftLength), abs(rightLength), angleThreshold = angleThreshold) { p1, p2, p3 ->
|
angleThreshold: Double = 180.0): ShapeContour {
|
||||||
|
|
||||||
|
|
||||||
|
val scaleRing = scales.ring()
|
||||||
|
val lengthRing = lengths.ring()
|
||||||
|
val expandRing = expands.ring()
|
||||||
|
val largeArcRing = largeArcs.ring()
|
||||||
|
|
||||||
|
var segmentIndex = 0
|
||||||
|
return chamferCorners({ index, _, _ -> lengthRing[index] },
|
||||||
|
{ index, _, _ -> expandRing[index] },
|
||||||
|
angleThreshold = angleThreshold) { p1, p2, p3 ->
|
||||||
|
|
||||||
val dx = abs(p3.x - p2.x)
|
val dx = abs(p3.x - p2.x)
|
||||||
val dy = abs(p3.y - p2.y)
|
val dy = abs(p3.y - p2.y)
|
||||||
val radius = sqrt(dx * dx + dy * dy)
|
val radius = sqrt(dx * dx + dy * dy)
|
||||||
val det = (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)
|
val det = (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)
|
||||||
val scale = if (det < 0.0) leftScale else rightScale
|
val scale = scaleRing[segmentIndex]
|
||||||
val sweep = scale * sign(det)
|
val sweep = scale * sign(det)
|
||||||
val largeArc = if (det < 0.0) leftLargeArc else rightLargeArc
|
val largeArc = largeArcRing[segmentIndex]
|
||||||
arcTo(radius * abs(scale), radius * abs(scale), 90.0, largeArc, sweep > 0.0, p3)
|
arcTo(radius * abs(scale), radius * abs(scale), 90.0, largeArc, sweep > 0.0, p3)
|
||||||
|
segmentIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Ring<T>(private val x: List<T>) : List<T> by x {
|
||||||
|
override operator fun get(index: Int): T {
|
||||||
|
return x[index.mod_(x.size)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> List<T>.ring(): List<T> {
|
||||||
|
return Ring(this)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user