@escapace/minimum-perimeter-triangle 0.1.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/LICENSE +373 -0
- package/README.md +1 -0
- package/lib/cjs/index.cjs +590 -0
- package/lib/cjs/index.cjs.map +7 -0
- package/lib/esm/index.mjs +567 -0
- package/lib/esm/index.mjs.map +7 -0
- package/lib/types/index.d.ts +36 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/inscribe.d.ts +35 -0
- package/lib/types/inscribe.d.ts.map +1 -0
- package/lib/types/line.d.ts +36 -0
- package/lib/types/line.d.ts.map +1 -0
- package/lib/types/vec2.d.ts +23 -0
- package/lib/types/vec2.d.ts.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
lineTangentToHull: () => lineTangentToHull,
|
|
24
|
+
minTriangle: () => minTriangle,
|
|
25
|
+
minTriangleWithBase: () => minTriangleWithBase
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
|
|
29
|
+
// src/line.ts
|
|
30
|
+
var Line = class {
|
|
31
|
+
start;
|
|
32
|
+
end;
|
|
33
|
+
delta;
|
|
34
|
+
constructor(start, end) {
|
|
35
|
+
this.start = start;
|
|
36
|
+
this.end = end;
|
|
37
|
+
this.delta = end.minus(start);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Length of a line is the length between its two defining points
|
|
41
|
+
*/
|
|
42
|
+
get length() {
|
|
43
|
+
return this.delta.norm;
|
|
44
|
+
}
|
|
45
|
+
evaluate(t) {
|
|
46
|
+
return this.start.plus(this.delta.times(t));
|
|
47
|
+
}
|
|
48
|
+
distanceToPoint(p) {
|
|
49
|
+
return Math.abs(p.cross(this.delta) - this.start.cross(this.end)) / this.delta.norm;
|
|
50
|
+
}
|
|
51
|
+
pointOnSide(p, err = 0) {
|
|
52
|
+
const num = this.start.cross(this.end) - p.cross(this.delta);
|
|
53
|
+
if (num === 0 || Math.abs(num) / this.delta.norm < err) {
|
|
54
|
+
return 0 /* Top */;
|
|
55
|
+
}
|
|
56
|
+
return num > 0 ? 1 /* Left */ : -1 /* Right */;
|
|
57
|
+
}
|
|
58
|
+
pointOnTop(p, err) {
|
|
59
|
+
return this.pointOnSide(p, err) === 0 /* Top */;
|
|
60
|
+
}
|
|
61
|
+
overlaps(that, err) {
|
|
62
|
+
return this.pointOnTop(that.start, err) && this.pointOnTop(that.end, err);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* If alpha is less than deviationFromZeroAngle, the 2 lines are
|
|
66
|
+
* considered parallel.
|
|
67
|
+
* _______________________________
|
|
68
|
+
* alpha (/
|
|
69
|
+
* /
|
|
70
|
+
* /
|
|
71
|
+
* /
|
|
72
|
+
*/
|
|
73
|
+
parallel(that, deviationFromZeroAngle) {
|
|
74
|
+
const d = Math.abs(this.delta.cross(that.delta));
|
|
75
|
+
return d === 0 || d < this.length * this.length * Math.sin(deviationFromZeroAngle);
|
|
76
|
+
}
|
|
77
|
+
intersectionParameter(that, err) {
|
|
78
|
+
const d = this.delta.cross(that.delta);
|
|
79
|
+
if (d === 0 || Math.abs(d) < err) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const dStart = this.start.minus(that.start);
|
|
83
|
+
return that.delta.cross(dStart) / d;
|
|
84
|
+
}
|
|
85
|
+
closestPointParam(p) {
|
|
86
|
+
return this.delta.dot(p.minus(this.start)) / this.delta.normSquared;
|
|
87
|
+
}
|
|
88
|
+
closestPoint(p) {
|
|
89
|
+
return this.evaluate(this.closestPointParam(p));
|
|
90
|
+
}
|
|
91
|
+
intersectionPoint(that, err) {
|
|
92
|
+
const t = this.intersectionParameter(that, err);
|
|
93
|
+
return t === null ? null : this.evaluate(t);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/vec2.ts
|
|
98
|
+
var Vec2 = class {
|
|
99
|
+
_x;
|
|
100
|
+
_y;
|
|
101
|
+
_normSquared;
|
|
102
|
+
_norm;
|
|
103
|
+
_normalized;
|
|
104
|
+
constructor(x, y) {
|
|
105
|
+
this._x = x;
|
|
106
|
+
this._y = y;
|
|
107
|
+
}
|
|
108
|
+
times(s) {
|
|
109
|
+
return new Vec2(this._x * s, this._y * s);
|
|
110
|
+
}
|
|
111
|
+
over(s) {
|
|
112
|
+
return new Vec2(this._x / s, this._y / s);
|
|
113
|
+
}
|
|
114
|
+
get x() {
|
|
115
|
+
return this._x;
|
|
116
|
+
}
|
|
117
|
+
get y() {
|
|
118
|
+
return this._y;
|
|
119
|
+
}
|
|
120
|
+
plus(that) {
|
|
121
|
+
return new Vec2(this._x + that._x, this._y + that._y);
|
|
122
|
+
}
|
|
123
|
+
minus(that) {
|
|
124
|
+
return new Vec2(this._x - that._x, this._y - that._y);
|
|
125
|
+
}
|
|
126
|
+
get normSquared() {
|
|
127
|
+
return this._normSquared === void 0 ? this._normSquared = this.dot(this) : this._normSquared;
|
|
128
|
+
}
|
|
129
|
+
get norm() {
|
|
130
|
+
return this._norm === void 0 ? this._norm = Math.sqrt(this.normSquared) : this._norm;
|
|
131
|
+
}
|
|
132
|
+
get normalized() {
|
|
133
|
+
return this._normalized === void 0 ? this._normalized = this.over(this.norm) : this._normalized;
|
|
134
|
+
}
|
|
135
|
+
dot(that) {
|
|
136
|
+
return this._x * that._x + this._y * that._y;
|
|
137
|
+
}
|
|
138
|
+
cross(that) {
|
|
139
|
+
return this._x * that._y - this._y * that._x;
|
|
140
|
+
}
|
|
141
|
+
equals(that, err) {
|
|
142
|
+
if (err === 0) {
|
|
143
|
+
return this.x === that.x && this.y === that.y;
|
|
144
|
+
}
|
|
145
|
+
return this.minus(that).normSquared < err * err;
|
|
146
|
+
}
|
|
147
|
+
normal() {
|
|
148
|
+
return new Vec2(this._y, -this._x);
|
|
149
|
+
}
|
|
150
|
+
toString() {
|
|
151
|
+
return `(${this.x}, ${this.y})`;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/inscribe.ts
|
|
156
|
+
var Wedge = class {
|
|
157
|
+
leftArm;
|
|
158
|
+
rightArm;
|
|
159
|
+
isDegenerate;
|
|
160
|
+
constructor(leftArm, rightArm, isDegenerate = false) {
|
|
161
|
+
this.leftArm = leftArm;
|
|
162
|
+
this.rightArm = rightArm;
|
|
163
|
+
this.isDegenerate = isDegenerate;
|
|
164
|
+
}
|
|
165
|
+
static new(leftArm, rightArm, err) {
|
|
166
|
+
if (leftArm === null || rightArm === null) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (err !== 0 && leftArm.overlaps(rightArm, err)) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const deviationFromZeroAngle = 0.1 / (leftArm.length * rightArm.length);
|
|
173
|
+
if (leftArm.parallel(rightArm, deviationFromZeroAngle)) {
|
|
174
|
+
const middle = new Line(leftArm.evaluate(0.5), rightArm.evaluate(0.5));
|
|
175
|
+
const p = middle.evaluate(0.5);
|
|
176
|
+
const sideLeft = leftArm.pointOnSide(p, err);
|
|
177
|
+
const sideRight = rightArm.pointOnSide(p, err);
|
|
178
|
+
if (sideLeft === 0 /* Top */ || sideRight === 0 /* Top */) {
|
|
179
|
+
throw new Error();
|
|
180
|
+
}
|
|
181
|
+
return sideLeft !== sideRight ? new Wedge(leftArm, rightArm, true) : new Wedge(leftArm, new Line(rightArm.end, rightArm.start));
|
|
182
|
+
}
|
|
183
|
+
const tLA = leftArm.intersectionParameter(rightArm, 0);
|
|
184
|
+
const tRA = rightArm.intersectionParameter(leftArm, 0);
|
|
185
|
+
if (tLA === 0.5 || tRA === 0.5) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const W = leftArm.evaluate(tLA);
|
|
189
|
+
const eLA = tLA < 1 - tLA ? leftArm.end : leftArm.start;
|
|
190
|
+
const eRA = tRA < 1 - tRA ? rightArm.end : rightArm.start;
|
|
191
|
+
return new Wedge(new Line(W, eLA), new Line(W, eRA));
|
|
192
|
+
}
|
|
193
|
+
formTriangle(line, err) {
|
|
194
|
+
const thin = this.leftArm.parallel(line, 0.1 / (this.leftArm.length * line.length)) || this.rightArm.parallel(line, 0.1 / (this.rightArm.length * line.length));
|
|
195
|
+
if (thin) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const A = line.intersectionPoint(this.leftArm, 0);
|
|
199
|
+
const B = line.intersectionPoint(this.rightArm, 0);
|
|
200
|
+
if (this.isDegenerate) {
|
|
201
|
+
return !A.equals(B, err);
|
|
202
|
+
}
|
|
203
|
+
const C = this.leftArm.intersectionPoint(this.rightArm, 0);
|
|
204
|
+
return !C.equals(A, err) && !C.equals(B, err) && !A.equals(B, err) && !new Line(A, B).pointOnTop(C, err);
|
|
205
|
+
}
|
|
206
|
+
looselyContains(p, err) {
|
|
207
|
+
const pLeft = this.leftArm.pointOnSide(p, err);
|
|
208
|
+
const pRight = this.rightArm.pointOnSide(p, err);
|
|
209
|
+
if (pLeft === 0 /* Top */ || pRight === 0 /* Top */) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
if (pLeft === pRight) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return this.isDegenerate ? (
|
|
216
|
+
// degenerate + different sides => true
|
|
217
|
+
true
|
|
218
|
+
) : (
|
|
219
|
+
// 2. (Because the arms intersect)
|
|
220
|
+
// Projection params of the point onto the arms must be larger than 0
|
|
221
|
+
this.leftArm.closestPointParam(p) >= 0 && this.rightArm.closestPointParam(p) >= 0
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
strictlyContains(p, err) {
|
|
225
|
+
const pLeft = this.leftArm.pointOnSide(p, err);
|
|
226
|
+
const pRight = this.rightArm.pointOnSide(p, err);
|
|
227
|
+
if (pLeft === 0 /* Top */ || pRight === 0 /* Top */) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
if (pLeft === pRight) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return this.isDegenerate ? (
|
|
234
|
+
// degenerate + different sides => true
|
|
235
|
+
true
|
|
236
|
+
) : (
|
|
237
|
+
// 2. (Because the arms intersect)
|
|
238
|
+
// Projection params of the point onto the arms must be larger than 0
|
|
239
|
+
this.leftArm.closestPointParam(p) >= 0 && this.rightArm.closestPointParam(p) >= 0
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
// While fitting circles into a wedge
|
|
243
|
+
// There are four distinct cases:
|
|
244
|
+
// 1. Wedge is degenerate and additional element is a point
|
|
245
|
+
// 2. Wedge is degenerate and additional element is a line
|
|
246
|
+
// 3. Wedge is non-degenerate and additional element is a point
|
|
247
|
+
// 4. Wedge is non-degenerate and additional element is a line
|
|
248
|
+
// according to these assumptions, the following methods are named
|
|
249
|
+
fit_Dp(p, err) {
|
|
250
|
+
if (!this.strictlyContains(p, err)) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const A = this.rightArm.closestPoint(p);
|
|
254
|
+
const Ap = this.leftArm.closestPoint(A);
|
|
255
|
+
const I = A.plus(Ap).over(2);
|
|
256
|
+
const r = A.minus(Ap).norm / 2;
|
|
257
|
+
const a = this.rightArm.delta.normSquared;
|
|
258
|
+
const b = I.minus(p).dot(this.rightArm.delta) * 2;
|
|
259
|
+
const c = I.minus(p).normSquared - r * r;
|
|
260
|
+
const discriminant = b * b - 4 * a * c;
|
|
261
|
+
if (discriminant < (-10) ** -5) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const t = [];
|
|
265
|
+
if (Math.abs(discriminant) < 10 ** -5) {
|
|
266
|
+
t.push(-b / (2 * a));
|
|
267
|
+
} else {
|
|
268
|
+
t.push((-b + Math.sqrt(discriminant)) / (2 * a));
|
|
269
|
+
t.push((-b - Math.sqrt(discriminant)) / (2 * a));
|
|
270
|
+
}
|
|
271
|
+
const result = [];
|
|
272
|
+
t.forEach((t0) => {
|
|
273
|
+
const O = this.rightArm.delta.times(t0).plus(I);
|
|
274
|
+
result.push({
|
|
275
|
+
circle: { centre: O, r },
|
|
276
|
+
tangent: new Line(p, p.plus(O.minus(p).normal()))
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
fit_Dl(l, err) {
|
|
282
|
+
if (!this.formTriangle(l, err)) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const A = l.intersectionPoint(this.rightArm, 0);
|
|
286
|
+
const B = l.intersectionPoint(this.leftArm, 0);
|
|
287
|
+
const AB = new Line(A, B);
|
|
288
|
+
const Ap = this.leftArm.closestPoint(A);
|
|
289
|
+
const I = A.plus(Ap).over(2);
|
|
290
|
+
const r = A.minus(Ap).norm / 2;
|
|
291
|
+
const t1 = (AB.delta.cross(A.minus(I)) + r * AB.delta.norm) / AB.delta.cross(this.rightArm.delta);
|
|
292
|
+
const t2 = (AB.delta.cross(A.minus(I)) - r * AB.delta.norm) / AB.delta.cross(this.rightArm.delta);
|
|
293
|
+
const o1 = this.rightArm.delta.times(t1).plus(I);
|
|
294
|
+
const o2 = this.rightArm.delta.times(t2).plus(I);
|
|
295
|
+
return [
|
|
296
|
+
{
|
|
297
|
+
circle: { centre: o1, r },
|
|
298
|
+
tangentParameter: l.closestPointParam(o1)
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
circle: { centre: o2, r },
|
|
302
|
+
tangentParameter: l.closestPointParam(o2)
|
|
303
|
+
}
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
fit_NDp(p, err) {
|
|
307
|
+
if (!this.strictlyContains(p, err)) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
const C = this.leftArm.start;
|
|
311
|
+
const A = this.leftArm.end;
|
|
312
|
+
const B = this.rightArm.end;
|
|
313
|
+
const a = C.minus(B).norm;
|
|
314
|
+
const b = C.minus(A).norm;
|
|
315
|
+
const D = A.minus(B).times(a / (a + b)).plus(B);
|
|
316
|
+
const bisector = new Line(C, D);
|
|
317
|
+
const eA = D.minus(C).normSquared - (A.minus(C).cross(D.minus(C)) / b) ** 2;
|
|
318
|
+
const eB = D.minus(C).dot(C.minus(p)) * 2;
|
|
319
|
+
const eC = C.minus(p).normSquared;
|
|
320
|
+
const discriminant = eB * eB - 4 * eA * eC;
|
|
321
|
+
if (discriminant < (-10) ** -5) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
let O, r;
|
|
325
|
+
if (Math.abs(discriminant) < 10 ** -5) {
|
|
326
|
+
const t = -eB / (2 * eA);
|
|
327
|
+
O = bisector.evaluate(t);
|
|
328
|
+
r = O.minus(p).norm;
|
|
329
|
+
} else {
|
|
330
|
+
const t1 = (-eB + Math.sqrt(discriminant)) / (2 * eA);
|
|
331
|
+
const t2 = (-eB - Math.sqrt(discriminant)) / (2 * eA);
|
|
332
|
+
if (bisector.evaluate(t1).minus(p).normSquared > bisector.evaluate(t2).minus(p).normSquared) {
|
|
333
|
+
O = bisector.evaluate(t1);
|
|
334
|
+
r = bisector.evaluate(t1).minus(p).norm;
|
|
335
|
+
} else {
|
|
336
|
+
O = bisector.evaluate(t2);
|
|
337
|
+
r = bisector.evaluate(t2).minus(p).norm;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return [
|
|
341
|
+
{
|
|
342
|
+
circle: { centre: O, r },
|
|
343
|
+
tangent: new Line(p, p.plus(O.minus(p).normal()))
|
|
344
|
+
}
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
fit_NDl(l, err) {
|
|
348
|
+
if (!this.formTriangle(l, err)) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const C = this.leftArm.start;
|
|
352
|
+
const A = l.intersectionPoint(this.leftArm, 0);
|
|
353
|
+
const B = l.intersectionPoint(this.rightArm, 0);
|
|
354
|
+
const AC = new Line(A, C);
|
|
355
|
+
const BC = new Line(B, C);
|
|
356
|
+
const AB = new Line(A, B);
|
|
357
|
+
const a = AC.length;
|
|
358
|
+
const b = BC.length;
|
|
359
|
+
const c = AB.length;
|
|
360
|
+
const s = (a + b + c) / 2;
|
|
361
|
+
if (s * (s - a) * (s - b) / (s - c) < 0) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const r = Math.sqrt(s * (s - a) * (s - b) / (s - c));
|
|
365
|
+
const det = AB.delta.cross(AC.delta);
|
|
366
|
+
const lhsAll = [
|
|
367
|
+
new Vec2(B.cross(A) + r * c, C.cross(A) + r * a),
|
|
368
|
+
new Vec2(B.cross(A) + r * c, C.cross(A) - r * a),
|
|
369
|
+
new Vec2(B.cross(A) - r * c, C.cross(A) + r * a),
|
|
370
|
+
new Vec2(B.cross(A) - r * c, C.cross(A) - r * a)
|
|
371
|
+
];
|
|
372
|
+
const OAll = [];
|
|
373
|
+
lhsAll.forEach((lhs) => {
|
|
374
|
+
OAll.push(
|
|
375
|
+
new Vec2(
|
|
376
|
+
new Vec2(AB.delta.x, AC.delta.x).cross(lhs),
|
|
377
|
+
new Vec2(AB.delta.y, AC.delta.y).cross(lhs)
|
|
378
|
+
).over(-det)
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
let o = null;
|
|
382
|
+
const dists = [];
|
|
383
|
+
for (const O of OAll) {
|
|
384
|
+
dists.push({
|
|
385
|
+
raw: Math.abs(BC.distanceToPoint(O) - r),
|
|
386
|
+
norm: Math.abs(BC.distanceToPoint(O) / r - 1)
|
|
387
|
+
});
|
|
388
|
+
const absoluteError = Math.abs(BC.distanceToPoint(O) - r);
|
|
389
|
+
const relativeError = Math.abs(BC.distanceToPoint(O) / r - 1);
|
|
390
|
+
if ((absoluteError < 10 ** -5 || relativeError < 10 ** -5) && AC.pointOnSide(O) !== BC.pointOnSide(O)) {
|
|
391
|
+
o = O;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
let msg = "";
|
|
396
|
+
if (o === null) {
|
|
397
|
+
msg = "fit_NDl, centre is undefined";
|
|
398
|
+
for (let i = 0; i < OAll.length; i++) {
|
|
399
|
+
msg += `centre: (${OAll[i].x}, ${OAll[i].y}), r: ${r}, dist raw: ${dists[i].raw}, dist norm: ${dists[i].norm}
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (o === null) {
|
|
404
|
+
throw new Error(msg);
|
|
405
|
+
}
|
|
406
|
+
return [
|
|
407
|
+
{
|
|
408
|
+
circle: { centre: o, r },
|
|
409
|
+
tangentParameter: l.closestPointParam(o)
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
}
|
|
413
|
+
fitCircles(element, err) {
|
|
414
|
+
if (element instanceof Vec2) {
|
|
415
|
+
return this.isDegenerate ? this.fit_Dp(element, err) : this.fit_NDp(element, err);
|
|
416
|
+
}
|
|
417
|
+
if (element instanceof Line) {
|
|
418
|
+
return this.isDegenerate ? this.fit_Dl(element instanceof Line ? element : element, err) : this.fit_NDl(element instanceof Line ? element : element, err);
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
toString() {
|
|
423
|
+
return `LA: ${this.leftArm.start.toString()} --> ${this.leftArm.end.toString()}
|
|
424
|
+
RA: ${this.rightArm.start.toString()} --> ${this.rightArm.end.toString()}`;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// src/index.ts
|
|
429
|
+
function lineTangentToHull(line, points, halo) {
|
|
430
|
+
let holds = true;
|
|
431
|
+
let side = 0 /* Top */;
|
|
432
|
+
let k = 0;
|
|
433
|
+
while (side === 0 /* Top */ && k < points.length) {
|
|
434
|
+
side = line.pointOnSide(points[k], halo);
|
|
435
|
+
k++;
|
|
436
|
+
}
|
|
437
|
+
for (let i = k; i < points.length; i++) {
|
|
438
|
+
const testSide = line.pointOnSide(points[i], halo);
|
|
439
|
+
if (testSide === 0 /* Top */) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (testSide !== side) {
|
|
443
|
+
holds = false;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return { holds, side };
|
|
448
|
+
}
|
|
449
|
+
function findEnclosingSide(wedge, startVertex, endVertex, points, halo) {
|
|
450
|
+
let side = null;
|
|
451
|
+
let stopVertex = startVertex;
|
|
452
|
+
let vertex = startVertex;
|
|
453
|
+
while (side === null && vertex > endVertex) {
|
|
454
|
+
const p1 = points[vertex];
|
|
455
|
+
const p2 = points[vertex - 1];
|
|
456
|
+
const edge = new Line(p1, p2);
|
|
457
|
+
const circlesEdge = wedge.fitCircles(edge, halo);
|
|
458
|
+
if (circlesEdge !== null) {
|
|
459
|
+
let tangentParameter = 100;
|
|
460
|
+
if (wedge.isDegenerate) {
|
|
461
|
+
let sidedness = 0 /* Top */;
|
|
462
|
+
let k = 0;
|
|
463
|
+
while (sidedness === 0 /* Top */ && k < points.length) {
|
|
464
|
+
sidedness = edge.pointOnSide(points[k], halo);
|
|
465
|
+
k++;
|
|
466
|
+
}
|
|
467
|
+
tangentParameter = edge.pointOnSide(circlesEdge[0].circle.centre) !== sidedness ? circlesEdge[0].tangentParameter : circlesEdge[1].tangentParameter;
|
|
468
|
+
} else {
|
|
469
|
+
tangentParameter = circlesEdge[0].tangentParameter;
|
|
470
|
+
}
|
|
471
|
+
if (tangentParameter > 0 && tangentParameter < 1) {
|
|
472
|
+
const Y = edge.evaluate(tangentParameter);
|
|
473
|
+
const joint = wedge.leftArm.intersectionPoint(edge, halo);
|
|
474
|
+
side = new Line(joint, Y);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (side === null) {
|
|
478
|
+
const circlesPoint = wedge.fitCircles(p2, halo);
|
|
479
|
+
if (circlesPoint !== null) {
|
|
480
|
+
let tangent;
|
|
481
|
+
if (wedge.isDegenerate) {
|
|
482
|
+
let sidedness = 0 /* Top */;
|
|
483
|
+
let k = 0;
|
|
484
|
+
while (sidedness === 0 /* Top */ && k < points.length) {
|
|
485
|
+
sidedness = circlesPoint[0].tangent.pointOnSide(points[k], halo);
|
|
486
|
+
k++;
|
|
487
|
+
}
|
|
488
|
+
tangent = circlesPoint[0].tangent.pointOnSide(
|
|
489
|
+
circlesPoint[0].circle.centre,
|
|
490
|
+
halo
|
|
491
|
+
) !== sidedness ? circlesPoint[0].tangent : circlesPoint[1].tangent;
|
|
492
|
+
} else {
|
|
493
|
+
tangent = circlesPoint[0].tangent;
|
|
494
|
+
}
|
|
495
|
+
if (lineTangentToHull(tangent, points, halo).holds) {
|
|
496
|
+
const joint = wedge.leftArm.intersectionPoint(tangent, halo);
|
|
497
|
+
side = new Line(joint, p2);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
stopVertex = vertex;
|
|
502
|
+
vertex--;
|
|
503
|
+
}
|
|
504
|
+
return side === null ? null : { side, stopVertex };
|
|
505
|
+
}
|
|
506
|
+
function findAntipode(points) {
|
|
507
|
+
let farthestIndex = 0;
|
|
508
|
+
let farthestDist = 0;
|
|
509
|
+
for (let i = 0, n = points.length; i < n; i++) {
|
|
510
|
+
const testDist = new Line(points[0], points[n - 1]).distanceToPoint(
|
|
511
|
+
points[i]
|
|
512
|
+
);
|
|
513
|
+
if (testDist > farthestDist) {
|
|
514
|
+
farthestDist = testDist;
|
|
515
|
+
farthestIndex = i;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return farthestIndex;
|
|
519
|
+
}
|
|
520
|
+
function minTriangleWithBase(convexHull, err, tol) {
|
|
521
|
+
let AB, AC;
|
|
522
|
+
const n = convexHull.length;
|
|
523
|
+
const BC = new Line(convexHull[0], convexHull[n - 1]);
|
|
524
|
+
const antipodIndex = findAntipode(convexHull);
|
|
525
|
+
const baseParallel = new Line(
|
|
526
|
+
convexHull[antipodIndex],
|
|
527
|
+
convexHull[antipodIndex].plus(BC.delta)
|
|
528
|
+
);
|
|
529
|
+
let wedge = Wedge.new(BC, baseParallel, err);
|
|
530
|
+
let Pn = n - 1;
|
|
531
|
+
let Qn = antipodIndex;
|
|
532
|
+
do {
|
|
533
|
+
const CQinfo = findEnclosingSide(wedge, Pn, antipodIndex, convexHull, err);
|
|
534
|
+
if (CQinfo === null) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
;
|
|
538
|
+
({ side: AC, stopVertex: Pn } = CQinfo);
|
|
539
|
+
wedge = Wedge.new(wedge.leftArm, AC, err);
|
|
540
|
+
const BPinfo = findEnclosingSide(wedge, Qn, 0, convexHull, err);
|
|
541
|
+
if (BPinfo === null) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
;
|
|
545
|
+
({ side: AB, stopVertex: Qn } = BPinfo);
|
|
546
|
+
wedge = Wedge.new(wedge.leftArm, AB, err);
|
|
547
|
+
} while (AB.length - AC.length > tol);
|
|
548
|
+
const A = AC.intersectionPoint(AB, 0);
|
|
549
|
+
const B = AB.start;
|
|
550
|
+
const C = AC.start;
|
|
551
|
+
return { A, B, C };
|
|
552
|
+
}
|
|
553
|
+
function minTriangle(convexHull, err, tol) {
|
|
554
|
+
if (convexHull.length < 3) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
if (convexHull.length === 3) {
|
|
558
|
+
return { A: convexHull[0], B: convexHull[1], C: convexHull[2] };
|
|
559
|
+
}
|
|
560
|
+
const points = convexHull.map((p) => {
|
|
561
|
+
return new Vec2(p.x, p.y);
|
|
562
|
+
});
|
|
563
|
+
let A = null;
|
|
564
|
+
let B = null;
|
|
565
|
+
let C = null;
|
|
566
|
+
let perimeter = -1;
|
|
567
|
+
let rotations = 0;
|
|
568
|
+
while (rotations < points.length) {
|
|
569
|
+
if (rotations > 0) {
|
|
570
|
+
points.push(points.shift());
|
|
571
|
+
}
|
|
572
|
+
const triangle = minTriangleWithBase(points, err, tol);
|
|
573
|
+
if (triangle !== null) {
|
|
574
|
+
const { A: A1, B: B1, C: C1 } = triangle;
|
|
575
|
+
const perimeter1 = A1.minus(B1).norm + B1.minus(C1).norm + C1.minus(A1).norm;
|
|
576
|
+
if (perimeter1 < perimeter || perimeter === -1) {
|
|
577
|
+
;
|
|
578
|
+
[A, B, C] = [A1, B1, C1];
|
|
579
|
+
perimeter = perimeter1;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
rotations++;
|
|
583
|
+
}
|
|
584
|
+
return perimeter === -1 ? null : {
|
|
585
|
+
A: { x: A.x, y: A.y },
|
|
586
|
+
B: { x: B.x, y: B.y },
|
|
587
|
+
C: { x: C.x, y: C.y }
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
//# sourceMappingURL=index.cjs.map
|