@escapace/minimum-perimeter-triangle 0.2.5 → 0.3.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.
@@ -1,25 +1,42 @@
1
- import { Line } from './line';
2
- import { Vec2 } from './vec2';
1
+ declare class Line {
2
+ readonly delta: Vec2;
3
+ readonly end: Vec2;
4
+ readonly start: Vec2;
5
+ constructor(start: Vec2, end: Vec2);
6
+ closestPoint(p: Vec2): Vec2;
7
+ closestPointParam(p: Vec2): number;
8
+ distanceToPoint(p: Vec2): number;
9
+ evaluate(t: number): Vec2;
10
+ intersectionParameter(that: Line, error: number): number | null;
11
+ intersectionPoint(that: Line, error: number): Vec2 | null;
12
+ overlaps(that: Line, error: number): boolean;
13
+ /**
14
+ * If alpha is less than deviationFromZeroAngle, the 2 lines are
15
+ * considered parallel.
16
+ * _______________________________
17
+ * alpha (/
18
+ * /
19
+ * /
20
+ * /
21
+ */
22
+ parallel(that: Line, deviationFromZeroAngle: number): boolean;
23
+ pointOnSide(p: Vec2, error?: number): number;
24
+ pointOnTop(p: Vec2, error: number): boolean;
25
+ /**
26
+ * Length of a line is the length between its two defining points
27
+ */
28
+ get length(): number;
29
+ }
30
+
3
31
  export declare function lineTangentToHull(line: Line, points: Vec2[], halo: number): {
4
32
  holds: boolean;
5
33
  side: number;
6
34
  };
