@jgphilpott/polytree 0.0.9 → 0.1.1

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.
@@ -175,7 +175,7 @@ if (typeof module !== 'undefined' && module.exports) {
175
175
  // Generated by CoffeeScript 2.7.0
176
176
  // === BUFFER UTILITIES ===
177
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;
178
+ var calculateWindingNumberFromBuffer, checkMemoryUsage, clearGeometryCache, clearIntersectionCache, createIntersectionCacheKey, createVector2Buffer, createVector3Buffer, disposePolytreeResources, extractCoordinatesFromArray, geometryCache, geometryCacheKeys, handleIntersectingPolytrees, intersectionCache, intersectionCacheKeys, operationCounter, prepareTriangleBufferFromPolygons, roundPointCoordinates, signedVolumeOfTriangle, sortRaycastIntersectionsByDistance, splitPolygonByPlane, splitPolygonVertexArray, testPolygonInsideUsingWindingNumber, testRayTriangleIntersection;
179
179
  intersectionCache = new Map();
180
180
  intersectionCacheKeys = [];
181
181
  // Geometry calculation cache for expensive operations.
@@ -325,29 +325,17 @@ splitPolygonByPlane = function(polygon, plane, result = []) {
325
325
  switch (polygonType) {
326
326
  case POLYGON_COPLANAR:
327
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
328
  result.push(returnPolygon);
332
329
  break;
333
330
  case POLYGON_FRONT:
334
331
  returnPolygon.type = "front";
335
332
  result.push(returnPolygon);
336
- if (DEBUG_VERBOSE_LOGGING) {
337
- console.log("Polygon classified as FRONT, debug color:", DEBUG_COLOR_FRONT.toString(16));
338
- }
339
333
  break;
340
334
  case POLYGON_BACK:
341
335
  returnPolygon.type = "back";
342
336
  result.push(returnPolygon);
343
- if (DEBUG_VERBOSE_LOGGING) {
344
- console.log("Polygon classified as BACK, debug color:", DEBUG_COLOR_BACK.toString(16));
345
- }
346
337
  break;
347
338
  case POLYGON_SPANNING:
348
- if (DEBUG_VERBOSE_LOGGING) {
349
- console.log("Polygon classified as SPANNING, debug color:", DEBUG_COLOR_SPANNING.toString(16));
350
- }
351
339
  frontVertices = [];
352
340
  backVertices = [];
353
341
  // Process each edge to build front and back vertex lists.
@@ -539,10 +527,7 @@ prepareTriangleBufferFromPolygons = function(polygonArray) {
539
527
  // @param targetVector - Optional Vector3 to store the intersection point.
540
528
  // @return Vector3 intersection point or null if no intersection.
541
529
  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
- }
530
+ var determinant, firstBarycentricCoordinate, intersectionDistance, inverseDeterminant, secondBarycentricCoordinate;
546
531
  // Calculate triangle edge vectors.
547
532
  rayTriangleEdge1.subVectors(triangle.b, triangle.a);
548
533
  rayTriangleEdge2.subVectors(triangle.c, triangle.a);
@@ -570,19 +555,7 @@ testRayTriangleIntersection = function(ray, triangle, targetVector = new Vector3
570
555
  intersectionDistance = inverseDeterminant * rayTriangleEdge2.dot(rayTriangleQVector);
571
556
  // Check if intersection is in front of ray origin.
572
557
  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;
558
+ return targetVector.copy(ray.direction).multiplyScalar(intersectionDistance).add(ray.origin);
586
559
  }
587
560
  return null;
588
561
  };
@@ -594,8 +567,7 @@ testRayTriangleIntersection = function(ray, triangle, targetVector = new Vector3
594
567
  // @param polytreeB - Second polytree for intersection processing.
595
568
  // @param processBothDirections - Whether to process intersections in both directions.
596
569
  handleIntersectingPolytrees = function(polytreeA, polytreeB, processBothDirections = true) {
597
- var endTime, polytreeABuffer, polytreeBBuffer, startTime;
598
- startTime = DEBUG_PERFORMANCE_TIMING ? performance.now() : 0;
570
+ var polytreeABuffer, polytreeBBuffer;
599
571
  operationCounter++; // Increment operation counter and check for GC threshold.
600
572
  if (operationCounter >= GARBAGE_COLLECTION_THRESHOLD) {
601
573
  operationCounter = 0;
@@ -623,14 +595,22 @@ handleIntersectingPolytrees = function(polytreeA, polytreeB, processBothDirectio
623
595
  polytreeABuffer = void 0;
624
596
  }
625
597
  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`);
598
+ return polytreeBBuffer = void 0;
632
599
  }
633
600
  };
601
+ // Calculate the signed volume of a tetrahedron formed by the origin and three triangle vertices.
602
+ // This is used for volume calculations of 3D geometries by summing the signed volumes of all triangles in the mesh.
603
+ // The formula is: V = (1/6) * dot(v1, cross(v2, v3))
604
+ // where v1, v2, v3 are the three vertices of the triangle.
605
+ // @param vertex1 - First vertex of the triangle (Vector3).
606
+ // @param vertex2 - Second vertex of the triangle (Vector3).
607
+ // @param vertex3 - Third vertex of the triangle (Vector3).
608
+ // @return Signed volume of the tetrahedron formed by origin and the three vertices.
609
+ signedVolumeOfTriangle = function(vertex1, vertex2, vertex3) {
610
+ // Use temporary vectors to avoid creating new objects
611
+ temporaryVector3Primary.copy(vertex2).cross(vertex3);
612
+ return vertex1.dot(temporaryVector3Primary) / 6.0;
613
+ };
634
614
  // Dispose of polytree resources to prevent memory leaks.
635
615
  // This utility safely calls the delete method on polytree instances
636
616
  // if the disposal feature is enabled in the Polytree configuration.
@@ -2120,6 +2100,492 @@ Polytree.fromMesh = function(obj, objectIndex, polytree = new Polytree(), buildT
2120
2100
  }
2121
2101
  return polytree;
2122
2102
  };
2103
+ // Calculate the surface area of a Three.js mesh or geometry.
2104
+ // This method extracts triangles from the geometry and sums their areas using Three.js Triangle.getArea().
2105
+ // @param meshOrGeometry - Three.js mesh or BufferGeometry to calculate surface area for.
2106
+ // @return Total surface area as a number.
2107
+ Polytree.getSurface = function(meshOrGeometry) {
2108
+ var geometry, i, index, k, posattr, ref, surface, triangle, v1, v1Index, v2, v2Index, v3, v3Index;
2109
+ surface = 0;
2110
+ if (!meshOrGeometry) {
2111
+ throw new Error("Input is required.");
2112
+ }
2113
+ // Extract geometry from mesh if needed.
2114
+ geometry = meshOrGeometry.geometry ? meshOrGeometry.geometry : meshOrGeometry;
2115
+ if (!(geometry && geometry.attributes)) {
2116
+ throw new Error("No geometry found.");
2117
+ }
2118
+ posattr = geometry.attributes.position; // Extract position and index attributes.
2119
+ if (!posattr) {
2120
+ throw new Error("Geometry has no position attribute.");
2121
+ }
2122
+ // Generate index array (explicit or implicit).
2123
+ index = geometry.index ? geometry.index.array : Array((posattr.array.length / posattr.itemSize) | 0).fill().map(function(_, i) {
2124
+ return i;
2125
+ });
2126
+ // Process each triangle in the geometry.
2127
+ for (i = k = 0, ref = index.length; k < ref; i = k += 3) {
2128
+ // Extract triangle vertices.
2129
+ v1Index = index[i + 0] * 3;
2130
+ v2Index = index[i + 1] * 3;
2131
+ v3Index = index[i + 2] * 3;
2132
+ v1 = new Vector3(posattr.array[v1Index], posattr.array[v1Index + 1], posattr.array[v1Index + 2]);
2133
+ v2 = new Vector3(posattr.array[v2Index], posattr.array[v2Index + 1], posattr.array[v2Index + 2]);
2134
+ v3 = new Vector3(posattr.array[v3Index], posattr.array[v3Index + 1], posattr.array[v3Index + 2]);
2135
+ // Create triangle and add its area to the total surface area.
2136
+ triangle = new Triangle(v1, v2, v3);
2137
+ surface += triangle.getArea();
2138
+ }
2139
+ return surface;
2140
+ };
2141
+ // Instance method: Calculate the surface area of a mesh or geometry.
2142
+ // Usage: polytree.getSurface(meshOrGeometry)
2143
+ Polytree.prototype.getSurface = function(meshOrGeometry) {
2144
+ return Polytree.getSurface(meshOrGeometry);
2145
+ };
2146
+ // Calculate the volume of a 3D geometry using the divergence theorem.
2147
+ // This method accepts either a Three.js Mesh or BufferGeometry and returns
2148
+ // the total volume by summing the signed volumes of all triangular faces.
2149
+ // @param input - Either a Three.js Mesh object or BufferGeometry to calculate volume for.
2150
+ // @return The volume of the geometry as a number.
2151
+ Polytree.getVolume = function(input) {
2152
+ var bufferGeometry, face, faces, geometry, index, k, l, position, ref, ref1, v1, v2, v3, volume;
2153
+ volume = 0;
2154
+ v1 = new Vector3();
2155
+ v2 = new Vector3();
2156
+ v3 = new Vector3();
2157
+ // Handle different input types.
2158
+ geometry = null;
2159
+ if (input.isMesh) {
2160
+ geometry = input.geometry;
2161
+ } else if (input.isBufferGeometry) {
2162
+ geometry = input;
2163
+ } else {
2164
+ throw new Error("Input must be a Three.js Mesh or BufferGeometry.");
2165
+ }
2166
+ // Ensure we have a BufferGeometry.
2167
+ bufferGeometry = (typeof geometry.toBuffer === "function" ? geometry.toBuffer() : void 0) || geometry;
2168
+ if (!bufferGeometry.isBufferGeometry) {
2169
+ throw new Error("Unable to convert input to BufferGeometry.");
2170
+ }
2171
+ position = bufferGeometry.attributes.position;
2172
+ if (!position) {
2173
+ return 0; // No position data means no volume.
2174
+ }
2175
+ if (bufferGeometry.index) {
2176
+ // Indexed geometry - use index buffer.
2177
+ index = bufferGeometry.index;
2178
+ faces = index.count / 3;
2179
+ for (face = k = 0, ref = faces; (0 <= ref ? k < ref : k > ref); face = 0 <= ref ? ++k : --k) {
2180
+ v1.fromBufferAttribute(position, index.array[face * 3 + 0]);
2181
+ v2.fromBufferAttribute(position, index.array[face * 3 + 1]);
2182
+ v3.fromBufferAttribute(position, index.array[face * 3 + 2]);
2183
+ volume += signedVolumeOfTriangle(v1, v2, v3);
2184
+ }
2185
+ } else {
2186
+ // Non-indexed geometry - use vertex order directly.
2187
+ faces = position.count / 3;
2188
+ for (face = l = 0, ref1 = faces; (0 <= ref1 ? l < ref1 : l > ref1); face = 0 <= ref1 ? ++l : --l) {
2189
+ v1.fromBufferAttribute(position, face * 3 + 0);
2190
+ v2.fromBufferAttribute(position, face * 3 + 1);
2191
+ v3.fromBufferAttribute(position, face * 3 + 2);
2192
+ volume += signedVolumeOfTriangle(v1, v2, v3);
2193
+ }
2194
+ }
2195
+ return Math.abs(volume); // Return absolute value for total volume.
2196
+ };
2197
+ // Instance method: Calculate the volume of a mesh or geometry.
2198
+ // Usage: polytree.getVolume(meshOrGeometry)
2199
+ Polytree.prototype.getVolume = function(input) {
2200
+ return Polytree.getVolume(input);
2201
+ };
2202
+ // Generated by CoffeeScript 2.7.0
2203
+ // Spatial query utilities for enhanced Polytree functionality.
2204
+ // These methods provide advanced spatial querying capabilities useful for 3D printing,
2205
+ // CAD operations, and general-purpose geometric analysis beyond basic CSG.
2206
+ // Inspired by three-mesh-bvh spatial query capabilities.
2207
+ // Helper function to convert various input types to polytree.
2208
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance.
2209
+ // @return Object with polytree and shouldCleanup flag, or null if invalid input.
2210
+ var convertToPolytree;
2211
+ convertToPolytree = function(input) {
2212
+ var tempMaterial, tempMesh;
2213
+ if (!input) {
2214
+ return null;
2215
+ }
2216
+ if (input.isPolytree) {
2217
+ return {
2218
+ polytree: input,
2219
+ shouldCleanup: false
2220
+ };
2221
+ } else if (input.isMesh) {
2222
+ return {
2223
+ polytree: Polytree.fromMesh(input),
2224
+ shouldCleanup: true
2225
+ };
2226
+ } else if (input.isBufferGeometry) {
2227
+ // Create a temporary mesh from BufferGeometry.
2228
+ // Use a simple material object instead of MeshBasicMaterial constructor.
2229
+ tempMaterial = {
2230
+ isMaterial: true,
2231
+ type: "MeshBasicMaterial"
2232
+ };
2233
+ tempMesh = new Mesh(input, tempMaterial);
2234
+ return {
2235
+ polytree: Polytree.fromMesh(tempMesh),
2236
+ shouldCleanup: true
2237
+ };
2238
+ } else {
2239
+ return null;
2240
+ }
2241
+ };
2242
+ // Find the closest point on any triangle surface to the given point.
2243
+ // This is essential for collision detection, mesh repair, and support structure generation.
2244
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to query.
2245
+ // @param targetPoint - Vector3 point to find closest point to.
2246
+ // @param target - Optional object to store result data.
2247
+ // @param maxDistance - Maximum search distance (Infinity by default).
2248
+ // @return Object with point, distance, and triangle properties or null if none found.
2249
+ Polytree.closestPointToPoint = function(input, targetPoint, target = {}, maxDistance = 2e308) {
2250
+ var closestDistance, closestPoint, closestTriangle, currentDistance, j, len, polytree, result, shouldCleanup, testTriangle, triangle, trianglePoint, triangles;
2251
+ if (!(input && targetPoint)) {
2252
+ return null;
2253
+ }
2254
+ // Handle different input types - convert to polytree if needed.
2255
+ result = convertToPolytree(input);
2256
+ if (!result) {
2257
+ return null;
2258
+ }
2259
+ ({polytree, shouldCleanup} = result);
2260
+ closestDistance = maxDistance;
2261
+ closestPoint = null;
2262
+ closestTriangle = null;
2263
+ triangles = polytree.getTriangles();
2264
+ for (j = 0, len = triangles.length; j < len; j++) {
2265
+ triangle = triangles[j];
2266
+ // Create temporary triangle for calculations.
2267
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2268
+ // Calculate closest point on triangle to target point.
2269
+ trianglePoint = new Vector3();
2270
+ testTriangle.closestPointToPoint(targetPoint, trianglePoint);
2271
+ // Calculate distance to this point.
2272
+ currentDistance = trianglePoint.distanceTo(targetPoint);
2273
+ if (currentDistance < closestDistance) {
2274
+ closestDistance = currentDistance;
2275
+ closestPoint = trianglePoint.clone();
2276
+ closestTriangle = triangle;
2277
+ }
2278
+ }
2279
+ if (closestPoint) {
2280
+ target.point = closestPoint;
2281
+ target.distance = closestDistance;
2282
+ target.triangle = closestTriangle;
2283
+ // Clean up temporary polytree if created.
2284
+ shouldCleanup && polytree.delete();
2285
+ return target;
2286
+ }
2287
+ // Clean up temporary polytree if created.
2288
+ shouldCleanup && polytree.delete();
2289
+ return null;
2290
+ };
2291
+ // Calculate the shortest distance from a point to any surface in the geometry.
2292
+ // Useful for distance field generation and proximity analysis.
2293
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to query.
2294
+ // @param targetPoint - Vector3 point to calculate distance to.
2295
+ // @return Distance value as number, or Infinity if no surfaces found.
2296
+ Polytree.distanceToPoint = function(input, targetPoint) {
2297
+ var result;
2298
+ result = Polytree.closestPointToPoint(input, targetPoint);
2299
+ return (result != null ? result.distance : void 0) || 2e308;
2300
+ };
2301
+ // Test if a sphere intersects with the geometry.
2302
+ // Useful for collision detection and proximity testing.
2303
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to test against.
2304
+ // @param sphere - Sphere object with center and radius properties.
2305
+ // @return Boolean indicating intersection.
2306
+ Polytree.intersectsSphere = function(input, sphere) {
2307
+ var closestPoint, distance, j, len, polytree, result, shouldCleanup, testTriangle, triangle, triangles;
2308
+ if (!(input && sphere)) {
2309
+ return false;
2310
+ }
2311
+ // Handle different input types - convert to polytree if needed.
2312
+ result = convertToPolytree(input);
2313
+ if (!result) {
2314
+ return false;
2315
+ }
2316
+ ({polytree, shouldCleanup} = result);
2317
+ triangles = polytree.getTriangles();
2318
+ for (j = 0, len = triangles.length; j < len; j++) {
2319
+ triangle = triangles[j];
2320
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2321
+ // Simple sphere-triangle intersection: check if sphere center is close to triangle.
2322
+ closestPoint = new Vector3();
2323
+ testTriangle.closestPointToPoint(sphere.center, closestPoint);
2324
+ distance = closestPoint.distanceTo(sphere.center);
2325
+ if (distance <= sphere.radius) {
2326
+ // Clean up temporary polytree if created.
2327
+ shouldCleanup && polytree.delete();
2328
+ return true;
2329
+ }
2330
+ }
2331
+ // Clean up temporary polytree if created.
2332
+ shouldCleanup && polytree.delete();
2333
+ return false;
2334
+ };
2335
+ // Test if a bounding box intersects with the geometry.
2336
+ // Useful for broad-phase collision detection and spatial partitioning.
2337
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to test against.
2338
+ // @param boundingBox - Box3 object defining the bounding volume.
2339
+ // @return Boolean indicating intersection.
2340
+ Polytree.intersectsBox = function(input, boundingBox) {
2341
+ var j, len, polytree, result, shouldCleanup, testTriangle, triangle, triangles;
2342
+ if (!(input && boundingBox)) {
2343
+ return false;
2344
+ }
2345
+ // Handle different input types - convert to polytree if needed.
2346
+ result = convertToPolytree(input);
2347
+ if (!result) {
2348
+ return false;
2349
+ }
2350
+ ({polytree, shouldCleanup} = result);
2351
+ triangles = polytree.getTriangles();
2352
+ for (j = 0, len = triangles.length; j < len; j++) {
2353
+ triangle = triangles[j];
2354
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2355
+ if (boundingBox.intersectsTriangle(testTriangle)) {
2356
+ // Clean up temporary polytree if created.
2357
+ shouldCleanup && polytree.delete();
2358
+ return true;
2359
+ }
2360
+ }
2361
+ // Clean up temporary polytree if created.
2362
+ shouldCleanup && polytree.delete();
2363
+ return false;
2364
+ };
2365
+ // Find all intersection points between a plane and the mesh surface.
2366
+ // This is the core functionality needed for 3D printing slicing operations.
2367
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to slice.
2368
+ // @param plane - Plane object defining the slicing plane.
2369
+ // @param target - Optional array to store intersection line segments.
2370
+ // @return Array of Line3 objects representing intersection segments.
2371
+ Polytree.intersectPlane = function(input, plane, target = []) {
2372
+ var edge, endDist, endPoint, intersectionPoint, intersectionPoints, j, k, len, len1, polytree, result, shouldCleanup, startDist, startPoint, t, testTriangle, triangle, triangleEdges, triangles;
2373
+ if (!(input && plane)) {
2374
+ return target;
2375
+ }
2376
+ // Handle different input types - convert to polytree if needed.
2377
+ result = convertToPolytree(input);
2378
+ if (!result) {
2379
+ return target;
2380
+ }
2381
+ ({polytree, shouldCleanup} = result);
2382
+ triangles = polytree.getTriangles();
2383
+ for (j = 0, len = triangles.length; j < len; j++) {
2384
+ triangle = triangles[j];
2385
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2386
+ intersectionPoints = [];
2387
+ // Test each edge of the triangle against the plane.
2388
+ triangleEdges = [[triangle.a, triangle.b], [triangle.b, triangle.c], [triangle.c, triangle.a]];
2389
+ for (k = 0, len1 = triangleEdges.length; k < len1; k++) {
2390
+ edge = triangleEdges[k];
2391
+ startPoint = edge[0];
2392
+ endPoint = edge[1];
2393
+ // Calculate distances to plane manually (since bundle context may not have plane methods).
2394
+ // Distance = normal.dot(point) + constant
2395
+ startDist = plane.normal.dot(startPoint) + plane.constant;
2396
+ endDist = plane.normal.dot(endPoint) + plane.constant;
2397
+ // Check if edge crosses the plane (different signs).
2398
+ if ((startDist * endDist) < 0) {
2399
+ // Calculate intersection point using linear interpolation.
2400
+ t = startDist / (startDist - endDist);
2401
+ intersectionPoint = new Vector3();
2402
+ intersectionPoint.lerpVectors(startPoint, endPoint, t);
2403
+ intersectionPoints.push(intersectionPoint);
2404
+ }
2405
+ }
2406
+ // If we have exactly 2 intersection points, create a line segment.
2407
+ if (intersectionPoints.length === 2) {
2408
+ target.push(new Line3(intersectionPoints[0], intersectionPoints[1]));
2409
+ }
2410
+ }
2411
+ // Clean up temporary polytree if created.
2412
+ shouldCleanup && polytree.delete();
2413
+ return target;
2414
+ };
2415
+ // Create a series of parallel plane intersections for layer-by-layer slicing.
2416
+ // Essential for 3D printing applications where the model needs to be sliced
2417
+ // into horizontal layers at regular intervals.
2418
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to slice.
2419
+ // @param layerHeight - Height between each slice layer.
2420
+ // @param minZ - Starting Z coordinate for slicing.
2421
+ // @param maxZ - Ending Z coordinate for slicing.
2422
+ // @param normal - Optional plane normal vector (defaults to Z-up).
2423
+ // @return Array of arrays, each containing Line3 segments for that layer.
2424
+ Polytree.sliceIntoLayers = function(input, layerHeight, minZ, maxZ, normal = new Vector3(0, 0, 1)) {
2425
+ var currentZ, edge, endDist, endPoint, intersectionPoint, intersectionPoints, j, k, layerSegments, layers, len, len1, planeConstant, planeNormal, polytree, result, shouldCleanup, startDist, startPoint, t, triangle, triangleEdges, triangles;
2426
+ if (!(input && layerHeight > 0 && minZ < maxZ)) {
2427
+ return [];
2428
+ }
2429
+ // Convert input to polytree once at the beginning.
2430
+ result = convertToPolytree(input);
2431
+ if (!result) {
2432
+ return [];
2433
+ }
2434
+ ({polytree, shouldCleanup} = result);
2435
+ layers = [];
2436
+ currentZ = minZ;
2437
+ while (currentZ <= maxZ) {
2438
+ // Create plane at current height using manual plane equation.
2439
+ // Since bundle context may not have proper Plane constructor access.
2440
+ planeNormal = normal.clone();
2441
+ planeConstant = -currentZ;
2442
+ // Use manual plane-triangle intersection instead of Plane object.
2443
+ layerSegments = [];
2444
+ triangles = polytree.getTriangles();
2445
+ for (j = 0, len = triangles.length; j < len; j++) {
2446
+ triangle = triangles[j];
2447
+ intersectionPoints = [];
2448
+ // Test each edge of the triangle against the plane.
2449
+ triangleEdges = [[triangle.a, triangle.b], [triangle.b, triangle.c], [triangle.c, triangle.a]];
2450
+ for (k = 0, len1 = triangleEdges.length; k < len1; k++) {
2451
+ edge = triangleEdges[k];
2452
+ startPoint = edge[0];
2453
+ endPoint = edge[1];
2454
+ // Calculate distances to plane manually.
2455
+ startDist = planeNormal.dot(startPoint) + planeConstant;
2456
+ endDist = planeNormal.dot(endPoint) + planeConstant;
2457
+ // Check if edge crosses the plane (different signs).
2458
+ if ((startDist * endDist) < 0) {
2459
+ // Calculate intersection point using linear interpolation.
2460
+ t = startDist / (startDist - endDist);
2461
+ intersectionPoint = new Vector3();
2462
+ intersectionPoint.lerpVectors(startPoint, endPoint, t);
2463
+ intersectionPoints.push(intersectionPoint);
2464
+ }
2465
+ }
2466
+ // If we have exactly 2 intersection points, create a line segment.
2467
+ if (intersectionPoints.length === 2) {
2468
+ layerSegments.push(new Line3(intersectionPoints[0], intersectionPoints[1]));
2469
+ }
2470
+ }
2471
+ layers.push(layerSegments);
2472
+ currentZ += layerHeight;
2473
+ }
2474
+ // Clean up temporary polytree if created.
2475
+ shouldCleanup && polytree.delete();
2476
+ return layers;
2477
+ };
2478
+ // Perform a generic spatial query using a custom callback function.
2479
+ // This provides flexibility for implementing custom spatial operations.
2480
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to query.
2481
+ // @param queryCallback - Function that tests each triangle and returns boolean.
2482
+ // @param collectCallback - Optional function to collect/process matching triangles.
2483
+ // @return Array of results from collectCallback, or array of matching triangles.
2484
+ Polytree.shapecast = function(input, queryCallback, collectCallback = null) {
2485
+ var j, len, polytree, result, results, shouldCleanup, testTriangle, triangle, triangles;
2486
+ if (!(input && queryCallback)) {
2487
+ return [];
2488
+ }
2489
+ // Handle different input types - convert to polytree if needed.
2490
+ result = convertToPolytree(input);
2491
+ if (!result) {
2492
+ return [];
2493
+ }
2494
+ ({polytree, shouldCleanup} = result);
2495
+ results = [];
2496
+ triangles = polytree.getTriangles();
2497
+ for (j = 0, len = triangles.length; j < len; j++) {
2498
+ triangle = triangles[j];
2499
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2500
+ if (queryCallback(testTriangle, triangle)) {
2501
+ if (collectCallback) {
2502
+ result = collectCallback(testTriangle, triangle);
2503
+ if (result != null) {
2504
+ results.push(result);
2505
+ }
2506
+ } else {
2507
+ results.push(triangle);
2508
+ }
2509
+ }
2510
+ }
2511
+ // Clean up temporary polytree if created.
2512
+ shouldCleanup && polytree.delete();
2513
+ return results;
2514
+ };
2515
+ // Find all triangles within a specified distance of a target point.
2516
+ // Useful for local mesh operations and region-based analysis.
2517
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to search.
2518
+ // @param targetPoint - Vector3 center point for the search.
2519
+ // @param searchRadius - Maximum distance from point to include triangles.
2520
+ // @return Array of triangles within the search radius.
2521
+ Polytree.getTrianglesNearPoint = function(input, targetPoint, searchRadius) {
2522
+ if (!(input && targetPoint && searchRadius > 0)) {
2523
+ return [];
2524
+ }
2525
+ return Polytree.shapecast(input, function(testTriangle, originalTriangle) {
2526
+ // Check if any vertex of the triangle is within the search radius.
2527
+ return testTriangle.a.distanceTo(targetPoint) <= searchRadius || testTriangle.b.distanceTo(targetPoint) <= searchRadius || testTriangle.c.distanceTo(targetPoint) <= searchRadius;
2528
+ });
2529
+ };
2530
+ // Calculate approximate volume using monte carlo sampling.
2531
+ // Useful for complex geometries where analytical volume calculation is difficult.
2532
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to analyze.
2533
+ // @param sampleCount - Number of random samples to use (default 10000).
2534
+ // @param boundingBox - Optional bounding box for sampling region.
2535
+ // @return Estimated volume as number.
2536
+ Polytree.estimateVolumeViaSampling = function(input, sampleCount = 10000, boundingBox = null) {
2537
+ var boxSize, boxVolume, i, identityMatrix, insideCount, intersections, j, k, len, polytree, ref, result, samplePoint, shouldCleanup, testRay, triangle, triangles, volumeRatio;
2538
+ if (!input) {
2539
+ return 0;
2540
+ }
2541
+ // Handle different input types - convert to polytree if needed.
2542
+ result = convertToPolytree(input);
2543
+ if (!result) {
2544
+ return 0;
2545
+ }
2546
+ ({polytree, shouldCleanup} = result);
2547
+ // Use polytree bounding box if none provided.
2548
+ if (!boundingBox) {
2549
+ boundingBox = new Box3();
2550
+ triangles = polytree.getTriangles();
2551
+ for (j = 0, len = triangles.length; j < len; j++) {
2552
+ triangle = triangles[j];
2553
+ boundingBox.expandByPoint(triangle.a);
2554
+ boundingBox.expandByPoint(triangle.b);
2555
+ boundingBox.expandByPoint(triangle.c);
2556
+ }
2557
+ }
2558
+ // Calculate bounding box volume for scaling.
2559
+ boxSize = new Vector3();
2560
+ boundingBox.getSize(boxSize);
2561
+ boxVolume = boxSize.x * boxSize.y * boxSize.z;
2562
+ if (boxVolume === 0) {
2563
+ return 0;
2564
+ }
2565
+ insideCount = 0;
2566
+ // Test random points inside bounding box.
2567
+ for (i = k = 0, ref = sampleCount; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
2568
+ // Generate random point in bounding box.
2569
+ samplePoint = new Vector3(boundingBox.min.x + Math.random() * boxSize.x, boundingBox.min.y + Math.random() * boxSize.y, boundingBox.min.z + Math.random() * boxSize.z);
2570
+ // Test if point is inside the mesh using ray casting.
2571
+ testRay = new Ray(samplePoint, new Vector3(1, 0, 0));
2572
+ // Create a simple identity matrix manually to avoid Three.js import issues.
2573
+ identityMatrix = {
2574
+ elements: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
2575
+ isMatrix4: true
2576
+ };
2577
+ intersections = polytree.rayIntersect(testRay, identityMatrix);
2578
+ if (intersections.length % 2 === 1) {
2579
+ // Point is inside if odd number of intersections.
2580
+ insideCount++;
2581
+ }
2582
+ }
2583
+ // Calculate volume ratio and scale by bounding box volume.
2584
+ volumeRatio = insideCount / sampleCount;
2585
+ // Clean up temporary polytree if created.
2586
+ shouldCleanup && polytree.delete();
2587
+ return volumeRatio * boxVolume;
2588
+ };
2123
2589
  // Generated by CoffeeScript 2.7.0
2124
2590
  // ============================================================================
2125
2591
  // Unite CSG Operation for Polytree
@@ -3246,9 +3712,6 @@ Notes:
3246
3712
  @returns {Boolean} - True if the triangles overlap in 2D, false otherwise. */
3247
3713
  var constructIntersection, intersectionTestEdge2D, intersectionTestVertex2D, isUniqueTriangle, isValidTriangle, pointInTriangleInclusive2D, pointOnSegmentInclusive2D, pointsEqual2D, resolveCoplanarTriangleIntersection, resolveTriangleIntersection, triangleIntersectionCCW2D, triangleIntersectsTriangle, triangleOrientation2D, trianglesOverlap2D;
3248
3714
  isValidTriangle = function(triangle) {
3249
- if (DEBUG_GEOMETRY_VALIDATION) {
3250
- console.log("Validating triangle:", triangle.a, triangle.b, triangle.c);
3251
- }
3252
3715
  if (triangle.a.equals(triangle.b)) {
3253
3716
  return false;
3254
3717
  }
@@ -3258,9 +3721,6 @@ isValidTriangle = function(triangle) {
3258
3721
  if (triangle.b.equals(triangle.c)) {
3259
3722
  return false;
3260
3723
  }
3261
- if (DEBUG_GEOMETRY_VALIDATION) {
3262
- console.log("Triangle validation passed.");
3263
- }
3264
3724
  return true;
3265
3725
  };
3266
3726
  isUniqueTriangle = function(triangle, set, map) {
@@ -3907,6 +4367,8 @@ Vertex = class Vertex {
3907
4367
  // Low-level 2D edge and vertex intersection tests for triangle operations.
3908
4368
  // === INTERSECTION CONSTRUCTION ===
3909
4369
  // Low-level intersection construction utilities for building intersection results.
4370
+ // === VOLUME CALCULATION UTILITIES ===
4371
+ // Helper functions for calculating volumes of 3D geometries.
3910
4372
 
3911
4373
  // ES module exports
3912
4374
  export default Polytree;
@@ -3996,3 +4458,4 @@ export { triangleIntersectionCCW2D as triangleIntersectionCCW2D };
3996
4458
  export { intersectionTestEdge2D as intersectionTestEdge2D };
3997
4459
  export { intersectionTestVertex2D as intersectionTestVertex2D };
3998
4460
  export { constructIntersection as constructIntersection };
4461
+ export { signedVolumeOfTriangle as signedVolumeOfTriangle };