@bitbybit-dev/base 0.20.1 → 0.20.3

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 (47) hide show
  1. package/LICENSE +1 -1
  2. package/lib/api/inputs/base-inputs.d.ts +8 -0
  3. package/lib/api/inputs/index.d.ts +3 -0
  4. package/lib/api/inputs/index.js +3 -0
  5. package/lib/api/inputs/inputs.d.ts +3 -0
  6. package/lib/api/inputs/inputs.js +3 -0
  7. package/lib/api/inputs/line-inputs.d.ts +240 -0
  8. package/lib/api/inputs/line-inputs.js +247 -0
  9. package/lib/api/inputs/mesh-inputs.d.ts +82 -0
  10. package/lib/api/inputs/mesh-inputs.js +83 -0
  11. package/lib/api/inputs/point-inputs.d.ts +153 -0
  12. package/lib/api/inputs/point-inputs.js +188 -0
  13. package/lib/api/inputs/polyline-inputs.d.ts +206 -0
  14. package/lib/api/inputs/polyline-inputs.js +229 -0
  15. package/lib/api/inputs/text-inputs.d.ts +1 -1
  16. package/lib/api/inputs/transforms-inputs.d.ts +18 -0
  17. package/lib/api/inputs/transforms-inputs.js +29 -0
  18. package/lib/api/inputs/vector-inputs.d.ts +8 -0
  19. package/lib/api/inputs/vector-inputs.js +8 -0
  20. package/lib/api/models/index.d.ts +1 -0
  21. package/lib/api/models/index.js +1 -0
  22. package/lib/api/models/point/bucket.d.ts +1 -0
  23. package/lib/api/models/point/bucket.js +1 -0
  24. package/lib/api/models/point/hex-grid-data.d.ts +8 -0
  25. package/lib/api/models/point/hex-grid-data.js +2 -0
  26. package/lib/api/models/point/index.d.ts +1 -0
  27. package/lib/api/models/point/index.js +1 -0
  28. package/lib/api/services/dates.js +45 -15
  29. package/lib/api/services/index.d.ts +3 -0
  30. package/lib/api/services/index.js +3 -0
  31. package/lib/api/services/line.d.ts +149 -0
  32. package/lib/api/services/line.js +320 -0
  33. package/lib/api/services/lists.d.ts +1 -1
  34. package/lib/api/services/lists.js +1 -2
  35. package/lib/api/services/mesh.d.ts +66 -0
  36. package/lib/api/services/mesh.js +235 -0
  37. package/lib/api/services/point.d.ts +96 -1
  38. package/lib/api/services/point.js +540 -1
  39. package/lib/api/services/polyline.d.ts +149 -0
  40. package/lib/api/services/polyline.js +444 -0
  41. package/lib/api/services/transforms.d.ts +26 -1
  42. package/lib/api/services/transforms.js +66 -3
  43. package/lib/api/services/vector.d.ts +18 -0
  44. package/lib/api/services/vector.js +27 -0
  45. package/lib/api/unit-test-helper.d.ts +20 -0
  46. package/lib/api/unit-test-helper.js +130 -0
  47. package/package.json +2 -2
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Contains various methods for lines and segments. Line in bitbybit is a simple object that has start and end point properties.
3
+ * { start: [ x, y, z ], end: [ x, y, z ] }
4
+ */
5
+ export class Line {
6
+ constructor(vector, point, geometryHelper) {
7
+ this.vector = vector;
8
+ this.point = point;
9
+ this.geometryHelper = geometryHelper;
10
+ }
11
+ /**
12
+ * Gets the start point of the line
13
+ * @param inputs a line
14
+ * @returns start point
15
+ * @group get
16
+ * @shortname line start point
17
+ * @drawable true
18
+ */
19
+ getStartPoint(inputs) {
20
+ return inputs.line.start;
21
+ }
22
+ /**
23
+ * Gets the end point of the line
24
+ * @param inputs a line
25
+ * @returns end point
26
+ * @group get
27
+ * @shortname line end point
28
+ * @drawable true
29
+ */
30
+ getEndPoint(inputs) {
31
+ return inputs.line.end;
32
+ }
33
+ /**
34
+ * Gets the length of the line
35
+ * @param inputs a line
36
+ * @returns line length
37
+ * @group get
38
+ * @shortname line length
39
+ * @drawable false
40
+ */
41
+ length(inputs) {
42
+ return this.point.distance({ startPoint: inputs.line.start, endPoint: inputs.line.end });
43
+ }
44
+ /**
45
+ * Reverse the endpoints of the line
46
+ * @param inputs a line
47
+ * @returns reversed line
48
+ * @group operations
49
+ * @shortname reversed line
50
+ * @drawable true
51
+ */
52
+ reverse(inputs) {
53
+ return { start: inputs.line.end, end: inputs.line.start };
54
+ }
55
+ /**
56
+ * Transform the line
57
+ * @param inputs a line
58
+ * @returns transformed line
59
+ * @group transforms
60
+ * @shortname transform line
61
+ * @drawable true
62
+ */
63
+ transformLine(inputs) {
64
+ const transformation = inputs.transformation;
65
+ let transformedControlPoints = [inputs.line.start, inputs.line.end];
66
+ transformedControlPoints = this.geometryHelper.transformControlPoints(transformation, transformedControlPoints);
67
+ return {
68
+ start: transformedControlPoints[0],
69
+ end: transformedControlPoints[1]
70
+ };
71
+ }
72
+ /**
73
+ * Transforms the lines with multiple transform for each line
74
+ * @param inputs lines
75
+ * @returns transformed lines
76
+ * @group transforms
77
+ * @shortname transform lines
78
+ * @drawable true
79
+ */
80
+ transformsForLines(inputs) {
81
+ return inputs.lines.map((line, index) => {
82
+ const transformation = inputs.transformation[index];
83
+ let transformedControlPoints = [line.start, line.end];
84
+ transformedControlPoints = this.geometryHelper.transformControlPoints(transformation, transformedControlPoints);
85
+ return {
86
+ start: transformedControlPoints[0],
87
+ end: transformedControlPoints[1]
88
+ };
89
+ });
90
+ }
91
+ /**
92
+ * Create the line
93
+ * @param inputs start and end points of the line
94
+ * @returns line
95
+ * @group create
96
+ * @shortname line
97
+ * @drawable true
98
+ */
99
+ create(inputs) {
100
+ return {
101
+ start: inputs.start,
102
+ end: inputs.end,
103
+ };
104
+ }
105
+ /**
106
+ * Gets the point on the line segment at a given param
107
+ * @param inputs line
108
+ * @returns point on line
109
+ * @group get
110
+ * @shortname point on line
111
+ * @drawable true
112
+ */
113
+ getPointOnLine(inputs) {
114
+ // Calculate direction vector of line segment
115
+ const point1 = inputs.line.start;
116
+ const point2 = inputs.line.end;
117
+ const parameter = inputs.param;
118
+ const direction = [point2[0] - point1[0], point2[1] - point1[1], point2[2] - point1[2]];
119
+ // Calculate point on line segment corresponding to parameter value
120
+ const point = [point1[0] + parameter * direction[0], point1[1] + parameter * direction[1], point1[2] + parameter * direction[2]];
121
+ return point;
122
+ }
123
+ /**
124
+ * Create the lines segments between all of the points in a list
125
+ * @param inputs points
126
+ * @returns lines
127
+ * @group create
128
+ * @shortname lines between points
129
+ * @drawable true
130
+ */
131
+ linesBetweenPoints(inputs) {
132
+ const lines = [];
133
+ for (let i = 1; i < inputs.points.length; i++) {
134
+ const previousPoint = inputs.points[i - 1];
135
+ const currentPoint = inputs.points[i];
136
+ lines.push({ start: previousPoint, end: currentPoint });
137
+ }
138
+ return lines;
139
+ }
140
+ /**
141
+ * Create the lines between start and end points
142
+ * @param inputs start points and end points
143
+ * @returns lines
144
+ * @group create
145
+ * @shortname start and end points to lines
146
+ * @drawable true
147
+ */
148
+ linesBetweenStartAndEndPoints(inputs) {
149
+ return inputs.startPoints
150
+ .map((s, index) => ({ start: s, end: inputs.endPoints[index] }))
151
+ .filter(line => this.point.distance({ startPoint: line.start, endPoint: line.end }) !== 0);
152
+ }
153
+ /**
154
+ * Convert the line to segment
155
+ * @param inputs line
156
+ * @returns segment
157
+ * @group convert
158
+ * @shortname line to segment
159
+ * @drawable false
160
+ */
161
+ lineToSegment(inputs) {
162
+ return [inputs.line.start, inputs.line.end];
163
+ }
164
+ /**
165
+ * Converts the lines to segments
166
+ * @param inputs lines
167
+ * @returns segments
168
+ * @group convert
169
+ * @shortname lines to segments
170
+ * @drawable false
171
+ */
172
+ linesToSegments(inputs) {
173
+ return inputs.lines.map(line => [line.start, line.end]);
174
+ }
175
+ /**
176
+ * Converts the segment to line
177
+ * @param inputs segment
178
+ * @returns line
179
+ * @group convert
180
+ * @shortname segment to line
181
+ * @drawable true
182
+ */
183
+ segmentToLine(inputs) {
184
+ return { start: inputs.segment[0], end: inputs.segment[1] };
185
+ }
186
+ /**
187
+ * Converts the segments to lines
188
+ * @param inputs segments
189
+ * @returns lines
190
+ * @group convert
191
+ * @shortname segments to lines
192
+ * @drawable true
193
+ */
194
+ segmentsToLines(inputs) {
195
+ return inputs.segments.map(segment => ({ start: segment[0], end: segment[1] }));
196
+ }
197
+ /**
198
+ * If two lines intersect return the intersection point
199
+ * @param inputs line1 and line2
200
+ * @returns intersection point or undefined if no intersection
201
+ * @group intersection
202
+ * @shortname line-line int
203
+ * @drawable true
204
+ */
205
+ lineLineIntersection(inputs) {
206
+ const epsilon = inputs.tolerance || 1e-6; // Default tolerance
207
+ const checkSegments = inputs.checkSegmentsOnly;
208
+ const line1 = inputs.line1;
209
+ const line2 = inputs.line2;
210
+ // Input validation
211
+ if (!(line1 === null || line1 === void 0 ? void 0 : line1.start) || !line1.end || !(line2 === null || line2 === void 0 ? void 0 : line2.start) || !line2.end ||
212
+ line1.start.length !== 3 || line1.end.length !== 3 ||
213
+ line2.start.length !== 3 || line2.end.length !== 3) {
214
+ console.error("Invalid line input to lineLineIntersection");
215
+ return undefined;
216
+ }
217
+ const p1 = line1.start;
218
+ const d1 = this.vector.sub({ first: line1.end, second: line1.start }); // Direction vector line 1
219
+ const p2 = line2.start;
220
+ const d2 = this.vector.sub({ first: line2.end, second: line2.start }); // Direction vector line 2
221
+ const p21 = this.vector.sub({ first: p2, second: p1 }); // Vector between start points
222
+ // --- Check for Zero-Length Segments ---
223
+ const lenSq1 = this.vector.lengthSq({ vector: d1 });
224
+ const lenSq2 = this.vector.lengthSq({ vector: d2 });
225
+ // Compare squared length against squared epsilon
226
+ if (lenSq1 < epsilon * epsilon || lenSq2 < epsilon * epsilon) {
227
+ return undefined;
228
+ }
229
+ // --- Check for Parallelism ---
230
+ const d1_cross_d2 = this.vector.cross({ first: d1, second: d2 });
231
+ const crossMagSq = this.vector.lengthSq({ vector: d1_cross_d2 });
232
+ // Check if squared magnitude of cross product is near zero (relative to segment lengths)
233
+ // Use epsilon squared as a base tolerance, potentially scale by magnitudes
234
+ const parallel_tolerance_sq = epsilon * epsilon; // May need adjustment: * lenSq1 * lenSq2;
235
+ if (crossMagSq < parallel_tolerance_sq) {
236
+ // Potentially Parallel or Collinear
237
+ // Check if collinear: p21 must be parallel to d1
238
+ const p21_cross_d1 = this.vector.cross({ first: p21, second: d1 });
239
+ // Use similar tolerance logic for collinear check
240
+ const collinear_tolerance_sq = epsilon * epsilon * lenSq1; // Scale by line1 length
241
+ if (this.vector.lengthSq({ vector: p21_cross_d1 }) < collinear_tolerance_sq) {
242
+ // Collinear
243
+ if (!checkSegments) {
244
+ return p1; // Infinite lines intersect everywhere, return p1 arbitrarily
245
+ }
246
+ else {
247
+ // --- Check for Segment Overlap (Collinear case) ---
248
+ const d1d1 = lenSq1; // Reuse calculated squared length
249
+ // Avoid division by zero if lenSq1 is extremely small (should be caught earlier)
250
+ const safe_d1d1 = (d1d1 < epsilon * epsilon) ? 1.0 : d1d1;
251
+ const d1p21 = this.vector.dot({ first: d1, second: p21 }); // Dot product d1·(p2-p1)
252
+ const t_p2 = d1p21 / safe_d1d1; // Parameter for p2 projected onto line1's frame
253
+ const vec_e2_p1 = this.vector.sub({ first: line2.end, second: p1 });
254
+ const t_e2 = this.vector.dot({ first: d1, second: vec_e2_p1 }) / safe_d1d1; // Param for e2
255
+ const interval2_t = [Math.min(t_p2, t_e2), Math.max(t_p2, t_e2)];
256
+ const interval1_t = [0, 1]; // Line1 segment parameter range
257
+ const overlap_start = Math.max(interval1_t[0], interval2_t[0]);
258
+ const overlap_end = Math.min(interval1_t[1], interval2_t[1]);
259
+ // Check for overlap including tolerance
260
+ if (overlap_start <= overlap_end + epsilon) {
261
+ // Overlap exists, but intersection is a segment, not a single point
262
+ return undefined;
263
+ }
264
+ else {
265
+ // Collinear but segments do not overlap
266
+ return undefined;
267
+ }
268
+ }
269
+ }
270
+ else {
271
+ // Parallel but not collinear
272
+ return undefined;
273
+ }
274
+ }
275
+ // --- Lines are NOT Parallel - Check for Skewness using Scalar Triple Product ---
276
+ const scalarTripleProduct = this.vector.dot({ first: p21, second: d1_cross_d2 });
277
+ // If the scalar triple product is significantly non-zero, the lines are skew.
278
+ // Tolerance needs consideration - relates to the "volume" formed by the vectors.
279
+ // A simple absolute check against epsilon^3 or similar might work for typical scales.
280
+ // Consider scaling tolerance if coordinates can be very large/small.
281
+ const skew_tolerance = epsilon * epsilon * epsilon;
282
+ if (Math.abs(scalarTripleProduct) > skew_tolerance) {
283
+ // Lines are Skew
284
+ return undefined;
285
+ }
286
+ // --- Lines are Intersecting (Coplanar and Non-Parallel) ---
287
+ // Calculate intersection parameters t (for line1) and u (for line2)
288
+ // We can use the formulas derived earlier, which are valid for intersecting lines.
289
+ const d1d1 = lenSq1;
290
+ const d2d2 = lenSq2;
291
+ const d1d2 = this.vector.dot({ first: d1, second: d2 });
292
+ const d1p21 = this.vector.dot({ first: d1, second: p21 });
293
+ const d2p21 = this.vector.dot({ first: d2, second: p21 });
294
+ // Denominator for parameter calculation (same as crossMagSq, essentially)
295
+ const denominator = d1d1 * d2d2 - d1d2 * d1d2;
296
+ // Denominator *should* be non-zero based on the parallelism check above,
297
+ // but add a defensive check.
298
+ if (Math.abs(denominator) < epsilon * epsilon) {
299
+ console.error("Internal error: Denominator near zero after non-parallel check.");
300
+ return undefined; // Should not happen
301
+ }
302
+ const t = (d2d2 * d1p21 - d1d2 * d2p21) / denominator;
303
+ const u = (d1d2 * d1p21 - d1d1 * d2p21) / denominator;
304
+ // --- Optional check: Is intersection within segment bounds? ---
305
+ if (checkSegments) {
306
+ // Check if t and u are within the range [0, 1] (using tolerance)
307
+ if (t < -epsilon || t > 1.0 + epsilon || u < -epsilon || u > 1.0 + epsilon) {
308
+ return undefined; // Intersection point is outside one or both segments
309
+ }
310
+ }
311
+ // --- Calculate Intersection Point ---
312
+ const intersectionPoint = this.getPointOnLine({ param: t, line: line1 });
313
+ // Clip near-zero results based on the input epsilon
314
+ return [
315
+ Math.abs(intersectionPoint[0]) < epsilon ? 0 : intersectionPoint[0],
316
+ Math.abs(intersectionPoint[1]) < epsilon ? 0 : intersectionPoint[1],
317
+ Math.abs(intersectionPoint[2]) < epsilon ? 0 : intersectionPoint[2],
318
+ ];
319
+ }
320
+ }
@@ -95,7 +95,7 @@ export declare class Lists {
95
95
  * @shortname group elements
96
96
  * @drawable false
97
97
  */
98
- groupNth<T>(inputs: Inputs.Lists.GroupListDto<T>): T[];
98
+ groupNth<T>(inputs: Inputs.Lists.GroupListDto<T>): T[][];
99
99
  /**
100
100
  * Get the depth of the list
101
101
  * @param inputs a list
@@ -267,13 +267,12 @@ export class Lists {
267
267
  result.push(currentGroup);
268
268
  currentGroup = [];
269
269
  }
270
- if (keepRemainder && index === list.length - 1) {
270
+ if (currentGroup.length > 0 && keepRemainder && index === list.length - 1) {
271
271
  result.push(currentGroup);
272
272
  }
273
273
  });
274
274
  return result;
275
275
  };
276
- // TODO make this work on any level
277
276
  return groupElements(inputs);
278
277
  }
279
278
  /**
@@ -0,0 +1,66 @@
1
+ import * as Inputs from "../inputs";
2
+ import { Polyline } from "./polyline";
3
+ import { Vector } from "./vector";
4
+ /**
5
+ * Contains various mesh helper methods that are not necessarily present in higher level CAD kernels that bitbybit is using.
6
+ */
7
+ export declare class MeshBitByBit {
8
+ private readonly vector;
9
+ private readonly polyline;
10
+ constructor(vector: Vector, polyline: Polyline);
11
+ /**
12
+ * Computes the signed distance from a point to a plane.
13
+ * @param inputs a point and a plane
14
+ * @returns signed distance
15
+ * @group base
16
+ * @shortname signed dist to plane
17
+ * @drawable false
18
+ */
19
+ signedDistanceToPlane(inputs: Inputs.Mesh.SignedDistanceFromPlaneToPointDto): number;
20
+ /**
21
+ * Calculates the triangle plane from triangle.
22
+ * @param inputs triangle and tolerance
23
+ * @returns triangle plane
24
+ * @group traingle
25
+ * @shortname triangle plane
26
+ * @drawable false
27
+ */
28
+ calculateTrianglePlane(inputs: Inputs.Mesh.TriangleToleranceDto): Inputs.Base.TrianglePlane3 | undefined;
29
+ /**
30
+ * Calculates the intersection of two triangles.
31
+ * @param inputs first triangle, second triangle, and tolerance
32
+ * @returns intersection segment or undefined if no intersection
33
+ * @group traingle
34
+ * @shortname triangle-triangle int
35
+ * @drawable false
36
+ */
37
+ triangleTriangleIntersection(inputs: Inputs.Mesh.TriangleTriangleToleranceDto): Inputs.Base.Segment3 | undefined;
38
+ /**
39
+ * Computes the intersection segments of two meshes.
40
+ * @param inputs first mesh, second mesh, and tolerance
41
+ * @returns array of intersection segments
42
+ * @group mesh
43
+ * @shortname mesh-mesh int segments
44
+ * @drawable false
45
+ */
46
+ meshMeshIntersectionSegments(inputs: Inputs.Mesh.MeshMeshToleranceDto): Inputs.Base.Segment3[];
47
+ /**
48
+ * Computes the intersection polylines of two meshes.
49
+ * @param inputs first mesh, second mesh, and tolerance
50
+ * @returns array of intersection polylines
51
+ * @group mesh
52
+ * @shortname mesh-mesh int polylines
53
+ * @drawable true
54
+ */
55
+ meshMeshIntersectionPolylines(inputs: Inputs.Mesh.MeshMeshToleranceDto): Inputs.Base.Polyline3[];
56
+ /**
57
+ * Computes the intersection points of two meshes.
58
+ * @param inputs first mesh, second mesh, and tolerance
59
+ * @returns array of intersection points
60
+ * @group mesh
61
+ * @shortname mesh-mesh int points
62
+ * @drawable false
63
+ */
64
+ meshMeshIntersectionPoints(inputs: Inputs.Mesh.MeshMeshToleranceDto): Inputs.Base.Point3[][];
65
+ private computeIntersectionPoint;
66
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Contains various mesh helper methods that are not necessarily present in higher level CAD kernels that bitbybit is using.
3
+ */
4
+ export class MeshBitByBit {
5
+ constructor(vector, polyline) {
6
+ this.vector = vector;
7
+ this.polyline = polyline;
8
+ }
9
+ /**
10
+ * Computes the signed distance from a point to a plane.
11
+ * @param inputs a point and a plane
12
+ * @returns signed distance
13
+ * @group base
14
+ * @shortname signed dist to plane
15
+ * @drawable false
16
+ */
17
+ signedDistanceToPlane(inputs) {
18
+ return this.vector.dot({ first: inputs.plane.normal, second: inputs.point }) - inputs.plane.d;
19
+ }
20
+ /**
21
+ * Calculates the triangle plane from triangle.
22
+ * @param inputs triangle and tolerance
23
+ * @returns triangle plane
24
+ * @group traingle
25
+ * @shortname triangle plane
26
+ * @drawable false
27
+ */
28
+ calculateTrianglePlane(inputs) {
29
+ const EPSILON_SQ = Math.pow((inputs.tolerance || 1e-7), 2);
30
+ const edge1 = this.vector.sub({ first: inputs.triangle[1], second: inputs.triangle[0] });
31
+ const edge2 = this.vector.sub({ first: inputs.triangle[2], second: inputs.triangle[0] });
32
+ const normal = this.vector.cross({ first: edge1, second: edge2 });
33
+ if (this.vector.lengthSq({ vector: normal }) < EPSILON_SQ) {
34
+ return undefined; // Degenerate triangle
35
+ }
36
+ // Defensive copy if normalize modifies in-place
37
+ const normalizedNormal = this.vector.normalized({ vector: normal });
38
+ const d = this.vector.dot({ first: normalizedNormal, second: inputs.triangle[0] });
39
+ return { normal: normalizedNormal, d: d };
40
+ }
41
+ /**
42
+ * Calculates the intersection of two triangles.
43
+ * @param inputs first triangle, second triangle, and tolerance
44
+ * @returns intersection segment or undefined if no intersection
45
+ * @group traingle
46
+ * @shortname triangle-triangle int
47
+ * @drawable false
48
+ */
49
+ triangleTriangleIntersection(inputs) {
50
+ const t1 = inputs.triangle1;
51
+ const t2 = inputs.triangle2;
52
+ const EPSILON = inputs.tolerance || 1e-7;
53
+ const p1 = t1[0], p2 = t1[1], p3 = t1[2];
54
+ const q1 = t2[0], q2 = t2[1], q3 = t2[2];
55
+ const plane1 = this.calculateTrianglePlane({ triangle: t1, tolerance: EPSILON });
56
+ const plane2 = this.calculateTrianglePlane({ triangle: t2, tolerance: EPSILON });
57
+ if (!plane1 || !plane2)
58
+ return undefined;
59
+ const distQ_Plane1 = [
60
+ this.signedDistanceToPlane({ point: q1, plane: plane1 }),
61
+ this.signedDistanceToPlane({ point: q2, plane: plane1 }),
62
+ this.signedDistanceToPlane({ point: q3, plane: plane1 }),
63
+ ];
64
+ if ((distQ_Plane1[0] > EPSILON && distQ_Plane1[1] > EPSILON && distQ_Plane1[2] > EPSILON) ||
65
+ (distQ_Plane1[0] < -EPSILON && distQ_Plane1[1] < -EPSILON && distQ_Plane1[2] < -EPSILON)) {
66
+ return undefined;
67
+ }
68
+ const distP_Plane2 = [
69
+ this.signedDistanceToPlane({ point: p1, plane: plane2 }),
70
+ this.signedDistanceToPlane({ point: p2, plane: plane2 }),
71
+ this.signedDistanceToPlane({ point: p3, plane: plane2 }),
72
+ ];
73
+ if ((distP_Plane2[0] > EPSILON && distP_Plane2[1] > EPSILON && distP_Plane2[2] > EPSILON) ||
74
+ (distP_Plane2[0] < -EPSILON && distP_Plane2[1] < -EPSILON && distP_Plane2[2] < -EPSILON)) {
75
+ return undefined;
76
+ }
77
+ const allDistPZero = distP_Plane2.every(d => Math.abs(d) < EPSILON);
78
+ const allDistQZero = distQ_Plane1.every(d => Math.abs(d) < EPSILON);
79
+ if (allDistPZero && allDistQZero) {
80
+ return undefined; // Explicitly not handling coplanar intersection areas
81
+ }
82
+ const lineDir = this.vector.cross({ first: plane1.normal, second: plane2.normal });
83
+ const det = this.vector.dot({ first: lineDir, second: lineDir }); // det = |lineDir|^2
84
+ if (det < EPSILON * EPSILON) {
85
+ return undefined; // Planes parallel, no line intersection (coplanar case handled above)
86
+ }
87
+ // --- Calculate Interval Projections ---
88
+ // Store the 3D points that define the intervals on the line
89
+ const t1_intersection_points_3d = [];
90
+ const t2_intersection_points_3d = [];
91
+ const edges1 = [[p1, p2], [p2, p3], [p3, p1]];
92
+ const dists1 = distP_Plane2;
93
+ for (let i = 0; i < 3; ++i) {
94
+ const u = edges1[i][0];
95
+ const v = edges1[i][1];
96
+ const du = dists1[i];
97
+ const dv = dists1[(i + 1) % 3];
98
+ if (Math.abs(du) < EPSILON)
99
+ t1_intersection_points_3d.push(u); // Start vertex is on plane2
100
+ // Removed the redundant check for dv here, handled by next edge start
101
+ if ((du * dv) < 0 && Math.abs(du - dv) > EPSILON) { // Edge crosses plane2
102
+ const t = du / (du - dv);
103
+ t1_intersection_points_3d.push(this.computeIntersectionPoint(u, v, t));
104
+ }
105
+ }
106
+ const edges2 = [[q1, q2], [q2, q3], [q3, q1]];
107
+ const dists2 = distQ_Plane1;
108
+ for (let i = 0; i < 3; ++i) {
109
+ const u = edges2[i][0];
110
+ const v = edges2[i][1];
111
+ const du = dists2[i];
112
+ const dv = dists2[(i + 1) % 3];
113
+ if (Math.abs(du) < EPSILON)
114
+ t2_intersection_points_3d.push(u); // Start vertex is on plane1
115
+ // Removed redundant check for dv
116
+ if ((du * dv) < 0 && Math.abs(du - dv) > EPSILON) { // Edge crosses plane1
117
+ const t = du / (du - dv);
118
+ t2_intersection_points_3d.push(this.computeIntersectionPoint(u, v, t));
119
+ }
120
+ }
121
+ // We expect exactly two points for each triangle in the standard piercing case.
122
+ // Handle potential duplicates or edge cases if more points are generated (e.g., edge lies on plane)
123
+ // A simple check for the common case:
124
+ if (t1_intersection_points_3d.length < 2 || t2_intersection_points_3d.length < 2) {
125
+ // This can happen if triangles touch at a vertex or edge without crossing planes,
126
+ // or due to numerical precision near edges/vertices.
127
+ return undefined; // Treat touch as no intersection segment
128
+ }
129
+ // Calculate a robust origin ON the intersection line
130
+ const n1 = plane1.normal;
131
+ const n2 = plane2.normal;
132
+ const d1 = plane1.d;
133
+ const d2 = plane2.d;
134
+ // Point P = ( (d1 * N2 - d2 * N1) x D ) / (D dot D)
135
+ const term1 = this.vector.mul({ vector: n2, scalar: d1 });
136
+ const term2 = this.vector.mul({ vector: n1, scalar: d2 });
137
+ const termSub = this.vector.sub({ first: term1, second: term2 });
138
+ const crossTerm = this.vector.cross({ first: termSub, second: lineDir });
139
+ const lineOrigin = this.vector.mul({ vector: crossTerm, scalar: 1.0 / det });
140
+ // Project the 3D intersection points onto the lineDir, relative to lineOrigin
141
+ const t1_params = t1_intersection_points_3d.map(p => this.vector.dot({ first: this.vector.sub({ first: p, second: lineOrigin }), second: lineDir }));
142
+ const t2_params = t2_intersection_points_3d.map(p => this.vector.dot({ first: this.vector.sub({ first: p, second: lineOrigin }), second: lineDir }));
143
+ // Find the intervals
144
+ const t1Interval = [Math.min(...t1_params), Math.max(...t1_params)];
145
+ const t2Interval = [Math.min(...t2_params), Math.max(...t2_params)];
146
+ // Find the overlap of the two intervals
147
+ const intersectionMinParam = Math.max(t1Interval[0], t2Interval[0]);
148
+ const intersectionMaxParam = Math.min(t1Interval[1], t2Interval[1]);
149
+ // Check if the overlap is valid
150
+ if (intersectionMinParam < intersectionMaxParam - (EPSILON * det)) { // Let's use scaled epsilon for robustness against small det values.
151
+ // Convert the final parameters back to 3D points using the lineOrigin
152
+ // P = lineOrigin + dir * (param / det)
153
+ const point1 = this.vector.add({ first: lineOrigin, second: this.vector.mul({ vector: lineDir, scalar: intersectionMinParam / det }) });
154
+ const point2 = this.vector.add({ first: lineOrigin, second: this.vector.mul({ vector: lineDir, scalar: intersectionMaxParam / det }) });
155
+ // Check if the resulting segment has non-zero length
156
+ const segVec = this.vector.sub({ first: point1, second: point2 });
157
+ if (this.vector.lengthSq({ vector: segVec }) > EPSILON * EPSILON) {
158
+ return [point1, point2];
159
+ }
160
+ else {
161
+ return undefined; // Degenerate segment
162
+ }
163
+ }
164
+ else {
165
+ return undefined; // Intervals do not overlap
166
+ }
167
+ }
168
+ /**
169
+ * Computes the intersection segments of two meshes.
170
+ * @param inputs first mesh, second mesh, and tolerance
171
+ * @returns array of intersection segments
172
+ * @group mesh
173
+ * @shortname mesh-mesh int segments
174
+ * @drawable false
175
+ */
176
+ meshMeshIntersectionSegments(inputs) {
177
+ const mesh1 = inputs.mesh1;
178
+ const mesh2 = inputs.mesh2;
179
+ const intersectionSegments = [];
180
+ for (let i = 0; i < mesh1.length; ++i) {
181
+ for (let j = 0; j < mesh2.length; ++j) {
182
+ const triangle1 = mesh1[i];
183
+ const triangle2 = mesh2[j];
184
+ const segment = this.triangleTriangleIntersection({ triangle1, triangle2, tolerance: inputs.tolerance });
185
+ if (segment) {
186
+ intersectionSegments.push(segment);
187
+ }
188
+ }
189
+ }
190
+ return intersectionSegments;
191
+ }
192
+ /**
193
+ * Computes the intersection polylines of two meshes.
194
+ * @param inputs first mesh, second mesh, and tolerance
195
+ * @returns array of intersection polylines
196
+ * @group mesh
197
+ * @shortname mesh-mesh int polylines
198
+ * @drawable true
199
+ */
200
+ meshMeshIntersectionPolylines(inputs) {
201
+ const segments = this.meshMeshIntersectionSegments(inputs);
202
+ return this.polyline.sortSegmentsIntoPolylines({ segments, tolerance: inputs.tolerance });
203
+ }
204
+ /**
205
+ * Computes the intersection points of two meshes.
206
+ * @param inputs first mesh, second mesh, and tolerance
207
+ * @returns array of intersection points
208
+ * @group mesh
209
+ * @shortname mesh-mesh int points
210
+ * @drawable false
211
+ */
212
+ meshMeshIntersectionPoints(inputs) {
213
+ const polylines = this.meshMeshIntersectionPolylines(inputs);
214
+ return polylines.map(polyline => {
215
+ if (polyline.isClosed) {
216
+ return [...polyline.points, polyline.points[0]];
217
+ }
218
+ else {
219
+ return polyline.points;
220
+ }
221
+ });
222
+ }
223
+ computeIntersectionPoint(u, v, t) {
224
+ return this.vector.add({
225
+ first: u,
226
+ second: this.vector.mul({
227
+ vector: this.vector.sub({
228
+ first: v,
229
+ second: u
230
+ }),
231
+ scalar: t
232
+ })
233
+ });
234
+ }
235
+ }