Update for OPENRNDR segment and path generalizations
This commit is contained in:
@@ -570,28 +570,28 @@ class CompositionDrawer(documentBounds: CompositionDimensions = defaultCompositi
|
|||||||
c1: Vector2,
|
c1: Vector2,
|
||||||
end: Vector2,
|
end: Vector2,
|
||||||
insert: Boolean = true
|
insert: Boolean = true
|
||||||
) = segment(Segment(start, c0, c1, end), insert)
|
) = segment(Segment2D(start, c0, c1, end), insert)
|
||||||
|
|
||||||
fun segment(
|
fun segment(
|
||||||
start: Vector2,
|
start: Vector2,
|
||||||
c0: Vector2,
|
c0: Vector2,
|
||||||
end: Vector2,
|
end: Vector2,
|
||||||
insert: Boolean = true
|
insert: Boolean = true
|
||||||
) = segment(Segment(start, c0, end), insert)
|
) = segment(Segment2D(start, c0, end), insert)
|
||||||
|
|
||||||
fun segment(
|
fun segment(
|
||||||
start: Vector2,
|
start: Vector2,
|
||||||
end: Vector2,
|
end: Vector2,
|
||||||
insert: Boolean = true
|
insert: Boolean = true
|
||||||
) = segment(Segment(start, end), insert)
|
) = segment(Segment2D(start, end), insert)
|
||||||
|
|
||||||
fun segment(
|
fun segment(
|
||||||
segment: Segment,
|
segment: Segment2D,
|
||||||
insert: Boolean = true
|
insert: Boolean = true
|
||||||
) = contour(segment.contour, insert)
|
) = contour(segment.contour, insert)
|
||||||
|
|
||||||
fun segments(
|
fun segments(
|
||||||
segments: List<Segment>,
|
segments: List<Segment2D>,
|
||||||
insert: Boolean = true
|
insert: Boolean = true
|
||||||
) = segments.map {
|
) = segments.map {
|
||||||
segment(it, insert)
|
segment(it, insert)
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package org.openrndr.extra.fcurve
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.math.transforms.buildTransform
|
import org.openrndr.math.transforms.buildTransform
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the (first) t value for a given [x] value
|
* 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) {
|
if (linear) {
|
||||||
return (x - start.x) / (end.x - start.x)
|
return (x - start.x) / (end.x - start.x)
|
||||||
} else {
|
} else {
|
||||||
@@ -29,7 +29,7 @@ private fun Segment.tForX(x: Double): Double {
|
|||||||
/**
|
/**
|
||||||
* Find the y value for a given [x] value
|
* 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)
|
val t = tForX(x)
|
||||||
return position(t).y
|
return position(t).y
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ private fun Segment.yForX(x: Double): Double {
|
|||||||
/**
|
/**
|
||||||
* Scale tangents such that tangent lines do not overlap
|
* 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) {
|
if (linear) {
|
||||||
return this
|
return this
|
||||||
} else {
|
} else {
|
||||||
@@ -74,7 +74,7 @@ fun Segment.scaleTangents(axis: Vector2 = Vector2.UNIT_X): Segment {
|
|||||||
* Fcurve class
|
* Fcurve class
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FCurve(val segments: List<Segment>) {
|
data class FCurve(val segments: List<Segment2D>) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse the fcurve
|
* Reverse the fcurve
|
||||||
@@ -105,7 +105,7 @@ data class FCurve(val segments: List<Segment>) {
|
|||||||
* Create a sampler or function from the Fcurve
|
* Create a sampler or function from the Fcurve
|
||||||
*/
|
*/
|
||||||
fun sampler(normalized: Boolean = false): (Double) -> Double {
|
fun sampler(normalized: Boolean = false): (Double) -> Double {
|
||||||
var cachedSegment: Segment? = null
|
var cachedSegment: Segment2D? = null
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
return { t ->
|
return { t ->
|
||||||
val r = valueWithSegment(t, cachedSegment)
|
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
|
* @param segment an optional segment that can be used to speed up scanning for the relevant segment
|
||||||
* @see valueWithSegment
|
* @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]
|
* Evaluate the Fcurve at [t]
|
||||||
* @param segment an optional segment that can be used to speed up scanning for the relevant segment
|
* @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 (cachedSegment != null) {
|
||||||
if (t >= cachedSegment.start.x && t < cachedSegment.end.x) {
|
if (t >= cachedSegment.start.x && t < cachedSegment.end.x) {
|
||||||
return Pair(cachedSegment.yForX(t), cachedSegment)
|
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
|
* Return a list of contours that can be used to visualize the Fcurve
|
||||||
*/
|
*/
|
||||||
fun contours(scale: Vector2 = Vector2.ONE): List<ShapeContour> {
|
fun contours(scale: Vector2 = Vector2.ONE): List<ShapeContour> {
|
||||||
var active = mutableListOf<Segment>()
|
var active = mutableListOf<Segment2D>()
|
||||||
val result = mutableListOf<ShapeContour>()
|
val result = mutableListOf<ShapeContour>()
|
||||||
|
|
||||||
for (segment in segments) {
|
for (segment in segments) {
|
||||||
@@ -210,7 +210,7 @@ data class FCurve(val segments: List<Segment>) {
|
|||||||
* Fcurve builder
|
* Fcurve builder
|
||||||
*/
|
*/
|
||||||
class FCurveBuilder {
|
class FCurveBuilder {
|
||||||
val segments = mutableListOf<Segment>()
|
val segments = mutableListOf<Segment2D>()
|
||||||
var cursor = Vector2(0.0, 0.0)
|
var cursor = Vector2(0.0, 0.0)
|
||||||
|
|
||||||
var path = ""
|
var path = ""
|
||||||
@@ -222,7 +222,7 @@ class FCurveBuilder {
|
|||||||
|
|
||||||
fun lineTo(x: Double, y: Double, relative: Boolean = false) {
|
fun lineTo(x: Double, y: Double, relative: Boolean = false) {
|
||||||
val r = if (relative) 1.0 else 0.0
|
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)
|
cursor = Vector2(cursor.x + x, cursor.y * r + y)
|
||||||
path += "${if (relative) "l" else "L"}$x,$y"
|
path += "${if (relative) "l" else "L"}$x,$y"
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ class FCurveBuilder {
|
|||||||
) {
|
) {
|
||||||
val r = if (relative) 1.0 else 0.0
|
val r = if (relative) 1.0 else 0.0
|
||||||
segments.add(
|
segments.add(
|
||||||
Segment(
|
Segment2D(
|
||||||
cursor,
|
cursor,
|
||||||
Vector2(cursor.x + x0, cursor.y * r + y0),
|
Vector2(cursor.x + x0, cursor.y * r + y0),
|
||||||
Vector2(cursor.x + x, cursor.y * r + y)
|
Vector2(cursor.x + x, cursor.y * r + y)
|
||||||
@@ -251,7 +251,7 @@ class FCurveBuilder {
|
|||||||
) {
|
) {
|
||||||
val r = if (relative) 1.0 else 0.0
|
val r = if (relative) 1.0 else 0.0
|
||||||
segments.add(
|
segments.add(
|
||||||
Segment(
|
Segment2D(
|
||||||
cursor,
|
cursor,
|
||||||
Vector2(cursor.x + x0, cursor.y * r + y0),
|
Vector2(cursor.x + x0, cursor.y * r + y0),
|
||||||
Vector2(cursor.x + x1, cursor.y * r + y1),
|
Vector2(cursor.x + x1, cursor.y * r + y1),
|
||||||
@@ -272,7 +272,7 @@ class FCurveBuilder {
|
|||||||
val dy = outPos.y - outTangent.y
|
val dy = outPos.y - outTangent.y
|
||||||
val ts = x / lastDuration
|
val ts = x / lastDuration
|
||||||
segments.add(
|
segments.add(
|
||||||
Segment(
|
Segment2D(
|
||||||
cursor,
|
cursor,
|
||||||
Vector2(cursor.x + dx * ts, cursor.y + dy),
|
Vector2(cursor.x + dx * ts, cursor.y + dy),
|
||||||
Vector2(cursor.x + x * 0.66, cursor.y * r + y),
|
Vector2(cursor.x + x * 0.66, cursor.y * r + y),
|
||||||
@@ -290,7 +290,7 @@ class FCurveBuilder {
|
|||||||
val dx = cursor.x - outTangent.x
|
val dx = cursor.x - outTangent.x
|
||||||
val dy = cursor.y - outTangent.y
|
val dy = cursor.y - outTangent.y
|
||||||
segments.add(
|
segments.add(
|
||||||
Segment(
|
Segment2D(
|
||||||
cursor,
|
cursor,
|
||||||
Vector2(cursor.x + dx, cursor.y + dy),
|
Vector2(cursor.x + dx, cursor.y + dy),
|
||||||
Vector2(cursor.x + x1, cursor.y * r + y1),
|
Vector2(cursor.x + x1, cursor.y * r + y1),
|
||||||
@@ -370,7 +370,7 @@ private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
|||||||
*/
|
*/
|
||||||
return fcurve {
|
return fcurve {
|
||||||
fun dx(): Double {
|
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
|
return lastSegment.end.x - lastSegment.start.x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import org.openrndr.application
|
//import org.openrndr.application
|
||||||
import org.openrndr.draw.*
|
//import org.openrndr.draw.*
|
||||||
import org.openrndr.extra.runway.*
|
//import org.openrndr.extra.runway.*
|
||||||
|
//
|
||||||
/**
|
///**
|
||||||
* This example requires a `runway/BASNet` model to be active in Runway.
|
// * This example requires a `runway/BASNet` model to be active in Runway.
|
||||||
*/
|
// */
|
||||||
fun main() = application {
|
//fun main() = application {
|
||||||
configure {
|
// configure {
|
||||||
width = 331
|
// width = 331
|
||||||
height = 400
|
// height = 400
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
program {
|
// program {
|
||||||
val image = loadImage("demo-data/images/life-cover.jpg")
|
// val image = loadImage("demo-data/images/life-cover.jpg")
|
||||||
|
//
|
||||||
val result: BASNETResult =
|
// val result: BASNETResult =
|
||||||
runwayQuery("http://localhost:8000/query", BASNETRequest(image.toData()))
|
// runwayQuery("http://localhost:8000/query", BASNETRequest(image.toData()))
|
||||||
|
//
|
||||||
val segmentImage = ColorBuffer.fromData(result.image)
|
// val segmentImage = ColorBuffer.fromData(result.image)
|
||||||
|
//
|
||||||
extend {
|
// extend {
|
||||||
drawer.image(segmentImage, 0.0, 0.0)
|
// drawer.image(segmentImage, 0.0, 0.0)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
@@ -86,7 +86,7 @@ fun ShapeProvider.scatter(
|
|||||||
hg
|
hg
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Segment.randomPoints(count: Int) = sequence {
|
fun Segment2D.randomPoints(count: Int) = sequence {
|
||||||
for (i in 0 until count) {
|
for (i in 0 until count) {
|
||||||
val t = random.nextDouble()
|
val t = random.nextDouble()
|
||||||
yield(position(t) - normal(t).normalized * distanceToEdge)
|
yield(position(t) - normal(t).normalized * distanceToEdge)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ kotlin {
|
|||||||
implementation(project(":orx-triangulation"))
|
implementation(project(":orx-triangulation"))
|
||||||
implementation(project(":orx-shapes"))
|
implementation(project(":orx-shapes"))
|
||||||
implementation(project(":orx-noise"))
|
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.collections.pop
|
||||||
import org.openrndr.extra.shapes.vertex.ContourVertex
|
import org.openrndr.extra.shapes.vertex.ContourVertex
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
|
||||||
class ContourAdjusterStatus(
|
class ContourAdjusterStatus(
|
||||||
val contour: ShapeContour,
|
val contour: ShapeContour,
|
||||||
val selectedSegments: List<Segment>,
|
val selectedSegments: List<Segment2D>,
|
||||||
val selectedPoints: List<Vector2>
|
val selectedPoints: List<Vector2>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import org.openrndr.extra.shapes.utilities.insertPointAt
|
|||||||
import org.openrndr.math.Matrix44
|
import org.openrndr.math.Matrix44
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.math.transforms.buildTransform
|
import org.openrndr.math.transforms.buildTransform
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.SegmentType
|
import org.openrndr.shape.SegmentType
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -151,7 +151,7 @@ data class ContourEdge(
|
|||||||
removeAt(segmentIndex)
|
removeAt(segmentIndex)
|
||||||
|
|
||||||
if (segment.start.distanceTo(openContour.position(0.0)) > 1E-3) {
|
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++
|
insertIndex++
|
||||||
}
|
}
|
||||||
for (s in openContour.segments) {
|
for (s in openContour.segments) {
|
||||||
@@ -159,7 +159,7 @@ data class ContourEdge(
|
|||||||
insertIndex++
|
insertIndex++
|
||||||
}
|
}
|
||||||
if (segment.end.distanceTo(openContour.position(1.0)) > 1E-3) {
|
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)
|
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.openrndr.extra.shapes.adjust
|
package org.openrndr.extra.shapes.adjust
|
||||||
|
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
|
|
||||||
sealed interface SegmentOperation {
|
sealed interface SegmentOperation {
|
||||||
data class Remove(val index: Int, val amount: Int) : SegmentOperation
|
data class Remove(val index: Int, val amount: Int) : SegmentOperation
|
||||||
@@ -9,7 +9,7 @@ sealed interface SegmentOperation {
|
|||||||
|
|
||||||
|
|
||||||
class SegmentAdjustments(
|
class SegmentAdjustments(
|
||||||
val replacements: List<Triple<Int, Segment, Segment>>,
|
val replacements: List<Triple<Int, Segment2D, Segment2D>>,
|
||||||
val operations: List<SegmentOperation>
|
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>()
|
val adjustments = mutableListOf<SegmentOperation>()
|
||||||
|
|
||||||
fun removeAt(index: Int) {
|
fun removeAt(index: Int) {
|
||||||
list.removeAt(index)
|
list.removeAt(index)
|
||||||
adjustments.add(SegmentOperation.Remove(index, 1))
|
adjustments.add(SegmentOperation.Remove(index, 1))
|
||||||
}
|
}
|
||||||
fun add(segment: Segment) {
|
fun add(segment: Segment2D) {
|
||||||
list.add(segment)
|
list.add(segment)
|
||||||
adjustments.add(SegmentOperation.Insert(list.lastIndex, 1))
|
adjustments.add(SegmentOperation.Insert(list.lastIndex, 1))
|
||||||
}
|
}
|
||||||
fun add(index: Int, segment: Segment) {
|
fun add(index: Int, segment: Segment2D) {
|
||||||
list.add(index, segment)
|
list.add(index, segment)
|
||||||
adjustments.add(SegmentOperation.Insert(index, 1))
|
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)
|
val adjuster = SegmentAdjuster(this)
|
||||||
adjuster.block()
|
adjuster.block()
|
||||||
return adjuster.adjustments
|
return adjuster.adjustments
|
||||||
|
|||||||
@@ -111,13 +111,13 @@ class AlphaShape(val points: List<Vector2>) {
|
|||||||
private fun edgesToShapeContour(edges: List<Pair<Int, Int>>): ShapeContour {
|
private fun edgesToShapeContour(edges: List<Pair<Int, Int>>): ShapeContour {
|
||||||
if (edges.isEmpty()) return ShapeContour.EMPTY
|
if (edges.isEmpty()) return ShapeContour.EMPTY
|
||||||
val mapping = edges.toMap()
|
val mapping = edges.toMap()
|
||||||
val segments = mutableListOf<Segment>()
|
val segments = mutableListOf<Segment2D>()
|
||||||
val start = edges.first().first
|
val start = edges.first().first
|
||||||
var current = start
|
var current = start
|
||||||
val left = edges.map { it.first }.toMutableSet()
|
val left = edges.map { it.first }.toMutableSet()
|
||||||
for (i in edges.indices) {
|
for (i in edges.indices) {
|
||||||
val next = mapping[current]!!
|
val next = mapping[current]!!
|
||||||
segments.add(Segment(getVec(current), getVec(next)))
|
segments.add(Segment2D(getVec(current), getVec(next)))
|
||||||
left.remove(current)
|
left.remove(current)
|
||||||
current = next
|
current = next
|
||||||
if (current == start) break
|
if (current == start) break
|
||||||
@@ -140,11 +140,11 @@ class AlphaShape(val points: List<Vector2>) {
|
|||||||
while (left.isNotEmpty()) {
|
while (left.isNotEmpty()) {
|
||||||
val start = left.first()
|
val start = left.first()
|
||||||
var current = start
|
var current = start
|
||||||
val segments = mutableListOf<Segment>()
|
val segments = mutableListOf<Segment2D>()
|
||||||
val contourPoints = mutableListOf<Vector2>()
|
val contourPoints = mutableListOf<Vector2>()
|
||||||
for (i in edges.indices) {
|
for (i in edges.indices) {
|
||||||
val next = mapping[current]!!
|
val next = mapping[current]!!
|
||||||
segments.add(Segment(getVec(current), getVec(next)))
|
segments.add(Segment2D(getVec(current), getVec(next)))
|
||||||
contourPoints.add(getVec(current))
|
contourPoints.add(getVec(current))
|
||||||
left.remove(current)
|
left.remove(current)
|
||||||
current = next
|
current = next
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import org.openrndr.color.ConvertibleToColorRGBa
|
|||||||
import org.openrndr.math.Matrix44
|
import org.openrndr.math.Matrix44
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.shape.Rectangle
|
import org.openrndr.shape.Rectangle
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ open class BezierPatchBase<C>(
|
|||||||
cps[j] += points[i][j] * cs[i]
|
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 {
|
fun vertical(u: Double): ShapeContour {
|
||||||
@@ -134,33 +134,33 @@ open class BezierPatchBase<C>(
|
|||||||
cps[j] += points[j][i] * cs[i]
|
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
|
* Extract a sub-patch based on uv parameterization
|
||||||
*/
|
*/
|
||||||
fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatchBase<C> {
|
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 c0 = Segment2D(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 c1 = Segment2D(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 c2 = Segment2D(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 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 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 d0 = Segment2D(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 d1 = Segment2D(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 d2 = Segment2D(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 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
|
return fromSegments<C>(d0, d1, d2, d3).transposed
|
||||||
}
|
}
|
||||||
|
|
||||||
val contour: ShapeContour = ShapeContour(
|
val contour: ShapeContour = ShapeContour(
|
||||||
listOf(
|
listOf(
|
||||||
Segment(points[0][0], points[0][1], points[0][2], points[0][3]),
|
Segment2D(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]),
|
Segment2D(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]),
|
Segment2D(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[3][0], points[2][0], points[1][0], points[0][0]),
|
||||||
), true
|
), true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ open class BezierPatchBase<C>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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 {
|
where C : AlgebraicColor<C>, C : ConvertibleToColorRGBa {
|
||||||
val c0c = c0.cubic
|
val c0c = c0.cubic
|
||||||
val c1c = c1.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
|
* 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 c0c = c0.cubic
|
||||||
val c1c = c1.cubic
|
val c1c = c1.cubic
|
||||||
val c2c = c2.cubic
|
val c2c = c2.cubic
|
||||||
@@ -289,7 +289,7 @@ fun BezierPatch.distort(shapeContour: ShapeContour, referenceRectangle: Rectangl
|
|||||||
val ns = position(s.x, s.y)
|
val ns = position(s.x, s.y)
|
||||||
val nc0 = position(c0.x, c0.y)
|
val nc0 = position(c0.x, c0.y)
|
||||||
val nc1 = position(c1.x, c1.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)
|
return ShapeContour(distortedSegments, shapeContour.closed, shapeContour.polarity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,16 +140,16 @@ open class BezierPatch3DBase<C>(
|
|||||||
* Extract a sub-patch based on uv parameterization
|
* Extract a sub-patch based on uv parameterization
|
||||||
*/
|
*/
|
||||||
fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatch3DBase<C> {
|
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 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)
|
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)
|
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)
|
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 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 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)
|
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)
|
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)
|
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
|
return fromSegments<C>(d0, d1, d2, d3).transposed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ fun ContourBlend(a: ShapeContour, b: ShapeContour): ContourBlend {
|
|||||||
val rb = b.rectified()
|
val rb = b.rectified()
|
||||||
val sa = ra.splitForBlend(rb)
|
val sa = ra.splitForBlend(rb)
|
||||||
val sb = rb.splitForBlend(ra)
|
val sb = rb.splitForBlend(ra)
|
||||||
require(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.contour.segments.size}, ${sb.contour.segments.size}"
|
"preprocessing for contours failed to produce equal number of segments. ${sa.path.segments.size}, ${sb.path.segments.size}"
|
||||||
}
|
}
|
||||||
return ContourBlend(sa, sb)
|
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.RectifiedContour
|
||||||
import org.openrndr.extra.shapes.rectify.rectified
|
import org.openrndr.extra.shapes.rectify.rectified
|
||||||
import org.openrndr.extra.shapes.utilities.fromContours
|
import org.openrndr.extra.shapes.utilities.fromContours
|
||||||
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split for blending with [other]
|
* Split for blending with [other]
|
||||||
*/
|
*/
|
||||||
fun RectifiedContour.splitForBlend(other: RectifiedContour): RectifiedContour {
|
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) }
|
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 {
|
fun RectifiedContour.mix(other: RectifiedContour, blendFunction: (Double) -> Double): ShapeContour {
|
||||||
val n = this.contour.segments.size.toDouble()
|
val n = this.path.segments.size.toDouble()
|
||||||
val segs = (this.contour.segments zip other.contour.segments).mapIndexed { index, it ->
|
val segs = (this.path.segments zip other.path.segments).mapIndexed { index, it ->
|
||||||
val t0 = inverseRectify(index / n)
|
val t0 = inverseRectify(index / n)
|
||||||
val t1 = inverseRectify((index + 1 / 3.0) / n)
|
val t1 = inverseRectify((index + 1 / 3.0) / n)
|
||||||
val t2 = inverseRectify((index + 2 / 3.0) / n)
|
val t2 = inverseRectify((index + 2 / 3.0) / n)
|
||||||
val t3 = inverseRectify((index + 1) / n)
|
val t3 = inverseRectify((index + 1) / n)
|
||||||
|
(it.first as Segment2D).mix(it.second as Segment2D, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3))
|
||||||
it.first.mix(it.second, 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
|
package org.openrndr.extra.shapes.blend
|
||||||
|
|
||||||
import org.openrndr.math.mix
|
import org.openrndr.math.mix
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
|
import org.openrndr.shape.Segment3D
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cubic segment mix
|
* Cubic segment mix
|
||||||
@@ -11,14 +13,14 @@ import org.openrndr.shape.Segment
|
|||||||
* @param f2 the mix factor for the second control point
|
* @param f2 the mix factor for the second control point
|
||||||
* @param f3 the mix factor for the end 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 ac = this.cubic
|
||||||
val bc = other.cubic
|
val bc = other.cubic
|
||||||
|
|
||||||
val acc = if (ac.corner) 1.0 else 0.0
|
val acc = if (ac.corner) 1.0 else 0.0
|
||||||
val bcc = if (bc.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.start.mix(bc.start, f0),
|
||||||
ac.control[0].mix(bc.control[0], f1),
|
ac.control[0].mix(bc.control[0], f1),
|
||||||
ac.control[1].mix(bc.control[1], f2),
|
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
|
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]
|
* Evaluates the bounds around all [Segment] instances in the [Iterable]
|
||||||
*/
|
*/
|
||||||
val Iterable<Segment>.bounds : Rectangle
|
val Iterable<Segment2D>.bounds : Rectangle
|
||||||
@JvmName("segmentBounds")
|
@JvmName("segmentBounds")
|
||||||
get() = map {
|
get() = map {
|
||||||
it.bounds
|
it.bounds
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.openrndr.extra.shapes.hobbycurve
|
|||||||
// Code adapted from http://weitz.de/hobby/
|
// Code adapted from http://weitz.de/hobby/
|
||||||
|
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.Shape
|
import org.openrndr.shape.Shape
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import kotlin.math.atan2
|
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)
|
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> {
|
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
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
|
||||||
private fun Segment.splitOnExtrema(): List<Segment> {
|
private fun Segment2D.splitOnExtrema(): List<Segment2D> {
|
||||||
var extrema = extrema().toMutableList()
|
var extrema = extrema().toMutableList()
|
||||||
|
|
||||||
if (isStraight(0.05)) {
|
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 t1 = 0.0
|
||||||
var t2 = 0.0
|
var t2 = 0.0
|
||||||
val result = mutableListOf<Segment>()
|
val result = mutableListOf<Segment2D>()
|
||||||
while (t2 <= 1.0) {
|
while (t2 <= 1.0) {
|
||||||
t2 = t1 + step
|
t2 = t1 + step
|
||||||
while (t2 <= 1.0 + 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()
|
val pass1 = splitOnExtrema()
|
||||||
//return pass1
|
//return pass1
|
||||||
return pass1.flatMap { it.splitToSimple(stepSize) }
|
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) {
|
if (control.size == 1) {
|
||||||
return cubic.scale(polarity, scale)
|
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,
|
distance: Double,
|
||||||
stepSize: Double = 0.01,
|
stepSize: Double = 0.01,
|
||||||
yPolarity: YPolarity = YPolarity.CW_NEGATIVE_Y
|
yPolarity: YPolarity = YPolarity.CW_NEGATIVE_Y
|
||||||
): List<Segment> {
|
): List<Segment2D> {
|
||||||
return if (linear) {
|
return if (linear) {
|
||||||
val n = normal(0.0, yPolarity)
|
val n = normal(0.0, yPolarity)
|
||||||
if (distance > 0.0) {
|
if (distance > 0.0) {
|
||||||
listOf(Segment(start + distance * n, end + distance * n))
|
listOf(Segment2D(start + distance * n, end + distance * n))
|
||||||
} else {
|
} else {
|
||||||
val d = direction()
|
val d = direction()
|
||||||
val s = distance.coerceAtMost(length / 2.0)
|
val s = distance.coerceAtMost(length / 2.0)
|
||||||
val candidate = Segment(
|
val candidate = Segment2D(
|
||||||
start - s * d + distance * n,
|
start - s * d + distance * n,
|
||||||
end + 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()
|
var final = candidateContour.removeLoops()
|
||||||
|
|
||||||
if (postProc && !final.empty) {
|
if (postProc && !final.empty) {
|
||||||
val head = Segment(
|
val head = Segment2D(
|
||||||
segments[0].start + segments[0].normal(0.0)
|
segments[0].start + segments[0].normal(0.0)
|
||||||
.perpendicular(polarity) * 1000.0, segments[0].start
|
.perpendicular(polarity) * 1000.0, segments[0].start
|
||||||
).offset(distance).firstOrNull()?.copy(end = final.segments[0].start)?.contour
|
).offset(distance).firstOrNull()?.copy(end = final.segments[0].start)?.contour
|
||||||
|
|
||||||
val tail = Segment(
|
val tail = Segment2D(
|
||||||
segments.last().end,
|
segments.last().end,
|
||||||
segments.last().end - segments.last().normal(1.0)
|
segments.last().end - segments.last().normal(1.0)
|
||||||
.perpendicular(polarity) * 1000.0
|
.perpendicular(polarity) * 1000.0
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package org.openrndr.extra.shapes.operators
|
package org.openrndr.extra.shapes.operators
|
||||||
|
|
||||||
import org.openrndr.math.mod_
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.Segment
|
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import org.openrndr.shape.contour
|
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 {
|
val c = contour {
|
||||||
moveTo(position(0.0))
|
moveTo(position(0.0))
|
||||||
var index = 0
|
var index = 0
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import kotlin.math.abs
|
|||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
import kotlin.math.sqrt
|
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)
|
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))
|
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 p3 = s1.end
|
||||||
val p2 = s0.end
|
val p2 = s0.end
|
||||||
val p1 = s0.start
|
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
|
* @param chamfer the chamfer function to apply
|
||||||
*/
|
*/
|
||||||
fun ShapeContour.chamferCorners(
|
fun ShapeContour.chamferCorners(
|
||||||
lengths: (index: Int, left: Segment, right: Segment) -> Double,
|
lengths: (index: Int, left: Segment2D, right: Segment2D) -> Double,
|
||||||
expands: (index: Int, left: Segment, right: Segment) -> Double = { _, _, _ -> 0.0 },
|
expands: (index: Int, left: Segment2D, right: Segment2D) -> 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
|
||||||
|
|||||||
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
|
package org.openrndr.extra.shapes.rectify
|
||||||
|
|
||||||
import org.openrndr.extra.shapes.utilities.splitAt
|
|
||||||
import org.openrndr.math.Matrix44
|
import org.openrndr.math.Matrix44
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.math.clamp
|
|
||||||
import org.openrndr.shape.Segment
|
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
/**
|
class RectifiedContour(contour: ShapeContour, distanceTolerance: Double = 0.5, lengthScale: Double = 1.0) :
|
||||||
* RectifiedContour provides an approximately uniform parameterization for [ShapeContour]
|
RectifiedPath<Vector2>(contour, distanceTolerance, lengthScale) {
|
||||||
*/
|
|
||||||
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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun velocity(t: Double): Vector2 {
|
fun velocity(t: Double): Vector2 {
|
||||||
return if (contour.empty) {
|
return if (path.empty) {
|
||||||
Vector2.ZERO
|
Vector2.ZERO
|
||||||
} else {
|
} else {
|
||||||
val (segment, st) = contour.segment(rectify(safe(t)))
|
val (segment, st) = path.segment(rectify(safe(t)))
|
||||||
contour.segments[segment].direction(st)
|
path.segments[segment].direction(st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun normal(t: Double): Vector2 {
|
fun normal(t: Double): Vector2 {
|
||||||
return if (contour.empty) {
|
return if (path.empty) {
|
||||||
Vector2.UNIT_Y
|
Vector2.UNIT_Y
|
||||||
} else {
|
} else {
|
||||||
contour.normal(rectify(safe(t)))
|
(path as ShapeContour).normal(rectify(safe(t)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pose(t: Double): Matrix44 {
|
fun pose(t: Double): Matrix44 {
|
||||||
return if (contour.empty) {
|
path as ShapeContour
|
||||||
|
return if (path.empty) {
|
||||||
Matrix44.IDENTITY
|
Matrix44.IDENTITY
|
||||||
} else {
|
} else {
|
||||||
contour.pose(rectify(safe(t)))
|
path.pose(rectify(safe(t)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sub(t0: Double, t1: Double): ShapeContour {
|
override fun sub(t0: Double, t1: Double): ShapeContour {
|
||||||
if (contour.empty) {
|
path as ShapeContour
|
||||||
|
if (path.empty) {
|
||||||
return ShapeContour.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 {
|
} else {
|
||||||
contour.sub(rectify(t0), rectify(t1))
|
path.sub(rectify(t0), rectify(t1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun splitAt(ascendingTs: List<Double>, weldEpsilon: Double): List<ShapeContour> {
|
||||||
* Split contour at [ascendingTs]
|
@Suppress("UNCHECKED_CAST")
|
||||||
* @since orx 0.4.4
|
return super.splitAt(ascendingTs, weldEpsilon) as List<ShapeContour>
|
||||||
*/
|
|
||||||
fun splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List<ShapeContour> {
|
|
||||||
return contour.splitAt(ascendingTs.map { rectify(it) }, weldEpsilon)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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]. */
|
/** Converts spline to a [Segment]. */
|
||||||
fun CatmullRom2.toSegment(): Segment {
|
fun CatmullRom2.toSegment(): Segment2D {
|
||||||
val d1a2 = (p1 - p0).length.pow(2 * alpha)
|
val d1a2 = (p1 - p0).length.pow(2 * alpha)
|
||||||
val d2a2 = (p2 - p1).length.pow(2 * alpha)
|
val d2a2 = (p2 - p1).length.pow(2 * alpha)
|
||||||
val d3a2 = (p3 - p2).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 b2 = (p1 * d3a2 - p3 * d2a2 + p2 * (2 * d3a2 + 3 * d3a * d2a + d2a2)) / (3 * d3a * (d3a + d2a))
|
||||||
val b3 = p2
|
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.math.Vector2
|
||||||
import org.openrndr.shape.LineSegment
|
import org.openrndr.shape.LineSegment
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.intersection
|
import org.openrndr.shape.intersection
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the Tunni point for the [Segment]
|
* Find the Tunni point for the [Segment]
|
||||||
* @since orx 0.4.5
|
* @since orx 0.4.5
|
||||||
*/
|
*/
|
||||||
val Segment.tunniPoint: Vector2
|
val Segment2D.tunniPoint: Vector2
|
||||||
get() {
|
get() {
|
||||||
val c = this.cubic
|
val c = this.cubic
|
||||||
val ac = LineSegment(c.start, c.control[0])
|
val ac = LineSegment(c.start, c.control[0])
|
||||||
@@ -23,7 +23,7 @@ val Segment.tunniPoint: Vector2
|
|||||||
* Find the Tunni line for the [Segment]
|
* Find the Tunni line for the [Segment]
|
||||||
* @since orx 0.4.5
|
* @since orx 0.4.5
|
||||||
*/
|
*/
|
||||||
val Segment.tunniLine: LineSegment
|
val Segment2D.tunniLine: LineSegment
|
||||||
get() {
|
get() {
|
||||||
val c = this.cubic
|
val c = this.cubic
|
||||||
return LineSegment(c.control[0], c.control[1])
|
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
|
* Find a new segment that has [tunniPoint] as its Tunni-point
|
||||||
* @since orx 0.4.5
|
* @since orx 0.4.5
|
||||||
*/
|
*/
|
||||||
fun Segment.withTunniPoint(tunniPoint: Vector2): Segment {
|
fun Segment2D.withTunniPoint(tunniPoint: Vector2): Segment2D {
|
||||||
val ha = (start + tunniPoint) / 2.0
|
val ha = (start + tunniPoint) / 2.0
|
||||||
val hb = (end + tunniPoint) / 2.0
|
val hb = (end + tunniPoint) / 2.0
|
||||||
val hpa = ha + this.cubic.control[1] - end
|
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
|
* Find a segment for which [pointOnLine] lies on its Tunni-line
|
||||||
* @since orx 0.4.5
|
* @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 ls = LineSegment(pointOnLine, pointOnLine + this.cubic.control[0] - this.cubic.control[1])
|
||||||
val ac0 = LineSegment(start, this.cubic.control[0])
|
val ac0 = LineSegment(start, this.cubic.control[0])
|
||||||
val bc1 = LineSegment(end, this.cubic.control[1])
|
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.extra.shapes.adjust.ContourEdge
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.shape.LineSegment
|
import org.openrndr.shape.LineSegment
|
||||||
import org.openrndr.shape.Segment
|
|
||||||
import org.openrndr.shape.ShapeContour
|
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
|
package org.openrndr.extra.shapes.utilities
|
||||||
|
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.math.EuclideanVector
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.*
|
||||||
|
|
||||||
fun ShapeContour.splitAt(segmentIndex: Double, segmentT: Double): List<ShapeContour> {
|
fun ShapeContour.splitAt(segmentIndex: Double, segmentT: Double): List<ShapeContour> {
|
||||||
val t = (1.0 / segments.size) * (segmentIndex + segmentT)
|
val t = (1.0 / segments.size) * (segmentIndex + segmentT)
|
||||||
return splitAt(listOf(t))
|
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()) {
|
if (empty || ascendingTs.isEmpty()) {
|
||||||
return listOf(this)
|
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()) {
|
if (ascendingTs.isEmpty()) {
|
||||||
return listOf(this)
|
return listOf(this)
|
||||||
}
|
}
|
||||||
@@ -27,4 +46,16 @@ fun Segment.splitAt(ascendingTs: List<Double>, weldEpsilon: Double = 1E-6): List
|
|||||||
return ascendingTs.windowed(2, 1).map {
|
return ascendingTs.windowed(2, 1).map {
|
||||||
sub(it[0], it[1])
|
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
|
var prevQuadCtrlPoint: Vector2? = null
|
||||||
|
|
||||||
val contours = compounds().map { compound ->
|
val contours = compounds().map { compound ->
|
||||||
val segments = mutableListOf<Segment>()
|
val segments = mutableListOf<Segment2D>()
|
||||||
var closed = false
|
var closed = false
|
||||||
// If an argument is invalid, an error is logged,
|
// If an argument is invalid, an error is logged,
|
||||||
// further interpreting is stopped and compound is returned as-is.
|
// 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
|
// Following points are implicit lineto arguments
|
||||||
segments += points.drop(1).map {
|
segments += points.drop(1).map {
|
||||||
Segment(cursor, it).apply {
|
Segment2D(cursor, it).apply {
|
||||||
cursor = it
|
cursor = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,21 +219,21 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
|
|
||||||
// Following points are implicit lineto arguments
|
// Following points are implicit lineto arguments
|
||||||
segments += points.drop(1).map {
|
segments += points.drop(1).map {
|
||||||
Segment(cursor, cursor + it).apply {
|
Segment2D(cursor, cursor + it).apply {
|
||||||
cursor += it
|
cursor += it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"L" -> {
|
"L" -> {
|
||||||
segments += points!!.map {
|
segments += points!!.map {
|
||||||
Segment(cursor, it).apply {
|
Segment2D(cursor, it).apply {
|
||||||
cursor = it
|
cursor = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"l" -> {
|
"l" -> {
|
||||||
segments += points!!.map {
|
segments += points!!.map {
|
||||||
Segment(cursor, cursor + it).apply {
|
Segment2D(cursor, cursor + it).apply {
|
||||||
cursor += it
|
cursor += it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
"H" -> {
|
"H" -> {
|
||||||
segments += command.operands.map {
|
segments += command.operands.map {
|
||||||
val target = Vector2(it, cursor.y)
|
val target = Vector2(it, cursor.y)
|
||||||
Segment(cursor, target).apply {
|
Segment2D(cursor, target).apply {
|
||||||
cursor = target
|
cursor = target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +249,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
"h" -> {
|
"h" -> {
|
||||||
segments += command.operands.map {
|
segments += command.operands.map {
|
||||||
val target = cursor + Vector2(it, 0.0)
|
val target = cursor + Vector2(it, 0.0)
|
||||||
Segment(cursor, target).apply {
|
Segment2D(cursor, target).apply {
|
||||||
cursor = target
|
cursor = target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
"V" -> {
|
"V" -> {
|
||||||
segments += command.operands.map {
|
segments += command.operands.map {
|
||||||
val target = Vector2(cursor.x, it)
|
val target = Vector2(cursor.x, it)
|
||||||
Segment(cursor, target).apply {
|
Segment2D(cursor, target).apply {
|
||||||
cursor = target
|
cursor = target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
"v" -> {
|
"v" -> {
|
||||||
segments += command.operands.map {
|
segments += command.operands.map {
|
||||||
val target = cursor + Vector2(0.0, it)
|
val target = cursor + Vector2(0.0, it)
|
||||||
Segment(cursor, target).apply {
|
Segment2D(cursor, target).apply {
|
||||||
cursor = target
|
cursor = target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
return@forEach
|
return@forEach
|
||||||
} else {
|
} else {
|
||||||
val (cp1, cp2, target) = it
|
val (cp1, cp2, target) = it
|
||||||
Segment(cursor, cp1, cp2, target).also {
|
Segment2D(cursor, cp1, cp2, target).also {
|
||||||
cursor = target
|
cursor = target
|
||||||
prevCubicCtrlPoint = cp2
|
prevCubicCtrlPoint = cp2
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
return@forEach
|
return@forEach
|
||||||
} else {
|
} else {
|
||||||
val (cp1, cp2, target) = it.map { v -> cursor + v }
|
val (cp1, cp2, target) = it.map { v -> cursor + v }
|
||||||
Segment(cursor, cp1, cp2, target).apply {
|
Segment2D(cursor, cp1, cp2, target).apply {
|
||||||
cursor = target
|
cursor = target
|
||||||
prevCubicCtrlPoint = cp2
|
prevCubicCtrlPoint = cp2
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
} else {
|
} else {
|
||||||
val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor)
|
val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor)
|
||||||
val (cp2, target) = it
|
val (cp2, target) = it
|
||||||
Segment(cursor, cp1, cp2, target).also {
|
Segment2D(cursor, cp1, cp2, target).also {
|
||||||
cursor = target
|
cursor = target
|
||||||
prevCubicCtrlPoint = cp2
|
prevCubicCtrlPoint = cp2
|
||||||
}
|
}
|
||||||
@@ -321,7 +321,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
} else {
|
} else {
|
||||||
val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor)
|
val cp1 = 2.0 * cursor - (prevCubicCtrlPoint ?: cursor)
|
||||||
val (cp2, target) = it.map { v -> cursor + v }
|
val (cp2, target) = it.map { v -> cursor + v }
|
||||||
Segment(cursor, cp1, cp2, target).also {
|
Segment2D(cursor, cp1, cp2, target).also {
|
||||||
cursor = target
|
cursor = target
|
||||||
prevCubicCtrlPoint = cp2
|
prevCubicCtrlPoint = cp2
|
||||||
}
|
}
|
||||||
@@ -335,7 +335,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
return@forEach
|
return@forEach
|
||||||
} else {
|
} else {
|
||||||
val (cp, target) = it
|
val (cp, target) = it
|
||||||
Segment(cursor, cp, target).also {
|
Segment2D(cursor, cp, target).also {
|
||||||
cursor = target
|
cursor = target
|
||||||
prevQuadCtrlPoint = cp
|
prevQuadCtrlPoint = cp
|
||||||
}
|
}
|
||||||
@@ -349,7 +349,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
return@forEach
|
return@forEach
|
||||||
} else {
|
} else {
|
||||||
val (cp, target) = it.map { v -> cursor + v }
|
val (cp, target) = it.map { v -> cursor + v }
|
||||||
Segment(cursor, cp, target).also {
|
Segment2D(cursor, cp, target).also {
|
||||||
cursor = target
|
cursor = target
|
||||||
prevQuadCtrlPoint = cp
|
prevQuadCtrlPoint = cp
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
"T" -> {
|
"T" -> {
|
||||||
points!!.forEach {
|
points!!.forEach {
|
||||||
val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor)
|
val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor)
|
||||||
Segment(cursor, cp, it).also { _ ->
|
Segment2D(cursor, cp, it).also { _ ->
|
||||||
cursor = it
|
cursor = it
|
||||||
prevQuadCtrlPoint = cp
|
prevQuadCtrlPoint = cp
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
"t" -> {
|
"t" -> {
|
||||||
points!!.forEach {
|
points!!.forEach {
|
||||||
val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor)
|
val cp = 2.0 * cursor - (prevQuadCtrlPoint ?: cursor)
|
||||||
Segment(cursor, cp, cursor + it).also { _ ->
|
Segment2D(cursor, cp, cursor + it).also { _ ->
|
||||||
cursor = it
|
cursor = it
|
||||||
prevQuadCtrlPoint = cp
|
prevQuadCtrlPoint = cp
|
||||||
}
|
}
|
||||||
@@ -376,7 +376,7 @@ internal class SVGPath(val element: Element? = null) : SVGElement(element) {
|
|||||||
}
|
}
|
||||||
"Z", "z" -> {
|
"Z", "z" -> {
|
||||||
if ((cursor - anchor).length >= 0.001) {
|
if ((cursor - anchor).length >= 0.001) {
|
||||||
segments += Segment(cursor, anchor)
|
segments += Segment2D(cursor, anchor)
|
||||||
}
|
}
|
||||||
cursor = anchor
|
cursor = anchor
|
||||||
closed = true
|
closed = true
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ plugins {
|
|||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@Suppress("UNUSED_VARIABLE")
|
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(libs.openrndr.math)
|
api(libs.openrndr.math)
|
||||||
@@ -12,12 +11,18 @@ kotlin {
|
|||||||
implementation(project(":orx-noise"))
|
implementation(project(":orx-noise"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val commonTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":orx-shapes"))
|
||||||
|
implementation(libs.openrndr.shape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_VARIABLE")
|
|
||||||
val jvmDemo by getting {
|
val jvmDemo by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":orx-shapes"))
|
implementation(project(":orx-shapes"))
|
||||||
implementation(project(":orx-noise"))
|
implementation(project(":orx-noise"))
|
||||||
|
implementation(libs.openrndr.shape)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import org.openrndr.extra.triangulation.Delaunay
|
import org.openrndr.extra.triangulation.Delaunay
|
||||||
import org.openrndr.shape.Circle
|
import org.openrndr.shape.Circle
|
||||||
import org.openrndr.shape.Rectangle
|
import org.openrndr.shape.Rectangle
|
||||||
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.openrndr.extra.turtle
|
|||||||
import org.openrndr.math.Matrix44
|
import org.openrndr.math.Matrix44
|
||||||
import org.openrndr.math.Vector4
|
import org.openrndr.math.Vector4
|
||||||
import org.openrndr.math.transforms.buildTransform
|
import org.openrndr.math.transforms.buildTransform
|
||||||
import org.openrndr.shape.Segment
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
|
|
||||||
fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) {
|
fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) {
|
||||||
@@ -16,7 +16,7 @@ fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Turtle.segment(
|
fun Turtle.segment(
|
||||||
segment: Segment,
|
segment: Segment2D,
|
||||||
alignTangent: Boolean = true,
|
alignTangent: Boolean = true,
|
||||||
externalAlignTransform: Matrix44 = Matrix44.IDENTITY
|
externalAlignTransform: Matrix44 = Matrix44.IDENTITY
|
||||||
): Matrix44 {
|
): Matrix44 {
|
||||||
|
|||||||
Reference in New Issue
Block a user