@ifc-lite/clash 1.2.0 → 1.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.
Files changed (56) hide show
  1. package/dist/contact/aabb.d.ts +20 -0
  2. package/dist/contact/aabb.d.ts.map +1 -0
  3. package/dist/contact/aabb.js +88 -0
  4. package/dist/contact/aabb.js.map +1 -0
  5. package/dist/contact/bvh.d.ts +39 -0
  6. package/dist/contact/bvh.d.ts.map +1 -0
  7. package/dist/contact/bvh.js +173 -0
  8. package/dist/contact/bvh.js.map +1 -0
  9. package/dist/contact/index.d.ts +31 -0
  10. package/dist/contact/index.d.ts.map +1 -0
  11. package/dist/contact/index.js +33 -0
  12. package/dist/contact/index.js.map +1 -0
  13. package/dist/contact/mesh-bvh.d.ts +21 -0
  14. package/dist/contact/mesh-bvh.d.ts.map +1 -0
  15. package/dist/contact/mesh-bvh.js +96 -0
  16. package/dist/contact/mesh-bvh.js.map +1 -0
  17. package/dist/contact/narrow-phase.d.ts +37 -0
  18. package/dist/contact/narrow-phase.d.ts.map +1 -0
  19. package/dist/contact/narrow-phase.js +49 -0
  20. package/dist/contact/narrow-phase.js.map +1 -0
  21. package/dist/contact/plane.d.ts +38 -0
  22. package/dist/contact/plane.d.ts.map +1 -0
  23. package/dist/contact/plane.js +83 -0
  24. package/dist/contact/plane.js.map +1 -0
  25. package/dist/contact/polygon-clip.d.ts +27 -0
  26. package/dist/contact/polygon-clip.d.ts.map +1 -0
  27. package/dist/contact/polygon-clip.js +162 -0
  28. package/dist/contact/polygon-clip.js.map +1 -0
  29. package/dist/contact/shared-faces.d.ts +61 -0
  30. package/dist/contact/shared-faces.d.ts.map +1 -0
  31. package/dist/contact/shared-faces.js +251 -0
  32. package/dist/contact/shared-faces.js.map +1 -0
  33. package/dist/contact/tri-tri.d.ts +34 -0
  34. package/dist/contact/tri-tri.d.ts.map +1 -0
  35. package/dist/contact/tri-tri.js +195 -0
  36. package/dist/contact/tri-tri.js.map +1 -0
  37. package/dist/contact/triangle.d.ts +22 -0
  38. package/dist/contact/triangle.d.ts.map +1 -0
  39. package/dist/contact/triangle.js +60 -0
  40. package/dist/contact/triangle.js.map +1 -0
  41. package/dist/contact/types.d.ts +33 -0
  42. package/dist/contact/types.d.ts.map +1 -0
  43. package/dist/contact/types.js +5 -0
  44. package/dist/contact/types.js.map +1 -0
  45. package/dist/contact/vec3.d.ts +19 -0
  46. package/dist/contact/vec3.d.ts.map +1 -0
  47. package/dist/contact/vec3.js +50 -0
  48. package/dist/contact/vec3.js.map +1 -0
  49. package/dist/engine-ts/narrow.d.ts.map +1 -1
  50. package/dist/engine-ts/narrow.js +102 -6
  51. package/dist/engine-ts/narrow.js.map +1 -1
  52. package/dist/engine-ts/tri-mesh.d.ts +10 -0
  53. package/dist/engine-ts/tri-mesh.d.ts.map +1 -1
  54. package/dist/engine-ts/tri-mesh.js +24 -0
  55. package/dist/engine-ts/tri-mesh.js.map +1 -1
  56. package/package.json +9 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narrow-phase.js","sourceRoot":"","sources":["../../src/contact/narrow-phase.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA+B1D;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,CAAO,EAAE,CAAO,EAAE,OAA2B,EAAE;IACzE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IAEpC,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEhE,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAEvD,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QAClC,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,EAAE;gBACR,IAAI,EAAE,EAAE;gBACR,CAAC,EAAE,IAAI;gBACP,CAAC,EAAE,IAAI;gBACP,IAAI,EAAE,OAAO;gBACb,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,GAAG,CAAC,EAAE;aACX,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Plane utilities. A plane is stored as a unit normal + signed offset:
