-
Notifications
You must be signed in to change notification settings - Fork 73
/
Curve2.kt
136 lines (117 loc) · 4.13 KB
/
Curve2.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package org.openrndr.kartifex
import org.openrndr.kartifex.utils.Intersections
interface Curve2 {
/**
* @param t a parametric point on the curve, not necessarily within [0, 1]
* @return the interpolated position on the curve
*/
fun position(t: Double): Vec2
/**
* Given a ring of curves, the sum of area() will be the area enclosed by that ring. For clockwise rings, the sum will
* be negative, for counter-clockwise rings it will be positive.
*
* @return the signed area of the curve
*/
fun signedArea(): Double
fun length(): Double
fun start() = position(0.0)
fun end() = position(1.0)
/**
* @return an updated curve with the specified endpoints.
*/
fun endpoints(start: Vec2, end: Vec2): Curve2
/**
* @param t a parametric point on the curve, not necessarily within [0, 1]
* @return the tangent at the interpolated position on the curve, which is not normalized
*/
fun direction(t: Double): Vec2
/**
* @param t a parametric point within the curve, which must be within (0, 1)
* @return an array representing the lower and upper regions of the curve, split at `t`
*/
fun split(t: Double): Array<Curve2>
fun isFlat(epsilon: Double): Boolean
/**
* @param interval the parametric range
* @return the curve within [interval.lo, interval.hi]
*/
fun range(interval: Interval) = range(interval.lo, interval.hi)
/**
* @param tMin the lower parametric bound
* @param tMax the upper parametric bound
* @return the curve within [tMin, tMax]
*/
fun range(tMin: Double, tMax: Double): Curve2 {
require(tMin != tMax) { "range must be non-zero" }
require(tMax >= tMin) { "tMin must be less than tMax" }
return when {
tMin == 0.0 && tMax == 1.0 -> this
tMin == 0.0 -> split(tMax)[0]
tMax == 1.0 -> split(tMin)[1]
else -> split(tMin)[1].split((tMax - tMin) / (1 - tMin))[0].endpoints(position(tMin), position(tMax))
}
}
/**
* @param unsafeTs an array of parametric split points
* @return an array of curves, split at the specified points.
*/
fun split(unsafeTs: DoubleArray): Array<Curve2> {
var previous = Double.POSITIVE_INFINITY
var tCount = 0
for (i in unsafeTs.indices) {
if (unsafeTs[i] != previous) {
tCount++
}
previous = unsafeTs[i]
}
var ts = DoubleArray(tCount)
tCount = 0
previous = Double.POSITIVE_INFINITY
for (i in unsafeTs.indices) {
if (unsafeTs[i] != previous) {
ts[tCount] = unsafeTs[i]
tCount++
}
previous = unsafeTs[i]
}
if (ts.isEmpty()) {
return arrayOf(this)
}
//ts = ts.clone()
//java.util.Arrays.sort(ts)
ts = ts.map { it }.toDoubleArray()
ts.sort()
val offset = if (ts[0] == 0.0) 1 else 0
val len = ts.size - offset - if (ts[ts.size - 1] == 1.0) 1 else 0
ts.copyInto(ts, 0, offset, offset+len)
if (len == 0) {
return arrayOf(this)
} else if (len == 1) {
return split(ts[0])
}
val result = arrayOfNulls<Curve2>(len + 1)
result[0] = range(0.0, ts[0])
for (i in 0 until len - 1) {
result[i + 1] = range(ts[i], ts[i + 1])
}
result[len] = range(ts[len - 1], 1.0)
return result.filterNotNull().toTypedArray()
}
/**
* @param p a point in 2D space
* @return the `t` parameter representing the closest point on the curve, not necessarily within [0,1]
*/
fun nearestPoint(p: Vec2): Double
fun bounds(): Box2 {
var bounds: Box2 = Box.box(start(), end())
for (t in inflections()) {
bounds = bounds.union(position(t))
}
return bounds
}
fun subdivide(error: Double): Array<Vec2>
fun transform(m: Matrix3): Curve2
fun reverse(): Curve2
fun inflections(): DoubleArray
fun intersections(c: Curve2) = Intersections.intersections(this, c)
}