@jgphilpott/polytree 0.0.7 → 0.0.9

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.
@@ -0,0 +1,3998 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ import { Vector2, Vector3, Box3, Line3, Sphere, Matrix3, Triangle, Ray, Raycaster, Mesh, DoubleSide, BufferGeometry, BufferAttribute, Plane as ThreePlane } from "three";
3
+ // Generated by CoffeeScript 2.7.0
4
+ // =============================================================================
5
+ // MATHEMATICAL CONSTANTS
6
+ // Constants used for floating-point precision, geometry, and mathematical operations.
7
+ // =============================================================================
8
+ // Floating-point tolerance for general geometric operations.
9
+ var ASYNC_OPERATION_TIMEOUT, DEBUG_COLOR_BACK, DEBUG_COLOR_COPLANAR, DEBUG_COLOR_FRONT, DEBUG_COLOR_SPANNING, DEBUG_GEOMETRY_VALIDATION, DEBUG_INTERSECTION_VERIFICATION, DEBUG_PERFORMANCE_TIMING, DEBUG_VERBOSE_LOGGING, DEFAULT_BUFFER_SIZE, DEFAULT_COORDINATE_PRECISION, DEFAULT_MATERIAL_INDEX, GARBAGE_COLLECTION_THRESHOLD, GEOMETRIC_EPSILON, GEOMETRY_CACHE_SIZE, INTERSECTION_CACHE_SIZE, MAX_REFINEMENT_ITERATIONS, MAX_WORKER_THREADS, MEMORY_USAGE_WARNING_LIMIT, POINT_COINCIDENCE_THRESHOLD, POLYGON_BACK, POLYGON_COPLANAR, POLYGON_FRONT, POLYGON_SPANNING, POLYTREE_MAX_DEPTH, POLYTREE_MAX_POLYGONS_PER_NODE, POLYTREE_MIN_NODE_SIZE, RAY_INTERSECTION_EPSILON, TRIANGLE_2D_EPSILON, WINDING_NUMBER_FULL_ROTATION, defaultRayDirection, meshOperationNormalVector, meshOperationVertexVector, polygonID, rayTriangleEdge1, rayTriangleEdge2, rayTriangleHVector, rayTriangleQVector, rayTriangleSVector, temporaryBoundingBox, temporaryMatrix3, temporaryMatrixWithNormalCalc, temporaryRay, temporaryRaycaster, temporaryTriangleVertex, temporaryTriangleVertexSecondary, temporaryVector3Primary, temporaryVector3Quaternary, temporaryVector3Secondary, temporaryVector3Tertiary, windingNumberEpsilonOffsets, windingNumberEpsilonOffsetsCount, windingNumberMatrix3, windingNumberTestPoint, windingNumberVector1, windingNumberVector2, windingNumberVector3;
10
+ GEOMETRIC_EPSILON = 1e-8;
11
+ // Ultra-high precision tolerance for ray-triangle intersection calculations.
12
+ RAY_INTERSECTION_EPSILON = 1e-12;
13
+ // 2D geometry tolerance for triangle overlap calculations.
14
+ TRIANGLE_2D_EPSILON = 1e-14;
15
+ // Full rotation constant (4π) used in winding number calculations.
16
+ WINDING_NUMBER_FULL_ROTATION = 4 * Math.PI;
17
+ // =============================================================================
18
+ // POLYGON CLASSIFICATION CONSTANTS
19
+ // Used for determining spatial relationships between polygons and planes.
20
+ // =============================================================================
21
+ // Polygon lies exactly on the splitting plane.
22
+ POLYGON_COPLANAR = 0;
23
+ // Polygon lies entirely in front of the splitting plane.
24
+ POLYGON_FRONT = 1;
25
+ // Polygon lies entirely behind the splitting plane
26
+ POLYGON_BACK = 2;
27
+ // Polygon crosses the splitting plane (requires splitting).
28
+ POLYGON_SPANNING = 3;
29
+ // =============================================================================
30
+ // GENERAL PURPOSE TEMPORARY OBJECTS
31
+ // Reusable objects to avoid memory allocation during calculations.
32
+ // =============================================================================
33
+ // Primary temporary vector for general 3D calculations.
34
+ temporaryVector3Primary = new Vector3();
35
+ // Secondary temporary vector for 3D calculations when two vectors needed.
36
+ temporaryVector3Secondary = new Vector3();
37
+ // Tertiary temporary vector for calculations requiring three vectors.
38
+ temporaryVector3Tertiary = new Vector3();
39
+ // Quaternary temporary vector for triangle-specific calculations.
40
+ temporaryVector3Quaternary = new Vector3();
41
+ // Temporary bounding box for spatial calculations.
42
+ temporaryBoundingBox = new Box3();
43
+ // Temporary raycaster for intersection calculations.
44
+ temporaryRaycaster = new Raycaster();
45
+ // Temporary ray object for geometric queries.
46
+ temporaryRay = new Ray();
47
+ // Default ray direction pointing along positive Z-axis.
48
+ defaultRayDirection = new Vector3(0, 0, 1);
49
+ // =============================================================================
50
+ // WINDING NUMBER ALGORITHM VARIABLES
51
+ // Used for point-in-polygon testing using the winding number method.
52
+ // =============================================================================
53
+ // First vertex vector relative to test point.
54
+ windingNumberVector1 = new Vector3();
55
+ // Second vertex vector relative to test point.
56
+ windingNumberVector2 = new Vector3();
57
+ // Third vertex vector relative to test point.
58
+ windingNumberVector3 = new Vector3();
59
+ // Test point position vector.
60
+ windingNumberTestPoint = new Vector3();
61
+ // Epsilon offset vectors for handling edge cases in winding number calculation.
62
+ windingNumberEpsilonOffsets = [new Vector3(GEOMETRIC_EPSILON, 0, 0), new Vector3(0, GEOMETRIC_EPSILON, 0), new Vector3(0, 0, GEOMETRIC_EPSILON), new Vector3(-GEOMETRIC_EPSILON, 0, 0), new Vector3(0, -GEOMETRIC_EPSILON, 0), new Vector3(0, 0, -GEOMETRIC_EPSILON)];
63
+ // Count of epsilon offset vectors for iteration.
64
+ windingNumberEpsilonOffsetsCount = windingNumberEpsilonOffsets.length;
65
+ // 3x3 matrix for winding number determinant calculations.
66
+ windingNumberMatrix3 = new Matrix3();
67
+ // =============================================================================
68
+ // RAY-TRIANGLE INTERSECTION VARIABLES
69
+ // Used in Möller–Trumbore ray-triangle intersection algorithm.
70
+ // =============================================================================
71
+ // Triangle edge vector from vertex A to vertex B.
72
+ rayTriangleEdge1 = new Vector3();
73
+ // Triangle edge vector from vertex A to vertex C.
74
+ rayTriangleEdge2 = new Vector3();
75
+ // Cross product of ray direction and second edge.
76
+ rayTriangleHVector = new Vector3();
77
+ // Vector from ray origin to triangle vertex A.
78
+ rayTriangleSVector = new Vector3();
79
+ // Cross product for final intersection calculation.
80
+ rayTriangleQVector = new Vector3();
81
+ // =============================================================================
82
+ // MATRIX AND TRANSFORMATION VARIABLES
83
+ // Used for coordinate transformations and matrix operations.
84
+ // =============================================================================
85
+ // Temporary vertex position for geometric calculations.
86
+ temporaryTriangleVertex = new Vector3();
87
+ // Second temporary vertex for calculations requiring multiple vertices.
88
+ temporaryTriangleVertexSecondary = new Vector3();
89
+ // Temporary 3x3 matrix for normal transformations and general calculations.
90
+ temporaryMatrix3 = new Matrix3();
91
+ // Extended temporary matrix with normal matrix calculation method.
92
+ temporaryMatrixWithNormalCalc = new Matrix3();
93
+ // Method to calculate normal matrix from 4x4 transformation matrix.
94
+ temporaryMatrixWithNormalCalc.getNormalMatrix = function(matrix) {
95
+ return this.setFromMatrix4(matrix).invert().transpose();
96
+ };
97
+ // Temporary vector for mesh normal calculations and geometry operations.
98
+ meshOperationNormalVector = new Vector3();
99
+ // Temporary vertex vector for mesh transformation and vertex operations.
100
+ meshOperationVertexVector = new Vector3();
101
+ // =============================================================================
102
+ // CSG AND POLYTREE OPERATION VARIABLES
103
+ // Variables for Constructive Solid Geometry and spatial partitioning.
104
+ // =============================================================================
105
+ // Maximum subdivision depth for polytree structures.
106
+ POLYTREE_MAX_DEPTH = 1000;
107
+ // Maximum polygons per polytree node before subdivision.
108
+ POLYTREE_MAX_POLYGONS_PER_NODE = 100000;
109
+ // Minimum polytree node size to prevent excessive subdivision.
110
+ POLYTREE_MIN_NODE_SIZE = 1e-6;
111
+ // Default material index for CSG operations.
112
+ DEFAULT_MATERIAL_INDEX = 0;
113
+ // Maximum iterations for iterative refinement algorithms.
114
+ MAX_REFINEMENT_ITERATIONS = 10000;
115
+ // =============================================================================
116
+ // PERFORMANCE AND OPTIMIZATION VARIABLES
117
+ // Settings for balancing accuracy vs performance.
118
+ // =============================================================================
119
+ // Default precision for point coordinate rounding.
120
+ DEFAULT_COORDINATE_PRECISION = 15;
121
+ // Threshold for considering two points as identical.
122
+ POINT_COINCIDENCE_THRESHOLD = 1e-15;
123
+ // Buffer size for batch processing operations.
124
+ DEFAULT_BUFFER_SIZE = 16384;
125
+ // Maximum number of worker threads for parallel processing.
126
+ MAX_WORKER_THREADS = 2e308;
127
+ // Timeout for async operations (in milliseconds).
128
+ ASYNC_OPERATION_TIMEOUT = 2e308;
129
+ // Memory management thresholds.
130
+ GARBAGE_COLLECTION_THRESHOLD = 100000;
131
+ MEMORY_USAGE_WARNING_LIMIT = 0.95;
132
+ // Cache size limits.
133
+ GEOMETRY_CACHE_SIZE = 2e308;
134
+ INTERSECTION_CACHE_SIZE = 2e308;
135
+ // =============================================================================
136
+ // POLYGON ID MANAGEMENT
137
+ // Global counter for assigning unique IDs to polygon instances.
138
+ // =============================================================================
139
+ // Global polygon ID counter for unique polygon identification.
140
+ polygonID = 0;
141
+ // =============================================================================
142
+ // DEBUGGING AND DEVELOPMENT VARIABLES
143
+ // Variables useful for debugging and development.
144
+ // =============================================================================
145
+ // Enable detailed logging for debugging.
146
+ DEBUG_VERBOSE_LOGGING = false;
147
+ // Enable performance timing measurements.
148
+ DEBUG_PERFORMANCE_TIMING = false;
149
+ // Enable geometry validation checks.
150
+ DEBUG_GEOMETRY_VALIDATION = false;
151
+ // Enable intersection result verification.
152
+ DEBUG_INTERSECTION_VERIFICATION = false;
153
+ // Color codes for debug visualization.
154
+ DEBUG_COLOR_FRONT = 0x00ff00;
155
+ DEBUG_COLOR_BACK = 0xff0000;
156
+ DEBUG_COLOR_COPLANAR = 0x0000ff;
157
+ DEBUG_COLOR_SPANNING = 0xffff00;
158
+ // =============================================================================
159
+ // EXPORTS FOR TESTING
160
+ // Export all variables so they can be tested.
161
+ // =============================================================================
162
+ // Only export when in a testing environment (when module.exports exists).
163
+ if (typeof module !== 'undefined' && module.exports) {
164
+ // Mathematical Constants
165
+ // Polygon Classification Constants
166
+ // General Purpose Temporary Objects
167
+ // Winding Number Algorithm Variables
168
+ // Ray-Triangle Intersection Variables
169
+ // Matrix and Transformation Variables
170
+ // CSG and Polytree Operation Variables
171
+ // Performance and Optimization Variables
172
+ // Polygon ID Management
173
+ // Debugging and Development Variables
174
+ }
175
+ // Generated by CoffeeScript 2.7.0
176
+ // === BUFFER UTILITIES ===
177
+ // Simple LRU cache for intersection results to improve performance.
178
+ var calculateWindingNumberFromBuffer, checkMemoryUsage, clearGeometryCache, clearIntersectionCache, createIntersectionCacheKey, createVector2Buffer, createVector3Buffer, disposePolytreeResources, extractCoordinatesFromArray, geometryCache, geometryCacheKeys, handleIntersectingPolytrees, intersectionCache, intersectionCacheKeys, operationCounter, prepareTriangleBufferFromPolygons, roundPointCoordinates, sortRaycastIntersectionsByDistance, splitPolygonByPlane, splitPolygonVertexArray, testPolygonInsideUsingWindingNumber, testRayTriangleIntersection;
179
+ intersectionCache = new Map();
180
+ intersectionCacheKeys = [];
181
+ // Geometry calculation cache for expensive operations.
182
+ geometryCache = new Map();
183
+ geometryCacheKeys = [];
184
+ // Operation counter for garbage collection threshold tracking.
185
+ operationCounter = 0;
186
+ // Clear intersection cache when it exceeds size limit.
187
+ clearIntersectionCache = function() {
188
+ var i, key, keysToRemove, len, results;
189
+ if (INTERSECTION_CACHE_SIZE !== 2e308 && intersectionCache.size > INTERSECTION_CACHE_SIZE) {
190
+ // Remove oldest entries (simple FIFO approach).
191
+ keysToRemove = intersectionCacheKeys.splice(0, Math.floor(INTERSECTION_CACHE_SIZE / 2));
192
+ results = [];
193
+ for (i = 0, len = keysToRemove.length; i < len; i++) {
194
+ key = keysToRemove[i];
195
+ results.push(intersectionCache.delete(key));
196
+ }
197
+ return results;
198
+ }
199
+ };
200
+ // Clear geometry cache when it exceeds size limit.
201
+ clearGeometryCache = function() {
202
+ var i, key, keysToRemove, len, results;
203
+ if (GEOMETRY_CACHE_SIZE !== 2e308 && geometryCache.size > GEOMETRY_CACHE_SIZE) {
204
+ // Remove oldest entries (simple FIFO approach).
205
+ keysToRemove = geometryCacheKeys.splice(0, Math.floor(GEOMETRY_CACHE_SIZE / 2));
206
+ results = [];
207
+ for (i = 0, len = keysToRemove.length; i < len; i++) {
208
+ key = keysToRemove[i];
209
+ results.push(geometryCache.delete(key));
210
+ }
211
+ return results;
212
+ }
213
+ };
214
+ // Create cache key from two polygons.
215
+ createIntersectionCacheKey = function(polygonA, polygonB) {
216
+ return `${polygonA.id}_${polygonB.id}`;
217
+ };
218
+ // Memory management utilities.
219
+ checkMemoryUsage = function() {
220
+ var heapTotalMB, heapUsedMB, memoryUsage, usageRatio;
221
+ if (typeof process !== 'undefined' && process.memoryUsage) {
222
+ memoryUsage = process.memoryUsage();
223
+ heapUsedMB = memoryUsage.heapUsed / 1024 / 1024;
224
+ heapTotalMB = memoryUsage.heapTotal / 1024 / 1024;
225
+ usageRatio = memoryUsage.heapUsed / memoryUsage.heapTotal;
226
+ if (DEBUG_VERBOSE_LOGGING) {
227
+ console.log(`Memory usage: ${heapUsedMB.toFixed(2)}MB / ${heapTotalMB.toFixed(2)}MB (${(usageRatio * 100).toFixed(1)}%)`);
228
+ }
229
+ if (usageRatio > MEMORY_USAGE_WARNING_LIMIT) {
230
+ console.warn(`Memory usage is high: ${(usageRatio * 100).toFixed(1)}%`);
231
+ if (typeof (typeof global !== "undefined" && global !== null ? global.gc : void 0) === 'function') {
232
+ console.log("Triggering garbage collection...");
233
+ global.gc();
234
+ }
235
+ }
236
+ return usageRatio;
237
+ }
238
+ return 0;
239
+ };
240
+ // Create a 2D vector buffer with write functionality.
241
+ // This helper provides efficient storage and writing of 2D vector data.
242
+ // @param vectorCount - The number of vectors this buffer can hold (defaults to DEFAULT_BUFFER_SIZE).
243
+ // @return Buffer object with write method and Float32Array storage.
244
+ createVector2Buffer = function(vectorCount = DEFAULT_BUFFER_SIZE) {
245
+ return {
246
+ top: 0,
247
+ array: new Float32Array(vectorCount * 2),
248
+ write: function(vector) {
249
+ this.array[this.top++] = vector.x;
250
+ return this.array[this.top++] = vector.y;
251
+ }
252
+ };
253
+ };
254
+ // Create a 3D vector buffer with write functionality.
255
+ // This helper provides efficient storage and writing of 3D vector data.
256
+ // @param vectorCount - The number of vectors this buffer can hold (defaults to DEFAULT_BUFFER_SIZE).
257
+ // @return Buffer object with write method and Float32Array storage.
258
+ createVector3Buffer = function(vectorCount = DEFAULT_BUFFER_SIZE) {
259
+ return {
260
+ top: 0,
261
+ array: new Float32Array(vectorCount * 3),
262
+ write: function(vector) {
263
+ this.array[this.top++] = vector.x;
264
+ this.array[this.top++] = vector.y;
265
+ return this.array[this.top++] = vector.z;
266
+ }
267
+ };
268
+ };
269
+ // === POINT AND GEOMETRIC UTILITIES ===
270
+ // Sort raycast intersections by distance in ascending order.
271
+ // Used for ordering ray intersection results from closest to farthest.
272
+ // @param intersectionA - First intersection object with distance property.
273
+ // @param intersectionB - Second intersection object with distance property.
274
+ // @return Comparison result for sorting (-1, 0, or 1).
275
+ sortRaycastIntersectionsByDistance = function(intersectionA, intersectionB) {
276
+ return intersectionA.distance - intersectionB.distance;
277
+ };
278
+ // Round point coordinates to specified decimal precision.
279
+ // This helps eliminate floating point precision errors in geometric calculations.
280
+ // @param point - Vector3 point to round.
281
+ // @param decimalPlaces - Number of decimal places to round to (defaults to DEFAULT_COORDINATE_PRECISION).
282
+ // @return The same point object with rounded coordinates.
283
+ roundPointCoordinates = function(point, decimalPlaces = DEFAULT_COORDINATE_PRECISION) {
284
+ point.x = +point.x.toFixed(decimalPlaces);
285
+ point.y = +point.y.toFixed(decimalPlaces);
286
+ point.z = +point.z.toFixed(decimalPlaces);
287
+ return point;
288
+ };
289
+ // Extract XYZ coordinates from a flat array at the specified index.
290
+ // Helper for working with triangle buffer data in winding number calculations.
291
+ // @param coordinatesArray - Float32Array containing XYZ coordinates.
292
+ // @param startIndex - Starting index in the array.
293
+ // @return Object with x, y, z properties.
294
+ extractCoordinatesFromArray = function(coordinatesArray, startIndex) {
295
+ return {
296
+ x: coordinatesArray[startIndex],
297
+ y: coordinatesArray[startIndex + 1],
298
+ z: coordinatesArray[startIndex + 2]
299
+ };
300
+ };
301
+ // === POLYGON OPERATIONS ===
302
+ // Split a polygon by a plane into front and back fragments.
303
+ // This is a core CSG operation that classifies polygon parts relative to a plane.
304
+ // The algorithm handles coplanar, front, back, and spanning polygon cases.
305
+ // @param polygon - The polygon to split.
306
+ // @param plane - The cutting plane with normal and distance properties.
307
+ // @param result - Array to store results (optional).
308
+ // @return Array of polygon fragments with classification types.
309
+ splitPolygonByPlane = function(polygon, plane, result = []) {
310
+ var backPolygonFragments, backVertices, currentVertex, currentVertexType, distanceToPlane, fragmentIndex, frontPolygonFragments, frontVertices, i, interpolatedVertex, intersectionParameter, j, k, l, nextVertex, nextVertexIndex, nextVertexType, polygonType, ref, ref1, ref2, ref3, returnPolygon, vertexIndex, vertexType, vertexTypes;
311
+ returnPolygon = {
312
+ polygon: polygon,
313
+ type: "undecided"
314
+ };
315
+ polygonType = 0;
316
+ vertexTypes = [];
317
+ // Classify each vertex relative to the plane.
318
+ for (vertexIndex = i = 0, ref = polygon.vertices.length; (0 <= ref ? i < ref : i > ref); vertexIndex = 0 <= ref ? ++i : --i) {
319
+ distanceToPlane = plane.normal.dot(polygon.vertices[vertexIndex].pos) - plane.distanceFromOrigin;
320
+ vertexType = distanceToPlane < -GEOMETRIC_EPSILON ? POLYGON_BACK : distanceToPlane > GEOMETRIC_EPSILON ? POLYGON_FRONT : POLYGON_COPLANAR;
321
+ polygonType |= vertexType;
322
+ vertexTypes.push(vertexType);
323
+ }
324
+ // Handle polygon classification based on vertex types.
325
+ switch (polygonType) {
326
+ case POLYGON_COPLANAR:
327
+ returnPolygon.type = plane.normal.dot(polygon.plane.normal) > 0 ? "coplanar-front" : "coplanar-back";
328
+ if (DEBUG_VERBOSE_LOGGING) {
329
+ console.log("Polygon classified as COPLANAR, debug color:", DEBUG_COLOR_COPLANAR.toString(16));
330
+ }
331
+ result.push(returnPolygon);
332
+ break;
333
+ case POLYGON_FRONT:
334
+ returnPolygon.type = "front";
335
+ result.push(returnPolygon);
336
+ if (DEBUG_VERBOSE_LOGGING) {
337
+ console.log("Polygon classified as FRONT, debug color:", DEBUG_COLOR_FRONT.toString(16));
338
+ }
339
+ break;
340
+ case POLYGON_BACK:
341
+ returnPolygon.type = "back";
342
+ result.push(returnPolygon);
343
+ if (DEBUG_VERBOSE_LOGGING) {
344
+ console.log("Polygon classified as BACK, debug color:", DEBUG_COLOR_BACK.toString(16));
345
+ }
346
+ break;
347
+ case POLYGON_SPANNING:
348
+ if (DEBUG_VERBOSE_LOGGING) {
349
+ console.log("Polygon classified as SPANNING, debug color:", DEBUG_COLOR_SPANNING.toString(16));
350
+ }
351
+ frontVertices = [];
352
+ backVertices = [];
353
+ // Process each edge to build front and back vertex lists.
354
+ for (vertexIndex = j = 0, ref1 = polygon.vertices.length; (0 <= ref1 ? j < ref1 : j > ref1); vertexIndex = 0 <= ref1 ? ++j : --j) {
355
+ nextVertexIndex = (vertexIndex + 1) % polygon.vertices.length;
356
+ currentVertexType = vertexTypes[vertexIndex];
357
+ nextVertexType = vertexTypes[nextVertexIndex];
358
+ currentVertex = polygon.vertices[vertexIndex];
359
+ nextVertex = polygon.vertices[nextVertexIndex];
360
+ // Add vertex to front list if not behind plane.
361
+ if (currentVertexType !== POLYGON_BACK) {
362
+ frontVertices.push(currentVertex);
363
+ }
364
+ // Add vertex to back list if not in front of plane.
365
+ if (currentVertexType !== POLYGON_FRONT) {
366
+ backVertices.push(currentVertexType !== POLYGON_BACK ? currentVertex.clone() : currentVertex);
367
+ }
368
+ // Handle edge intersections with the plane.
369
+ if ((currentVertexType | nextVertexType) === POLYGON_SPANNING) {
370
+ intersectionParameter = (plane.distanceFromOrigin - plane.normal.dot(currentVertex.pos)) / plane.normal.dot(temporaryTriangleVertex.copy(nextVertex.pos).sub(currentVertex.pos));
371
+ interpolatedVertex = currentVertex.interpolate(nextVertex, intersectionParameter);
372
+ frontVertices.push(interpolatedVertex);
373
+ backVertices.push(interpolatedVertex.clone());
374
+ }
375
+ }
376
+ // Create front polygon fragments if we have enough vertices.
377
+ if (frontVertices.length >= 3) {
378
+ if (frontVertices.length > 3) {
379
+ frontPolygonFragments = splitPolygonVertexArray(frontVertices);
380
+ for (fragmentIndex = k = 0, ref2 = frontPolygonFragments.length; (0 <= ref2 ? k < ref2 : k > ref2); fragmentIndex = 0 <= ref2 ? ++k : --k) {
381
+ result.push({
382
+ polygon: new Polygon(frontPolygonFragments[fragmentIndex], polygon.shared),
383
+ type: "front"
384
+ });
385
+ }
386
+ } else {
387
+ result.push({
388
+ polygon: new Polygon(frontVertices, polygon.shared),
389
+ type: "front"
390
+ });
391
+ }
392
+ }
393
+ // Create back polygon fragments if we have enough vertices.
394
+ if (backVertices.length >= 3) {
395
+ if (backVertices.length > 3) {
396
+ backPolygonFragments = splitPolygonVertexArray(backVertices);
397
+ for (fragmentIndex = l = 0, ref3 = backPolygonFragments.length; (0 <= ref3 ? l < ref3 : l > ref3); fragmentIndex = 0 <= ref3 ? ++l : --l) {
398
+ result.push({
399
+ polygon: new Polygon(backPolygonFragments[fragmentIndex], polygon.shared),
400
+ type: "back"
401
+ });
402
+ }
403
+ } else {
404
+ result.push({
405
+ polygon: new Polygon(backVertices, polygon.shared),
406
+ type: "back"
407
+ });
408
+ }
409
+ }
410
+ }
411
+ // If no fragments were created, return the original polygon.
412
+ if (result.length === 0) {
413
+ result.push(returnPolygon);
414
+ }
415
+ return result;
416
+ };
417
+ // Split a polygon vertex array into triangulated fragments.
418
+ // This handles polygons with more than 3 vertices by creating triangle fans.
419
+ // @param vertexArray - Array of vertices to triangulate.
420
+ // @return Array of vertex arrays, each representing a triangle.
421
+ splitPolygonVertexArray = function(vertexArray) {
422
+ var i, ref, triangleFragments, triangleIndex, triangleVertices;
423
+ triangleFragments = [];
424
+ // Handle polygons with more than 4 vertices using fan triangulation.
425
+ if (vertexArray.length > 4) {
426
+ console.warn("[splitPolygonVertexArray] vertexArray.length > 4", vertexArray.length);
427
+ for (triangleIndex = i = 3, ref = vertexArray.length; (3 <= ref ? i <= ref : i >= ref); triangleIndex = 3 <= ref ? ++i : --i) {
428
+ triangleVertices = [];
429
+ triangleVertices.push(vertexArray[0].clone());
430
+ triangleVertices.push(vertexArray[triangleIndex - 2].clone());
431
+ triangleVertices.push(vertexArray[triangleIndex - 1].clone());
432
+ triangleFragments.push(triangleVertices);
433
+ }
434
+ } else {
435
+ // For quadrilaterals, choose the best diagonal based on distance.
436
+ if (vertexArray[0].pos.distanceTo(vertexArray[2].pos) <= vertexArray[1].pos.distanceTo(vertexArray[3].pos)) {
437
+ triangleFragments.push([vertexArray[0].clone(), vertexArray[1].clone(), vertexArray[2].clone()], [vertexArray[0].clone(), vertexArray[2].clone(), vertexArray[3].clone()]);
438
+ } else {
439
+ triangleFragments.push([vertexArray[0].clone(), vertexArray[1].clone(), vertexArray[3].clone()], [vertexArray[1].clone(), vertexArray[2].clone(), vertexArray[3].clone()]);
440
+ }
441
+ }
442
+ return triangleFragments;
443
+ };
444
+ // === WINDING NUMBER CALCULATIONS ===
445
+ // Calculate the winding number for a point relative to triangle mesh data.
446
+ // The winding number determines how many times the mesh winds around the test point.
447
+ // This is used for robust inside/outside testing of complex 3D geometry.
448
+ // @param triangleDataArray - Float32Array containing triangle vertex coordinates.
449
+ // @param testPoint - Point to test for winding number.
450
+ // @return Integer winding number (0 = outside, non-zero = inside).
451
+ calculateWindingNumberFromBuffer = function(triangleDataArray, testPoint) {
452
+ var i, ref, solidAngle, triangleStartIndex, vectorLengthA, vectorLengthB, vectorLengthC, windingNumber;
453
+ windingNumber = 0;
454
+ // Process each triangle in the buffer (9 floats per triangle: 3 vertices × 3 coordinates).
455
+ for (triangleStartIndex = i = 0, ref = triangleDataArray.length; i < ref; triangleStartIndex = i += 9) {
456
+ windingNumberVector1.subVectors(extractCoordinatesFromArray(triangleDataArray, triangleStartIndex), testPoint);
457
+ windingNumberVector2.subVectors(extractCoordinatesFromArray(triangleDataArray, triangleStartIndex + 3), testPoint);
458
+ windingNumberVector3.subVectors(extractCoordinatesFromArray(triangleDataArray, triangleStartIndex + 6), testPoint);
459
+ vectorLengthA = windingNumberVector1.length();
460
+ vectorLengthB = windingNumberVector2.length();
461
+ vectorLengthC = windingNumberVector3.length();
462
+ // Skip degenerate triangles that are too small to contribute meaningfully.
463
+ if (vectorLengthA < POINT_COINCIDENCE_THRESHOLD || vectorLengthB < POINT_COINCIDENCE_THRESHOLD || vectorLengthC < POINT_COINCIDENCE_THRESHOLD) {
464
+ continue;
465
+ }
466
+ // Calculate the solid angle using the determinant formula.
467
+ windingNumberMatrix3.set(windingNumberVector1.x, windingNumberVector1.y, windingNumberVector1.z, windingNumberVector2.x, windingNumberVector2.y, windingNumberVector2.z, windingNumberVector3.x, windingNumberVector3.y, windingNumberVector3.z);
468
+ solidAngle = 2 * Math.atan2(windingNumberMatrix3.determinant(), vectorLengthA * vectorLengthB * vectorLengthC + windingNumberVector1.dot(windingNumberVector2) * vectorLengthC + windingNumberVector2.dot(windingNumberVector3) * vectorLengthA + windingNumberVector3.dot(windingNumberVector1) * vectorLengthB);
469
+ windingNumber += solidAngle;
470
+ }
471
+ // Round to nearest integer to get the final winding number.
472
+ windingNumber = Math.round(windingNumber / WINDING_NUMBER_FULL_ROTATION);
473
+ return windingNumber;
474
+ };
475
+ // Test if a polygon is inside a mesh using winding number algorithm.
476
+ // This provides robust inside/outside testing that handles complex cases.
477
+ // For coplanar polygons, epsilon offsets are tested to resolve ambiguity.
478
+ // @param triangleDataArray - Float32Array containing mesh triangle data.
479
+ // @param testPoint - Point to test for inside/outside status.
480
+ // @param isCoplanar - Whether the polygon is coplanar with mesh surfaces.
481
+ // @return Boolean indicating if the polygon is inside the mesh.
482
+ testPolygonInsideUsingWindingNumber = function(triangleDataArray, testPoint, isCoplanar) {
483
+ var i, isInside, maxOffsetTests, offsetIndex, ref, windingNumber;
484
+ isInside = false;
485
+ windingNumberTestPoint.copy(testPoint);
486
+ windingNumber = calculateWindingNumberFromBuffer(triangleDataArray, windingNumberTestPoint);
487
+ // For non-coplanar cases, winding number directly indicates inside/outside.
488
+ if (windingNumber === 0) {
489
+ if (isCoplanar) {
490
+ // For coplanar cases, test multiple epsilon-offset points.
491
+ // Limit iterations to prevent infinite loops in degenerate cases.
492
+ maxOffsetTests = Math.min(windingNumberEpsilonOffsetsCount, MAX_REFINEMENT_ITERATIONS);
493
+ for (offsetIndex = i = 0, ref = maxOffsetTests; (0 <= ref ? i < ref : i > ref); offsetIndex = 0 <= ref ? ++i : --i) {
494
+ windingNumberTestPoint.copy(testPoint).add(windingNumberEpsilonOffsets[offsetIndex]);
495
+ windingNumber = calculateWindingNumberFromBuffer(triangleDataArray, windingNumberTestPoint);
496
+ if (windingNumber !== 0) {
497
+ isInside = true;
498
+ break;
499
+ }
500
+ }
501
+ }
502
+ } else {
503
+ isInside = true;
504
+ }
505
+ return isInside;
506
+ };
507
+ // Prepare a triangle buffer from polygon array for winding number calculations.
508
+ // This converts polygon data into a flat Float32Array for efficient processing.
509
+ // @param polygonArray - Array of polygons to convert.
510
+ // @return Float32Array containing triangle vertex coordinates.
511
+ prepareTriangleBufferFromPolygons = function(polygonArray) {
512
+ var bufferIndex, coordinateArray, i, polygonIndex, ref, triangle, triangleCount;
513
+ triangleCount = polygonArray.length;
514
+ coordinateArray = new Float32Array(triangleCount * 3 * 3); // 3 vertices × 3 coordinates per triangle
515
+ bufferIndex = 0;
516
+ for (polygonIndex = i = 0, ref = triangleCount; (0 <= ref ? i < ref : i > ref); polygonIndex = 0 <= ref ? ++i : --i) {
517
+ triangle = polygonArray[polygonIndex].triangle;
518
+ // Store first vertex coordinates.
519
+ coordinateArray[bufferIndex++] = triangle.a.x;
520
+ coordinateArray[bufferIndex++] = triangle.a.y;
521
+ coordinateArray[bufferIndex++] = triangle.a.z;
522
+ // Store second vertex coordinates.
523
+ coordinateArray[bufferIndex++] = triangle.b.x;
524
+ coordinateArray[bufferIndex++] = triangle.b.y;
525
+ coordinateArray[bufferIndex++] = triangle.b.z;
526
+ // Store third vertex coordinates.
527
+ coordinateArray[bufferIndex++] = triangle.c.x;
528
+ coordinateArray[bufferIndex++] = triangle.c.y;
529
+ coordinateArray[bufferIndex++] = triangle.c.z;
530
+ }
531
+ return coordinateArray;
532
+ };
533
+ // === RAY-TRIANGLE INTERSECTION ===
534
+ // Test ray-triangle intersection using the Möller–Trumbore algorithm.
535
+ // This is a fast, efficient algorithm for ray-triangle intersection testing.
536
+ // Returns the intersection point if found, or null if no intersection exists.
537
+ // @param ray - Ray object with origin and direction properties.
538
+ // @param triangle - Triangle object with a, b, c vertex properties.
539
+ // @param targetVector - Optional Vector3 to store the intersection point.
540
+ // @return Vector3 intersection point or null if no intersection.
541
+ testRayTriangleIntersection = function(ray, triangle, targetVector = new Vector3()) {
542
+ var determinant, firstBarycentricCoordinate, intersectionDistance, inverseDeterminant, result, secondBarycentricCoordinate;
543
+ if (DEBUG_VERBOSE_LOGGING) {
544
+ console.log("Testing ray-triangle intersection with ray origin:", ray.origin, "direction:", ray.direction);
545
+ }
546
+ // Calculate triangle edge vectors.
547
+ rayTriangleEdge1.subVectors(triangle.b, triangle.a);
548
+ rayTriangleEdge2.subVectors(triangle.c, triangle.a);
549
+ // Calculate determinant to check if ray is parallel to triangle.
550
+ rayTriangleHVector.crossVectors(ray.direction, rayTriangleEdge2);
551
+ determinant = rayTriangleEdge1.dot(rayTriangleHVector);
552
+ // If determinant is near zero, ray is parallel to triangle plane.
553
+ if (determinant > -RAY_INTERSECTION_EPSILON && determinant < RAY_INTERSECTION_EPSILON) {
554
+ return null;
555
+ }
556
+ inverseDeterminant = 1 / determinant;
557
+ rayTriangleSVector.subVectors(ray.origin, triangle.a);
558
+ firstBarycentricCoordinate = inverseDeterminant * rayTriangleSVector.dot(rayTriangleHVector);
559
+ // Check if intersection point is outside triangle (first barycentric test).
560
+ if (firstBarycentricCoordinate < 0 || firstBarycentricCoordinate > 1) {
561
+ return null;
562
+ }
563
+ rayTriangleQVector.crossVectors(rayTriangleSVector, rayTriangleEdge1);
564
+ secondBarycentricCoordinate = inverseDeterminant * ray.direction.dot(rayTriangleQVector);
565
+ // Check if intersection point is outside triangle (second barycentric test).
566
+ if (secondBarycentricCoordinate < 0 || firstBarycentricCoordinate + secondBarycentricCoordinate > 1) {
567
+ return null;
568
+ }
569
+ // Calculate intersection distance along ray.
570
+ intersectionDistance = inverseDeterminant * rayTriangleEdge2.dot(rayTriangleQVector);
571
+ // Check if intersection is in front of ray origin.
572
+ if (intersectionDistance > RAY_INTERSECTION_EPSILON) {
573
+ result = targetVector.copy(ray.direction).multiplyScalar(intersectionDistance).add(ray.origin);
574
+ if (DEBUG_INTERSECTION_VERIFICATION) {
575
+ console.log("Ray-triangle intersection verified:", {
576
+ distance: intersectionDistance,
577
+ point: result,
578
+ triangle: {
579
+ a: triangle.a,
580
+ b: triangle.b,
581
+ c: triangle.c
582
+ }
583
+ });
584
+ }
585
+ return result;
586
+ }
587
+ return null;
588
+ };
589
+ // === POLYTREE MANAGEMENT ===
590
+ // Handle intersection processing between two polytrees.
591
+ // This coordinates the CSG intersection algorithm by preparing triangle buffers
592
+ // and calling intersection handling methods on the polytree instances.
593
+ // @param polytreeA - First polytree for intersection processing.
594
+ // @param polytreeB - Second polytree for intersection processing.
595
+ // @param processBothDirections - Whether to process intersections in both directions.
596
+ handleIntersectingPolytrees = function(polytreeA, polytreeB, processBothDirections = true) {
597
+ var endTime, polytreeABuffer, polytreeBBuffer, startTime;
598
+ startTime = DEBUG_PERFORMANCE_TIMING ? performance.now() : 0;
599
+ operationCounter++; // Increment operation counter and check for GC threshold.
600
+ if (operationCounter >= GARBAGE_COLLECTION_THRESHOLD) {
601
+ operationCounter = 0;
602
+ clearIntersectionCache();
603
+ clearGeometryCache();
604
+ checkMemoryUsage();
605
+ }
606
+ polytreeABuffer = void 0;
607
+ polytreeBBuffer = void 0;
608
+ // Prepare triangle buffers if winding number algorithm is enabled.
609
+ if (Polytree.useWindingNumber === true) {
610
+ if (processBothDirections) {
611
+ polytreeABuffer = prepareTriangleBufferFromPolygons(polytreeA.getPolygons());
612
+ }
613
+ polytreeBBuffer = prepareTriangleBufferFromPolygons(polytreeB.getPolygons());
614
+ }
615
+ // Process intersections from A's perspective.
616
+ polytreeA.handleIntersectingPolygons(polytreeB, polytreeBBuffer);
617
+ // Process intersections from B's perspective if requested.
618
+ if (processBothDirections) {
619
+ polytreeB.handleIntersectingPolygons(polytreeA, polytreeABuffer);
620
+ }
621
+ // Clean up buffers to free memory.
622
+ if (polytreeABuffer !== void 0) {
623
+ polytreeABuffer = void 0;
624
+ }
625
+ if (polytreeBBuffer !== void 0) {
626
+ polytreeBBuffer = void 0;
627
+ }
628
+ if (DEBUG_PERFORMANCE_TIMING) {
629
+ endTime = performance.now();
630
+ polytreeBBuffer = void 0;
631
+ return console.log(`handleIntersectingPolytrees took ${endTime - startTime} milliseconds`);
632
+ }
633
+ };
634
+ // Dispose of polytree resources to prevent memory leaks.
635
+ // This utility safely calls the delete method on polytree instances
636
+ // if the disposal feature is enabled in the Polytree configuration.
637
+ // @param polytreeInstances - Variable number of polytree instances to dispose.
638
+ disposePolytreeResources = function(...polytreeInstances) {
639
+ if (Polytree.disposePolytree) {
640
+ return polytreeInstances.forEach(function(polytreeInstance) {
641
+ return polytreeInstance.delete();
642
+ });
643
+ }
644
+ };
645
+ // Generated by CoffeeScript 2.7.0
646
+ // Main operation handler for processing CSG operations on Polytree objects.
647
+ // This function handles both synchronous and asynchronous CSG operations (unite, subtract, intersect)
648
+ // and manages the conversion between meshes and polytrees as needed.
649
+ // @param operationObject - Object containing the operation definition with properties:
650
+ // - op: Operation type ('unite', 'subtract', 'intersect')
651
+ // - objA: First operand (mesh, polytree, or nested operation)
652
+ // - objB: Second operand (mesh, polytree, or nested operation)
653
+ // - material: Optional material for final mesh output
654
+ // @param returnPolytrees - Whether to return polytree objects instead of meshes.
655
+ // @param buildTargetPolytree - Whether to build the target polytree structure.
656
+ // @param options - Configuration options including objCounter for unique IDs.
657
+ // @param firstRun - Whether this is the top-level operation call.
658
+ // @param async - Whether to execute asynchronously using promises.
659
+ // @return Result mesh, polytree, or operation tree depending on parameters.
660
+ var handleObjectForOperation, operationHandler;
661
+ operationHandler = function(operationObject, returnPolytrees = false, buildTargetPolytree = true, options = {
662
+ objCounter: 0
663
+ }, firstRun = true, async = true) {
664
+ var allPolygons, defaultMaterial, finalMesh, firstOperand, materialForMesh, ref, resultPolytree, secondOperand;
665
+ if (async) {
666
+ return new Promise(function(resolve, reject) {
667
+ var asyncError, firstOperand, materialForMesh, operandPromise, operandPromises, originalMaterialA, originalMaterialB, ref, ref1, resultPolytree, secondOperand;
668
+ try {
669
+ // Initialize variables for the two operands and result.
670
+ firstOperand = void 0;
671
+ secondOperand = void 0;
672
+ resultPolytree = void 0;
673
+ materialForMesh = void 0;
674
+ // Capture original material for default use before processing operands.
675
+ originalMaterialA = (ref = operationObject.objA) != null ? ref.material : void 0;
676
+ originalMaterialB = (ref1 = operationObject.objB) != null ? ref1.material : void 0;
677
+ if (operationObject.material) {
678
+ materialForMesh = operationObject.material;
679
+ }
680
+ // Process operands in parallel for async operations.
681
+ operandPromises = [];
682
+ if (operationObject.objA) {
683
+ operandPromise = handleObjectForOperation(operationObject.objA, returnPolytrees, buildTargetPolytree, options, 0, async);
684
+ operandPromises.push(operandPromise);
685
+ }
686
+ if (operationObject.objB) {
687
+ operandPromise = handleObjectForOperation(operationObject.objB, returnPolytrees, buildTargetPolytree, options, 1, async);
688
+ operandPromises.push(operandPromise);
689
+ }
690
+ return Promise.allSettled(operandPromises).then(function(promiseResults) {
691
+ var operationPromise;
692
+ // Extract operands from promise results.
693
+ promiseResults.forEach(function(promiseResult) {
694
+ if (promiseResult.status === "fulfilled") {
695
+ if (promiseResult.value.objIndex === 0) {
696
+ return firstOperand = promiseResult.value;
697
+ } else if (promiseResult.value.objIndex === 1) {
698
+ return secondOperand = promiseResult.value;
699
+ }
700
+ }
701
+ });
702
+ // Handle polytree return mode by updating operation object references.
703
+ if (returnPolytrees === true) {
704
+ // Extract polytrees from wrapper objects when in polytree return mode.
705
+ if (firstOperand) {
706
+ operationObject.objA = firstOperand.original;
707
+ firstOperand = firstOperand.result;
708
+ }
709
+ if (secondOperand) {
710
+ operationObject.objB = secondOperand.original;
711
+ secondOperand = secondOperand.result;
712
+ }
713
+ }
714
+ // Execute the appropriate CSG operation based on operation type.
715
+ operationPromise = void 0;
716
+ switch (operationObject.op) {
717
+ case 'unite':
718
+ if (firstOperand && secondOperand) {
719
+ operationPromise = Polytree.async.unite(firstOperand, secondOperand, buildTargetPolytree);
720
+ } else {
721
+ // Handle missing operands gracefully.
722
+ operationPromise = Promise.resolve(firstOperand || secondOperand || new Polytree());
723
+ }
724
+ break;
725
+ case 'subtract':
726
+ if (firstOperand && secondOperand) {
727
+ operationPromise = Polytree.async.subtract(firstOperand, secondOperand, buildTargetPolytree);
728
+ } else {
729
+ // For subtract, return first operand or empty polytree.
730
+ operationPromise = Promise.resolve(firstOperand || new Polytree());
731
+ }
732
+ break;
733
+ case 'intersect':
734
+ if (firstOperand && secondOperand) {
735
+ operationPromise = Polytree.async.intersect(firstOperand, secondOperand, buildTargetPolytree);
736
+ } else {
737
+ // For intersect, missing operand means no result.
738
+ operationPromise = Promise.resolve(new Polytree());
739
+ }
740
+ break;
741
+ default:
742
+ // Handle invalid operation types gracefully.
743
+ operationPromise = Promise.resolve(new Polytree());
744
+ }
745
+ // Handle the operation result and determine final output format.
746
+ return operationPromise.then(function(resultPolytree) {
747
+ var allPolygons, defaultMaterial, finalMesh;
748
+ // Ensure result polytree has proper bounding box for subsequent operations.
749
+ if (resultPolytree && !resultPolytree.box && resultPolytree.bounds) {
750
+ resultPolytree.buildTree();
751
+ }
752
+ // Convert to mesh for first run operations unless returning polytrees.
753
+ if (firstRun && !returnPolytrees) {
754
+ // Skip mesh conversion if result polytree has no valid polygons.
755
+ allPolygons = resultPolytree.getPolygons();
756
+ if (!resultPolytree || allPolygons.length === 0) {
757
+ resolve(void 0);
758
+ return;
759
+ }
760
+ if (materialForMesh) {
761
+ finalMesh = Polytree.toMesh(resultPolytree, materialForMesh);
762
+ } else {
763
+ // Use default material from original operands if no material specified.
764
+ defaultMaterial = void 0;
765
+ if (originalMaterialA) {
766
+ defaultMaterial = Array.isArray(originalMaterialA) ? originalMaterialA[0] : originalMaterialA;
767
+ defaultMaterial = defaultMaterial.clone();
768
+ } else if (originalMaterialB) {
769
+ defaultMaterial = Array.isArray(originalMaterialB) ? originalMaterialB[0] : originalMaterialB;
770
+ defaultMaterial = defaultMaterial.clone();
771
+ } else {
772
+ // No material available - resolve with undefined.
773
+ resolve(void 0);
774
+ return;
775
+ }
776
+ finalMesh = Polytree.toMesh(resultPolytree, defaultMaterial);
777
+ }
778
+ disposePolytreeResources(resultPolytree);
779
+ resolve(finalMesh);
780
+ } else if (firstRun && returnPolytrees) {
781
+ if (materialForMesh) {
782
+ finalMesh = Polytree.toMesh(resultPolytree, materialForMesh);
783
+ disposePolytreeResources(resultPolytree);
784
+ resolve({
785
+ result: finalMesh,
786
+ operationTree: operationObject
787
+ });
788
+ } else {
789
+ resolve({
790
+ result: resultPolytree,
791
+ operationTree: operationObject
792
+ });
793
+ }
794
+ } else {
795
+ resolve(resultPolytree);
796
+ }
797
+ // Clean up intermediate polytrees unless returning them.
798
+ if (!returnPolytrees) {
799
+ if (firstOperand || secondOperand) {
800
+ return disposePolytreeResources(firstOperand, secondOperand);
801
+ }
802
+ }
803
+ }).catch(function(operationError) {
804
+ return reject(operationError);
805
+ });
806
+ });
807
+ } catch (error) {
808
+ asyncError = error;
809
+ return reject(asyncError);
810
+ }
811
+ });
812
+ } else {
813
+ // Synchronous operation handling.
814
+ firstOperand = void 0;
815
+ secondOperand = void 0;
816
+ resultPolytree = void 0;
817
+ materialForMesh = void 0;
818
+ if (operationObject.material) {
819
+ materialForMesh = operationObject.material;
820
+ }
821
+ // Process first operand.
822
+ if (operationObject.objA) {
823
+ firstOperand = handleObjectForOperation(operationObject.objA, returnPolytrees, buildTargetPolytree, options, void 0, async);
824
+ if (returnPolytrees === true) {
825
+ operationObject.objA = firstOperand.original;
826
+ firstOperand = firstOperand.result;
827
+ }
828
+ }
829
+ // Process second operand.
830
+ if (operationObject.objB) {
831
+ secondOperand = handleObjectForOperation(operationObject.objB, returnPolytrees, buildTargetPolytree, options, void 0, async);
832
+ if (returnPolytrees === true) {
833
+ operationObject.objB = secondOperand.original;
834
+ secondOperand = secondOperand.result;
835
+ }
836
+ }
837
+ // Execute the appropriate CSG operation.
838
+ switch (operationObject.op) {
839
+ case 'unite':
840
+ if (firstOperand && secondOperand) {
841
+ resultPolytree = Polytree.unite(firstOperand, secondOperand, false);
842
+ } else {
843
+ // Handle missing operands - return the available operand or empty polytree.
844
+ resultPolytree = firstOperand || secondOperand || new Polytree();
845
+ }
846
+ break;
847
+ case 'subtract':
848
+ if (firstOperand && secondOperand) {
849
+ resultPolytree = Polytree.subtract(firstOperand, secondOperand, false);
850
+ } else {
851
+ // For subtract, if missing second operand, return first; if missing first, return empty.
852
+ resultPolytree = firstOperand || new Polytree();
853
+ }
854
+ break;
855
+ case 'intersect':
856
+ if (firstOperand && secondOperand) {
857
+ resultPolytree = Polytree.intersect(firstOperand, secondOperand, false);
858
+ } else {
859
+ // For intersect, missing either operand means no intersection - return empty polytree.
860
+ resultPolytree = new Polytree();
861
+ }
862
+ break;
863
+ default:
864
+ // Handle invalid operation types gracefully - return empty polytree.
865
+ resultPolytree = new Polytree();
866
+ }
867
+ // Ensure result polytree has proper bounding box for subsequent operations.
868
+ if (resultPolytree && !resultPolytree.box && resultPolytree.bounds) {
869
+ resultPolytree.buildTree();
870
+ }
871
+ // Clean up intermediate polytrees unless returning them.
872
+ if (!returnPolytrees) {
873
+ if (firstOperand || secondOperand) {
874
+ disposePolytreeResources(firstOperand, secondOperand);
875
+ }
876
+ }
877
+ // Handle final output format for synchronous operations.
878
+ if (firstRun && !returnPolytrees) {
879
+ // Skip mesh conversion if result polytree has no valid polygons.
880
+ allPolygons = resultPolytree.getPolygons();
881
+ if (!resultPolytree || allPolygons.length === 0) {
882
+ return void 0;
883
+ }
884
+ // Convert polytree result to mesh for top-level operations.
885
+ if (materialForMesh) {
886
+ finalMesh = Polytree.toMesh(resultPolytree, materialForMesh);
887
+ } else {
888
+ // Use default material from first operand if no material specified.
889
+ defaultMaterial = void 0;
890
+ if ((ref = operationObject.objA) != null ? ref.material : void 0) {
891
+ defaultMaterial = Array.isArray(operationObject.objA.material) ? operationObject.objA.material[0] : operationObject.objA.material;
892
+ defaultMaterial = defaultMaterial.clone();
893
+ } else {
894
+ return void 0; // No material available - return undefined instead of creating empty mesh.
895
+ }
896
+ finalMesh = Polytree.toMesh(resultPolytree, defaultMaterial);
897
+ }
898
+ disposePolytreeResources(resultPolytree);
899
+ return finalMesh;
900
+ }
901
+ if (firstRun && returnPolytrees) {
902
+ return {
903
+ result: resultPolytree,
904
+ operationTree: operationObject
905
+ };
906
+ }
907
+ return resultPolytree;
908
+ }
909
+ };
910
+ // Handle individual object processing for CSG operations.
911
+ // Converts meshes to polytrees and handles nested operations recursively.
912
+ // @param inputObject - Object to process (mesh, polytree, or nested operation).
913
+ // @param returnPolytrees - Whether to return polytree objects instead of meshes.
914
+ // @param buildTargetPolytree - Whether to build the target polytree structure.
915
+ // @param options - Configuration options including objCounter for unique IDs.
916
+ // @param objectIndex - Index identifier for tracking operand position (0 or 1).
917
+ // @param async - Whether to execute asynchronously using promises.
918
+ // @return Processed polytree object or promise resolving to one.
919
+ handleObjectForOperation = function(inputObject, returnPolytrees, buildTargetPolytree, options, objectIndex, async = true) {
920
+ var processedObject;
921
+ if (async) {
922
+ return new Promise(function(resolve, reject) {
923
+ var processedObject, processingError;
924
+ try {
925
+ processedObject = void 0;
926
+ // Convert Three.js mesh to polytree.
927
+ if (inputObject.isMesh) {
928
+ processedObject = Polytree.fromMesh(inputObject, options.objCounter++);
929
+ if (returnPolytrees) {
930
+ processedObject = {
931
+ result: processedObject,
932
+ original: processedObject.clone()
933
+ };
934
+ }
935
+ processedObject.objIndex = objectIndex;
936
+ return resolve(processedObject);
937
+ // Handle existing polytree objects.
938
+ } else if (inputObject.isPolytree) {
939
+ processedObject = inputObject;
940
+ if (returnPolytrees) {
941
+ processedObject = {
942
+ result: inputObject,
943
+ original: inputObject.clone()
944
+ };
945
+ }
946
+ processedObject.objIndex = objectIndex;
947
+ return resolve(processedObject);
948
+ // Handle nested operations recursively.
949
+ } else if (inputObject.op) {
950
+ return Polytree.operation(inputObject, returnPolytrees, buildTargetPolytree, options, false, async).then(function(nestedResult) {
951
+ if (returnPolytrees) {
952
+ nestedResult = {
953
+ result: nestedResult,
954
+ original: inputObject
955
+ };
956
+ }
957
+ nestedResult.objIndex = objectIndex;
958
+ return resolve(nestedResult);
959
+ });
960
+ }
961
+ } catch (error) {
962
+ processingError = error;
963
+ return reject(processingError);
964
+ }
965
+ });
966
+ } else {
967
+ // Synchronous object processing.
968
+ processedObject = void 0;
969
+ // Convert Three.js mesh to polytree.
970
+ if (inputObject.isMesh) {
971
+ processedObject = Polytree.fromMesh(inputObject, options.objCounter++);
972
+ if (returnPolytrees) {
973
+ processedObject = {
974
+ result: processedObject,
975
+ original: processedObject.clone()
976
+ };
977
+ }
978
+ // Handle existing polytree objects.
979
+ } else if (inputObject.isPolytree) {
980
+ processedObject = inputObject;
981
+ if (returnPolytrees) {
982
+ processedObject = {
983
+ result: inputObject,
984
+ original: inputObject.clone()
985
+ };
986
+ }
987
+ // Handle nested operations recursively.
988
+ } else if (inputObject.op) {
989
+ processedObject = Polytree.operation(inputObject, returnPolytrees, buildTargetPolytree, options, false, async);
990
+ if (returnPolytrees) {
991
+ processedObject = {
992
+ result: processedObject,
993
+ original: inputObject
994
+ };
995
+ }
996
+ }
997
+ return processedObject;
998
+ }
999
+ };
1000
+ // Generated by CoffeeScript 2.7.0
1001
+ var Polytree;
1002
+ Polytree = (function() {
1003
+ class Polytree {
1004
+ // Main constructor for creating Polytree nodes.
1005
+ // Initializes core properties and sets up polygon array management.
1006
+ // @param box - Optional bounding box for this tree node (used internally for polytree subdivision).
1007
+ // @param parent - Optional parent Polytree node (used internally, null for root).
1008
+ constructor(box = null, parent = null) {
1009
+ // Core geometric data.
1010
+ this.box = box; // Bounding box for spatial partitioning.
1011
+ this.polygons = []; // Primary polygon storage for this node.
1012
+ this.replacedPolygons = []; // Temporary storage for replaced polygons during operations.
1013
+
1014
+ // Tree structure properties.
1015
+ this.parent = parent; // Reference to parent node (null for root).
1016
+ this.subTrees = []; // Child Polytree nodes for polytree subdivision.
1017
+ this.level = 0; // Depth level in polytree hierarchy.
1018
+
1019
+ // Mesh and Matrix transformation data.
1020
+ this.originalMatrixWorld; // Original world transformation matrix.
1021
+ this.mesh; // Reference to Three.js mesh object.
1022
+
1023
+ // Polygon array management for root node.
1024
+ this.polygonArrays = void 0; // Collection of all polygon arrays (root node only).
1025
+ this.addPolygonsArrayToRoot(this.polygons);
1026
+ }
1027
+ // === SMALL HELPERS AND GETTERS/SETTERS ===
1028
+ isEmpty() { // Check if this tree node contains any polygons.
1029
+ return this.polygons.length === 0;
1030
+ }
1031
+ getMesh() { // Get the Three.js mesh associated with this Polytree (traverses to root).
1032
+ var current, ref, visited;
1033
+ visited = new Set();
1034
+ current = this;
1035
+ while (current && !visited.has(current)) {
1036
+ visited.add(current);
1037
+ if (!current.parent) {
1038
+ return (ref = current.mesh) != null ? ref : null;
1039
+ }
1040
+ current = current.parent;
1041
+ }
1042
+ return null; // Circular reference or no mesh found.
1043
+ }
1044
+ newPolytree(box, parent) { // Create a new Polytree node with given parameters.
1045
+ return new this.constructor(box, parent);
1046
+ }
1047
+ setPolygonIndex(index) { // Set polygon material index for all polygons in this tree.
1048
+ if (index === void 0) {
1049
+ return;
1050
+ }
1051
+ if (this.polygonArrays) {
1052
+ return this.polygonArrays.forEach(function(polygonsArray) {
1053
+ if (polygonsArray != null ? polygonsArray.length : void 0) {
1054
+ return polygonsArray.forEach(function(polygon) {
1055
+ return polygon.shared = index;
1056
+ });
1057
+ }
1058
+ });
1059
+ }
1060
+ }
1061
+ // === OBJECT CREATION AND COPYING ===
1062
+ clone() { // Creates a deep copy of this Polytree.
1063
+ return (new this.constructor()).copy(this);
1064
+ }
1065
+ copy(source) { // Copy data from another Polytree instance.
1066
+ var i, k, ref, ref1, subTree;
1067
+ if (!source) {
1068
+ return this;
1069
+ }
1070
+ this.deletePolygonsArrayFromRoot(this.polygons);
1071
+ this.polygons = source.polygons ? source.polygons.map(function(polygon) {
1072
+ return polygon.clone();
1073
+ }) : [];
1074
+ this.addPolygonsArrayToRoot(this.polygons);
1075
+ this.replacedPolygons = source.replacedPolygons ? source.replacedPolygons.map(function(polygon) {
1076
+ return polygon.clone();
1077
+ }) : [];
1078
+ if (source.mesh) {
1079
+ this.mesh = source.mesh;
1080
+ }
1081
+ if (source.originalMatrixWorld) {
1082
+ this.originalMatrixWorld = source.originalMatrixWorld.clone();
1083
+ }
1084
+ this.box = source.box ? source.box.clone() : null;
1085
+ this.level = (ref = source.level) != null ? ref : 0;
1086
+ if (source.subTrees) {
1087
+ for (i = k = 0, ref1 = source.subTrees.length; (0 <= ref1 ? k < ref1 : k > ref1); i = 0 <= ref1 ? ++k : --k) {
1088
+ subTree = new this.constructor(void 0, this).copy(source.subTrees[i]);
1089
+ this.subTrees.push(subTree);
1090
+ }
1091
+ }
1092
+ return this;
1093
+ }
1094
+ // === CSG OPERATIONS (INSTANCE METHODS) ===
1095
+ // Perform union operation (combines both meshes).
1096
+ unite(mesh1, mesh2, targetMaterial = null) {
1097
+ return Polytree.unite(mesh1, mesh2, targetMaterial);
1098
+ }
1099
+ // Perform subtraction operation (mesh1 minus mesh2).
1100
+ subtract(mesh1, mesh2, targetMaterial = null) {
1101
+ return Polytree.subtract(mesh1, mesh2, targetMaterial);
1102
+ }
1103
+ // Perform intersection operation (keep only overlapping volume).
1104
+ intersect(mesh1, mesh2, targetMaterial = null) {
1105
+ return Polytree.intersect(mesh1, mesh2, targetMaterial);
1106
+ }
1107
+ // === POLYGON ARRAY MANAGEMENT ===
1108
+ // Add polygon array to root node's collection (internal helper).
1109
+ addPolygonsArrayToRoot(array) {
1110
+ if (this.parent) {
1111
+ return this.parent.addPolygonsArrayToRoot(array);
1112
+ } else {
1113
+ if (this.polygonArrays === void 0) {
1114
+ this.polygonArrays = [];
1115
+ }
1116
+ return this.polygonArrays.push(array);
1117
+ }
1118
+ }
1119
+ // Remove polygon array from root node's collection (internal helper).
1120
+ deletePolygonsArrayFromRoot(array) {
1121
+ var index;
1122
+ if (this.parent) {
1123
+ return this.parent.deletePolygonsArrayFromRoot(array);
1124
+ } else {
1125
+ index = this.polygonArrays.indexOf(array);
1126
+ if (index > -1) {
1127
+ return this.polygonArrays.splice(index, 1);
1128
+ }
1129
+ }
1130
+ }
1131
+ // === CORE POLYGON OPERATIONS ===
1132
+ // Add a polygon to this tree node with spatial bounds calculation.
1133
+ addPolygon(polygon, trianglesSet) {
1134
+ var triangle;
1135
+ if (!this.bounds) {
1136
+ this.bounds = new Box3();
1137
+ }
1138
+ triangle = polygon.triangle;
1139
+ if (trianglesSet && !isUniqueTriangle(triangle, trianglesSet)) {
1140
+ return this;
1141
+ }
1142
+ // Expand bounds to include all triangle vertices.
1143
+ this.bounds.expandByPoint(triangle.a);
1144
+ this.bounds.expandByPoint(triangle.b);
1145
+ this.bounds.expandByPoint(triangle.c);
1146
+ this.polygons.push(polygon);
1147
+ return this;
1148
+ }
1149
+ calcBox() { // Calculate and set bounding box from polygon bounds.
1150
+ var offset;
1151
+ if (!this.bounds) {
1152
+ this.bounds = new Box3();
1153
+ }
1154
+ this.box = this.bounds.clone();
1155
+ offset = POLYTREE_MIN_NODE_SIZE; // Use configurable minimum node size to guarantee that all polygons are included in queries.
1156
+ this.box.min.x -= offset;
1157
+ this.box.min.y -= offset;
1158
+ this.box.min.z -= offset;
1159
+ this.box.max.x += offset;
1160
+ this.box.max.y += offset;
1161
+ this.box.max.z += offset;
1162
+ return this;
1163
+ }
1164
+ // === TREE CONSTRUCTION AND SPATIAL PARTITIONING ===
1165
+ // Split this node into 8 octree children based on spatial subdivision.
1166
+ // This creates a polytree by recursively subdividing space until polygon density is acceptable.
1167
+ split(level) {
1168
+ var box, found, halfSize, i, k, l, len, m, maxDepthLimit, n, nodeSize, o, polygon, polygonLimit, ref, ref1, subTrees, vectorPosition, x, y, z;
1169
+ subTrees = [];
1170
+ if (!this.box) {
1171
+ return this;
1172
+ }
1173
+ halfSize = temporaryVector3Secondary.copy(this.box.max).sub(this.box.min).multiplyScalar(0.5);
1174
+ // Create 8 child boxes in a 2x2x2 grid.
1175
+ for (x = k = 0; k <= 1; x = ++k) {
1176
+ for (y = l = 0; l <= 1; y = ++l) {
1177
+ for (z = m = 0; m <= 1; z = ++m) {
1178
+ box = new Box3();
1179
+ vectorPosition = temporaryVector3Primary.set(x, y, z);
1180
+ box.min.copy(this.box.min).add(vectorPosition.multiply(halfSize));
1181
+ box.max.copy(box.min).add(halfSize);
1182
+ box.expandByScalar(GEOMETRIC_EPSILON);
1183
+ subTrees.push(this.newPolytree(box, this));
1184
+ }
1185
+ }
1186
+ }
1187
+ // Redistribute polygons to appropriate child nodes based on midpoint.
1188
+ polygon = void 0;
1189
+ while (polygon = this.polygons.pop()) {
1190
+ found = false;
1191
+ for (i = n = 0, ref = subTrees.length; (0 <= ref ? n < ref : n > ref); i = 0 <= ref ? ++n : --n) {
1192
+ if (subTrees[i].box.containsPoint(polygon.getMidpoint())) {
1193
+ subTrees[i].polygons.push(polygon);
1194
+ found = true;
1195
+ }
1196
+ }
1197
+ if (!found) {
1198
+ throw new Error(`Unable to find subtree for triangle at level ${level}.`);
1199
+ }
1200
+ }
1201
+ // Recursively split child nodes if they exceed polygon threshold.
1202
+ for (i = o = 0, ref1 = subTrees.length; (0 <= ref1 ? o < ref1 : o > ref1); i = 0 <= ref1 ? ++o : --o) {
1203
+ subTrees[i].level = level + 1;
1204
+ len = subTrees[i].polygons.length;
1205
+ // Calculate node size to check minimum size constraint.
1206
+ nodeSize = subTrees[i].box.getSize(temporaryVector3Tertiary).length();
1207
+ // Continue subdivision if polygon count exceeds threshold, max depth not reached, and node size is above minimum.
1208
+ // Use the more restrictive of the two depth limits and polygon limits for maximum performance control.
1209
+ maxDepthLimit = Math.min(Polytree.maxLevel, POLYTREE_MAX_DEPTH);
1210
+ polygonLimit = Math.min(Polytree.polygonsPerTree, POLYTREE_MAX_POLYGONS_PER_NODE);
1211
+ if (len > polygonLimit && level < maxDepthLimit && nodeSize > POLYTREE_MIN_NODE_SIZE) {
1212
+ subTrees[i].split(level + 1);
1213
+ }
1214
+ this.subTrees.push(subTrees[i]);
1215
+ }
1216
+ return this;
1217
+ }
1218
+ buildTree() { // Build complete polytree structure from polygon data.
1219
+ this.calcBox();
1220
+ this.split(0);
1221
+ this.processTree();
1222
+ return this;
1223
+ }
1224
+ // Process tree nodes to update bounding boxes after polygon distribution.
1225
+ processTree() {
1226
+ var i, k, l, ref, ref1, results;
1227
+ if (!this.isEmpty()) {
1228
+ temporaryBoundingBox.copy(this.box);
1229
+ for (i = k = 0, ref = this.polygons.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1230
+ this.box.expandByPoint(this.polygons[i].triangle.a);
1231
+ this.box.expandByPoint(this.polygons[i].triangle.b);
1232
+ this.box.expandByPoint(this.polygons[i].triangle.c);
1233
+ }
1234
+ this.expandParentBox();
1235
+ }
1236
+ results = [];
1237
+ for (i = l = 0, ref1 = this.subTrees.length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) {
1238
+ results.push(this.subTrees[i].processTree());
1239
+ }
1240
+ return results;
1241
+ }
1242
+ // Recursively expand parent bounding boxes up the tree hierarchy.
1243
+ expandParentBox(visited = new Set()) {
1244
+ if (this.parent && !visited.has(this.parent)) {
1245
+ visited.add(this.parent);
1246
+ this.parent.box.expandByPoint(this.box.min);
1247
+ this.parent.box.expandByPoint(this.box.max);
1248
+ return this.parent.expandParentBox(visited);
1249
+ }
1250
+ }
1251
+ // === POLYGON QUERIES AND INTERSECTION TESTING ===
1252
+ // Invert all polygons by flipping their face normals.
1253
+ invert() {
1254
+ return this.polygonArrays.forEach(function(polygonsArray) {
1255
+ if (polygonsArray.length) {
1256
+ return polygonsArray.forEach(function(polygon) {
1257
+ return polygon.flip();
1258
+ });
1259
+ }
1260
+ });
1261
+ }
1262
+ // Get all valid polygons from all polygon arrays in this tree.
1263
+ getPolygons(polygons = []) {
1264
+ this.polygonArrays.forEach(function(polygonsArray) {
1265
+ var i, k, ref, results;
1266
+ if (polygonsArray.length) {
1267
+ results = [];
1268
+ for (i = k = 0, ref = polygonsArray.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1269
+ if (polygonsArray[i].valid) {
1270
+ if (polygons.indexOf(polygonsArray[i]) === -1) {
1271
+ results.push(polygons.push(polygonsArray[i]));
1272
+ } else {
1273
+ results.push(void 0);
1274
+ }
1275
+ } else {
1276
+ results.push(void 0);
1277
+ }
1278
+ }
1279
+ return results;
1280
+ }
1281
+ });
1282
+ return polygons;
1283
+ }
1284
+ // Extract triangles from all valid polygons in the tree.
1285
+ // This provides triangle data format as complement to getPolygons().
1286
+ getTriangles(triangles = []) {
1287
+ var polygons;
1288
+ polygons = this.getPolygons();
1289
+ polygons.forEach(function(polygon) {
1290
+ return triangles.push(polygon.triangle);
1291
+ });
1292
+ return triangles;
1293
+ }
1294
+ // Collect polygons that intersect with a ray for raycasting operations.
1295
+ getRayPolygons(ray, polygons = []) {
1296
+ var i, k, l, ref, ref1;
1297
+ if (this.polygons.length > 0) {
1298
+ for (i = k = 0, ref = this.polygons.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1299
+ if (this.polygons[i].valid && this.polygons[i].originalValid) {
1300
+ if (polygons.indexOf(this.polygons[i]) === -1) {
1301
+ polygons.push(this.polygons[i]);
1302
+ }
1303
+ }
1304
+ }
1305
+ }
1306
+ if (this.replacedPolygons.length > 0) {
1307
+ polygons.push(...this.replacedPolygons);
1308
+ }
1309
+ for (i = l = 0, ref1 = this.subTrees.length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) {
1310
+ if (ray.intersectsBox(this.subTrees[i].box)) {
1311
+ this.subTrees[i].getRayPolygons(ray, polygons);
1312
+ }
1313
+ }
1314
+ return polygons;
1315
+ }
1316
+ // Extract triangles from polygons that intersect with a ray.
1317
+ // This provides triangle data format as complement to getRayPolygons().
1318
+ getRayTriangles(ray, triangles = []) {
1319
+ var polygons;
1320
+ polygons = this.getRayPolygons(ray);
1321
+ polygons.forEach(function(polygon) {
1322
+ return triangles.push(polygon.triangle);
1323
+ });
1324
+ return triangles;
1325
+ }
1326
+ // Perform ray intersection testing against all polygons in the tree.
1327
+ // Returns array of intersection results sorted by distance.
1328
+ rayIntersect(ray, matrixWorld, intersects = []) {
1329
+ var distance, i, k, newDistance, polygons, ref, result;
1330
+ if (ray.direction.length() === 0) {
1331
+ return [];
1332
+ }
1333
+ distance = 1e100;
1334
+ polygons = this.getRayPolygons(ray);
1335
+ for (i = k = 0, ref = polygons.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1336
+ result = void 0;
1337
+ if (Polytree.rayIntersectTriangleType === "regular") {
1338
+ result = ray.intersectTriangle(polygons[i].triangle.a, polygons[i].triangle.b, polygons[i].triangle.c, false, temporaryVector3Primary);
1339
+ if (result) {
1340
+ temporaryVector3Primary.applyMatrix4(matrixWorld);
1341
+ distance = temporaryVector3Primary.distanceTo(ray.origin);
1342
+ if (distance < 0 || distance > 2e308) {
1343
+ console.warn("[rayIntersect] Failed ray distance check.", ray);
1344
+ } else {
1345
+ intersects.push({
1346
+ distance: distance,
1347
+ polygon: polygons[i],
1348
+ position: temporaryVector3Primary.clone()
1349
+ });
1350
+ }
1351
+ }
1352
+ } else {
1353
+ result = testRayTriangleIntersection(ray, polygons[i].triangle, temporaryVector3Primary);
1354
+ if (result) {
1355
+ newDistance = result.clone().sub(ray.origin).length();
1356
+ if (distance > newDistance) {
1357
+ distance = newDistance;
1358
+ }
1359
+ if (distance < 1e100) {
1360
+ intersects.push({
1361
+ distance: distance,
1362
+ polygon: polygons[i],
1363
+ position: result.clone().add(ray.origin)
1364
+ });
1365
+ }
1366
+ }
1367
+ }
1368
+ }
1369
+ intersects.length && intersects.sort(sortRaycastIntersectionsByDistance);
1370
+ return intersects;
1371
+ }
1372
+ // Get all polygons marked as intersecting from polygon arrays.
1373
+ getIntersectingPolygons(polygons = []) {
1374
+ this.polygonArrays.forEach(function(polygonsArray) {
1375
+ var i, k, ref, results;
1376
+ if (polygonsArray.length) {
1377
+ results = [];
1378
+ for (i = k = 0, ref = polygonsArray.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1379
+ if (polygonsArray[i].valid && polygonsArray[i].intersects) {
1380
+ results.push(polygons.push(polygonsArray[i]));
1381
+ } else {
1382
+ results.push(void 0);
1383
+ }
1384
+ }
1385
+ return results;
1386
+ }
1387
+ });
1388
+ return polygons;
1389
+ }
1390
+ // Find all polygons that intersect with a target polygon using spatial partitioning.
1391
+ getPolygonsIntersectingPolygon(targetPolygon, polygons = []) {
1392
+ var allPolygons, i, k, l, m, polygon, ref, ref1, ref2;
1393
+ if (this.box.intersectsTriangle(targetPolygon.triangle)) {
1394
+ if (this.polygons.length > 0) {
1395
+ allPolygons = this.polygons.slice();
1396
+ if (this.replacedPolygons.length > 0) {
1397
+ for (i = k = 0, ref = this.replacedPolygons.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1398
+ allPolygons.push(this.replacedPolygons[i]);
1399
+ }
1400
+ }
1401
+ for (i = l = 0, ref1 = allPolygons.length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) {
1402
+ polygon = allPolygons[i];
1403
+ if (!(polygon.originalValid && polygon.valid && polygon.intersects)) {
1404
+ continue;
1405
+ }
1406
+ if (triangleIntersectsTriangle(targetPolygon.triangle, polygon.triangle)) {
1407
+ polygons.push(polygon);
1408
+ }
1409
+ }
1410
+ }
1411
+ }
1412
+ for (i = m = 0, ref2 = this.subTrees.length; (0 <= ref2 ? m < ref2 : m > ref2); i = 0 <= ref2 ? ++m : --m) {
1413
+ this.subTrees[i].getPolygonsIntersectingPolygon(targetPolygon, polygons);
1414
+ }
1415
+ return polygons;
1416
+ }
1417
+ // === POLYGON MODIFICATION AND STATE MANAGEMENT ===
1418
+ // Replace a polygon with one or more new polygons during CSG operations.
1419
+ replacePolygon(polygon, newPolygons) {
1420
+ var i, k, polygonIndex, ref, results;
1421
+ if (!Array.isArray(newPolygons)) {
1422
+ newPolygons = [newPolygons];
1423
+ }
1424
+ if (this.polygons.length > 0) {
1425
+ polygonIndex = this.polygons.indexOf(polygon);
1426
+ if (polygonIndex > -1) {
1427
+ if (polygon.originalValid === true) {
1428
+ this.replacedPolygons.push(polygon);
1429
+ } else {
1430
+ polygon.setInvalid();
1431
+ }
1432
+ this.polygons.splice(polygonIndex, 1, ...newPolygons);
1433
+ }
1434
+ }
1435
+ results = [];
1436
+ for (i = k = 0, ref = this.subTrees.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1437
+ results.push(this.subTrees[i].replacePolygon(polygon, newPolygons));
1438
+ }
1439
+ return results;
1440
+ }
1441
+ // === COMPLEX CSG OPERATIONS AND STATE MANAGEMENT ===
1442
+ // Delete polygons based on complex state rules for CSG operations.
1443
+ // This implements the polygon classification logic for boolean operations.
1444
+ deletePolygonsByStateRules(rulesArr, firstRun = true) {
1445
+ return this.polygonArrays.forEach(function(polygonsArray) {
1446
+ var polygonArr;
1447
+ if (polygonsArray.length) {
1448
+ polygonArr = polygonsArray.filter(function(polygon) {
1449
+ return (polygon.valid === true) && (polygon.intersects === true);
1450
+ });
1451
+ return polygonArr.forEach(function(polygon) {
1452
+ var found, i, j, k, l, mainStatesObj, polygonIndex, ref, ref1, state, states, statesObj;
1453
+ found = false;
1454
+ for (j = k = 0, ref = rulesArr.length; (0 <= ref ? k < ref : k > ref); j = 0 <= ref ? ++k : --k) {
1455
+ if (rulesArr[j].array) {
1456
+ states = rulesArr[j].rule;
1457
+ if ((states.includes(polygon.state)) && (((polygon.previousState !== "undecided") && (states.includes(polygon.previousState))) || (polygon.previousState === "undecided"))) {
1458
+ found = true;
1459
+ statesObj = {};
1460
+ mainStatesObj = {};
1461
+ states.forEach(function(state) {
1462
+ return statesObj[state] = false;
1463
+ });
1464
+ states.forEach(function(state) {
1465
+ return mainStatesObj[state] = false;
1466
+ });
1467
+ statesObj[polygon.state] = true;
1468
+ for (i = l = 0, ref1 = polygon.previousStates.length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) {
1469
+ if (!states.includes(polygon.previousStates[i])) {
1470
+ found = false;
1471
+ break;
1472
+ } else {
1473
+ statesObj[polygon.previousStates[i]] = true;
1474
+ }
1475
+ }
1476
+ if (found) {
1477
+ for (state in statesObj) {
1478
+ if (statesObj[state] === false) {
1479
+ found = false;
1480
+ break;
1481
+ }
1482
+ }
1483
+ if (found) {
1484
+ break;
1485
+ }
1486
+ }
1487
+ }
1488
+ } else {
1489
+ if (polygon.checkAllStates(rulesArr[j].rule)) {
1490
+ found = true;
1491
+ break;
1492
+ }
1493
+ }
1494
+ }
1495
+ if (found) {
1496
+ polygonIndex = polygonsArray.indexOf(polygon);
1497
+ if (polygonIndex > -1) {
1498
+ polygon.setInvalid();
1499
+ polygonsArray.splice(polygonIndex, 1);
1500
+ }
1501
+ if (firstRun) {
1502
+ return polygon.delete();
1503
+ }
1504
+ }
1505
+ });
1506
+ }
1507
+ });
1508
+ }
1509
+ // Delete polygons based on their intersection status (simpler filtering).
1510
+ deletePolygonsByIntersection(intersects, firstRun = true) {
1511
+ if (intersects === void 0) {
1512
+ return;
1513
+ }
1514
+ return this.polygonArrays.forEach(function(polygonsArray) {
1515
+ var polygonArr;
1516
+ if (polygonsArray.length) {
1517
+ polygonArr = polygonsArray.slice();
1518
+ return polygonArr.forEach(function(polygon) {
1519
+ var polygonIndex;
1520
+ if (polygon.valid) {
1521
+ if (polygon.intersects === intersects) {
1522
+ polygonIndex = polygonsArray.indexOf(polygon);
1523
+ if (polygonIndex > -1) {
1524
+ polygon.setInvalid();
1525
+ polygonsArray.splice(polygonIndex, 1);
1526
+ }
1527
+ if (firstRun) {
1528
+ return polygon.delete();
1529
+ }
1530
+ }
1531
+ }
1532
+ });
1533
+ }
1534
+ });
1535
+ }
1536
+ // Check if a polygon intersects with this tree's bounding box.
1537
+ isPolygonIntersecting(polygon) {
1538
+ if (!this.box.intersectsTriangle(polygon.triangle)) {
1539
+ return false;
1540
+ }
1541
+ return true;
1542
+ }
1543
+ // Mark polygons as intersecting with target polytree.
1544
+ markIntesectingPolygons(targetPolytree) {
1545
+ return this.polygonArrays.forEach(function(polygonsArray) {
1546
+ if (polygonsArray.length) {
1547
+ return polygonsArray.forEach(function(polygon) {
1548
+ return polygon.intersects = targetPolytree.isPolygonIntersecting(polygon);
1549
+ });
1550
+ }
1551
+ });
1552
+ }
1553
+ // Reset polygon states for CSG operation preparation.
1554
+ resetPolygons(resetOriginal = true) {
1555
+ return this.polygonArrays.forEach(function(polygonsArray) {
1556
+ if (polygonsArray.length) {
1557
+ return polygonsArray.forEach(function(polygon) {
1558
+ return polygon.reset(resetOriginal);
1559
+ });
1560
+ }
1561
+ });
1562
+ }
1563
+ // Complex CSG intersection handling - splits and classifies polygons.
1564
+ // This is the core method for polygon classification in boolean operations.
1565
+ handleIntersectingPolygons(targetPolytree, targetPolytreeBuffer) {
1566
+ var currentPolygon, i, inside, intersects, j, k, l, m, n, o, point, polygon, polygonStack, ref, ref1, ref2, ref3, ref4, results, splitResults, target, targetPolygons;
1567
+ if (this.polygons.length > 0) {
1568
+ polygonStack = this.polygons.filter(function(polygon) {
1569
+ return (polygon.valid === true) && (polygon.intersects === true) && (polygon.state === "undecided");
1570
+ });
1571
+ currentPolygon = polygonStack.pop();
1572
+ while (currentPolygon) {
1573
+ if (currentPolygon.state !== "undecided") {
1574
+ continue;
1575
+ }
1576
+ if (!currentPolygon.valid) {
1577
+ continue;
1578
+ }
1579
+ targetPolygons = targetPolytree.getPolygonsIntersectingPolygon(currentPolygon);
1580
+ if (targetPolygons.length > 0) {
1581
+ for (j = k = 0, ref = targetPolygons.length; (0 <= ref ? k < ref : k > ref); j = 0 <= ref ? ++k : --k) {
1582
+ target = targetPolygons[j];
1583
+ splitResults = splitPolygonByPlane(currentPolygon, target.plane);
1584
+ if (splitResults.length > 1) {
1585
+ for (i = l = 0, ref1 = splitResults.length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) {
1586
+ polygon = splitResults[i].polygon;
1587
+ polygon.intersects = currentPolygon.intersects;
1588
+ polygon.newPolygon = true;
1589
+ polygonStack.push(polygon);
1590
+ }
1591
+ this.replacePolygon(currentPolygon, splitResults.map(function(result) {
1592
+ return result.polygon;
1593
+ }));
1594
+ break;
1595
+ } else {
1596
+ if (currentPolygon.id !== splitResults[0].polygon.id) {
1597
+ splitResults[0].polygon.intersects = currentPolygon.intersects;
1598
+ splitResults[0].polygon.newPolygon = true;
1599
+ polygonStack.push(splitResults[0].polygon);
1600
+ this.replacePolygon(currentPolygon, splitResults[0].polygon);
1601
+ break;
1602
+ } else {
1603
+ if ((splitResults[0].type === "coplanar-front") || (splitResults[0].type === "coplanar-back")) {
1604
+ currentPolygon.setState(splitResults[0].type);
1605
+ currentPolygon.coplanar = true;
1606
+ }
1607
+ }
1608
+ }
1609
+ }
1610
+ }
1611
+ currentPolygon = polygonStack.pop();
1612
+ }
1613
+ polygonStack = this.polygons.filter(function(polygon) {
1614
+ return (polygon.valid === true) && (polygon.intersects === true);
1615
+ });
1616
+ currentPolygon = polygonStack.pop();
1617
+ inside = false;
1618
+ while (currentPolygon) {
1619
+ if (!currentPolygon.valid) {
1620
+ continue;
1621
+ }
1622
+ inside = false;
1623
+ if (targetPolytree.box.containsPoint(currentPolygon.getMidpoint())) {
1624
+ if (Polytree.useWindingNumber === true) {
1625
+ inside = testPolygonInsideUsingWindingNumber(targetPolytreeBuffer, currentPolygon.getMidpoint(), currentPolygon.coplanar);
1626
+ } else {
1627
+ point = roundPointCoordinates(temporaryVector3Secondary.copy(currentPolygon.getMidpoint()));
1628
+ if (Polytree.usePolytreeRay !== true && targetPolytree.mesh) {
1629
+ defaultRayDirection.copy(currentPolygon.plane.normal);
1630
+ temporaryRaycaster.set(point, defaultRayDirection);
1631
+ intersects = temporaryRaycaster.intersectObject(targetPolytree.mesh);
1632
+ if (intersects.length) {
1633
+ if (defaultRayDirection.dot(intersects[0].face.normal) > 0) {
1634
+ inside = true;
1635
+ }
1636
+ }
1637
+ if (!(inside || !currentPolygon.coplanar)) {
1638
+ for (j = m = 0, ref2 = windingNumberEpsilonOffsetsCount; (0 <= ref2 ? m < ref2 : m > ref2); j = 0 <= ref2 ? ++m : --m) {
1639
+ temporaryRaycaster.ray.origin.copy(point).add(windingNumberEpsilonOffsets[j]);
1640
+ intersects = temporaryRaycaster.intersectObject(targetPolytree.mesh);
1641
+ if (intersects.length) {
1642
+ if (defaultRayDirection.dot(intersects[0].face.normal) > 0) {
1643
+ inside = true;
1644
+ break;
1645
+ }
1646
+ }
1647
+ }
1648
+ }
1649
+ } else {
1650
+ temporaryRay.origin.copy(point);
1651
+ defaultRayDirection.copy(currentPolygon.plane.normal);
1652
+ temporaryRay.direction.copy(currentPolygon.plane.normal);
1653
+ intersects = targetPolytree.rayIntersect(temporaryRay, targetPolytree.originalMatrixWorld);
1654
+ if (intersects.length) {
1655
+ if (defaultRayDirection.dot(intersects[0].polygon.plane.normal) > 0) {
1656
+ inside = true;
1657
+ }
1658
+ }
1659
+ if (!(inside || !currentPolygon.coplanar)) {
1660
+ for (j = n = 0, ref3 = windingNumberEpsilonOffsetsCount; (0 <= ref3 ? n < ref3 : n > ref3); j = 0 <= ref3 ? ++n : --n) {
1661
+ temporaryRay.origin.copy(point).add(windingNumberEpsilonOffsets[j]);
1662
+ defaultRayDirection.copy(currentPolygon.plane.normal);
1663
+ temporaryRay.direction.copy(currentPolygon.plane.normal);
1664
+ intersects = targetPolytree.rayIntersect(temporaryRay, targetPolytree.originalMatrixWorld);
1665
+ if (intersects.length) {
1666
+ if (defaultRayDirection.dot(intersects[0].polygon.plane.normal) > 0) {
1667
+ inside = true;
1668
+ break;
1669
+ }
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+ }
1675
+ }
1676
+ if (inside === true) {
1677
+ currentPolygon.setState("inside");
1678
+ } else {
1679
+ currentPolygon.setState("outside");
1680
+ }
1681
+ currentPolygon = polygonStack.pop();
1682
+ }
1683
+ }
1684
+ results = [];
1685
+ for (i = o = 0, ref4 = this.subTrees.length; (0 <= ref4 ? o < ref4 : o > ref4); i = 0 <= ref4 ? ++o : --o) {
1686
+ results.push(this.subTrees[i].handleIntersectingPolygons(targetPolytree, targetPolytreeBuffer));
1687
+ }
1688
+ return results;
1689
+ }
1690
+ // Apply transformation matrix to all polygons in this tree.
1691
+ applyMatrix(matrix, normalMatrix, firstRun = true) {
1692
+ var i, k, l, ref, ref1;
1693
+ if (matrix.isMesh) {
1694
+ matrix.updateMatrix();
1695
+ matrix = matrix.matrix;
1696
+ }
1697
+ if (this.box) {
1698
+ this.box.makeEmpty();
1699
+ }
1700
+ normalMatrix = normalMatrix || temporaryMatrixWithNormalCalc.getNormalMatrix(matrix);
1701
+ if (this.polygons.length > 0) {
1702
+ for (i = k = 0, ref = this.polygons.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1703
+ if (this.polygons[i].valid) {
1704
+ this.polygons[i].applyMatrix(matrix, normalMatrix);
1705
+ }
1706
+ }
1707
+ }
1708
+ for (i = l = 0, ref1 = this.subTrees.length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) {
1709
+ this.subTrees[i].applyMatrix(matrix, normalMatrix, false);
1710
+ }
1711
+ if (firstRun) {
1712
+ return this.processTree();
1713
+ }
1714
+ }
1715
+ // === ADVANCED COLLISION DETECTION ===
1716
+ // Test intersection between a sphere and a triangle.
1717
+ // Returns intersection data or false if no intersection.
1718
+ // @param sphere - Sphere object with center and radius properties.
1719
+ // @param triangle - Triangle object with a, b, c vertex properties.
1720
+ // @return Object with normal, point, depth properties or false if no intersection.
1721
+ triangleSphereIntersect(sphere, triangle) {
1722
+ var distanceSquared, i, intersectionDepth, k, planePoint, radiusSquared, ref, temporaryLine, temporaryVector1, temporaryVector2, triangleEdges, trianglePlane;
1723
+ // Create temporary objects for calculations.
1724
+ temporaryVector1 = new Vector3();
1725
+ temporaryVector2 = new Vector3();
1726
+ temporaryLine = new Line3();
1727
+ trianglePlane = new ThreePlane();
1728
+ triangle.getPlane(trianglePlane);
1729
+ if (!sphere.intersectsPlane(trianglePlane)) {
1730
+ return false;
1731
+ }
1732
+ intersectionDepth = Math.abs(trianglePlane.distanceToSphere(sphere));
1733
+ radiusSquared = sphere.radius * sphere.radius - intersectionDepth * intersectionDepth;
1734
+ planePoint = trianglePlane.projectPoint(sphere.center, temporaryVector1);
1735
+ if (triangle.containsPoint(sphere.center)) {
1736
+ return {
1737
+ depth: Math.abs(trianglePlane.distanceToSphere(sphere)),
1738
+ normal: trianglePlane.normal.clone(),
1739
+ point: planePoint.clone()
1740
+ };
1741
+ }
1742
+ triangleEdges = [[triangle.a, triangle.b], [triangle.b, triangle.c], [triangle.c, triangle.a]];
1743
+ for (i = k = 0, ref = triangleEdges.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1744
+ temporaryLine.set(triangleEdges[i][0], triangleEdges[i][1]);
1745
+ temporaryLine.closestPointToPoint(planePoint, true, temporaryVector2);
1746
+ distanceSquared = temporaryVector2.distanceToSquared(sphere.center);
1747
+ if (distanceSquared < radiusSquared) {
1748
+ return {
1749
+ depth: sphere.radius - Math.sqrt(distanceSquared),
1750
+ normal: sphere.center.clone().sub(temporaryVector2).normalize(),
1751
+ point: temporaryVector2.clone()
1752
+ };
1753
+ }
1754
+ }
1755
+ return false;
1756
+ }
1757
+ // Collect triangles that intersect with a sphere using spatial partitioning.
1758
+ // This method recursively traverses the polytree to find relevant triangles.
1759
+ // @param sphere - Sphere object to test intersection against.
1760
+ // @param triangles - Array to collect intersecting triangles.
1761
+ getSphereTriangles(sphere, triangles) {
1762
+ var currentPolygon, currentSubTree, k, polygonIndex, ref, results, subTreeIndex;
1763
+ results = [];
1764
+ for (subTreeIndex = k = 0, ref = this.subTrees.length; (0 <= ref ? k < ref : k > ref); subTreeIndex = 0 <= ref ? ++k : --k) {
1765
+ currentSubTree = this.subTrees[subTreeIndex];
1766
+ if (!sphere.intersectsBox(currentSubTree.box)) {
1767
+ continue;
1768
+ }
1769
+ if (currentSubTree.polygons.length > 0) {
1770
+ results.push((function() {
1771
+ var l, ref1, results1;
1772
+ results1 = [];
1773
+ for (polygonIndex = l = 0, ref1 = currentSubTree.polygons.length; (0 <= ref1 ? l < ref1 : l > ref1); polygonIndex = 0 <= ref1 ? ++l : --l) {
1774
+ currentPolygon = currentSubTree.polygons[polygonIndex];
1775
+ if (!currentPolygon.valid) {
1776
+ continue;
1777
+ }
1778
+ if (triangles.indexOf(currentPolygon.triangle) === -1) {
1779
+ results1.push(triangles.push(currentPolygon.triangle));
1780
+ } else {
1781
+ results1.push(void 0);
1782
+ }
1783
+ }
1784
+ return results1;
1785
+ })());
1786
+ } else {
1787
+ results.push(currentSubTree.getSphereTriangles(sphere, triangles));
1788
+ }
1789
+ }
1790
+ return results;
1791
+ }
1792
+ // Perform high-level sphere intersection testing against the entire polytree.
1793
+ // Returns collision data with adjusted position and penetration depth.
1794
+ // @param sphere - Sphere object to test collision against.
1795
+ // @return Object with normal and depth properties or false if no collision.
1796
+ sphereIntersect(sphere) {
1797
+ var adjustedSphere, collisionDetected, collisionVector, currentTriangle, intersectingTriangles, intersectionResult, k, penetrationDepth, ref, triangleIndex;
1798
+ collisionDetected = false;
1799
+ intersectionResult = void 0;
1800
+ intersectingTriangles = [];
1801
+ adjustedSphere = new Sphere();
1802
+ adjustedSphere.copy(sphere);
1803
+ this.getSphereTriangles(sphere, intersectingTriangles);
1804
+ for (triangleIndex = k = 0, ref = intersectingTriangles.length; (0 <= ref ? k < ref : k > ref); triangleIndex = 0 <= ref ? ++k : --k) {
1805
+ currentTriangle = intersectingTriangles[triangleIndex];
1806
+ if (intersectionResult = this.triangleSphereIntersect(adjustedSphere, currentTriangle)) {
1807
+ collisionDetected = true;
1808
+ adjustedSphere.center.add(intersectionResult.normal.multiplyScalar(intersectionResult.depth));
1809
+ }
1810
+ }
1811
+ if (collisionDetected) {
1812
+ collisionVector = adjustedSphere.center.clone().sub(sphere.center);
1813
+ penetrationDepth = collisionVector.length();
1814
+ return {
1815
+ normal: collisionVector.normalize(),
1816
+ depth: penetrationDepth
1817
+ };
1818
+ }
1819
+ return false;
1820
+ }
1821
+ // Build polytree from Three.js scene graph (Group or Object3D).
1822
+ // This method traverses the scene graph and converts all meshes to polytree data.
1823
+ // @param group - Three.js Group or Object3D to traverse.
1824
+ fromGraphNode(sceneGraphNode) {
1825
+ var targetPolytreeInstance;
1826
+ sceneGraphNode.updateWorldMatrix(true, true);
1827
+ targetPolytreeInstance = this;
1828
+ sceneGraphNode.traverse(function(sceneObject) {
1829
+ if (sceneObject.isMesh === true) {
1830
+ return Polytree.fromMesh(sceneObject, void 0, targetPolytreeInstance, false);
1831
+ }
1832
+ });
1833
+ return this.buildTree();
1834
+ }
1835
+ // === CLEANUP AND DISPOSAL METHODS ===
1836
+ // Clean up replaced polygons from CSG operations.
1837
+ deleteReplacedPolygons() {
1838
+ var i, k, ref, results;
1839
+ if (this.replacedPolygons.length > 0) {
1840
+ this.replacedPolygons.forEach(function(polygon) {
1841
+ return polygon.delete();
1842
+ });
1843
+ this.replacedPolygons.length = 0;
1844
+ }
1845
+ results = [];
1846
+ for (i = k = 0, ref = this.subTrees.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1847
+ results.push(this.subTrees[i].deleteReplacedPolygons());
1848
+ }
1849
+ return results;
1850
+ }
1851
+ // Mark all polygons as original (not generated by CSG operations).
1852
+ markPolygonsAsOriginal() {
1853
+ return this.polygonArrays.forEach(function(polygonsArray) {
1854
+ if (polygonsArray.length) {
1855
+ return polygonsArray.forEach(function(polygon) {
1856
+ return polygon.originalValid = true;
1857
+ });
1858
+ }
1859
+ });
1860
+ }
1861
+ // Get polygon clones via callback for CSG operations.
1862
+ getPolygonCloneCallback(cbFunc, trianglesSet) {
1863
+ return this.polygonArrays.forEach(function(polygonsArray) {
1864
+ var i, k, ref, results;
1865
+ if (polygonsArray.length) {
1866
+ results = [];
1867
+ for (i = k = 0, ref = polygonsArray.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1868
+ if (polygonsArray[i].valid) {
1869
+ results.push(cbFunc(polygonsArray[i].clone(), trianglesSet));
1870
+ } else {
1871
+ results.push(void 0);
1872
+ }
1873
+ }
1874
+ return results;
1875
+ }
1876
+ });
1877
+ }
1878
+ // Delete this tree and all its data (primary cleanup method).
1879
+ delete(deletePolygons = true) {
1880
+ var i, k, ref;
1881
+ if (this.polygons.length > 0 && deletePolygons) {
1882
+ this.polygons.forEach(function(polygon) {
1883
+ return polygon.delete();
1884
+ });
1885
+ this.polygons.length = 0;
1886
+ }
1887
+ if (this.replacedPolygons.length > 0 && deletePolygons) {
1888
+ this.replacedPolygons.forEach(function(polygon) {
1889
+ return polygon.delete();
1890
+ });
1891
+ this.replacedPolygons.length = 0;
1892
+ }
1893
+ if (this.polygonArrays) {
1894
+ this.polygonArrays.length = 0;
1895
+ }
1896
+ if (this.subTrees.length) {
1897
+ for (i = k = 0, ref = this.subTrees.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
1898
+ this.subTrees[i].delete(deletePolygons);
1899
+ }
1900
+ this.subTrees.length = 0;
1901
+ }
1902
+ this.mesh = void 0;
1903
+ this.originalMatrixWorld = void 0;
1904
+ this.box = void 0;
1905
+ this.parent = void 0;
1906
+ return this.level = void 0;
1907
+ }
1908
+ // Dispose of this tree (alias for delete).
1909
+ dispose(deletePolygons = true) {
1910
+ return this.delete(deletePolygons);
1911
+ }
1912
+ };
1913
+ // ----- Prototype Properties -----
1914
+ Polytree.prototype.isPolytree = true;
1915
+ // ----- Static Properties -----
1916
+ Polytree.usePolytreeRay = true;
1917
+ Polytree.disposePolytree = true;
1918
+ Polytree.useWindingNumber = false;
1919
+ Polytree.maxLevel = POLYTREE_MAX_DEPTH;
1920
+ Polytree.polygonsPerTree = POLYTREE_MAX_POLYGONS_PER_NODE;
1921
+ Polytree.rayIntersectTriangleType = "MollerTrumbore";
1922
+ // ----- Static Methods -----
1923
+ Polytree.operation = operationHandler;
1924
+ Polytree.rayIntersectsTriangle = testRayTriangleIntersection;
1925
+ return Polytree;
1926
+ }).call(this);
1927
+ // Generated by CoffeeScript 2.7.0
1928
+ // Convert a Polytree instance to a THREE.js BufferGeometry.
1929
+ // This method extracts polygon data from the polytree and creates a proper
1930
+ // THREE.js geometry with positions, normals, UVs, colors, and material groups.
1931
+ // @param polytree - The Polytree instance to convert.
1932
+ // @return THREE.js BufferGeometry ready for rendering.
1933
+ Polytree.toGeometry = function(polytree) {
1934
+ var colors, defaultGroup, geometry, groupBase, groups, i, index, k, l, len, len1, m, n, normals, polygon, polygons, positions, ref, ref1, totalTriangles, uvs, vertices, verticesLen;
1935
+ groups = [];
1936
+ defaultGroup = [];
1937
+ uvs = void 0;
1938
+ colors = void 0;
1939
+ totalTriangles = 0;
1940
+ polygons = polytree.getPolygons();
1941
+ // Calculate the total number of triangles that will be generated.
1942
+ for (k = 0, len = polygons.length; k < len; k++) {
1943
+ polygon = polygons[k];
1944
+ totalTriangles += Math.max(0, polygon.vertices.length - 2);
1945
+ }
1946
+ positions = createVector3Buffer(totalTriangles * 3);
1947
+ normals = createVector3Buffer(totalTriangles * 3);
1948
+ for (l = 0, len1 = polygons.length; l < len1; l++) {
1949
+ polygon = polygons[l];
1950
+ vertices = polygon.vertices;
1951
+ verticesLen = vertices.length;
1952
+ // Initialize material group array if polygon has a shared material index.
1953
+ if (polygon.shared !== void 0) {
1954
+ if (!groups[polygon.shared]) {
1955
+ groups[polygon.shared] = [];
1956
+ }
1957
+ }
1958
+ // Initialize UV and color buffers if vertices contain this data.
1959
+ if (verticesLen > 0) {
1960
+ if (vertices[0].uv !== void 0) {
1961
+ uvs || (uvs = createVector2Buffer(totalTriangles * 3));
1962
+ }
1963
+ if (vertices[0].color !== void 0) {
1964
+ colors || (colors = createVector3Buffer(totalTriangles * 3));
1965
+ }
1966
+ }
1967
+ // Triangulate polygon by creating triangles from vertex fan.
1968
+ // Each triangle uses vertices[0] as the common vertex.
1969
+ for (i = m = 3, ref = verticesLen; (3 <= ref ? m <= ref : m >= ref); i = 3 <= ref ? ++m : --m) {
1970
+ // Add triangle indices to appropriate group (material-based or default).
1971
+ (polygon.shared === void 0 ? defaultGroup : groups[polygon.shared]).push(positions.top / 3, (positions.top / 3) + 1, (positions.top / 3) + 2);
1972
+ // Write vertex positions for the triangle.
1973
+ positions.write(vertices[0].pos);
1974
+ positions.write(vertices[i - 2].pos);
1975
+ positions.write(vertices[i - 1].pos);
1976
+ // Write vertex normals for the triangle.
1977
+ normals.write(vertices[0].normal);
1978
+ normals.write(vertices[i - 2].normal);
1979
+ normals.write(vertices[i - 1].normal);
1980
+ // Write UV coordinates if available.
1981
+ if (uvs != null) {
1982
+ uvs.write(vertices[0].uv);
1983
+ uvs.write(vertices[i - 2].uv);
1984
+ uvs.write(vertices[i - 1].uv);
1985
+ }
1986
+ // Write vertex colors if available.
1987
+ if (colors != null) {
1988
+ colors.write(vertices[0].color);
1989
+ colors.write(vertices[i - 2].color);
1990
+ colors.write(vertices[i - 1].color);
1991
+ }
1992
+ }
1993
+ }
1994
+ geometry = new BufferGeometry(); // Create THREE.js BufferGeometry and set attributes.
1995
+ geometry.setAttribute('position', new BufferAttribute(positions.array, 3));
1996
+ geometry.setAttribute('normal', new BufferAttribute(normals.array, 3));
1997
+ uvs && geometry.setAttribute('uv', new BufferAttribute(uvs.array, 2));
1998
+ colors && geometry.setAttribute('color', new BufferAttribute(colors.array, 3));
1999
+ // Set up material groups and indices for multi-material support.
2000
+ if (groups.length > 0) {
2001
+ index = [];
2002
+ groupBase = 0;
2003
+ // Process each material group.
2004
+ for (i = n = 0, ref1 = groups.length; (0 <= ref1 ? n < ref1 : n > ref1); i = 0 <= ref1 ? ++n : --n) {
2005
+ groups[i] = groups[i] || [];
2006
+ geometry.addGroup(groupBase, groups[i].length, i);
2007
+ groupBase += groups[i].length;
2008
+ index = index.concat(groups[i]);
2009
+ }
2010
+ // Add default group if it has triangles.
2011
+ if (defaultGroup.length) {
2012
+ geometry.addGroup(groupBase, defaultGroup.length, groups.length);
2013
+ index = index.concat(defaultGroup);
2014
+ }
2015
+ geometry.setIndex(index);
2016
+ }
2017
+ return geometry;
2018
+ };
2019
+ // Convert a Polytree instance to a complete THREE.js Mesh.
2020
+ // This is a convenience method that combines geometry creation with material assignment.
2021
+ // @param polytree - The Polytree instance to convert.
2022
+ // @param toMaterial - The THREE.js material to apply to the mesh.
2023
+ // @return THREE.js Mesh ready for scene addition.
2024
+ Polytree.toMesh = function(polytree, toMaterial) {
2025
+ var geometry;
2026
+ geometry = Polytree.toGeometry(polytree);
2027
+ return new Mesh(geometry, toMaterial);
2028
+ };
2029
+ // Convert a THREE.js Mesh to a Polytree instance.
2030
+ // This method extracts geometry data from a THREE.js mesh and creates polygon objects
2031
+ // for use in CSG operations. Handles material groups, transformations, and validation.
2032
+ // @param obj - The THREE.js Mesh object to convert.
2033
+ // @param objectIndex - Material index to assign to all polygons (optional).
2034
+ // @param polytree - Existing Polytree to add polygons to (optional, creates new if not provided).
2035
+ // @param buildTargetPolytree - Whether to build the spatial tree structure (default: true).
2036
+ // @return Polytree instance containing the mesh data as polygons.
2037
+ Polytree.fromMesh = function(obj, objectIndex, polytree = new Polytree(), buildTargetPolytree = true) {
2038
+ var color, colorattr, geometry, group, groups, i, index, j, k, l, len, m, n, normal, normalattr, polygon, polys, pos, posattr, positionIndex, ref, ref1, uvCoords, uvIndex, uvattr, vertexIndex, vertices;
2039
+ if (obj.isPolytree) {
2040
+ // Return early if object is already a Polytree.
2041
+ return obj;
2042
+ }
2043
+ // Store original matrix world for ray intersection calculations.
2044
+ if (Polytree.rayIntersectTriangleType === "regular") {
2045
+ polytree.originalMatrixWorld = obj.matrixWorld.clone();
2046
+ }
2047
+ // Update transformation matrices and extract geometry attributes.
2048
+ obj.updateWorldMatrix(true, true);
2049
+ geometry = obj.geometry;
2050
+ temporaryMatrixWithNormalCalc.getNormalMatrix(obj.matrix);
2051
+ groups = geometry.groups;
2052
+ uvattr = geometry.attributes.uv;
2053
+ colorattr = geometry.attributes.color;
2054
+ posattr = geometry.attributes.position;
2055
+ normalattr = geometry.attributes.normal;
2056
+ // Generate index array (explicit or implicit).
2057
+ index = geometry.index ? geometry.index.array : Array((posattr.array.length / posattr.itemSize) | 0).fill().map(function(_, i) {
2058
+ return i;
2059
+ });
2060
+ polys = []; // Process each triangle in the geometry.
2061
+ for (i = k = 0, ref = index.length; k < ref; i = k += 3) {
2062
+ vertices = [];
2063
+ // Create vertices for each triangle corner.
2064
+ for (j = l = 0; l < 3; j = ++l) {
2065
+ vertexIndex = index[i + j];
2066
+ positionIndex = vertexIndex * 3;
2067
+ uvIndex = vertexIndex * 2;
2068
+ // Extract and transform vertex position.
2069
+ pos = new Vector3(posattr.array[positionIndex], posattr.array[positionIndex + 1], posattr.array[positionIndex + 2]);
2070
+ normal = new Vector3(normalattr.array[positionIndex], normalattr.array[positionIndex + 1], normalattr.array[positionIndex + 2]);
2071
+ pos.applyMatrix4(obj.matrix);
2072
+ normal.applyMatrix3(temporaryMatrixWithNormalCalc);
2073
+ // Extract UV coordinates if available.
2074
+ uvCoords = uvattr ? {
2075
+ x: uvattr.array[uvIndex],
2076
+ y: uvattr.array[uvIndex + 1]
2077
+ } : void 0;
2078
+ // Extract vertex color if available.
2079
+ color = colorattr ? {
2080
+ x: colorattr.array[uvIndex],
2081
+ y: colorattr.array[uvIndex + 1],
2082
+ z: colorattr.array[uvIndex + 2]
2083
+ } : void 0;
2084
+ vertices.push(new Vertex(pos, normal, uvCoords, color));
2085
+ }
2086
+ // Determine material index from geometry groups or use provided objectIndex.
2087
+ if ((objectIndex === void 0) && groups && groups.length > 0) {
2088
+ polygon = void 0;
2089
+ // Find which material group this triangle belongs to.
2090
+ for (m = 0, len = groups.length; m < len; m++) {
2091
+ group = groups[m];
2092
+ if ((index[i] >= group.start) && (index[i] < (group.start + group.count))) {
2093
+ polygon = new Polygon(vertices, group.materialIndex);
2094
+ polygon.originalValid = true;
2095
+ }
2096
+ }
2097
+ if (polygon) {
2098
+ polys.push(polygon);
2099
+ }
2100
+ } else {
2101
+ // Use provided objectIndex for all polygons.
2102
+ polygon = new Polygon(vertices, objectIndex);
2103
+ polygon.originalValid = true;
2104
+ polys.push(polygon);
2105
+ }
2106
+ }
2107
+ // Add valid triangles to the polytree and clean up invalid ones.
2108
+ for (i = n = 0, ref1 = polys.length; (0 <= ref1 ? n < ref1 : n > ref1); i = 0 <= ref1 ? ++n : --n) {
2109
+ if (isValidTriangle(polys[i].triangle)) {
2110
+ polytree.addPolygon(polys[i]);
2111
+ } else {
2112
+ polys[i].delete();
2113
+ }
2114
+ }
2115
+ // Build spatial tree structure if requested.
2116
+ buildTargetPolytree && polytree.buildTree();
2117
+ // Store reference to original mesh for non-ray operations.
2118
+ if (Polytree.usePolytreeRay !== true) {
2119
+ polytree.mesh = obj;
2120
+ }
2121
+ return polytree;
2122
+ };
2123
+ // Generated by CoffeeScript 2.7.0
2124
+ // ============================================================================
2125
+ // Unite CSG Operation for Polytree
2126
+ // ============================================================================
2127
+ // This module implements the union (unite) boolean operation for Constructive
2128
+ // Solid Geometry (CSG). The unite operation combines two 3D objects by merging
2129
+ // all their surfaces while removing internal/overlapping geometry.
2130
+ // Algorithm Overview:
2131
+ // The unite operation preserves polygons from both objects that contribute to
2132
+ // the external surface of the combined result. It removes:
2133
+ // - Polygons from A that are inside B or coplanar-back with B
2134
+ // - Polygons from B that are inside A or coplanar-back/front with A
2135
+ // The operation supports both mesh-to-mesh and polytree-to-polytree inputs
2136
+ // for backward compatibility and provides optimized processing paths.
2137
+ // === POLYGON CLASSIFICATION RULES FOR UNITE OPERATION ===
2138
+ // Define which polygons to remove during unite operation.
2139
+ // Rules specify polygon states that should be deleted from each object.
2140
+ var uniteRules;
2141
+ uniteRules = {
2142
+ // Rules for object A: Remove polygons that are inside B or coplanar-back with B.
2143
+ a: [
2144
+ {
2145
+ array: true,
2146
+ rule: ["inside",
2147
+ "coplanar-back"]
2148
+ },
2149
+ {
2150
+ array: false,
2151
+ rule: "inside"
2152
+ }
2153
+ ],
2154
+ // Rules for object B: Remove polygons inside A or coplanar with A.
2155
+ b: [
2156
+ {
2157
+ array: true,
2158
+ rule: ["inside",
2159
+ "coplanar-back"]
2160
+ },
2161
+ {
2162
+ array: true,
2163
+ rule: ["inside",
2164
+ "coplanar-front"]
2165
+ },
2166
+ {
2167
+ array: false,
2168
+ rule: "inside"
2169
+ }
2170
+ ]
2171
+ };
2172
+ // === PUBLIC CSG OPERATIONS ===
2173
+ // Perform unite (union) operation between two 3D objects.
2174
+ // This is the main entry point that handles both mesh and polytree inputs.
2175
+ // @param mesh1 - First 3D object (Three.js Mesh or Polytree instance).
2176
+ // @param mesh2 - Second 3D object (Three.js Mesh or Polytree instance).
2177
+ // @param async - Whether to perform operation asynchronously (default: true).
2178
+ // @return Three.js Mesh with united geometry, Polytree instance, or Promise.
2179
+ Polytree.unite = function(mesh1, mesh2, async = true) {
2180
+ var polytreeA, polytreeB, resultMesh, resultPolytree, targetMaterial;
2181
+ // Handle async request - delegate to async module.
2182
+ if (async) {
2183
+ // Convert meshes to polytrees if needed for async processing.
2184
+ if (mesh1.isPolytree && mesh2.isPolytree) {
2185
+ return Polytree.async.unite(mesh1, mesh2, true);
2186
+ } else {
2187
+ // Convert meshes to polytrees and return Promise that resolves to mesh.
2188
+ polytreeA = Polytree.fromMesh(mesh1);
2189
+ polytreeB = Polytree.fromMesh(mesh2);
2190
+ // Get material from first mesh for final result.
2191
+ targetMaterial = Array.isArray(mesh1.material) ? mesh1.material[0] : mesh1.material;
2192
+ targetMaterial = targetMaterial.clone();
2193
+ return Polytree.async.unite(polytreeA, polytreeB, false).then(function(resultPolytree) {
2194
+ var resultMesh;
2195
+ resultMesh = Polytree.toMesh(resultPolytree, targetMaterial);
2196
+ disposePolytreeResources(polytreeA, polytreeB, resultPolytree);
2197
+ return resultMesh;
2198
+ });
2199
+ }
2200
+ }
2201
+ // Handle both mesh and polytree inputs for backward compatibility.
2202
+ if (mesh1.isPolytree && mesh2.isPolytree) {
2203
+ // Direct polytree-to-polytree operation - most efficient path.
2204
+ polytreeA = mesh1;
2205
+ polytreeB = mesh2;
2206
+ return this.uniteCore(polytreeA, polytreeB, true);
2207
+ } else {
2208
+ // Mesh-to-mesh operation (default behavior) - converts to polytrees internally.
2209
+ polytreeA = Polytree.fromMesh(mesh1);
2210
+ polytreeB = Polytree.fromMesh(mesh2);
2211
+ // Always use material from first mesh (cloned for safety).
2212
+ targetMaterial = Array.isArray(mesh1.material) ? mesh1.material[0] : mesh1.material;
2213
+ targetMaterial = targetMaterial.clone();
2214
+ // Perform unite operation and convert result back to mesh.
2215
+ resultPolytree = this.uniteCore(polytreeA, polytreeB, false);
2216
+ resultMesh = Polytree.toMesh(resultPolytree, targetMaterial);
2217
+ disposePolytreeResources(polytreeA, polytreeB, resultPolytree);
2218
+ return resultMesh;
2219
+ }
2220
+ };
2221
+ // === CORE CSG IMPLEMENTATION ===
2222
+ // Core unite operation implementation working with polytree objects.
2223
+ // This method performs the actual CSG boolean logic for union operations.
2224
+ // @param polytreeA - First polytree object to unite.
2225
+ // @param polytreeB - Second polytree object to unite.
2226
+ // @param buildTargetPolytree - Whether to build spatial tree structure in result.
2227
+ // @return Polytree containing the united geometry.
2228
+ Polytree.uniteCore = function(polytreeA, polytreeB, buildTargetPolytree = true) {
2229
+ var currentMeshSideA, currentMeshSideB, polytree, trianglesSet;
2230
+ // Initialize result polytree and triangle tracking set.
2231
+ polytree = new Polytree();
2232
+ trianglesSet = new Set();
2233
+ // Only process intersection if bounding boxes overlap.
2234
+ if (polytreeA.box.intersectsBox(polytreeB.box)) {
2235
+ // Store original material sides for restoration later.
2236
+ currentMeshSideA = void 0;
2237
+ currentMeshSideB = void 0;
2238
+ // Temporarily set materials to DoubleSide for accurate intersection detection.
2239
+ if (polytreeA.mesh) {
2240
+ currentMeshSideA = polytreeA.mesh.material.side;
2241
+ polytreeA.mesh.material.side = DoubleSide;
2242
+ }
2243
+ if (polytreeB.mesh) {
2244
+ currentMeshSideB = polytreeB.mesh.material.side;
2245
+ polytreeB.mesh.material.side = DoubleSide;
2246
+ }
2247
+ // === CSG PROCESSING PIPELINE ===
2248
+ // Step 1: Reset polygon states for fresh classification.
2249
+ polytreeA.resetPolygons(false);
2250
+ polytreeB.resetPolygons(false);
2251
+ // Step 2: Mark polygons that intersect between the two objects.
2252
+ // Note: Method name has typo but is consistently used throughout codebase.
2253
+ polytreeA.markIntesectingPolygons(polytreeB);
2254
+ polytreeB.markIntesectingPolygons(polytreeA);
2255
+ // Step 3: Handle intersecting polygons by splitting and classifying them.
2256
+ handleIntersectingPolytrees(polytreeA, polytreeB);
2257
+ // Step 4: Clean up replaced polygons from splitting operations.
2258
+ polytreeA.deleteReplacedPolygons();
2259
+ polytreeB.deleteReplacedPolygons();
2260
+ // Step 5: Apply unite-specific rules to remove unwanted polygons.
2261
+ polytreeA.deletePolygonsByStateRules(uniteRules.a);
2262
+ polytreeB.deletePolygonsByStateRules(uniteRules.b);
2263
+ // Step 6: Copy remaining polygons to result polytree.
2264
+ polytreeA.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2265
+ polytreeB.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2266
+ // Restore original material sides.
2267
+ if (polytreeA.mesh && polytreeA.mesh.material.side !== currentMeshSideA) {
2268
+ polytreeA.mesh.material.side = currentMeshSideA;
2269
+ }
2270
+ if (polytreeB.mesh && polytreeB.mesh.material.side !== currentMeshSideB) {
2271
+ polytreeB.mesh.material.side = currentMeshSideB;
2272
+ }
2273
+ } else {
2274
+ // No intersection detected - simply combine all polygons from both objects.
2275
+ polytreeA.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2276
+ polytreeB.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2277
+ }
2278
+ // === FINALIZATION ===
2279
+ // Clean up temporary triangle tracking.
2280
+ trianglesSet.clear();
2281
+ trianglesSet = void 0;
2282
+ // Mark polygons as original (not generated by CSG operations).
2283
+ polytree.markPolygonsAsOriginal();
2284
+ // Build spatial tree structure if requested for optimization.
2285
+ buildTargetPolytree && polytree.buildTree();
2286
+ return polytree;
2287
+ };
2288
+ // Generated by CoffeeScript 2.7.0
2289
+ // ============================================================================
2290
+ // Subtract CSG Operation for Polytree
2291
+ // ============================================================================
2292
+ // This module implements the subtraction (difference) boolean operation for
2293
+ // Constructive Solid Geometry (CSG). The subtract operation removes the volume
2294
+ // of the second object from the first object, creating a carved-out result.
2295
+ // Algorithm Overview:
2296
+ // The subtract operation preserves polygons that define the external surface
2297
+ // of object A minus the volume of object B. It processes:
2298
+ // - Polygons from A that are outside B or coplanar-front with B
2299
+ // - Polygons from B that are inside A, inverted to form internal surfaces
2300
+ // The operation supports both mesh-to-mesh and polytree-to-polytree inputs
2301
+ // for backward compatibility and provides optimized processing paths.
2302
+ // === POLYGON CLASSIFICATION RULES FOR SUBTRACT OPERATION ===
2303
+ // Define which polygons to remove during subtract operation.
2304
+ // Rules specify polygon states that should be deleted from each object.
2305
+ var subtractRules;
2306
+ subtractRules = {
2307
+ // Rules for object A: Remove polygons that are inside B.
2308
+ a: [
2309
+ {
2310
+ array: true,
2311
+ rule: ["inside",
2312
+ "coplanar-back"]
2313
+ },
2314
+ {
2315
+ array: true,
2316
+ rule: ["inside",
2317
+ "coplanar-front"]
2318
+ },
2319
+ {
2320
+ array: false,
2321
+ rule: "inside"
2322
+ }
2323
+ ],
2324
+ // Rules for object B: Remove polygons that are outside A.
2325
+ b: [
2326
+ {
2327
+ array: true,
2328
+ rule: ["outside",
2329
+ "coplanar-back"]
2330
+ },
2331
+ {
2332
+ array: true,
2333
+ rule: ["outside",
2334
+ "coplanar-front"]
2335
+ },
2336
+ {
2337
+ array: true,
2338
+ rule: ["inside",
2339
+ "coplanar-front"]
2340
+ },
2341
+ {
2342
+ array: false,
2343
+ rule: "outside"
2344
+ }
2345
+ ]
2346
+ };
2347
+ // === PUBLIC CSG OPERATIONS ===
2348
+ // Perform subtract (difference) operation between two 3D objects.
2349
+ // This removes the volume of mesh2 from mesh1, creating a carved result.
2350
+ // @param mesh1 - Primary 3D object to subtract from (Three.js Mesh or Polytree instance).
2351
+ // @param mesh2 - 3D object to subtract away (Three.js Mesh or Polytree instance).
2352
+ // @param async - Whether to perform operation asynchronously (default: true).
2353
+ // @return Three.js Mesh with subtracted geometry, Polytree instance, or Promise.
2354
+ Polytree.subtract = function(mesh1, mesh2, async = true) {
2355
+ var polytreeA, polytreeB, resultMesh, resultPolytree, targetMaterial;
2356
+ // Handle async request - delegate to async module.
2357
+ if (async) {
2358
+ // Convert meshes to polytrees if needed for async processing.
2359
+ if (mesh1.isPolytree && mesh2.isPolytree) {
2360
+ return Polytree.async.subtract(mesh1, mesh2, true);
2361
+ } else {
2362
+ // Convert meshes to polytrees and return Promise that resolves to mesh.
2363
+ polytreeA = Polytree.fromMesh(mesh1);
2364
+ polytreeB = Polytree.fromMesh(mesh2);
2365
+ // Get material from first mesh for final result.
2366
+ targetMaterial = Array.isArray(mesh1.material) ? mesh1.material[0] : mesh1.material;
2367
+ targetMaterial = targetMaterial.clone();
2368
+ return Polytree.async.subtract(polytreeA, polytreeB, false).then(function(resultPolytree) {
2369
+ var resultMesh;
2370
+ resultMesh = Polytree.toMesh(resultPolytree, targetMaterial);
2371
+ disposePolytreeResources(polytreeA, polytreeB, resultPolytree);
2372
+ return resultMesh;
2373
+ });
2374
+ }
2375
+ }
2376
+ // Handle both mesh and polytree inputs for backward compatibility.
2377
+ if (mesh1.isPolytree && mesh2.isPolytree) {
2378
+ // Direct polytree-to-polytree operation - most efficient path.
2379
+ polytreeA = mesh1;
2380
+ polytreeB = mesh2;
2381
+ return this.subtractCore(polytreeA, polytreeB, true);
2382
+ } else {
2383
+ // Mesh-to-mesh operation (default behavior) - converts to polytrees internally.
2384
+ polytreeA = Polytree.fromMesh(mesh1);
2385
+ polytreeB = Polytree.fromMesh(mesh2);
2386
+ // Always use material from first mesh (cloned for safety).
2387
+ targetMaterial = Array.isArray(mesh1.material) ? mesh1.material[0] : mesh1.material;
2388
+ targetMaterial = targetMaterial.clone();
2389
+ // Perform subtract operation and convert result back to mesh.
2390
+ resultPolytree = this.subtractCore(polytreeA, polytreeB, false);
2391
+ resultMesh = Polytree.toMesh(resultPolytree, targetMaterial);
2392
+ disposePolytreeResources(polytreeA, polytreeB, resultPolytree);
2393
+ return resultMesh;
2394
+ }
2395
+ };
2396
+ // === CORE CSG IMPLEMENTATION ===
2397
+ // Core subtract operation implementation working with polytree objects.
2398
+ // This method performs the actual CSG boolean logic for difference operations.
2399
+ // @param polytreeA - Primary polytree object to subtract from.
2400
+ // @param polytreeB - Polytree object to subtract away from A.
2401
+ // @param buildTargetPolytree - Whether to build spatial tree structure in result.
2402
+ // @return Polytree containing the subtracted geometry.
2403
+ Polytree.subtractCore = function(polytreeA, polytreeB, buildTargetPolytree = true) {
2404
+ var currentMeshSideA, currentMeshSideB, polytree, trianglesSet;
2405
+ // Initialize result polytree and triangle tracking set.
2406
+ polytree = new Polytree();
2407
+ trianglesSet = new Set();
2408
+ // Only process intersection if bounding boxes overlap.
2409
+ if (polytreeA.box.intersectsBox(polytreeB.box)) {
2410
+ // Store original material sides for restoration later.
2411
+ currentMeshSideA = void 0;
2412
+ currentMeshSideB = void 0;
2413
+ // Temporarily set materials to DoubleSide for accurate intersection detection.
2414
+ if (polytreeA.mesh) {
2415
+ currentMeshSideA = polytreeA.mesh.material.side;
2416
+ polytreeA.mesh.material.side = DoubleSide;
2417
+ }
2418
+ if (polytreeB.mesh) {
2419
+ currentMeshSideB = polytreeB.mesh.material.side;
2420
+ polytreeB.mesh.material.side = DoubleSide;
2421
+ }
2422
+ // === CSG PROCESSING PIPELINE ===
2423
+ // Step 1: Reset polygon states for fresh classification.
2424
+ polytreeA.resetPolygons(false);
2425
+ polytreeB.resetPolygons(false);
2426
+ // Step 2: Mark polygons that intersect between the two objects.
2427
+ // Note: Method name has typo but is consistently used throughout codebase.
2428
+ polytreeA.markIntesectingPolygons(polytreeB);
2429
+ polytreeB.markIntesectingPolygons(polytreeA);
2430
+ // Step 3: Handle intersecting polygons by splitting and classifying them.
2431
+ handleIntersectingPolytrees(polytreeA, polytreeB);
2432
+ // Step 4: Clean up replaced polygons from splitting operations.
2433
+ polytreeA.deleteReplacedPolygons();
2434
+ polytreeB.deleteReplacedPolygons();
2435
+ // Step 5: Apply subtract-specific rules to remove unwanted polygons.
2436
+ polytreeA.deletePolygonsByStateRules(subtractRules.a);
2437
+ polytreeB.deletePolygonsByStateRules(subtractRules.b);
2438
+ // Step 6: Special processing for subtract operation.
2439
+ // Remove intersecting polygons from B and invert B's normals for cavity formation.
2440
+ polytreeB.deletePolygonsByIntersection(false);
2441
+ polytreeB.invert();
2442
+ // Step 7: Copy remaining polygons to result polytree.
2443
+ polytreeA.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2444
+ polytreeB.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2445
+ // Restore original material sides.
2446
+ if (polytreeA.mesh && polytreeA.mesh.material.side !== currentMeshSideA) {
2447
+ polytreeA.mesh.material.side = currentMeshSideA;
2448
+ }
2449
+ if (polytreeB.mesh && polytreeB.mesh.material.side !== currentMeshSideB) {
2450
+ polytreeB.mesh.material.side = currentMeshSideB;
2451
+ }
2452
+ } else {
2453
+ // No intersection detected - return only object A since B doesn't affect it.
2454
+ polytreeA.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2455
+ }
2456
+ // === FINALIZATION ===
2457
+ // Clean up temporary triangle tracking.
2458
+ trianglesSet.clear();
2459
+ trianglesSet = void 0;
2460
+ // Mark polygons as original (not generated by CSG operations).
2461
+ polytree.markPolygonsAsOriginal();
2462
+ // Build spatial tree structure if requested for optimization.
2463
+ buildTargetPolytree && polytree.buildTree();
2464
+ return polytree;
2465
+ };
2466
+ // Generated by CoffeeScript 2.7.0
2467
+ // ============================================================================
2468
+ // Intersect CSG Operation for Polytree
2469
+ // ============================================================================
2470
+ // This module implements the intersection boolean operation for Constructive
2471
+ // Solid Geometry (CSG). The intersect operation keeps only the overlapping
2472
+ // volume that is common to both input objects.
2473
+ // Algorithm Overview:
2474
+ // The intersect operation preserves only polygons that contribute to the
2475
+ // volume where both objects overlap. It removes extensive sets of polygons:
2476
+ // - From A: inside+coplanar-back, outside+coplanar-front/back, and outside
2477
+ // - From B: inside+coplanar-front/back, outside+coplanar-front/back, and outside
2478
+ // The operation supports both mesh-to-mesh and polytree-to-polytree inputs
2479
+ // for backward compatibility and provides optimized processing paths.
2480
+ // === POLYGON CLASSIFICATION RULES FOR INTERSECT OPERATION ===
2481
+ // Define which polygons to remove during intersect operation.
2482
+ // Rules specify polygon states that should be deleted from each object.
2483
+ // Intersect keeps only the overlapping volume, so most polygons are removed.
2484
+ var intersectRules;
2485
+ intersectRules = {
2486
+ // Rules for object A: Remove inside+coplanar-back, outside+coplanar-front/back, and outside.
2487
+ a: [
2488
+ {
2489
+ array: true,
2490
+ rule: ["inside",
2491
+ "coplanar-back"]
2492
+ },
2493
+ {
2494
+ array: true,
2495
+ rule: ["outside",
2496
+ "coplanar-front"]
2497
+ },
2498
+ {
2499
+ array: true,
2500
+ rule: ["outside",
2501
+ "coplanar-back"]
2502
+ },
2503
+ {
2504
+ array: false,
2505
+ rule: "outside"
2506
+ }
2507
+ ],
2508
+ // Rules for object B: Remove inside+coplanar-front/back, outside+coplanar-front/back, and outside.
2509
+ b: [
2510
+ {
2511
+ array: true,
2512
+ rule: ["inside",
2513
+ "coplanar-front"]
2514
+ },
2515
+ {
2516
+ array: true,
2517
+ rule: ["inside",
2518
+ "coplanar-back"]
2519
+ },
2520
+ {
2521
+ array: true,
2522
+ rule: ["outside",
2523
+ "coplanar-front"]
2524
+ },
2525
+ {
2526
+ array: true,
2527
+ rule: ["outside",
2528
+ "coplanar-back"]
2529
+ },
2530
+ {
2531
+ array: false,
2532
+ rule: "outside"
2533
+ }
2534
+ ]
2535
+ };
2536
+ // === PUBLIC CSG OPERATIONS ===
2537
+ // Perform intersect operation between two 3D objects.
2538
+ // This keeps only the overlapping volume common to both objects.
2539
+ // @param mesh1 - First 3D object to intersect (Three.js Mesh or Polytree instance).
2540
+ // @param mesh2 - Second 3D object to intersect (Three.js Mesh or Polytree instance).
2541
+ // @param async - Whether to perform operation asynchronously (default: true).
2542
+ // @return Three.js Mesh with intersected geometry, Polytree instance, or Promise.
2543
+ Polytree.intersect = function(mesh1, mesh2, async = true) {
2544
+ var polytreeA, polytreeB, resultMesh, resultPolytree, targetMaterial;
2545
+ // Handle async request - delegate to async module.
2546
+ if (async) {
2547
+ // Convert meshes to polytrees if needed for async processing.
2548
+ if (mesh1.isPolytree && mesh2.isPolytree) {
2549
+ return Polytree.async.intersect(mesh1, mesh2, true);
2550
+ } else {
2551
+ // Convert meshes to polytrees and return Promise that resolves to mesh.
2552
+ polytreeA = Polytree.fromMesh(mesh1);
2553
+ polytreeB = Polytree.fromMesh(mesh2);
2554
+ // Get material from first mesh for final result.
2555
+ targetMaterial = Array.isArray(mesh1.material) ? mesh1.material[0] : mesh1.material;
2556
+ targetMaterial = targetMaterial.clone();
2557
+ return Polytree.async.intersect(polytreeA, polytreeB, false).then(function(resultPolytree) {
2558
+ var resultMesh;
2559
+ resultMesh = Polytree.toMesh(resultPolytree, targetMaterial);
2560
+ disposePolytreeResources(polytreeA, polytreeB, resultPolytree);
2561
+ return resultMesh;
2562
+ });
2563
+ }
2564
+ }
2565
+ // Handle both mesh and polytree inputs for backward compatibility.
2566
+ if (mesh1.isPolytree && mesh2.isPolytree) {
2567
+ // Direct polytree-to-polytree operation - most efficient path.
2568
+ polytreeA = mesh1;
2569
+ polytreeB = mesh2;
2570
+ return this.intersectCore(polytreeA, polytreeB, true);
2571
+ } else {
2572
+ // Mesh-to-mesh operation (default behavior) - converts to polytrees internally.
2573
+ polytreeA = Polytree.fromMesh(mesh1);
2574
+ polytreeB = Polytree.fromMesh(mesh2);
2575
+ // Always use material from first mesh (cloned for safety).
2576
+ targetMaterial = Array.isArray(mesh1.material) ? mesh1.material[0] : mesh1.material;
2577
+ targetMaterial = targetMaterial.clone();
2578
+ // Perform intersect operation and convert result back to mesh.
2579
+ resultPolytree = this.intersectCore(polytreeA, polytreeB, false);
2580
+ resultMesh = Polytree.toMesh(resultPolytree, targetMaterial);
2581
+ disposePolytreeResources(polytreeA, polytreeB, resultPolytree);
2582
+ return resultMesh;
2583
+ }
2584
+ };
2585
+ // === CORE CSG IMPLEMENTATION ===
2586
+ // Core intersect operation implementation working with polytree objects.
2587
+ // This method performs the actual CSG boolean logic for intersection operations.
2588
+ // @param polytreeA - First polytree object to intersect.
2589
+ // @param polytreeB - Second polytree object to intersect.
2590
+ // @param buildTargetPolytree - Whether to build spatial tree structure in result.
2591
+ // @return Polytree containing the intersected geometry.
2592
+ Polytree.intersectCore = function(polytreeA, polytreeB, buildTargetPolytree = true) {
2593
+ var currentMeshSideA, currentMeshSideB, polytree, trianglesSet;
2594
+ // Initialize result polytree and triangle tracking set.
2595
+ polytree = new Polytree();
2596
+ trianglesSet = new Set();
2597
+ // Only process intersection if bounding boxes overlap.
2598
+ if (polytreeA.box.intersectsBox(polytreeB.box)) {
2599
+ // Store original material sides for restoration later.
2600
+ currentMeshSideA = void 0;
2601
+ currentMeshSideB = void 0;
2602
+ // Temporarily set materials to DoubleSide for accurate intersection detection.
2603
+ if (polytreeA.mesh) {
2604
+ currentMeshSideA = polytreeA.mesh.material.side;
2605
+ polytreeA.mesh.material.side = DoubleSide;
2606
+ }
2607
+ if (polytreeB.mesh) {
2608
+ currentMeshSideB = polytreeB.mesh.material.side;
2609
+ polytreeB.mesh.material.side = DoubleSide;
2610
+ }
2611
+ // === CSG PROCESSING PIPELINE ===
2612
+ // Step 1: Reset polygon states for fresh classification.
2613
+ polytreeA.resetPolygons(false);
2614
+ polytreeB.resetPolygons(false);
2615
+ // Step 2: Mark polygons that intersect between the two objects.
2616
+ // Note: Method name has typo but is consistently used throughout codebase.
2617
+ polytreeA.markIntesectingPolygons(polytreeB);
2618
+ polytreeB.markIntesectingPolygons(polytreeA);
2619
+ // Step 3: Handle intersecting polygons by splitting and classifying them.
2620
+ handleIntersectingPolytrees(polytreeA, polytreeB);
2621
+ // Step 4: Clean up replaced polygons from splitting operations.
2622
+ polytreeA.deleteReplacedPolygons();
2623
+ polytreeB.deleteReplacedPolygons();
2624
+ // Step 5: Apply intersect-specific rules to remove unwanted polygons.
2625
+ polytreeA.deletePolygonsByStateRules(intersectRules.a);
2626
+ polytreeB.deletePolygonsByStateRules(intersectRules.b);
2627
+ // Step 6: Special processing for intersect operation.
2628
+ // Remove intersecting polygons from both objects to clean up overlapping geometry.
2629
+ polytreeA.deletePolygonsByIntersection(false);
2630
+ polytreeB.deletePolygonsByIntersection(false);
2631
+ // Step 7: Copy remaining polygons to result polytree.
2632
+ polytreeA.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2633
+ polytreeB.getPolygonCloneCallback(polytree.addPolygon.bind(polytree), trianglesSet);
2634
+ // Restore original material sides.
2635
+ if (polytreeA.mesh && polytreeA.mesh.material.side !== currentMeshSideA) {
2636
+ polytreeA.mesh.material.side = currentMeshSideA;
2637
+ }
2638
+ if (polytreeB.mesh && polytreeB.mesh.material.side !== currentMeshSideB) {
2639
+ polytreeB.mesh.material.side = currentMeshSideB;
2640
+ }
2641
+ }
2642
+ // Note: If no intersection, result is empty (no polygons added).
2643
+ // === FINALIZATION ===
2644
+ // Clean up temporary triangle tracking.
2645
+ trianglesSet.clear();
2646
+ trianglesSet = void 0;
2647
+ // Mark polygons as original (not generated by CSG operations).
2648
+ polytree.markPolygonsAsOriginal();
2649
+ // Build spatial tree structure if requested for optimization.
2650
+ buildTargetPolytree && polytree.buildTree();
2651
+ return polytree;
2652
+ };
2653
+ // Generated by CoffeeScript 2.7.0
2654
+ // === ASYNC CSG OPERATIONS FOR POLYTREE ===
2655
+ // This module provides asynchronous implementations of Constructive Solid Geometry (CSG) operations.
2656
+ // All operations are Promise-based and include proper resource management and error handling.
2657
+ // The async operations allow for better performance in web environments by preventing UI blocking.
2658
+ Polytree.async = {
2659
+ // Default batch size for processing large arrays of objects.
2660
+ // Objects arrays larger than this size will be processed in batches to prevent memory issues.
2661
+ batchSize: DEFAULT_BUFFER_SIZE,
2662
+ // Maximum worker threads hint for parallel processing (informational).
2663
+ // Set to Infinity for unlimited parallelism when supported by the environment.
2664
+ maxWorkerThreads: MAX_WORKER_THREADS,
2665
+ // Perform asynchronous union operation between two polytree objects.
2666
+ // Creates a new polytree containing the combined volume of both input polytrees.
2667
+ // @param polytreeA - First polytree operand for the union operation.
2668
+ // @param polytreeB - Second polytree operand for the union operation.
2669
+ // @param buildTargetPolytree - Whether to build the target polytree structure (default: true).
2670
+ // @return Promise that resolves to the resulting polytree from the union operation.
2671
+ unite: function(polytreeA, polytreeB, buildTargetPolytree = true) {
2672
+ return new Promise(function(resolve, reject) {
2673
+ var error, result, timeoutId;
2674
+ timeoutId = null; // Set up operation timeout only if not infinite.
2675
+ if (ASYNC_OPERATION_TIMEOUT !== 2e308) {
2676
+ timeoutId = setTimeout(function() {
2677
+ return reject(new Error(`Union operation timed out after ${ASYNC_OPERATION_TIMEOUT}ms`));
2678
+ }, ASYNC_OPERATION_TIMEOUT);
2679
+ }
2680
+ try {
2681
+ result = Polytree.uniteCore(polytreeA, polytreeB, buildTargetPolytree);
2682
+ disposePolytreeResources(polytreeA, polytreeB);
2683
+ if (timeoutId) {
2684
+ clearTimeout(timeoutId);
2685
+ }
2686
+ return resolve(result);
2687
+ } catch (error1) {
2688
+ error = error1;
2689
+ if (timeoutId) {
2690
+ clearTimeout(timeoutId);
2691
+ }
2692
+ disposePolytreeResources(polytreeA, polytreeB);
2693
+ return reject(error);
2694
+ }
2695
+ });
2696
+ },
2697
+ // Perform asynchronous subtraction operation between two polytree objects.
2698
+ // Creates a new polytree by removing the volume of polytreeB from polytreeA.
2699
+ // @param polytreeA - The polytree to subtract from (minuend).
2700
+ // @param polytreeB - The polytree to subtract (subtrahend).
2701
+ // @param buildTargetPolytree - Whether to build the target polytree structure (default: true).
2702
+ // @return Promise that resolves to the resulting polytree from the subtraction operation.
2703
+ subtract: function(polytreeA, polytreeB, buildTargetPolytree = true) {
2704
+ return new Promise(function(resolve, reject) {
2705
+ var error, result, timeoutId;
2706
+ timeoutId = null; // Set up operation timeout only if not infinite.
2707
+ if (ASYNC_OPERATION_TIMEOUT !== 2e308) {
2708
+ timeoutId = setTimeout(function() {
2709
+ return reject(new Error(`Subtract operation timed out after ${ASYNC_OPERATION_TIMEOUT}ms`));
2710
+ }, ASYNC_OPERATION_TIMEOUT);
2711
+ }
2712
+ try {
2713
+ result = Polytree.subtractCore(polytreeA, polytreeB, buildTargetPolytree);
2714
+ disposePolytreeResources(polytreeA, polytreeB);
2715
+ if (timeoutId) {
2716
+ clearTimeout(timeoutId);
2717
+ }
2718
+ return resolve(result);
2719
+ } catch (error1) {
2720
+ error = error1;
2721
+ if (timeoutId) {
2722
+ clearTimeout(timeoutId);
2723
+ }
2724
+ disposePolytreeResources(polytreeA, polytreeB);
2725
+ return reject(error);
2726
+ }
2727
+ });
2728
+ },
2729
+ // Perform asynchronous intersection operation between two polytree objects.
2730
+ // Creates a new polytree containing only the overlapping volume of both input polytrees.
2731
+ // @param polytreeA - First polytree operand for the intersection operation.
2732
+ // @param polytreeB - Second polytree operand for the intersection operation.
2733
+ // @param buildTargetPolytree - Whether to build the target polytree structure (default: true).
2734
+ // @return Promise that resolves to the resulting polytree from the intersection operation.
2735
+ intersect: function(polytreeA, polytreeB, buildTargetPolytree = true) {
2736
+ return new Promise(function(resolve, reject) {
2737
+ var error, result, timeoutId;
2738
+ timeoutId = null; // Set up operation timeout only if not infinite.
2739
+ if (ASYNC_OPERATION_TIMEOUT !== 2e308) {
2740
+ timeoutId = setTimeout(function() {
2741
+ return reject(new Error(`Intersect operation timed out after ${ASYNC_OPERATION_TIMEOUT}ms`));
2742
+ }, ASYNC_OPERATION_TIMEOUT);
2743
+ }
2744
+ try {
2745
+ result = Polytree.intersectCore(polytreeA, polytreeB, buildTargetPolytree);
2746
+ disposePolytreeResources(polytreeA, polytreeB);
2747
+ if (timeoutId) {
2748
+ clearTimeout(timeoutId);
2749
+ }
2750
+ return resolve(result);
2751
+ } catch (error1) {
2752
+ error = error1;
2753
+ if (timeoutId) {
2754
+ clearTimeout(timeoutId);
2755
+ }
2756
+ disposePolytreeResources(polytreeA, polytreeB);
2757
+ return reject(error);
2758
+ }
2759
+ });
2760
+ },
2761
+ // Perform asynchronous union operation on an array of objects.
2762
+ // Efficiently processes large arrays using batching and parallel execution.
2763
+ // This method can handle arrays of meshes or polytrees and will convert them as needed.
2764
+ // @param objectArray - Array of meshes or polytrees to unite.
2765
+ // @param materialIndexMax - Maximum material index for assignment (default: Infinity).
2766
+ // @return Promise that resolves to a single polytree containing the union of all objects.
2767
+ uniteArray: function(objectArray, materialIndexMax = 2e308) {
2768
+ return Polytree.async.processArrayWithOperation(objectArray, materialIndexMax, Polytree.async.unite, Polytree.async.uniteArray, 'union');
2769
+ },
2770
+ // Perform asynchronous subtraction operation on an array of objects.
2771
+ // Efficiently processes large arrays using batching and parallel execution.
2772
+ // This method subtracts all subsequent objects from the first object in the array.
2773
+ // @param objectArray - Array of meshes or polytrees to process with subtraction.
2774
+ // @param materialIndexMax - Maximum material index for assignment (default: Infinity).
2775
+ // @return Promise that resolves to a single polytree with all subtractions applied.
2776
+ subtractArray: function(objectArray, materialIndexMax = 2e308) {
2777
+ return Polytree.async.processArrayWithOperation(objectArray, materialIndexMax, Polytree.async.subtract, Polytree.async.subtractArray, 'subtraction');
2778
+ },
2779
+ // Perform asynchronous intersection operation on an array of objects.
2780
+ // Efficiently processes large arrays using batching and parallel execution.
2781
+ // This method finds the overlapping volume common to all objects in the array.
2782
+ // @param objectArray - Array of meshes or polytrees to intersect.
2783
+ // @param materialIndexMax - Maximum material index for assignment (default: Infinity).
2784
+ // @return Promise that resolves to a single polytree containing the intersection of all objects.
2785
+ intersectArray: function(objectArray, materialIndexMax = 2e308) {
2786
+ return Polytree.async.processArrayWithOperation(objectArray, materialIndexMax, Polytree.async.intersect, Polytree.async.intersectArray, 'intersection');
2787
+ },
2788
+ // Main operation handler that delegates to the synchronous operation method.
2789
+ // This provides a unified interface for complex CSG operations with async support.
2790
+ // @param operationObject - Object containing the operation definition.
2791
+ // @param returnPolytrees - Whether to return polytree objects instead of meshes.
2792
+ // @param buildTargetPolytree - Whether to build the target polytree structure.
2793
+ // @param options - Configuration options including objCounter for unique IDs.
2794
+ // @param firstRun - Whether this is the top-level operation call.
2795
+ // @return Result of the operation (mesh, polytree, or operation tree).
2796
+ operation: function(operationObject, returnPolytrees = false, buildTargetPolytree = true, options = {
2797
+ objCounter: 0
2798
+ }, firstRun = true) {
2799
+ return Polytree.operation(operationObject, returnPolytrees, buildTargetPolytree, options, firstRun, true);
2800
+ },
2801
+ // Generic helper method for processing array operations with any CSG operation.
2802
+ // This reduces code duplication between uniteArray, subtractArray, and intersectArray.
2803
+ // @param objectArray - Array of meshes or polytrees to process.
2804
+ // @param materialIndexMax - Maximum material index for assignment.
2805
+ // @param operationMethod - The async CSG method to use (unite, subtract, or intersect).
2806
+ // @param arrayOperationMethod - The corresponding array method for recursive calls.
2807
+ // @param operationName - Name of the operation for error messages.
2808
+ // @return Promise that resolves to the final result polytree.
2809
+ processArrayWithOperation: function(objectArray, materialIndexMax, operationMethod, arrayOperationMethod, operationName) {
2810
+ return new Promise(function(resolve, reject) {
2811
+ var batchArray, batchPromise, convertedPolytree, currentBatch, currentBatchIndex, error, i, isMainPolytreeUsed, j, leftOverPolytree, leftOverPromise, mainPolytree, materialIndex, objectIndex, operationPromises, pairPromise, pairStartIndex, polytreesArray, ref, ref1, shouldUseBatching;
2812
+ try {
2813
+ // Determine if we should use batching based on array size and batch configuration.
2814
+ shouldUseBatching = Polytree.async.batchSize > 4 && Polytree.async.batchSize < objectArray.length;
2815
+ mainPolytree = void 0;
2816
+ isMainPolytreeUsed = false;
2817
+ operationPromises = [];
2818
+ if (shouldUseBatching) {
2819
+ // Process large arrays in batches to prevent memory issues.
2820
+ batchArray = [];
2821
+ currentBatchIndex = 0;
2822
+ while (currentBatchIndex < objectArray.length) {
2823
+ batchArray.push(objectArray.slice(currentBatchIndex, currentBatchIndex + Polytree.async.batchSize));
2824
+ currentBatchIndex += Polytree.async.batchSize;
2825
+ }
2826
+ currentBatch = batchArray.shift();
2827
+ while (currentBatch) {
2828
+ batchPromise = arrayOperationMethod(currentBatch, 0);
2829
+ operationPromises.push(batchPromise);
2830
+ currentBatch = batchArray.shift();
2831
+ }
2832
+ // Mark that we're using batching and clear the original array.
2833
+ shouldUseBatching = true;
2834
+ isMainPolytreeUsed = true;
2835
+ objectArray.length = 0;
2836
+ } else {
2837
+ // Process smaller arrays directly without batching.
2838
+ polytreesArray = [];
2839
+ for (objectIndex = i = 0, ref = objectArray.length; (0 <= ref ? i < ref : i > ref); objectIndex = 0 <= ref ? ++i : --i) {
2840
+ materialIndex = objectIndex > materialIndexMax ? materialIndexMax : objectIndex;
2841
+ convertedPolytree = void 0;
2842
+ // Convert mesh to polytree if necessary.
2843
+ if (objectArray[objectIndex].isMesh) {
2844
+ convertedPolytree = Polytree.fromMesh(objectArray[objectIndex], materialIndexMax > -1 ? materialIndex : void 0);
2845
+ } else {
2846
+ convertedPolytree = objectArray[objectIndex];
2847
+ if (materialIndexMax > -1) {
2848
+ convertedPolytree.setPolygonIndex(materialIndex);
2849
+ }
2850
+ }
2851
+ // Set tracking index for debugging and traceability.
2852
+ convertedPolytree.polytreeIndex = objectIndex;
2853
+ polytreesArray.push(convertedPolytree);
2854
+ }
2855
+ // Extract the first polytree as the main polytree.
2856
+ mainPolytree = polytreesArray.shift();
2857
+ leftOverPolytree = void 0;
2858
+ // Process polytrees in pairs for parallel execution.
2859
+ for (pairStartIndex = j = 0, ref1 = polytreesArray.length; j < ref1; pairStartIndex = j += 2) {
2860
+ if (pairStartIndex + 1 >= polytreesArray.length) {
2861
+ // Handle odd number of polytrees - save the leftover.
2862
+ leftOverPolytree = polytreesArray[pairStartIndex];
2863
+ break;
2864
+ }
2865
+ pairPromise = operationMethod(polytreesArray[pairStartIndex], polytreesArray[pairStartIndex + 1]);
2866
+ operationPromises.push(pairPromise);
2867
+ }
2868
+ // If there's a leftover polytree, apply operation with the main polytree.
2869
+ if (leftOverPolytree) {
2870
+ leftOverPromise = operationMethod(mainPolytree, leftOverPolytree);
2871
+ operationPromises.push(leftOverPromise);
2872
+ isMainPolytreeUsed = true;
2873
+ }
2874
+ }
2875
+ // Wait for all parallel operations to complete and process results.
2876
+ return Promise.allSettled(operationPromises).then(function(promiseResults) {
2877
+ var successfulPolytrees;
2878
+ successfulPolytrees = [];
2879
+ // Extract successful results from the promise results.
2880
+ promiseResults.forEach(function(promiseResult) {
2881
+ if (promiseResult.status === "fulfilled") {
2882
+ return successfulPolytrees.push(promiseResult.value);
2883
+ }
2884
+ });
2885
+ // Add the main polytree if it wasn't used in operations.
2886
+ if (!isMainPolytreeUsed) {
2887
+ successfulPolytrees.unshift(mainPolytree);
2888
+ }
2889
+ // Process the final results based on count.
2890
+ if (successfulPolytrees.length > 0) {
2891
+ if (successfulPolytrees.length === 1) {
2892
+ return resolve(successfulPolytrees[0]);
2893
+ } else if (successfulPolytrees.length > 3) {
2894
+ // Use recursive processing for large result sets.
2895
+ return arrayOperationMethod(successfulPolytrees, shouldUseBatching ? 0 : -1).then(function(finalResult) {
2896
+ return resolve(finalResult);
2897
+ }).catch(function(error) {
2898
+ return reject(error);
2899
+ });
2900
+ } else {
2901
+ // Handle 2-3 results directly for efficiency.
2902
+ return operationMethod(successfulPolytrees[0], successfulPolytrees[1]).then(function(intermediateResult) {
2903
+ if (successfulPolytrees.length === 3) {
2904
+ return operationMethod(intermediateResult, successfulPolytrees[2]).then(function(finalResult) {
2905
+ return resolve(finalResult);
2906
+ }).catch(function(error) {
2907
+ return reject(error);
2908
+ });
2909
+ } else {
2910
+ return resolve(intermediateResult);
2911
+ }
2912
+ }).catch(function(error) {
2913
+ return reject(error);
2914
+ });
2915
+ }
2916
+ } else {
2917
+ return reject(`Unable to find any result polytree after ${operationName} operation.`);
2918
+ }
2919
+ });
2920
+ } catch (error1) {
2921
+ error = error1;
2922
+ return reject(error);
2923
+ }
2924
+ });
2925
+ }
2926
+ };
2927
+ // Generated by CoffeeScript 2.7.0
2928
+ // Main class for representing geometric planes in 3D space.
2929
+ // Planes are defined by a normal vector and a distance from the origin.
2930
+ // Used extensively in CSG operations for polygon classification and splitting.
2931
+ var Plane;
2932
+ Plane = class Plane {
2933
+ // Main constructor for creating plane instances.
2934
+ // Creates a plane defined by the equation: normal·point = distanceFromOrigin
2935
+ // @param normal - Three.js Vector3 representing the plane's normal direction.
2936
+ // @param distanceFromOrigin - Distance from origin along the normal direction.
2937
+ constructor(normal, distanceFromOrigin) {
2938
+ // Core geometric properties.
2939
+ this.normal = normal; // Normal vector defining plane orientation.
2940
+ this.distanceFromOrigin = distanceFromOrigin; // Distance parameter in plane equation.
2941
+ }
2942
+
2943
+ // === OBJECT CREATION AND COPYING ===
2944
+ clone() { // Create a deep copy of this plane with all properties.
2945
+ return new Plane(this.normal.clone(), this.distanceFromOrigin);
2946
+ }
2947
+ // === GEOMETRIC TRANSFORMATIONS ===
2948
+ flip() { // Flip the plane orientation (reverse normal and negate distance).
2949
+ this.normal.negate();
2950
+ return this.distanceFromOrigin = -this.distanceFromOrigin;
2951
+ }
2952
+ // === COMPARISON OPERATIONS ===
2953
+ // Test if this plane is identical to another plane.
2954
+ // Compares both normal vector and distance parameter for equality.
2955
+ // @param otherPlane - The other plane to compare against.
2956
+ // @return Boolean indicating whether planes are identical.
2957
+ equals(otherPlane) {
2958
+ return this.normal.equals(otherPlane.normal) && this.distanceFromOrigin === otherPlane.distanceFromOrigin;
2959
+ }
2960
+ // === CLEANUP AND DISPOSAL METHODS ===
2961
+ // Clean up plane data by setting all properties to undefined.
2962
+ // Used for memory management during intensive operations.
2963
+ delete() {
2964
+ this.normal = void 0;
2965
+ return this.distanceFromOrigin = void 0;
2966
+ }
2967
+ // === STATIC FACTORY METHODS ===
2968
+ // Create a plane from three points in 3D space.
2969
+ // Uses cross product to compute normal and dot product for distance.
2970
+ // Points should be ordered counter-clockwise when viewed from the front face.
2971
+ // @param firstPoint - First point (Three.js Vector3).
2972
+ // @param secondPoint - Second point (Three.js Vector3).
2973
+ // @param thirdPoint - Third point (Three.js Vector3).
2974
+ // @return New Plane instance passing through the three points.
2975
+ static fromPoints(firstPoint, secondPoint, thirdPoint) {
2976
+ var planeNormal;
2977
+ planeNormal = temporaryTriangleVertex.copy(secondPoint).sub(firstPoint).cross(temporaryTriangleVertexSecondary.copy(thirdPoint).sub(firstPoint)).normalize().clone();
2978
+ return new Plane(planeNormal, planeNormal.dot(firstPoint));
2979
+ }
2980
+ };
2981
+ // Generated by CoffeeScript 2.7.0
2982
+ // Main class for representing polygons (triangular faces) in 3D space for CSG operations.
2983
+ // Polygons are fundamental building blocks for geometric operations and mesh generation.
2984
+ var Polygon;
2985
+ Polygon = class Polygon {
2986
+ // Main constructor for creating polygon instances.
2987
+ // Initializes all geometric and state properties for CSG processing.
2988
+ // @param vertices - Array of Vertex instances representing the polygon corners.
2989
+ // @param shared - Material or shared data index for grouping polygons.
2990
+ constructor(vertices, shared) {
2991
+ // Core geometric properties.
2992
+ this.id = polygonID++; // Unique identifier for this polygon.
2993
+ this.vertices = vertices.map(function(vertex) {
2994
+ return vertex.clone(); // Deep copy of vertex array.
2995
+ });
2996
+ this.shared = shared; // Material index or shared data.
2997
+
2998
+ // Geometric calculations.
2999
+ this.plane = Plane.fromPoints(this.vertices[0].pos, this.vertices[1].pos, this.vertices[2].pos);
3000
+ this.triangle = new Triangle(this.vertices[0].pos, this.vertices[1].pos, this.vertices[2].pos);
3001
+ // CSG operation state properties.
3002
+ this.intersects = false; // Whether polygon intersects with other geometry.
3003
+ this.state = "undecided"; // Current classification state.
3004
+ this.previousState = "undecided"; // Previous classification state.
3005
+ this.previousStates = []; // History of all previous states.
3006
+
3007
+ // Validity and processing flags.
3008
+ this.valid = true; // Whether polygon is valid for processing.
3009
+ this.coplanar = false; // Whether polygon is coplanar with splitting plane.
3010
+ this.originalValid = false; // Whether polygon was part of original input.
3011
+ this.newPolygon = false; // Whether polygon was created during processing.
3012
+ }
3013
+
3014
+ // === SMALL HELPERS AND GETTERS ===
3015
+ // Get the midpoint of the polygon triangle.
3016
+ // Caches the result for performance optimization.
3017
+ // @return Three.js Vector3 representing the triangle midpoint.
3018
+ getMidpoint() {
3019
+ if (this.triangle.midPoint) {
3020
+ return this.triangle.midPoint;
3021
+ } else {
3022
+ return this.triangle.midPoint = this.triangle.getMidpoint(new Vector3());
3023
+ }
3024
+ }
3025
+ // === GEOMETRIC TRANSFORMATIONS ===
3026
+ // Apply a transformation matrix to the polygon vertices and update derived geometry.
3027
+ // Updates vertex positions, normals, and recalculates plane and triangle data.
3028
+ // @param matrix - Three.js Matrix4 transformation to apply to vertices.
3029
+ // @param normalMatrix - Three.js Matrix3 for transforming normals (auto-calculated if not provided).
3030
+ applyMatrix(matrix, normalMatrix) {
3031
+ normalMatrix = normalMatrix || temporaryMatrixWithNormalCalc.getNormalMatrix(matrix);
3032
+ this.vertices.forEach(function(vertex) {
3033
+ vertex.pos.applyMatrix4(matrix);
3034
+ return vertex.normal.applyMatrix3(normalMatrix);
3035
+ });
3036
+ this.plane.delete();
3037
+ this.plane = Plane.fromPoints(this.vertices[0].pos, this.vertices[1].pos, this.vertices[2].pos);
3038
+ this.triangle.set(this.vertices[0].pos, this.vertices[1].pos, this.vertices[2].pos);
3039
+ if (this.triangle.midPoint) {
3040
+ return this.triangle.getMidpoint(this.triangle.midPoint);
3041
+ }
3042
+ }
3043
+ // Flip the polygon orientation by reversing vertex order and flipping normals.
3044
+ // Used for changing polygon winding and surface normal direction.
3045
+ flip() {
3046
+ var tmp;
3047
+ this.vertices.reverse().forEach(function(vertex) {
3048
+ return vertex.flip();
3049
+ });
3050
+ tmp = this.triangle.a;
3051
+ this.triangle.a = this.triangle.c;
3052
+ this.triangle.c = tmp;
3053
+ return this.plane.flip();
3054
+ }
3055
+ // === CSG STATE MANAGEMENT ===
3056
+ // Reset polygon state to initial values for fresh CSG operations.
3057
+ // Clears intersection flags and classification states.
3058
+ // @param resetOriginal - Whether to reset the originalValid flag (default: true).
3059
+ reset(resetOriginal = true) {
3060
+ this.intersects = false;
3061
+ this.state = "undecided";
3062
+ this.previousState = "undecided";
3063
+ this.previousStates.length = 0;
3064
+ this.valid = true;
3065
+ this.coplanar = false;
3066
+ resetOriginal && (this.originalValid = false);
3067
+ return this.newPolygon = false;
3068
+ }
3069
+ // Set the classification state of the polygon during CSG operations.
3070
+ // Maintains state history for complex CSG rule evaluation.
3071
+ // @param state - New state to assign ("inside", "outside", "coplanar-front", etc.).
3072
+ // @param keepState - State to preserve (won't change if current state matches this).
3073
+ setState(state, keepState) {
3074
+ if (this.state === keepState) {
3075
+ return;
3076
+ }
3077
+ this.previousState = this.state;
3078
+ this.state !== "undecided" && this.previousStates.push(this.state);
3079
+ return this.state = state;
3080
+ }
3081
+ // Check if polygon has consistently been in the specified state.
3082
+ // Used for CSG rule evaluation requiring state consistency.
3083
+ // @param state - State to check for consistency.
3084
+ // @return Boolean indicating if all states match the specified state.
3085
+ checkAllStates(state) {
3086
+ var i, len, previousState, ref;
3087
+ if ((this.state !== state) || ((this.previousState !== state) && (this.previousState !== "undecided"))) {
3088
+ return false;
3089
+ }
3090
+ ref = this.previousStates;
3091
+ for (i = 0, len = ref.length; i < len; i++) {
3092
+ previousState = ref[i];
3093
+ if (previousState !== state) {
3094
+ return false;
3095
+ }
3096
+ }
3097
+ return true;
3098
+ }
3099
+ // === VALIDITY MANAGEMENT ===
3100
+ // Mark polygon as invalid (will be excluded from processing).
3101
+ setInvalid() {
3102
+ return this.valid = false;
3103
+ }
3104
+ // Mark polygon as valid (will be included in processing).
3105
+ setValid() {
3106
+ return this.valid = true;
3107
+ }
3108
+ // === OBJECT CREATION AND COPYING ===
3109
+ // Create a polygon with default material index.
3110
+ // Convenience method for creating polygons without specifying material.
3111
+ // @param vertices - Array of Vertex instances representing the polygon corners.
3112
+ // @return New Polygon instance with default material.
3113
+ static createWithDefaultMaterial(vertices) {
3114
+ return new Polygon(vertices, DEFAULT_MATERIAL_INDEX);
3115
+ }
3116
+ // Create a deep copy of this polygon with all properties.
3117
+ // Preserves all state information and geometric data.
3118
+ // @return New Polygon instance with copied data.
3119
+ clone() {
3120
+ var polygon;
3121
+ polygon = new Polygon(this.vertices.map(function(vertex) {
3122
+ return vertex.clone();
3123
+ }), this.shared);
3124
+ polygon.intersects = this.intersects;
3125
+ polygon.valid = this.valid;
3126
+ polygon.coplanar = this.coplanar;
3127
+ polygon.state = this.state;
3128
+ polygon.originalValid = this.originalValid;
3129
+ polygon.newPolygon = this.newPolygon;
3130
+ polygon.previousState = this.previousState;
3131
+ polygon.previousStates = this.previousStates.slice();
3132
+ if (this.triangle.midPoint) {
3133
+ polygon.triangle.midPoint = this.triangle.midPoint.clone();
3134
+ }
3135
+ return polygon;
3136
+ }
3137
+ // === CLEANUP AND DISPOSAL METHODS ===
3138
+ // Clean up polygon data and free memory.
3139
+ // Deletes vertices, geometric objects, and marks polygon as invalid.
3140
+ delete() {
3141
+ this.vertices.forEach(function(vertex) {
3142
+ return vertex.delete();
3143
+ });
3144
+ this.vertices.length = 0;
3145
+ if (this.plane) {
3146
+ this.plane.delete();
3147
+ this.plane = void 0;
3148
+ }
3149
+ this.triangle = void 0;
3150
+ this.shared = void 0;
3151
+ return this.setInvalid();
3152
+ }
3153
+ };
3154
+ // Generated by CoffeeScript 2.7.0
3155
+ /* Validate a triangle (three distinct vertices) in 3D space.
3156
+ Notes:
3157
+ - This does NOT check for area > 0 beyond coincident points (collinear but distinct points are treated as valid).
3158
+ - Use additional orientation/area tests if you need to exclude collinear triangles. */
3159
+ /* Determines the intersection segment (if any) between two triangles in 3D space.
3160
+ If an intersection segment exists, its endpoints are written to `additions.source` and `additions.target`.
3161
+ @param {Vector3} vertex1TriangleA, vertex2TriangleA, vertex3TriangleA - Vertices of triangle A
3162
+ @param {Vector3} vertex1TriangleB, vertex2TriangleB, vertex3TriangleB - Vertices of triangle B
3163
+ @param {Object} additions - Data object used for storing extra intersection info:
3164
+ - coplanar {Boolean} whether the triangles lie in the same plane.
3165
+ - source {Vector3} intersection segment start (if applicable).
3166
+ - target {Vector3} intersection segment end (if applicable).
3167
+ - normal1 {Vector3} normal of Triangle A (used in coplanar case).
3168
+ - normal2 {Vector3} normal of Triangle B (used in coplanar case).
3169
+ @return {Boolean} True if intersection segment exists, false otherwise. */
3170
+ /* Checks for edge intersection between two triangles in 2D.
3171
+ @param {Vector2} vertex1TriangleA, vertex2TriangleA, vertex3TriangleA - Vertices of triangle A
3172
+ @param {Vector2} vertex1TriangleB, vertex2TriangleB, vertex3TriangleB - Vertices of triangle B
3173
+ @return {Boolean} True if an edge intersection is found, false otherwise. */
3174
+ /* Checks for vertex intersection between two triangles in 2D.
3175
+ @param {Vector2} vertex1TriangleA, vertex2TriangleA, vertex3TriangleA - Vertices of triangle A
3176
+ @param {Vector2} vertex1TriangleB, vertex2TriangleB, vertex3TriangleB - Vertices of triangle B
3177
+ @return {Boolean} True if a vertex intersection is found, false otherwise. */
3178
+ /* Check and register triangle uniqueness in a Set/Map.
3179
+ Hash Scheme:
3180
+ - Generates a directional hash using the ordered vertex triplet (a,b,c).
3181
+ - Different vertex order permutations of the same geometric triangle will be treated as different unless normalized before calling.
3182
+ Usage Guidance:
3183
+ - For order-invariant uniqueness, sort or canonicalize vertices first (e.g., by lexicographic (x,y,z)) before invoking.
3184
+ - For performance, this function only creates a single concatenated string and performs a Set lookup. */
3185
+ /* Resolves intersection between two coplanar triangles in 3D space.
3186
+ Since the triangles lie in the same plane, the problem is reduced from 3D to 2D.
3187
+ By projecting both triangles onto the axis-aligned plane (XY, YZ, or XZ) that maximizes the projected area.
3188
+ This minimizes numerical errors when working in 2D. The function then delegates the overlap test to `trianglesOverlap2D`.
3189
+ @param {Vector2} vertex1TriangleA, vertex2TriangleA, vertex3TriangleA - Vertices of triangle A
3190
+ @param {Vector2} vertex1TriangleB, vertex2TriangleB, vertex3TriangleB - Vertices of triangle B
3191
+ @param {Vector3} normalTriangleA - Normal vector of Triangle A.
3192
+ @param {Vector3} normalTriangleB - Normal vector of Triangle B.
3193
+ @returns {Boolean} - True if the coplanar triangles overlap in 2D, false otherwise. */
3194
+ /* Determines the intersection between two 3D triangles given their vertices and the signed distances of Triangle B’s vertices to the plane of Triangle A.
3195
+ Goal: Always pass to `constructIntersection` the triangles arranged so the first vertex of each lies alone on one side of the other triangle’s plane (or is on the plane), and the remaining two share the opposite side.
3196
+ This function chooses one of several vertex orderings based on the sign pattern of (distanceVertex1B, distanceVertex2B, distanceVertex3B).
3197
+ If all three distances for Triangle B are zero → triangles are coplanar and handled by `resolveCoplanarTriangleIntersection`.
3198
+ @param {Vector2} vertex1TriangleA, vertex2TriangleA, vertex3TriangleA - Vertices of triangle A
3199
+ @param {Vector2} vertex1TriangleB, vertex2TriangleB, vertex3TriangleB - Vertices of triangle B
3200
+ @param {Number} distanceVertex1B - Signed distance of vertex1TriangleB to Triangle A’s plane.
3201
+ @param {Number} distanceVertex2B - Signed distance of vertex2TriangleB to Triangle A’s plane.
3202
+ @param {Number} distanceVertex3B - Signed distance of vertex3TriangleB to Triangle A’s plane.
3203
+ @param {Object} additions - Data object used for storing extra intersection info:
3204
+ - coplanar {Boolean} whether the triangles lie in the same plane.
3205
+ - source {Vector3} intersection segment start (if applicable).
3206
+ - target {Vector3} intersection segment end (if applicable).
3207
+ - normal1 {Vector3} normal of Triangle A (used in coplanar case).
3208
+ - normal2 {Vector3} normal of Triangle B (used in coplanar case).
3209
+ @returns {Boolean} - True if an intersection is found, false otherwise. */
3210
+ /* Determines if two counter-clockwise (CCW) triangles in 2D overlap.
3211
+ The function checks the relative orientation of triangle B's vertices with respect to triangle A.
3212
+ Then recursively tests for edge or vertex intersection depending on the configuration.
3213
+ @param {Vector2} vertex1TriangleA, vertex2TriangleA, vertex3TriangleA - Vertices of triangle A (CCW order)
3214
+ @param {Vector2} vertex1TriangleB, vertex2TriangleB, vertex3TriangleB - Vertices of triangle B (CCW order)
3215
+ @return {Boolean} True if triangles overlap, false otherwise. */
3216
+ /* Checks whether two triangles in 3D space intersect.
3217
+ @param {Object} triangleA - First triangle, with properties {a, b, c} (Vector3 vertices).
3218
+ @param {Object} triangleB - Second triangle, with properties {a, b, c} (Vector3 vertices).
3219
+ @param {Object} additions - Optional data object used for storing extra intersection info:
3220
+ - coplanar {Boolean} whether the triangles lie in the same plane.
3221
+ - source {Vector3} intersection segment start (if applicable).
3222
+ - target {Vector3} intersection segment end (if applicable).
3223
+ @returns {Boolean} - True if the triangles intersect, false otherwise. */
3224
+ /* Computes the orientation (signed area) of a 2D triangle defined by three vertices.
3225
+ The result indicates whether the points are arranged clockwise (CW), counter-clockwise (CCW), or collinear.
3226
+ Note: This returns the raw signed area (twice the triangle area), without applying an epsilon threshold.
3227
+ Callers should compare against a small EPS (e.g., TRIANGLE_2D_EPSILON) when classifying near-collinear inputs.
3228
+ Formula: orientation(a, b, c) = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)
3229
+ - If result > 0 → counter-clockwise (CCW).
3230
+ - If result < 0 → clockwise (CW).
3231
+ - If result = 0 → points are collinear.
3232
+ @param {Vector2} a - First vertex.
3233
+ @param {Vector2} b - Second vertex.
3234
+ @param {Vector2} c - Third vertex.
3235
+ @returns {Number} - Positive if CCW, negative if CW, zero if collinear. */
3236
+ /* Determines whether two triangles in 2D overlap.
3237
+ Behavior and notes:
3238
+ - Orientation: Triangles may be CW or CCW; inputs are normalized to CCW before testing.
3239
+ - Inclusivity: Overlap is inclusive of shared edges and shared vertices.
3240
+ - Degenerate handling:
3241
+ - If one triangle degenerates to a point, returns whether that point lies in (or on) the other triangle.
3242
+ - If both degenerate to points, returns true only if the points coincide within TRIANGLE_2D_EPSILON.
3243
+ - Degenerate line triangles are handled by the main CCW intersection routine and by point/segment checks where applicable.
3244
+ @param {Vector2} vertex1TriangleA, vertex2TriangleA, vertex3TriangleA - Vertices of triangle A
3245
+ @param {Vector2} vertex1TriangleB, vertex2TriangleB, vertex3TriangleB - Vertices of triangle B
3246
+ @returns {Boolean} - True if the triangles overlap in 2D, false otherwise. */
3247
+ var constructIntersection, intersectionTestEdge2D, intersectionTestVertex2D, isUniqueTriangle, isValidTriangle, pointInTriangleInclusive2D, pointOnSegmentInclusive2D, pointsEqual2D, resolveCoplanarTriangleIntersection, resolveTriangleIntersection, triangleIntersectionCCW2D, triangleIntersectsTriangle, triangleOrientation2D, trianglesOverlap2D;
3248
+ isValidTriangle = function(triangle) {
3249
+ if (DEBUG_GEOMETRY_VALIDATION) {
3250
+ console.log("Validating triangle:", triangle.a, triangle.b, triangle.c);
3251
+ }
3252
+ if (triangle.a.equals(triangle.b)) {
3253
+ return false;
3254
+ }
3255
+ if (triangle.a.equals(triangle.c)) {
3256
+ return false;
3257
+ }
3258
+ if (triangle.b.equals(triangle.c)) {
3259
+ return false;
3260
+ }
3261
+ if (DEBUG_GEOMETRY_VALIDATION) {
3262
+ console.log("Triangle validation passed.");
3263
+ }
3264
+ return true;
3265
+ };
3266
+ isUniqueTriangle = function(triangle, set, map) {
3267
+ var hash1;
3268
+ hash1 = `{${triangle.a.x},${triangle.a.y},${triangle.a.z}}-{${triangle.b.x},${triangle.b.y},${triangle.b.z}}-{${triangle.c.x},${triangle.c.y},${triangle.c.z}}`;
3269
+ if (set.has(hash1) === true) {
3270
+ return false;
3271
+ } else {
3272
+ set.add(hash1);
3273
+ if (map) {
3274
+ map.set(triangle, triangle);
3275
+ }
3276
+ return true;
3277
+ }
3278
+ };
3279
+ // Epsilon-aware 2D point equality.
3280
+ pointsEqual2D = function(p, q, eps = TRIANGLE_2D_EPSILON) {
3281
+ return Math.abs(p.x - q.x) <= eps && Math.abs(p.y - q.y) <= eps;
3282
+ };
3283
+ // Inclusive point-on-segment check for collinear points.
3284
+ pointOnSegmentInclusive2D = function(p, a, b, eps = TRIANGLE_2D_EPSILON) {
3285
+ var maxX, maxY, minX, minY;
3286
+ if (pointsEqual2D(a, b, eps)) {
3287
+ return pointsEqual2D(p, a, eps) || pointsEqual2D(p, b, eps);
3288
+ }
3289
+ if (Math.abs(triangleOrientation2D(p, a, b)) > eps) {
3290
+ return false;
3291
+ }
3292
+ minX = Math.min(a.x, b.x) - eps;
3293
+ maxX = Math.max(a.x, b.x) + eps;
3294
+ minY = Math.min(a.y, b.y) - eps;
3295
+ maxY = Math.max(a.y, b.y) + eps;
3296
+ return p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY;
3297
+ };
3298
+ // Inclusive point-in-triangle test using orientations.
3299
+ // - Works for CW or CCW input (auto-detects orientation).
3300
+ // - Inclusive of edges/vertices.
3301
+ // - Handles degenerate triangles (point or segment) robustly.
3302
+ pointInTriangleInclusive2D = function(p, a, b, c, eps = TRIANGLE_2D_EPSILON) {
3303
+ var o, s1, s2, s3;
3304
+ o = triangleOrientation2D(a, b, c);
3305
+ if (o > eps) {
3306
+ s1 = triangleOrientation2D(p, a, b);
3307
+ s2 = triangleOrientation2D(p, b, c);
3308
+ s3 = triangleOrientation2D(p, c, a);
3309
+ return (s1 >= -eps) && (s2 >= -eps) && (s3 >= -eps);
3310
+ } else if (o < -eps) {
3311
+ s1 = triangleOrientation2D(p, a, b);
3312
+ s2 = triangleOrientation2D(p, b, c);
3313
+ s3 = triangleOrientation2D(p, c, a);
3314
+ return (s1 <= eps) && (s2 <= eps) && (s3 <= eps);
3315
+ } else {
3316
+ // Degenerate triangle: either a point (all equal) or a segment (collinear).
3317
+ if (pointsEqual2D(a, b, eps) && pointsEqual2D(b, c, eps)) {
3318
+ return pointsEqual2D(p, a, eps);
3319
+ }
3320
+ // Reduce to the non-degenerate segment and test inclusively.
3321
+ if (pointsEqual2D(a, b, eps)) {
3322
+ return pointOnSegmentInclusive2D(p, b, c, eps);
3323
+ }
3324
+ if (pointsEqual2D(b, c, eps)) {
3325
+ return pointOnSegmentInclusive2D(p, a, b, eps);
3326
+ }
3327
+ if (pointsEqual2D(c, a, eps)) {
3328
+ return pointOnSegmentInclusive2D(p, a, b, eps);
3329
+ }
3330
+ // All three distinct but collinear: test against hull segments.
3331
+ return pointOnSegmentInclusive2D(p, a, b, eps) || pointOnSegmentInclusive2D(p, b, c, eps) || pointOnSegmentInclusive2D(p, c, a, eps);
3332
+ }
3333
+ };
3334
+ triangleIntersectsTriangle = function(triangleA, triangleB, additions = {
3335
+ coplanar: false,
3336
+ source: new Vector3(),
3337
+ target: new Vector3()
3338
+ }) {
3339
+ var distanceVertex1A, distanceVertex1B, distanceVertex2A, distanceVertex2B, distanceVertex3A, distanceVertex3B, normal1, normal2, vertex1TriangleA, vertex1TriangleB, vertex2TriangleA, vertex2TriangleB, vertex3TriangleA, vertex3TriangleB;
3340
+ // Extract vertices of triangle A.
3341
+ vertex1TriangleA = triangleA.a;
3342
+ vertex2TriangleA = triangleA.b;
3343
+ vertex3TriangleA = triangleA.c;
3344
+ // Extract vertices of triangle B.
3345
+ vertex1TriangleB = triangleB.a;
3346
+ vertex2TriangleB = triangleB.b;
3347
+ vertex3TriangleB = triangleB.c;
3348
+ // Step 1: Compute signed distances of Triangle A’s vertices relative to the plane defined by Triangle B.
3349
+ temporaryVector3Primary.copy(vertex1TriangleB).sub(vertex3TriangleB);
3350
+ temporaryVector3Secondary.copy(vertex2TriangleB).sub(vertex3TriangleB);
3351
+ normal2 = (new Vector3()).copy(temporaryVector3Primary).cross(temporaryVector3Secondary);
3352
+ temporaryVector3Primary.copy(vertex1TriangleA).sub(vertex3TriangleB);
3353
+ distanceVertex1A = temporaryVector3Primary.dot(normal2);
3354
+ temporaryVector3Primary.copy(vertex2TriangleA).sub(vertex3TriangleB);
3355
+ distanceVertex2A = temporaryVector3Primary.dot(normal2);
3356
+ temporaryVector3Primary.copy(vertex3TriangleA).sub(vertex3TriangleB);
3357
+ distanceVertex3A = temporaryVector3Primary.dot(normal2);
3358
+ if (((distanceVertex1A * distanceVertex2A) > 0) && ((distanceVertex1A * distanceVertex3A) > 0)) {
3359
+ return false; // All vertices of Triangle A are on the same side of Triangle B’s plane.
3360
+ }
3361
+
3362
+ // Step 2: Compute signed distances of Triangle B’s vertices relative to the plane defined by Triangle A.
3363
+ temporaryVector3Primary.copy(vertex2TriangleA).sub(vertex1TriangleA);
3364
+ temporaryVector3Secondary.copy(vertex3TriangleA).sub(vertex1TriangleA);
3365
+ normal1 = (new Vector3()).copy(temporaryVector3Primary).cross(temporaryVector3Secondary);
3366
+ temporaryVector3Primary.copy(vertex1TriangleB).sub(vertex3TriangleA);
3367
+ distanceVertex1B = temporaryVector3Primary.dot(normal1);
3368
+ temporaryVector3Primary.copy(vertex2TriangleB).sub(vertex3TriangleA);
3369
+ distanceVertex2B = temporaryVector3Primary.dot(normal1);
3370
+ temporaryVector3Primary.copy(vertex3TriangleB).sub(vertex3TriangleA);
3371
+ distanceVertex3B = temporaryVector3Primary.dot(normal1);
3372
+ if (((distanceVertex1B * distanceVertex2B) > 0) && ((distanceVertex1B * distanceVertex3B) > 0)) {
3373
+ return false; // All vertices of Triangle B are on the same side of Triangle A’s plane.
3374
+ }
3375
+
3376
+ // Step 3: At this point, neither triangle is fully on one side of the other’s plane. This means the triangles potentially intersect.
3377
+ // Next, we use the vertex signed distances to decide which configuration applies and call `resolveTriangleIntersection` (or `resolveCoplanarTriangleIntersection` if the triangles are coplanar).
3378
+ additions.normal1 = normal1;
3379
+ additions.normal2 = normal2;
3380
+ // Decide how to proceed by looking at the signs of distanceVertex1A, distanceVertex2A and distanceVertex3A.
3381
+ // These numbers tell us whether each vertex of Triangle A sits above the flat surface of Triangle B (positive), below it (negative), or exactly on it (zero).
3382
+ // We then pass the vertices to the resolver in a stable order: the single “different-side” vertex first, followed by the two vertices that are on the same side.
3383
+ if (distanceVertex1A > 0) {
3384
+ // If distanceVertex1A is positive (the first vertex of Triangle A is above Triangle B's surface):
3385
+ if (distanceVertex2A > 0) {
3386
+ // Then distanceVertex2A is also positive, while distanceVertex3A is zero or negative.
3387
+ // In plain terms: the third vertex of Triangle A lies on the other side of Triangle B’s surface (or exactly on it).
3388
+ return resolveTriangleIntersection(vertex3TriangleA, vertex1TriangleA, vertex2TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB, distanceVertex1B, distanceVertex3B, distanceVertex2B, additions);
3389
+ } else if (distanceVertex3A > 0) {
3390
+ // Then distanceVertex3A is positive, while distanceVertex2A is zero or negative.
3391
+ // That means the second vertex of Triangle A is the one on the other side (or exactly on the surface).
3392
+ return resolveTriangleIntersection(vertex2TriangleA, vertex3TriangleA, vertex1TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB, distanceVertex1B, distanceVertex3B, distanceVertex2B, additions);
3393
+ } else {
3394
+ // Here only the first vertex is above the surface; the second and third are on or below it.
3395
+ return resolveTriangleIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, distanceVertex1B, distanceVertex2B, distanceVertex3B, additions);
3396
+ }
3397
+ } else if (distanceVertex1A < 0) {
3398
+ // If distanceVertex1A is negative (the first vertex of Triangle A is below Triangle B's surface):
3399
+ if (distanceVertex2A < 0) {
3400
+ // Then distanceVertex2A is also negative, while distanceVertex3A is zero or positive.
3401
+ // In other words: the third vertex of Triangle A is on the other side (or exactly on the surface).
3402
+ return resolveTriangleIntersection(vertex3TriangleA, vertex1TriangleA, vertex2TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, distanceVertex1B, distanceVertex2B, distanceVertex3B, additions);
3403
+ } else if (distanceVertex3A < 0) {
3404
+ // Then distanceVertex3A is negative, while distanceVertex2A is zero or positive.
3405
+ // So the second vertex is the one on the other side (or exactly on the surface).
3406
+ return resolveTriangleIntersection(vertex2TriangleA, vertex3TriangleA, vertex1TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, distanceVertex1B, distanceVertex2B, distanceVertex3B, additions);
3407
+ } else {
3408
+ // Only the first vertex is below the surface; the second and third are on or above it.
3409
+ // Note: We also swap the order of Triangle B’s vertices here to keep a consistent “one different, two the same” pattern for the resolver.
3410
+ return resolveTriangleIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB, distanceVertex1B, distanceVertex3B, distanceVertex2B, additions);
3411
+ }
3412
+ } else {
3413
+ // Then the first vertex of Triangle A lies exactly on Triangle B’s surface (distanceVertex1A is zero).
3414
+ // We look at distanceVertex2A and distanceVertex3A to decide which side the other vertices are on.
3415
+ if (distanceVertex2A < 0) {
3416
+ if (distanceVertex3A >= 0) {
3417
+ // The second vertex is below the surface, while the third is on or above it.
3418
+ return resolveTriangleIntersection(vertex2TriangleA, vertex3TriangleA, vertex1TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB, distanceVertex1B, distanceVertex3B, distanceVertex2B, additions);
3419
+ } else {
3420
+ // Both the second and third vertices are below the surface.
3421
+ return resolveTriangleIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, distanceVertex1B, distanceVertex2B, distanceVertex3B, additions);
3422
+ }
3423
+ } else if (distanceVertex2A > 0) {
3424
+ if (distanceVertex3A > 0) {
3425
+ // Both the second and third vertices are above the surface.
3426
+ return resolveTriangleIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB, distanceVertex1B, distanceVertex3B, distanceVertex2B, additions);
3427
+ } else {
3428
+ // The second vertex is above the surface, while the third is on or below it.
3429
+ return resolveTriangleIntersection(vertex2TriangleA, vertex3TriangleA, vertex1TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, distanceVertex1B, distanceVertex2B, distanceVertex3B, additions);
3430
+ }
3431
+ } else {
3432
+ if (distanceVertex3A > 0) {
3433
+ // The second vertex is exactly on the surface, and the third is above it.
3434
+ return resolveTriangleIntersection(vertex3TriangleA, vertex1TriangleA, vertex2TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, distanceVertex1B, distanceVertex2B, distanceVertex3B, additions);
3435
+ } else if (distanceVertex3A < 0) {
3436
+ // The second vertex is exactly on the surface, and the third is below it.
3437
+ return resolveTriangleIntersection(vertex3TriangleA, vertex1TriangleA, vertex2TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB, distanceVertex1B, distanceVertex3B, distanceVertex2B, additions);
3438
+ } else {
3439
+ additions.coplanar = true; // All three vertices of Triangle A lie in Triangle B's plane.
3440
+ return resolveCoplanarTriangleIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, normal1, normal2);
3441
+ }
3442
+ }
3443
+ }
3444
+ };
3445
+ resolveTriangleIntersection = function(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, distanceVertex1B, distanceVertex2B, distanceVertex3B, additions) {
3446
+ // Early exit → If all B's distances are strictly positive (totally outside/above) or negative (totally outside/below), there's no intersection.
3447
+ if ((distanceVertex1B > 0 && distanceVertex2B > 0 && distanceVertex3B > 0) || (distanceVertex1B < 0 && distanceVertex2B < 0 && distanceVertex3B < 0)) {
3448
+ return false;
3449
+ }
3450
+ if (distanceVertex1B > 0) { // First vertex of Triangle B is above (positive side of) Triangle A's plane.
3451
+ if (distanceVertex2B > 0) { // First two vertices of B are positive, third is zero or negative.
3452
+ return constructIntersection(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex3TriangleB, vertex1TriangleB, vertex2TriangleB, additions); // Reorder B as (C, A, B) so differing vertex is last.
3453
+ } else if (distanceVertex3B > 0) { // First and third vertices of B are positive, second is zero or negative.
3454
+ return constructIntersection(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex2TriangleB, vertex3TriangleB, vertex1TriangleB, additions); // Reorder B as (B, C, A).
3455
+ // Only first vertex of B is positive.
3456
+ } else {
3457
+ return constructIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, additions); // Pass original B ordering.
3458
+ }
3459
+ } else if (distanceVertex1B < 0) { // First vertex of Triangle B is below (negative side of) Triangle A's plane.
3460
+ if (distanceVertex2B < 0) { // First two vertices of B are negative, third is zero or positive.
3461
+ return constructIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex3TriangleB, vertex1TriangleB, vertex2TriangleB, additions); // Reorder B as (C, A, B) so differing vertex is last.
3462
+ } else if (distanceVertex3B < 0) { // First and third vertices of B are negative, second is zero or positive.
3463
+ return constructIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex2TriangleB, vertex3TriangleB, vertex1TriangleB, additions); // Reorder B as (B, C, A).
3464
+ // Only first vertex of B is negative.
3465
+ } else {
3466
+ return constructIntersection(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, additions); // Pass A, reordered B.
3467
+ // First vertex of Triangle B is exactly on the plane (distance zero).
3468
+ }
3469
+ } else {
3470
+ if (distanceVertex2B < 0) { // Second vertex is negative, third will decide.
3471
+ if (distanceVertex3B >= 0) { // Second negative, third zero or positive.
3472
+ return constructIntersection(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex2TriangleB, vertex3TriangleB, vertex1TriangleB, additions); // Mixed across the plane.
3473
+ // Second & third negative.
3474
+ } else {
3475
+ return constructIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, additions); // Only first on the plane.
3476
+ }
3477
+ } else if (distanceVertex2B > 0) { // Second vertex is positive.
3478
+ if (distanceVertex3B > 0) { // Both second and third are positive.
3479
+ return constructIntersection(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, additions); // Both above the plane.
3480
+ // Second positive, third zero or negative.
3481
+ } else {
3482
+ return constructIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex2TriangleB, vertex3TriangleB, vertex1TriangleB, additions); // Mixed, split across plane.
3483
+ // Second vertex is exactly on the plane (zero).
3484
+ }
3485
+ } else {
3486
+ if (distanceVertex3B > 0) { // Only third is positive.
3487
+ return constructIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex3TriangleB, vertex1TriangleB, vertex2TriangleB, additions); // Third above, first/second on plane.
3488
+ } else if (distanceVertex3B < 0) { // Only third is negative.
3489
+ return constructIntersection(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex3TriangleB, vertex1TriangleB, vertex2TriangleB, additions); // Third below plane.
3490
+ // All three B vertices are exactly on the plane (coplanar).
3491
+ } else {
3492
+ additions.coplanar = true; // Mark coplanar.
3493
+ return resolveCoplanarTriangleIntersection(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, additions.normal1, additions.normal2);
3494
+ }
3495
+ }
3496
+ }
3497
+ };
3498
+ resolveCoplanarTriangleIntersection = function(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, normalTriangleA, normalTriangleB) {
3499
+ var normalAbsX, normalAbsY, normalAbsZ, vertex1TriangleA2D, vertex1TriangleB2D, vertex2TriangleA2D, vertex2TriangleB2D, vertex3TriangleA2D, vertex3TriangleB2D;
3500
+ // Prepare 2D projected vertices.
3501
+ vertex1TriangleA2D = new Vector2();
3502
+ vertex2TriangleA2D = new Vector2();
3503
+ vertex3TriangleA2D = new Vector2();
3504
+ vertex1TriangleB2D = new Vector2();
3505
+ vertex2TriangleB2D = new Vector2();
3506
+ vertex3TriangleB2D = new Vector2();
3507
+ // Absolute values of the triangle's normal components.
3508
+ // Used to determine the dominant axis, which we drop during 2D projection.
3509
+ normalAbsX = Math.abs(normalTriangleA.x);
3510
+ normalAbsY = Math.abs(normalTriangleA.y);
3511
+ normalAbsZ = Math.abs(normalTriangleA.z);
3512
+ // Project triangles into 2D by dropping the dominant axis of the normal.
3513
+ if ((normalAbsX > normalAbsZ) && (normalAbsX >= normalAbsY)) { // Project onto YZ plane.
3514
+ vertex1TriangleA2D.set(vertex1TriangleA.y, vertex1TriangleA.z);
3515
+ vertex2TriangleA2D.set(vertex2TriangleA.y, vertex2TriangleA.z);
3516
+ vertex3TriangleA2D.set(vertex3TriangleA.y, vertex3TriangleA.z);
3517
+ vertex1TriangleB2D.set(vertex1TriangleB.y, vertex1TriangleB.z);
3518
+ vertex2TriangleB2D.set(vertex2TriangleB.y, vertex2TriangleB.z);
3519
+ vertex3TriangleB2D.set(vertex3TriangleB.y, vertex3TriangleB.z);
3520
+ } else if ((normalAbsY > normalAbsZ) && (normalAbsY >= normalAbsX)) { // Project onto XZ plane.
3521
+ vertex1TriangleA2D.set(vertex1TriangleA.x, vertex1TriangleA.z);
3522
+ vertex2TriangleA2D.set(vertex2TriangleA.x, vertex2TriangleA.z);
3523
+ vertex3TriangleA2D.set(vertex3TriangleA.x, vertex3TriangleA.z);
3524
+ vertex1TriangleB2D.set(vertex1TriangleB.x, vertex1TriangleB.z);
3525
+ vertex2TriangleB2D.set(vertex2TriangleB.x, vertex2TriangleB.z);
3526
+ vertex3TriangleB2D.set(vertex3TriangleB.x, vertex3TriangleB.z); // Project onto XY plane
3527
+ } else {
3528
+ vertex1TriangleA2D.set(vertex1TriangleA.x, vertex1TriangleA.y);
3529
+ vertex2TriangleA2D.set(vertex2TriangleA.x, vertex2TriangleA.y);
3530
+ vertex3TriangleA2D.set(vertex3TriangleA.x, vertex3TriangleA.y);
3531
+ vertex1TriangleB2D.set(vertex1TriangleB.x, vertex1TriangleB.y);
3532
+ vertex2TriangleB2D.set(vertex2TriangleB.x, vertex2TriangleB.y);
3533
+ vertex3TriangleB2D.set(vertex3TriangleB.x, vertex3TriangleB.y);
3534
+ }
3535
+ return trianglesOverlap2D(vertex1TriangleA2D, vertex2TriangleA2D, vertex3TriangleA2D, vertex1TriangleB2D, vertex2TriangleB2D, vertex3TriangleB2D);
3536
+ };
3537
+ trianglesOverlap2D = function(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB) {
3538
+ var aE, aMax, aMin, aS, bE, bMax, bMin, bS, extractSegment, isLineDegenerate, maxAx, maxAy, maxBx, maxBy, minAx, minAy, minBx, minBy, spanAx, spanAy;
3539
+ // Early handle point-degenerate cases explicitly and efficiently.
3540
+ if (pointsEqual2D(vertex1TriangleB, vertex2TriangleB, TRIANGLE_2D_EPSILON) && pointsEqual2D(vertex2TriangleB, vertex3TriangleB, TRIANGLE_2D_EPSILON)) {
3541
+ return pointInTriangleInclusive2D(vertex1TriangleB, vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, TRIANGLE_2D_EPSILON);
3542
+ }
3543
+ if (pointsEqual2D(vertex1TriangleA, vertex2TriangleA, TRIANGLE_2D_EPSILON) && pointsEqual2D(vertex2TriangleA, vertex3TriangleA, TRIANGLE_2D_EPSILON)) {
3544
+ return pointInTriangleInclusive2D(vertex1TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, TRIANGLE_2D_EPSILON);
3545
+ }
3546
+ // If both are line-degenerate (not points): reduce to segment overlap test.
3547
+ isLineDegenerate = function(v1, v2, v3) {
3548
+ return (pointsEqual2D(v1, v2, TRIANGLE_2D_EPSILON) && !pointsEqual2D(v2, v3, TRIANGLE_2D_EPSILON)) || (pointsEqual2D(v2, v3, TRIANGLE_2D_EPSILON) && !pointsEqual2D(v1, v2, TRIANGLE_2D_EPSILON)) || (pointsEqual2D(v3, v1, TRIANGLE_2D_EPSILON) && !pointsEqual2D(v1, v2, TRIANGLE_2D_EPSILON));
3549
+ };
3550
+ extractSegment = function(v1, v2, v3) {
3551
+ // Return the two distinct endpoints in stable order.
3552
+ if (pointsEqual2D(v1, v2, TRIANGLE_2D_EPSILON)) {
3553
+ return [v2, v3];
3554
+ } else if (pointsEqual2D(v2, v3, TRIANGLE_2D_EPSILON)) {
3555
+ return [v1, v2];
3556
+ } else {
3557
+ return [
3558
+ v1,
3559
+ v2 // fallback (should not reach if collinear case handled earlier)
3560
+ ];
3561
+ }
3562
+ };
3563
+ if (isLineDegenerate(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA) && isLineDegenerate(vertex1TriangleB, vertex2TriangleB, vertex3TriangleB)) {
3564
+ [aS, aE] = extractSegment(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA);
3565
+ [bS, bE] = extractSegment(vertex1TriangleB, vertex2TriangleB, vertex3TriangleB);
3566
+ // Fast reject by axis-aligned bounding boxes.
3567
+ minAx = Math.min(aS.x, aE.x);
3568
+ maxAx = Math.max(aS.x, aE.x);
3569
+ minAy = Math.min(aS.y, aE.y);
3570
+ maxAy = Math.max(aS.y, aE.y);
3571
+ minBx = Math.min(bS.x, bE.x);
3572
+ maxBx = Math.max(bS.x, bE.x);
3573
+ minBy = Math.min(bS.y, bE.y);
3574
+ maxBy = Math.max(bS.y, bE.y);
3575
+ if (maxAx < minBx - TRIANGLE_2D_EPSILON || maxBx < minAx - TRIANGLE_2D_EPSILON || maxAy < minBy - TRIANGLE_2D_EPSILON || maxBy < minAy - TRIANGLE_2D_EPSILON) {
3576
+ return false;
3577
+ }
3578
+ // Collinear check: orientation of any mixed triple should be ~0; since we know each triangle is a line, test one.
3579
+ if (Math.abs(triangleOrientation2D(aS, aE, bS)) > TRIANGLE_2D_EPSILON) {
3580
+ return false;
3581
+ }
3582
+ // 1D overlap test along dominant axis (choose axis with larger span to reduce precision issues).
3583
+ spanAx = Math.abs(aE.x - aS.x);
3584
+ spanAy = Math.abs(aE.y - aS.y);
3585
+ if (spanAx >= spanAy) {
3586
+ // Project onto X.
3587
+ aMin = Math.min(aS.x, aE.x) - TRIANGLE_2D_EPSILON;
3588
+ aMax = Math.max(aS.x, aE.x) + TRIANGLE_2D_EPSILON;
3589
+ bMin = Math.min(bS.x, bE.x) - TRIANGLE_2D_EPSILON;
3590
+ bMax = Math.max(bS.x, bE.x) + TRIANGLE_2D_EPSILON;
3591
+ return !(aMax < bMin || bMax < aMin);
3592
+ } else {
3593
+ aMin = Math.min(aS.y, aE.y) - TRIANGLE_2D_EPSILON;
3594
+ aMax = Math.max(aS.y, aE.y) + TRIANGLE_2D_EPSILON;
3595
+ bMin = Math.min(bS.y, bE.y) - TRIANGLE_2D_EPSILON;
3596
+ bMax = Math.max(bS.y, bE.y) + TRIANGLE_2D_EPSILON;
3597
+ return !(aMax < bMin || bMax < aMin);
3598
+ }
3599
+ }
3600
+ if (triangleOrientation2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA) < 0) { // If triangle A is CW.
3601
+ if (triangleOrientation2D(vertex1TriangleB, vertex2TriangleB, vertex3TriangleB) < 0) { // If both A and B are CW → reorder both.
3602
+ return triangleIntersectionCCW2D(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB); // Only A is CW → reorder A.
3603
+ } else {
3604
+ return triangleIntersectionCCW2D(vertex1TriangleA, vertex3TriangleA, vertex2TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB); // Triangle A is CCW.
3605
+ }
3606
+ } else {
3607
+ if (triangleOrientation2D(vertex1TriangleB, vertex2TriangleB, vertex3TriangleB) < 0) { // If only B is CW → reorder B.
3608
+ return triangleIntersectionCCW2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex3TriangleB, vertex2TriangleB); // Both A and B are CCW → no reordering.
3609
+ } else {
3610
+ return triangleIntersectionCCW2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB);
3611
+ }
3612
+ }
3613
+ };
3614
+ triangleOrientation2D = function(a, b, c) {
3615
+ // Compute the signed area of the triangle (a, b, c).
3616
+ return (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);
3617
+ };
3618
+ triangleIntersectionCCW2D = function(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB) {
3619
+ // If vertex1TriangleB is on or to the left of edge vertex1TriangleA-vertex2TriangleA-vertex3TriangleA.
3620
+ if (triangleOrientation2D(vertex1TriangleB, vertex2TriangleB, vertex1TriangleA) >= 0) {
3621
+ // If vertex2TriangleB is on or to the left of edge vertex2TriangleA-vertex3TriangleA-vertex1TriangleA.
3622
+ if (triangleOrientation2D(vertex2TriangleB, vertex3TriangleB, vertex1TriangleA) >= 0) {
3623
+ // If vertex3TriangleB is on or to the left of edge vertex3TriangleA-vertex1TriangleA-vertex1TriangleA.
3624
+ if (triangleOrientation2D(vertex3TriangleB, vertex1TriangleB, vertex1TriangleA) >= 0) {
3625
+ return true; // All vertices of B are inside A.
3626
+ // Then vertex3TriangleB is outside, test edge intersection.
3627
+ } else {
3628
+ return intersectionTestEdge2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB); // Then vertex2TriangleB is outside.
3629
+ }
3630
+ } else {
3631
+
3632
+ // If vertex3TriangleB is on or to the left of edge vertex3TriangleA-vertex1TriangleA-vertex1TriangleA, test edge intersection.
3633
+ if (triangleOrientation2D(vertex3TriangleB, vertex1TriangleB, vertex1TriangleA) >= 0) {
3634
+ return intersectionTestEdge2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex3TriangleB, vertex1TriangleB, vertex2TriangleB); // Then both vertex2TriangleB and vertex3TriangleB are outside, test vertex intersection.
3635
+ } else {
3636
+ return intersectionTestVertex2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB); // Then vertex2TriangleB is on or to the left of edge vertex2TriangleA-vertex3TriangleA-vertex1TriangleA.
3637
+ }
3638
+ }
3639
+ } else {
3640
+
3641
+ // If vertex2TriangleB is on or to the left of edge vertex2TriangleA-vertex3TriangleA-vertex1TriangleA.
3642
+ if (triangleOrientation2D(vertex2TriangleB, vertex3TriangleB, vertex1TriangleA) >= 0) {
3643
+ // If vertex3TriangleB is on or to the left of edge vertex3TriangleA-vertex1TriangleA-vertex1TriangleA, test edge intersection.
3644
+ if (triangleOrientation2D(vertex3TriangleB, vertex1TriangleB, vertex1TriangleA) >= 0) {
3645
+ return intersectionTestEdge2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex2TriangleB, vertex3TriangleB, vertex1TriangleB); // Then vertex3TriangleB is outside, test vertex intersection.
3646
+ } else {
3647
+ return intersectionTestVertex2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex2TriangleB, vertex3TriangleB, vertex1TriangleB); // Then both vertex1TriangleB and vertex2TriangleB are outside, test vertex intersection.
3648
+ }
3649
+ } else {
3650
+ return intersectionTestVertex2D(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex3TriangleB, vertex1TriangleB, vertex2TriangleB);
3651
+ }
3652
+ }
3653
+ };
3654
+ intersectionTestEdge2D = function(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB) {
3655
+ var a1, a2, b1, b2, edgesA, edgesB, i, j, len, len1, segmentIntersectsInclusive;
3656
+ segmentIntersectsInclusive = function(a1, a2, b1, b2) {
3657
+ var maxAx, maxAy, maxBx, maxBy, minAx, minAy, minBx, minBy, o1, o2, o3, o4;
3658
+ // Fast reject by bounding boxes.
3659
+ minAx = Math.min(a1.x, a2.x);
3660
+ maxAx = Math.max(a1.x, a2.x);
3661
+ minAy = Math.min(a1.y, a2.y);
3662
+ maxAy = Math.max(a1.y, a2.y);
3663
+ minBx = Math.min(b1.x, b2.x);
3664
+ maxBx = Math.max(b1.x, b2.x);
3665
+ minBy = Math.min(b1.y, b2.y);
3666
+ maxBy = Math.max(b1.y, b2.y);
3667
+ if (maxAx < minBx || maxBx < minAx || maxAy < minBy || maxBy < minAy) {
3668
+ return false;
3669
+ }
3670
+ o1 = triangleOrientation2D(a1, a2, b1);
3671
+ o2 = triangleOrientation2D(a1, a2, b2);
3672
+ o3 = triangleOrientation2D(b1, b2, a1);
3673
+ o4 = triangleOrientation2D(b1, b2, a2);
3674
+ if ((o1 > 0 && o2 < 0 || o1 < 0 && o2 > 0) && (o3 > 0 && o4 < 0 || o3 < 0 && o4 > 0)) {
3675
+ return true; // Proper intersection.
3676
+ }
3677
+
3678
+ // Collinear / endpoint inclusion checks.
3679
+ if (Math.abs(o1) <= TRIANGLE_2D_EPSILON && pointOnSegmentInclusive2D(b1, a1, a2, TRIANGLE_2D_EPSILON)) {
3680
+ return true;
3681
+ }
3682
+ if (Math.abs(o2) <= TRIANGLE_2D_EPSILON && pointOnSegmentInclusive2D(b2, a1, a2, TRIANGLE_2D_EPSILON)) {
3683
+ return true;
3684
+ }
3685
+ if (Math.abs(o3) <= TRIANGLE_2D_EPSILON && pointOnSegmentInclusive2D(a1, b1, b2, TRIANGLE_2D_EPSILON)) {
3686
+ return true;
3687
+ }
3688
+ if (Math.abs(o4) <= TRIANGLE_2D_EPSILON && pointOnSegmentInclusive2D(a2, b1, b2, TRIANGLE_2D_EPSILON)) {
3689
+ return true;
3690
+ }
3691
+ return false;
3692
+ };
3693
+ edgesA = [[vertex1TriangleA, vertex2TriangleA], [vertex2TriangleA, vertex3TriangleA], [vertex3TriangleA, vertex1TriangleA]];
3694
+ edgesB = [[vertex1TriangleB, vertex2TriangleB], [vertex2TriangleB, vertex3TriangleB], [vertex3TriangleB, vertex1TriangleB]];
3695
+ for (i = 0, len = edgesA.length; i < len; i++) {
3696
+ [a1, a2] = edgesA[i];
3697
+ for (j = 0, len1 = edgesB.length; j < len1; j++) {
3698
+ [b1, b2] = edgesB[j];
3699
+ if (segmentIntersectsInclusive(a1, a2, b1, b2)) {
3700
+ return true;
3701
+ }
3702
+ }
3703
+ }
3704
+ return false;
3705
+ };
3706
+ intersectionTestVertex2D = function(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB) {
3707
+ if (pointInTriangleInclusive2D(vertex1TriangleB, vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, TRIANGLE_2D_EPSILON)) {
3708
+ // Returns true if any vertex of triangle B lies inside (or on) triangle A OR any vertex of triangle A lies inside (or on) triangle B.
3709
+ return true;
3710
+ }
3711
+ if (pointInTriangleInclusive2D(vertex2TriangleB, vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, TRIANGLE_2D_EPSILON)) {
3712
+ return true;
3713
+ }
3714
+ if (pointInTriangleInclusive2D(vertex3TriangleB, vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, TRIANGLE_2D_EPSILON)) {
3715
+ return true;
3716
+ }
3717
+ if (pointInTriangleInclusive2D(vertex1TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, TRIANGLE_2D_EPSILON)) {
3718
+ return true;
3719
+ }
3720
+ if (pointInTriangleInclusive2D(vertex2TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, TRIANGLE_2D_EPSILON)) {
3721
+ return true;
3722
+ }
3723
+ if (pointInTriangleInclusive2D(vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, TRIANGLE_2D_EPSILON)) {
3724
+ return true;
3725
+ }
3726
+ return false;
3727
+ };
3728
+ constructIntersection = function(vertex1TriangleA, vertex2TriangleA, vertex3TriangleA, vertex1TriangleB, vertex2TriangleB, vertex3TriangleB, additions) {
3729
+ var alpha, crossNormal;
3730
+ alpha = void 0;
3731
+ crossNormal = new Vector3();
3732
+ // Compute cross product for triangle orientation.
3733
+ temporaryVector3Primary.subVectors(vertex2TriangleA, vertex1TriangleA);
3734
+ temporaryVector3Secondary.subVectors(vertex3TriangleB, vertex1TriangleA);
3735
+ crossNormal.copy(temporaryVector3Primary).cross(temporaryVector3Secondary);
3736
+ temporaryVector3Quaternary.subVectors(vertex1TriangleB, vertex1TriangleA);
3737
+ if (temporaryVector3Quaternary.dot(crossNormal) > 0) {
3738
+ // Check orientation with triangle A's third vertex.
3739
+ temporaryVector3Primary.subVectors(vertex3TriangleA, vertex1TriangleA);
3740
+ crossNormal.copy(temporaryVector3Primary).cross(temporaryVector3Secondary);
3741
+ if (temporaryVector3Quaternary.dot(crossNormal) <= 0) {
3742
+ // Check orientation with triangle B's second vertex.
3743
+ temporaryVector3Secondary.subVectors(vertex2TriangleB, vertex1TriangleA);
3744
+ crossNormal.copy(temporaryVector3Primary).cross(temporaryVector3Secondary);
3745
+ if (temporaryVector3Quaternary.dot(crossNormal) > 0) {
3746
+ // Compute intersection segment endpoints (case 1).
3747
+ temporaryVector3Primary.subVectors(vertex1TriangleA, vertex1TriangleB);
3748
+ temporaryVector3Secondary.subVectors(vertex1TriangleA, vertex3TriangleA);
3749
+ alpha = temporaryVector3Primary.dot(additions.normal2) / temporaryVector3Secondary.dot(additions.normal2);
3750
+ if (!isFinite(alpha)) {
3751
+ return false;
3752
+ }
3753
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3754
+ additions.source.subVectors(vertex1TriangleA, temporaryVector3Primary);
3755
+ temporaryVector3Primary.subVectors(vertex1TriangleB, vertex1TriangleA);
3756
+ temporaryVector3Secondary.subVectors(vertex1TriangleB, vertex3TriangleB);
3757
+ alpha = temporaryVector3Primary.dot(additions.normal1) / temporaryVector3Secondary.dot(additions.normal1);
3758
+ if (!isFinite(alpha)) {
3759
+ return false;
3760
+ }
3761
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3762
+ additions.target.subVectors(vertex1TriangleB, temporaryVector3Primary);
3763
+ return true;
3764
+ } else {
3765
+ // Compute intersection segment endpoints (case 2).
3766
+ temporaryVector3Primary.subVectors(vertex1TriangleB, vertex1TriangleA);
3767
+ temporaryVector3Secondary.subVectors(vertex1TriangleB, vertex2TriangleB);
3768
+ alpha = temporaryVector3Primary.dot(additions.normal1) / temporaryVector3Secondary.dot(additions.normal1);
3769
+ if (!isFinite(alpha)) {
3770
+ return false;
3771
+ }
3772
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3773
+ additions.source.subVectors(vertex1TriangleB, temporaryVector3Primary);
3774
+ temporaryVector3Primary.subVectors(vertex1TriangleB, vertex1TriangleA);
3775
+ temporaryVector3Secondary.subVectors(vertex1TriangleB, vertex3TriangleB);
3776
+ alpha = temporaryVector3Primary.dot(additions.normal1) / temporaryVector3Secondary.dot(additions.normal1);
3777
+ if (!isFinite(alpha)) {
3778
+ return false;
3779
+ }
3780
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3781
+ additions.target.subVectors(vertex1TriangleB, temporaryVector3Primary);
3782
+ return true;
3783
+ }
3784
+ } else {
3785
+ return false; // No intersection, orientation test failed.
3786
+ }
3787
+ } else {
3788
+ temporaryVector3Secondary.subVectors(vertex2TriangleB, vertex1TriangleA);
3789
+ crossNormal.copy(temporaryVector3Primary).cross(temporaryVector3Secondary);
3790
+ if (temporaryVector3Quaternary.dot(crossNormal) < 0) {
3791
+ return false; // No intersection, orientation test failed.
3792
+ } else {
3793
+ temporaryVector3Primary.subVectors(vertex3TriangleA, vertex1TriangleA);
3794
+ crossNormal.copy(temporaryVector3Primary).cross(temporaryVector3Secondary);
3795
+ if (temporaryVector3Quaternary.dot(crossNormal) < 0) {
3796
+ // Compute intersection segment endpoints (case 3).
3797
+ temporaryVector3Primary.subVectors(vertex1TriangleB, vertex1TriangleA);
3798
+ temporaryVector3Secondary.subVectors(vertex1TriangleB, vertex2TriangleB);
3799
+ alpha = temporaryVector3Primary.dot(additions.normal1) / temporaryVector3Secondary.dot(additions.normal1);
3800
+ if (!isFinite(alpha)) {
3801
+ return false;
3802
+ }
3803
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3804
+ additions.source.subVectors(vertex1TriangleB, temporaryVector3Primary);
3805
+ temporaryVector3Primary.subVectors(vertex1TriangleB, vertex1TriangleA);
3806
+ temporaryVector3Secondary.subVectors(vertex1TriangleB, vertex3TriangleB);
3807
+ alpha = temporaryVector3Primary.dot(additions.normal1) / temporaryVector3Secondary.dot(additions.normal1);
3808
+ if (!isFinite(alpha)) {
3809
+ return false;
3810
+ }
3811
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3812
+ additions.target.subVectors(vertex1TriangleB, temporaryVector3Primary);
3813
+ return true;
3814
+ } else {
3815
+ // Compute intersection segment endpoints (case 4).
3816
+ temporaryVector3Primary.subVectors(vertex1TriangleA, vertex1TriangleB);
3817
+ temporaryVector3Secondary.subVectors(vertex1TriangleA, vertex3TriangleA);
3818
+ alpha = temporaryVector3Primary.dot(additions.normal2) / temporaryVector3Secondary.dot(additions.normal2);
3819
+ if (!isFinite(alpha)) {
3820
+ return false;
3821
+ }
3822
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3823
+ additions.source.subVectors(vertex1TriangleA, temporaryVector3Primary);
3824
+ temporaryVector3Primary.subVectors(vertex1TriangleA, vertex1TriangleB);
3825
+ temporaryVector3Secondary.subVectors(vertex1TriangleA, vertex2TriangleA);
3826
+ alpha = temporaryVector3Primary.dot(additions.normal2) / temporaryVector3Secondary.dot(additions.normal2);
3827
+ if (!isFinite(alpha)) {
3828
+ return false;
3829
+ }
3830
+ temporaryVector3Primary.copy(temporaryVector3Secondary).multiplyScalar(alpha);
3831
+ additions.target.subVectors(vertex1TriangleA, temporaryVector3Primary);
3832
+ return true;
3833
+ }
3834
+ }
3835
+ }
3836
+ return false; // If none of the above, no intersection found.
3837
+ };
3838
+ // Generated by CoffeeScript 2.7.0
3839
+ // Main class for representing vertices in 3D space with optional texture and color data.
3840
+ // Vertices are the fundamental building blocks for polygons and meshes in the geometry system.
3841
+ var Vertex;
3842
+ Vertex = class Vertex {
3843
+ // Main constructor for creating vertex instances.
3844
+ // Initializes position, normal, and optional texture/color attributes.
3845
+ // @param pos - Three.js Vector3 representing the vertex position in 3D space.
3846
+ // @param normal - Three.js Vector3 representing the surface normal at this vertex.
3847
+ // @param uv - Optional Three.js Vector2 for texture coordinates.
3848
+ // @param color - Optional Three.js Vector3 for vertex color information.
3849
+ constructor(pos, normal, uv, color) {
3850
+ // Core geometric properties.
3851
+ this.pos = new Vector3().copy(pos); // 3D position coordinates.
3852
+ this.normal = new Vector3().copy(normal); // Surface normal vector.
3853
+
3854
+ // Optional texture and visual properties.
3855
+ uv && (this.uv = new Vector2().copy(uv)); // Texture coordinates (UV mapping).
3856
+ color && (this.color = new Vector3().copy(color)); // Vertex color data.
3857
+ }
3858
+
3859
+ // === OBJECT CREATION AND COPYING ===
3860
+ clone() { // Create a deep copy of this vertex with all properties.
3861
+ return new Vertex(this.pos.clone(), this.normal.clone(), this.uv && this.uv.clone(), this.color && this.color.clone());
3862
+ }
3863
+ // === GEOMETRIC TRANSFORMATIONS ===
3864
+ flip() { // Flip the normal vector direction (invert surface orientation).
3865
+ return this.normal.negate();
3866
+ }
3867
+ // Creates a new vertex interpolated between this vertex and another.
3868
+ // All properties (position, normal, UV, color) are interpolated if present on both vertices.
3869
+ // @param other - The target vertex to interpolate towards.
3870
+ // @param interpolationFactor - Factor from 0.0 (this vertex) to 1.0 (other vertex).
3871
+ // @return New interpolated Vertex instance.
3872
+ interpolate(other, interpolationFactor) {
3873
+ return new Vertex(this.pos.clone().lerp(other.pos, interpolationFactor), this.normal.clone().lerp(other.normal, interpolationFactor), this.uv && other.uv && this.uv.clone().lerp(other.uv, interpolationFactor), this.color && other.color && this.color.clone().lerp(other.color, interpolationFactor));
3874
+ }
3875
+ // === CLEANUP AND DISPOSAL METHODS ===
3876
+ // Clean up vertex data by setting all properties to undefined.
3877
+ // Used for memory management during intensive operations.
3878
+ delete() {
3879
+ this.pos = void 0;
3880
+ this.normal = void 0;
3881
+ this.uv && (this.uv = void 0);
3882
+ return this.color && (this.color = void 0);
3883
+ }
3884
+ };
3885
+ // Generated by CoffeeScript 2.7.0
3886
+ // === CORE GEOMETRY CLASSES ===
3887
+ // Primary geometric data structures for CSG operations.
3888
+ // === BUFFER UTILITIES ===
3889
+ // Efficient storage and writing of vector data for geometric calculations.
3890
+ // === TRIANGLE VALIDATION AND INTERSECTION ===
3891
+ // Core triangle operations including validation, uniqueness checking, and intersection testing.
3892
+ // === TRIANGLE INTERSECTION RESOLUTION ===
3893
+ // Advanced intersection resolution for both general and coplanar triangle cases.
3894
+ // === GEOMETRIC UTILITIES ===
3895
+ // Point manipulation, coordinate extraction, and spatial calculations.
3896
+ // === POLYGON OPERATIONS ===
3897
+ // Polygon splitting, buffer preparation, and CSG-related polygon management.
3898
+ // === WINDING NUMBER AND INSIDE/OUTSIDE TESTING ===
3899
+ // Robust point-in-polygon testing using winding number algorithms.
3900
+ // === RAY-TRIANGLE INTERSECTION ===
3901
+ // High-performance ray-triangle intersection testing for raycasting operations.
3902
+ // === POLYTREE RESOURCE MANAGEMENT ===
3903
+ // Memory management and cleanup utilities for Polytree operations.
3904
+ // === 2D TRIANGLE OPERATIONS ===
3905
+ // Specialized 2D triangle overlap and intersection testing for planar geometry.
3906
+ // === 2D INTERSECTION TESTING ===
3907
+ // Low-level 2D edge and vertex intersection tests for triangle operations.
3908
+ // === INTERSECTION CONSTRUCTION ===
3909
+ // Low-level intersection construction utilities for building intersection results.
3910
+
3911
+ // ES module exports
3912
+ export default Polytree;
3913
+ export { GEOMETRIC_EPSILON as GEOMETRIC_EPSILON };
3914
+ export { RAY_INTERSECTION_EPSILON as RAY_INTERSECTION_EPSILON };
3915
+ export { TRIANGLE_2D_EPSILON as TRIANGLE_2D_EPSILON };
3916
+ export { WINDING_NUMBER_FULL_ROTATION as WINDING_NUMBER_FULL_ROTATION };
3917
+ export { POLYGON_COPLANAR as POLYGON_COPLANAR };
3918
+ export { POLYGON_FRONT as POLYGON_FRONT };
3919
+ export { POLYGON_BACK as POLYGON_BACK };
3920
+ export { POLYGON_SPANNING as POLYGON_SPANNING };
3921
+ export { temporaryVector3Primary as temporaryVector3Primary };
3922
+ export { temporaryVector3Secondary as temporaryVector3Secondary };
3923
+ export { temporaryVector3Tertiary as temporaryVector3Tertiary };
3924
+ export { temporaryVector3Quaternary as temporaryVector3Quaternary };
3925
+ export { temporaryBoundingBox as temporaryBoundingBox };
3926
+ export { temporaryRaycaster as temporaryRaycaster };
3927
+ export { temporaryRay as temporaryRay };
3928
+ export { defaultRayDirection as defaultRayDirection };
3929
+ export { windingNumberVector1 as windingNumberVector1 };
3930
+ export { windingNumberVector2 as windingNumberVector2 };
3931
+ export { windingNumberVector3 as windingNumberVector3 };
3932
+ export { windingNumberTestPoint as windingNumberTestPoint };
3933
+ export { windingNumberEpsilonOffsets as windingNumberEpsilonOffsets };
3934
+ export { windingNumberEpsilonOffsetsCount as windingNumberEpsilonOffsetsCount };
3935
+ export { windingNumberMatrix3 as windingNumberMatrix3 };
3936
+ export { rayTriangleEdge1 as rayTriangleEdge1 };
3937
+ export { rayTriangleEdge2 as rayTriangleEdge2 };
3938
+ export { rayTriangleHVector as rayTriangleHVector };
3939
+ export { rayTriangleSVector as rayTriangleSVector };
3940
+ export { rayTriangleQVector as rayTriangleQVector };
3941
+ export { temporaryTriangleVertex as temporaryTriangleVertex };
3942
+ export { temporaryTriangleVertexSecondary as temporaryTriangleVertexSecondary };
3943
+ export { temporaryMatrix3 as temporaryMatrix3 };
3944
+ export { temporaryMatrixWithNormalCalc as temporaryMatrixWithNormalCalc };
3945
+ export { meshOperationNormalVector as meshOperationNormalVector };
3946
+ export { meshOperationVertexVector as meshOperationVertexVector };
3947
+ export { POLYTREE_MAX_DEPTH as POLYTREE_MAX_DEPTH };
3948
+ export { POLYTREE_MAX_POLYGONS_PER_NODE as POLYTREE_MAX_POLYGONS_PER_NODE };
3949
+ export { POLYTREE_MIN_NODE_SIZE as POLYTREE_MIN_NODE_SIZE };
3950
+ export { DEFAULT_MATERIAL_INDEX as DEFAULT_MATERIAL_INDEX };
3951
+ export { MAX_REFINEMENT_ITERATIONS as MAX_REFINEMENT_ITERATIONS };
3952
+ export { DEFAULT_COORDINATE_PRECISION as DEFAULT_COORDINATE_PRECISION };
3953
+ export { POINT_COINCIDENCE_THRESHOLD as POINT_COINCIDENCE_THRESHOLD };
3954
+ export { DEFAULT_BUFFER_SIZE as DEFAULT_BUFFER_SIZE };
3955
+ export { MAX_WORKER_THREADS as MAX_WORKER_THREADS };
3956
+ export { ASYNC_OPERATION_TIMEOUT as ASYNC_OPERATION_TIMEOUT };
3957
+ export { GARBAGE_COLLECTION_THRESHOLD as GARBAGE_COLLECTION_THRESHOLD };
3958
+ export { MEMORY_USAGE_WARNING_LIMIT as MEMORY_USAGE_WARNING_LIMIT };
3959
+ export { GEOMETRY_CACHE_SIZE as GEOMETRY_CACHE_SIZE };
3960
+ export { INTERSECTION_CACHE_SIZE as INTERSECTION_CACHE_SIZE };
3961
+ export { polygonID as polygonID };
3962
+ export { DEBUG_VERBOSE_LOGGING as DEBUG_VERBOSE_LOGGING };
3963
+ export { DEBUG_PERFORMANCE_TIMING as DEBUG_PERFORMANCE_TIMING };
3964
+ export { DEBUG_GEOMETRY_VALIDATION as DEBUG_GEOMETRY_VALIDATION };
3965
+ export { DEBUG_INTERSECTION_VERIFICATION as DEBUG_INTERSECTION_VERIFICATION };
3966
+ export { DEBUG_COLOR_FRONT as DEBUG_COLOR_FRONT };
3967
+ export { DEBUG_COLOR_BACK as DEBUG_COLOR_BACK };
3968
+ export { DEBUG_COLOR_COPLANAR as DEBUG_COLOR_COPLANAR };
3969
+ export { DEBUG_COLOR_SPANNING as DEBUG_COLOR_SPANNING };
3970
+ export { Polytree as Polytree };
3971
+ export { Plane as Plane };
3972
+ export { Vertex as Vertex };
3973
+ export { Polygon as Polygon };
3974
+ export { createVector2Buffer as createVector2Buffer };
3975
+ export { createVector3Buffer as createVector3Buffer };
3976
+ export { isValidTriangle as isValidTriangle };
3977
+ export { isUniqueTriangle as isUniqueTriangle };
3978
+ export { testRayTriangleIntersection as rayIntersectsTriangle };
3979
+ export { triangleIntersectsTriangle as triangleIntersectsTriangle };
3980
+ export { resolveTriangleIntersection as resolveTriangleIntersection };
3981
+ export { resolveCoplanarTriangleIntersection as resolveCoplanarTriangleIntersection };
3982
+ export { sortRaycastIntersectionsByDistance as sortRaycastIntersectionsByDistance };
3983
+ export { roundPointCoordinates as roundPointCoordinates };
3984
+ export { extractCoordinatesFromArray as extractCoordinatesFromArray };
3985
+ export { splitPolygonByPlane as splitPolygonByPlane };
3986
+ export { splitPolygonVertexArray as splitPolygonVertexArray };
3987
+ export { prepareTriangleBufferFromPolygons as prepareTriangleBufferFromPolygons };
3988
+ export { calculateWindingNumberFromBuffer as calculateWindingNumberFromBuffer };
3989
+ export { testPolygonInsideUsingWindingNumber as testPolygonInsideUsingWindingNumber };
3990
+ export { testRayTriangleIntersection as testRayTriangleIntersection };
3991
+ export { handleIntersectingPolytrees as handleIntersectingPolytrees };
3992
+ export { disposePolytreeResources as disposePolytreeResources };
3993
+ export { trianglesOverlap2D as trianglesOverlap2D };
3994
+ export { triangleOrientation2D as triangleOrientation2D };
3995
+ export { triangleIntersectionCCW2D as triangleIntersectionCCW2D };
3996
+ export { intersectionTestEdge2D as intersectionTestEdge2D };
3997
+ export { intersectionTestVertex2D as intersectionTestVertex2D };
3998
+ export { constructIntersection as constructIntersection };