107 lines
3.2 KiB
Kotlin
107 lines
3.2 KiB
Kotlin
package org.openrndr.extra.shapes.rectify
|
|
|
|
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)
|
|
|
|
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 position(t: Double): Vector2 {
|
|
return if (contour.empty) {
|
|
Vector2.INFINITY
|
|
} else {
|
|
contour.position(rectify(safe(t)))
|
|
}
|
|
}
|
|
|
|
fun velocity(t: Double): Vector2 {
|
|
return if (contour.empty) {
|
|
Vector2.ZERO
|
|
} else {
|
|
val (segment, st) = contour.segment(rectify(safe(t)))
|
|
contour.segments[segment].direction(st)
|
|
}
|
|
}
|
|
|
|
fun normal(t: Double): Vector2 {
|
|
return if (contour.empty) {
|
|
Vector2.UNIT_Y
|
|
} else {
|
|
contour.normal(rectify(safe(t)))
|
|
}
|
|
}
|
|
|
|
fun pose(t: Double): Matrix44 {
|
|
return if (contour.empty) {
|
|
Matrix44.IDENTITY
|
|
} else {
|
|
contour.pose(rectify(safe(t)))
|
|
}
|
|
}
|
|
|
|
fun sub(t0: Double, t1: Double): ShapeContour {
|
|
if (contour.empty) {
|
|
return ShapeContour.EMPTY
|
|
}
|
|
return if (contour.closed) {
|
|
contour.sub(rectify(t0.mod(1.0)) + floor(t0), rectify(t1.mod(1.0)) + floor(t1))
|
|
} else {
|
|
contour.sub(rectify(t0), rectify(t1))
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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)
|
|
} |