@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.
- package/LICENSE +1 -1
- package/lib/api/inputs/base-inputs.d.ts +8 -0
- package/lib/api/inputs/index.d.ts +3 -0
- package/lib/api/inputs/index.js +3 -0
- package/lib/api/inputs/inputs.d.ts +3 -0
- package/lib/api/inputs/inputs.js +3 -0
- package/lib/api/inputs/line-inputs.d.ts +240 -0
- package/lib/api/inputs/line-inputs.js +247 -0
- package/lib/api/inputs/mesh-inputs.d.ts +82 -0
- package/lib/api/inputs/mesh-inputs.js +83 -0
- package/lib/api/inputs/point-inputs.d.ts +153 -0
- package/lib/api/inputs/point-inputs.js +188 -0
- package/lib/api/inputs/polyline-inputs.d.ts +206 -0
- package/lib/api/inputs/polyline-inputs.js +229 -0
- package/lib/api/inputs/text-inputs.d.ts +1 -1
- package/lib/api/inputs/transforms-inputs.d.ts +18 -0
- package/lib/api/inputs/transforms-inputs.js +29 -0
- package/lib/api/inputs/vector-inputs.d.ts +8 -0
- package/lib/api/inputs/vector-inputs.js +8 -0
- package/lib/api/models/index.d.ts +1 -0
- package/lib/api/models/index.js +1 -0
- package/lib/api/models/point/bucket.d.ts +1 -0
- package/lib/api/models/point/bucket.js +1 -0
- package/lib/api/models/point/hex-grid-data.d.ts +8 -0
- package/lib/api/models/point/hex-grid-data.js +2 -0
- package/lib/api/models/point/index.d.ts +1 -0
- package/lib/api/models/point/index.js +1 -0
- package/lib/api/services/dates.js +45 -15
- package/lib/api/services/index.d.ts +3 -0
- package/lib/api/services/index.js +3 -0
- package/lib/api/services/line.d.ts +149 -0
- package/lib/api/services/line.js +320 -0
- package/lib/api/services/lists.d.ts +1 -1
- package/lib/api/services/lists.js +1 -2
- package/lib/api/services/mesh.d.ts +66 -0
- package/lib/api/services/mesh.js +235 -0
- package/lib/api/services/point.d.ts +96 -1
- package/lib/api/services/point.js +540 -1
- package/lib/api/services/polyline.d.ts +149 -0
- package/lib/api/services/polyline.js +444 -0
- package/lib/api/services/transforms.d.ts +26 -1
- package/lib/api/services/transforms.js +66 -3
- package/lib/api/services/vector.d.ts +18 -0
- package/lib/api/services/vector.js +27 -0
- package/lib/api/unit-test-helper.d.ts +20 -0
- package/lib/api/unit-test-helper.js +130 -0
- 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
|
+
}
|