@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.
@@ -309,7 +309,7 @@ if (typeof module !== 'undefined' && module.exports) {
309
309
  // === BUFFER UTILITIES ===
310
310
 
311
311
  // Simple LRU cache for intersection results to improve performance.
312
- var calculateWindingNumberFromBuffer, checkMemoryUsage, clearGeometryCache, clearIntersectionCache, createIntersectionCacheKey, createVector2Buffer, createVector3Buffer, disposePolytreeResources, extractCoordinatesFromArray, geometryCache, geometryCacheKeys, handleIntersectingPolytrees, intersectionCache, intersectionCacheKeys, operationCounter, prepareTriangleBufferFromPolygons, roundPointCoordinates, sortRaycastIntersectionsByDistance, splitPolygonByPlane, splitPolygonVertexArray, testPolygonInsideUsingWindingNumber, testRayTriangleIntersection;
312
+ var calculateWindingNumberFromBuffer, checkMemoryUsage, clearGeometryCache, clearIntersectionCache, createIntersectionCacheKey, createVector2Buffer, createVector3Buffer, disposePolytreeResources, extractCoordinatesFromArray, geometryCache, geometryCacheKeys, handleIntersectingPolytrees, intersectionCache, intersectionCacheKeys, operationCounter, prepareTriangleBufferFromPolygons, roundPointCoordinates, signedVolumeOfTriangle, sortRaycastIntersectionsByDistance, splitPolygonByPlane, splitPolygonVertexArray, testPolygonInsideUsingWindingNumber, testRayTriangleIntersection;
313
313
 
314
314
  intersectionCache = new Map();
315
315
 
@@ -488,29 +488,17 @@ splitPolygonByPlane = function(polygon, plane, result = []) {
488
488
  switch (polygonType) {
489
489
  case POLYGON_COPLANAR:
490
490
  returnPolygon.type = plane.normal.dot(polygon.plane.normal) > 0 ? "coplanar-front" : "coplanar-back";
491
- if (DEBUG_VERBOSE_LOGGING) {
492
- console.log("Polygon classified as COPLANAR, debug color:", DEBUG_COLOR_COPLANAR.toString(16));
493
- }
494
491
  result.push(returnPolygon);
495
492
  break;
496
493
  case POLYGON_FRONT:
497
494
  returnPolygon.type = "front";
498
495
  result.push(returnPolygon);
499
- if (DEBUG_VERBOSE_LOGGING) {
500
- console.log("Polygon classified as FRONT, debug color:", DEBUG_COLOR_FRONT.toString(16));
501
- }
502
496
  break;
503
497
  case POLYGON_BACK:
504
498
  returnPolygon.type = "back";
505
499
  result.push(returnPolygon);
506
- if (DEBUG_VERBOSE_LOGGING) {
507
- console.log("Polygon classified as BACK, debug color:", DEBUG_COLOR_BACK.toString(16));
508
- }
509
500
  break;
510
501
  case POLYGON_SPANNING:
511
- if (DEBUG_VERBOSE_LOGGING) {
512
- console.log("Polygon classified as SPANNING, debug color:", DEBUG_COLOR_SPANNING.toString(16));
513
- }
514
502
  frontVertices = [];
515
503
  backVertices = [];
516
504
  // Process each edge to build front and back vertex lists.
@@ -719,10 +707,7 @@ prepareTriangleBufferFromPolygons = function(polygonArray) {
719
707
 
720
708
  // @return Vector3 intersection point or null if no intersection.
721
709
  testRayTriangleIntersection = function(ray, triangle, targetVector = new Vector3()) {
722
- var determinant, firstBarycentricCoordinate, intersectionDistance, inverseDeterminant, result, secondBarycentricCoordinate;
723
- if (DEBUG_VERBOSE_LOGGING) {
724
- console.log("Testing ray-triangle intersection with ray origin:", ray.origin, "direction:", ray.direction);
725
- }
710
+ var determinant, firstBarycentricCoordinate, intersectionDistance, inverseDeterminant, secondBarycentricCoordinate;
726
711
  // Calculate triangle edge vectors.
727
712
  rayTriangleEdge1.subVectors(triangle.b, triangle.a);
728
713
  rayTriangleEdge2.subVectors(triangle.c, triangle.a);
@@ -750,19 +735,7 @@ testRayTriangleIntersection = function(ray, triangle, targetVector = new Vector3
750
735
  intersectionDistance = inverseDeterminant * rayTriangleEdge2.dot(rayTriangleQVector);
751
736
  // Check if intersection is in front of ray origin.
752
737
  if (intersectionDistance > RAY_INTERSECTION_EPSILON) {
753
- result = targetVector.copy(ray.direction).multiplyScalar(intersectionDistance).add(ray.origin);
754
- if (DEBUG_INTERSECTION_VERIFICATION) {
755
- console.log("Ray-triangle intersection verified:", {
756
- distance: intersectionDistance,
757
- point: result,
758
- triangle: {
759
- a: triangle.a,
760
- b: triangle.b,
761
- c: triangle.c
762
- }
763
- });
764
- }
765
- return result;
738
+ return targetVector.copy(ray.direction).multiplyScalar(intersectionDistance).add(ray.origin);
766
739
  }
767
740
  return null;
768
741
  };
@@ -777,8 +750,7 @@ testRayTriangleIntersection = function(ray, triangle, targetVector = new Vector3
777
750
  // @param polytreeB - Second polytree for intersection processing.
778
751
  // @param processBothDirections - Whether to process intersections in both directions.
779
752
  handleIntersectingPolytrees = function(polytreeA, polytreeB, processBothDirections = true) {
780
- var endTime, polytreeABuffer, polytreeBBuffer, startTime;
781
- startTime = DEBUG_PERFORMANCE_TIMING ? performance.now() : 0;
753
+ var polytreeABuffer, polytreeBBuffer;
782
754
  operationCounter++; // Increment operation counter and check for GC threshold.
783
755
  if (operationCounter >= GARBAGE_COLLECTION_THRESHOLD) {
784
756
  operationCounter = 0;
@@ -806,15 +778,27 @@ handleIntersectingPolytrees = function(polytreeA, polytreeB, processBothDirectio
806
778
  polytreeABuffer = void 0;
807
779
  }
808
780
  if (polytreeBBuffer !== void 0) {
809
- polytreeBBuffer = void 0;
810
- }
811
- if (DEBUG_PERFORMANCE_TIMING) {
812
- endTime = performance.now();
813
- polytreeBBuffer = void 0;
814
- return console.log(`handleIntersectingPolytrees took ${endTime - startTime} milliseconds`);
781
+ return polytreeBBuffer = void 0;
815
782
  }
816
783
  };
817
784
 
785
+ // Calculate the signed volume of a tetrahedron formed by the origin and three triangle vertices.
786
+ // This is used for volume calculations of 3D geometries by summing the signed volumes of all triangles in the mesh.
787
+
788
+ // The formula is: V = (1/6) * dot(v1, cross(v2, v3))
789
+ // where v1, v2, v3 are the three vertices of the triangle.
790
+
791
+ // @param vertex1 - First vertex of the triangle (Vector3).
792
+ // @param vertex2 - Second vertex of the triangle (Vector3).
793
+ // @param vertex3 - Third vertex of the triangle (Vector3).
794
+
795
+ // @return Signed volume of the tetrahedron formed by origin and the three vertices.
796
+ signedVolumeOfTriangle = function(vertex1, vertex2, vertex3) {
797
+ // Use temporary vectors to avoid creating new objects
798
+ temporaryVector3Primary.copy(vertex2).cross(vertex3);
799
+ return vertex1.dot(temporaryVector3Primary) / 6.0;
800
+ };
801
+
818
802
  // Dispose of polytree resources to prevent memory leaks.
819
803
  // This utility safely calls the delete method on polytree instances
820
804
  // if the disposal feature is enabled in the Polytree configuration.
@@ -2390,6 +2374,533 @@ Polytree.fromMesh = function(obj, objectIndex, polytree = new Polytree(), buildT
2390
2374
  }
2391
2375
  return polytree;
2392
2376
  };
2377
+
2378
+ // Calculate the surface area of a Three.js mesh or geometry.
2379
+ // This method extracts triangles from the geometry and sums their areas using Three.js Triangle.getArea().
2380
+
2381
+ // @param meshOrGeometry - Three.js mesh or BufferGeometry to calculate surface area for.
2382
+
2383
+ // @return Total surface area as a number.
2384
+ Polytree.getSurface = function(meshOrGeometry) {
2385
+ var geometry, i, index, k, posattr, ref, surface, triangle, v1, v1Index, v2, v2Index, v3, v3Index;
2386
+ surface = 0;
2387
+ if (!meshOrGeometry) {
2388
+ throw new Error("Input is required.");
2389
+ }
2390
+ // Extract geometry from mesh if needed.
2391
+ geometry = meshOrGeometry.geometry ? meshOrGeometry.geometry : meshOrGeometry;
2392
+ if (!(geometry && geometry.attributes)) {
2393
+ throw new Error("No geometry found.");
2394
+ }
2395
+ posattr = geometry.attributes.position; // Extract position and index attributes.
2396
+ if (!posattr) {
2397
+ throw new Error("Geometry has no position attribute.");
2398
+ }
2399
+ // Generate index array (explicit or implicit).
2400
+ index = geometry.index ? geometry.index.array : Array((posattr.array.length / posattr.itemSize) | 0).fill().map(function(_, i) {
2401
+ return i;
2402
+ });
2403
+ // Process each triangle in the geometry.
2404
+ for (i = k = 0, ref = index.length; k < ref; i = k += 3) {
2405
+ // Extract triangle vertices.
2406
+ v1Index = index[i + 0] * 3;
2407
+ v2Index = index[i + 1] * 3;
2408
+ v3Index = index[i + 2] * 3;
2409
+ v1 = new Vector3(posattr.array[v1Index], posattr.array[v1Index + 1], posattr.array[v1Index + 2]);
2410
+ v2 = new Vector3(posattr.array[v2Index], posattr.array[v2Index + 1], posattr.array[v2Index + 2]);
2411
+ v3 = new Vector3(posattr.array[v3Index], posattr.array[v3Index + 1], posattr.array[v3Index + 2]);
2412
+ // Create triangle and add its area to the total surface area.
2413
+ triangle = new Triangle(v1, v2, v3);
2414
+ surface += triangle.getArea();
2415
+ }
2416
+ return surface;
2417
+ };
2418
+
2419
+ // Instance method: Calculate the surface area of a mesh or geometry.
2420
+ // Usage: polytree.getSurface(meshOrGeometry)
2421
+ Polytree.prototype.getSurface = function(meshOrGeometry) {
2422
+ return Polytree.getSurface(meshOrGeometry);
2423
+ };
2424
+
2425
+ // Calculate the volume of a 3D geometry using the divergence theorem.
2426
+ // This method accepts either a Three.js Mesh or BufferGeometry and returns
2427
+ // the total volume by summing the signed volumes of all triangular faces.
2428
+
2429
+ // @param input - Either a Three.js Mesh object or BufferGeometry to calculate volume for.
2430
+
2431
+ // @return The volume of the geometry as a number.
2432
+ Polytree.getVolume = function(input) {
2433
+ var bufferGeometry, face, faces, geometry, index, k, l, position, ref, ref1, v1, v2, v3, volume;
2434
+ volume = 0;
2435
+ v1 = new Vector3();
2436
+ v2 = new Vector3();
2437
+ v3 = new Vector3();
2438
+ // Handle different input types.
2439
+ geometry = null;
2440
+ if (input.isMesh) {
2441
+ geometry = input.geometry;
2442
+ } else if (input.isBufferGeometry) {
2443
+ geometry = input;
2444
+ } else {
2445
+ throw new Error("Input must be a Three.js Mesh or BufferGeometry.");
2446
+ }
2447
+ // Ensure we have a BufferGeometry.
2448
+ bufferGeometry = (typeof geometry.toBuffer === "function" ? geometry.toBuffer() : void 0) || geometry;
2449
+ if (!bufferGeometry.isBufferGeometry) {
2450
+ throw new Error("Unable to convert input to BufferGeometry.");
2451
+ }
2452
+ position = bufferGeometry.attributes.position;
2453
+ if (!position) {
2454
+ return 0; // No position data means no volume.
2455
+ }
2456
+ if (bufferGeometry.index) {
2457
+ // Indexed geometry - use index buffer.
2458
+ index = bufferGeometry.index;
2459
+ faces = index.count / 3;
2460
+ for (face = k = 0, ref = faces; (0 <= ref ? k < ref : k > ref); face = 0 <= ref ? ++k : --k) {
2461
+ v1.fromBufferAttribute(position, index.array[face * 3 + 0]);
2462
+ v2.fromBufferAttribute(position, index.array[face * 3 + 1]);
2463
+ v3.fromBufferAttribute(position, index.array[face * 3 + 2]);
2464
+ volume += signedVolumeOfTriangle(v1, v2, v3);
2465
+ }
2466
+ } else {
2467
+ // Non-indexed geometry - use vertex order directly.
2468
+ faces = position.count / 3;
2469
+ for (face = l = 0, ref1 = faces; (0 <= ref1 ? l < ref1 : l > ref1); face = 0 <= ref1 ? ++l : --l) {
2470
+ v1.fromBufferAttribute(position, face * 3 + 0);
2471
+ v2.fromBufferAttribute(position, face * 3 + 1);
2472
+ v3.fromBufferAttribute(position, face * 3 + 2);
2473
+ volume += signedVolumeOfTriangle(v1, v2, v3);
2474
+ }
2475
+ }
2476
+ return Math.abs(volume); // Return absolute value for total volume.
2477
+ };
2478
+
2479
+
2480
+ // Instance method: Calculate the volume of a mesh or geometry.
2481
+ // Usage: polytree.getVolume(meshOrGeometry)
2482
+ Polytree.prototype.getVolume = function(input) {
2483
+ return Polytree.getVolume(input);
2484
+ };
2485
+ // Generated by CoffeeScript 2.7.0
2486
+ // Spatial query utilities for enhanced Polytree functionality.
2487
+ // These methods provide advanced spatial querying capabilities useful for 3D printing,
2488
+ // CAD operations, and general-purpose geometric analysis beyond basic CSG.
2489
+
2490
+ // Inspired by three-mesh-bvh spatial query capabilities.
2491
+
2492
+ // Helper function to convert various input types to polytree.
2493
+
2494
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance.
2495
+
2496
+ // @return Object with polytree and shouldCleanup flag, or null if invalid input.
2497
+ var convertToPolytree;
2498
+
2499
+ convertToPolytree = function(input) {
2500
+ var tempMaterial, tempMesh;
2501
+ if (!input) {
2502
+ return null;
2503
+ }
2504
+ if (input.isPolytree) {
2505
+ return {
2506
+ polytree: input,
2507
+ shouldCleanup: false
2508
+ };
2509
+ } else if (input.isMesh) {
2510
+ return {
2511
+ polytree: Polytree.fromMesh(input),
2512
+ shouldCleanup: true
2513
+ };
2514
+ } else if (input.isBufferGeometry) {
2515
+ // Create a temporary mesh from BufferGeometry.
2516
+ // Use a simple material object instead of MeshBasicMaterial constructor.
2517
+ tempMaterial = {
2518
+ isMaterial: true,
2519
+ type: "MeshBasicMaterial"
2520
+ };
2521
+ tempMesh = new Mesh(input, tempMaterial);
2522
+ return {
2523
+ polytree: Polytree.fromMesh(tempMesh),
2524
+ shouldCleanup: true
2525
+ };
2526
+ } else {
2527
+ return null;
2528
+ }
2529
+ };
2530
+
2531
+ // Find the closest point on any triangle surface to the given point.
2532
+ // This is essential for collision detection, mesh repair, and support structure generation.
2533
+
2534
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to query.
2535
+ // @param targetPoint - Vector3 point to find closest point to.
2536
+ // @param target - Optional object to store result data.
2537
+ // @param maxDistance - Maximum search distance (Infinity by default).
2538
+
2539
+ // @return Object with point, distance, and triangle properties or null if none found.
2540
+ Polytree.closestPointToPoint = function(input, targetPoint, target = {}, maxDistance = 2e308) {
2541
+ var closestDistance, closestPoint, closestTriangle, currentDistance, j, len, polytree, result, shouldCleanup, testTriangle, triangle, trianglePoint, triangles;
2542
+ if (!(input && targetPoint)) {
2543
+ return null;
2544
+ }
2545
+ // Handle different input types - convert to polytree if needed.
2546
+ result = convertToPolytree(input);
2547
+ if (!result) {
2548
+ return null;
2549
+ }
2550
+ ({polytree, shouldCleanup} = result);
2551
+ closestDistance = maxDistance;
2552
+ closestPoint = null;
2553
+ closestTriangle = null;
2554
+ triangles = polytree.getTriangles();
2555
+ for (j = 0, len = triangles.length; j < len; j++) {
2556
+ triangle = triangles[j];
2557
+ // Create temporary triangle for calculations.
2558
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2559
+ // Calculate closest point on triangle to target point.
2560
+ trianglePoint = new Vector3();
2561
+ testTriangle.closestPointToPoint(targetPoint, trianglePoint);
2562
+ // Calculate distance to this point.
2563
+ currentDistance = trianglePoint.distanceTo(targetPoint);
2564
+ if (currentDistance < closestDistance) {
2565
+ closestDistance = currentDistance;
2566
+ closestPoint = trianglePoint.clone();
2567
+ closestTriangle = triangle;
2568
+ }
2569
+ }
2570
+ if (closestPoint) {
2571
+ target.point = closestPoint;
2572
+ target.distance = closestDistance;
2573
+ target.triangle = closestTriangle;
2574
+ // Clean up temporary polytree if created.
2575
+ shouldCleanup && polytree.delete();
2576
+ return target;
2577
+ }
2578
+ // Clean up temporary polytree if created.
2579
+ shouldCleanup && polytree.delete();
2580
+ return null;
2581
+ };
2582
+
2583
+ // Calculate the shortest distance from a point to any surface in the geometry.
2584
+ // Useful for distance field generation and proximity analysis.
2585
+
2586
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to query.
2587
+ // @param targetPoint - Vector3 point to calculate distance to.
2588
+
2589
+ // @return Distance value as number, or Infinity if no surfaces found.
2590
+ Polytree.distanceToPoint = function(input, targetPoint) {
2591
+ var result;
2592
+ result = Polytree.closestPointToPoint(input, targetPoint);
2593
+ return (result != null ? result.distance : void 0) || 2e308;
2594
+ };
2595
+
2596
+ // Test if a sphere intersects with the geometry.
2597
+ // Useful for collision detection and proximity testing.
2598
+
2599
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to test against.
2600
+ // @param sphere - Sphere object with center and radius properties.
2601
+
2602
+ // @return Boolean indicating intersection.
2603
+ Polytree.intersectsSphere = function(input, sphere) {
2604
+ var closestPoint, distance, j, len, polytree, result, shouldCleanup, testTriangle, triangle, triangles;
2605
+ if (!(input && sphere)) {
2606
+ return false;
2607
+ }
2608
+ // Handle different input types - convert to polytree if needed.
2609
+ result = convertToPolytree(input);
2610
+ if (!result) {
2611
+ return false;
2612
+ }
2613
+ ({polytree, shouldCleanup} = result);
2614
+ triangles = polytree.getTriangles();
2615
+ for (j = 0, len = triangles.length; j < len; j++) {
2616
+ triangle = triangles[j];
2617
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2618
+ // Simple sphere-triangle intersection: check if sphere center is close to triangle.
2619
+ closestPoint = new Vector3();
2620
+ testTriangle.closestPointToPoint(sphere.center, closestPoint);
2621
+ distance = closestPoint.distanceTo(sphere.center);
2622
+ if (distance <= sphere.radius) {
2623
+ // Clean up temporary polytree if created.
2624
+ shouldCleanup && polytree.delete();
2625
+ return true;
2626
+ }
2627
+ }
2628
+ // Clean up temporary polytree if created.
2629
+ shouldCleanup && polytree.delete();
2630
+ return false;
2631
+ };
2632
+
2633
+ // Test if a bounding box intersects with the geometry.
2634
+ // Useful for broad-phase collision detection and spatial partitioning.
2635
+
2636
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to test against.
2637
+ // @param boundingBox - Box3 object defining the bounding volume.
2638
+
2639
+ // @return Boolean indicating intersection.
2640
+ Polytree.intersectsBox = function(input, boundingBox) {
2641
+ var j, len, polytree, result, shouldCleanup, testTriangle, triangle, triangles;
2642
+ if (!(input && boundingBox)) {
2643
+ return false;
2644
+ }
2645
+ // Handle different input types - convert to polytree if needed.
2646
+ result = convertToPolytree(input);
2647
+ if (!result) {
2648
+ return false;
2649
+ }
2650
+ ({polytree, shouldCleanup} = result);
2651
+ triangles = polytree.getTriangles();
2652
+ for (j = 0, len = triangles.length; j < len; j++) {
2653
+ triangle = triangles[j];
2654
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2655
+ if (boundingBox.intersectsTriangle(testTriangle)) {
2656
+ // Clean up temporary polytree if created.
2657
+ shouldCleanup && polytree.delete();
2658
+ return true;
2659
+ }
2660
+ }
2661
+ // Clean up temporary polytree if created.
2662
+ shouldCleanup && polytree.delete();
2663
+ return false;
2664
+ };
2665
+
2666
+ // Find all intersection points between a plane and the mesh surface.
2667
+ // This is the core functionality needed for 3D printing slicing operations.
2668
+
2669
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to slice.
2670
+ // @param plane - Plane object defining the slicing plane.
2671
+ // @param target - Optional array to store intersection line segments.
2672
+
2673
+ // @return Array of Line3 objects representing intersection segments.
2674
+ Polytree.intersectPlane = function(input, plane, target = []) {
2675
+ var edge, endDist, endPoint, intersectionPoint, intersectionPoints, j, k, len, len1, polytree, result, shouldCleanup, startDist, startPoint, t, testTriangle, triangle, triangleEdges, triangles;
2676
+ if (!(input && plane)) {
2677
+ return target;
2678
+ }
2679
+ // Handle different input types - convert to polytree if needed.
2680
+ result = convertToPolytree(input);
2681
+ if (!result) {
2682
+ return target;
2683
+ }
2684
+ ({polytree, shouldCleanup} = result);
2685
+ triangles = polytree.getTriangles();
2686
+ for (j = 0, len = triangles.length; j < len; j++) {
2687
+ triangle = triangles[j];
2688
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2689
+ intersectionPoints = [];
2690
+ // Test each edge of the triangle against the plane.
2691
+ triangleEdges = [[triangle.a, triangle.b], [triangle.b, triangle.c], [triangle.c, triangle.a]];
2692
+ for (k = 0, len1 = triangleEdges.length; k < len1; k++) {
2693
+ edge = triangleEdges[k];
2694
+ startPoint = edge[0];
2695
+ endPoint = edge[1];
2696
+ // Calculate distances to plane manually (since bundle context may not have plane methods).
2697
+ // Distance = normal.dot(point) + constant
2698
+ startDist = plane.normal.dot(startPoint) + plane.constant;
2699
+ endDist = plane.normal.dot(endPoint) + plane.constant;
2700
+ // Check if edge crosses the plane (different signs).
2701
+ if ((startDist * endDist) < 0) {
2702
+ // Calculate intersection point using linear interpolation.
2703
+ t = startDist / (startDist - endDist);
2704
+ intersectionPoint = new Vector3();
2705
+ intersectionPoint.lerpVectors(startPoint, endPoint, t);
2706
+ intersectionPoints.push(intersectionPoint);
2707
+ }
2708
+ }
2709
+ // If we have exactly 2 intersection points, create a line segment.
2710
+ if (intersectionPoints.length === 2) {
2711
+ target.push(new Line3(intersectionPoints[0], intersectionPoints[1]));
2712
+ }
2713
+ }
2714
+ // Clean up temporary polytree if created.
2715
+ shouldCleanup && polytree.delete();
2716
+ return target;
2717
+ };
2718
+
2719
+ // Create a series of parallel plane intersections for layer-by-layer slicing.
2720
+ // Essential for 3D printing applications where the model needs to be sliced
2721
+ // into horizontal layers at regular intervals.
2722
+
2723
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to slice.
2724
+ // @param layerHeight - Height between each slice layer.
2725
+ // @param minZ - Starting Z coordinate for slicing.
2726
+ // @param maxZ - Ending Z coordinate for slicing.
2727
+ // @param normal - Optional plane normal vector (defaults to Z-up).
2728
+
2729
+ // @return Array of arrays, each containing Line3 segments for that layer.
2730
+ Polytree.sliceIntoLayers = function(input, layerHeight, minZ, maxZ, normal = new Vector3(0, 0, 1)) {
2731
+ var currentZ, edge, endDist, endPoint, intersectionPoint, intersectionPoints, j, k, layerSegments, layers, len, len1, planeConstant, planeNormal, polytree, result, shouldCleanup, startDist, startPoint, t, triangle, triangleEdges, triangles;
2732
+ if (!(input && layerHeight > 0 && minZ < maxZ)) {
2733
+ return [];
2734
+ }
2735
+ // Convert input to polytree once at the beginning.
2736
+ result = convertToPolytree(input);
2737
+ if (!result) {
2738
+ return [];
2739
+ }
2740
+ ({polytree, shouldCleanup} = result);
2741
+ layers = [];
2742
+ currentZ = minZ;
2743
+ while (currentZ <= maxZ) {
2744
+ // Create plane at current height using manual plane equation.
2745
+ // Since bundle context may not have proper Plane constructor access.
2746
+ planeNormal = normal.clone();
2747
+ planeConstant = -currentZ;
2748
+ // Use manual plane-triangle intersection instead of Plane object.
2749
+ layerSegments = [];
2750
+ triangles = polytree.getTriangles();
2751
+ for (j = 0, len = triangles.length; j < len; j++) {
2752
+ triangle = triangles[j];
2753
+ intersectionPoints = [];
2754
+ // Test each edge of the triangle against the plane.
2755
+ triangleEdges = [[triangle.a, triangle.b], [triangle.b, triangle.c], [triangle.c, triangle.a]];
2756
+ for (k = 0, len1 = triangleEdges.length; k < len1; k++) {
2757
+ edge = triangleEdges[k];
2758
+ startPoint = edge[0];
2759
+ endPoint = edge[1];
2760
+ // Calculate distances to plane manually.
2761
+ startDist = planeNormal.dot(startPoint) + planeConstant;
2762
+ endDist = planeNormal.dot(endPoint) + planeConstant;
2763
+ // Check if edge crosses the plane (different signs).
2764
+ if ((startDist * endDist) < 0) {
2765
+ // Calculate intersection point using linear interpolation.
2766
+ t = startDist / (startDist - endDist);
2767
+ intersectionPoint = new Vector3();
2768
+ intersectionPoint.lerpVectors(startPoint, endPoint, t);
2769
+ intersectionPoints.push(intersectionPoint);
2770
+ }
2771
+ }
2772
+ // If we have exactly 2 intersection points, create a line segment.
2773
+ if (intersectionPoints.length === 2) {
2774
+ layerSegments.push(new Line3(intersectionPoints[0], intersectionPoints[1]));
2775
+ }
2776
+ }
2777
+ layers.push(layerSegments);
2778
+ currentZ += layerHeight;
2779
+ }
2780
+ // Clean up temporary polytree if created.
2781
+ shouldCleanup && polytree.delete();
2782
+ return layers;
2783
+ };
2784
+
2785
+ // Perform a generic spatial query using a custom callback function.
2786
+ // This provides flexibility for implementing custom spatial operations.
2787
+
2788
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to query.
2789
+ // @param queryCallback - Function that tests each triangle and returns boolean.
2790
+ // @param collectCallback - Optional function to collect/process matching triangles.
2791
+
2792
+ // @return Array of results from collectCallback, or array of matching triangles.
2793
+ Polytree.shapecast = function(input, queryCallback, collectCallback = null) {
2794
+ var j, len, polytree, result, results, shouldCleanup, testTriangle, triangle, triangles;
2795
+ if (!(input && queryCallback)) {
2796
+ return [];
2797
+ }
2798
+ // Handle different input types - convert to polytree if needed.
2799
+ result = convertToPolytree(input);
2800
+ if (!result) {
2801
+ return [];
2802
+ }
2803
+ ({polytree, shouldCleanup} = result);
2804
+ results = [];
2805
+ triangles = polytree.getTriangles();
2806
+ for (j = 0, len = triangles.length; j < len; j++) {
2807
+ triangle = triangles[j];
2808
+ testTriangle = new Triangle(triangle.a, triangle.b, triangle.c);
2809
+ if (queryCallback(testTriangle, triangle)) {
2810
+ if (collectCallback) {
2811
+ result = collectCallback(testTriangle, triangle);
2812
+ if (result != null) {
2813
+ results.push(result);
2814
+ }
2815
+ } else {
2816
+ results.push(triangle);
2817
+ }
2818
+ }
2819
+ }
2820
+ // Clean up temporary polytree if created.
2821
+ shouldCleanup && polytree.delete();
2822
+ return results;
2823
+ };
2824
+
2825
+ // Find all triangles within a specified distance of a target point.
2826
+ // Useful for local mesh operations and region-based analysis.
2827
+
2828
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to search.
2829
+ // @param targetPoint - Vector3 center point for the search.
2830
+ // @param searchRadius - Maximum distance from point to include triangles.
2831
+
2832
+ // @return Array of triangles within the search radius.
2833
+ Polytree.getTrianglesNearPoint = function(input, targetPoint, searchRadius) {
2834
+ if (!(input && targetPoint && searchRadius > 0)) {
2835
+ return [];
2836
+ }
2837
+ return Polytree.shapecast(input, function(testTriangle, originalTriangle) {
2838
+ // Check if any vertex of the triangle is within the search radius.
2839
+ return testTriangle.a.distanceTo(targetPoint) <= searchRadius || testTriangle.b.distanceTo(targetPoint) <= searchRadius || testTriangle.c.distanceTo(targetPoint) <= searchRadius;
2840
+ });
2841
+ };
2842
+
2843
+ // Calculate approximate volume using monte carlo sampling.
2844
+ // Useful for complex geometries where analytical volume calculation is difficult.
2845
+
2846
+ // @param input - Three.js Mesh, BufferGeometry, or Polytree instance to analyze.
2847
+ // @param sampleCount - Number of random samples to use (default 10000).
2848
+ // @param boundingBox - Optional bounding box for sampling region.
2849
+
2850
+ // @return Estimated volume as number.
2851
+ Polytree.estimateVolumeViaSampling = function(input, sampleCount = 10000, boundingBox = null) {
2852
+ var boxSize, boxVolume, i, identityMatrix, insideCount, intersections, j, k, len, polytree, ref, result, samplePoint, shouldCleanup, testRay, triangle, triangles, volumeRatio;
2853
+ if (!input) {
2854
+ return 0;
2855
+ }
2856
+ // Handle different input types - convert to polytree if needed.
2857
+ result = convertToPolytree(input);
2858
+ if (!result) {
2859
+ return 0;
2860
+ }
2861
+ ({polytree, shouldCleanup} = result);
2862
+ // Use polytree bounding box if none provided.
2863
+ if (!boundingBox) {
2864
+ boundingBox = new Box3();
2865
+ triangles = polytree.getTriangles();
2866
+ for (j = 0, len = triangles.length; j < len; j++) {
2867
+ triangle = triangles[j];
2868
+ boundingBox.expandByPoint(triangle.a);
2869
+ boundingBox.expandByPoint(triangle.b);
2870
+ boundingBox.expandByPoint(triangle.c);
2871
+ }
2872
+ }
2873
+ // Calculate bounding box volume for scaling.
2874
+ boxSize = new Vector3();
2875
+ boundingBox.getSize(boxSize);
2876
+ boxVolume = boxSize.x * boxSize.y * boxSize.z;
2877
+ if (boxVolume === 0) {
2878
+ return 0;
2879
+ }
2880
+ insideCount = 0;
2881
+ // Test random points inside bounding box.
2882
+ for (i = k = 0, ref = sampleCount; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
2883
+ // Generate random point in bounding box.
2884
+ samplePoint = new Vector3(boundingBox.min.x + Math.random() * boxSize.x, boundingBox.min.y + Math.random() * boxSize.y, boundingBox.min.z + Math.random() * boxSize.z);
2885
+ // Test if point is inside the mesh using ray casting.
2886
+ testRay = new Ray(samplePoint, new Vector3(1, 0, 0));
2887
+ // Create a simple identity matrix manually to avoid Three.js import issues.
2888
+ identityMatrix = {
2889
+ elements: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
2890
+ isMatrix4: true
2891
+ };
2892
+ intersections = polytree.rayIntersect(testRay, identityMatrix);
2893
+ if (intersections.length % 2 === 1) {
2894
+ // Point is inside if odd number of intersections.
2895
+ insideCount++;
2896
+ }
2897
+ }
2898
+ // Calculate volume ratio and scale by bounding box volume.
2899
+ volumeRatio = insideCount / sampleCount;
2900
+ // Clean up temporary polytree if created.
2901
+ shouldCleanup && polytree.delete();
2902
+ return volumeRatio * boxVolume;
2903
+ };
2393
2904
  // Generated by CoffeeScript 2.7.0
2394
2905
  // ============================================================================
2395
2906
  // Unite CSG Operation for Polytree
@@ -3671,9 +4182,6 @@ Notes:
3671
4182
  var constructIntersection, intersectionTestEdge2D, intersectionTestVertex2D, isUniqueTriangle, isValidTriangle, pointInTriangleInclusive2D, pointOnSegmentInclusive2D, pointsEqual2D, resolveCoplanarTriangleIntersection, resolveTriangleIntersection, triangleIntersectionCCW2D, triangleIntersectsTriangle, triangleOrientation2D, trianglesOverlap2D;
3672
4183
 
3673
4184
  isValidTriangle = function(triangle) {
3674
- if (DEBUG_GEOMETRY_VALIDATION) {
3675
- console.log("Validating triangle:", triangle.a, triangle.b, triangle.c);
3676
- }
3677
4185
  if (triangle.a.equals(triangle.b)) {
3678
4186
  return false;
3679
4187
  }
@@ -3683,9 +4191,6 @@ isValidTriangle = function(triangle) {
3683
4191
  if (triangle.b.equals(triangle.c)) {
3684
4192
  return false;
3685
4193
  }
3686
- if (DEBUG_GEOMETRY_VALIDATION) {
3687
- console.log("Triangle validation passed.");
3688
- }
3689
4194
  return true;
3690
4195
  };
3691
4196
 
@@ -4412,3 +4917,7 @@ module.exports.intersectionTestVertex2D = intersectionTestVertex2D;
4412
4917
  // === INTERSECTION CONSTRUCTION ===
4413
4918
  // Low-level intersection construction utilities for building intersection results.
4414
4919
  module.exports.constructIntersection = constructIntersection;
4920
+
4921
+ // === VOLUME CALCULATION UTILITIES ===
4922
+ // Helper functions for calculating volumes of 3D geometries.
4923
+ module.exports.signedVolumeOfTriangle = signedVolumeOfTriangle;