"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const THREE = require("three"); const Visible = 0; const Deleted = 1; const _v1 = /* @__PURE__ */ new THREE.Vector3(); const _line3 = /* @__PURE__ */ new THREE.Line3(); const _plane = /* @__PURE__ */ new THREE.Plane(); const _closestPoint = /* @__PURE__ */ new THREE.Vector3(); const _triangle = /* @__PURE__ */ new THREE.Triangle(); class ConvexHull { constructor() { this.tolerance = -1; this.faces = []; this.newFaces = []; this.assigned = new VertexList(); this.unassigned = new VertexList(); this.vertices = []; } setFromPoints(points) { if (points.length >= 4) { this.makeEmpty(); for (let i = 0, l = points.length; i < l; i++) { this.vertices.push(new VertexNode(points[i])); } this.compute(); } return this; } setFromObject(object) { const points = []; object.updateMatrixWorld(true); object.traverse(function(node) { const geometry = node.geometry; if (geometry !== void 0) { const attribute = geometry.attributes.position; if (attribute !== void 0) { for (let i = 0, l = attribute.count; i < l; i++) { const point = new THREE.Vector3(); point.fromBufferAttribute(attribute, i).applyMatrix4(node.matrixWorld); points.push(point); } } } }); return this.setFromPoints(points); } containsPoint(point) { const faces = this.faces; for (let i = 0, l = faces.length; i < l; i++) { const face = faces[i]; if (face.distanceToPoint(point) > this.tolerance) return false; } return true; } intersectRay(ray, target) { const faces = this.faces; let tNear = -Infinity; let tFar = Infinity; for (let i = 0, l = faces.length; i < l; i++) { const face = faces[i]; const vN = face.distanceToPoint(ray.origin); const vD = face.normal.dot(ray.direction); if (vN > 0 && vD >= 0) return null; const t = vD !== 0 ? -vN / vD : 0; if (t <= 0) continue; if (vD > 0) { tFar = Math.min(t, tFar); } else { tNear = Math.max(t, tNear); } if (tNear > tFar) { return null; } } if (tNear !== -Infinity) { ray.at(tNear, target); } else { ray.at(tFar, target); } return target; } intersectsRay(ray) { return this.intersectRay(ray, _v1) !== null; } makeEmpty() { this.faces = []; this.vertices = []; return this; } // Adds a vertex to the 'assigned' list of vertices and assigns it to the given face addVertexToFace(vertex, face) { vertex.face = face; if (face.outside === null) { this.assigned.append(vertex); } else { this.assigned.insertBefore(face.outside, vertex); } face.outside = vertex; return this; } // Removes a vertex from the 'assigned' list of vertices and from the given face removeVertexFromFace(vertex, face) { if (vertex === face.outside) { if (vertex.next !== null && vertex.next.face === face) { face.outside = vertex.next; } else { face.outside = null; } } this.assigned.remove(vertex); return this; } // Removes all the visible vertices that a given face is able to see which are stored in the 'assigned' vertex list removeAllVerticesFromFace(face) { if (face.outside !== null) { const start = face.outside; let end = face.outside; while (end.next !== null && end.next.face === face) { end = end.next; } this.assigned.removeSubList(start, end); start.prev = end.next = null; face.outside = null; return start; } } // Removes all the visible vertices that 'face' is able to see deleteFaceVertices(face, absorbingFace) { const faceVertices = this.removeAllVerticesFromFace(face); if (faceVertices !== void 0) { if (absorbingFace === void 0) { this.unassigned.appendChain(faceVertices); } else { let vertex = faceVertices; do { const nextVertex = vertex.next; const distance = absorbingFace.distanceToPoint(vertex.point); if (distance > this.tolerance) { this.addVertexToFace(vertex, absorbingFace); } else { this.unassigned.append(vertex); } vertex = nextVertex; } while (vertex !== null); } } return this; } // Reassigns as many vertices as possible from the unassigned list to the new faces resolveUnassignedPoints(newFaces) { if (this.unassigned.isEmpty() === false) { let vertex = this.unassigned.first(); do { const nextVertex = vertex.next; let maxDistance = this.tolerance; let maxFace = null; for (let i = 0; i < newFaces.length; i++) { const face = newFaces[i]; if (face.mark === Visible) { const distance = face.distanceToPoint(vertex.point); if (distance > maxDistance) { maxDistance = distance; maxFace = face; } if (maxDistance > 1e3 * this.tolerance) break; } } if (maxFace !== null) { this.addVertexToFace(vertex, maxFace); } vertex = nextVertex; } while (vertex !== null); } return this; } // Computes the extremes of a simplex which will be the initial hull computeExtremes() { const min = new THREE.Vector3(); const max = new THREE.Vector3(); const minVertices = []; const maxVertices = []; for (let i = 0; i < 3; i++) { minVertices[i] = maxVertices[i] = this.vertices[0]; } min.copy(this.vertices[0].point); max.copy(this.vertices[0].point); for (let i = 0, l = this.vertices.length; i < l; i++) { const vertex = this.vertices[i]; const point = vertex.point; for (let j = 0; j < 3; j++) { if (point.getComponent(j) < min.getComponent(j)) { min.setComponent(j, point.getComponent(j)); minVertices[j] = vertex; } } for (let j = 0; j < 3; j++) { if (point.getComponent(j) > max.getComponent(j)) { max.setComponent(j, point.getComponent(j)); maxVertices[j] = vertex; } } } this.tolerance = 3 * Number.EPSILON * (Math.max(Math.abs(min.x), Math.abs(max.x)) + Math.max(Math.abs(min.y), Math.abs(max.y)) + Math.max(Math.abs(min.z), Math.abs(max.z))); return { min: minVertices, max: maxVertices }; } // Computes the initial simplex assigning to its faces all the points // that are candidates to form part of the hull computeInitialHull() { const vertices = this.vertices; const extremes = this.computeExtremes(); const min = extremes.min; const max = extremes.max; let maxDistance = 0; let index = 0; for (let i = 0; i < 3; i++) { const distance = max[i].point.getComponent(i) - min[i].point.getComponent(i); if (distance > maxDistance) { maxDistance = distance; index = i; } } const v0 = min[index]; const v1 = max[index]; let v2; let v3; maxDistance = 0; _line3.set(v0.point, v1.point); for (let i = 0, l = this.vertices.length; i < l; i++) { const vertex = vertices[i]; if (vertex !== v0 && vertex !== v1) { _line3.closestPointToPoint(vertex.point, true, _closestPoint); const distance = _closestPoint.distanceToSquared(vertex.point); if (distance > maxDistance) { maxDistance = distance; v2 = vertex; } } } maxDistance = -1; _plane.setFromCoplanarPoints(v0.point, v1.point, v2.point); for (let i = 0, l = this.vertices.length; i < l; i++) { const vertex = vertices[i]; if (vertex !== v0 && vertex !== v1 && vertex !== v2) { const distance = Math.abs(_plane.distanceToPoint(vertex.point)); if (distance > maxDistance) { maxDistance = distance; v3 = vertex; } } } const faces = []; if (_plane.distanceToPoint(v3.point) < 0) { faces.push(Face.create(v0, v1, v2), Face.create(v3, v1, v0), Face.create(v3, v2, v1), Face.create(v3, v0, v2)); for (let i = 0; i < 3; i++) { const j = (i + 1) % 3; faces[i + 1].getEdge(2).setTwin(faces[0].getEdge(j)); faces[i + 1].getEdge(1).setTwin(faces[j + 1].getEdge(0)); } } else { faces.push(Face.create(v0, v2, v1), Face.create(v3, v0, v1), Face.create(v3, v1, v2), Face.create(v3, v2, v0)); for (let i = 0; i < 3; i++) { const j = (i + 1) % 3; faces[i + 1].getEdge(2).setTwin(faces[0].getEdge((3 - i) % 3)); faces[i + 1].getEdge(0).setTwin(faces[j + 1].getEdge(1)); } } for (let i = 0; i < 4; i++) { this.faces.push(faces[i]); } for (let i = 0, l = vertices.length; i < l; i++) { const vertex = vertices[i]; if (vertex !== v0 && vertex !== v1 && vertex !== v2 && vertex !== v3) { maxDistance = this.tolerance; let maxFace = null; for (let j = 0; j < 4; j++) { const distance = this.faces[j].distanceToPoint(vertex.point); if (distance > maxDistance) { maxDistance = distance; maxFace = this.faces[j]; } } if (maxFace !== null) { this.addVertexToFace(vertex, maxFace); } } } return this; } // Removes inactive faces reindexFaces() { const activeFaces = []; for (let i = 0; i < this.faces.length; i++) { const face = this.faces[i]; if (face.mark === Visible) { activeFaces.push(face); } } this.faces = activeFaces; return this; } // Finds the next vertex to create faces with the current hull nextVertexToAdd() { if (this.assigned.isEmpty() === false) { let eyeVertex, maxDistance = 0; const eyeFace = this.assigned.first().face; let vertex = eyeFace.outside; do { const distance = eyeFace.distanceToPoint(vertex.point); if (distance > maxDistance) { maxDistance = distance; eyeVertex = vertex; } vertex = vertex.next; } while (vertex !== null && vertex.face === eyeFace); return eyeVertex; } } // Computes a chain of half edges in CCW order called the 'horizon'. // For an edge to be part of the horizon it must join a face that can see // 'eyePoint' and a face that cannot see 'eyePoint'. computeHorizon(eyePoint, crossEdge, face, horizon) { this.deleteFaceVertices(face); face.mark = Deleted; let edge; if (crossEdge === null) { edge = crossEdge = face.getEdge(0); } else { edge = crossEdge.next; } do { const twinEdge = edge.twin; const oppositeFace = twinEdge.face; if (oppositeFace.mark === Visible) { if (oppositeFace.distanceToPoint(eyePoint) > this.tolerance) { this.computeHorizon(eyePoint, twinEdge, oppositeFace, horizon); } else { horizon.push(edge); } } edge = edge.next; } while (edge !== crossEdge); return this; } // Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head' in CCW order addAdjoiningFace(eyeVertex, horizonEdge) { const face = Face.create(eyeVertex, horizonEdge.tail(), horizonEdge.head()); this.faces.push(face); face.getEdge(-1).setTwin(horizonEdge.twin); return face.getEdge(0); } // Adds 'horizon.length' faces to the hull, each face will be linked with the // horizon opposite face and the face on the left/right addNewFaces(eyeVertex, horizon) { this.newFaces = []; let firstSideEdge = null; let previousSideEdge = null; for (let i = 0; i < horizon.length; i++) { const horizonEdge = horizon[i]; const sideEdge = this.addAdjoiningFace(eyeVertex, horizonEdge); if (firstSideEdge === null) { firstSideEdge = sideEdge; } else { sideEdge.next.setTwin(previousSideEdge); } this.newFaces.push(sideEdge.face); previousSideEdge = sideEdge; } firstSideEdge.next.setTwin(previousSideEdge); return this; } // Adds a vertex to the hull addVertexToHull(eyeVertex) { const horizon = []; this.unassigned.clear(); this.removeVertexFromFace(eyeVertex, eyeVertex.face); this.computeHorizon(eyeVertex.point, null, eyeVertex.face, horizon); this.addNewFaces(eyeVertex, horizon); this.resolveUnassignedPoints(this.newFaces); return this; } cleanup() { this.assigned.clear(); this.unassigned.clear(); this.newFaces = []; return this; } compute() { let vertex; this.computeInitialHull(); while ((vertex = this.nextVertexToAdd()) !== void 0) { this.addVertexToHull(vertex); } this.reindexFaces(); this.cleanup(); return this; } } const Face = /* @__PURE__ */ (() => { class Face2 { constructor() { this.normal = new THREE.Vector3(); this.midpoint = new THREE.Vector3(); this.area = 0; this.constant = 0; this.outside = null; this.mark = Visible; this.edge = null; } static create(a, b, c) { const face = new Face2(); const e0 = new HalfEdge(a, face); const e1 = new HalfEdge(b, face); const e2 = new HalfEdge(c, face); e0.next = e2.prev = e1; e1.next = e0.prev = e2; e2.next = e1.prev = e0; face.edge = e0; return face.compute(); } getEdge(i) { let edge = this.edge; while (i > 0) { edge = edge.next; i--; } while (i < 0) { edge = edge.prev; i++; } return edge; } compute() { const a = this.edge.tail(); const b = this.edge.head(); const c = this.edge.next.head(); _triangle.set(a.point, b.point, c.point); _triangle.getNormal(this.normal); _triangle.getMidpoint(this.midpoint); this.area = _triangle.getArea(); this.constant = this.normal.dot(this.midpoint); return this; } distanceToPoint(point) { return this.normal.dot(point) - this.constant; } } return Face2; })(); class HalfEdge { constructor(vertex, face) { this.vertex = vertex; this.prev = null; this.next = null; this.twin = null; this.face = face; } head() { return this.vertex; } tail() { return this.prev ? this.prev.vertex : null; } length() { const head = this.head(); const tail = this.tail(); if (tail !== null) { return tail.point.distanceTo(head.point); } return -1; } lengthSquared() { const head = this.head(); const tail = this.tail(); if (tail !== null) { return tail.point.distanceToSquared(head.point); } return -1; } setTwin(edge) { this.twin = edge; edge.twin = this; return this; } } class VertexNode { constructor(point) { this.point = point; this.prev = null; this.next = null; this.face = null; } } class VertexList { constructor() { this.head = null; this.tail = null; } first() { return this.head; } last() { return this.tail; } clear() { this.head = this.tail = null; return this; } // Inserts a vertex before the target vertex insertBefore(target, vertex) { vertex.prev = target.prev; vertex.next = target; if (vertex.prev === null) { this.head = vertex; } else { vertex.prev.next = vertex; } target.prev = vertex; return this; } // Inserts a vertex after the target vertex insertAfter(target, vertex) { vertex.prev = target; vertex.next = target.next; if (vertex.next === null) { this.tail = vertex; } else { vertex.next.prev = vertex; } target.next = vertex; return this; } // Appends a vertex to the end of the linked list append(vertex) { if (this.head === null) { this.head = vertex; } else { this.tail.next = vertex; } vertex.prev = this.tail; vertex.next = null; this.tail = vertex; return this; } // Appends a chain of vertices where 'vertex' is the head. appendChain(vertex) { if (this.head === null) { this.head = vertex; } else { this.tail.next = vertex; } vertex.prev = this.tail; while (vertex.next !== null) { vertex = vertex.next; } this.tail = vertex; return this; } // Removes a vertex from the linked list remove(vertex) { if (vertex.prev === null) { this.head = vertex.next; } else { vertex.prev.next = vertex.next; } if (vertex.next === null) { this.tail = vertex.prev; } else { vertex.next.prev = vertex.prev; } return this; } // Removes a list of vertices whose 'head' is 'a' and whose 'tail' is b removeSubList(a, b) { if (a.prev === null) { this.head = b.next; } else { a.prev.next = b.next; } if (b.next === null) { this.tail = a.prev; } else { b.next.prev = a.prev; } return this; } isEmpty() { return this.head === null; } } exports.ConvexHull = ConvexHull; exports.Face = Face; exports.HalfEdge = HalfEdge; exports.VertexList = VertexList; exports.VertexNode = VertexNode; //# sourceMappingURL=ConvexHull.cjs.map