Update for OPENRNDR segment and path generalizations
This commit is contained in:
@@ -570,28 +570,28 @@ class CompositionDrawer(documentBounds: CompositionDimensions = defaultCompositi
|
||||
c1: Vector2,
|
||||
end: Vector2,
|
||||
insert: Boolean = true
|
||||
) = segment(Segment(start, c0, c1, end), insert)
|
||||
) = segment(Segment2D(start, c0, c1, end), insert)
|
||||
|
||||
fun segment(
|
||||
start: Vector2,
|
||||
c0: Vector2,
|
||||
end: Vector2,
|
||||
insert: Boolean = true
|
||||
) = segment(Segment(start, c0, end), insert)
|
||||
) = segment(Segment2D(start, c0, end), insert)
|
||||
|
||||
fun segment(
|
||||
start: Vector2,
|
||||
end: Vector2,
|
||||
insert: Boolean = true
|
||||
) = segment(Segment(start, end), insert)
|
||||
) = segment(Segment2D(start, end), insert)
|
||||
|
||||
fun segment(
|
||||
segment: Segment,
|
||||
segment: Segment2D,
|
||||
insert: Boolean = true
|
||||
) = contour(segment.contour, insert)
|
||||
|
||||
fun segments(
|
||||
segments: List<Segment>,
|
||||
segments: List<Segment2D>,
|
||||
insert: Boolean = true
|
||||
) = segments.map {
|
||||
segment(it, insert)
|
||||
|
||||
@@ -3,14 +3,14 @@ package org.openrndr.extra.fcurve
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.transforms.buildTransform
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Find the (first) t value for a given [x] value
|
||||
*/
|
||||
private fun Segment.tForX(x: Double): Double {
|
||||
private fun Segment2D.tForX(x: Double): Double {
|
||||
if (linear) {
|
||||
return (x - start.x) / (end.x - start.x)
|
||||
} else {
|
||||
@@ -29,7 +29,7 @@ private fun Segment.tForX(x: Double): Double {
|
||||
/**
|
||||
* Find the y value for a given [x] value
|
||||
*/
|
||||
private fun Segment.yForX(x: Double): Double {
|
||||
private fun Segment2D.yForX(x: Double): Double {
|
||||
val t = tForX(x)
|
||||
return position(t).y
|
||||
}
|
||||
@@ -37,7 +37,7 @@ private fun Segment.yForX(x: Double): Double {
|
||||
/**
|
||||
* Scale tangents such that tangent lines do not overlap
|
||||
*/
|
||||
fun Segment.scaleTangents(axis: Vector2 = Vector2.UNIT_X): Segment {
|
||||
fun Segment2D.scaleTangents(axis: Vector2 = Vector2.UNIT_X): Segment2D {
|
||||
if (linear) {
|
||||
return this
|
||||
} else {
|
||||
@@ -74,7 +74,7 @@ fun Segment.scaleTangents(axis: Vector2 = Vector2.UNIT_X): Segment {
|
||||
* Fcurve class
|
||||
*/
|
||||
@Serializable
|
||||
data class FCurve(val segments: List<Segment>) {
|
||||
data class FCurve(val segments: List<Segment2D>) {
|
||||
|
||||
/**
|
||||
* Reverse the fcurve
|
||||
@@ -105,7 +105,7 @@ data class FCurve(val segments: List<Segment>) {
|
||||
* Create a sampler or function from the Fcurve
|
||||
*/
|
||||
fun sampler(normalized: Boolean = false): (Double) -> Double {
|
||||
var cachedSegment: Segment? = null
|
||||
var cachedSegment: Segment2D? = null
|
||||
if (!normalized) {
|
||||
return { t ->
|
||||
val r = valueWithSegment(t, cachedSegment)
|
||||
@@ -139,13 +139,13 @@ data class FCurve(val segments: List<Segment>) {
|
||||
* @param segment an optional segment that can be used to speed up scanning for the relevant segment
|
||||
* @see valueWithSegment
|
||||
*/
|
||||
fun value(t: Double, segment: Segment? = null): Double = valueWithSegment(t, segment).first
|
||||
fun value(t: Double, segment: Segment2D? = null): Double = valueWithSegment(t, segment).first
|
||||
|
||||
/**
|
||||
* Evaluate the Fcurve at [t]
|
||||
* @param segment an optional segment that can be used to speed up scanning for the relevant segment
|
||||
*/
|
||||
fun valueWithSegment(t: Double, cachedSegment: Segment? = null): Pair<Double, Segment?> {
|
||||
fun valueWithSegment(t: Double, cachedSegment: Segment2D? = null): Pair<Double, Segment2D?> {
|
||||
if (cachedSegment != null) {
|
||||
if (t >= cachedSegment.start.x && t < cachedSegment.end.x) {
|
||||
return Pair(cachedSegment.yForX(t), cachedSegment)
|
||||
@@ -180,7 +180,7 @@ data class FCurve(val segments: List<Segment>) {
|
||||
* Return a list of contours that can be used to visualize the Fcurve
|
||||
*/
|
||||
fun contours(scale: Vector2 = Vector2.ONE): List<ShapeContour> {
|
||||
var active = mutableListOf<Segment>()
|
||||
var active = mutableListOf<Segment2D>()
|
||||
val result = mutableListOf<ShapeContour>()
|
||||
|
||||
for (segment in segments) {
|
||||
@@ -210,7 +210,7 @@ data class FCurve(val segments: List<Segment>) {
|
||||
* Fcurve builder
|
||||
*/
|
||||
class FCurveBuilder {
|
||||
val segments = mutableListOf<Segment>()
|
||||
val segments = mutableListOf<Segment2D>()
|
||||
var cursor = Vector2(0.0, 0.0)
|
||||
|
||||
var path = ""
|
||||
@@ -222,7 +222,7 @@ class FCurveBuilder {
|
||||
|
||||
fun lineTo(x: Double, y: Double, relative: Boolean = false) {
|
||||
val r = if (relative) 1.0 else 0.0
|
||||
segments.add(Segment(cursor, Vector2(x + cursor.x, y + cursor.y * r)))
|
||||
segments.add(Segment2D(cursor, Vector2(x + cursor.x, y + cursor.y * r)))
|
||||
cursor = Vector2(cursor.x + x, cursor.y * r + y)
|
||||
path += "${if (relative) "l" else "L"}$x,$y"
|
||||
}
|
||||
@@ -234,7 +234,7 @@ class FCurveBuilder {
|
||||
) {
|
||||
val r = if (relative) 1.0 else 0.0
|
||||
segments.add(
|
||||
Segment(
|
||||
Segment2D(
|
||||
cursor,
|
||||
Vector2(cursor.x + x0, cursor.y * r + y0),
|
||||
Vector2(cursor.x + x, cursor.y * r + y)
|
||||
@@ -251,7 +251,7 @@ class FCurveBuilder {
|
||||
) {
|
||||
val r = if (relative) 1.0 else 0.0
|
||||
segments.add(
|
||||
Segment(
|
||||
Segment2D(
|
||||
cursor,
|
||||
Vector2(cursor.x + x0, cursor.y * r + y0),
|
||||
Vector2(cursor.x + x1, cursor.y * r + y1),
|
||||
@@ -272,7 +272,7 @@ class FCurveBuilder {
|
||||
val dy = outPos.y - outTangent.y
|
||||
val ts = x / lastDuration
|
||||
segments.add(
|
||||
Segment(
|
||||
Segment2D(
|
||||
cursor,
|
||||
Vector2(cursor.x + dx * ts, cursor.y + dy),
|
||||
Vector2(cursor.x + x * 0.66, cursor.y * r + y),
|
||||
@@ -290,7 +290,7 @@ class FCurveBuilder {
|
||||
val dx = cursor.x - outTangent.x
|
||||
val dy = cursor.y - outTangent.y
|
||||
segments.add(
|
||||
Segment(
|
||||
Segment2D(
|
||||
cursor,
|
||||
Vector2(cursor.x + dx, cursor.y + dy),
|
||||
Vector2(cursor.x + x1, cursor.y * r + y1),
|
||||
@@ -370,7 +370,7 @@ private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
||||
*/
|
||||
return fcurve {
|
||||
fun dx(): Double {
|
||||
val lastSegment = segments.lastOrNull() ?: Segment(Vector2.ZERO, Vector2.ZERO)
|
||||
val lastSegment = segments.lastOrNull() ?: Segment2D(Vector2.ZERO, Vector2.ZERO)
|
||||
return lastSegment.end.x - lastSegment.start.x
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.runway.*
|
||||
|
||||
/**
|
||||
* This example requires a `runway/BASNet` model to be active in Runway.
|
||||
*/
|
||||
fun main() = application {
|
||||
configure {
|
||||
width = 331
|
||||
height = 400
|
||||
}
|
||||
|
||||
program {
|
||||
val image = loadImage("demo-data/images/life-cover.jpg")
|
||||
|
||||
val result: BASNETResult =
|
||||
runwayQuery("http://localhost:8000/query", BASNETRequest(image.toData()))
|
||||
|
||||
val segmentImage = ColorBuffer.fromData(result.image)
|
||||
|
||||
extend {
|
||||
drawer.image(segmentImage, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
//import org.openrndr.application
|
||||
//import org.openrndr.draw.*
|
||||
//import org.openrndr.extra.runway.*
|
||||
//
|
||||
///**
|
||||
// * This example requires a `runway/BASNet` model to be active in Runway.
|
||||
// */
|
||||
//fun main() = application {
|
||||
// configure {
|
||||
// width = 331
|
||||
// height = 400
|
||||
// }
|
||||
//
|
||||
// program {
|
||||
// val image = loadImage("demo-data/images/life-cover.jpg")
|
||||
//
|
||||
// val result: BASNETResult =
|
||||
// runwayQuery("http://localhost:8000/query", BASNETRequest(image.toData()))
|
||||
//
|
||||
// val segmentImage = ColorBuffer.fromData(result.image)
|
||||
//
|
||||
// extend {
|
||||
// drawer.image(segmentImage, 0.0, 0.0)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -86,7 +86,7 @@ fun ShapeProvider.scatter(
|
||||
hg
|
||||
}
|
||||
|
||||
fun Segment.randomPoints(count: Int) = sequence {
|
||||
fun Segment2D.randomPoints(count: Int) = sequence {
|
||||
for (i in 0 until count) {
|
||||
val t = random.nextDouble()
|
||||
yield(position(t) - normal(t).normalized * distanceToEdge)
|
||||
|
||||
@@ -37,6 +37,7 @@ kotlin {
|
||||
implementation(project(":orx-triangulation"))
|
||||
implementation(project(":orx-shapes"))
|
||||
implementation(project(":orx-noise"))
|
||||
implementation(project(":orx-mesh-generators"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ package org.openrndr.extra.shapes.adjust
|
||||
import org.openrndr.collections.pop
|
||||
import org.openrndr.extra.shapes.vertex.ContourVertex
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
|
||||
class ContourAdjusterStatus(
|
||||
val contour: ShapeContour,
|
||||
val selectedSegments: List<Segment>,
|
||||
val selectedSegments: List<Segment2D>,
|
||||
val selectedPoints: List<Vector2>
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ 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.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.SegmentType
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import kotlin.math.abs
|
||||
@@ -151,7 +151,7 @@ data class ContourEdge(
|
||||
removeAt(segmentIndex)
|
||||
|
||||
if (segment.start.distanceTo(openContour.position(0.0)) > 1E-3) {
|
||||
add(insertIndex, Segment(segment.start, openContour.position(0.0)))
|
||||
add(insertIndex, Segment2D(segment.start, openContour.position(0.0)))
|
||||
insertIndex++
|
||||
}
|
||||
for (s in openContour.segments) {
|
||||
@@ -159,7 +159,7 @@ data class ContourEdge(
|
||||
insertIndex++
|
||||
}
|
||||
if (segment.end.distanceTo(openContour.position(1.0)) > 1E-3) {
|
||||
add(insertIndex, Segment(segment.end, openContour.position(1.0)))
|
||||
add(insertIndex, Segment2D(segment.end, openContour.position(1.0)))
|
||||
}
|
||||
}
|
||||
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.openrndr.extra.shapes.adjust
|
||||
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
|
||||
sealed interface SegmentOperation {
|
||||
data class Remove(val index: Int, val amount: Int) : SegmentOperation
|
||||
@@ -9,7 +9,7 @@ sealed interface SegmentOperation {
|
||||
|
||||
|
||||
class SegmentAdjustments(
|
||||
val replacements: List<Triple<Int, Segment, Segment>>,
|
||||
val replacements: List<Triple<Int, Segment2D, Segment2D>>,
|
||||
val operations: List<SegmentOperation>
|
||||
) {
|
||||
|
||||
@@ -18,24 +18,24 @@ class SegmentAdjustments(
|
||||
}
|
||||
}
|
||||
|
||||
class SegmentAdjuster(val list: MutableList<Segment>) {
|
||||
class SegmentAdjuster(val list: MutableList<Segment2D>) {
|
||||
val adjustments = mutableListOf<SegmentOperation>()
|
||||
|
||||
fun removeAt(index: Int) {
|
||||
list.removeAt(index)
|
||||
adjustments.add(SegmentOperation.Remove(index, 1))
|
||||
}
|
||||
fun add(segment: Segment) {
|
||||
fun add(segment: Segment2D) {
|
||||
list.add(segment)
|
||||
adjustments.add(SegmentOperation.Insert(list.lastIndex, 1))
|
||||
}
|
||||
fun add(index: Int, segment: Segment) {
|
||||
fun add(index: Int, segment: Segment2D) {
|
||||
list.add(index, segment)
|
||||
adjustments.add(SegmentOperation.Insert(index, 1))
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<Segment>.adjust(block: SegmentAdjuster.() -> Unit) : List<SegmentOperation> {
|
||||
fun MutableList<Segment2D>.adjust(block: SegmentAdjuster.() -> Unit) : List<SegmentOperation> {
|
||||
val adjuster = SegmentAdjuster(this)
|
||||
adjuster.block()
|
||||
return adjuster.adjustments
|
||||
|
||||
@@ -111,13 +111,13 @@ class AlphaShape(val points: List<Vector2>) {
|
||||
private fun edgesToShapeContour(edges: List<Pair<Int, Int>>): ShapeContour {
|
||||
if (edges.isEmpty()) return ShapeContour.EMPTY
|
||||
val mapping = edges.toMap()
|
||||
val segments = mutableListOf<Segment>()
|
||||
val segments = mutableListOf<Segment2D>()
|
||||
val start = edges.first().first
|
||||
var current = start
|
||||
val left = edges.map { it.first }.toMutableSet()
|
||||
for (i in edges.indices) {
|
||||
val next = mapping[current]!!
|
||||
segments.add(Segment(getVec(current), getVec(next)))
|
||||
segments.add(Segment2D(getVec(current), getVec(next)))
|
||||
left.remove(current)
|
||||
current = next
|
||||
if (current == start) break
|
||||
@@ -140,11 +140,11 @@ class AlphaShape(val points: List<Vector2>) {
|
||||
while (left.isNotEmpty()) {
|
||||
val start = left.first()
|
||||
var current = start
|
||||
val segments = mutableListOf<Segment>()
|
||||
val segments = mutableListOf<Segment2D>()
|
||||
val contourPoints = mutableListOf<Vector2>()
|
||||
for (i in edges.indices) {
|
||||
val next = mapping[current]!!
|
||||
segments.add(Segment(getVec(current), getVec(next)))
|
||||
segments.add(Segment2D(getVec(current), getVec(next)))
|
||||
contourPoints.add(getVec(current))
|
||||
left.remove(current)
|
||||
current = next
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.openrndr.color.ConvertibleToColorRGBa
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.shape.Rectangle
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -123,7 +123,7 @@ open class BezierPatchBase<C>(
|
||||
cps[j] += points[i][j] * cs[i]
|
||||
}
|
||||
}
|
||||
return ShapeContour(listOf(Segment(cps[0], cps[1], cps[2], cps[3])), false)
|
||||
return ShapeContour(listOf(Segment2D(cps[0], cps[1], cps[2], cps[3])), false)
|
||||
}
|
||||
|
||||
fun vertical(u: Double): ShapeContour {
|
||||
@@ -134,33 +134,33 @@ open class BezierPatchBase<C>(
|
||||
cps[j] += points[j][i] * cs[i]
|
||||
}
|
||||
}
|
||||
return ShapeContour(listOf(Segment(cps[0], cps[1], cps[2], cps[3])), false)
|
||||
return ShapeContour(listOf(Segment2D(cps[0], cps[1], cps[2], cps[3])), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a sub-patch based on uv parameterization
|
||||
*/
|
||||
fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatchBase<C> {
|
||||
val c0 = Segment(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1)
|
||||
val c1 = Segment(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1)
|
||||
val c2 = Segment(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1)
|
||||
val c3 = Segment(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1)
|
||||
val c0 = Segment2D(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1)
|
||||
val c1 = Segment2D(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1)
|
||||
val c2 = Segment2D(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1)
|
||||
val c3 = Segment2D(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1)
|
||||
|
||||
val sub0 = bezierPatch(c0, c1, c2, c3)
|
||||
val d0 = Segment(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1)
|
||||
val d1 = Segment(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1)
|
||||
val d2 = Segment(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1)
|
||||
val d3 = Segment(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1)
|
||||
val d0 = Segment2D(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1)
|
||||
val d1 = Segment2D(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1)
|
||||
val d2 = Segment2D(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1)
|
||||
val d3 = Segment2D(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1)
|
||||
|
||||
return fromSegments<C>(d0, d1, d2, d3).transposed
|
||||
}
|
||||
|
||||
val contour: ShapeContour = ShapeContour(
|
||||
listOf(
|
||||
Segment(points[0][0], points[0][1], points[0][2], points[0][3]),
|
||||
Segment(points[0][3], points[1][3], points[2][3], points[3][3]),
|
||||
Segment(points[3][3], points[3][2], points[3][1], points[3][0]),
|
||||
Segment(points[3][0], points[2][0], points[1][0], points[0][0]),
|
||||
Segment2D(points[0][0], points[0][1], points[0][2], points[0][3]),
|
||||
Segment2D(points[0][3], points[1][3], points[2][3], points[3][3]),
|
||||
Segment2D(points[3][3], points[3][2], points[3][1], points[3][0]),
|
||||
Segment2D(points[3][0], points[2][0], points[1][0], points[0][0]),
|
||||
), true
|
||||
)
|
||||
|
||||
@@ -196,7 +196,7 @@ open class BezierPatchBase<C>(
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <C> fromSegments(c0: Segment, c1: Segment, c2: Segment, c3: Segment): BezierPatchBase<C>
|
||||
fun <C> fromSegments(c0: Segment2D, c1: Segment2D, c2: Segment2D, c3: Segment2D): BezierPatchBase<C>
|
||||
where C : AlgebraicColor<C>, C : ConvertibleToColorRGBa {
|
||||
val c0c = c0.cubic
|
||||
val c1c = c1.cubic
|
||||
@@ -219,7 +219,7 @@ class BezierPatch(points: List<List<Vector2>>, colors: List<List<ColorRGBa>> = e
|
||||
/**
|
||||
* Create a cubic bezier patch from 4 segments. The control points of the segments are used in row-wise fashion
|
||||
*/
|
||||
fun bezierPatch(c0: Segment, c1: Segment, c2: Segment, c3: Segment): BezierPatch {
|
||||
fun bezierPatch(c0: Segment2D, c1: Segment2D, c2: Segment2D, c3: Segment2D): BezierPatch {
|
||||
val c0c = c0.cubic
|
||||
val c1c = c1.cubic
|
||||
val c2c = c2.cubic
|
||||
@@ -289,7 +289,7 @@ fun BezierPatch.distort(shapeContour: ShapeContour, referenceRectangle: Rectangl
|
||||
val ns = position(s.x, s.y)
|
||||
val nc0 = position(c0.x, c0.y)
|
||||
val nc1 = position(c1.x, c1.y)
|
||||
Segment(ns, nc0, nc1, ne)
|
||||
Segment2D(ns, nc0, nc1, ne)
|
||||
}
|
||||
return ShapeContour(distortedSegments, shapeContour.closed, shapeContour.polarity)
|
||||
}
|
||||
|
||||
@@ -140,16 +140,16 @@ open class BezierPatch3DBase<C>(
|
||||
* Extract a sub-patch based on uv parameterization
|
||||
*/
|
||||
fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatch3DBase<C> {
|
||||
val c0 = Segment3D(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1)
|
||||
val c1 = Segment3D(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1)
|
||||
val c2 = Segment3D(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1)
|
||||
val c3 = Segment3D(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1)
|
||||
val c0 = Segment3D(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1) as Segment3D
|
||||
val c1 = Segment3D(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1) as Segment3D
|
||||
val c2 = Segment3D(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1) as Segment3D
|
||||
val c3 = Segment3D(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1) as Segment3D
|
||||
|
||||
val sub0 = bezierPatch(c0, c1, c2, c3)
|
||||
val d0 = Segment3D(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1)
|
||||
val d1 = Segment3D(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1)
|
||||
val d2 = Segment3D(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1)
|
||||
val d3 = Segment3D(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1)
|
||||
val d0 = Segment3D(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1) as Segment3D
|
||||
val d1 = Segment3D(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1) as Segment3D
|
||||
val d2 = Segment3D(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1) as Segment3D
|
||||
val d3 = Segment3D(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1) as Segment3D
|
||||
|
||||
return fromSegments<C>(d0, d1, d2, d3).transposed
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ fun ContourBlend(a: ShapeContour, b: ShapeContour): ContourBlend {
|
||||
val rb = b.rectified()
|
||||
val sa = ra.splitForBlend(rb)
|
||||
val sb = rb.splitForBlend(ra)
|
||||
require(sa.contour.segments.size == sb.contour.segments.size) {
|
||||
"preprocessing for contours failed to produce equal number of segments. ${sa.contour.segments.size}, ${sb.contour.segments.size}"
|
||||
require(sa.path.segments.size == sb.path.segments.size) {
|
||||
"preprocessing for contours failed to produce equal number of segments. ${sa.path.segments.size}, ${sb.path.segments.size}"
|
||||
}
|
||||
return ContourBlend(sa, sb)
|
||||
}
|
||||
37
orx-shapes/src/commonMain/kotlin/blend/Path3DBlend.kt
Normal file
37
orx-shapes/src/commonMain/kotlin/blend/Path3DBlend.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package org.openrndr.extra.shapes.blend
|
||||
|
||||
import org.openrndr.extra.shapes.rectify.RectifiedContour
|
||||
import org.openrndr.extra.shapes.rectify.RectifiedPath3D
|
||||
import org.openrndr.extra.shapes.rectify.rectified
|
||||
import org.openrndr.shape.Path3D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
|
||||
/**
|
||||
* ContourBlend holds two rectified contours with an equal amount of segments
|
||||
*/
|
||||
class Path3DBlend(val a: RectifiedPath3D, val b: RectifiedPath3D) {
|
||||
fun mix(blendFunction: (Double) -> Double): Path3D {
|
||||
return a.mix(b, blendFunction)
|
||||
}
|
||||
|
||||
fun mix(blend: Double): Path3D {
|
||||
return a.mix(b) { blend }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ContourBlend] for contours [a] and [b]
|
||||
*
|
||||
* Finding the pose that minimizes the error between [a] and [b] is not part of this function's work.
|
||||
*
|
||||
*/
|
||||
fun Path3DBlend(a: Path3D, b: Path3D): Path3DBlend {
|
||||
val ra = a.rectified()
|
||||
val rb = b.rectified()
|
||||
val sa = ra.splitForBlend(rb)
|
||||
val sb = rb.splitForBlend(ra)
|
||||
require(sa.path.segments.size == sb.path.segments.size) {
|
||||
"preprocessing for contours failed to produce equal number of segments. ${sa.path.segments.size}, ${sb.path.segments.size}"
|
||||
}
|
||||
return Path3DBlend(sa, sb)
|
||||
}
|
||||
@@ -3,28 +3,28 @@ package org.openrndr.extra.shapes.blend
|
||||
import org.openrndr.extra.shapes.rectify.RectifiedContour
|
||||
import org.openrndr.extra.shapes.rectify.rectified
|
||||
import org.openrndr.extra.shapes.utilities.fromContours
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
|
||||
/**
|
||||
* Split for blending with [other]
|
||||
*/
|
||||
fun RectifiedContour.splitForBlend(other: RectifiedContour): RectifiedContour {
|
||||
val ts = (0 until other.contour.segments.size + 1).map { it.toDouble() / other.contour.segments.size }
|
||||
val ts = (0 until other.path.segments.size + 1).map { it.toDouble() / other.path.segments.size }
|
||||
val rts = ts.map { other.inverseRectify(it) }
|
||||
|
||||
return ShapeContour.fromContours(splitAt(rts), contour.closed && other.contour.closed).rectified()
|
||||
return ShapeContour.fromContours(splitAt(rts), path.closed && other.path.closed).rectified()
|
||||
}
|
||||
|
||||
fun RectifiedContour.mix(other: RectifiedContour, blendFunction: (Double) -> Double): ShapeContour {
|
||||
val n = this.contour.segments.size.toDouble()
|
||||
val segs = (this.contour.segments zip other.contour.segments).mapIndexed { index, it ->
|
||||
val n = this.path.segments.size.toDouble()
|
||||
val segs = (this.path.segments zip other.path.segments).mapIndexed { index, it ->
|
||||
val t0 = inverseRectify(index / n)
|
||||
val t1 = inverseRectify((index + 1 / 3.0) / n)
|
||||
val t2 = inverseRectify((index + 2 / 3.0) / n)
|
||||
val t3 = inverseRectify((index + 1) / n)
|
||||
|
||||
it.first.mix(it.second, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3))
|
||||
(it.first as Segment2D).mix(it.second as Segment2D, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3))
|
||||
}
|
||||
return ShapeContour.fromSegments(segs, contour.closed && other.contour.closed)
|
||||
return ShapeContour.fromSegments(segs, path.closed && other.path.closed)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.openrndr.extra.shapes.blend
|
||||
|
||||
import org.openrndr.extra.shapes.rectify.RectifiedPath3D
|
||||
import org.openrndr.extra.shapes.rectify.rectified
|
||||
import org.openrndr.extra.shapes.utilities.fromPaths
|
||||
import org.openrndr.shape.Path3D
|
||||
import org.openrndr.shape.Segment3D
|
||||
|
||||
/**
|
||||
* Split for blending with [other]
|
||||
*/
|
||||
fun RectifiedPath3D.splitForBlend(other: RectifiedPath3D): RectifiedPath3D {
|
||||
val ts = (0 until other.path.segments.size + 1).map { it.toDouble() / other.path.segments.size }
|
||||
val rts = ts.map { other.inverseRectify(it) }
|
||||
|
||||
return Path3D.fromPaths(splitAt(rts), path.closed && other.path.closed).rectified()
|
||||
}
|
||||
|
||||
fun RectifiedPath3D.mix(other: RectifiedPath3D, blendFunction: (Double) -> Double): Path3D {
|
||||
val n = this.path.segments.size.toDouble()
|
||||
val segs = (this.path.segments zip other.path.segments).mapIndexed { index, it ->
|
||||
val t0 = inverseRectify(index / n)
|
||||
val t1 = inverseRectify((index + 1 / 3.0) / n)
|
||||
val t2 = inverseRectify((index + 2 / 3.0) / n)
|
||||
val t3 = inverseRectify((index + 1) / n)
|
||||
(it.first as Segment3D).mix(it.second as Segment3D, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3))
|
||||
}
|
||||
return Path3D.fromSegments(segs, path.closed && other.path.closed)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.openrndr.extra.shapes.blend
|
||||
|
||||
import org.openrndr.math.mix
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.Segment3D
|
||||
|
||||
|
||||
/**
|
||||
* Cubic segment mix
|
||||
@@ -11,14 +13,14 @@ import org.openrndr.shape.Segment
|
||||
* @param f2 the mix factor for the second control point
|
||||
* @param f3 the mix factor for the end point
|
||||
*/
|
||||
fun Segment.mix(other: Segment, f0: Double, f1: Double, f2: Double, f3: Double): Segment {
|
||||
fun Segment2D.mix(other: Segment2D, f0: Double, f1: Double, f2: Double, f3: Double): Segment2D {
|
||||
val ac = this.cubic
|
||||
val bc = other.cubic
|
||||
|
||||
val acc = if (ac.corner) 1.0 else 0.0
|
||||
val bcc = if (bc.corner) 1.0 else 0.0
|
||||
|
||||
return Segment(
|
||||
return Segment2D(
|
||||
ac.start.mix(bc.start, f0),
|
||||
ac.control[0].mix(bc.control[0], f1),
|
||||
ac.control[1].mix(bc.control[1], f2),
|
||||
@@ -26,3 +28,23 @@ fun Segment.mix(other: Segment, f0: Double, f1: Double, f2: Double, f3: Double):
|
||||
corner = mix(acc, bcc, f0) >= 0.5
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cubic segment mix
|
||||
* @param other the segment to mix with
|
||||
* @param f0 the mix factor for the start point
|
||||
* @param f1 the mix factor for the first control point
|
||||
* @param f2 the mix factor for the second control point
|
||||
* @param f3 the mix factor for the end point
|
||||
*/
|
||||
fun Segment3D.mix(other: Segment3D, f0: Double, f1: Double, f2: Double, f3: Double): Segment3D {
|
||||
val ac = this.cubic
|
||||
val bc = other.cubic
|
||||
|
||||
return Segment3D(
|
||||
ac.start.mix(bc.start, f0),
|
||||
ac.control[0].mix(bc.control[0], f1),
|
||||
ac.control[1].mix(bc.control[1], f2),
|
||||
ac.end.mix(bc.end, f3),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ val Iterable<Shape>.bounds : Rectangle
|
||||
/**
|
||||
* Evaluates the bounds around all [Segment] instances in the [Iterable]
|
||||
*/
|
||||
val Iterable<Segment>.bounds : Rectangle
|
||||
val Iterable<Segment2D>.bounds : Rectangle
|
||||
@JvmName("segmentBounds")
|
||||
get() = map {
|
||||
it.bounds
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.openrndr.extra.shapes.hobbycurve
|
||||
// Code adapted from http://weitz.de/hobby/
|
||||
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.Shape
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import kotlin.math.atan2
|
||||
@@ -109,7 +109,7 @@ fun hobbyCurve(points: List<Vector2>, closed: Boolean = false, curl: Double = 0.
|
||||
c2s.add(points[(i+1) % m] - v2 * rho(beta[i]!!, alpha[i]) * distances[i] / 3.0)
|
||||
}
|
||||
|
||||
return ShapeContour(List(n) { Segment(points[it], c1s[it], c2s[it], points[(it+1)%m]) }, closed=closed)
|
||||
return ShapeContour(List(n) { Segment2D(points[it], c1s[it], c2s[it], points[(it+1)%m]) }, closed=closed)
|
||||
}
|
||||
|
||||
private fun thomas(a: Array<Double>, b: Array<Double>, c: Array<Double>, d: Array<Double>): Array<Double> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import kotlin.math.sign
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
private fun Segment.splitOnExtrema(): List<Segment> {
|
||||
private fun Segment2D.splitOnExtrema(): List<Segment2D> {
|
||||
var extrema = extrema().toMutableList()
|
||||
|
||||
if (isStraight(0.05)) {
|
||||
@@ -40,10 +40,10 @@ private fun Segment.splitOnExtrema(): List<Segment> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Segment.splitToSimple(step: Double): List<Segment> {
|
||||
private fun Segment2D.splitToSimple(step: Double): List<Segment2D> {
|
||||
var t1 = 0.0
|
||||
var t2 = 0.0
|
||||
val result = mutableListOf<Segment>()
|
||||
val result = mutableListOf<Segment2D>()
|
||||
while (t2 <= 1.0) {
|
||||
t2 = t1 + step
|
||||
while (t2 <= 1.0 + step) {
|
||||
@@ -72,15 +72,15 @@ private fun Segment.splitToSimple(step: Double): List<Segment> {
|
||||
}
|
||||
|
||||
|
||||
fun Segment.reduced(stepSize: Double = 0.01): List<Segment> {
|
||||
fun Segment2D.reduced(stepSize: Double = 0.01): List<Segment2D> {
|
||||
val pass1 = splitOnExtrema()
|
||||
//return pass1
|
||||
return pass1.flatMap { it.splitToSimple(stepSize) }
|
||||
}
|
||||
|
||||
fun Segment.scale(scale: Double, polarity: YPolarity) = scale(polarity) { scale }
|
||||
fun Segment2D.scale(scale: Double, polarity: YPolarity) = scale(polarity) { scale }
|
||||
|
||||
fun Segment.scale(polarity: YPolarity, scale: (Double) -> Double): Segment {
|
||||
fun Segment2D.scale(polarity: YPolarity, scale: (Double) -> Double): Segment2D {
|
||||
if (control.size == 1) {
|
||||
return cubic.scale(polarity, scale)
|
||||
}
|
||||
@@ -111,19 +111,19 @@ fun Segment.scale(polarity: YPolarity, scale: (Double) -> Double): Segment {
|
||||
}
|
||||
}
|
||||
|
||||
fun Segment.offset(
|
||||
fun Segment2D.offset(
|
||||
distance: Double,
|
||||
stepSize: Double = 0.01,
|
||||
yPolarity: YPolarity = YPolarity.CW_NEGATIVE_Y
|
||||
): List<Segment> {
|
||||
): List<Segment2D> {
|
||||
return if (linear) {
|
||||
val n = normal(0.0, yPolarity)
|
||||
if (distance > 0.0) {
|
||||
listOf(Segment(start + distance * n, end + distance * n))
|
||||
listOf(Segment2D(start + distance * n, end + distance * n))
|
||||
} else {
|
||||
val d = direction()
|
||||
val s = distance.coerceAtMost(length / 2.0)
|
||||
val candidate = Segment(
|
||||
val candidate = Segment2D(
|
||||
start - s * d + distance * n,
|
||||
end + s * d + distance * n
|
||||
)
|
||||
@@ -226,12 +226,12 @@ fun ShapeContour.offset(distance: Double, joinType: SegmentJoin = SegmentJoin.RO
|
||||
var final = candidateContour.removeLoops()
|
||||
|
||||
if (postProc && !final.empty) {
|
||||
val head = Segment(
|
||||
val head = Segment2D(
|
||||
segments[0].start + segments[0].normal(0.0)
|
||||
.perpendicular(polarity) * 1000.0, segments[0].start
|
||||
).offset(distance).firstOrNull()?.copy(end = final.segments[0].start)?.contour
|
||||
|
||||
val tail = Segment(
|
||||
val tail = Segment2D(
|
||||
segments.last().end,
|
||||
segments.last().end - segments.last().normal(1.0)
|
||||
.perpendicular(polarity) * 1000.0
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package org.openrndr.extra.shapes.operators
|
||||
|
||||
import org.openrndr.math.mod_
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import org.openrndr.shape.contour
|
||||
|
||||
fun ShapeContour.bulgeSegments(distortion: (index: Int, segment: Segment) -> Double): ShapeContour {
|
||||
fun ShapeContour.bulgeSegments(distortion: (index: Int, segment: Segment2D) -> Double): ShapeContour {
|
||||
val c = contour {
|
||||
moveTo(position(0.0))
|
||||
var index = 0
|
||||
|
||||
@@ -7,15 +7,15 @@ import kotlin.math.abs
|
||||
import kotlin.math.sign
|
||||
import kotlin.math.sqrt
|
||||
|
||||
private fun Segment.linearSub(l0: Double, l1: Double): Segment {
|
||||
private fun Segment2D.linearSub(l0: Double, l1: Double): Segment2D {
|
||||
return sub(l0 / length, l1 / length)
|
||||
}
|
||||
|
||||
private fun Segment.linearPosition(l: Double): Vector2 {
|
||||
private fun Segment2D.linearPosition(l: Double): Vector2 {
|
||||
return position((l / length).coerceIn(0.0, 1.0))
|
||||
}
|
||||
|
||||
private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment, s1: Segment): Double {
|
||||
private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment2D, s1: Segment2D): Double {
|
||||
val p3 = s1.end
|
||||
val p2 = s0.end
|
||||
val p1 = s0.start
|
||||
@@ -36,8 +36,8 @@ private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment, s1:
|
||||
* @param chamfer the chamfer function to apply
|
||||
*/
|
||||
fun ShapeContour.chamferCorners(
|
||||
lengths: (index: Int, left: Segment, right: Segment) -> Double,
|
||||
expands: (index: Int, left: Segment, right: Segment) -> Double = { _, _, _ -> 0.0 },
|
||||
lengths: (index: Int, left: Segment2D, right: Segment2D) -> Double,
|
||||
expands: (index: Int, left: Segment2D, right: Segment2D) -> Double = { _, _, _ -> 0.0 },
|
||||
clip: Boolean = true,
|
||||
angleThreshold: Double = 180.0,
|
||||
chamfer: ContourBuilder.(p1: Vector2, p2: Vector2, p3: Vector2) -> Unit
|
||||
|
||||
10
orx-shapes/src/commonMain/kotlin/rectify/Path3DExtensions.kt
Normal file
10
orx-shapes/src/commonMain/kotlin/rectify/Path3DExtensions.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package org.openrndr.extra.shapes.rectify
|
||||
|
||||
import org.openrndr.shape.Path3D
|
||||
|
||||
/** create a rectified contour
|
||||
* @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance
|
||||
* @param lengthScale used to compute the size of the LUT, default value is 1.0
|
||||
**/
|
||||
fun Path3D.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedPath3D =
|
||||
RectifiedPath3D(this, distanceTolerance, lengthScale)
|
||||
@@ -1,152 +1,53 @@
|
||||
package org.openrndr.extra.shapes.rectify
|
||||
|
||||
import org.openrndr.extra.shapes.utilities.splitAt
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.clamp
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* RectifiedContour provides an approximately uniform parameterization for [ShapeContour]
|
||||
*/
|
||||
class RectifiedContour(val contour: ShapeContour, distanceTolerance: Double = 0.5, lengthScale: Double = 1.0) {
|
||||
val points =
|
||||
contour.equidistantPositionsWithT((contour.length * lengthScale).toInt().coerceAtLeast(2), distanceTolerance)
|
||||
|
||||
val intervals by lazy {
|
||||
points.zipWithNext().map {
|
||||
Pair(it.first.second, it.second.second)
|
||||
}
|
||||
}
|
||||
|
||||
private fun safe(t: Double): Double {
|
||||
return if (contour.closed) {
|
||||
t.mod(1.0)
|
||||
} else {
|
||||
t.clamp(0.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* computes a rectified t-value for [contour]
|
||||
*/
|
||||
fun rectify(t: Double): Double {
|
||||
if (contour.empty) {
|
||||
return 0.0
|
||||
} else {
|
||||
if (t <= 0.0) {
|
||||
return 0.0
|
||||
}
|
||||
val fi = t * (points.size - 1.0)
|
||||
val fr = fi.mod(1.0)
|
||||
val i0 = fi.toInt()
|
||||
val i1 = i0 + 1
|
||||
|
||||
return if (i0 >= points.size - 1) {
|
||||
1.0
|
||||
} else {
|
||||
(points[i0].second * (1.0 - fr) + points[i1].second * fr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inverseRectify(t: Double): Double {
|
||||
if (contour.empty) {
|
||||
return 0.0
|
||||
} else {
|
||||
if (t <= 0.0) {
|
||||
return 0.0
|
||||
} else if (t >= 1.0) {
|
||||
return 1.0
|
||||
} else {
|
||||
val index = intervals.binarySearch {
|
||||
if (t < it.first) {
|
||||
1
|
||||
} else if (t > it.second) {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
val t0 = t - intervals[index].first
|
||||
val dt = intervals[index].second - intervals[index].first
|
||||
val f = t0 / dt
|
||||
val f0 = index.toDouble() / intervals.size
|
||||
val f1 = (index + 1.0) / intervals.size
|
||||
|
||||
return f0 * (1.0 - f) + f1 * f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun position(t: Double): Vector2 {
|
||||
return if (contour.empty) {
|
||||
Vector2.INFINITY
|
||||
} else {
|
||||
contour.position(rectify(safe(t)))
|
||||
}
|
||||
}
|
||||
|
||||
class RectifiedContour(contour: ShapeContour, distanceTolerance: Double = 0.5, lengthScale: Double = 1.0) :
|
||||
RectifiedPath<Vector2>(contour, distanceTolerance, lengthScale) {
|
||||
fun velocity(t: Double): Vector2 {
|
||||
return if (contour.empty) {
|
||||
return if (path.empty) {
|
||||
Vector2.ZERO
|
||||
} else {
|
||||
val (segment, st) = contour.segment(rectify(safe(t)))
|
||||
contour.segments[segment].direction(st)
|
||||
val (segment, st) = path.segment(rectify(safe(t)))
|
||||
path.segments[segment].direction(st)
|
||||
}
|
||||
}
|
||||
|
||||
fun normal(t: Double): Vector2 {
|
||||
return if (contour.empty) {
|
||||
return if (path.empty) {
|
||||
Vector2.UNIT_Y
|
||||
} else {
|
||||
contour.normal(rectify(safe(t)))
|
||||
(path as ShapeContour).normal(rectify(safe(t)))
|
||||
}
|
||||
}
|
||||
|
||||
fun pose(t: Double): Matrix44 {
|
||||
return if (contour.empty) {
|
||||
path as ShapeContour
|
||||
return if (path.empty) {
|
||||
Matrix44.IDENTITY
|
||||
} else {
|
||||
contour.pose(rectify(safe(t)))
|
||||
path.pose(rectify(safe(t)))
|
||||
}
|
||||
}
|
||||
|
||||
fun sub(t0: Double, t1: Double): ShapeContour {
|
||||
if (contour.empty) {
|
||||
override fun sub(t0: Double, t1: Double): ShapeContour {
|
||||
path as ShapeContour
|
||||
if (path.empty) {
|
||||
return ShapeContour.EMPTY
|
||||
}
|
||||
return if (contour.closed) {
|
||||
contour.sub(rectify(t0.mod(1.0)) + floor(t0), rectify(t1.mod(1.0)) + floor(t1))
|
||||
|
||||
return if (path.closed) {
|
||||
path.sub(rectify(t0.mod(1.0)) + floor(t0), rectify(t1.mod(1.0)) + floor(t1))
|
||||
} else {
|
||||
contour.sub(rectify(t0), rectify(t1))
|
||||
path.sub(rectify(t0), rectify(t1))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split contour at [ascendingTs]
|
||||
* @since orx 0.4.4
|
||||
*/
|
||||
fun splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<ShapeContour> {
|
||||
return contour.splitAt(ascendingTs.map { rectify(it) }, weldEpsilon)
|
||||
override fun splitAt(ascendingTs: List<Double>, weldEpsilon: Double): List<ShapeContour> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return super.splitAt(ascendingTs, weldEpsilon) as List<ShapeContour>
|
||||
}
|
||||
}
|
||||
|
||||
/** create a rectified contour
|
||||
* @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance
|
||||
* @param lengthScale used to compute the size of the LUT, default value is 1.0
|
||||
**/
|
||||
fun ShapeContour.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour {
|
||||
return RectifiedContour(this, distanceTolerance, lengthScale)
|
||||
}
|
||||
|
||||
/** create a rectified contour
|
||||
* @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance
|
||||
* @param lengthScale used to compute the size of the LUT, default value is 1.0
|
||||
*
|
||||
* */
|
||||
fun Segment.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour {
|
||||
return RectifiedContour(this.contour, distanceTolerance, lengthScale)
|
||||
}
|
||||
104
orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath.kt
Normal file
104
orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath.kt
Normal file
@@ -0,0 +1,104 @@
|
||||
package org.openrndr.extra.shapes.rectify
|
||||
|
||||
import org.openrndr.extra.shapes.utilities.splitAt
|
||||
import org.openrndr.extra.shapes.utilities.splitAtBase
|
||||
import org.openrndr.math.EuclideanVector
|
||||
import org.openrndr.math.clamp
|
||||
import org.openrndr.shape.Path
|
||||
import org.openrndr.shape.ShapeContour
|
||||
|
||||
/**
|
||||
* RectifiedContour provides an approximately uniform parameterization for [ShapeContour]
|
||||
*/
|
||||
abstract class RectifiedPath<T : EuclideanVector<T>>(
|
||||
val path: Path<T>,
|
||||
distanceTolerance: Double = 0.5,
|
||||
lengthScale: Double = 1.0
|
||||
) {
|
||||
val points =
|
||||
path.equidistantPositionsWithT((path.length * lengthScale).toInt().coerceAtLeast(2), distanceTolerance)
|
||||
|
||||
val intervals by lazy {
|
||||
points.zipWithNext().map {
|
||||
Pair(it.first.second, it.second.second)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun safe(t: Double): Double {
|
||||
return if (path.closed) {
|
||||
t.mod(1.0)
|
||||
} else {
|
||||
t.clamp(0.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* computes a rectified t-value for [path]
|
||||
*/
|
||||
fun rectify(t: Double): Double {
|
||||
if (path.empty) {
|
||||
return 0.0
|
||||
} else {
|
||||
if (t <= 0.0) {
|
||||
return 0.0
|
||||
}
|
||||
val fi = t * (points.size - 1.0)
|
||||
val fr = fi.mod(1.0)
|
||||
val i0 = fi.toInt()
|
||||
val i1 = i0 + 1
|
||||
|
||||
return if (i0 >= points.size - 1) {
|
||||
1.0
|
||||
} else {
|
||||
(points[i0].second * (1.0 - fr) + points[i1].second * fr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inverseRectify(t: Double): Double {
|
||||
if (path.empty) {
|
||||
return 0.0
|
||||
} else {
|
||||
if (t <= 0.0) {
|
||||
return 0.0
|
||||
} else if (t >= 1.0) {
|
||||
return 1.0
|
||||
} else {
|
||||
val index = intervals.binarySearch {
|
||||
if (t < it.first) {
|
||||
1
|
||||
} else if (t > it.second) {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
val t0 = t - intervals[index].first
|
||||
val dt = intervals[index].second - intervals[index].first
|
||||
val f = t0 / dt
|
||||
val f0 = index.toDouble() / intervals.size
|
||||
val f1 = (index + 1.0) / intervals.size
|
||||
|
||||
return f0 * (1.0 - f) + f1 * f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun position(t: Double): T {
|
||||
return if (path.empty) {
|
||||
path.infinity
|
||||
} else {
|
||||
path.position(rectify(safe(t)))
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun sub(t0: Double, t1: Double): Path<T>
|
||||
|
||||
/**
|
||||
* Split contour at [ascendingTs]
|
||||
* @since orx 0.4.4
|
||||
*/
|
||||
open fun splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<Path<T>> {
|
||||
return path.splitAtBase(ascendingTs.map { rectify(it) }, weldEpsilon)
|
||||
}
|
||||
}
|
||||
27
orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath3D.kt
Normal file
27
orx-shapes/src/commonMain/kotlin/rectify/RectifiedPath3D.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.openrndr.extra.shapes.rectify
|
||||
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.shape.Path3D
|
||||
import kotlin.math.floor
|
||||
|
||||
class RectifiedPath3D(contour: Path3D, distanceTolerance: Double = 0.5, lengthScale: Double = 1.0) :
|
||||
RectifiedPath<Vector3>(contour, distanceTolerance, lengthScale) {
|
||||
|
||||
override fun sub(t0: Double, t1: Double): Path3D {
|
||||
path as Path3D
|
||||
if (path.empty) {
|
||||
return Path3D(emptyList(), false)
|
||||
}
|
||||
|
||||
return if (path.closed) {
|
||||
path.sub(rectify(t0.mod(1.0)) + floor(t0), rectify(t1.mod(1.0)) + floor(t1))
|
||||
} else {
|
||||
path.sub(rectify(t0), rectify(t1))
|
||||
}
|
||||
}
|
||||
|
||||
override fun splitAt(ascendingTs: List<Double>, weldEpsilon: Double): List<Path3D> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return super.splitAt(ascendingTs, weldEpsilon) as List<Path3D>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.openrndr.extra.shapes.rectify
|
||||
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
|
||||
/** create a rectified contour
|
||||
* @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance
|
||||
* @param lengthScale used to compute the size of the LUT, default value is 1.0
|
||||
**/
|
||||
fun ShapeContour.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour {
|
||||
return RectifiedContour(this, distanceTolerance, lengthScale)
|
||||
}
|
||||
|
||||
/** create a rectified contour
|
||||
* @param distanceTolerance distance tolerance to use, 0.5 is the default distance tolerance
|
||||
* @param lengthScale used to compute the size of the LUT, default value is 1.0
|
||||
*
|
||||
* */
|
||||
fun Segment2D.rectified(distanceTolerance: Double = 0.5, lengthScale: Double = 1.0): RectifiedContour {
|
||||
return RectifiedContour(this.contour, distanceTolerance, lengthScale)
|
||||
}
|
||||
@@ -287,7 +287,7 @@ fun List<Vector3>.catmullRom(alpha: Double = 0.5, closed: Boolean) = CatmullRomC
|
||||
|
||||
|
||||
/** Converts spline to a [Segment]. */
|
||||
fun CatmullRom2.toSegment(): Segment {
|
||||
fun CatmullRom2.toSegment(): Segment2D {
|
||||
val d1a2 = (p1 - p0).length.pow(2 * alpha)
|
||||
val d2a2 = (p2 - p1).length.pow(2 * alpha)
|
||||
val d3a2 = (p3 - p2).length.pow(2 * alpha)
|
||||
@@ -300,7 +300,7 @@ fun CatmullRom2.toSegment(): Segment {
|
||||
val b2 = (p1 * d3a2 - p3 * d2a2 + p2 * (2 * d3a2 + 3 * d3a * d2a + d2a2)) / (3 * d3a * (d3a + d2a))
|
||||
val b3 = p2
|
||||
|
||||
return Segment(b0, b1, b2, b3)
|
||||
return Segment2D(b0, b1, b2, b3)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ package org.openrndr.extra.shapes.tunni
|
||||
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.shape.LineSegment
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.intersection
|
||||
|
||||
/**
|
||||
* Find the Tunni point for the [Segment]
|
||||
* @since orx 0.4.5
|
||||
*/
|
||||
val Segment.tunniPoint: Vector2
|
||||
val Segment2D.tunniPoint: Vector2
|
||||
get() {
|
||||
val c = this.cubic
|
||||
val ac = LineSegment(c.start, c.control[0])
|
||||
@@ -23,7 +23,7 @@ val Segment.tunniPoint: Vector2
|
||||
* Find the Tunni line for the [Segment]
|
||||
* @since orx 0.4.5
|
||||
*/
|
||||
val Segment.tunniLine: LineSegment
|
||||
val Segment2D.tunniLine: LineSegment
|
||||
get() {
|
||||
val c = this.cubic
|
||||
return LineSegment(c.control[0], c.control[1])
|
||||
@@ -33,7 +33,7 @@ val Segment.tunniLine: LineSegment
|
||||
* Find a new segment that has [tunniPoint] as its Tunni-point
|
||||
* @since orx 0.4.5
|
||||
*/
|
||||
fun Segment.withTunniPoint(tunniPoint: Vector2): Segment {
|
||||
fun Segment2D.withTunniPoint(tunniPoint: Vector2): Segment2D {
|
||||
val ha = (start + tunniPoint) / 2.0
|
||||
val hb = (end + tunniPoint) / 2.0
|
||||
val hpa = ha + this.cubic.control[1] - end
|
||||
@@ -57,7 +57,7 @@ fun Segment.withTunniPoint(tunniPoint: Vector2): Segment {
|
||||
* Find a segment for which [pointOnLine] lies on its Tunni-line
|
||||
* @since orx 0.4.5
|
||||
*/
|
||||
fun Segment.withTunniLine(pointOnLine: Vector2): Segment {
|
||||
fun Segment2D.withTunniLine(pointOnLine: Vector2): Segment2D {
|
||||
val ls = LineSegment(pointOnLine, pointOnLine + this.cubic.control[0] - this.cubic.control[1])
|
||||
val ac0 = LineSegment(start, this.cubic.control[0])
|
||||
val bc1 = LineSegment(end, this.cubic.control[1])
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.openrndr.extra.shapes.adjust.ContourAdjusterEdge
|
||||
import org.openrndr.extra.shapes.adjust.ContourEdge
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.shape.LineSegment
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.ShapeContour
|
||||
|
||||
|
||||
|
||||
31
orx-shapes/src/commonMain/kotlin/utilities/FromPaths.kt
Normal file
31
orx-shapes/src/commonMain/kotlin/utilities/FromPaths.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
package org.openrndr.extra.shapes.utilities
|
||||
|
||||
import org.openrndr.shape.Path3D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import org.openrndr.shape.contour
|
||||
import org.openrndr.shape.path3D
|
||||
|
||||
/**
|
||||
* Create a [Path3D] from a list of paths
|
||||
*/
|
||||
fun Path3D.Companion.fromPaths(contours: List<Path3D>, closed: Boolean, connectEpsilon:Double=1E-6) : Path3D {
|
||||
@Suppress("NAME_SHADOWING") val contours = contours.filter { !it.empty }
|
||||
if (contours.isEmpty()) {
|
||||
return EMPTY
|
||||
}
|
||||
return path3D {
|
||||
moveTo(contours.first().position(0.0))
|
||||
for (c in contours.windowed(2,1,true)) {
|
||||
copy(c[0])
|
||||
if (c.size == 2) {
|
||||
val d = c[0].position(1.0).distanceTo(c[1].position(0.0))
|
||||
if (d > connectEpsilon ) {
|
||||
lineTo(c[1].position(0.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closed) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
package org.openrndr.extra.shapes.utilities
|
||||
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.ShapeContour
|
||||
import org.openrndr.math.EuclideanVector
|
||||
import org.openrndr.shape.*
|
||||
|
||||
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> {
|
||||
fun Path3D.splitAt(segmentIndex: Double, segmentT: Double): List<Path3D> {
|
||||
val t = (1.0 / segments.size) * (segmentIndex + segmentT)
|
||||
return splitAt(listOf(t))
|
||||
}
|
||||
|
||||
|
||||
fun <T : EuclideanVector<T>> Path<T>.splitAtBase(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<Path<T>> {
|
||||
if (empty || ascendingTs.isEmpty()) {
|
||||
return listOf(this)
|
||||
}
|
||||
@@ -18,7 +24,20 @@ fun ShapeContour.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6):
|
||||
}
|
||||
}
|
||||
|
||||
fun Segment.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<Segment> {
|
||||
fun ShapeContour.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<ShapeContour> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return splitAtBase(ascendingTs, weldEpsilon) as List<ShapeContour>
|
||||
}
|
||||
|
||||
fun Path3D.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<Path3D> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return splitAtBase(ascendingTs, weldEpsilon) as List<Path3D>
|
||||
}
|
||||
|
||||
fun <T : EuclideanVector<T>> BezierSegment<T>.splitAtBase(
|
||||
ascendingTs: List<Double>,
|
||||
weldEpsilon: Double = 1E-6
|
||||
): List<BezierSegment<T>> {
|
||||
if (ascendingTs.isEmpty()) {
|
||||
return listOf(this)
|
||||
}
|
||||
@@ -28,3 +47,15 @@ fun Segment.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List
|
||||
sub(it[0], it[1])
|
||||
}
|
||||
}
|
||||
|
||||
fun Segment2D.splitAt(ascendingTs: List<Double>,
|
||||
weldEpsilon: Double = 1E-6) : List<Segment2D> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return splitAtBase(ascendingTs, weldEpsilon) as List<Segment2D>
|
||||
}
|
||||
|
||||
fun Segment3D.splitAt(ascendingTs: List<Double>,
|
||||
weldEpsilon: Double = 1E-6) : List<Segment3D> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return splitAtBase(ascendingTs, weldEpsilon) as List<Segment3D>
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package rectify
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.extra.noise.scatter
|
||||
import org.openrndr.extra.shapes.hobbycurve.hobbyCurve
|
||||
import org.openrndr.extra.shapes.rectify.rectified
|
||||
import kotlin.random.Random
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
configure {
|
||||
width = 720
|
||||
height = 720
|
||||
}
|
||||
program {
|
||||
val points = drawer.bounds.scatter(80.0, distanceToEdge = 100.0, random = Random(0))
|
||||
val curve = hobbyCurve(points, closed = true)
|
||||
val rectified = curve.rectified()
|
||||
extend {
|
||||
drawer.clear(ColorRGBa.BLACK)
|
||||
drawer.fill = null
|
||||
drawer.stroke = ColorRGBa.GRAY
|
||||
drawer.contour(curve)
|
||||
|
||||
val points = (0 until 100).map {
|
||||
rectified.position(it/100.0)
|
||||
}
|
||||
drawer.circles(points, 5.0)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package rectify
|
||||
|
||||
import org.openrndr.WindowMultisample
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.DrawPrimitive
|
||||
import org.openrndr.draw.isolated
|
||||
import org.openrndr.extra.camera.Orbital
|
||||
import org.openrndr.extra.meshgenerators.sphereMesh
|
||||
import org.openrndr.extra.noise.uniformRing
|
||||
import org.openrndr.extra.shapes.rectify.rectified
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.shape.path3D
|
||||
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
configure {
|
||||
width = 720
|
||||
height = 720
|
||||
multisample = WindowMultisample.SampleCount(4)
|
||||
}
|
||||
program {
|
||||
val p = path3D {
|
||||
moveTo(0.0, 0.0, 0.0)
|
||||
for (i in 0 until 10) {
|
||||
curveTo(
|
||||
Vector3.uniformRing(0.1, 1.0)*10.0,
|
||||
Vector3.uniformRing(0.1, 1.0)*10.0,
|
||||
Vector3.uniformRing(0.1, 1.0)*10.0
|
||||
)
|
||||
}
|
||||
}
|
||||
val pr = p.rectified(0.01, 100.0)
|
||||
val sphere = sphereMesh(radius = 0.1)
|
||||
extend(Orbital())
|
||||
extend {
|
||||
drawer.stroke = ColorRGBa.PINK
|
||||
for (i in 0 until 500) {
|
||||
drawer.isolated {
|
||||
drawer.translate(pr.position(i/499.0))
|
||||
drawer.vertexBuffer(sphere, DrawPrimitive.TRIANGLES)
|
||||
}
|
||||
}
|
||||
drawer.path(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
var prevQuadCtrlPoint: Vector2? = null
|
||||
|
||||
val contours = compounds().map { compound ->
|
||||
val segments = mutableListOf<Segment>()
|
||||
val segments = mutableListOf<Segment2D>()
|
||||
var closed = false
|
||||
// If an argument is invalid, an error is logged,
|
||||
// further interpreting is stopped and compound is returned as-is.
|
||||
@@ -207,7 +207,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
|
||||
// Following points are implicit lineto arguments
|
||||
segments += points.drop(1).map {
|
||||
Segment(cursor, it).apply {
|
||||
Segment2D(cursor, it).apply {
|
||||
cursor = it
|
||||
}
|
||||
}
|
||||
@@ -219,21 +219,21 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
|
||||
// Following points are implicit lineto arguments
|
||||
segments += points.drop(1).map {
|
||||
Segment(cursor, cursor + it).apply {
|
||||
Segment2D(cursor, cursor + it).apply {
|
||||
cursor += it
|
||||
}
|
||||
}
|
||||
}
|
||||
"L" -> {
|
||||
segments += points!!.map {
|
||||
Segment(cursor, it).apply {
|
||||
Segment2D(cursor, it).apply {
|
||||
cursor = it
|
||||
}
|
||||
}
|
||||
}
|
||||
"l" -> {
|
||||
segments += points!!.map {
|
||||
Segment(cursor, cursor + it).apply {
|
||||
Segment2D(cursor, cursor + it).apply {
|
||||
cursor += it
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
"H" -> {
|
||||
segments += command.operands.map {
|
||||
val target = Vector2(it, cursor.y)
|
||||
Segment(cursor, target).apply {
|
||||
Segment2D(cursor, target).apply {
|
||||
cursor = target
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
"h" -> {
|
||||
segments += command.operands.map {
|
||||
val target = cursor + Vector2(it, 0.0)
|
||||
Segment(cursor, target).apply {
|
||||
Segment2D(cursor, target).apply {
|
||||
cursor = target
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
"V" -> {
|
||||
segments += command.operands.map {
|
||||
val target = Vector2(cursor.x, it)
|
||||
Segment(cursor, target).apply {
|
||||
Segment2D(cursor, target).apply {
|
||||
cursor = target
|
||||
}
|
||||
}
|
||||
@@ -265,7 +265,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
"v" -> {
|
||||
segments += command.operands.map {
|
||||
val target = cursor + Vector2(0.0, it)
|
||||
Segment(cursor, target).apply {
|
||||
Segment2D(cursor, target).apply {
|
||||
cursor = target
|
||||
}
|
||||
}
|
||||
@@ -277,7 +277,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
return@forEach
|
||||
} else {
|
||||
val (cp1, cp2, target) = it
|
||||
Segment(cursor, cp1, cp2, target).also {
|
||||
Segment2D(cursor, cp1, cp2, target).also {
|
||||
cursor = target
|
||||
prevCubicCtrlPoint = cp2
|
||||
}
|
||||
@@ -291,7 +291,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
return@forEach
|
||||
} else {
|
||||
val (cp1, cp2, target) = it.map { v -> cursor + v }
|
||||
Segment(cursor, cp1, cp2, target).apply {
|
||||
Segment2D(cursor, cp1, cp2, target).apply {
|
||||
cursor = target
|
||||
prevCubicCtrlPoint = cp2
|
||||
}
|
||||
@@ -306,7 +306,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
} else {
|
||||
val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor)
|
||||
val (cp2, target) = it
|
||||
Segment(cursor, cp1, cp2, target).also {
|
||||
Segment2D(cursor, cp1, cp2, target).also {
|
||||
cursor = target
|
||||
prevCubicCtrlPoint = cp2
|
||||
}
|
||||
@@ -321,7 +321,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
} else {
|
||||
val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor)
|
||||
val (cp2, target) = it.map { v -> cursor + v }
|
||||
Segment(cursor, cp1, cp2, target).also {
|
||||
Segment2D(cursor, cp1, cp2, target).also {
|
||||
cursor = target
|
||||
prevCubicCtrlPoint = cp2
|
||||
}
|
||||
@@ -335,7 +335,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
return@forEach
|
||||
} else {
|
||||
val (cp, target) = it
|
||||
Segment(cursor, cp, target).also {
|
||||
Segment2D(cursor, cp, target).also {
|
||||
cursor = target
|
||||
prevQuadCtrlPoint = cp
|
||||
}
|
||||
@@ -349,7 +349,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
return@forEach
|
||||
} else {
|
||||
val (cp, target) = it.map { v -> cursor + v }
|
||||
Segment(cursor, cp, target).also {
|
||||
Segment2D(cursor, cp, target).also {
|
||||
cursor = target
|
||||
prevQuadCtrlPoint = cp
|
||||
}
|
||||
@@ -359,7 +359,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
"T" -> {
|
||||
points!!.forEach {
|
||||
val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor)
|
||||
Segment(cursor, cp, it).also { _ ->
|
||||
Segment2D(cursor, cp, it).also { _ ->
|
||||
cursor = it
|
||||
prevQuadCtrlPoint = cp
|
||||
}
|
||||
@@ -368,7 +368,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
"t" -> {
|
||||
points!!.forEach {
|
||||
val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor)
|
||||
Segment(cursor, cp, cursor + it).also { _ ->
|
||||
Segment2D(cursor, cp, cursor + it).also { _ ->
|
||||
cursor = it
|
||||
prevQuadCtrlPoint = cp
|
||||
}
|
||||
@@ -376,7 +376,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
||||
}
|
||||
"Z", "z" -> {
|
||||
if ((cursor - anchor).length >= 0.001) {
|
||||
segments += Segment(cursor, anchor)
|
||||
segments += Segment2D(cursor, anchor)
|
||||
}
|
||||
cursor = anchor
|
||||
closed = true
|
||||
|
||||
@@ -4,7 +4,6 @@ plugins {
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(libs.openrndr.math)
|
||||
@@ -12,12 +11,18 @@ kotlin {
|
||||
implementation(project(":orx-noise"))
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(project(":orx-shapes"))
|
||||
implementation(libs.openrndr.shape)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val jvmDemo by getting {
|
||||
dependencies {
|
||||
implementation(project(":orx-shapes"))
|
||||
implementation(project(":orx-noise"))
|
||||
implementation(libs.openrndr.shape)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import org.openrndr.extra.triangulation.Delaunay
|
||||
import org.openrndr.shape.Circle
|
||||
import org.openrndr.shape.Rectangle
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.openrndr.extra.turtle
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.Vector4
|
||||
import org.openrndr.math.transforms.buildTransform
|
||||
import org.openrndr.shape.Segment
|
||||
import org.openrndr.shape.Segment2D
|
||||
import org.openrndr.shape.ShapeContour
|
||||
|
||||
fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) {
|
||||
@@ -16,7 +16,7 @@ fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) {
|
||||
}
|
||||
|
||||
fun Turtle.segment(
|
||||
segment: Segment,
|
||||
segment: Segment2D,
|
||||
alignTangent: Boolean = true,
|
||||
externalAlignTransform: Matrix44 = Matrix44.IDENTITY
|
||||
): Matrix44 {
|
||||
|
||||
Reference in New Issue
Block a user