7
- /**
8
- * Finds a minimal perimeter triangle enclosing a convex hull of an _arc_ trace
9
- * The longest side of the hull is considered to be a bottom of the triangle and
10
- * _is fixed_. This procedure is tailored from the generic algorithm given in
11
- * http://scholar.uwindsor.ca/cgi/viewcontent.cgi?article=2527&context=etd
12
- * starting from p. 22; In the notation of the generic algorithm BC is fixed
13
- */
14
- export declare function minTriangleWithBase(convexHull: Vec2[], err: number, tol: number): {
15
- A: Vec2;
16
- B: Vec2;
17
- C: Vec2;
18
- } | null;
35
+
19
36
  export declare function minTriangle(convexHull: Array<{
20
37
  x: number;
21
38
  y: number;
22
- }>, err: number, tol: number): {
39
+ }>, error: number, tol: number): {
23
40
  A: {
24
41
  x: number;
25
42
  y: number;
@@ -33,4 +50,41 @@ export declare function minTriangle(convexHull: Array<{
33
50
  y: number;
34
51
  };
35
52
  } | null;
36
- //# sourceMappingURL=index.d.ts.map
53
+
54
+ /**
55
+ * Finds a minimal perimeter triangle enclosing a convex hull of an _arc_ trace
56
+ * The longest side of the hull is considered to be a bottom of the triangle and
57
+ * _is fixed_. This procedure is tailored from the generic algorithm given in
58
+ * http://scholar.uwindsor.ca/cgi/viewcontent.cgi?article=2527&context=etd
59
+ * starting from p. 22; In the notation of the generic algorithm BC is fixed
60
+ */
61
+ export declare function minTriangleWithBase(convexHull: Vec2[], error: number, tol: number): {
62
+ A: Vec2;
63
+ B: Vec2;
64
+ C: Vec2;
65
+ } | null;
66
+
67
+ declare class Vec2 {
68
+ private _norm;
69
+ private _normalized;
70
+ private _normSquared;
71
+ private readonly _x;
72
+ private readonly _y;
73
+ constructor(x: number, y: number);
74
+ cross(that: Vec2): number;
75
+ dot(that: Vec2): number;
76
+ equals(that: Vec2, error: number): boolean;
77
+ minus(that: Vec2): Vec2;
78
+ normal(): Vec2;
79
+ over(s: number): Vec2;
80
+ plus(that: Vec2): Vec2;
81
+ times(s: number): Vec2;
82
+ toString(): string;
83
+ get norm(): number;
84
+ get normalized(): Vec2;
85
+ get normSquared(): number;
86
+ get x(): number;
87
+ get y(): number;
88
+ }
89
+
90
+ export { }
package/package.json CHANGED
@@ -1,69 +1,65 @@
1
1
  {
2
2
  "name": "@escapace/minimum-perimeter-triangle",
3
3
  "description": "",
4
- "version": "0.2.5",
4
+ "version": "0.3.0",
5
5
  "author": {
6
6
  "name": "escapace",
7
7
  "email": "opensource@escapace.com"
8
8
  },
9
9
  "bugs": "https://github.com/escapace/minimum-perimeter-triangle/issues",
10
10
  "devDependencies": {
11
- "@commitlint/cli": "17.7.1",
12
- "@commitlint/config-conventional": "17.7.0",
13
- "@ls-lint/ls-lint": "2.1.0",
14
- "@types/chai": "4.3.6",
15
- "@types/mocha": "10.0.1",
16
- "@types/node": "20.6.4",
17
- "@typescript-eslint/eslint-plugin": "6.7.2",
18
- "@typescript-eslint/parser": "6.7.2",
19
- "c8": "8.0.1",
20
- "chai": "4.3.8",
21
- "esbuild": "0.19.3",
22
- "eslint": "8.50.0",
23
- "eslint-config-escapace": "3.18.1",
24
- "eslint-config-prettier": "9.0.0",
25
- "eslint-plugin-editorconfig": "4.0.3",
26
- "eslint-plugin-no-null": "1.0.2",
27
- "execa": "8.0.1",
28
- "fast-glob": "3.3.1",
29
- "fs-extra": "11.1.1",
30
- "husky": "8.0.3",
31
- "is-ci": "3.0.1",
32
- "lint-staged": "14.0.1",
33
- "mocha": "10.2.0",
34
- "prettier": "3.0.3",
35
- "prettier-config-escapace": "1.0.6",
36
- "semver": "7.5.4",
37
- "syncpack": "11.2.1",
38
- "ts-node": "10.9.1",
39
- "typescript": "5.2.2"
11
+ "@commitlint/cli": "20.4.2",
12
+ "@commitlint/config-conventional": "20.4.2",
13
+ "@escapace/pnpm-pack": "0.7.0",
14
+ "@escapace/syncpack-configuration": "0.1.0",
15
+ "@ls-lint/ls-lint": "2.3.1",
16
+ "@vitest/coverage-v8": "4.0.18",
17
+ "eslint": "9.39.3",
18
+ "eslint-config-escapace": "5.13.0",
19
+ "esroll": "1.1.5",
20
+ "knip": "5.85.0",
21
+ "lefthook": "2.1.1",
22
+ "prettier": "3.8.1",
23
+ "prettier-config-escapace": "1.2.2",
24
+ "syncpack": "14.0.1",
25
+ "tsx": "4.21.0",
26
+ "typescript": "5.9.3",
27
+ "vitest": "4.0.18"
40
28
  },
41
29
  "engines": {
42
- "node": ">=18.18.0",
43
- "pnpm": ">=8.7.6"
30
+ "pnpm": ">=10.29.2"
44
31
  },
45
32
  "exports": {
46
33
  ".": {
47
- "import": "./lib/esm/index.mjs",
48
- "types": "./lib/types/index.d.ts"
34
+ "types": "./lib/types/index.d.ts",
35
+ "import": "./lib/neutral/index.js"
49
36
  }
50
37
  },
51
38
  "files": [
52
- "lib/esm",
39
+ "lib/neutral",
53
40
  "lib/types"
54
41
  ],
55
- "homepage": "https://github.com/escapace/minimum-perimeter-triangle",
42
+ "homepage": "https://github.com/escapace/minimum-perimeter-triangle#readme",
56
43
  "license": "MPL-2.0",
57
- "module": "lib/esm/index.mjs",
44
+ "module": "lib/neutral/index.js",
58
45
  "private": false,
59
- "repository": "escapace/minimum-perimeter-triangle.git",
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "provenance": true
49
+ },
50
+ "repository": "escapace/minimum-perimeter-triangle",
60
51
  "sideEffects": false,
61
52
  "type": "module",
62
53
  "types": "lib/types/index.d.ts",
63
54
  "scripts": {
64
- "build": "node ./scripts/build.mjs",
65
- "lint": "ls-lint && eslint 'src/**/*.{js,mjs,cjs,ts,mts,cts}'",
66
- "test": "node ./scripts/test.mjs && c8 mocha --enable-source-maps 'lib/tests/**/*.spec.js'",
67
- "typecheck": "tsc --noEmit"
55
+ "build": "tsx scripts/build.ts",
56
+ "format": "syncpack format && eslint --no-warn-ignored --fix && prettier --no-error-on-unmatched-pattern -uw .",
57
+ "lint": "ls-lint --config .ls-lint.yaml && knip --no-config-hints && eslint",
58
+ "ls-lint": "ls-lint --config .ls-lint.yaml",
59
+ "pack": "pnpm-pack package --pack-destination lib",
60
+ "syncpack": "syncpack fix && syncpack format",
61
+ "test": "vitest --no-watch --coverage",
62
+ "typecheck": "tsc --noEmit",
63
+ "update": "ncu --cooldown 7 -p pnpm -u --peer --dep dev,peer,prod,optional && pnpm run syncpack"
68
64
  }
69
65
  }
package/lib/esm/index.mjs DELETED
@@ -1,399 +0,0 @@
1
- // src/line.ts
2
- var Line = class {
3
- start;
4
- end;
5
- delta;
6
- constructor(start, end) {
7
- this.start = start, this.end = end, this.delta = end.minus(start);
8
- }
9
- /**
10
- * Length of a line is the length between its two defining points
11
- */
12
- get length() {
13
- return this.delta.norm;
14
- }
15
- evaluate(t) {
16
- return this.start.plus(this.delta.times(t));
17
- }
18
- distanceToPoint(p) {
19
- return Math.abs(p.cross(this.delta) - this.start.cross(this.end)) / this.delta.norm;
20
- }
21
- pointOnSide(p, err = 0) {
22
- let num = this.start.cross(this.end) - p.cross(this.delta);
23
- return num === 0 || Math.abs(num) / this.delta.norm < err ? 0 /* Top */ : num > 0 ? 1 /* Left */ : -1 /* Right */;
24
- }
25
- pointOnTop(p, err) {
26
- return this.pointOnSide(p, err) === 0 /* Top */;
27
- }
28
- overlaps(that, err) {
29
- return this.pointOnTop(that.start, err) && this.pointOnTop(that.end, err);
30
- }
31
- /**
32
- * If alpha is less than deviationFromZeroAngle, the 2 lines are
33
- * considered parallel.
34
- * _______________________________
35
- * alpha (/
36
- * /
37
- * /
38
- * /
39
- */
40
- parallel(that, deviationFromZeroAngle) {
41
- let d = Math.abs(this.delta.cross(that.delta));
42
- return d === 0 || d < this.length * this.length * Math.sin(deviationFromZeroAngle);
43
- }
44
- intersectionParameter(that, err) {
45
- let d = this.delta.cross(that.delta);
46
- if (d === 0 || Math.abs(d) < err)
47
- return null;
48
- let dStart = this.start.minus(that.start);
49
- return that.delta.cross(dStart) / d;
50
- }
51
- closestPointParam(p) {
52
- return this.delta.dot(p.minus(this.start)) / this.delta.normSquared;
53
- }
54
- closestPoint(p) {
55
- return this.evaluate(this.closestPointParam(p));
56
- }
57
- intersectionPoint(that, err) {
58
- let t = this.intersectionParameter(that, err);
59
- return t === null ? null : this.evaluate(t);
60
- }
61
- };
62
-
63
- // src/vec2.ts
64
- var Vec2 = class _Vec2 {
65
- _x;
66
- _y;
67
- _normSquared;
68
- _norm;
69
- _normalized;
70
- constructor(x, y) {
71
- this._x = x, this._y = y;
72
- }
73
- times(s) {
74
- return new _Vec2(this._x * s, this._y * s);
75
- }
76
- over(s) {
77
- return new _Vec2(this._x / s, this._y / s);
78
- }
79
- get x() {
80
- return this._x;
81
- }
82
- get y() {
83
- return this._y;
84
- }
85
- plus(that) {
86
- return new _Vec2(this._x + that._x, this._y + that._y);
87
- }
88
- minus(that) {
89
- return new _Vec2(this._x - that._x, this._y - that._y);
90
- }
91
- get normSquared() {
92
- return this._normSquared === void 0 ? this._normSquared = this.dot(this) : this._normSquared;
93
- }
94
- get norm() {
95
- return this._norm === void 0 ? this._norm = Math.sqrt(this.normSquared) : this._norm;
96
- }
97
- get normalized() {
98
- return this._normalized === void 0 ? this._normalized = this.over(this.norm) : this._normalized;
99
- }
100
- dot(that) {
101
- return this._x * that._x + this._y * that._y;
102
- }
103
- cross(that) {
104
- return this._x * that._y - this._y * that._x;
105
- }
106
- equals(that, err) {
107
- return err === 0 ? this.x === that.x && this.y === that.y : this.minus(that).normSquared < err * err;
108
- }
109
- normal() {
110
- return new _Vec2(this._y, -this._x);
111
- }
112
- toString() {
113
- return `(${this.x}, ${this.y})`;
114
- }
115
- };
116
-
117
- // src/inscribe.ts
118
- var Wedge = class _Wedge {
119
- leftArm;
120
- rightArm;
121
- isDegenerate;
122
- constructor(leftArm, rightArm, isDegenerate = !1) {
123
- this.leftArm = leftArm, this.rightArm = rightArm, this.isDegenerate = isDegenerate;
124
- }
125
- static new(leftArm, rightArm, err) {
126
- if (leftArm === null || rightArm === null || err !== 0 && leftArm.overlaps(rightArm, err))
127
- return null;
128
- let deviationFromZeroAngle = 0.1 / (leftArm.length * rightArm.length);
129
- if (leftArm.parallel(rightArm, deviationFromZeroAngle)) {
130
- let p = new Line(leftArm.evaluate(0.5), rightArm.evaluate(0.5)).evaluate(0.5), sideLeft = leftArm.pointOnSide(p, err), sideRight = rightArm.pointOnSide(p, err);
131
- if (sideLeft === 0 /* Top */ || sideRight === 0 /* Top */)
132
- throw new Error();
133
- return sideLeft !== sideRight ? new _Wedge(leftArm, rightArm, !0) : new _Wedge(leftArm, new Line(rightArm.end, rightArm.start));
134
- }
135
- let tLA = leftArm.intersectionParameter(rightArm, 0), tRA = rightArm.intersectionParameter(leftArm, 0);
136
- if (tLA === 0.5 || tRA === 0.5)
137
- return null;
138
- let W = leftArm.evaluate(tLA), eLA = tLA < 1 - tLA ? leftArm.end : leftArm.start, eRA = tRA < 1 - tRA ? rightArm.end : rightArm.start;
139
- return new _Wedge(new Line(W, eLA), new Line(W, eRA));
140
- }
141
- formTriangle(line, err) {
142
- if (this.leftArm.parallel(line, 0.1 / (this.leftArm.length * line.length)) || this.rightArm.parallel(line, 0.1 / (this.rightArm.length * line.length)))
143
- return !1;
144
- let A = line.intersectionPoint(this.leftArm, 0), B = line.intersectionPoint(this.rightArm, 0);
145
- if (this.isDegenerate)
146
- return !A.equals(B, err);
147
- let C = this.leftArm.intersectionPoint(this.rightArm, 0);
148
- return !C.equals(A, err) && !C.equals(B, err) && !A.equals(B, err) && !new Line(A, B).pointOnTop(C, err);
149
- }
150
- looselyContains(p, err) {
151
- let pLeft = this.leftArm.pointOnSide(p, err), pRight = this.rightArm.pointOnSide(p, err);
152
- return pLeft === 0 /* Top */ || pRight === 0 /* Top */ ? !0 : pLeft === pRight ? !1 : this.isDegenerate ? (
153
- // degenerate + different sides => true
154
- !0
155
- ) : (
156
- // 2. (Because the arms intersect)
157
- // Projection params of the point onto the arms must be larger than 0
158
- this.leftArm.closestPointParam(p) >= 0 && this.rightArm.closestPointParam(p) >= 0
159
- );
160
- }
161
- strictlyContains(p, err) {
162
- let pLeft = this.leftArm.pointOnSide(p, err), pRight = this.rightArm.pointOnSide(p, err);
163
- return pLeft === 0 /* Top */ || pRight === 0 /* Top */ || pLeft === pRight ? !1 : this.isDegenerate ? (
164
- // degenerate + different sides => true
165
- !0
166
- ) : (
167
- // 2. (Because the arms intersect)
168
- // Projection params of the point onto the arms must be larger than 0
169
- this.leftArm.closestPointParam(p) >= 0 && this.rightArm.closestPointParam(p) >= 0
170
- );
171
- }
172
- // While fitting circles into a wedge
173
- // There are four distinct cases:
174
- // 1. Wedge is degenerate and additional element is a point
175
- // 2. Wedge is degenerate and additional element is a line
176
- // 3. Wedge is non-degenerate and additional element is a point
177
- // 4. Wedge is non-degenerate and additional element is a line
178
- // according to these assumptions, the following methods are named
179
- fit_Dp(p, err) {
180
- if (!this.strictlyContains(p, err))
181
- return null;
182
- let A = this.rightArm.closestPoint(p), Ap = this.leftArm.closestPoint(A), I = A.plus(Ap).over(2), r = A.minus(Ap).norm / 2, a = this.rightArm.delta.normSquared, b = I.minus(p).dot(this.rightArm.delta) * 2, c = I.minus(p).normSquared - r * r, discriminant = b * b - 4 * a * c;
183
- if (discriminant < (-10) ** -5)
184
- return null;
185
- let t = [];
186
- Math.abs(discriminant) < 10 ** -5 ? t.push(-b / (2 * a)) : (t.push((-b + Math.sqrt(discriminant)) / (2 * a)), t.push((-b - Math.sqrt(discriminant)) / (2 * a)));
187
- let result = [];
188
- return t.forEach((t0) => {
189
- let O = this.rightArm.delta.times(t0).plus(I);
190
- result.push({
191
- circle: { centre: O, r },
192
- tangent: new Line(p, p.plus(O.minus(p).normal()))
193
- });
194
- }), result;
195
- }
196
- fit_Dl(l, err) {
197
- if (!this.formTriangle(l, err))
198
- return null;
199
- let A = l.intersectionPoint(this.rightArm, 0), B = l.intersectionPoint(this.leftArm, 0), AB = new Line(A, B), Ap = this.leftArm.closestPoint(A), I = A.plus(Ap).over(2), r = A.minus(Ap).norm / 2, t1 = (AB.delta.cross(A.minus(I)) + r * AB.delta.norm) / AB.delta.cross(this.rightArm.delta), t2 = (AB.delta.cross(A.minus(I)) - r * AB.delta.norm) / AB.delta.cross(this.rightArm.delta), o1 = this.rightArm.delta.times(t1).plus(I), o2 = this.rightArm.delta.times(t2).plus(I);
200
- return [
201
- {
202
- circle: { centre: o1, r },
203
- tangentParameter: l.closestPointParam(o1)
204
- },
205
- {
206
- circle: { centre: o2, r },
207
- tangentParameter: l.closestPointParam(o2)
208
- }
209
- ];
210
- }
211
- fit_NDp(p, err) {
212
- if (!this.strictlyContains(p, err))
213
- return null;
214
- let C = this.leftArm.start, A = this.leftArm.end, B = this.rightArm.end, a = C.minus(B).norm, b = C.minus(A).norm, D = A.minus(B).times(a / (a + b)).plus(B), bisector = new Line(C, D), eA = D.minus(C).normSquared - (A.minus(C).cross(D.minus(C)) / b) ** 2, eB = D.minus(C).dot(C.minus(p)) * 2, eC = C.minus(p).normSquared, discriminant = eB * eB - 4 * eA * eC;
215
- if (discriminant < (-10) ** -5)
216
- return null;
217
- let O, r;
218
- if (Math.abs(discriminant) < 10 ** -5) {
219
- let t = -eB / (2 * eA);
220
- O = bisector.evaluate(t), r = O.minus(p).norm;
221
- } else {
222
- let t1 = (-eB + Math.sqrt(discriminant)) / (2 * eA), t2 = (-eB - Math.sqrt(discriminant)) / (2 * eA);
223
- bisector.evaluate(t1).minus(p).normSquared > bisector.evaluate(t2).minus(p).normSquared ? (O = bisector.evaluate(t1), r = bisector.evaluate(t1).minus(p).norm) : (O = bisector.evaluate(t2), r = bisector.evaluate(t2).minus(p).norm);
224
- }
225
- return [
226
- {
227
- circle: { centre: O, r },
228
- tangent: new Line(p, p.plus(O.minus(p).normal()))
229
- }
230
- ];
231
- }
232
- fit_NDl(l, err) {
233
- if (!this.formTriangle(l, err))
234
- return null;
235
- let C = this.leftArm.start, A = l.intersectionPoint(this.leftArm, 0), B = l.intersectionPoint(this.rightArm, 0), AC = new Line(A, C), BC = new Line(B, C), AB = new Line(A, B), a = AC.length, b = BC.length, c = AB.length, s = (a + b + c) / 2;
236
- if (s * (s - a) * (s - b) / (s - c) < 0)
237
- return null;
238
- let r = Math.sqrt(s * (s - a) * (s - b) / (s - c)), det = AB.delta.cross(AC.delta), lhsAll = [
239
- new Vec2(B.cross(A) + r * c, C.cross(A) + r * a),
240
- new Vec2(B.cross(A) + r * c, C.cross(A) - r * a),
241
- new Vec2(B.cross(A) - r * c, C.cross(A) + r * a),
242
- new Vec2(B.cross(A) - r * c, C.cross(A) - r * a)
243
- ], OAll = [];
244
- lhsAll.forEach((lhs) => {
245
- OAll.push(
246
- new Vec2(
247
- new Vec2(AB.delta.x, AC.delta.x).cross(lhs),
248
- new Vec2(AB.delta.y, AC.delta.y).cross(lhs)
249
- ).over(-det)
250
- );
251
- });
252
- let o = null, dists = [];
253
- for (let O of OAll) {
254
- dists.push({
255
- raw: Math.abs(BC.distanceToPoint(O) - r),
256
- norm: Math.abs(BC.distanceToPoint(O) / r - 1)
257
- });
258
- let absoluteError = Math.abs(BC.distanceToPoint(O) - r), relativeError = Math.abs(BC.distanceToPoint(O) / r - 1);
259
- if ((absoluteError < 10 ** -5 || relativeError < 10 ** -5) && AC.pointOnSide(O) !== BC.pointOnSide(O)) {
260
- o = O;
261
- break;
262
- }
263
- }
264
- let msg = "";
265
- if (o === null) {
266
- msg = "fit_NDl, centre is undefined";
267
- for (let i = 0; i < OAll.length; i++)
268
- msg += `centre: (${OAll[i].x}, ${OAll[i].y}), r: ${r}, dist raw: ${dists[i].raw}, dist norm: ${dists[i].norm}
269
- `;
270
- }
271
- if (o === null)
272
- throw new Error(msg);
273
- return [
274
- {
275
- circle: { centre: o, r },
276
- tangentParameter: l.closestPointParam(o)
277
- }
278
- ];
279
- }
280
- fitCircles(element, err) {
281
- return element instanceof Vec2 ? this.isDegenerate ? this.fit_Dp(element, err) : this.fit_NDp(element, err) : element instanceof Line ? this.isDegenerate ? this.fit_Dl((element instanceof Line, element), err) : this.fit_NDl((element instanceof Line, element), err) : null;
282
- }
283
- toString() {
284
- return `LA: ${this.leftArm.start.toString()} --> ${this.leftArm.end.toString()}
285
- RA: ${this.rightArm.start.toString()} --> ${this.rightArm.end.toString()}`;
286
- }
287
- };
288
-
289
- // src/index.ts
290
- function lineTangentToHull(line, points, halo) {
291
- let holds = !0, side = 0 /* Top */, k = 0;
292
- for (; side === 0 /* Top */ && k < points.length; )
293
- side = line.pointOnSide(points[k], halo), k++;
294
- for (let i = k; i < points.length; i++) {
295
- let testSide = line.pointOnSide(points[i], halo);
296
- if (testSide !== 0 /* Top */ && testSide !== side) {
297
- holds = !1;
298
- break;
299
- }
300
- }
301
- return { holds, side };
302
- }
303
- function findEnclosingSide(wedge, startVertex, endVertex, points, halo) {
304
- let side = null, stopVertex = startVertex, vertex = startVertex;
305
- for (; side === null && vertex > endVertex; ) {
306
- let p1 = points[vertex], p2 = points[vertex - 1], edge = new Line(p1, p2), circlesEdge = wedge.fitCircles(edge, halo);
307
- if (circlesEdge !== null) {
308
- let tangentParameter = 100;
309
- if (wedge.isDegenerate) {
310
- let sidedness = 0 /* Top */, k = 0;
311
- for (; sidedness === 0 /* Top */ && k < points.length; )
312
- sidedness = edge.pointOnSide(points[k], halo), k++;
313
- tangentParameter = edge.pointOnSide(circlesEdge[0].circle.centre) !== sidedness ? circlesEdge[0].tangentParameter : circlesEdge[1].tangentParameter;
314
- } else
315
- tangentParameter = circlesEdge[0].tangentParameter;
316
- if (tangentParameter > 0 && tangentParameter < 1) {
317
- let Y = edge.evaluate(tangentParameter), joint = wedge.leftArm.intersectionPoint(edge, halo);
318
- side = new Line(joint, Y);
319
- }
320
- }
321
- if (side === null) {
322
- let circlesPoint = wedge.fitCircles(p2, halo);
323
- if (circlesPoint !== null) {
324
- let tangent;
325
- if (wedge.isDegenerate) {
326
- let sidedness = 0 /* Top */, k = 0;
327
- for (; sidedness === 0 /* Top */ && k < points.length; )
328
- sidedness = circlesPoint[0].tangent.pointOnSide(points[k], halo), k++;
329
- tangent = circlesPoint[0].tangent.pointOnSide(
330
- circlesPoint[0].circle.centre,
331
- halo
332
- ) !== sidedness ? circlesPoint[0].tangent : circlesPoint[1].tangent;
333
- } else
334
- tangent = circlesPoint[0].tangent;
335
- if (lineTangentToHull(tangent, points, halo).holds) {
336
- let joint = wedge.leftArm.intersectionPoint(tangent, halo);
337
- side = new Line(joint, p2);
338
- }
339
- }
340
- }
341
- stopVertex = vertex, vertex--;
342
- }
343
- return side === null ? null : { side, stopVertex };
344
- }
345
- function findAntipode(points) {
346
- let farthestIndex = 0, farthestDist = 0;
347
- for (let i = 0, n = points.length; i < n; i++) {
348
- let testDist = new Line(points[0], points[n - 1]).distanceToPoint(
349
- points[i]
350
- );
351
- testDist > farthestDist && (farthestDist = testDist, farthestIndex = i);
352
- }
353
- return farthestIndex;
354
- }
355
- function minTriangleWithBase(convexHull, err, tol) {
356
- let AB, AC, n = convexHull.length, BC = new Line(convexHull[0], convexHull[n - 1]), antipodIndex = findAntipode(convexHull), baseParallel = new Line(
357
- convexHull[antipodIndex],
358
- convexHull[antipodIndex].plus(BC.delta)
359
- ), wedge = Wedge.new(BC, baseParallel, err), Pn = n - 1, Qn = antipodIndex;
360
- do {
361
- let CQinfo = findEnclosingSide(wedge, Pn, antipodIndex, convexHull, err);
362
- if (CQinfo === null)
363
- return null;
364
- ({ side: AC, stopVertex: Pn } = CQinfo), wedge = Wedge.new(wedge.leftArm, AC, err);
365
- let BPinfo = findEnclosingSide(wedge, Qn, 0, convexHull, err);
366
- if (BPinfo === null)
367
- return null;
368
- ({ side: AB, stopVertex: Qn } = BPinfo), wedge = Wedge.new(wedge.leftArm, AB, err);
369
- } while (AB.length - AC.length > tol);
370
- let A = AC.intersectionPoint(AB, 0), B = AB.start, C = AC.start;
371
- return { A, B, C };
372
- }
373
- function minTriangle(convexHull, err, tol) {
374
- if (convexHull.length < 3)
375
- return null;
376
- if (convexHull.length === 3)
377
- return { A: convexHull[0], B: convexHull[1], C: convexHull[2] };
378
- let points = convexHull.map((p) => new Vec2(p.x, p.y)), A = null, B = null, C = null, perimeter = -1, rotations = 0;
379
- for (; rotations < points.length; ) {
380
- rotations > 0 && points.push(points.shift());
381
- let triangle = minTriangleWithBase(points, err, tol);
382
- if (triangle !== null) {
383
- let { A: A1, B: B1, C: C1 } = triangle, perimeter1 = A1.minus(B1).norm + B1.minus(C1).norm + C1.minus(A1).norm;
384
- (perimeter1 < perimeter || perimeter === -1) && ([A, B, C] = [A1, B1, C1], perimeter = perimeter1);
385
- }
386
- rotations++;
387
- }
388
- return perimeter === -1 ? null : {
389
- A: { x: A.x, y: A.y },
390
- B: { x: B.x, y: B.y },
391
- C: { x: C.x, y: C.y }
392
- };
393
- }
394
- export {
395
- lineTangentToHull,
396
- minTriangle,
397
- minTriangleWithBase
398
- };
399
- //# sourceMappingURL=index.mjs.map