3
+ * normal · x = offset
4
+ * `canonicalPlane` produces a direction-independent key so that the pair
5
+ * (normal, -normal, offset, -offset) hashes identically.
6
+ */
7
+ import type { Vec3 } from "./types.js";
8
+ export interface Plane {
9
+ readonly normal: Vec3;
10
+ readonly offset: number;
11
+ }
12
+ export interface PlaneBasis {
13
+ readonly origin: Vec3;
14
+ readonly u: Vec3;
15
+ readonly v: Vec3;
16
+ readonly normal: Vec3;
17
+ }
18
+ /** Build a plane from a normal (not required to be unit) and a point on it. */
19
+ export declare function planeFromNormalPoint(normal: Vec3, point: Vec3): Plane;
20
+ /**
21
+ * Direction-independent quantised plane key.
22
+ *
23
+ * The normal is reflected so its first nonzero component is positive,
24
+ * then rounded to `angleSnap` radians in spherical form. The offset is
25
+ * signed accordingly and rounded to `distSnap` world units.
26
+ */
27
+ export declare function planeKey(plane: Plane, angleSnap?: number, distSnap?: number): string;
28
+ /**
29
+ * Build an orthonormal basis `{u, v}` in the plane so 3D plane points can
30
+ * be projected to 2D and back. `origin` is chosen as the foot of the normal
31
+ * from the world origin.
32
+ */
33
+ export declare function planeBasis(plane: Plane): PlaneBasis;
34
+ /** Project a 3D point onto the plane's 2D basis. */
35
+ export declare function projectTo2D(basis: PlaneBasis, p: Vec3): readonly [number, number];
36
+ /** Lift a 2D basis point back to 3D. */
37
+ export declare function liftTo3D(basis: PlaneBasis, p: readonly [number, number]): Vec3;
38
+ //# sourceMappingURL=plane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plane.d.ts","sourceRoot":"","sources":["../../src/contact/plane.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;CACvB;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,KAAK,CAKrE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,SAAO,EAAE,QAAQ,SAAO,GAAG,MAAM,CAmBhF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,CAenD;AAED,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAQjF;AAED,wCAAwC;AACxC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAM9E"}
@@ -0,0 +1,83 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { dot3, length3 } from "./vec3.js";
5
+ /** Build a plane from a normal (not required to be unit) and a point on it. */
6
+ export function planeFromNormalPoint(normal, point) {
7
+ const ln = length3(normal);
8
+ if (ln === 0)
9
+ return { normal: [0, 0, 1], offset: 0 };
10
+ const n = [normal[0] / ln, normal[1] / ln, normal[2] / ln];
11
+ return { normal: n, offset: dot3(n, point) };
12
+ }
13
+ /**
14
+ * Direction-independent quantised plane key.
15
+ *
16
+ * The normal is reflected so its first nonzero component is positive,
17
+ * then rounded to `angleSnap` radians in spherical form. The offset is
18
+ * signed accordingly and rounded to `distSnap` world units.
19
+ */
20
+ export function planeKey(plane, angleSnap = 1e-3, distSnap = 1e-3) {
21
+ const n = plane.normal;
22
+ const ln = length3(n);
23
+ if (ln === 0)
24
+ return "0|0|0|0";
25
+ // Canonicalise direction: first nonzero coordinate must be positive.
26
+ let sign = 1;
27
+ if (Math.abs(n[0]) > 1e-9)
28
+ sign = n[0] > 0 ? 1 : -1;
29
+ else if (Math.abs(n[1]) > 1e-9)
30
+ sign = n[1] > 0 ? 1 : -1;
31
+ else
32
+ sign = n[2] > 0 ? 1 : -1;
33
+ const cx = (n[0] / ln) * sign;
34
+ const cy = (n[1] / ln) * sign;
35
+ const cz = (n[2] / ln) * sign;
36
+ const off = plane.offset * sign;
37
+ return [
38
+ Math.round(cx / angleSnap),
39
+ Math.round(cy / angleSnap),
40
+ Math.round(cz / angleSnap),
41
+ Math.round(off / distSnap),
42
+ ].join("|");
43
+ }
44
+ /**
45
+ * Build an orthonormal basis `{u, v}` in the plane so 3D plane points can
46
+ * be projected to 2D and back. `origin` is chosen as the foot of the normal
47
+ * from the world origin.
48
+ */
49
+ export function planeBasis(plane) {
50
+ const n = plane.normal;
51
+ // origin = offset * n (foot of normal from world origin if |n|=1)
52
+ const origin = [n[0] * plane.offset, n[1] * plane.offset, n[2] * plane.offset];
53
+ // pick a helper not parallel to n
54
+ const helper = Math.abs(n[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
55
+ const u0 = [
56
+ helper[1] * n[2] - helper[2] * n[1],
57
+ helper[2] * n[0] - helper[0] * n[2],
58
+ helper[0] * n[1] - helper[1] * n[0],
59
+ ];
60
+ const uLen = length3(u0);
61
+ const u = uLen === 0 ? [1, 0, 0] : [u0[0] / uLen, u0[1] / uLen, u0[2] / uLen];
62
+ const v = [n[1] * u[2] - n[2] * u[1], n[2] * u[0] - n[0] * u[2], n[0] * u[1] - n[1] * u[0]];
63
+ return { origin, u, v, normal: n };
64
+ }
65
+ /** Project a 3D point onto the plane's 2D basis. */
66
+ export function projectTo2D(basis, p) {
67
+ const dx = p[0] - basis.origin[0];
68
+ const dy = p[1] - basis.origin[1];
69
+ const dz = p[2] - basis.origin[2];
70
+ return [
71
+ dx * basis.u[0] + dy * basis.u[1] + dz * basis.u[2],
72
+ dx * basis.v[0] + dy * basis.v[1] + dz * basis.v[2],
73
+ ];
74
+ }
75
+ /** Lift a 2D basis point back to 3D. */
76
+ export function liftTo3D(basis, p) {
77
+ return [
78
+ basis.origin[0] + basis.u[0] * p[0] + basis.v[0] * p[1],
79
+ basis.origin[1] + basis.u[1] * p[0] + basis.v[1] * p[1],
80
+ basis.origin[2] + basis.u[2] * p[0] + basis.v[2] * p[1],
81
+ ];
82
+ }
83
+ //# sourceMappingURL=plane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plane.js","sourceRoot":"","sources":["../../src/contact/plane.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 2D polygon utilities: Sutherland–Hodgman clip of a subject polygon by
3
+ * a convex clip polygon, shoelace area, perimeter, centroid.
4
+ *
5
+ * Both polygons are arrays of `[x, y]` pairs. CCW winding gives positive
6
+ * area (standard convention).
7
+ */
8
+ export type Point2 = readonly [number, number];
9
+ export type Polygon2 = readonly Point2[];
10
+ /** Shoelace-formula signed area (positive when CCW). */
11
+ export declare function polygonSignedArea(poly: Polygon2): number;
12
+ export declare const polygonArea: (poly: Polygon2) => number;
13
+ export declare function polygonPerimeter(poly: Polygon2): number;
14
+ /** Area-weighted centroid. Falls back to vertex mean for degenerate input. */
15
+ export declare function polygonCentroid(poly: Polygon2): Point2;
16
+ /**
17
+ * Sutherland–Hodgman polygon clip. `subject` may be any simple polygon;
18
+ * `clip` must be **convex** (CCW). Returns the intersection polygon, which
19
+ * can be empty (no overlap) or degenerate (collinear/point contact).
20
+ */
21
+ export declare function sutherlandHodgman(subject: Polygon2, clip: Polygon2): Polygon2;
22
+ /**
23
+ * Convex polygon from (unordered) points: sort by angle around centroid
24
+ * and return CCW. For use as a Sutherland-Hodgman clip.
25
+ */
26
+ export declare function convexHull2(points: Polygon2): Polygon2;
27
+ //# sourceMappingURL=polygon-clip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polygon-clip.d.ts","sourceRoot":"","sources":["../../src/contact/polygon-clip.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH,MAAM,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC/C,MAAM,MAAM,QAAQ,GAAG,SAAS,MAAM,EAAE,CAAC;AAEzC,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAUxD;AAED,eAAO,MAAM,WAAW,GAAI,MAAM,QAAQ,KAAG,MAA2C,CAAC;AAEzF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAYvD;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CA2BtD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CA8B7E;AAsBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CA4BtD"}
@@ -0,0 +1,162 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /** Shoelace-formula signed area (positive when CCW). */
5
+ export function polygonSignedArea(poly) {
6
+ const n = poly.length;
7
+ if (n < 3)
8
+ return 0;
9
+ let sum = 0;
10
+ for (let i = 0; i < n; i++) {
11
+ const a = poly[i];
12
+ const b = poly[(i + 1) % n];
13
+ sum += a[0] * b[1] - b[0] * a[1];
14
+ }
15
+ return sum / 2;
16
+ }
17
+ export const polygonArea = (poly) => Math.abs(polygonSignedArea(poly));
18
+ export function polygonPerimeter(poly) {
19
+ const n = poly.length;
20
+ if (n < 2)
21
+ return 0;
22
+ let sum = 0;
23
+ for (let i = 0; i < n; i++) {
24
+ const a = poly[i];
25
+ const b = poly[(i + 1) % n];
26
+ const dx = b[0] - a[0];
27
+ const dy = b[1] - a[1];
28
+ sum += Math.sqrt(dx * dx + dy * dy);
29
+ }
30
+ return sum;
31
+ }
32
+ /** Area-weighted centroid. Falls back to vertex mean for degenerate input. */
33
+ export function polygonCentroid(poly) {
34
+ const n = poly.length;
35
+ if (n === 0)
36
+ return [0, 0];
37
+ if (n === 1) {
38
+ const p0 = poly[0];
39
+ return [p0[0], p0[1]];
40
+ }
41
+ const a2 = polygonSignedArea(poly) * 2;
42
+ if (Math.abs(a2) < 1e-18) {
43
+ let mx = 0;
44
+ let my = 0;
45
+ for (const p of poly) {
46
+ mx += p[0];
47
+ my += p[1];
48
+ }
49
+ return [mx / n, my / n];
50
+ }
51
+ let cx = 0;
52
+ let cy = 0;
53
+ for (let i = 0; i < n; i++) {
54
+ const a = poly[i];
55
+ const b = poly[(i + 1) % n];
56
+ const cross = a[0] * b[1] - b[0] * a[1];
57
+ cx += (a[0] + b[0]) * cross;
58
+ cy += (a[1] + b[1]) * cross;
59
+ }
60
+ return [cx / (3 * a2), cy / (3 * a2)];
61
+ }
62
+ /**
63
+ * Sutherland–Hodgman polygon clip. `subject` may be any simple polygon;
64
+ * `clip` must be **convex** (CCW). Returns the intersection polygon, which
65
+ * can be empty (no overlap) or degenerate (collinear/point contact).
66
+ */
67
+ export function sutherlandHodgman(subject, clip) {
68
+ if (subject.length === 0 || clip.length < 3)
69
+ return [];
70
+ // Enforce CCW clip — if the clip is CW we flip it.
71
+ const clipCCW = polygonSignedArea(clip) >= 0 ? clip : [...clip].reverse();
72
+ let output = subject.map((p) => [p[0], p[1]]);
73
+ for (let i = 0; i < clipCCW.length; i++) {
74
+ if (output.length === 0)
75
+ break;
76
+ const a = clipCCW[i];
77
+ const b = clipCCW[(i + 1) % clipCCW.length];
78
+ const input = output;
79
+ output = [];
80
+ for (let j = 0; j < input.length; j++) {
81
+ const current = input[j];
82
+ const prev = input[(j - 1 + input.length) % input.length];
83
+ const curIn = isLeftOrOn(a, b, current);
84
+ const prevIn = isLeftOrOn(a, b, prev);
85
+ if (curIn) {
86
+ if (!prevIn) {
87
+ const ip = segmentIntersection(prev, current, a, b);
88
+ if (ip)
89
+ output.push(ip);
90
+ }
91
+ output.push(current);
92
+ }
93
+ else if (prevIn) {
94
+ const ip = segmentIntersection(prev, current, a, b);
95
+ if (ip)
96
+ output.push(ip);
97
+ }
98
+ }
99
+ }
100
+ return output;
101
+ }
102
+ /** True when point `p` is to the left of, or on, directed edge `a → b`. */
103
+ function isLeftOrOn(a, b, p) {
104
+ const cross = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
105
+ return cross >= 0;
106
+ }
107
+ /** Intersection of segment `p1→p2` with infinite line through `a→b`. */
108
+ function segmentIntersection(p1, p2, a, b) {
109
+ const r0 = p2[0] - p1[0];
110
+ const r1 = p2[1] - p1[1];
111
+ const s0 = b[0] - a[0];
112
+ const s1 = b[1] - a[1];
113
+ const denom = r0 * s1 - r1 * s0;
114
+ if (Math.abs(denom) < 1e-15)
115
+ return null; // parallel
116
+ const qp0 = a[0] - p1[0];
117
+ const qp1 = a[1] - p1[1];
118
+ const t = (qp0 * s1 - qp1 * s0) / denom;
119
+ return [p1[0] + t * r0, p1[1] + t * r1];
120
+ }
121
+ /**
122
+ * Convex polygon from (unordered) points: sort by angle around centroid
123
+ * and return CCW. For use as a Sutherland-Hodgman clip.
124
+ */
125
+ export function convexHull2(points) {
126
+ if (points.length < 3)
127
+ return points.map((p) => [p[0], p[1]]);
128
+ // Andrew's monotone chain
129
+ const pts = [...points].sort((a, b) => (a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]));
130
+ const lower = [];
131
+ for (const p of pts) {
132
+ while (lower.length >= 2) {
133
+ const a = lower[lower.length - 2];
134
+ const b = lower[lower.length - 1];
135
+ if (cross2(a, b, p) <= 0)
136
+ lower.pop();
137
+ else
138
+ break;
139
+ }
140
+ lower.push(p);
141
+ }
142
+ const upper = [];
143
+ for (let i = pts.length - 1; i >= 0; i--) {
144
+ const p = pts[i];
145
+ while (upper.length >= 2) {
146
+ const a = upper[upper.length - 2];
147
+ const b = upper[upper.length - 1];
148
+ if (cross2(a, b, p) <= 0)
149
+ upper.pop();
150
+ else
151
+ break;
152
+ }
153
+ upper.push(p);
154
+ }
155
+ lower.pop();
156
+ upper.pop();
157
+ return [...lower, ...upper];
158
+ }
159
+ function cross2(o, a, b) {
160
+ return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
161
+ }
162
+ //# sourceMappingURL=polygon-clip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polygon-clip.js","sourceRoot":"","sources":["../../src/contact/polygon-clip.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAa/D,wDAAwD;AACxD,MAAM,UAAU,iBAAiB,CAAC,IAAc;IAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IACtB,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpB,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;QACtC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAc,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;AAEzF,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IACtB,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpB,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;QACtC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,IAAc;IAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IACtB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC7B,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC;QACzB,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACX,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACb,CAAC;QACD,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;QACtC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QAC5B,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IAC9B,CAAC;IACD,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAiB,EAAE,IAAc;IACjE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACvD,mDAAmD;IACnD,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;IAC1E,IAAI,MAAM,GAAa,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IAElE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM;QAC/B,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAW,CAAC;QAC/B,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAW,CAAC;QACtD,MAAM,KAAK,GAAG,MAAM,CAAC;QACrB,MAAM,GAAG,EAAE,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAW,CAAC;YACnC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAW,CAAC;YACpE,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YACtC,IAAI,KAAK,EAAE,CAAC;gBACV,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,MAAM,EAAE,GAAG,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;oBACpD,IAAI,EAAE;wBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC1B,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;iBAAM,IAAI,MAAM,EAAE,CAAC;gBAClB,MAAM,EAAE,GAAG,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBACpD,IAAI,EAAE;oBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2EAA2E;AAC3E,SAAS,UAAU,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACjD,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,OAAO,KAAK,IAAI,CAAC,CAAC;AACpB,CAAC;AAED,wEAAwE;AACxE,SAAS,mBAAmB,CAAC,EAAU,EAAE,EAAU,EAAE,CAAS,EAAE,CAAS;IACvE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAChC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK;QAAE,OAAO,IAAI,CAAC,CAAC,WAAW;IACrD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAgB;IAC1C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IACxE,0BAA0B;IAC1B,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC;YAC5C,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC;YAC5C,IAAI,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,GAAG,EAAE,CAAC;;gBACjC,MAAM;QACb,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC;IAC1B,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAW,CAAC;QAC3B,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC;YAC5C,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC;YAC5C,IAAI,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,GAAG,EAAE,CAAC;;gBACjC,MAAM;QACb,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;IACD,KAAK,CAAC,GAAG,EAAE,CAAC;IACZ,KAAK,CAAC,GAAG,EAAE,CAAC;IACZ,OAAO,CAAC,GAAG,KAAK,EAAE,GAAG,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,MAAM,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IAC7C,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared-face clustering.
3
+ *
4
+ * Inputs: TrianglePair[] emitted by narrowPhase() for a single pair of
5
+ * elements. Output: one or more SharedFaceCluster objects, each representing
6
+ * a distinct contact between the two elements, classified as
7
+ * surface / line / point.
8
+ *
9
+ * Coplanar pairs cluster by plane key. For each cluster we Sutherland-Hodgman
10
+ * every (A,B) triangle pair on the shared plane, sum polygon areas, build a
11
+ * 3D boundary polygon as the convex hull of the union of clip vertices.
12
+ *
13
+ * Cross pairs cluster by intersection-line key (direction + point on line).
14
+ * Length is the union length of the per-pair 3D segments projected onto
15
+ * the line direction.
16
+ *
17
+ * Remaining incidental contacts fall into either classification by
18
+ * magnitude against the thresholds.
19
+ */
20
+ import type { TrianglePair } from "./narrow-phase.js";
21
+ import type { Plane } from "./plane.js";
22
+ import type { Vec3 } from "./types.js";
23
+ export interface SharedFaceCluster {
24
+ readonly kind: "surface" | "line" | "point";
25
+ /** Anchoring plane (for surface) or line (for line/point, where direction is the line dir). */
26
+ readonly plane?: Plane;
27
+ /** World-space centroid of the contact. */
28
+ readonly centroid: Vec3;
29
+ /** Outward normal (surface) or line direction (line/point). */
30
+ readonly normal: Vec3;
31
+ /** Shared-face area in m² (surface only — 0 otherwise). */
32
+ readonly area_m2: number;
33
+ /** Shared-line length in m (line only — 0 otherwise). */
34
+ readonly length_m: number;
35
+ /** Narrowest dimension of the contact patch in m. Distinguishes a broad
36
+ * face (layered wall) from a thin bearing strip / cap. Length for a line
37
+ * cluster is carried by `length_m`, so its `spanMin` is 0. */
38
+ readonly spanMin: number;
39
+ /** Widest dimension (diameter) of the contact patch in m. */
40
+ readonly spanMax: number;
41
+ /** Boundary polygon (3D) for surface contacts. Segment endpoints for line contacts. Empty for point. */
42
+ readonly boundary: readonly Vec3[];
43
+ /** Contributing triangle pairs. */
44
+ readonly pairs: readonly TrianglePair[];
45
+ }
46
+ export interface SharedFaceOptions {
47
+ /** Absolute tolerance for plane coincidence (unit: model-native length). Default 1e-3. */
48
+ readonly planeDistSnap?: number;
49
+ /** Angular snap for plane normal hashing (unitless, ~1 − cos). Default 1e-3. */
50
+ readonly planeAngleSnap?: number;
51
+ /** Area (m²) threshold: ≥ surface. Default 0.01. */
52
+ readonly surfaceAreaM2?: number;
53
+ /** Area (m²) below which a contact may still be line or point. Default 0.001. */
54
+ readonly pointAreaM2?: number;
55
+ /** Length (m) threshold: ≥ line. Default 0.05. */
56
+ readonly lineLengthM?: number;
57
+ /** Snap used when hashing intersection-line direction + base point. Default 1e-3. */
58
+ readonly lineSnap?: number;
59
+ }
60
+ export declare function clusterSharedFaces(pairs: readonly TrianglePair[], opts?: SharedFaceOptions): SharedFaceCluster[];
61
+ //# sourceMappingURL=shared-faces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-faces.d.ts","sourceRoot":"","sources":["../../src/contact/shared-faces.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKxC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,+FAA+F;IAC/F,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;mEAE+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wGAAwG;IACxG,QAAQ,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAC;IACnC,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,oDAAoD;IACpD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,iFAAiF;IACjF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,IAAI,GAAE,iBAAsB,GAC3B,iBAAiB,EAAE,CAiDrB"}
@@ -0,0 +1,251 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { liftTo3D, planeBasis, planeFromNormalPoint, planeKey, projectTo2D } from "./plane.js";
5
+ import { convexHull2, polygonArea, polygonCentroid, sutherlandHodgman } from "./polygon-clip.js";
6
+ import { triangleNormalRaw } from "./triangle.js";
7
+ import { cross3, dot3, length3, normalize3 } from "./vec3.js";
8
+ export function clusterSharedFaces(pairs, opts = {}) {
9
+ const planeDistSnap = opts.planeDistSnap ?? 1e-3;
10
+ const planeAngleSnap = opts.planeAngleSnap ?? 1e-3;
11
+ const lineSnap = opts.lineSnap ?? 1e-3;
12
+ const surfaceAreaM2 = opts.surfaceAreaM2 ?? 0.01;
13
+ const pointAreaM2 = opts.pointAreaM2 ?? 0.001;
14
+ const lineLengthM = opts.lineLengthM ?? 0.05;
15
+ if (pairs.length === 0)
16
+ return [];
17
+ // 1. Split into coplanar clusters and cross clusters.
18
+ const coplanarBuckets = new Map();
19
+ const crossBuckets = new Map();
20
+ for (const p of pairs) {
21
+ if (p.kind === "coplanar") {
22
+ const n = triangleNormalRaw(p.a);
23
+ const ln = length3(n);
24
+ if (ln === 0)
25
+ continue;
26
+ const plane = planeFromNormalPoint(n, p.a.v0);
27
+ const key = planeKey(plane, planeAngleSnap, planeDistSnap);
28
+ const entry = coplanarBuckets.get(key) ?? { plane, pairs: [] };
29
+ entry.pairs.push(p);
30
+ coplanarBuckets.set(key, entry);
31
+ }
32
+ else if (p.kind === "cross") {
33
+ const seg = [p.p1[0] - p.p0[0], p.p1[1] - p.p0[1], p.p1[2] - p.p0[2]];
34
+ const len = length3(seg);
35
+ if (len < 1e-12)
36
+ continue;
37
+ const dir = normalize3(seg);
38
+ // Canonicalise direction so parallel + anti-parallel hash identically.
39
+ const canon = canonicaliseDir(dir);
40
+ const base = closestPointOnLineFromOrigin(p.p0, canon);
41
+ const key = `${quant(canon[0], lineSnap)}|${quant(canon[1], lineSnap)}|${quant(canon[2], lineSnap)}|${quant(base[0], lineSnap)}|${quant(base[1], lineSnap)}|${quant(base[2], lineSnap)}`;
42
+ const entry = crossBuckets.get(key) ?? { dir: canon, point: base, pairs: [] };
43
+ entry.pairs.push(p);
44
+ crossBuckets.set(key, entry);
45
+ }
46
+ }
47
+ const out = [];
48
+ for (const { plane, pairs: bucketPairs } of coplanarBuckets.values()) {
49
+ out.push(classify(buildSurfaceCluster(plane, bucketPairs), surfaceAreaM2, pointAreaM2, lineLengthM));
50
+ }
51
+ for (const { dir, pairs: bucketPairs } of crossBuckets.values()) {
52
+ out.push(classify(buildLineCluster(dir, bucketPairs), surfaceAreaM2, pointAreaM2, lineLengthM));
53
+ }
54
+ return out;
55
+ }
56
+ // -------- surface / coplanar clusters --------
57
+ function buildSurfaceCluster(plane, bucketPairs) {
58
+ const basis = planeBasis(plane);
59
+ let totalArea = 0;
60
+ const boundaryPts = [];
61
+ for (const p of bucketPairs) {
62
+ const pa = [projectTo2D(basis, p.a.v0), projectTo2D(basis, p.a.v1), projectTo2D(basis, p.a.v2)];
63
+ const pb = [projectTo2D(basis, p.b.v0), projectTo2D(basis, p.b.v1), projectTo2D(basis, p.b.v2)];
64
+ // Clip A against B; union of these clip polygons approximates the
65
+ // true shared face. For typical IFC meshes (tiled, non-overlapping)
66
+ // summed clip area ≤ true contact area ≤ the total area of A-triangles
67
+ // whose pair is in the bucket. We take the clipped sum.
68
+ const clipped = sutherlandHodgman(pa, pb);
69
+ if (clipped.length >= 3) {
70
+ totalArea += polygonArea(clipped);
71
+ boundaryPts.push(...clipped);
72
+ }
73
+ }
74
+ const boundary2d = boundaryPts.length >= 3 ? convexHull2(boundaryPts) : boundaryPts;
75
+ const centroid2d = boundary2d.length >= 3
76
+ ? polygonCentroid(boundary2d)
77
+ : boundary2d.length === 0
78
+ ? [0, 0]
79
+ : averagePoint2(boundary2d);
80
+ const centroid = liftTo3D(basis, centroid2d);
81
+ const boundary = boundary2d.map((p) => liftTo3D(basis, p));
82
+ const spans = polygonSpans(boundary2d);
83
+ return {
84
+ plane,
85
+ centroid,
86
+ normal: plane.normal,
87
+ area_m2: totalArea,
88
+ length_m: 0,
89
+ spanMin: spans.min,
90
+ spanMax: spans.max,
91
+ boundary,
92
+ pairs: bucketPairs,
93
+ };
94
+ }
95
+ /**
96
+ * Narrowest and widest dimensions of a convex 2D polygon (metres). `max` is
97
+ * the diameter (largest vertex-pair distance); `min` is the rotating-calipers
98
+ * width — the smallest gap between two parallel supporting lines. Degenerate
99
+ * (< 3 pts) polygons have width 0.
100
+ */
101
+ function polygonSpans(pts) {
102
+ const n = pts.length;
103
+ if (n === 0)
104
+ return { min: 0, max: 0 };
105
+ let max = 0;
106
+ for (let i = 0; i < n; i++) {
107
+ for (let j = i + 1; j < n; j++) {
108
+ const a = pts[i];
109
+ const b = pts[j];
110
+ const d = Math.hypot(a[0] - b[0], a[1] - b[1]);
111
+ if (d > max)
112
+ max = d;
113
+ }
114
+ }
115
+ if (n < 3)
116
+ return { min: 0, max };
117
+ let min = Number.POSITIVE_INFINITY;
118
+ for (let i = 0; i < n; i++) {
119
+ const a = pts[i];
120
+ const b = pts[(i + 1) % n];
121
+ const ex = b[0] - a[0];
122
+ const ey = b[1] - a[1];
123
+ const elen = Math.hypot(ex, ey);
124
+ if (elen < 1e-12)
125
+ continue;
126
+ const nx = -ey / elen;
127
+ const ny = ex / elen;
128
+ let lo = Number.POSITIVE_INFINITY;
129
+ let hi = Number.NEGATIVE_INFINITY;
130
+ for (const p of pts) {
131
+ const d = (p[0] - a[0]) * nx + (p[1] - a[1]) * ny;
132
+ if (d < lo)
133
+ lo = d;
134
+ if (d > hi)
135
+ hi = d;
136
+ }
137
+ const width = hi - lo;
138
+ if (width < min)
139
+ min = width;
140
+ }
141
+ return { min: Number.isFinite(min) ? min : 0, max };
142
+ }
143
+ // -------- line / cross clusters --------
144
+ function buildLineCluster(dir, bucketPairs) {
145
+ // Project every segment onto the line direction to get 1D intervals,
146
+ // union them, sum to get length.
147
+ const intervals = [];
148
+ let refPoint = null;
149
+ for (const p of bucketPairs) {
150
+ if (p.kind !== "cross")
151
+ continue;
152
+ if (!refPoint)
153
+ refPoint = p.p0;
154
+ const t0 = projectOnto(p.p0, refPoint, dir);
155
+ const t1 = projectOnto(p.p1, refPoint, dir);
156
+ intervals.push([Math.min(t0, t1), Math.max(t0, t1)]);
157
+ }
158
+ if (!refPoint || intervals.length === 0) {
159
+ return {
160
+ centroid: [0, 0, 0],
161
+ normal: dir,
162
+ area_m2: 0,
163
+ length_m: 0,
164
+ spanMin: 0,
165
+ spanMax: 0,
166
+ boundary: [],
167
+ pairs: bucketPairs,
168
+ };
169
+ }
170
+ intervals.sort((a, b) => a[0] - b[0]);
171
+ const merged = [];
172
+ for (const iv of intervals) {
173
+ if (merged.length === 0) {
174
+ merged.push([iv[0], iv[1]]);
175
+ continue;
176
+ }
177
+ const last = merged[merged.length - 1];
178
+ if (iv[0] <= last[1])
179
+ last[1] = Math.max(last[1], iv[1]);
180
+ else
181
+ merged.push([iv[0], iv[1]]);
182
+ }
183
+ const length = merged.reduce((s, iv) => s + (iv[1] - iv[0]), 0);
184
+ // Pick the union's overall min/max as the line's centroid basis.
185
+ const tMin = merged[0][0];
186
+ const tMax = merged[merged.length - 1][1];
187
+ const centroidT = (tMin + tMax) / 2;
188
+ const centroid = [
189
+ refPoint[0] + dir[0] * centroidT,
190
+ refPoint[1] + dir[1] * centroidT,
191
+ refPoint[2] + dir[2] * centroidT,
192
+ ];
193
+ const boundary = [
194
+ [refPoint[0] + dir[0] * tMin, refPoint[1] + dir[1] * tMin, refPoint[2] + dir[2] * tMin],
195
+ [refPoint[0] + dir[0] * tMax, refPoint[1] + dir[1] * tMax, refPoint[2] + dir[2] * tMax],
196
+ ];
197
+ return {
198
+ centroid,
199
+ normal: dir,
200
+ area_m2: 0,
201
+ length_m: length,
202
+ spanMin: 0,
203
+ spanMax: length,
204
+ boundary,
205
+ pairs: bucketPairs,
206
+ };
207
+ }
208
+ // -------- helpers --------
209
+ function classify(base, surfaceAreaM2, pointAreaM2, lineLengthM) {
210
+ if (base.area_m2 >= surfaceAreaM2)
211
+ return { ...base, kind: "surface" };
212
+ if (base.length_m >= lineLengthM)
213
+ return { ...base, kind: "line" };
214
+ if (base.area_m2 >= pointAreaM2)
215
+ return { ...base, kind: "line" };
216
+ return { ...base, kind: "point" };
217
+ }
218
+ function canonicaliseDir(v) {
219
+ // Flip so the first significant component is positive.
220
+ if (Math.abs(v[0]) > 1e-9)
221
+ return v[0] > 0 ? v : [-v[0], -v[1], -v[2]];
222
+ if (Math.abs(v[1]) > 1e-9)
223
+ return v[1] > 0 ? v : [-v[0], -v[1], -v[2]];
224
+ return v[2] > 0 ? v : [-v[0], -v[1], -v[2]];
225
+ }
226
+ function closestPointOnLineFromOrigin(point, dir) {
227
+ const t = dot3(point, dir);
228
+ return [point[0] - dir[0] * t, point[1] - dir[1] * t, point[2] - dir[2] * t];
229
+ }
230
+ function projectOnto(point, refPoint, dir) {
231
+ return ((point[0] - refPoint[0]) * dir[0] +
232
+ (point[1] - refPoint[1]) * dir[1] +
233
+ (point[2] - refPoint[2]) * dir[2]);
234
+ }
235
+ function quant(x, snap) {
236
+ return Math.round(x / snap);
237
+ }
238
+ function averagePoint2(pts) {
239
+ if (pts.length === 0)
240
+ return [0, 0];
241
+ let x = 0;
242
+ let y = 0;
243
+ for (const p of pts) {
244
+ x += p[0];
245
+ y += p[1];
246
+ }
247
+ return [x / pts.length, y / pts.length];
248
+ }
249
+ // keep symbol imports that downstream modules may re-export
250
+ void cross3;
251
+ //# sourceMappingURL=shared-faces.js.map