@ue-too/curve 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/b-curve.d.ts +1 -0
- package/curve.tsbuildinfo +1 -1
- package/index.js +1511 -1562
- package/index.js.map +13 -1
- package/package.json +13 -7
- package/LICENSE.txt +0 -19
package/index.js
CHANGED
|
@@ -1,1642 +1,1591 @@
|
|
|
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
|
-
|
|
1
|
+
// src/b-curve.ts
|
|
2
|
+
import { PointCal } from "@ue-too/math";
|
|
3
|
+
var T = [
|
|
4
|
+
-0.06405689286260563,
|
|
5
|
+
0.06405689286260563,
|
|
6
|
+
-0.1911188674736163,
|
|
7
|
+
0.1911188674736163,
|
|
8
|
+
-0.3150426796961634,
|
|
9
|
+
0.3150426796961634,
|
|
10
|
+
-0.4337935076260451,
|
|
11
|
+
0.4337935076260451,
|
|
12
|
+
-0.5454214713888396,
|
|
13
|
+
0.5454214713888396,
|
|
14
|
+
-0.6480936519369755,
|
|
15
|
+
0.6480936519369755,
|
|
16
|
+
-0.7401241915785544,
|
|
17
|
+
0.7401241915785544,
|
|
18
|
+
-0.820001985973903,
|
|
19
|
+
0.820001985973903,
|
|
20
|
+
-0.8864155270044011,
|
|
21
|
+
0.8864155270044011,
|
|
22
|
+
-0.9382745520027328,
|
|
23
|
+
0.9382745520027328,
|
|
24
|
+
-0.9747285559713095,
|
|
25
|
+
0.9747285559713095,
|
|
26
|
+
-0.9951872199970213,
|
|
27
|
+
0.9951872199970213
|
|
28
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
|
-
|
|
29
|
+
var C = [
|
|
30
|
+
0.12793819534675216,
|
|
31
|
+
0.12793819534675216,
|
|
32
|
+
0.1258374563468283,
|
|
33
|
+
0.1258374563468283,
|
|
34
|
+
0.12167047292780339,
|
|
35
|
+
0.12167047292780339,
|
|
36
|
+
0.1155056680537256,
|
|
37
|
+
0.1155056680537256,
|
|
38
|
+
0.10744427011596563,
|
|
39
|
+
0.10744427011596563,
|
|
40
|
+
0.09761865210411388,
|
|
41
|
+
0.09761865210411388,
|
|
42
|
+
0.08619016153195327,
|
|
43
|
+
0.08619016153195327,
|
|
44
|
+
0.0733464814110803,
|
|
45
|
+
0.0733464814110803,
|
|
46
|
+
0.05929858491543678,
|
|
47
|
+
0.05929858491543678,
|
|
48
|
+
0.04427743881741981,
|
|
49
|
+
0.04427743881741981,
|
|
50
|
+
0.028531388628933663,
|
|
51
|
+
0.028531388628933663,
|
|
52
|
+
0.0123412297999872,
|
|
53
|
+
0.0123412297999872
|
|
54
54
|
];
|
|
55
|
+
|
|
55
56
|
class BCurve {
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
57
|
+
controlPoints;
|
|
58
|
+
dControlPoints = [];
|
|
59
|
+
arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
60
|
+
_fullLength;
|
|
61
|
+
lengthCache = new Map;
|
|
62
|
+
getCacheStats() {
|
|
63
|
+
return {
|
|
64
|
+
size: this.lengthCache.size,
|
|
65
|
+
hitRate: 0
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
preWarmCache(steps = 100) {
|
|
69
|
+
const tSteps = 1 / steps;
|
|
70
|
+
for (let tVal = 0;tVal <= 1; tVal += tSteps) {
|
|
71
|
+
this.lengthAtT(tVal);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
clearCache() {
|
|
75
|
+
this.lengthCache.clear();
|
|
76
|
+
}
|
|
77
|
+
constructor(controlPoints) {
|
|
78
|
+
this.controlPoints = controlPoints;
|
|
79
|
+
this.dControlPoints = this.getDerivativeControlPoints(this.controlPoints);
|
|
80
|
+
this._fullLength = this.calculateFullLength();
|
|
81
|
+
this.arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
82
|
+
this.clearCache();
|
|
83
|
+
}
|
|
84
|
+
getPointbyPercentage(percentage) {
|
|
85
|
+
let controlPointsChangedSinceLastArcLengthLUT = this.arcLengthLUT.controlPoints.length != this.controlPoints.length;
|
|
86
|
+
controlPointsChangedSinceLastArcLengthLUT = controlPointsChangedSinceLastArcLengthLUT || this.arcLengthLUT.controlPoints.reduce((prevVal, curVal, index) => {
|
|
87
|
+
return prevVal || !PointCal.isEqual(curVal, this.controlPoints[index]);
|
|
88
|
+
}, false);
|
|
89
|
+
let points = [];
|
|
90
|
+
if (controlPointsChangedSinceLastArcLengthLUT) {
|
|
91
|
+
this.arcLengthLUT = this.getArcLengthLUT(1000);
|
|
92
|
+
}
|
|
93
|
+
points = [...this.arcLengthLUT.arcLengthLUT.map((item) => item.length)];
|
|
94
|
+
let targetLength = percentage * this.fullLength;
|
|
95
|
+
let low = 0;
|
|
96
|
+
let high = points.length - 1;
|
|
97
|
+
while (low <= high) {
|
|
98
|
+
let mid = Math.floor((low + high) / 2);
|
|
99
|
+
if (points[mid] == targetLength) {
|
|
100
|
+
return this.get((mid + 1) / points.length);
|
|
101
|
+
} else if (points[mid] < targetLength) {
|
|
102
|
+
low = mid + 1;
|
|
103
|
+
} else {
|
|
104
|
+
high = mid - 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return low >= points.length ? this.get(1) : this.get((low + 1) / points.length);
|
|
108
|
+
}
|
|
109
|
+
getDerivativeControlPoints(controlPoints) {
|
|
110
|
+
const derivativeControlPoints = [];
|
|
111
|
+
for (let index = 1;index < controlPoints.length; index++) {
|
|
112
|
+
derivativeControlPoints.push(PointCal.multiplyVectorByScalar(PointCal.subVector(controlPoints[index], controlPoints[index - 1]), controlPoints.length - 1));
|
|
113
|
+
}
|
|
114
|
+
return derivativeControlPoints;
|
|
115
|
+
}
|
|
116
|
+
validateTVal(tVal) {
|
|
117
|
+
if (tVal > 1 || tVal < 0) {
|
|
118
|
+
throw new TValOutofBoundError("tVal is greater than 1 or less than 0");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
getControlPoints() {
|
|
122
|
+
return this.controlPoints;
|
|
123
|
+
}
|
|
124
|
+
setControlPoints(controlPoints) {
|
|
125
|
+
this.controlPoints = controlPoints;
|
|
126
|
+
this.dControlPoints = this.getDerivativeControlPoints(this.controlPoints);
|
|
127
|
+
this._fullLength = this.calculateFullLength();
|
|
128
|
+
this.arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
129
|
+
this.clearCache();
|
|
130
|
+
}
|
|
131
|
+
setControlPointAtIndex(index, newPoint) {
|
|
132
|
+
if (index < 0 || index >= this.controlPoints.length) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
this.controlPoints[index] = newPoint;
|
|
136
|
+
this.dControlPoints = this.getDerivativeControlPoints(this.controlPoints);
|
|
137
|
+
this._fullLength = this.calculateFullLength();
|
|
138
|
+
this.arcLengthLUT = { controlPoints: [], arcLengthLUT: [] };
|
|
139
|
+
this.clearCache();
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
compute(tVal) {
|
|
143
|
+
this.validateTVal(tVal);
|
|
144
|
+
let points = this.controlPoints;
|
|
145
|
+
while (points.length > 1) {
|
|
146
|
+
let lowerLevelPoints = points.slice(1);
|
|
147
|
+
for (let index = 0;index < lowerLevelPoints.length; index++) {
|
|
148
|
+
lowerLevelPoints[index] = PointCal.addVector(PointCal.multiplyVectorByScalar(points[index], 1 - tVal), PointCal.multiplyVectorByScalar(points[index + 1], tVal));
|
|
149
|
+
}
|
|
150
|
+
points = lowerLevelPoints;
|
|
140
151
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
if (this.controlPoints.length == 4) {
|
|
175
|
-
let firstTerm = PointCal.multiplyVectorByScalar(this.controlPoints[0], (1 - tVal) * (1 - tVal) * (1 - tVal));
|
|
176
|
-
let secondTerm = PointCal.multiplyVectorByScalar(this.controlPoints[1], 3 * (1 - tVal) * (1 - tVal) * tVal);
|
|
177
|
-
let thirdTerm = PointCal.multiplyVectorByScalar(this.controlPoints[2], 3 * (1 - tVal) * tVal * tVal);
|
|
178
|
-
let forthTerm = PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal * tVal * tVal);
|
|
179
|
-
let res = PointCal.addVector(PointCal.addVector(firstTerm, secondTerm), PointCal.addVector(thirdTerm, forthTerm));
|
|
180
|
-
return res;
|
|
181
|
-
}
|
|
182
|
-
return this.compute(tVal);
|
|
183
|
-
}
|
|
184
|
-
getLUT(steps = 100) {
|
|
185
|
-
const stepSpan = 1 / steps;
|
|
186
|
-
const res = [];
|
|
187
|
-
let tVal = 0;
|
|
188
|
-
res.push(this.get(tVal));
|
|
189
|
-
for (let index = 0; index < steps; index += 1) {
|
|
190
|
-
tVal += stepSpan;
|
|
191
|
-
if ((tVal > 1 && tVal - stepSpan < 1) || index == steps - 1) {
|
|
192
|
-
tVal = 1;
|
|
193
|
-
}
|
|
194
|
-
res.push(this.get(tVal));
|
|
195
|
-
}
|
|
196
|
-
return res;
|
|
152
|
+
return points[0];
|
|
153
|
+
}
|
|
154
|
+
get(tVal) {
|
|
155
|
+
this.validateTVal(tVal);
|
|
156
|
+
if (this.controlPoints.length == 3) {
|
|
157
|
+
let firstTerm = PointCal.multiplyVectorByScalar(this.controlPoints[0], (1 - tVal) * (1 - tVal));
|
|
158
|
+
let secondTerm = PointCal.multiplyVectorByScalar(this.controlPoints[1], 2 * (1 - tVal) * tVal);
|
|
159
|
+
let thirdTerm = PointCal.multiplyVectorByScalar(this.controlPoints[2], tVal * tVal);
|
|
160
|
+
let res = PointCal.addVector(PointCal.addVector(firstTerm, secondTerm), thirdTerm);
|
|
161
|
+
return res;
|
|
162
|
+
}
|
|
163
|
+
if (this.controlPoints.length == 4) {
|
|
164
|
+
let firstTerm = PointCal.multiplyVectorByScalar(this.controlPoints[0], (1 - tVal) * (1 - tVal) * (1 - tVal));
|
|
165
|
+
let secondTerm = PointCal.multiplyVectorByScalar(this.controlPoints[1], 3 * (1 - tVal) * (1 - tVal) * tVal);
|
|
166
|
+
let thirdTerm = PointCal.multiplyVectorByScalar(this.controlPoints[2], 3 * (1 - tVal) * tVal * tVal);
|
|
167
|
+
let forthTerm = PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal * tVal * tVal);
|
|
168
|
+
let res = PointCal.addVector(PointCal.addVector(firstTerm, secondTerm), PointCal.addVector(thirdTerm, forthTerm));
|
|
169
|
+
return res;
|
|
170
|
+
}
|
|
171
|
+
return this.compute(tVal);
|
|
172
|
+
}
|
|
173
|
+
getLUT(steps = 100) {
|
|
174
|
+
const stepSpan = 1 / steps;
|
|
175
|
+
const res = [];
|
|
176
|
+
let tVal = 0;
|
|
177
|
+
res.push(this.get(tVal));
|
|
178
|
+
for (let index = 0;index < steps; index += 1) {
|
|
179
|
+
tVal += stepSpan;
|
|
180
|
+
if (tVal > 1 && tVal - stepSpan < 1 || index == steps - 1) {
|
|
181
|
+
tVal = 1;
|
|
182
|
+
}
|
|
183
|
+
res.push(this.get(tVal));
|
|
197
184
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
185
|
+
return res;
|
|
186
|
+
}
|
|
187
|
+
getLUTWithTVal(steps) {
|
|
188
|
+
if (steps == undefined) {
|
|
189
|
+
steps = 100;
|
|
190
|
+
}
|
|
191
|
+
const stepSpan = 1 / steps;
|
|
192
|
+
const res = [];
|
|
193
|
+
let tVal = 0;
|
|
194
|
+
res.push({ point: this.get(tVal), tVal });
|
|
195
|
+
for (let index = 0;index < steps; index += 1) {
|
|
196
|
+
tVal += stepSpan;
|
|
197
|
+
if (tVal > 1 && tVal - stepSpan < 1 || index == steps - 1) {
|
|
198
|
+
tVal = 1;
|
|
199
|
+
}
|
|
200
|
+
res.push({ point: this.get(tVal), tVal });
|
|
214
201
|
}
|
|
215
|
-
|
|
216
|
-
|
|
202
|
+
return res;
|
|
203
|
+
}
|
|
204
|
+
get fullLength() {
|
|
205
|
+
return this._fullLength;
|
|
206
|
+
}
|
|
207
|
+
calculateFullLength() {
|
|
208
|
+
return this.lengthAtT(1);
|
|
209
|
+
}
|
|
210
|
+
lengthAtT(tVal) {
|
|
211
|
+
this.validateTVal(tVal);
|
|
212
|
+
const cacheKey = Math.round(tVal * 1e6) / 1e6;
|
|
213
|
+
if (this.lengthCache.has(cacheKey)) {
|
|
214
|
+
return this.lengthCache.get(cacheKey);
|
|
215
|
+
}
|
|
216
|
+
const z = tVal / 2, len = T.length;
|
|
217
|
+
let sum = 0;
|
|
218
|
+
for (let i = 0, t;i < len; i++) {
|
|
219
|
+
t = z * T[i] + z;
|
|
220
|
+
sum += C[i] * PointCal.magnitude(this.derivative(t));
|
|
221
|
+
}
|
|
222
|
+
const result = z * sum;
|
|
223
|
+
this.lengthCache.set(cacheKey, result);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
derivative(tVal) {
|
|
227
|
+
return computeWithControlPoints(tVal, this.dControlPoints);
|
|
228
|
+
}
|
|
229
|
+
derivativeNormalized(tVal) {
|
|
230
|
+
return PointCal.unitVector(computeWithControlPoints(tVal, this.dControlPoints));
|
|
231
|
+
}
|
|
232
|
+
getArcLengthLUT(steps = 50) {
|
|
233
|
+
const controlPointsChanged = this.arcLengthLUT.controlPoints.length !== this.controlPoints.length || this.arcLengthLUT.controlPoints.some((cp, index) => !PointCal.isEqual(cp, this.controlPoints[index]));
|
|
234
|
+
if (controlPointsChanged || this.arcLengthLUT.arcLengthLUT.length === 0) {
|
|
235
|
+
this.clearCache();
|
|
236
|
+
let res = [];
|
|
237
|
+
let tSteps = 1 / steps;
|
|
238
|
+
for (let tVal = 0;tVal <= 1; tVal += tSteps) {
|
|
239
|
+
res.push({ tVal, length: this.lengthAtT(tVal) });
|
|
240
|
+
}
|
|
241
|
+
this.arcLengthLUT = { controlPoints: [...this.controlPoints], arcLengthLUT: res };
|
|
242
|
+
}
|
|
243
|
+
return this.arcLengthLUT;
|
|
244
|
+
}
|
|
245
|
+
splitIntoCurves(tVal) {
|
|
246
|
+
const res = this.split(tVal);
|
|
247
|
+
return [new BCurve(res[0]), new BCurve(res[1])];
|
|
248
|
+
}
|
|
249
|
+
split(tVal) {
|
|
250
|
+
this.validateTVal(tVal);
|
|
251
|
+
if (this.controlPoints.length == 3) {
|
|
252
|
+
let newControlPoint12 = this.controlPoints[0];
|
|
253
|
+
let newControlPoint22 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[1], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[0], tVal - 1));
|
|
254
|
+
let newControlPoint32 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[2], tVal * tVal), PointCal.multiplyVectorByScalar(this.controlPoints[1], 2 * tVal * (tVal - 1)));
|
|
255
|
+
newControlPoint32 = PointCal.addVector(newControlPoint32, PointCal.multiplyVectorByScalar(this.controlPoints[0], (tVal - 1) * (tVal - 1)));
|
|
256
|
+
let newControlPoint42 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[2], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[1], tVal - 1));
|
|
257
|
+
let newControlPoint52 = this.controlPoints[2];
|
|
258
|
+
return [[newControlPoint12, newControlPoint22, newControlPoint32], [newControlPoint32, newControlPoint42, newControlPoint52]];
|
|
259
|
+
}
|
|
260
|
+
let newControlPoint1 = this.controlPoints[0];
|
|
261
|
+
let newControlPoint2 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[1], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[0], tVal - 1));
|
|
262
|
+
let newControlPoint3 = PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[2], tVal * tVal), PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[1], -(2 * tVal * (tVal - 1))), PointCal.multiplyVectorByScalar(this.controlPoints[0], (tVal - 1) * (tVal - 1))));
|
|
263
|
+
let term1 = PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal * tVal * tVal);
|
|
264
|
+
let term2 = PointCal.multiplyVectorByScalar(this.controlPoints[2], -(3 * tVal * tVal * (tVal - 1)));
|
|
265
|
+
let term3 = PointCal.multiplyVectorByScalar(this.controlPoints[1], 3 * tVal * (tVal - 1) * (tVal - 1));
|
|
266
|
+
let term4 = PointCal.multiplyVectorByScalar(this.controlPoints[0], -((tVal - 1) * (tVal - 1) * (tVal - 1)));
|
|
267
|
+
let newControlPoint4 = PointCal.addVector(term4, PointCal.addVector(term3, PointCal.addVector(term1, term2)));
|
|
268
|
+
let newControlPoint5 = PointCal.addVector(PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal * tVal), PointCal.multiplyVectorByScalar(this.controlPoints[2], -(2 * tVal * (tVal - 1)))), PointCal.multiplyVectorByScalar(this.controlPoints[1], (tVal - 1) * (tVal - 1)));
|
|
269
|
+
let newControlPoint6 = PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[2], -(tVal - 1)));
|
|
270
|
+
let newControlPoint7 = this.controlPoints[3];
|
|
271
|
+
return [[newControlPoint1, newControlPoint2, newControlPoint3, newControlPoint4], [newControlPoint4, newControlPoint5, newControlPoint6, newControlPoint7]];
|
|
272
|
+
}
|
|
273
|
+
splitIn3WithControlPoints(tVal, tVal2) {
|
|
274
|
+
if (tVal2 < tVal) {
|
|
275
|
+
console.warn("tVal2 is less than tVal, swapping them");
|
|
276
|
+
[tVal, tVal2] = [tVal2, tVal];
|
|
277
|
+
}
|
|
278
|
+
const firstSplit = this.split(tVal);
|
|
279
|
+
const secondHalf = new BCurve(firstSplit[1]);
|
|
280
|
+
const mappedTVal2 = map(tVal2, tVal, 1, 0, 1);
|
|
281
|
+
const secondSplit = secondHalf.split(mappedTVal2);
|
|
282
|
+
return [firstSplit[0], secondSplit[0], secondSplit[1]];
|
|
283
|
+
}
|
|
284
|
+
splitIn3Curves(tVal, tVal2) {
|
|
285
|
+
if (tVal2 < tVal) {
|
|
286
|
+
console.warn("tVal2 is less than tVal, swapping them");
|
|
287
|
+
[tVal, tVal2] = [tVal2, tVal];
|
|
288
|
+
}
|
|
289
|
+
const firstSplit = this.split(tVal);
|
|
290
|
+
const secondHalf = new BCurve(firstSplit[1]);
|
|
291
|
+
const mappedTVal2 = map(tVal2, tVal, 1, 0, 1);
|
|
292
|
+
const secondSplit = secondHalf.split(mappedTVal2);
|
|
293
|
+
return [new BCurve(firstSplit[0]), new BCurve(secondSplit[0]), new BCurve(secondSplit[1])];
|
|
294
|
+
}
|
|
295
|
+
splitAndTakeMidCurve(tVal, tVal2) {
|
|
296
|
+
const [firstSplit, secondSplit, thirdSplit] = this.splitIn3Curves(tVal, tVal2);
|
|
297
|
+
return secondSplit;
|
|
298
|
+
}
|
|
299
|
+
getProjection(point) {
|
|
300
|
+
const threshold = 0.00001;
|
|
301
|
+
let distance = Number.MAX_VALUE;
|
|
302
|
+
let preliminaryProjectionTVal = 0;
|
|
303
|
+
let preliminaryProjectionPoint = this.get(0);
|
|
304
|
+
let preliminaryProjectionIndex = 0;
|
|
305
|
+
const LUT = this.getLUTWithTVal(500);
|
|
306
|
+
LUT.forEach((curvePoint, index) => {
|
|
307
|
+
const curDistance = PointCal.distanceBetweenPoints(curvePoint.point, point);
|
|
308
|
+
if (curDistance < distance) {
|
|
309
|
+
distance = curDistance;
|
|
310
|
+
preliminaryProjectionPoint = { ...curvePoint.point };
|
|
311
|
+
preliminaryProjectionTVal = curvePoint.tVal;
|
|
312
|
+
preliminaryProjectionIndex = index;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
let low = LUT[preliminaryProjectionIndex].tVal;
|
|
316
|
+
let high = LUT[preliminaryProjectionIndex].tVal;
|
|
317
|
+
if (preliminaryProjectionIndex < LUT.length - 1) {
|
|
318
|
+
high = LUT[preliminaryProjectionIndex + 1].tVal;
|
|
319
|
+
}
|
|
320
|
+
if (preliminaryProjectionIndex > 0) {
|
|
321
|
+
low = LUT[preliminaryProjectionIndex - 1].tVal;
|
|
322
|
+
}
|
|
323
|
+
while (low < high && high - low > threshold) {
|
|
324
|
+
let mid = low + (high - low) / 2;
|
|
325
|
+
let halfSpan = mid - low;
|
|
326
|
+
let lowMidMid = mid + halfSpan / 2;
|
|
327
|
+
let highMidMid = mid + halfSpan / 2;
|
|
328
|
+
let prevDist = distance;
|
|
329
|
+
if (lowMidMid <= 1 && lowMidMid >= 0) {
|
|
330
|
+
let curDist = PointCal.distanceBetweenPoints(this.get(lowMidMid), point);
|
|
331
|
+
if (curDist < distance) {
|
|
332
|
+
distance = curDist;
|
|
333
|
+
preliminaryProjectionPoint = this.get(lowMidMid);
|
|
334
|
+
preliminaryProjectionTVal = lowMidMid;
|
|
335
|
+
high = lowMidMid + halfSpan / 2;
|
|
336
|
+
low = lowMidMid - halfSpan / 2;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (highMidMid <= 1 && highMidMid >= 0) {
|
|
340
|
+
let curDist = PointCal.distanceBetweenPoints(this.get(highMidMid), point);
|
|
341
|
+
if (curDist < distance) {
|
|
342
|
+
distance = curDist;
|
|
343
|
+
preliminaryProjectionPoint = this.get(highMidMid);
|
|
344
|
+
preliminaryProjectionTVal = highMidMid;
|
|
345
|
+
high = highMidMid + halfSpan / 2;
|
|
346
|
+
low = highMidMid - halfSpan / 2;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (prevDist == distance) {
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return { projection: preliminaryProjectionPoint, tVal: preliminaryProjectionTVal };
|
|
354
|
+
}
|
|
355
|
+
findArcs(errorThreshold) {
|
|
356
|
+
let low = 0;
|
|
357
|
+
const res = [];
|
|
358
|
+
while (low < 1) {
|
|
359
|
+
let loopRes = this.findArcStartingAt(errorThreshold, low);
|
|
360
|
+
if (loopRes == null || loopRes.arc == undefined) {
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
res.push(loopRes.arc);
|
|
364
|
+
low = loopRes.arc.endT;
|
|
365
|
+
if (low >= 1) {
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
217
368
|
}
|
|
218
|
-
|
|
219
|
-
|
|
369
|
+
return res;
|
|
370
|
+
}
|
|
371
|
+
findArcStartingAt(errorThreshold, low) {
|
|
372
|
+
let high = 1;
|
|
373
|
+
let mid = low + (high - low) / 2;
|
|
374
|
+
let prevArc = { good: false };
|
|
375
|
+
let count = 0;
|
|
376
|
+
while (true) {
|
|
377
|
+
count++;
|
|
378
|
+
mid = low + (high - low) / 2;
|
|
379
|
+
if (high > 1 || mid > 1) {
|
|
380
|
+
if (prevArc.good) {
|
|
381
|
+
return prevArc;
|
|
382
|
+
} else {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const lowPoint = this.get(low);
|
|
387
|
+
const highPoint = this.get(high);
|
|
388
|
+
const midPoint = this.get(mid);
|
|
389
|
+
const fitArcRes = this.fitArc(lowPoint, highPoint, midPoint);
|
|
390
|
+
if (!fitArcRes.exists || fitArcRes.center == null || fitArcRes.radius == null) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
const n = high - mid;
|
|
394
|
+
const e1 = mid - n / 2;
|
|
395
|
+
const e2 = mid + n / 2;
|
|
396
|
+
const checkPoint1 = this.get(e1);
|
|
397
|
+
const checkPoint2 = this.get(e2);
|
|
398
|
+
const checkRadius = PointCal.distanceBetweenPoints(checkPoint1, fitArcRes.center);
|
|
399
|
+
const checkRadius2 = PointCal.distanceBetweenPoints(checkPoint2, fitArcRes.center);
|
|
400
|
+
if (Math.abs(checkRadius - fitArcRes.radius) > errorThreshold || Math.abs(checkRadius2 - fitArcRes.radius) > errorThreshold) {
|
|
401
|
+
if (prevArc.good == true) {
|
|
402
|
+
return prevArc;
|
|
403
|
+
}
|
|
404
|
+
prevArc.good = false;
|
|
405
|
+
high = mid;
|
|
406
|
+
} else {
|
|
407
|
+
prevArc.good = true;
|
|
408
|
+
if (fitArcRes.startPoint !== undefined && fitArcRes.endPoint !== undefined) {
|
|
409
|
+
prevArc.arc = { center: fitArcRes.center, radius: fitArcRes.radius, startPoint: fitArcRes.startPoint, endPoint: fitArcRes.endPoint, startT: low, endT: high };
|
|
410
|
+
}
|
|
411
|
+
high = high + (mid - low);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
fitArc(startPoint, endPoint, midPoint) {
|
|
416
|
+
const M11 = [[startPoint.x, startPoint.y, 1], [midPoint.x, midPoint.y, 1], [endPoint.x, endPoint.y, 1]];
|
|
417
|
+
if (this.determinant3by3(M11) == 0) {
|
|
418
|
+
return { exists: false };
|
|
419
|
+
}
|
|
420
|
+
const M12 = [
|
|
421
|
+
[startPoint.x * startPoint.x + startPoint.y * startPoint.y, startPoint.y, 1],
|
|
422
|
+
[midPoint.x * midPoint.x + midPoint.y * midPoint.y, midPoint.y, 1],
|
|
423
|
+
[endPoint.x * endPoint.x + endPoint.y * endPoint.y, endPoint.y, 1]
|
|
424
|
+
];
|
|
425
|
+
const M13 = [
|
|
426
|
+
[startPoint.x * startPoint.x + startPoint.y * startPoint.y, startPoint.x, 1],
|
|
427
|
+
[midPoint.x * midPoint.x + midPoint.y * midPoint.y, midPoint.x, 1],
|
|
428
|
+
[endPoint.x * endPoint.x + endPoint.y * endPoint.y, endPoint.x, 1]
|
|
429
|
+
];
|
|
430
|
+
const M14 = [
|
|
431
|
+
[startPoint.x * startPoint.x + startPoint.y * startPoint.y, startPoint.x, startPoint.y],
|
|
432
|
+
[midPoint.x * midPoint.x + midPoint.y * midPoint.y, midPoint.x, midPoint.y],
|
|
433
|
+
[endPoint.x * endPoint.x + endPoint.y * endPoint.y, endPoint.x, endPoint.y]
|
|
434
|
+
];
|
|
435
|
+
const centerX = 1 / 2 * (this.determinant3by3(M12) / this.determinant3by3(M11));
|
|
436
|
+
const centerY = -1 / 2 * (this.determinant3by3(M13) / this.determinant3by3(M11));
|
|
437
|
+
const radius = Math.sqrt(centerX * centerX + centerY * centerY + this.determinant3by3(M14) / this.determinant3by3(M11));
|
|
438
|
+
return { exists: true, center: { x: centerX, y: centerY }, radius, startPoint, endPoint };
|
|
439
|
+
}
|
|
440
|
+
determinant3by3(matrix) {
|
|
441
|
+
const a = matrix[0][0];
|
|
442
|
+
const b = matrix[0][1];
|
|
443
|
+
const c = matrix[0][2];
|
|
444
|
+
const d = matrix[1][0];
|
|
445
|
+
const e = matrix[1][1];
|
|
446
|
+
const f = matrix[1][2];
|
|
447
|
+
const g = matrix[2][0];
|
|
448
|
+
const h = matrix[2][1];
|
|
449
|
+
const i = matrix[2][2];
|
|
450
|
+
return a * (e * i - f * h) - b * (d * i - g * f) + c * (d * h - e * g);
|
|
451
|
+
}
|
|
452
|
+
curvature(tVal) {
|
|
453
|
+
const derivative = computeWithControlPoints(tVal, this.dControlPoints);
|
|
454
|
+
const secondDerivative = computeWithControlPoints(tVal, this.getDerivativeControlPoints(this.dControlPoints));
|
|
455
|
+
const numerator = derivative.x * secondDerivative.y - secondDerivative.x * derivative.y;
|
|
456
|
+
const denominator = Math.pow(derivative.x * derivative.x + derivative.y * derivative.y, 3 / 2);
|
|
457
|
+
if (denominator == 0)
|
|
458
|
+
return NaN;
|
|
459
|
+
return numerator / denominator;
|
|
460
|
+
}
|
|
461
|
+
secondDerivative(tVal) {
|
|
462
|
+
return computeWithControlPoints(tVal, this.getDerivativeControlPoints(this.dControlPoints));
|
|
463
|
+
}
|
|
464
|
+
getCoefficientOfTTerms() {
|
|
465
|
+
return this.getCoefficientOfTTermsWithControlPoints(this.controlPoints);
|
|
466
|
+
}
|
|
467
|
+
getDerivativeCoefficients() {
|
|
468
|
+
return this.getCoefficientOfTTermsWithControlPoints(this.dControlPoints);
|
|
469
|
+
}
|
|
470
|
+
getCoefficientOfTTermsWithControlPoints(controlPoints) {
|
|
471
|
+
const terms = [];
|
|
472
|
+
let matrix = [];
|
|
473
|
+
if (controlPoints.length == 3) {
|
|
474
|
+
matrix = [[1, 0, 0], [-2, 2, 0], [1, -2, 1]];
|
|
475
|
+
} else if (controlPoints.length == 4) {
|
|
476
|
+
matrix = [[1, 0, 0, 0], [-3, 3, 0, 0], [3, -6, 3, 0], [-1, 3, -3, 1]];
|
|
477
|
+
} else if (controlPoints.length == 2) {
|
|
478
|
+
matrix = [[1, 0], [-1, 1]];
|
|
479
|
+
} else {
|
|
480
|
+
throw new Error("number of control points is wrong");
|
|
481
|
+
}
|
|
482
|
+
for (let index = 0;index < controlPoints.length; index++) {
|
|
483
|
+
terms.push(controlPoints.reduce((prevVal, curVal, jindex) => {
|
|
484
|
+
return { x: prevVal.x + matrix[index][jindex] * curVal.x, y: prevVal.y + matrix[index][jindex] * curVal.y };
|
|
485
|
+
}, { x: 0, y: 0 }));
|
|
486
|
+
}
|
|
487
|
+
return terms;
|
|
488
|
+
}
|
|
489
|
+
getControlPointsAlignedWithXAxis() {
|
|
490
|
+
const alignedAxis = PointCal.unitVectorFromA2B(this.controlPoints[0], this.controlPoints[this.controlPoints.length - 1]);
|
|
491
|
+
const angle = PointCal.angleFromA2B({ x: 1, y: 0 }, alignedAxis);
|
|
492
|
+
const startingPoint = this.controlPoints[0];
|
|
493
|
+
const res = [{ x: 0, y: 0 }];
|
|
494
|
+
for (let index = 1;index < this.controlPoints.length; index++) {
|
|
495
|
+
const vector = PointCal.subVector(this.controlPoints[index], startingPoint);
|
|
496
|
+
const rotatedVector = PointCal.rotatePoint(vector, -angle);
|
|
497
|
+
res.push(rotatedVector);
|
|
220
498
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
let newControlPoint1 = this.controlPoints[0];
|
|
265
|
-
let newControlPoint2 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[1], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[0], tVal - 1));
|
|
266
|
-
let newControlPoint3 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[2], tVal * tVal), PointCal.multiplyVectorByScalar(this.controlPoints[1], 2 * tVal * (tVal - 1)));
|
|
267
|
-
newControlPoint3 = PointCal.addVector(newControlPoint3, PointCal.multiplyVectorByScalar(this.controlPoints[0], (tVal - 1) * (tVal - 1)));
|
|
268
|
-
let newControlPoint4 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[2], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[1], tVal - 1));
|
|
269
|
-
let newControlPoint5 = this.controlPoints[2];
|
|
270
|
-
return [[newControlPoint1, newControlPoint2, newControlPoint3], [newControlPoint3, newControlPoint4, newControlPoint5]];
|
|
271
|
-
}
|
|
272
|
-
let newControlPoint1 = this.controlPoints[0];
|
|
273
|
-
let newControlPoint2 = PointCal.subVector(PointCal.multiplyVectorByScalar(this.controlPoints[1], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[0], (tVal - 1)));
|
|
274
|
-
let newControlPoint3 = PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[2], tVal * tVal), PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[1], -(2 * tVal * (tVal - 1))), PointCal.multiplyVectorByScalar(this.controlPoints[0], (tVal - 1) * (tVal - 1))));
|
|
275
|
-
let term1 = PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal * tVal * tVal);
|
|
276
|
-
let term2 = PointCal.multiplyVectorByScalar(this.controlPoints[2], -(3 * tVal * tVal * (tVal - 1)));
|
|
277
|
-
let term3 = PointCal.multiplyVectorByScalar(this.controlPoints[1], 3 * tVal * (tVal - 1) * (tVal - 1));
|
|
278
|
-
let term4 = PointCal.multiplyVectorByScalar(this.controlPoints[0], -((tVal - 1) * (tVal - 1) * (tVal - 1)));
|
|
279
|
-
let newControlPoint4 = PointCal.addVector(term4, PointCal.addVector(term3, PointCal.addVector(term1, term2)));
|
|
280
|
-
let newControlPoint5 = PointCal.addVector(PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal * tVal), PointCal.multiplyVectorByScalar(this.controlPoints[2], -(2 * tVal * (tVal - 1)))), PointCal.multiplyVectorByScalar(this.controlPoints[1], (tVal - 1) * (tVal - 1)));
|
|
281
|
-
let newControlPoint6 = PointCal.addVector(PointCal.multiplyVectorByScalar(this.controlPoints[3], tVal), PointCal.multiplyVectorByScalar(this.controlPoints[2], -(tVal - 1)));
|
|
282
|
-
let newControlPoint7 = this.controlPoints[3];
|
|
283
|
-
return [[newControlPoint1, newControlPoint2, newControlPoint3, newControlPoint4], [newControlPoint4, newControlPoint5, newControlPoint6, newControlPoint7]];
|
|
284
|
-
}
|
|
285
|
-
splitIn3WithControlPoints(tVal, tVal2) {
|
|
286
|
-
if (tVal2 < tVal) {
|
|
287
|
-
console.warn("tVal2 is less than tVal, swapping them");
|
|
288
|
-
[tVal, tVal2] = [tVal2, tVal];
|
|
289
|
-
}
|
|
290
|
-
const firstSplit = this.split(tVal);
|
|
291
|
-
const secondHalf = new BCurve(firstSplit[1]);
|
|
292
|
-
const secondSplit = secondHalf.split(tVal2);
|
|
293
|
-
return [firstSplit[0], secondSplit[0], secondSplit[1]];
|
|
294
|
-
}
|
|
295
|
-
splitIn3Curves(tVal, tVal2) {
|
|
296
|
-
if (tVal2 < tVal) {
|
|
297
|
-
console.warn("tVal2 is less than tVal, swapping them");
|
|
298
|
-
[tVal, tVal2] = [tVal2, tVal];
|
|
299
|
-
}
|
|
300
|
-
const firstSplit = this.split(tVal);
|
|
301
|
-
const secondHalf = new BCurve(firstSplit[1]);
|
|
302
|
-
const secondSplit = secondHalf.split(tVal2);
|
|
303
|
-
return [new BCurve(firstSplit[0]), new BCurve(secondSplit[0]), new BCurve(secondSplit[1])];
|
|
304
|
-
}
|
|
305
|
-
splitAndTakeMidCurve(tVal, tVal2) {
|
|
306
|
-
const [firstSplit, secondSplit, thirdSplit] = this.splitIn3Curves(tVal, tVal2);
|
|
307
|
-
return secondSplit;
|
|
308
|
-
}
|
|
309
|
-
getProjection(point) {
|
|
310
|
-
const threshold = 0.00001;
|
|
311
|
-
let distance = Number.MAX_VALUE;
|
|
312
|
-
let preliminaryProjectionTVal = 0;
|
|
313
|
-
let preliminaryProjectionPoint = this.get(0);
|
|
314
|
-
let preliminaryProjectionIndex = 0;
|
|
315
|
-
const LUT = this.getLUTWithTVal(500);
|
|
316
|
-
LUT.forEach((curvePoint, index) => {
|
|
317
|
-
const curDistance = PointCal.distanceBetweenPoints(curvePoint.point, point);
|
|
318
|
-
if (curDistance < distance) {
|
|
319
|
-
distance = curDistance;
|
|
320
|
-
preliminaryProjectionPoint = { ...curvePoint.point };
|
|
321
|
-
preliminaryProjectionTVal = curvePoint.tVal;
|
|
322
|
-
preliminaryProjectionIndex = index;
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
// console.log(preliminaryProjectionIndex, preliminaryProjectionPoint, preliminaryProjectionTVal);
|
|
326
|
-
let low = LUT[preliminaryProjectionIndex].tVal;
|
|
327
|
-
let high = LUT[preliminaryProjectionIndex].tVal;
|
|
328
|
-
if (preliminaryProjectionIndex < LUT.length - 1) {
|
|
329
|
-
high = LUT[preliminaryProjectionIndex + 1].tVal;
|
|
330
|
-
}
|
|
331
|
-
if (preliminaryProjectionIndex > 0) {
|
|
332
|
-
low = LUT[preliminaryProjectionIndex - 1].tVal;
|
|
333
|
-
}
|
|
334
|
-
while (low < high && high - low > threshold) {
|
|
335
|
-
let mid = low + (high - low) / 2;
|
|
336
|
-
let halfSpan = mid - low;
|
|
337
|
-
let lowMidMid = mid + halfSpan / 2;
|
|
338
|
-
let highMidMid = mid + halfSpan / 2;
|
|
339
|
-
let prevDist = distance;
|
|
340
|
-
if (lowMidMid <= 1 && lowMidMid >= 0) {
|
|
341
|
-
let curDist = PointCal.distanceBetweenPoints(this.get(lowMidMid), point);
|
|
342
|
-
if (curDist < distance) {
|
|
343
|
-
distance = curDist;
|
|
344
|
-
preliminaryProjectionPoint = this.get(lowMidMid);
|
|
345
|
-
preliminaryProjectionTVal = lowMidMid;
|
|
346
|
-
high = lowMidMid + halfSpan / 2;
|
|
347
|
-
low = lowMidMid - halfSpan / 2;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (highMidMid <= 1 && highMidMid >= 0) {
|
|
351
|
-
let curDist = PointCal.distanceBetweenPoints(this.get(highMidMid), point);
|
|
352
|
-
if (curDist < distance) {
|
|
353
|
-
distance = curDist;
|
|
354
|
-
preliminaryProjectionPoint = this.get(highMidMid);
|
|
355
|
-
preliminaryProjectionTVal = highMidMid;
|
|
356
|
-
high = highMidMid + halfSpan / 2;
|
|
357
|
-
low = highMidMid - halfSpan / 2;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
if (prevDist == distance) {
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return { projection: preliminaryProjectionPoint, tVal: preliminaryProjectionTVal };
|
|
365
|
-
}
|
|
366
|
-
findArcs(errorThreshold) {
|
|
367
|
-
let low = 0;
|
|
368
|
-
const res = [];
|
|
369
|
-
while (low < 1) {
|
|
370
|
-
let loopRes = this.findArcStartingAt(errorThreshold, low);
|
|
371
|
-
if (loopRes == null || loopRes.arc == undefined) {
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
res.push(loopRes.arc);
|
|
375
|
-
low = loopRes.arc.endT;
|
|
376
|
-
if (low >= 1) {
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return res;
|
|
381
|
-
}
|
|
382
|
-
findArcStartingAt(errorThreshold, low) {
|
|
383
|
-
let high = 1;
|
|
384
|
-
let mid = low + (high - low) / 2;
|
|
385
|
-
let prevArc = { good: false };
|
|
386
|
-
while (true) {
|
|
387
|
-
mid = low + (high - low) / 2;
|
|
388
|
-
if (high > 1 || mid > 1) {
|
|
389
|
-
if (prevArc.good) {
|
|
390
|
-
return prevArc;
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
const lowPoint = this.get(low);
|
|
397
|
-
const highPoint = this.get(high);
|
|
398
|
-
const midPoint = this.get(mid);
|
|
399
|
-
const fitArcRes = this.fitArc(lowPoint, highPoint, midPoint);
|
|
400
|
-
if (!fitArcRes.exists || fitArcRes.center == null || fitArcRes.radius == null) {
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
const n = high - mid;
|
|
404
|
-
const e1 = mid - n / 2;
|
|
405
|
-
const e2 = mid + n / 2;
|
|
406
|
-
const checkPoint1 = this.get(e1);
|
|
407
|
-
const checkPoint2 = this.get(e2);
|
|
408
|
-
const checkRadius = PointCal.distanceBetweenPoints(checkPoint1, fitArcRes.center);
|
|
409
|
-
const checkRadius2 = PointCal.distanceBetweenPoints(checkPoint2, fitArcRes.center);
|
|
410
|
-
if (Math.abs(checkRadius - fitArcRes.radius) > errorThreshold || Math.abs(checkRadius2 - fitArcRes.radius) > errorThreshold) {
|
|
411
|
-
// arc is bad
|
|
412
|
-
if (prevArc.good == true) {
|
|
413
|
-
return prevArc;
|
|
414
|
-
}
|
|
415
|
-
prevArc.good = false;
|
|
416
|
-
high = mid;
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
prevArc.good = true;
|
|
420
|
-
if (fitArcRes.startPoint !== undefined && fitArcRes.endPoint !== undefined) {
|
|
421
|
-
prevArc.arc = { center: fitArcRes.center, radius: fitArcRes.radius, startPoint: fitArcRes.startPoint, endPoint: fitArcRes.endPoint, startT: low, endT: high };
|
|
422
|
-
}
|
|
423
|
-
high = high + (mid - low);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
499
|
+
return res;
|
|
500
|
+
}
|
|
501
|
+
getExtrema() {
|
|
502
|
+
const res = { x: [], y: [] };
|
|
503
|
+
const derivativeCoefficients = this.getDerivativeCoefficients();
|
|
504
|
+
let xCoefficients = [0, 0, 0, 0];
|
|
505
|
+
let yCoefficients = [0, 0, 0, 0];
|
|
506
|
+
derivativeCoefficients.forEach((coefficient, index) => {
|
|
507
|
+
xCoefficients[3 - index] = coefficient.x;
|
|
508
|
+
yCoefficients[3 - index] = coefficient.y;
|
|
509
|
+
});
|
|
510
|
+
const xRoots = solveCubic(xCoefficients[0], xCoefficients[1], xCoefficients[2], xCoefficients[3]);
|
|
511
|
+
const yRoots = solveCubic(yCoefficients[0], yCoefficients[1], yCoefficients[2], yCoefficients[3]);
|
|
512
|
+
xRoots.forEach((root) => {
|
|
513
|
+
if (root >= 0 && root <= 1) {
|
|
514
|
+
res.x.push(root);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
yRoots.forEach((root) => {
|
|
518
|
+
if (root >= 0 && root <= 1) {
|
|
519
|
+
res.y.push(root);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
if (derivativeCoefficients.length >= 3) {
|
|
523
|
+
xCoefficients = [0, 0, 0, 0];
|
|
524
|
+
yCoefficients = [0, 0, 0, 0];
|
|
525
|
+
const secondDerivativeCoefficients = this.getCoefficientOfTTermsWithControlPoints(this.getDerivativeControlPoints(this.dControlPoints));
|
|
526
|
+
secondDerivativeCoefficients.forEach((coefficient, index) => {
|
|
527
|
+
xCoefficients[3 - index] = coefficient.x;
|
|
528
|
+
yCoefficients[3 - index] = coefficient.y;
|
|
529
|
+
});
|
|
530
|
+
const secondXRoots = solveCubic(xCoefficients[0], xCoefficients[1], xCoefficients[2], xCoefficients[3]);
|
|
531
|
+
const secondYRoots = solveCubic(yCoefficients[0], yCoefficients[1], yCoefficients[2], yCoefficients[3]);
|
|
532
|
+
secondXRoots.forEach((root) => {
|
|
533
|
+
if (root >= 0 && root <= 1) {
|
|
534
|
+
res.x.push(root);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
secondYRoots.forEach((root) => {
|
|
538
|
+
if (root >= 0 && root <= 1) {
|
|
539
|
+
res.y.push(root);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
426
542
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const M12 = [[startPoint.x * startPoint.x + startPoint.y * startPoint.y, startPoint.y, 1],
|
|
434
|
-
[midPoint.x * midPoint.x + midPoint.y * midPoint.y, midPoint.y, 1],
|
|
435
|
-
[endPoint.x * endPoint.x + endPoint.y * endPoint.y, endPoint.y, 1]];
|
|
436
|
-
const M13 = [[startPoint.x * startPoint.x + startPoint.y * startPoint.y, startPoint.x, 1],
|
|
437
|
-
[midPoint.x * midPoint.x + midPoint.y * midPoint.y, midPoint.x, 1],
|
|
438
|
-
[endPoint.x * endPoint.x + endPoint.y * endPoint.y, endPoint.x, 1]];
|
|
439
|
-
const M14 = [[startPoint.x * startPoint.x + startPoint.y * startPoint.y, startPoint.x, startPoint.y],
|
|
440
|
-
[midPoint.x * midPoint.x + midPoint.y * midPoint.y, midPoint.x, midPoint.y],
|
|
441
|
-
[endPoint.x * endPoint.x + endPoint.y * endPoint.y, endPoint.x, endPoint.y]];
|
|
442
|
-
const centerX = (1 / 2) * (this.determinant3by3(M12) / this.determinant3by3(M11));
|
|
443
|
-
const centerY = (-1 / 2) * (this.determinant3by3(M13) / this.determinant3by3(M11));
|
|
444
|
-
const radius = Math.sqrt(centerX * centerX + centerY * centerY + (this.determinant3by3(M14) / this.determinant3by3(M11)));
|
|
445
|
-
return { exists: true, center: { x: centerX, y: centerY }, radius: radius, startPoint: startPoint, endPoint: endPoint };
|
|
446
|
-
}
|
|
447
|
-
determinant3by3(matrix) {
|
|
448
|
-
const a = matrix[0][0];
|
|
449
|
-
const b = matrix[0][1];
|
|
450
|
-
const c = matrix[0][2];
|
|
451
|
-
const d = matrix[1][0];
|
|
452
|
-
const e = matrix[1][1];
|
|
453
|
-
const f = matrix[1][2];
|
|
454
|
-
const g = matrix[2][0];
|
|
455
|
-
const h = matrix[2][1];
|
|
456
|
-
const i = matrix[2][2];
|
|
457
|
-
return a * (e * i - f * h) - b * (d * i - g * f) + c * (d * h - e * g);
|
|
458
|
-
}
|
|
459
|
-
curvature(tVal) {
|
|
460
|
-
const derivative = computeWithControlPoints(tVal, this.dControlPoints);
|
|
461
|
-
const secondDerivative = computeWithControlPoints(tVal, this.getDerivativeControlPoints(this.dControlPoints));
|
|
462
|
-
const numerator = derivative.x * secondDerivative.y - secondDerivative.x * derivative.y;
|
|
463
|
-
const denominator = Math.pow(derivative.x * derivative.x + derivative.y * derivative.y, 3 / 2);
|
|
464
|
-
if (denominator == 0)
|
|
465
|
-
return NaN;
|
|
466
|
-
return numerator / denominator;
|
|
467
|
-
}
|
|
468
|
-
secondDerivative(tVal) {
|
|
469
|
-
return computeWithControlPoints(tVal, this.getDerivativeControlPoints(this.dControlPoints));
|
|
470
|
-
}
|
|
471
|
-
getCoefficientOfTTerms() {
|
|
472
|
-
return this.getCoefficientOfTTermsWithControlPoints(this.controlPoints);
|
|
473
|
-
}
|
|
474
|
-
getDerivativeCoefficients() {
|
|
475
|
-
return this.getCoefficientOfTTermsWithControlPoints(this.dControlPoints);
|
|
476
|
-
}
|
|
477
|
-
getCoefficientOfTTermsWithControlPoints(controlPoints) {
|
|
478
|
-
const terms = [];
|
|
479
|
-
let matrix = [];
|
|
480
|
-
if (controlPoints.length == 3) {
|
|
481
|
-
matrix = [[1, 0, 0], [-2, 2, 0], [1, -2, 1]];
|
|
482
|
-
}
|
|
483
|
-
else if (controlPoints.length == 4) {
|
|
484
|
-
matrix = [[1, 0, 0, 0], [-3, 3, 0, 0], [3, -6, 3, 0], [-1, 3, -3, 1]];
|
|
485
|
-
}
|
|
486
|
-
else if (controlPoints.length == 2) {
|
|
487
|
-
matrix = [[1, 0], [-1, 1]];
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
throw new Error("number of control points is wrong");
|
|
491
|
-
}
|
|
492
|
-
for (let index = 0; index < controlPoints.length; index++) {
|
|
493
|
-
terms.push(controlPoints.reduce((prevVal, curVal, jindex) => {
|
|
494
|
-
return { x: prevVal.x + matrix[index][jindex] * curVal.x, y: prevVal.y + matrix[index][jindex] * curVal.y };
|
|
495
|
-
}, { x: 0, y: 0 }));
|
|
496
|
-
}
|
|
497
|
-
return terms;
|
|
498
|
-
}
|
|
499
|
-
getControlPointsAlignedWithXAxis() {
|
|
500
|
-
const alignedAxis = PointCal.unitVectorFromA2B(this.controlPoints[0], this.controlPoints[this.controlPoints.length - 1]);
|
|
501
|
-
const angle = PointCal.angleFromA2B({ x: 1, y: 0 }, alignedAxis);
|
|
502
|
-
const startingPoint = this.controlPoints[0];
|
|
503
|
-
const res = [{ x: 0, y: 0 }];
|
|
504
|
-
for (let index = 1; index < this.controlPoints.length; index++) {
|
|
505
|
-
const vector = PointCal.subVector(this.controlPoints[index], startingPoint);
|
|
506
|
-
const rotatedVector = PointCal.rotatePoint(vector, -angle);
|
|
507
|
-
res.push(rotatedVector);
|
|
508
|
-
}
|
|
509
|
-
return res;
|
|
510
|
-
}
|
|
511
|
-
getExtrema() {
|
|
512
|
-
const res = { x: [], y: [] };
|
|
513
|
-
const derivativeCoefficients = this.getDerivativeCoefficients();
|
|
514
|
-
let xCoefficients = [0, 0, 0, 0];
|
|
515
|
-
let yCoefficients = [0, 0, 0, 0];
|
|
516
|
-
derivativeCoefficients.forEach((coefficient, index) => {
|
|
517
|
-
xCoefficients[3 - index] = coefficient.x;
|
|
518
|
-
yCoefficients[3 - index] = coefficient.y;
|
|
519
|
-
});
|
|
520
|
-
const xRoots = solveCubic(xCoefficients[0], xCoefficients[1], xCoefficients[2], xCoefficients[3]);
|
|
521
|
-
const yRoots = solveCubic(yCoefficients[0], yCoefficients[1], yCoefficients[2], yCoefficients[3]);
|
|
522
|
-
xRoots.forEach((root) => {
|
|
523
|
-
if (root >= 0 && root <= 1) {
|
|
524
|
-
res.x.push(root);
|
|
525
|
-
}
|
|
526
|
-
});
|
|
527
|
-
yRoots.forEach((root) => {
|
|
528
|
-
if (root >= 0 && root <= 1) {
|
|
529
|
-
res.y.push(root);
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
if (derivativeCoefficients.length >= 3) {
|
|
533
|
-
xCoefficients = [0, 0, 0, 0];
|
|
534
|
-
yCoefficients = [0, 0, 0, 0];
|
|
535
|
-
const secondDerivativeCoefficients = this.getCoefficientOfTTermsWithControlPoints(this.getDerivativeControlPoints(this.dControlPoints));
|
|
536
|
-
secondDerivativeCoefficients.forEach((coefficient, index) => {
|
|
537
|
-
xCoefficients[3 - index] = coefficient.x;
|
|
538
|
-
yCoefficients[3 - index] = coefficient.y;
|
|
539
|
-
});
|
|
540
|
-
const secondXRoots = solveCubic(xCoefficients[0], xCoefficients[1], xCoefficients[2], xCoefficients[3]);
|
|
541
|
-
const secondYRoots = solveCubic(yCoefficients[0], yCoefficients[1], yCoefficients[2], yCoefficients[3]);
|
|
542
|
-
secondXRoots.forEach((root) => {
|
|
543
|
-
if (root >= 0 && root <= 1) {
|
|
544
|
-
res.x.push(root);
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
secondYRoots.forEach((root) => {
|
|
548
|
-
if (root >= 0 && root <= 1) {
|
|
549
|
-
res.y.push(root);
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
return res;
|
|
543
|
+
return res;
|
|
544
|
+
}
|
|
545
|
+
translateRotateControlPoints(translation, rotationAngle) {
|
|
546
|
+
const res = [];
|
|
547
|
+
for (let index = 0;index < this.controlPoints.length; index++) {
|
|
548
|
+
res.push(PointCal.rotatePoint(PointCal.addVector(this.controlPoints[index], translation), rotationAngle));
|
|
554
549
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
});
|
|
572
|
-
const yRoots = solveCubic(yCoefficients[0], yCoefficients[1], yCoefficients[2], yCoefficients[3]);
|
|
573
|
-
yRoots.forEach((root) => {
|
|
574
|
-
if (root >= 0 && root <= 1) {
|
|
575
|
-
if (line.pointInLine(this.get(root))) {
|
|
576
|
-
res.push(root);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
return res;
|
|
581
|
-
}
|
|
582
|
-
getSelfIntersections() {
|
|
583
|
-
const [subCurveControlPoints1, subCurveControlPoints2] = this.split(0.5);
|
|
584
|
-
const subCurve1 = new BCurve(subCurveControlPoints1);
|
|
585
|
-
const subCurve2 = new BCurve(subCurveControlPoints2);
|
|
586
|
-
let initialRes = getIntersectionsBetweenCurves(subCurve1, subCurve2);
|
|
587
|
-
initialRes.forEach((intersection) => {
|
|
588
|
-
intersection.selfT = intersection.selfT * 0.5;
|
|
589
|
-
intersection.otherT = intersection.otherT * 0.5 + 0.5;
|
|
590
|
-
});
|
|
591
|
-
initialRes.shift();
|
|
592
|
-
return initialRes;
|
|
593
|
-
}
|
|
594
|
-
getCircleIntersections(circleCenter, circleRadius) {
|
|
595
|
-
const LUT = this.getLUTWithTVal(500);
|
|
596
|
-
LUT[0].point;
|
|
597
|
-
LUT[0].tVal;
|
|
598
|
-
LUT.forEach((curvePoint, index) => {
|
|
599
|
-
Math.abs(PointCal.distanceBetweenPoints(circleCenter, curvePoint.point));
|
|
600
|
-
});
|
|
601
|
-
const LUTD = LUT.map((curvePoint, index) => {
|
|
602
|
-
return { ...curvePoint, distance: 0 };
|
|
603
|
-
});
|
|
604
|
-
let start = 0;
|
|
605
|
-
let count = 0;
|
|
606
|
-
let indices = [];
|
|
607
|
-
while (++count < 25) {
|
|
608
|
-
let i = this.findClosest(circleCenter.x, circleCenter.y, LUTD, circleRadius, 5, LUTD[start - 2]?.distance, LUTD[start - 1]?.distance);
|
|
609
|
-
if (i < start)
|
|
610
|
-
break;
|
|
611
|
-
if (i > 0 && i == start)
|
|
612
|
-
break;
|
|
613
|
-
indices.push(i);
|
|
614
|
-
start = i + 2;
|
|
615
|
-
}
|
|
616
|
-
const finalList = [];
|
|
617
|
-
indices.forEach((index) => {
|
|
618
|
-
let res = this.refineBinary(this, circleCenter.x, circleCenter.y, LUTD, index, circleRadius);
|
|
619
|
-
if (res != undefined) {
|
|
620
|
-
finalList.push({ intersection: res.point, tVal: res.tVal });
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
return finalList;
|
|
624
|
-
}
|
|
625
|
-
advanceAtTWithLength(tVal, length) {
|
|
626
|
-
const currentLength = this.lengthAtT(tVal);
|
|
627
|
-
const targetLength = currentLength + length;
|
|
628
|
-
// Handle edge cases first
|
|
629
|
-
if (tVal === 0 && length < 0) {
|
|
630
|
-
return { type: "beforeCurve", remainLength: -length };
|
|
631
|
-
}
|
|
632
|
-
if (tVal === 1 && length > 0) {
|
|
633
|
-
return { type: "afterCurve", remainLength: length };
|
|
634
|
-
}
|
|
635
|
-
if (targetLength > this.fullLength) {
|
|
636
|
-
return { type: "afterCurve", remainLength: targetLength - this.fullLength };
|
|
637
|
-
}
|
|
638
|
-
else if (targetLength < 0) {
|
|
639
|
-
return { type: "beforeCurve", remainLength: -targetLength };
|
|
640
|
-
}
|
|
641
|
-
// Use LUT for binary search
|
|
642
|
-
if (this.arcLengthLUT.arcLengthLUT.length === 0) {
|
|
643
|
-
this.arcLengthLUT = this.getArcLengthLUT(1000);
|
|
644
|
-
}
|
|
645
|
-
const points = this.arcLengthLUT.arcLengthLUT;
|
|
646
|
-
let low = 0;
|
|
647
|
-
let high = points.length - 1;
|
|
648
|
-
// Binary search to find the interval containing targetLength
|
|
649
|
-
while (low <= high) {
|
|
650
|
-
const mid = Math.floor((low + high) / 2);
|
|
651
|
-
const midLength = points[mid].length;
|
|
652
|
-
if (approximately(midLength, targetLength, 0.01)) {
|
|
653
|
-
// Found exact match
|
|
654
|
-
const resultTVal = points[mid].tVal;
|
|
655
|
-
const point = this.get(resultTVal);
|
|
656
|
-
return { type: "withinCurve", tVal: resultTVal, point: point };
|
|
657
|
-
}
|
|
658
|
-
else if (midLength < targetLength) {
|
|
659
|
-
low = mid + 1;
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
high = mid - 1;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
// After binary search, 'high' points to the largest index with length < targetLength
|
|
666
|
-
// and 'low' points to the smallest index with length >= targetLength
|
|
667
|
-
// Handle edge cases
|
|
668
|
-
if (high < 0) {
|
|
669
|
-
// targetLength is smaller than the first point's length
|
|
670
|
-
high = 1;
|
|
671
|
-
low = 0;
|
|
672
|
-
}
|
|
673
|
-
if (low >= points.length) {
|
|
674
|
-
// targetLength is larger than the last point's length
|
|
675
|
-
high = points.length - 1;
|
|
676
|
-
low = points.length - 2;
|
|
677
|
-
}
|
|
678
|
-
// Interpolate between points[high] and points[low]
|
|
679
|
-
const p1 = points[high];
|
|
680
|
-
const p2 = points[low];
|
|
681
|
-
// Linear interpolation
|
|
682
|
-
const lengthRange = p2.length - p1.length;
|
|
683
|
-
const tRange = p2.tVal - p1.tVal;
|
|
684
|
-
if (lengthRange === 0) {
|
|
685
|
-
// Both points have the same length, use the first one
|
|
686
|
-
const point = this.get(p1.tVal);
|
|
687
|
-
return { type: "withinCurve", tVal: p1.tVal, point: point };
|
|
688
|
-
}
|
|
689
|
-
const ratio = (targetLength - p1.length) / lengthRange;
|
|
690
|
-
const interpolatedT = p1.tVal + ratio * tRange;
|
|
691
|
-
// Clamp to valid range
|
|
692
|
-
const clampedT = Math.max(0, Math.min(1, interpolatedT));
|
|
693
|
-
const point = this.get(clampedT);
|
|
694
|
-
return { type: "withinCurve", tVal: clampedT, point: point };
|
|
695
|
-
}
|
|
696
|
-
advanceByDistance(startT, distance) {
|
|
697
|
-
let currentT = startT;
|
|
698
|
-
let remainingDistance = distance;
|
|
699
|
-
const stepSize = 0.01; // Adjust for precision vs performance
|
|
700
|
-
if (distance > this.fullLength) {
|
|
701
|
-
return { type: "afterCurve", remainLength: distance - this.fullLength };
|
|
702
|
-
}
|
|
703
|
-
else if (distance < 0) {
|
|
704
|
-
return { type: "beforeCurve", remainLength: -distance };
|
|
705
|
-
}
|
|
706
|
-
while (remainingDistance > 0 && currentT < 1) {
|
|
707
|
-
const currentPoint = this.get(currentT);
|
|
708
|
-
const nextT = Math.min(currentT + stepSize, 1);
|
|
709
|
-
const nextPoint = this.get(nextT);
|
|
710
|
-
const segmentLength = Math.sqrt(Math.pow(nextPoint.x - currentPoint.x, 2) +
|
|
711
|
-
Math.pow(nextPoint.y - currentPoint.y, 2));
|
|
712
|
-
if (segmentLength >= remainingDistance) {
|
|
713
|
-
// Interpolate within this segment
|
|
714
|
-
const ratio = remainingDistance / segmentLength;
|
|
715
|
-
return { type: "withinCurve", tVal: currentT + ratio * (nextT - currentT), point: this.get(currentT + ratio * (nextT - currentT)) };
|
|
716
|
-
}
|
|
717
|
-
remainingDistance -= segmentLength;
|
|
718
|
-
currentT = nextT;
|
|
719
|
-
}
|
|
720
|
-
return { type: "withinCurve", tVal: currentT, point: this.get(currentT) };
|
|
721
|
-
}
|
|
722
|
-
refineBinary(curve, x, y, LUT, i, targetDistance = 0, epsilon = 0.01) {
|
|
723
|
-
let q = LUT[i], count = 1, distance = Number.MAX_SAFE_INTEGER;
|
|
724
|
-
do {
|
|
725
|
-
let i1 = i === 0 ? 0 : i - 1, i2 = i === LUT.length - 1 ? LUT.length - 1 : i + 1, t1 = LUT[i1].tVal, t2 = LUT[i2].tVal, lut = [], step = (t2 - t1) / 4;
|
|
726
|
-
if (step < 0.001)
|
|
727
|
-
break;
|
|
728
|
-
lut.push(LUT[i1]);
|
|
729
|
-
for (let j = 1; j <= 3; j++) {
|
|
730
|
-
let n = curve.get(t1 + j * step);
|
|
731
|
-
let nDistance = Math.abs(PointCal.distanceBetweenPoints(n, { x: x, y: y }) - targetDistance);
|
|
732
|
-
if (nDistance < distance) {
|
|
733
|
-
distance = nDistance;
|
|
734
|
-
q = { point: n, tVal: t1 + j * step, distance: nDistance };
|
|
735
|
-
i = j;
|
|
736
|
-
}
|
|
737
|
-
lut.push({ point: n, tVal: t1 + j * step, distance: nDistance });
|
|
738
|
-
}
|
|
739
|
-
lut.push(LUT[i2]);
|
|
740
|
-
// update the LUT to be our new five point LUT, and run again.
|
|
741
|
-
LUT = lut;
|
|
742
|
-
// The "count" test is mostly a safety measure: it will
|
|
743
|
-
// never kick in, but something that _will_ terminate is
|
|
744
|
-
// always better than while(true). Never use while(true)
|
|
745
|
-
} while (count++ < 25);
|
|
746
|
-
// If we're trying to hit a target, discard the result if
|
|
747
|
-
// it is not close enough to the target.
|
|
748
|
-
if (targetDistance && distance > epsilon) {
|
|
749
|
-
q = undefined;
|
|
750
|
-
}
|
|
751
|
-
return q;
|
|
752
|
-
}
|
|
753
|
-
findClosest(x, y, LUT, circleRadius, distanceEpsilon = 5, pd2, pd1) {
|
|
754
|
-
let distance = Number.MAX_SAFE_INTEGER, prevDistance2 = pd2 || distance, prevDistance1 = pd1 || distance, i = -1;
|
|
755
|
-
for (let index = 0, e = LUT.length; index < e; index++) {
|
|
756
|
-
let p = LUT[index].point;
|
|
757
|
-
LUT[index].distance = Math.abs(PointCal.distanceBetweenPoints({ x: x, y: y }, p) - circleRadius);
|
|
758
|
-
// Realistically, there's only going to be an intersection if
|
|
759
|
-
// the distance to the circle center is already approximately
|
|
760
|
-
// the circle's radius.
|
|
761
|
-
if (prevDistance1 < distanceEpsilon && prevDistance2 > prevDistance1 && prevDistance1 < LUT[index].distance) {
|
|
762
|
-
i = index - 1;
|
|
763
|
-
break;
|
|
764
|
-
}
|
|
765
|
-
if (LUT[index].distance < distance) {
|
|
766
|
-
distance = LUT[index].distance;
|
|
767
|
-
}
|
|
768
|
-
prevDistance2 = prevDistance1;
|
|
769
|
-
prevDistance1 = LUT[index].distance;
|
|
550
|
+
return res;
|
|
551
|
+
}
|
|
552
|
+
getLineIntersections(line) {
|
|
553
|
+
const translationRotation = line.getTranslationRotationToAlginXAxis();
|
|
554
|
+
const res = [];
|
|
555
|
+
const alignedControlPoints = this.translateRotateControlPoints(translationRotation.translation, translationRotation.rotationAngle);
|
|
556
|
+
const coefficients = this.getCoefficientOfTTermsWithControlPoints(alignedControlPoints);
|
|
557
|
+
let yCoefficients = [0, 0, 0, 0];
|
|
558
|
+
coefficients.forEach((coefficient, index) => {
|
|
559
|
+
yCoefficients[3 - index] = coefficient.y;
|
|
560
|
+
});
|
|
561
|
+
const yRoots = solveCubic(yCoefficients[0], yCoefficients[1], yCoefficients[2], yCoefficients[3]);
|
|
562
|
+
yRoots.forEach((root) => {
|
|
563
|
+
if (root >= 0 && root <= 1) {
|
|
564
|
+
if (line.pointInLine(this.get(root))) {
|
|
565
|
+
res.push(root);
|
|
770
566
|
}
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
return res;
|
|
570
|
+
}
|
|
571
|
+
getSelfIntersections() {
|
|
572
|
+
const [subCurveControlPoints1, subCurveControlPoints2] = this.split(0.5);
|
|
573
|
+
const subCurve1 = new BCurve(subCurveControlPoints1);
|
|
574
|
+
const subCurve2 = new BCurve(subCurveControlPoints2);
|
|
575
|
+
let initialRes = getIntersectionsBetweenCurves(subCurve1, subCurve2);
|
|
576
|
+
initialRes.forEach((intersection) => {
|
|
577
|
+
intersection.selfT = intersection.selfT * 0.5;
|
|
578
|
+
intersection.otherT = intersection.otherT * 0.5 + 0.5;
|
|
579
|
+
});
|
|
580
|
+
initialRes.shift();
|
|
581
|
+
return initialRes;
|
|
582
|
+
}
|
|
583
|
+
getCircleIntersections(circleCenter, circleRadius) {
|
|
584
|
+
const LUT = this.getLUTWithTVal(500);
|
|
585
|
+
let distanceError = Number.MAX_VALUE;
|
|
586
|
+
let preliminaryIntersectionIndex = 0;
|
|
587
|
+
let preliminaryIntersectionPoint = LUT[0].point;
|
|
588
|
+
let preliminaryIntersectionTVal = LUT[0].tVal;
|
|
589
|
+
LUT.forEach((curvePoint, index) => {
|
|
590
|
+
let curDistanceError = Math.abs(PointCal.distanceBetweenPoints(circleCenter, curvePoint.point));
|
|
591
|
+
if (curDistanceError < distanceError) {
|
|
592
|
+
distanceError = curDistanceError;
|
|
593
|
+
preliminaryIntersectionIndex = index;
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
const LUTD = LUT.map((curvePoint, index) => {
|
|
597
|
+
return { ...curvePoint, distance: 0 };
|
|
598
|
+
});
|
|
599
|
+
distanceError = Number.MAX_VALUE;
|
|
600
|
+
let start = 0;
|
|
601
|
+
let count = 0;
|
|
602
|
+
let indices = [];
|
|
603
|
+
while (++count < 25) {
|
|
604
|
+
let i = this.findClosest(circleCenter.x, circleCenter.y, LUTD, circleRadius, 5, LUTD[start - 2]?.distance, LUTD[start - 1]?.distance);
|
|
605
|
+
if (i < start)
|
|
606
|
+
break;
|
|
607
|
+
if (i > 0 && i == start)
|
|
608
|
+
break;
|
|
609
|
+
indices.push(i);
|
|
610
|
+
start = i + 2;
|
|
611
|
+
}
|
|
612
|
+
const finalList = [];
|
|
613
|
+
indices.forEach((index) => {
|
|
614
|
+
let res = this.refineBinary(this, circleCenter.x, circleCenter.y, LUTD, index, circleRadius);
|
|
615
|
+
if (res != null) {
|
|
616
|
+
finalList.push({ intersection: res.point, tVal: res.tVal });
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
return finalList;
|
|
620
|
+
}
|
|
621
|
+
advanceAtTWithLength(tVal, length) {
|
|
622
|
+
const currentLength = this.lengthAtT(tVal);
|
|
623
|
+
const targetLength = currentLength + length;
|
|
624
|
+
if (tVal === 0 && length < 0) {
|
|
625
|
+
return { type: "beforeCurve", remainLength: -length };
|
|
626
|
+
}
|
|
627
|
+
if (tVal === 1 && length > 0) {
|
|
628
|
+
return { type: "afterCurve", remainLength: length };
|
|
629
|
+
}
|
|
630
|
+
if (targetLength > this.fullLength) {
|
|
631
|
+
return { type: "afterCurve", remainLength: targetLength - this.fullLength };
|
|
632
|
+
} else if (targetLength < 0) {
|
|
633
|
+
return { type: "beforeCurve", remainLength: -targetLength };
|
|
634
|
+
}
|
|
635
|
+
if (this.arcLengthLUT.arcLengthLUT.length === 0) {
|
|
636
|
+
this.arcLengthLUT = this.getArcLengthLUT(1000);
|
|
637
|
+
}
|
|
638
|
+
const points = this.arcLengthLUT.arcLengthLUT;
|
|
639
|
+
let low = 0;
|
|
640
|
+
let high = points.length - 1;
|
|
641
|
+
while (low <= high) {
|
|
642
|
+
const mid = Math.floor((low + high) / 2);
|
|
643
|
+
const midLength = points[mid].length;
|
|
644
|
+
if (approximately(midLength, targetLength, 0.01)) {
|
|
645
|
+
const resultTVal = points[mid].tVal;
|
|
646
|
+
const point2 = this.get(resultTVal);
|
|
647
|
+
return { type: "withinCurve", tVal: resultTVal, point: point2 };
|
|
648
|
+
} else if (midLength < targetLength) {
|
|
649
|
+
low = mid + 1;
|
|
650
|
+
} else {
|
|
651
|
+
high = mid - 1;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (high < 0) {
|
|
655
|
+
high = 1;
|
|
656
|
+
low = 0;
|
|
657
|
+
}
|
|
658
|
+
if (low >= points.length) {
|
|
659
|
+
high = points.length - 1;
|
|
660
|
+
low = points.length - 2;
|
|
661
|
+
}
|
|
662
|
+
const p1 = points[high];
|
|
663
|
+
const p2 = points[low];
|
|
664
|
+
const lengthRange = p2.length - p1.length;
|
|
665
|
+
const tRange = p2.tVal - p1.tVal;
|
|
666
|
+
if (lengthRange === 0) {
|
|
667
|
+
const point2 = this.get(p1.tVal);
|
|
668
|
+
return { type: "withinCurve", tVal: p1.tVal, point: point2 };
|
|
669
|
+
}
|
|
670
|
+
const ratio = (targetLength - p1.length) / lengthRange;
|
|
671
|
+
const interpolatedT = p1.tVal + ratio * tRange;
|
|
672
|
+
const clampedT = Math.max(0, Math.min(1, interpolatedT));
|
|
673
|
+
const point = this.get(clampedT);
|
|
674
|
+
return { type: "withinCurve", tVal: clampedT, point };
|
|
675
|
+
}
|
|
676
|
+
advanceByDistance(startT, distance) {
|
|
677
|
+
let currentT = startT;
|
|
678
|
+
let remainingDistance = distance;
|
|
679
|
+
const stepSize = 0.01;
|
|
680
|
+
if (distance > this.fullLength) {
|
|
681
|
+
return { type: "afterCurve", remainLength: distance - this.fullLength };
|
|
682
|
+
} else if (distance < 0) {
|
|
683
|
+
return { type: "beforeCurve", remainLength: -distance };
|
|
684
|
+
}
|
|
685
|
+
while (remainingDistance > 0 && currentT < 1) {
|
|
686
|
+
const currentPoint = this.get(currentT);
|
|
687
|
+
const nextT = Math.min(currentT + stepSize, 1);
|
|
688
|
+
const nextPoint = this.get(nextT);
|
|
689
|
+
const segmentLength = Math.sqrt(Math.pow(nextPoint.x - currentPoint.x, 2) + Math.pow(nextPoint.y - currentPoint.y, 2));
|
|
690
|
+
if (segmentLength >= remainingDistance) {
|
|
691
|
+
const ratio = remainingDistance / segmentLength;
|
|
692
|
+
return { type: "withinCurve", tVal: currentT + ratio * (nextT - currentT), point: this.get(currentT + ratio * (nextT - currentT)) };
|
|
693
|
+
}
|
|
694
|
+
remainingDistance -= segmentLength;
|
|
695
|
+
currentT = nextT;
|
|
696
|
+
}
|
|
697
|
+
return { type: "withinCurve", tVal: currentT, point: this.get(currentT) };
|
|
698
|
+
}
|
|
699
|
+
refineBinary(curve, x, y, LUT, i, targetDistance = 0, epsilon = 0.01) {
|
|
700
|
+
let q = LUT[i], count = 1, distance = Number.MAX_SAFE_INTEGER;
|
|
701
|
+
do {
|
|
702
|
+
let i1 = i === 0 ? 0 : i - 1, i2 = i === LUT.length - 1 ? LUT.length - 1 : i + 1, t1 = LUT[i1].tVal, t2 = LUT[i2].tVal, lut = [], step = (t2 - t1) / 4;
|
|
703
|
+
if (step < 0.001)
|
|
704
|
+
break;
|
|
705
|
+
lut.push(LUT[i1]);
|
|
706
|
+
for (let j = 1;j <= 3; j++) {
|
|
707
|
+
let n = curve.get(t1 + j * step);
|
|
708
|
+
let nDistance = Math.abs(PointCal.distanceBetweenPoints(n, { x, y }) - targetDistance);
|
|
709
|
+
if (nDistance < distance) {
|
|
710
|
+
distance = nDistance;
|
|
711
|
+
q = { point: n, tVal: t1 + j * step, distance: nDistance };
|
|
712
|
+
i = j;
|
|
713
|
+
}
|
|
714
|
+
lut.push({ point: n, tVal: t1 + j * step, distance: nDistance });
|
|
715
|
+
}
|
|
716
|
+
lut.push(LUT[i2]);
|
|
717
|
+
LUT = lut;
|
|
718
|
+
} while (count++ < 25);
|
|
719
|
+
if (targetDistance && distance > epsilon) {
|
|
720
|
+
q = undefined;
|
|
721
|
+
}
|
|
722
|
+
return q;
|
|
723
|
+
}
|
|
724
|
+
findClosest(x, y, LUT, circleRadius, distanceEpsilon = 5, pd2, pd1) {
|
|
725
|
+
let distance = Number.MAX_SAFE_INTEGER, prevDistance2 = pd2 || distance, prevDistance1 = pd1 || distance, i = -1;
|
|
726
|
+
for (let index = 0, e = LUT.length;index < e; index++) {
|
|
727
|
+
let p = LUT[index].point;
|
|
728
|
+
LUT[index].distance = Math.abs(PointCal.distanceBetweenPoints({ x, y }, p) - circleRadius);
|
|
729
|
+
if (prevDistance1 < distanceEpsilon && prevDistance2 > prevDistance1 && prevDistance1 < LUT[index].distance) {
|
|
730
|
+
i = index - 1;
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
if (LUT[index].distance < distance) {
|
|
734
|
+
distance = LUT[index].distance;
|
|
735
|
+
}
|
|
736
|
+
prevDistance2 = prevDistance1;
|
|
737
|
+
prevDistance1 = LUT[index].distance;
|
|
738
|
+
}
|
|
739
|
+
return i;
|
|
740
|
+
}
|
|
741
|
+
getCurveIntersections(curve, deduplicationTolerance) {
|
|
742
|
+
return getIntersectionsBetweenCurves(this, curve, deduplicationTolerance);
|
|
743
|
+
}
|
|
744
|
+
get AABB() {
|
|
745
|
+
const extrema = this.getExtrema();
|
|
746
|
+
const tVals = [0, 1];
|
|
747
|
+
let min = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };
|
|
748
|
+
let max = { x: -Number.MAX_VALUE, y: -Number.MAX_VALUE };
|
|
749
|
+
extrema.x.forEach((tVal) => {
|
|
750
|
+
tVals.push(tVal);
|
|
751
|
+
});
|
|
752
|
+
extrema.y.forEach((tVal) => {
|
|
753
|
+
tVals.push(tVal);
|
|
754
|
+
});
|
|
755
|
+
tVals.forEach((tVal) => {
|
|
756
|
+
const curPoint = this.get(tVal);
|
|
757
|
+
min.x = Math.min(min.x, curPoint.x);
|
|
758
|
+
min.y = Math.min(min.y, curPoint.y);
|
|
759
|
+
max.x = Math.max(max.x, curPoint.x);
|
|
760
|
+
max.y = Math.max(max.y, curPoint.y);
|
|
761
|
+
});
|
|
762
|
+
return { min, max };
|
|
763
|
+
}
|
|
764
|
+
normal(tVal) {
|
|
765
|
+
const d = this.derivative(tVal);
|
|
766
|
+
const q = Math.sqrt(d.x * d.x + d.y * d.y);
|
|
767
|
+
return { tVal, direction: { x: -d.y / q, y: d.x / q } };
|
|
768
|
+
}
|
|
801
769
|
}
|
|
802
770
|
function reduce(curve) {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
pass2.push(segment);
|
|
844
|
-
}
|
|
845
|
-
});
|
|
846
|
-
return pass2;
|
|
771
|
+
let i, t1 = 0, t2 = 0, step = 0.01, segment, pass1 = [], pass2 = [];
|
|
772
|
+
let extrema = curve.getExtrema().x;
|
|
773
|
+
if (extrema.indexOf(0) === -1) {
|
|
774
|
+
extrema = [0].concat(extrema);
|
|
775
|
+
}
|
|
776
|
+
if (extrema.indexOf(1) === -1) {
|
|
777
|
+
extrema.push(1);
|
|
778
|
+
}
|
|
779
|
+
for (t1 = extrema[0], i = 1;i < extrema.length; i++) {
|
|
780
|
+
t2 = extrema[i];
|
|
781
|
+
segment = curve.splitAndTakeMidCurve(t1, t2);
|
|
782
|
+
pass1.push(segment);
|
|
783
|
+
t1 = t2;
|
|
784
|
+
}
|
|
785
|
+
pass1.forEach((p1) => {
|
|
786
|
+
t1 = 0;
|
|
787
|
+
t2 = 0;
|
|
788
|
+
while (t2 <= 1) {
|
|
789
|
+
for (t2 = t1 + step;t2 <= 1 + step; t2 += step) {
|
|
790
|
+
const clampedT2 = Math.min(t2, 1);
|
|
791
|
+
segment = p1.splitAndTakeMidCurve(t1, clampedT2);
|
|
792
|
+
if (!curveIsSimple(segment)) {
|
|
793
|
+
t2 -= step;
|
|
794
|
+
if (Math.abs(t1 - t2) < step) {
|
|
795
|
+
return [];
|
|
796
|
+
}
|
|
797
|
+
const finalT2 = Math.min(t2, 1);
|
|
798
|
+
segment = p1.splitAndTakeMidCurve(t1, finalT2);
|
|
799
|
+
pass2.push(segment);
|
|
800
|
+
t1 = finalT2;
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (t1 < 1) {
|
|
806
|
+
segment = p1.splitAndTakeMidCurve(t1, 1);
|
|
807
|
+
pass2.push(segment);
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
return pass2;
|
|
847
811
|
}
|
|
848
812
|
function raiseCurveOrder(curve) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
813
|
+
const p = curve.getControlPoints(), np = [p[0]], k = p.length;
|
|
814
|
+
for (let i = 1;i < k; i++) {
|
|
815
|
+
const pi = p[i];
|
|
816
|
+
const pim = p[i - 1];
|
|
817
|
+
np[i] = {
|
|
818
|
+
x: (k - i) / k * pi.x + i / k * pim.x,
|
|
819
|
+
y: (k - i) / k * pi.y + i / k * pim.y
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
np[k] = p[k - 1];
|
|
823
|
+
return new BCurve(np);
|
|
860
824
|
}
|
|
861
825
|
function offset(curve, t, d) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
x: p.x + t * nv.x,
|
|
882
|
-
y: p.y + t * nv.y,
|
|
883
|
-
};
|
|
884
|
-
return ret;
|
|
885
|
-
});
|
|
886
|
-
return [new BCurve(coords)];
|
|
887
|
-
}
|
|
888
|
-
// For non-linear curves, reduce to simple segments and scale each
|
|
889
|
-
return reduce(curve).map(function (s) {
|
|
890
|
-
if (curveIsLinear(s)) {
|
|
891
|
-
return offset(s, t)[0];
|
|
892
|
-
}
|
|
893
|
-
return scaleCurve(s, t);
|
|
826
|
+
if (d !== undefined) {
|
|
827
|
+
const c = curve.get(t), n = curve.normal(t).direction;
|
|
828
|
+
const ret = {
|
|
829
|
+
c,
|
|
830
|
+
n,
|
|
831
|
+
x: c.x + n.x * d,
|
|
832
|
+
y: c.y + n.y * d
|
|
833
|
+
};
|
|
834
|
+
return ret;
|
|
835
|
+
}
|
|
836
|
+
const points = curve.getControlPoints();
|
|
837
|
+
const linear = curveIsLinear(curve);
|
|
838
|
+
if (linear) {
|
|
839
|
+
const nv = curve.normal(0).direction, coords = points.map(function(p) {
|
|
840
|
+
const ret = {
|
|
841
|
+
x: p.x + t * nv.x,
|
|
842
|
+
y: p.y + t * nv.y
|
|
843
|
+
};
|
|
844
|
+
return ret;
|
|
894
845
|
});
|
|
846
|
+
return [new BCurve(coords)];
|
|
847
|
+
}
|
|
848
|
+
return reduce(curve).map(function(s) {
|
|
849
|
+
if (curveIsLinear(s)) {
|
|
850
|
+
return offset(s, t)[0];
|
|
851
|
+
}
|
|
852
|
+
return scaleCurve(s, t);
|
|
853
|
+
});
|
|
895
854
|
}
|
|
896
855
|
function offset2(curve, d) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
856
|
+
const lut = curve.getLUTWithTVal(100);
|
|
857
|
+
const res = lut.map((item) => {
|
|
858
|
+
const derivative = PointCal.unitVector(curve.derivative(item.tVal));
|
|
859
|
+
const normal = { x: -derivative.y, y: derivative.x };
|
|
860
|
+
const offsetPoint = { x: item.point.x + normal.x * d, y: item.point.y + normal.y * d };
|
|
861
|
+
return offsetPoint;
|
|
862
|
+
});
|
|
863
|
+
return res;
|
|
905
864
|
}
|
|
906
|
-
// Helper function for line-line intersection (ported from bezier-js utils)
|
|
907
865
|
function lli4(p1, p2, p3, p4) {
|
|
908
|
-
|
|
909
|
-
|
|
866
|
+
const { x: x1, y: y1 } = p1, x2 = p2.x, y2 = p2.y, x3 = p3.x, y3 = p3.y, x4 = p4.x, y4 = p4.y;
|
|
867
|
+
return lli8(x1, y1, x2, y2, x3, y3, x4, y4);
|
|
910
868
|
}
|
|
911
869
|
function lli8(x1, y1, x2, y2, x3, y3, x4, y4) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
870
|
+
const nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
|
|
871
|
+
const ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
|
|
872
|
+
const d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
873
|
+
if (d == 0) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
return { x: nx / d, y: ny / d };
|
|
919
877
|
}
|
|
920
878
|
function curveIsLinear(curve) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
return linear;
|
|
879
|
+
const order = curve.getControlPoints().length - 1;
|
|
880
|
+
const points = curve.getControlPoints();
|
|
881
|
+
const alignedPoints = alignPointsToLine(points, { p1: points[0], p2: points[order] });
|
|
882
|
+
const baseLength = PointCal.distanceBetweenPoints(points[0], points[order]);
|
|
883
|
+
const linear = alignedPoints.reduce((t, p) => t + Math.abs(p.y), 0) < baseLength / 50;
|
|
884
|
+
return linear;
|
|
928
885
|
}
|
|
929
886
|
function scaleCurve(curve, d) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const intersection = lli4(p, p2, o, points[t + 1]);
|
|
971
|
-
if (intersection) {
|
|
972
|
-
np[t + 1] = intersection;
|
|
973
|
-
}
|
|
974
|
-
else {
|
|
975
|
-
// Fallback: use original control point with simple offset
|
|
976
|
-
const originalPoint = points[t + 1];
|
|
977
|
-
const normal = curve.normal((t + 1) / order).direction;
|
|
978
|
-
np[t + 1] = {
|
|
979
|
-
x: originalPoint.x + (t ? r2 : r1) * normal.x,
|
|
980
|
-
y: originalPoint.y + (t ? r2 : r1) * normal.y
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
});
|
|
984
|
-
return new BCurve(np);
|
|
985
|
-
}
|
|
986
|
-
// For function-based distances, move control points by distance
|
|
987
|
-
// to ensure correct tangent at endpoints
|
|
988
|
-
[0, 1].forEach(function (t) {
|
|
989
|
-
if (order === 2 && !!t)
|
|
990
|
-
return;
|
|
991
|
-
const p = points[t + 1];
|
|
992
|
-
const ov = {
|
|
993
|
-
x: p.x - o.x,
|
|
994
|
-
y: p.y - o.y,
|
|
995
|
-
};
|
|
996
|
-
let rc = distanceFn((t + 1) / order);
|
|
997
|
-
const m = Math.sqrt(ov.x * ov.x + ov.y * ov.y);
|
|
998
|
-
ov.x /= m;
|
|
999
|
-
ov.y /= m;
|
|
887
|
+
const order = curve.getControlPoints().length - 1;
|
|
888
|
+
let distanceFn = undefined;
|
|
889
|
+
if (typeof d === "function") {
|
|
890
|
+
distanceFn = d;
|
|
891
|
+
}
|
|
892
|
+
if (distanceFn && order === 2) {
|
|
893
|
+
return scaleCurve(raiseCurveOrder(curve), distanceFn);
|
|
894
|
+
}
|
|
895
|
+
const points = curve.getControlPoints();
|
|
896
|
+
if (curveIsLinear(curve)) {
|
|
897
|
+
return translate(curve, curve.normal(0).direction, distanceFn ? distanceFn(0) : d, distanceFn ? distanceFn(1) : d);
|
|
898
|
+
}
|
|
899
|
+
const r1 = distanceFn ? distanceFn(0) : d;
|
|
900
|
+
const r2 = distanceFn ? distanceFn(1) : d;
|
|
901
|
+
const v = [offset(curve, 0, 10), offset(curve, 1, 10)];
|
|
902
|
+
const np = [];
|
|
903
|
+
const o = lli4(v[0], v[0].c, v[1], v[1].c);
|
|
904
|
+
if (!o) {
|
|
905
|
+
return translate(curve, curve.normal(0).direction, r1, r2);
|
|
906
|
+
}
|
|
907
|
+
[0, 1].forEach(function(t) {
|
|
908
|
+
const p = JSON.parse(JSON.stringify(points[t * order]));
|
|
909
|
+
const vt = v[t];
|
|
910
|
+
p.x += (t ? r2 : r1) * vt.n.x;
|
|
911
|
+
p.y += (t ? r2 : r1) * vt.n.y;
|
|
912
|
+
np[t * order] = p;
|
|
913
|
+
});
|
|
914
|
+
if (!distanceFn) {
|
|
915
|
+
[0, 1].forEach((t) => {
|
|
916
|
+
if (order === 2 && !!t)
|
|
917
|
+
return;
|
|
918
|
+
const p = np[t * order];
|
|
919
|
+
const derivativeAtT = curve.derivative(t);
|
|
920
|
+
const p2 = { x: p.x + derivativeAtT.x, y: p.y + derivativeAtT.y };
|
|
921
|
+
const intersection = lli4(p, p2, o, points[t + 1]);
|
|
922
|
+
if (intersection) {
|
|
923
|
+
np[t + 1] = intersection;
|
|
924
|
+
} else {
|
|
925
|
+
const originalPoint = points[t + 1];
|
|
926
|
+
const normal = curve.normal((t + 1) / order).direction;
|
|
1000
927
|
np[t + 1] = {
|
|
1001
|
-
|
|
1002
|
-
|
|
928
|
+
x: originalPoint.x + (t ? r2 : r1) * normal.x,
|
|
929
|
+
y: originalPoint.y + (t ? r2 : r1) * normal.y
|
|
1003
930
|
};
|
|
931
|
+
}
|
|
1004
932
|
});
|
|
1005
933
|
return new BCurve(np);
|
|
934
|
+
}
|
|
935
|
+
[0, 1].forEach(function(t) {
|
|
936
|
+
if (order === 2 && !!t)
|
|
937
|
+
return;
|
|
938
|
+
const p = points[t + 1];
|
|
939
|
+
const ov = {
|
|
940
|
+
x: p.x - o.x,
|
|
941
|
+
y: p.y - o.y
|
|
942
|
+
};
|
|
943
|
+
let rc = distanceFn((t + 1) / order);
|
|
944
|
+
const m = Math.sqrt(ov.x * ov.x + ov.y * ov.y);
|
|
945
|
+
ov.x /= m;
|
|
946
|
+
ov.y /= m;
|
|
947
|
+
np[t + 1] = {
|
|
948
|
+
x: p.x + rc * ov.x,
|
|
949
|
+
y: p.y + rc * ov.y
|
|
950
|
+
};
|
|
951
|
+
});
|
|
952
|
+
return new BCurve(np);
|
|
1006
953
|
}
|
|
1007
954
|
function alignPointsToLine(points, line) {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
};
|
|
955
|
+
const tx = line.p1.x, ty = line.p1.y, a = -Math.atan2(line.p2.y - ty, line.p2.x - tx), d = function(v) {
|
|
956
|
+
return {
|
|
957
|
+
x: (v.x - tx) * Math.cos(a) - (v.y - ty) * Math.sin(a),
|
|
958
|
+
y: (v.x - tx) * Math.sin(a) + (v.y - ty) * Math.cos(a)
|
|
1013
959
|
};
|
|
1014
|
-
|
|
960
|
+
};
|
|
961
|
+
return points.map(d);
|
|
962
|
+
}
|
|
963
|
+
function map(v, ds, de, ts, te) {
|
|
964
|
+
const d1 = de - ds, d2 = te - ts, v2 = v - ds, r = v2 / d1;
|
|
965
|
+
return ts + d2 * r;
|
|
1015
966
|
}
|
|
967
|
+
|
|
1016
968
|
class TValOutofBoundError extends Error {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
969
|
+
constructor(message) {
|
|
970
|
+
super(message);
|
|
971
|
+
}
|
|
1020
972
|
}
|
|
1021
973
|
function AABBIntersects(AABB1, AABB2) {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
974
|
+
if (AABB1.min.x <= AABB2.max.x && AABB2.min.x <= AABB1.max.x && (AABB1.min.y <= AABB2.max.y && AABB2.min.y <= AABB1.max.y)) {
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
return false;
|
|
1026
978
|
}
|
|
1027
979
|
function approximately(a, b, precision) {
|
|
1028
|
-
|
|
1029
|
-
|
|
980
|
+
const epsilon = 0.000001;
|
|
981
|
+
return Math.abs(a - b) <= (precision || epsilon);
|
|
1030
982
|
}
|
|
1031
983
|
function cuberoot2(v) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
984
|
+
if (v < 0)
|
|
985
|
+
return -Math.pow(-v, 1 / 3);
|
|
986
|
+
return Math.pow(v, 1 / 3);
|
|
1035
987
|
}
|
|
1036
988
|
function accept(t) {
|
|
1037
|
-
|
|
989
|
+
return 0 <= t && t <= 1;
|
|
1038
990
|
}
|
|
1039
991
|
function getCubicRoots(pa, pb, pc, pd) {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
if (approximately(
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
a
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
u1 = q2 < 0 ? cuberoot2(-q2) : -cuberoot2(q2);
|
|
1075
|
-
root1 = 2 * u1 - a / 3;
|
|
1076
|
-
root2 = -u1 - a / 3;
|
|
1077
|
-
return [root1, root2].filter(accept);
|
|
1078
|
-
}
|
|
1079
|
-
// one real root, two complex roots
|
|
1080
|
-
var sd = Math.sqrt(discriminant);
|
|
1081
|
-
u1 = cuberoot2(sd - q2);
|
|
1082
|
-
v1 = cuberoot2(sd + q2);
|
|
1083
|
-
root1 = u1 - v1 - a / 3;
|
|
1084
|
-
return [root1].filter(accept);
|
|
992
|
+
let a = 3 * pa - 6 * pb + 3 * pc, b = -3 * pa + 3 * pb, c = pa, d = -pa + 3 * pb - 3 * pc + pd;
|
|
993
|
+
if (approximately(d, 0)) {
|
|
994
|
+
if (approximately(a, 0)) {
|
|
995
|
+
if (approximately(b, 0)) {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
return [-c / b].filter(accept);
|
|
999
|
+
}
|
|
1000
|
+
let q3 = Math.sqrt(b * b - 4 * a * c), a2 = 2 * a;
|
|
1001
|
+
return [(q3 - b) / a2, (-b - q3) / a2].filter(accept);
|
|
1002
|
+
}
|
|
1003
|
+
a /= d;
|
|
1004
|
+
b /= d;
|
|
1005
|
+
c /= d;
|
|
1006
|
+
let p = (3 * b - a * a) / 3, p3 = p / 3, q = (2 * a * a * a - 9 * a * b + 27 * c) / 27, q2 = q / 2, discriminant = q2 * q2 + p3 * p3 * p3;
|
|
1007
|
+
let u1, v1, root1, root2, root3;
|
|
1008
|
+
if (discriminant < 0) {
|
|
1009
|
+
let mp3 = -p / 3, mp33 = mp3 * mp3 * mp3, r = Math.sqrt(mp33), t = -q / (2 * r), cosphi = t < -1 ? -1 : t > 1 ? 1 : t, phi = Math.acos(cosphi), crtr = cuberoot2(r), t1 = 2 * crtr;
|
|
1010
|
+
root1 = t1 * Math.cos(phi / 3) - a / 3;
|
|
1011
|
+
root2 = t1 * Math.cos((phi + 2 * Math.PI) / 3) - a / 3;
|
|
1012
|
+
root3 = t1 * Math.cos((phi + 4 * Math.PI) / 3) - a / 3;
|
|
1013
|
+
return [root1, root2, root3].filter(accept);
|
|
1014
|
+
}
|
|
1015
|
+
if (discriminant === 0) {
|
|
1016
|
+
u1 = q2 < 0 ? cuberoot2(-q2) : -cuberoot2(q2);
|
|
1017
|
+
root1 = 2 * u1 - a / 3;
|
|
1018
|
+
root2 = -u1 - a / 3;
|
|
1019
|
+
return [root1, root2].filter(accept);
|
|
1020
|
+
}
|
|
1021
|
+
var sd = Math.sqrt(discriminant);
|
|
1022
|
+
u1 = cuberoot2(sd - q2);
|
|
1023
|
+
v1 = cuberoot2(sd + q2);
|
|
1024
|
+
root1 = u1 - v1 - a / 3;
|
|
1025
|
+
return [root1].filter(accept);
|
|
1085
1026
|
}
|
|
1086
1027
|
function getIntersectionsBetweenCurves(curve, curve2, deduplicationTolerance = 0.01) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
if (!isDuplicate) {
|
|
1190
|
-
tVals.push({ selfT: intersection.tVal1, otherT: intersection.tVal2 });
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
return tVals;
|
|
1028
|
+
const threshold = 0.5;
|
|
1029
|
+
let pairs = [{ curve1: { curve, startTVal: 0, endTVal: 1 }, curve2: { curve: curve2, startTVal: 0, endTVal: 1 } }];
|
|
1030
|
+
const finalRes = [];
|
|
1031
|
+
while (pairs.length > 0) {
|
|
1032
|
+
let curLength = pairs.length;
|
|
1033
|
+
for (let index = 0;index < curLength; index++) {
|
|
1034
|
+
let pair = pairs.shift();
|
|
1035
|
+
if (pair == undefined) {
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
let aabb1 = pair.curve1.curve.AABB;
|
|
1039
|
+
let aabb2 = pair.curve2.curve.AABB;
|
|
1040
|
+
let intersects = AABBIntersects(aabb1, aabb2);
|
|
1041
|
+
if (pair.curve1.curve.fullLength < threshold && pair.curve2.curve.fullLength < threshold) {
|
|
1042
|
+
finalRes.push({ intersection: pair.curve1.curve.get(0.5), tVal1: (pair.curve1.startTVal + pair.curve1.endTVal) * 0.5, tVal2: (pair.curve2.startTVal + pair.curve2.endTVal) * 0.5 });
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (intersects) {
|
|
1046
|
+
let [subCurveControlPoints1, subCurveControlPoints2] = pair.curve1.curve.split(0.5);
|
|
1047
|
+
let [subCurveControlPoints3, subCurveControlPoints4] = pair.curve2.curve.split(0.5);
|
|
1048
|
+
pairs.push({
|
|
1049
|
+
curve1: {
|
|
1050
|
+
curve: new BCurve(subCurveControlPoints1),
|
|
1051
|
+
startTVal: pair.curve1.startTVal,
|
|
1052
|
+
endTVal: pair.curve1.startTVal + (pair.curve1.endTVal - pair.curve1.startTVal) * 0.5
|
|
1053
|
+
},
|
|
1054
|
+
curve2: {
|
|
1055
|
+
curve: new BCurve(subCurveControlPoints3),
|
|
1056
|
+
startTVal: pair.curve2.startTVal,
|
|
1057
|
+
endTVal: pair.curve2.startTVal + (pair.curve2.endTVal - pair.curve2.startTVal) * 0.5
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
pairs.push({
|
|
1061
|
+
curve1: {
|
|
1062
|
+
curve: new BCurve(subCurveControlPoints1),
|
|
1063
|
+
startTVal: pair.curve1.startTVal,
|
|
1064
|
+
endTVal: pair.curve1.startTVal + (pair.curve1.endTVal - pair.curve1.startTVal) * 0.5
|
|
1065
|
+
},
|
|
1066
|
+
curve2: {
|
|
1067
|
+
curve: new BCurve(subCurveControlPoints4),
|
|
1068
|
+
startTVal: pair.curve2.startTVal + (pair.curve2.endTVal - pair.curve2.startTVal) * 0.5,
|
|
1069
|
+
endTVal: pair.curve2.endTVal
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
pairs.push({
|
|
1073
|
+
curve1: {
|
|
1074
|
+
curve: new BCurve(subCurveControlPoints2),
|
|
1075
|
+
startTVal: pair.curve1.startTVal + (pair.curve1.endTVal - pair.curve1.startTVal) * 0.5,
|
|
1076
|
+
endTVal: pair.curve1.endTVal
|
|
1077
|
+
},
|
|
1078
|
+
curve2: {
|
|
1079
|
+
curve: new BCurve(subCurveControlPoints3),
|
|
1080
|
+
startTVal: pair.curve2.startTVal,
|
|
1081
|
+
endTVal: pair.curve2.startTVal + (pair.curve2.endTVal - pair.curve2.startTVal) * 0.5
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
pairs.push({
|
|
1085
|
+
curve1: {
|
|
1086
|
+
curve: new BCurve(subCurveControlPoints2),
|
|
1087
|
+
startTVal: pair.curve1.startTVal + (pair.curve1.endTVal - pair.curve1.startTVal) * 0.5,
|
|
1088
|
+
endTVal: pair.curve1.endTVal
|
|
1089
|
+
},
|
|
1090
|
+
curve2: {
|
|
1091
|
+
curve: new BCurve(subCurveControlPoints4),
|
|
1092
|
+
startTVal: pair.curve2.startTVal + (pair.curve2.endTVal - pair.curve2.startTVal) * 0.5,
|
|
1093
|
+
endTVal: pair.curve2.endTVal
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const tVals = [];
|
|
1100
|
+
finalRes.sort((a, b) => a.tVal1 - b.tVal1);
|
|
1101
|
+
for (const intersection of finalRes) {
|
|
1102
|
+
let isDuplicate = false;
|
|
1103
|
+
for (const existing of tVals) {
|
|
1104
|
+
const selfTClose = approximately(intersection.tVal1, existing.selfT, deduplicationTolerance);
|
|
1105
|
+
const otherTClose = approximately(intersection.tVal2, existing.otherT, deduplicationTolerance);
|
|
1106
|
+
if (selfTClose && otherTClose) {
|
|
1107
|
+
isDuplicate = true;
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
const selfTVeryClose = approximately(intersection.tVal1, existing.selfT, deduplicationTolerance * 10);
|
|
1111
|
+
const otherTVeryClose = approximately(intersection.tVal2, existing.otherT, deduplicationTolerance * 10);
|
|
1112
|
+
if (selfTVeryClose || otherTVeryClose) {
|
|
1113
|
+
const point1 = curve.get(intersection.tVal1);
|
|
1114
|
+
const point2 = curve2.get(intersection.tVal2);
|
|
1115
|
+
const existingPoint1 = curve.get(existing.selfT);
|
|
1116
|
+
const existingPoint2 = curve2.get(existing.otherT);
|
|
1117
|
+
const distance1 = PointCal.distanceBetweenPoints(point1, existingPoint1);
|
|
1118
|
+
const distance2 = PointCal.distanceBetweenPoints(point2, existingPoint2);
|
|
1119
|
+
if (distance1 < deduplicationTolerance * 100 && distance2 < deduplicationTolerance * 100) {
|
|
1120
|
+
isDuplicate = true;
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (!isDuplicate) {
|
|
1126
|
+
tVals.push({ selfT: intersection.tVal1, otherT: intersection.tVal2 });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return tVals;
|
|
1194
1130
|
}
|
|
1195
1131
|
function solveCubic(a, b, c, d) {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
return [];
|
|
1205
|
-
return [-b / a];
|
|
1206
|
-
}
|
|
1207
|
-
let D = b * b - 4 * a * c;
|
|
1208
|
-
if (Math.abs(D) < 1e-8)
|
|
1209
|
-
return [-b / (2 * a)];
|
|
1210
|
-
else if (D > 0)
|
|
1211
|
-
return [(-b + Math.sqrt(D)) / (2 * a), (-b - Math.sqrt(D)) / (2 * a)];
|
|
1132
|
+
if (Math.abs(a) < 0.00000001) {
|
|
1133
|
+
a = b;
|
|
1134
|
+
b = c;
|
|
1135
|
+
c = d;
|
|
1136
|
+
if (Math.abs(a) < 0.00000001) {
|
|
1137
|
+
a = b;
|
|
1138
|
+
b = c;
|
|
1139
|
+
if (Math.abs(a) < 0.00000001)
|
|
1212
1140
|
return [];
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
let
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
if (
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1141
|
+
return [-b / a];
|
|
1142
|
+
}
|
|
1143
|
+
let D = b * b - 4 * a * c;
|
|
1144
|
+
if (Math.abs(D) < 0.00000001)
|
|
1145
|
+
return [-b / (2 * a)];
|
|
1146
|
+
else if (D > 0)
|
|
1147
|
+
return [(-b + Math.sqrt(D)) / (2 * a), (-b - Math.sqrt(D)) / (2 * a)];
|
|
1148
|
+
return [];
|
|
1149
|
+
}
|
|
1150
|
+
let p = (3 * a * c - b * b) / (3 * a * a);
|
|
1151
|
+
let q = (2 * b * b * b - 9 * a * b * c + 27 * a * a * d) / (27 * a * a * a);
|
|
1152
|
+
let roots;
|
|
1153
|
+
if (Math.abs(p) < 0.00000001) {
|
|
1154
|
+
roots = [cuberoot(-q)];
|
|
1155
|
+
} else if (Math.abs(q) < 0.00000001) {
|
|
1156
|
+
roots = [0].concat(p < 0 ? [Math.sqrt(-p), -Math.sqrt(-p)] : []);
|
|
1157
|
+
} else {
|
|
1158
|
+
let D = q * q / 4 + p * p * p / 27;
|
|
1159
|
+
if (Math.abs(D) < 0.00000001) {
|
|
1160
|
+
roots = [-1.5 * q / p, 3 * q / p];
|
|
1161
|
+
} else if (D > 0) {
|
|
1162
|
+
let u = cuberoot(-q / 2 - Math.sqrt(D));
|
|
1163
|
+
let v = cuberoot(-q / 2 + Math.sqrt(D));
|
|
1164
|
+
roots = [u - p / (3 * u)];
|
|
1165
|
+
} else {
|
|
1166
|
+
let u = 2 * Math.sqrt(-p / 3);
|
|
1167
|
+
let t = Math.acos(3 * q / p / u) / 3;
|
|
1168
|
+
let k = 2 * Math.PI / 3;
|
|
1169
|
+
roots = [u * Math.cos(t), u * Math.cos(t - k), u * Math.cos(t - 2 * k)];
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
for (let i = 0;i < roots.length; i++)
|
|
1173
|
+
roots[i] -= b / (3 * a);
|
|
1174
|
+
return roots;
|
|
1246
1175
|
}
|
|
1247
1176
|
function cuberoot(x) {
|
|
1248
|
-
|
|
1249
|
-
|
|
1177
|
+
var y = Math.pow(Math.abs(x), 1 / 3);
|
|
1178
|
+
return x < 0 ? -y : y;
|
|
1250
1179
|
}
|
|
1251
1180
|
function computeWithControlPoints(tVal, controlPoints) {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1181
|
+
let points = [...controlPoints];
|
|
1182
|
+
while (points.length > 1) {
|
|
1183
|
+
let lowerLevelPoints = points.slice(1);
|
|
1184
|
+
for (let index = 0;index < lowerLevelPoints.length; index++) {
|
|
1185
|
+
lowerLevelPoints[index] = PointCal.addVector(PointCal.multiplyVectorByScalar(points[index], 1 - tVal), PointCal.multiplyVectorByScalar(points[index + 1], tVal));
|
|
1186
|
+
}
|
|
1187
|
+
points = lowerLevelPoints;
|
|
1188
|
+
}
|
|
1189
|
+
return points[0];
|
|
1261
1190
|
}
|
|
1262
1191
|
function curveIsSimple(curve) {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
// s += n1.z * n2.z;
|
|
1278
|
-
// }
|
|
1279
|
-
return Math.abs(Math.acos(s)) < Math.PI / 3;
|
|
1192
|
+
if (curve.getControlPoints().length === 4) {
|
|
1193
|
+
const points = curve.getControlPoints();
|
|
1194
|
+
const p0ToP3Vector = PointCal.subVector(points[3], points[0]);
|
|
1195
|
+
const p0ToP1Vector = PointCal.subVector(points[1], points[0]);
|
|
1196
|
+
const p0ToP2Vector = PointCal.subVector(points[2], points[0]);
|
|
1197
|
+
const a1 = PointCal.angleFromA2B(p0ToP3Vector, p0ToP1Vector);
|
|
1198
|
+
const a2 = PointCal.angleFromA2B(p0ToP3Vector, p0ToP2Vector);
|
|
1199
|
+
if (a1 > 0 && a2 < 0 || a1 < 0 && a2 > 0)
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
const n1 = curve.normal(0).direction;
|
|
1203
|
+
const n2 = curve.normal(1).direction;
|
|
1204
|
+
let s = n1.x * n2.x + n1.y * n2.y;
|
|
1205
|
+
return Math.abs(Math.acos(s)) < Math.PI / 3;
|
|
1280
1206
|
}
|
|
1281
1207
|
function translate(curve, vector, d1, d2) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1208
|
+
const order = curve.getControlPoints().length - 1;
|
|
1209
|
+
const points = curve.getControlPoints();
|
|
1210
|
+
const d = points.map((_, i) => (1 - i / order) * d1 + i / order * d2);
|
|
1211
|
+
return new BCurve(points.map((p, i) => ({ x: p.x + d[i] * vector.x, y: p.y + d[i] * vector.y })));
|
|
1286
1212
|
}
|
|
1213
|
+
// src/line.ts
|
|
1214
|
+
import { PointCal as PointCal2 } from "@ue-too/math";
|
|
1287
1215
|
|
|
1288
1216
|
class Line {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1217
|
+
startPoint;
|
|
1218
|
+
endPoint;
|
|
1219
|
+
constructor(startPoint, endPoint) {
|
|
1220
|
+
this.startPoint = startPoint;
|
|
1221
|
+
this.endPoint = endPoint;
|
|
1222
|
+
}
|
|
1223
|
+
getStartPoint() {
|
|
1224
|
+
return this.startPoint;
|
|
1225
|
+
}
|
|
1226
|
+
getEndPoint() {
|
|
1227
|
+
return this.endPoint;
|
|
1228
|
+
}
|
|
1229
|
+
intersectionWithAnotherLine(lineToIntersect) {
|
|
1230
|
+
return getLineIntersection(this.startPoint, this.endPoint, lineToIntersect.getStartPoint(), lineToIntersect.getEndPoint());
|
|
1231
|
+
}
|
|
1232
|
+
projectPoint(point) {
|
|
1233
|
+
return projectPointOntoLine(point, this.getStartPoint(), this.getEndPoint());
|
|
1234
|
+
}
|
|
1235
|
+
length() {
|
|
1236
|
+
return PointCal2.distanceBetweenPoints(this.startPoint, this.endPoint);
|
|
1237
|
+
}
|
|
1238
|
+
getTranslationRotationToAlginXAxis() {
|
|
1239
|
+
const translation = PointCal2.subVector({ x: 0, y: 0 }, this.startPoint);
|
|
1240
|
+
const rotationAngle = PointCal2.angleFromA2B(PointCal2.subVector(this.endPoint, this.startPoint), { x: 1, y: 0 });
|
|
1241
|
+
return { translation, rotationAngle };
|
|
1242
|
+
}
|
|
1243
|
+
pointInLine(point) {
|
|
1244
|
+
const baseVector = PointCal2.unitVectorFromA2B(this.startPoint, this.endPoint);
|
|
1245
|
+
const start2PointVector = PointCal2.subVector(point, this.startPoint);
|
|
1246
|
+
const length = PointCal2.dotProduct(start2PointVector, baseVector);
|
|
1247
|
+
const start2PointUnitVector = PointCal2.unitVector(start2PointVector);
|
|
1248
|
+
const errorThreshold = PointCal2.distanceBetweenPoints(this.startPoint, this.endPoint) * 0.0001;
|
|
1249
|
+
return length <= PointCal2.distanceBetweenPoints(this.startPoint, this.endPoint) && length >= 0 && Math.abs(start2PointUnitVector.x - baseVector.x) < 0.0001 && Math.abs(start2PointUnitVector.y - baseVector.y) < 0.0001;
|
|
1250
|
+
}
|
|
1251
|
+
lerp(ratio) {
|
|
1252
|
+
return PointCal2.linearInterpolation(this.startPoint, this.endPoint, ratio);
|
|
1253
|
+
}
|
|
1324
1254
|
}
|
|
1325
1255
|
function getLineIntersection(startPoint, endPoint, startPoint2, endPoint2) {
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
}
|
|
1256
|
+
const numerator = (endPoint2.x - startPoint2.x) * (startPoint.y - startPoint2.y) - (endPoint2.y - startPoint2.y) * (startPoint.x - startPoint2.x);
|
|
1257
|
+
const denominator = (endPoint2.y - startPoint2.y) * (endPoint.x - startPoint.x) - (endPoint2.x - startPoint2.x) * (endPoint.y - startPoint.y);
|
|
1258
|
+
if (denominator === 0) {
|
|
1259
|
+
return { intersects: false };
|
|
1260
|
+
}
|
|
1261
|
+
const t = numerator / denominator;
|
|
1262
|
+
if (t >= 0 && t <= 1) {
|
|
1263
|
+
return {
|
|
1264
|
+
intersects: true,
|
|
1265
|
+
intersection: PointCal2.linearInterpolation(startPoint, endPoint, t),
|
|
1266
|
+
offset: t
|
|
1267
|
+
};
|
|
1268
|
+
} else {
|
|
1269
|
+
return {
|
|
1270
|
+
intersects: false
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1344
1273
|
}
|
|
1345
1274
|
function projectPointOntoLine(point, lineStartPoint, lineEndPoint) {
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
return {
|
|
1351
|
-
within: false,
|
|
1352
|
-
};
|
|
1353
|
-
}
|
|
1275
|
+
const baseVector = PointCal2.unitVector(PointCal2.subVector(lineEndPoint, lineStartPoint));
|
|
1276
|
+
const vectorToPoint = PointCal2.subVector(point, lineStartPoint);
|
|
1277
|
+
const res = PointCal2.dotProduct(vectorToPoint, baseVector);
|
|
1278
|
+
if (res < 0 || res > PointCal2.magnitude(PointCal2.subVector(lineEndPoint, lineStartPoint))) {
|
|
1354
1279
|
return {
|
|
1355
|
-
|
|
1356
|
-
projectionPoint: PointCal.addVector(lineStartPoint, PointCal.multiplyVectorByScalar(baseVector, res)),
|
|
1357
|
-
offset: res / PointCal.magnitude(PointCal.subVector(lineEndPoint, lineStartPoint))
|
|
1280
|
+
within: false
|
|
1358
1281
|
};
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
within: true,
|
|
1285
|
+
projectionPoint: PointCal2.addVector(lineStartPoint, PointCal2.multiplyVectorByScalar(baseVector, res)),
|
|
1286
|
+
offset: res / PointCal2.magnitude(PointCal2.subVector(lineEndPoint, lineStartPoint))
|
|
1287
|
+
};
|
|
1359
1288
|
}
|
|
1289
|
+
// src/composite-curve.ts
|
|
1290
|
+
import { PointCal as PointCal3 } from "@ue-too/math";
|
|
1360
1291
|
|
|
1361
1292
|
class ControlPoint {
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1293
|
+
position;
|
|
1294
|
+
leftHandle;
|
|
1295
|
+
rightHandle;
|
|
1296
|
+
constructor(position, leftHandle, rightHandle) {
|
|
1297
|
+
this.position = position;
|
|
1298
|
+
this.leftHandle = leftHandle;
|
|
1299
|
+
this.rightHandle = rightHandle;
|
|
1300
|
+
}
|
|
1301
|
+
setPosition(destinationPosition, prevControlPoint, nextControlPoint) {
|
|
1302
|
+
let diff = PointCal3.subVector(destinationPosition, this.position);
|
|
1303
|
+
this.position = destinationPosition;
|
|
1304
|
+
this.leftHandle.position = PointCal3.addVector(this.leftHandle.position, diff);
|
|
1305
|
+
this.rightHandle.position = PointCal3.addVector(this.rightHandle.position, diff);
|
|
1306
|
+
if (this.leftHandle.type == "VECTOR" && prevControlPoint) {
|
|
1307
|
+
let relativeVector = PointCal3.subVector(prevControlPoint.getPosition(), this.position);
|
|
1308
|
+
relativeVector = PointCal3.multiplyVectorByScalar(relativeVector, 1 / 3);
|
|
1309
|
+
this.leftHandle.position = PointCal3.addVector(this.position, relativeVector);
|
|
1310
|
+
if (this.rightHandle.type == "ALIGNED") {
|
|
1311
|
+
let relativeVector2 = PointCal3.subVector(this.rightHandle.position, this.position);
|
|
1312
|
+
let mag = PointCal3.magnitude(relativeVector2);
|
|
1313
|
+
let direction = PointCal3.unitVectorFromA2B(this.leftHandle.position, this.position);
|
|
1314
|
+
this.rightHandle.position = PointCal3.addVector(this.position, PointCal3.multiplyVectorByScalar(direction, mag));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
if (this.rightHandle.type == "VECTOR" && nextControlPoint) {
|
|
1318
|
+
let relativeVector = PointCal3.subVector(nextControlPoint.getPosition(), this.position);
|
|
1319
|
+
relativeVector = PointCal3.multiplyVectorByScalar(relativeVector, 1 / 3);
|
|
1320
|
+
this.rightHandle.position = PointCal3.addVector(this.position, relativeVector);
|
|
1321
|
+
if (this.leftHandle.type == "ALIGNED") {
|
|
1322
|
+
let mag = PointCal3.distanceBetweenPoints(this.leftHandle.position, this.position);
|
|
1323
|
+
let direction = PointCal3.subVector(this.position, this.rightHandle.position);
|
|
1324
|
+
this.leftHandle.position = PointCal3.addVector(this.position, PointCal3.multiplyVectorByScalar(direction, mag));
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (prevControlPoint !== undefined && prevControlPoint.getRightHandle().type == "VECTOR") {
|
|
1328
|
+
let relativeVector = PointCal3.subVector(this.position, prevControlPoint.getPosition());
|
|
1329
|
+
relativeVector = PointCal3.multiplyVectorByScalar(relativeVector, 1 / 3);
|
|
1330
|
+
prevControlPoint.setRightHandlePosition(PointCal3.addVector(prevControlPoint.getPosition(), relativeVector));
|
|
1331
|
+
}
|
|
1332
|
+
if (nextControlPoint !== undefined && nextControlPoint.getLeftHandle().type == "VECTOR") {
|
|
1333
|
+
let relativeVector = PointCal3.subVector(this.position, nextControlPoint.getPosition());
|
|
1334
|
+
relativeVector = PointCal3.multiplyVectorByScalar(relativeVector, 1 / 3);
|
|
1335
|
+
nextControlPoint.setLeftHandlePosition(PointCal3.addVector(nextControlPoint.getPosition(), relativeVector));
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
getPosition() {
|
|
1339
|
+
return this.position;
|
|
1340
|
+
}
|
|
1341
|
+
setLeftHandleTypeVector(prevControlPoint) {
|
|
1342
|
+
if (this.rightHandle.type != "VECTOR") {
|
|
1343
|
+
this.rightHandle.type = "FREE";
|
|
1344
|
+
}
|
|
1345
|
+
let relativeVector;
|
|
1346
|
+
if (prevControlPoint == undefined) {
|
|
1347
|
+
relativeVector = PointCal3.subVector(this.leftHandle.position, this.position);
|
|
1348
|
+
} else {
|
|
1349
|
+
relativeVector = PointCal3.subVector(prevControlPoint.getPosition(), this.position);
|
|
1350
|
+
relativeVector = PointCal3.multiplyVectorByScalar(relativeVector, 1 / 3);
|
|
1351
|
+
}
|
|
1352
|
+
this.leftHandle.position = PointCal3.addVector(this.position, relativeVector);
|
|
1353
|
+
}
|
|
1354
|
+
setLeftHandleTypeAligned() {
|
|
1355
|
+
this.leftHandle.type = "ALIGNED";
|
|
1356
|
+
if (this.rightHandle.type == "VECTOR") {
|
|
1357
|
+
let direction = PointCal3.unitVectorFromA2B(this.rightHandle.position, this.position);
|
|
1358
|
+
let mag = PointCal3.distanceBetweenPoints(this.position, this.leftHandle.position);
|
|
1359
|
+
this.leftHandle.position = PointCal3.addVector(this.position, PointCal3.multiplyVectorByScalar(direction, mag));
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
setLeftHandleTypeFree() {
|
|
1363
|
+
this.leftHandle.type = "FREE";
|
|
1364
|
+
}
|
|
1365
|
+
setRightHandleTypeVector(nextControlPoint) {
|
|
1366
|
+
if (this.leftHandle.type != "VECTOR") {
|
|
1367
|
+
this.leftHandle.type = "FREE";
|
|
1368
|
+
}
|
|
1369
|
+
let relativeVector;
|
|
1370
|
+
if (nextControlPoint == undefined) {
|
|
1371
|
+
relativeVector = PointCal3.subVector(this.rightHandle.position, this.position);
|
|
1372
|
+
} else {
|
|
1373
|
+
relativeVector = PointCal3.subVector(nextControlPoint.getPosition(), this.position);
|
|
1374
|
+
relativeVector = PointCal3.multiplyVectorByScalar(relativeVector, 1 / 3);
|
|
1375
|
+
}
|
|
1376
|
+
this.rightHandle.position = PointCal3.addVector(this.position, relativeVector);
|
|
1377
|
+
}
|
|
1378
|
+
setRightHandleTypeAligned() {
|
|
1379
|
+
this.rightHandle.type = "ALIGNED";
|
|
1380
|
+
if (this.leftHandle.type == "VECTOR") {
|
|
1381
|
+
let direciton = PointCal3.unitVectorFromA2B(this.leftHandle.position, this.position);
|
|
1382
|
+
let mag = PointCal3.distanceBetweenPoints(this.position, this.rightHandle.position);
|
|
1383
|
+
this.rightHandle.position = PointCal3.addVector(this.position, PointCal3.multiplyVectorByScalar(direciton, mag));
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
setRightHandleTypeFree() {
|
|
1387
|
+
this.rightHandle.type = "FREE";
|
|
1388
|
+
}
|
|
1389
|
+
setLeftHandlePosition(destPos) {
|
|
1390
|
+
let leftHandleType = this.leftHandle.type;
|
|
1391
|
+
switch (leftHandleType) {
|
|
1392
|
+
case "ALIGNED":
|
|
1423
1393
|
if (this.rightHandle.type == "VECTOR") {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1394
|
+
let diff = PointCal3.subVector(destPos, this.position);
|
|
1395
|
+
let rightHandleDiff = PointCal3.unitVectorFromA2B(this.rightHandle.position, this.position);
|
|
1396
|
+
let resMag = PointCal3.dotProduct(diff, rightHandleDiff);
|
|
1397
|
+
let res = PointCal3.multiplyVectorByScalar(rightHandleDiff, resMag);
|
|
1398
|
+
this.leftHandle.position = PointCal3.addVector(this.position, res);
|
|
1399
|
+
} else if (this.rightHandle.type == "ALIGNED") {
|
|
1400
|
+
this.leftHandle.position = destPos;
|
|
1401
|
+
let mag = PointCal3.distanceBetweenPoints(this.rightHandle.position, this.position);
|
|
1402
|
+
let direction = PointCal3.unitVectorFromA2B(this.leftHandle.position, this.position);
|
|
1403
|
+
let res = PointCal3.multiplyVectorByScalar(direction, mag);
|
|
1404
|
+
this.rightHandle.position = PointCal3.addVector(res, this.position);
|
|
1405
|
+
} else {
|
|
1406
|
+
this.leftHandle.position = destPos;
|
|
1407
|
+
}
|
|
1408
|
+
break;
|
|
1409
|
+
case "FREE":
|
|
1410
|
+
this.leftHandle.position = destPos;
|
|
1411
|
+
break;
|
|
1412
|
+
case "VECTOR":
|
|
1413
|
+
break;
|
|
1414
|
+
default:
|
|
1415
|
+
throw new Error(`Unknown left handle type for control point`);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
setRightHandlePosition(destPos) {
|
|
1419
|
+
let rightHandleType = this.rightHandle.type;
|
|
1420
|
+
switch (rightHandleType) {
|
|
1421
|
+
case "ALIGNED":
|
|
1448
1422
|
if (this.leftHandle.type == "VECTOR") {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
case "FREE":
|
|
1480
|
-
this.leftHandle.position = destPos;
|
|
1481
|
-
break;
|
|
1482
|
-
case "VECTOR":
|
|
1483
|
-
break;
|
|
1484
|
-
default:
|
|
1485
|
-
throw new Error(`Unknown left handle type for control point`);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
setRightHandlePosition(destPos) {
|
|
1489
|
-
let rightHandleType = this.rightHandle.type;
|
|
1490
|
-
switch (rightHandleType) {
|
|
1491
|
-
case "ALIGNED":
|
|
1492
|
-
if (this.leftHandle.type == "VECTOR") {
|
|
1493
|
-
let diff = PointCal.subVector(destPos, this.position);
|
|
1494
|
-
let leftHandleDiff = PointCal.unitVectorFromA2B(this.leftHandle.position, this.position);
|
|
1495
|
-
let resMag = PointCal.dotProduct(diff, leftHandleDiff);
|
|
1496
|
-
let res = PointCal.multiplyVectorByScalar(leftHandleDiff, resMag);
|
|
1497
|
-
this.rightHandle.position = PointCal.addVector(this.position, res);
|
|
1498
|
-
}
|
|
1499
|
-
else if (this.rightHandle.type == "ALIGNED") {
|
|
1500
|
-
this.rightHandle.position = destPos;
|
|
1501
|
-
let mag = PointCal.distanceBetweenPoints(this.leftHandle.position, this.position);
|
|
1502
|
-
let direction = PointCal.unitVectorFromA2B(this.rightHandle.position, this.position);
|
|
1503
|
-
let res = PointCal.multiplyVectorByScalar(direction, mag);
|
|
1504
|
-
this.leftHandle.position = PointCal.addVector(res, this.position);
|
|
1505
|
-
}
|
|
1506
|
-
else {
|
|
1507
|
-
this.rightHandle.position = destPos;
|
|
1508
|
-
}
|
|
1509
|
-
break;
|
|
1510
|
-
case "FREE":
|
|
1511
|
-
this.rightHandle.position = destPos;
|
|
1512
|
-
break;
|
|
1513
|
-
case "VECTOR":
|
|
1514
|
-
break;
|
|
1515
|
-
default:
|
|
1516
|
-
throw new Error(`Unknown left handle type for control point`);
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
getLeftHandle() {
|
|
1520
|
-
return this.leftHandle;
|
|
1521
|
-
}
|
|
1522
|
-
getRightHandle() {
|
|
1523
|
-
return this.rightHandle;
|
|
1524
|
-
}
|
|
1423
|
+
let diff = PointCal3.subVector(destPos, this.position);
|
|
1424
|
+
let leftHandleDiff = PointCal3.unitVectorFromA2B(this.leftHandle.position, this.position);
|
|
1425
|
+
let resMag = PointCal3.dotProduct(diff, leftHandleDiff);
|
|
1426
|
+
let res = PointCal3.multiplyVectorByScalar(leftHandleDiff, resMag);
|
|
1427
|
+
this.rightHandle.position = PointCal3.addVector(this.position, res);
|
|
1428
|
+
} else if (this.rightHandle.type == "ALIGNED") {
|
|
1429
|
+
this.rightHandle.position = destPos;
|
|
1430
|
+
let mag = PointCal3.distanceBetweenPoints(this.leftHandle.position, this.position);
|
|
1431
|
+
let direction = PointCal3.unitVectorFromA2B(this.rightHandle.position, this.position);
|
|
1432
|
+
let res = PointCal3.multiplyVectorByScalar(direction, mag);
|
|
1433
|
+
this.leftHandle.position = PointCal3.addVector(res, this.position);
|
|
1434
|
+
} else {
|
|
1435
|
+
this.rightHandle.position = destPos;
|
|
1436
|
+
}
|
|
1437
|
+
break;
|
|
1438
|
+
case "FREE":
|
|
1439
|
+
this.rightHandle.position = destPos;
|
|
1440
|
+
break;
|
|
1441
|
+
case "VECTOR":
|
|
1442
|
+
break;
|
|
1443
|
+
default:
|
|
1444
|
+
throw new Error(`Unknown left handle type for control point`);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
getLeftHandle() {
|
|
1448
|
+
return this.leftHandle;
|
|
1449
|
+
}
|
|
1450
|
+
getRightHandle() {
|
|
1451
|
+
return this.rightHandle;
|
|
1452
|
+
}
|
|
1525
1453
|
}
|
|
1454
|
+
|
|
1526
1455
|
class CompositeBCurve {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1456
|
+
controlPoints;
|
|
1457
|
+
constructor(controlPoints = []) {
|
|
1458
|
+
this.controlPoints = controlPoints;
|
|
1459
|
+
}
|
|
1460
|
+
getControlPoints() {
|
|
1461
|
+
return this.controlPoints;
|
|
1462
|
+
}
|
|
1463
|
+
appendControlPoint(position) {
|
|
1464
|
+
let leftHandlePosition = PointCal3.addVector(position, { x: -100, y: 0 });
|
|
1465
|
+
let rightHandlePosition = PointCal3.addVector(position, { x: 100, y: 0 });
|
|
1466
|
+
let leftHandlePoint = {
|
|
1467
|
+
position: leftHandlePosition,
|
|
1468
|
+
type: "FREE"
|
|
1469
|
+
};
|
|
1470
|
+
let rightHandlePoint = {
|
|
1471
|
+
position: rightHandlePosition,
|
|
1472
|
+
type: "FREE"
|
|
1473
|
+
};
|
|
1474
|
+
let newControlPoint = new ControlPoint(position, leftHandlePoint, rightHandlePoint);
|
|
1475
|
+
this.controlPoints.push(newControlPoint);
|
|
1476
|
+
}
|
|
1477
|
+
setLeftHandlePositionOfControlPoint(controlPointIndex, destPos) {
|
|
1478
|
+
if (controlPointIndex >= this.controlPoints.length || controlPointIndex < 0) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
this.controlPoints[controlPointIndex].setLeftHandlePosition(destPos);
|
|
1482
|
+
}
|
|
1483
|
+
setRightHandlePositionOfControlPoint(controlPointIndex, destPos) {
|
|
1484
|
+
if (controlPointIndex >= this.controlPoints.length || controlPointIndex < 0) {
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
this.controlPoints[controlPointIndex].setRightHandlePosition(destPos);
|
|
1488
|
+
}
|
|
1489
|
+
setPositionOfControlPoint(controlPointIndex, destPos) {
|
|
1490
|
+
if (controlPointIndex >= this.controlPoints.length || controlPointIndex < 0) {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
let prevControlPoint = undefined;
|
|
1494
|
+
let nextControlPoint = undefined;
|
|
1495
|
+
if (controlPointIndex + 1 < this.controlPoints.length) {
|
|
1496
|
+
nextControlPoint = this.controlPoints[controlPointIndex + 1];
|
|
1497
|
+
}
|
|
1498
|
+
if (controlPointIndex - 1 >= 0) {
|
|
1499
|
+
prevControlPoint = this.controlPoints[controlPointIndex - 1];
|
|
1500
|
+
}
|
|
1501
|
+
this.controlPoints[controlPointIndex].setPosition(destPos, prevControlPoint, nextControlPoint);
|
|
1502
|
+
}
|
|
1573
1503
|
}
|
|
1574
|
-
|
|
1504
|
+
// src/path.ts
|
|
1575
1505
|
class Path {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
return line.lerp(ratio);
|
|
1638
|
-
}
|
|
1506
|
+
lines;
|
|
1507
|
+
constructor(lines) {
|
|
1508
|
+
this.lines = lines;
|
|
1509
|
+
}
|
|
1510
|
+
append(line) {
|
|
1511
|
+
this.lines.push(line);
|
|
1512
|
+
}
|
|
1513
|
+
clear() {
|
|
1514
|
+
this.lines = [];
|
|
1515
|
+
}
|
|
1516
|
+
prepend(line) {
|
|
1517
|
+
this.lines.unshift(line);
|
|
1518
|
+
}
|
|
1519
|
+
getLines() {
|
|
1520
|
+
return this.lines;
|
|
1521
|
+
}
|
|
1522
|
+
getLength() {
|
|
1523
|
+
let res = 0;
|
|
1524
|
+
this.lines.forEach((line) => {
|
|
1525
|
+
res += line.length();
|
|
1526
|
+
});
|
|
1527
|
+
return res;
|
|
1528
|
+
}
|
|
1529
|
+
getPercentages() {
|
|
1530
|
+
const length = this.getLength();
|
|
1531
|
+
let currentCurvePercentage = 0;
|
|
1532
|
+
const res = [];
|
|
1533
|
+
this.lines.forEach((line) => {
|
|
1534
|
+
const lineLength = line.length();
|
|
1535
|
+
const linePercentage = lineLength / length;
|
|
1536
|
+
let start = currentCurvePercentage;
|
|
1537
|
+
currentCurvePercentage += linePercentage;
|
|
1538
|
+
let end = currentCurvePercentage;
|
|
1539
|
+
res.push({ start, end });
|
|
1540
|
+
});
|
|
1541
|
+
res[res.length - 1].end = 1;
|
|
1542
|
+
return res;
|
|
1543
|
+
}
|
|
1544
|
+
getPointByPercentage(percentage) {
|
|
1545
|
+
if (percentage < 0 || percentage > 1) {
|
|
1546
|
+
throw new Error("Percentage must be between 0 and 1");
|
|
1547
|
+
}
|
|
1548
|
+
const percentages = this.getPercentages();
|
|
1549
|
+
let left = 0;
|
|
1550
|
+
let right = percentages.length - 1;
|
|
1551
|
+
while (left <= right) {
|
|
1552
|
+
const mid = Math.floor((left + right) / 2);
|
|
1553
|
+
if (percentage < percentages[mid].end) {
|
|
1554
|
+
right = mid - 1;
|
|
1555
|
+
} else if (percentage > percentages[mid].end) {
|
|
1556
|
+
left = mid + 1;
|
|
1557
|
+
} else {
|
|
1558
|
+
left = mid;
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
const line = this.lines[left];
|
|
1563
|
+
const linePercentage = percentages[left];
|
|
1564
|
+
const ratio = (percentage - linePercentage.start) / (linePercentage.end - linePercentage.start);
|
|
1565
|
+
return line.lerp(ratio);
|
|
1566
|
+
}
|
|
1639
1567
|
}
|
|
1568
|
+
export {
|
|
1569
|
+
solveCubic,
|
|
1570
|
+
reduce,
|
|
1571
|
+
projectPointOntoLine,
|
|
1572
|
+
offset2,
|
|
1573
|
+
offset,
|
|
1574
|
+
getLineIntersection,
|
|
1575
|
+
getIntersectionsBetweenCurves,
|
|
1576
|
+
getCubicRoots,
|
|
1577
|
+
cuberoot2,
|
|
1578
|
+
cuberoot,
|
|
1579
|
+
computeWithControlPoints,
|
|
1580
|
+
approximately,
|
|
1581
|
+
accept,
|
|
1582
|
+
TValOutofBoundError,
|
|
1583
|
+
Path,
|
|
1584
|
+
Line,
|
|
1585
|
+
ControlPoint,
|
|
1586
|
+
CompositeBCurve,
|
|
1587
|
+
BCurve,
|
|
1588
|
+
AABBIntersects
|
|
1589
|
+
};
|
|
1640
1590
|
|
|
1641
|
-
|
|
1642
|
-
//# sourceMappingURL=index.js.map
|
|
1591
|
+
//# debugId=EABF32F5EF0F7EC864756E2164756E21
|