three-stdlib
Version:
stand-alone library of threejs examples
601 lines (600 loc) • 17.5 kB
JavaScript
"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