@jgphilpott/polytree 0.0.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -56
- package/package.json +1 -1
- package/polytree.bundle.browser.js +507 -44
- package/polytree.bundle.browser.min.js +1 -1
- package/polytree.bundle.js +553 -44
- package/polytree.bundle.min.js +1 -1
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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 };
|