cannon-es
Version:
A lightweight 3D physics engine written in JavaScript.
2,317 lines (1,882 loc) • 346 kB
JavaScript
/**
* Records what objects are colliding with each other
*/
class ObjectCollisionMatrix {
/**
* The matrix storage.
*/
/**
* @todo Remove useless constructor
*/
constructor() {
this.matrix = {};
}
/**
* get
*/
get(bi, bj) {
let {
id: i
} = bi;
let {
id: j
} = bj;
if (j > i) {
const temp = j;
j = i;
i = temp;
}
return `${i}-${j}` in this.matrix;
}
/**
* set
*/
set(bi, bj, value) {
let {
id: i
} = bi;
let {
id: j
} = bj;
if (j > i) {
const temp = j;
j = i;
i = temp;
}
if (value) {
this.matrix[`${i}-${j}`] = true;
} else {
delete this.matrix[`${i}-${j}`];
}
}
/**
* Empty the matrix
*/
reset() {
this.matrix = {};
}
/**
* Set max number of objects
*/
setNumObjects(n) {}
}
/**
* A 3x3 matrix.
* Authored by {@link http://github.com/schteppe/ schteppe}
*/
class Mat3 {
/**
* A vector of length 9, containing all matrix elements.
*/
/**
* @param elements A vector of length 9, containing all matrix elements.
*/
constructor(elements) {
if (elements === void 0) {
elements = [0, 0, 0, 0, 0, 0, 0, 0, 0];
}
this.elements = elements;
}
/**
* Sets the matrix to identity
* @todo Should perhaps be renamed to `setIdentity()` to be more clear.
* @todo Create another function that immediately creates an identity matrix eg. `eye()`
*/
identity() {
const e = this.elements;
e[0] = 1;
e[1] = 0;
e[2] = 0;
e[3] = 0;
e[4] = 1;
e[5] = 0;
e[6] = 0;
e[7] = 0;
e[8] = 1;
}
/**
* Set all elements to zero
*/
setZero() {
const e = this.elements;
e[0] = 0;
e[1] = 0;
e[2] = 0;
e[3] = 0;
e[4] = 0;
e[5] = 0;
e[6] = 0;
e[7] = 0;
e[8] = 0;
}
/**
* Sets the matrix diagonal elements from a Vec3
*/
setTrace(vector) {
const e = this.elements;
e[0] = vector.x;
e[4] = vector.y;
e[8] = vector.z;
}
/**
* Gets the matrix diagonal elements
*/
getTrace(target) {
if (target === void 0) {
target = new Vec3();
}
const e = this.elements;
target.x = e[0];
target.y = e[4];
target.z = e[8];
return target;
}
/**
* Matrix-Vector multiplication
* @param v The vector to multiply with
* @param target Optional, target to save the result in.
*/
vmult(v, target) {
if (target === void 0) {
target = new Vec3();
}
const e = this.elements;
const x = v.x;
const y = v.y;
const z = v.z;
target.x = e[0] * x + e[1] * y + e[2] * z;
target.y = e[3] * x + e[4] * y + e[5] * z;
target.z = e[6] * x + e[7] * y + e[8] * z;
return target;
}
/**
* Matrix-scalar multiplication
*/
smult(s) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i] *= s;
}
}
/**
* Matrix multiplication
* @param matrix Matrix to multiply with from left side.
*/
mmult(matrix, target) {
if (target === void 0) {
target = new Mat3();
}
const A = this.elements;
const B = matrix.elements;
const T = target.elements;
const a11 = A[0],
a12 = A[1],
a13 = A[2],
a21 = A[3],
a22 = A[4],
a23 = A[5],
a31 = A[6],
a32 = A[7],
a33 = A[8];
const b11 = B[0],
b12 = B[1],
b13 = B[2],
b21 = B[3],
b22 = B[4],
b23 = B[5],
b31 = B[6],
b32 = B[7],
b33 = B[8];
T[0] = a11 * b11 + a12 * b21 + a13 * b31;
T[1] = a11 * b12 + a12 * b22 + a13 * b32;
T[2] = a11 * b13 + a12 * b23 + a13 * b33;
T[3] = a21 * b11 + a22 * b21 + a23 * b31;
T[4] = a21 * b12 + a22 * b22 + a23 * b32;
T[5] = a21 * b13 + a22 * b23 + a23 * b33;
T[6] = a31 * b11 + a32 * b21 + a33 * b31;
T[7] = a31 * b12 + a32 * b22 + a33 * b32;
T[8] = a31 * b13 + a32 * b23 + a33 * b33;
return target;
}
/**
* Scale each column of the matrix
*/
scale(vector, target) {
if (target === void 0) {
target = new Mat3();
}
const e = this.elements;
const t = target.elements;
for (let i = 0; i !== 3; i++) {
t[3 * i + 0] = vector.x * e[3 * i + 0];
t[3 * i + 1] = vector.y * e[3 * i + 1];
t[3 * i + 2] = vector.z * e[3 * i + 2];
}
return target;
}
/**
* Solve Ax=b
* @param b The right hand side
* @param target Optional. Target vector to save in.
* @return The solution x
* @todo should reuse arrays
*/
solve(b, target) {
if (target === void 0) {
target = new Vec3();
}
// Construct equations
const nr = 3; // num rows
const nc = 4; // num cols
const eqns = [];
let i;
let j;
for (i = 0; i < nr * nc; i++) {
eqns.push(0);
}
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
eqns[i + nc * j] = this.elements[i + 3 * j];
}
}
eqns[3 + 4 * 0] = b.x;
eqns[3 + 4 * 1] = b.y;
eqns[3 + 4 * 2] = b.z; // Compute right upper triangular version of the matrix - Gauss elimination
let n = 3;
const k = n;
let np;
const kp = 4; // num rows
let p;
do {
i = k - n;
if (eqns[i + nc * i] === 0) {
// the pivot is null, swap lines
for (j = i + 1; j < k; j++) {
if (eqns[i + nc * j] !== 0) {
np = kp;
do {
// do ligne( i ) = ligne( i ) + ligne( k )
p = kp - np;
eqns[p + nc * i] += eqns[p + nc * j];
} while (--np);
break;
}
}
}
if (eqns[i + nc * i] !== 0) {
for (j = i + 1; j < k; j++) {
const multiplier = eqns[i + nc * j] / eqns[i + nc * i];
np = kp;
do {
// do ligne( k ) = ligne( k ) - multiplier * ligne( i )
p = kp - np;
eqns[p + nc * j] = p <= i ? 0 : eqns[p + nc * j] - eqns[p + nc * i] * multiplier;
} while (--np);
}
}
} while (--n); // Get the solution
target.z = eqns[2 * nc + 3] / eqns[2 * nc + 2];
target.y = (eqns[1 * nc + 3] - eqns[1 * nc + 2] * target.z) / eqns[1 * nc + 1];
target.x = (eqns[0 * nc + 3] - eqns[0 * nc + 2] * target.z - eqns[0 * nc + 1] * target.y) / eqns[0 * nc + 0];
if (isNaN(target.x) || isNaN(target.y) || isNaN(target.z) || target.x === Infinity || target.y === Infinity || target.z === Infinity) {
throw `Could not solve equation! Got x=[${target.toString()}], b=[${b.toString()}], A=[${this.toString()}]`;
}
return target;
}
/**
* Get an element in the matrix by index. Index starts at 0, not 1!!!
* @param value If provided, the matrix element will be set to this value.
*/
e(row, column, value) {
if (value === undefined) {
return this.elements[column + 3 * row];
} else {
// Set value
this.elements[column + 3 * row] = value;
}
}
/**
* Copy another matrix into this matrix object.
*/
copy(matrix) {
for (let i = 0; i < matrix.elements.length; i++) {
this.elements[i] = matrix.elements[i];
}
return this;
}
/**
* Returns a string representation of the matrix.
*/
toString() {
let r = '';
const sep = ',';
for (let i = 0; i < 9; i++) {
r += this.elements[i] + sep;
}
return r;
}
/**
* reverse the matrix
* @param target Target matrix to save in.
* @return The solution x
*/
reverse(target) {
if (target === void 0) {
target = new Mat3();
}
// Construct equations
const nr = 3; // num rows
const nc = 6; // num cols
const eqns = reverse_eqns;
let i;
let j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
eqns[i + nc * j] = this.elements[i + 3 * j];
}
}
eqns[3 + 6 * 0] = 1;
eqns[3 + 6 * 1] = 0;
eqns[3 + 6 * 2] = 0;
eqns[4 + 6 * 0] = 0;
eqns[4 + 6 * 1] = 1;
eqns[4 + 6 * 2] = 0;
eqns[5 + 6 * 0] = 0;
eqns[5 + 6 * 1] = 0;
eqns[5 + 6 * 2] = 1; // Compute right upper triangular version of the matrix - Gauss elimination
let n = 3;
const k = n;
let np;
const kp = nc; // num rows
let p;
do {
i = k - n;
if (eqns[i + nc * i] === 0) {
// the pivot is null, swap lines
for (j = i + 1; j < k; j++) {
if (eqns[i + nc * j] !== 0) {
np = kp;
do {
// do line( i ) = line( i ) + line( k )
p = kp - np;
eqns[p + nc * i] += eqns[p + nc * j];
} while (--np);
break;
}
}
}
if (eqns[i + nc * i] !== 0) {
for (j = i + 1; j < k; j++) {
const multiplier = eqns[i + nc * j] / eqns[i + nc * i];
np = kp;
do {
// do line( k ) = line( k ) - multiplier * line( i )
p = kp - np;
eqns[p + nc * j] = p <= i ? 0 : eqns[p + nc * j] - eqns[p + nc * i] * multiplier;
} while (--np);
}
}
} while (--n); // eliminate the upper left triangle of the matrix
i = 2;
do {
j = i - 1;
do {
const multiplier = eqns[i + nc * j] / eqns[i + nc * i];
np = nc;
do {
p = nc - np;
eqns[p + nc * j] = eqns[p + nc * j] - eqns[p + nc * i] * multiplier;
} while (--np);
} while (j--);
} while (--i); // operations on the diagonal
i = 2;
do {
const multiplier = 1 / eqns[i + nc * i];
np = nc;
do {
p = nc - np;
eqns[p + nc * i] = eqns[p + nc * i] * multiplier;
} while (--np);
} while (i--);
i = 2;
do {
j = 2;
do {
p = eqns[nr + j + nc * i];
if (isNaN(p) || p === Infinity) {
throw `Could not reverse! A=[${this.toString()}]`;
}
target.e(i, j, p);
} while (j--);
} while (i--);
return target;
}
/**
* Set the matrix from a quaterion
*/
setRotationFromQuaternion(q) {
const x = q.x;
const y = q.y;
const z = q.z;
const w = q.w;
const x2 = x + x;
const y2 = y + y;
const z2 = z + z;
const xx = x * x2;
const xy = x * y2;
const xz = x * z2;
const yy = y * y2;
const yz = y * z2;
const zz = z * z2;
const wx = w * x2;
const wy = w * y2;
const wz = w * z2;
const e = this.elements;
e[3 * 0 + 0] = 1 - (yy + zz);
e[3 * 0 + 1] = xy - wz;
e[3 * 0 + 2] = xz + wy;
e[3 * 1 + 0] = xy + wz;
e[3 * 1 + 1] = 1 - (xx + zz);
e[3 * 1 + 2] = yz - wx;
e[3 * 2 + 0] = xz - wy;
e[3 * 2 + 1] = yz + wx;
e[3 * 2 + 2] = 1 - (xx + yy);
return this;
}
/**
* Transpose the matrix
* @param target Optional. Where to store the result.
* @return The target Mat3, or a new Mat3 if target was omitted.
*/
transpose(target) {
if (target === void 0) {
target = new Mat3();
}
const M = this.elements;
const T = target.elements;
let tmp; //Set diagonals
T[0] = M[0];
T[4] = M[4];
T[8] = M[8];
tmp = M[1];
T[1] = M[3];
T[3] = tmp;
tmp = M[2];
T[2] = M[6];
T[6] = tmp;
tmp = M[5];
T[5] = M[7];
T[7] = tmp;
return target;
}
}
const reverse_eqns = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
/**
* 3-dimensional vector
* @example
* const v = new Vec3(1, 2, 3)
* console.log('x=' + v.x) // x=1
*/
class Vec3 {
constructor(x, y, z) {
if (x === void 0) {
x = 0.0;
}
if (y === void 0) {
y = 0.0;
}
if (z === void 0) {
z = 0.0;
}
this.x = x;
this.y = y;
this.z = z;
}
/**
* Vector cross product
* @param target Optional target to save in.
*/
cross(vector, target) {
if (target === void 0) {
target = new Vec3();
}
const vx = vector.x;
const vy = vector.y;
const vz = vector.z;
const x = this.x;
const y = this.y;
const z = this.z;
target.x = y * vz - z * vy;
target.y = z * vx - x * vz;
target.z = x * vy - y * vx;
return target;
}
/**
* Set the vectors' 3 elements
*/
set(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* Set all components of the vector to zero.
*/
setZero() {
this.x = this.y = this.z = 0;
}
/**
* Vector addition
*/
vadd(vector, target) {
if (target) {
target.x = vector.x + this.x;
target.y = vector.y + this.y;
target.z = vector.z + this.z;
} else {
return new Vec3(this.x + vector.x, this.y + vector.y, this.z + vector.z);
}
}
/**
* Vector subtraction
* @param target Optional target to save in.
*/
vsub(vector, target) {
if (target) {
target.x = this.x - vector.x;
target.y = this.y - vector.y;
target.z = this.z - vector.z;
} else {
return new Vec3(this.x - vector.x, this.y - vector.y, this.z - vector.z);
}
}
/**
* Get the cross product matrix a_cross from a vector, such that a x b = a_cross * b = c
*
* See {@link https://www8.cs.umu.se/kurser/TDBD24/VT06/lectures/Lecture6.pdf Umeå University Lecture}
*/
crossmat() {
return new Mat3([0, -this.z, this.y, this.z, 0, -this.x, -this.y, this.x, 0]);
}
/**
* Normalize the vector. Note that this changes the values in the vector.
* @return Returns the norm of the vector
*/
normalize() {
const x = this.x;
const y = this.y;
const z = this.z;
const n = Math.sqrt(x * x + y * y + z * z);
if (n > 0.0) {
const invN = 1 / n;
this.x *= invN;
this.y *= invN;
this.z *= invN;
} else {
// Make something up
this.x = 0;
this.y = 0;
this.z = 0;
}
return n;
}
/**
* Get the version of this vector that is of length 1.
* @param target Optional target to save in
* @return Returns the unit vector
*/
unit(target) {
if (target === void 0) {
target = new Vec3();
}
const x = this.x;
const y = this.y;
const z = this.z;
let ninv = Math.sqrt(x * x + y * y + z * z);
if (ninv > 0.0) {
ninv = 1.0 / ninv;
target.x = x * ninv;
target.y = y * ninv;
target.z = z * ninv;
} else {
target.x = 1;
target.y = 0;
target.z = 0;
}
return target;
}
/**
* Get the length of the vector
*/
length() {
const x = this.x;
const y = this.y;
const z = this.z;
return Math.sqrt(x * x + y * y + z * z);
}
/**
* Get the squared length of the vector.
*/
lengthSquared() {
return this.dot(this);
}
/**
* Get distance from this point to another point
*/
distanceTo(p) {
const x = this.x;
const y = this.y;
const z = this.z;
const px = p.x;
const py = p.y;
const pz = p.z;
return Math.sqrt((px - x) * (px - x) + (py - y) * (py - y) + (pz - z) * (pz - z));
}
/**
* Get squared distance from this point to another point
*/
distanceSquared(p) {
const x = this.x;
const y = this.y;
const z = this.z;
const px = p.x;
const py = p.y;
const pz = p.z;
return (px - x) * (px - x) + (py - y) * (py - y) + (pz - z) * (pz - z);
}
/**
* Multiply all the components of the vector with a scalar.
* @param target The vector to save the result in.
*/
scale(scalar, target) {
if (target === void 0) {
target = new Vec3();
}
const x = this.x;
const y = this.y;
const z = this.z;
target.x = scalar * x;
target.y = scalar * y;
target.z = scalar * z;
return target;
}
/**
* Multiply the vector with an other vector, component-wise.
* @param target The vector to save the result in.
*/
vmul(vector, target) {
if (target === void 0) {
target = new Vec3();
}
target.x = vector.x * this.x;
target.y = vector.y * this.y;
target.z = vector.z * this.z;
return target;
}
/**
* Scale a vector and add it to this vector. Save the result in "target". (target = this + vector * scalar)
* @param target The vector to save the result in.
*/
addScaledVector(scalar, vector, target) {
if (target === void 0) {
target = new Vec3();
}
target.x = this.x + scalar * vector.x;
target.y = this.y + scalar * vector.y;
target.z = this.z + scalar * vector.z;
return target;
}
/**
* Calculate dot product
* @param vector
*/
dot(vector) {
return this.x * vector.x + this.y * vector.y + this.z * vector.z;
}
isZero() {
return this.x === 0 && this.y === 0 && this.z === 0;
}
/**
* Make the vector point in the opposite direction.
* @param target Optional target to save in
*/
negate(target) {
if (target === void 0) {
target = new Vec3();
}
target.x = -this.x;
target.y = -this.y;
target.z = -this.z;
return target;
}
/**
* Compute two artificial tangents to the vector
* @param t1 Vector object to save the first tangent in
* @param t2 Vector object to save the second tangent in
*/
tangents(t1, t2) {
const norm = this.length();
if (norm > 0.0) {
const n = Vec3_tangents_n;
const inorm = 1 / norm;
n.set(this.x * inorm, this.y * inorm, this.z * inorm);
const randVec = Vec3_tangents_randVec;
if (Math.abs(n.x) < 0.9) {
randVec.set(1, 0, 0);
n.cross(randVec, t1);
} else {
randVec.set(0, 1, 0);
n.cross(randVec, t1);
}
n.cross(t1, t2);
} else {
// The normal length is zero, make something up
t1.set(1, 0, 0);
t2.set(0, 1, 0);
}
}
/**
* Converts to a more readable format
*/
toString() {
return `${this.x},${this.y},${this.z}`;
}
/**
* Converts to an array
*/
toArray() {
return [this.x, this.y, this.z];
}
/**
* Copies value of source to this vector.
*/
copy(vector) {
this.x = vector.x;
this.y = vector.y;
this.z = vector.z;
return this;
}
/**
* Do a linear interpolation between two vectors
* @param t A number between 0 and 1. 0 will make this function return u, and 1 will make it return v. Numbers in between will generate a vector in between them.
*/
lerp(vector, t, target) {
const x = this.x;
const y = this.y;
const z = this.z;
target.x = x + (vector.x - x) * t;
target.y = y + (vector.y - y) * t;
target.z = z + (vector.z - z) * t;
}
/**
* Check if a vector equals is almost equal to another one.
*/
almostEquals(vector, precision) {
if (precision === void 0) {
precision = 1e-6;
}
if (Math.abs(this.x - vector.x) > precision || Math.abs(this.y - vector.y) > precision || Math.abs(this.z - vector.z) > precision) {
return false;
}
return true;
}
/**
* Check if a vector is almost zero
*/
almostZero(precision) {
if (precision === void 0) {
precision = 1e-6;
}
if (Math.abs(this.x) > precision || Math.abs(this.y) > precision || Math.abs(this.z) > precision) {
return false;
}
return true;
}
/**
* Check if the vector is anti-parallel to another vector.
* @param precision Set to zero for exact comparisons
*/
isAntiparallelTo(vector, precision) {
this.negate(antip_neg);
return antip_neg.almostEquals(vector, precision);
}
/**
* Clone the vector
*/
clone() {
return new Vec3(this.x, this.y, this.z);
}
}
Vec3.ZERO = new Vec3(0, 0, 0);
Vec3.UNIT_X = new Vec3(1, 0, 0);
Vec3.UNIT_Y = new Vec3(0, 1, 0);
Vec3.UNIT_Z = new Vec3(0, 0, 1);
const Vec3_tangents_n = new Vec3();
const Vec3_tangents_randVec = new Vec3();
const antip_neg = new Vec3();
/**
* Axis aligned bounding box class.
*/
class AABB {
/**
* The lower bound of the bounding box
*/
/**
* The upper bound of the bounding box
*/
constructor(options) {
if (options === void 0) {
options = {};
}
this.lowerBound = new Vec3();
this.upperBound = new Vec3();
if (options.lowerBound) {
this.lowerBound.copy(options.lowerBound);
}
if (options.upperBound) {
this.upperBound.copy(options.upperBound);
}
}
/**
* Set the AABB bounds from a set of points.
* @param points An array of Vec3's.
* @return The self object
*/
setFromPoints(points, position, quaternion, skinSize) {
const l = this.lowerBound;
const u = this.upperBound;
const q = quaternion; // Set to the first point
l.copy(points[0]);
if (q) {
q.vmult(l, l);
}
u.copy(l);
for (let i = 1; i < points.length; i++) {
let p = points[i];
if (q) {
q.vmult(p, tmp$1);
p = tmp$1;
}
if (p.x > u.x) {
u.x = p.x;
}
if (p.x < l.x) {
l.x = p.x;
}
if (p.y > u.y) {
u.y = p.y;
}
if (p.y < l.y) {
l.y = p.y;
}
if (p.z > u.z) {
u.z = p.z;
}
if (p.z < l.z) {
l.z = p.z;
}
} // Add offset
if (position) {
position.vadd(l, l);
position.vadd(u, u);
}
if (skinSize) {
l.x -= skinSize;
l.y -= skinSize;
l.z -= skinSize;
u.x += skinSize;
u.y += skinSize;
u.z += skinSize;
}
return this;
}
/**
* Copy bounds from an AABB to this AABB
* @param aabb Source to copy from
* @return The this object, for chainability
*/
copy(aabb) {
this.lowerBound.copy(aabb.lowerBound);
this.upperBound.copy(aabb.upperBound);
return this;
}
/**
* Clone an AABB
*/
clone() {
return new AABB().copy(this);
}
/**
* Extend this AABB so that it covers the given AABB too.
*/
extend(aabb) {
this.lowerBound.x = Math.min(this.lowerBound.x, aabb.lowerBound.x);
this.upperBound.x = Math.max(this.upperBound.x, aabb.upperBound.x);
this.lowerBound.y = Math.min(this.lowerBound.y, aabb.lowerBound.y);
this.upperBound.y = Math.max(this.upperBound.y, aabb.upperBound.y);
this.lowerBound.z = Math.min(this.lowerBound.z, aabb.lowerBound.z);
this.upperBound.z = Math.max(this.upperBound.z, aabb.upperBound.z);
}
/**
* Returns true if the given AABB overlaps this AABB.
*/
overlaps(aabb) {
const l1 = this.lowerBound;
const u1 = this.upperBound;
const l2 = aabb.lowerBound;
const u2 = aabb.upperBound; // l2 u2
// |---------|
// |--------|
// l1 u1
const overlapsX = l2.x <= u1.x && u1.x <= u2.x || l1.x <= u2.x && u2.x <= u1.x;
const overlapsY = l2.y <= u1.y && u1.y <= u2.y || l1.y <= u2.y && u2.y <= u1.y;
const overlapsZ = l2.z <= u1.z && u1.z <= u2.z || l1.z <= u2.z && u2.z <= u1.z;
return overlapsX && overlapsY && overlapsZ;
} // Mostly for debugging
volume() {
const l = this.lowerBound;
const u = this.upperBound;
return (u.x - l.x) * (u.y - l.y) * (u.z - l.z);
}
/**
* Returns true if the given AABB is fully contained in this AABB.
*/
contains(aabb) {
const l1 = this.lowerBound;
const u1 = this.upperBound;
const l2 = aabb.lowerBound;
const u2 = aabb.upperBound; // l2 u2
// |---------|
// |---------------|
// l1 u1
return l1.x <= l2.x && u1.x >= u2.x && l1.y <= l2.y && u1.y >= u2.y && l1.z <= l2.z && u1.z >= u2.z;
}
getCorners(a, b, c, d, e, f, g, h) {
const l = this.lowerBound;
const u = this.upperBound;
a.copy(l);
b.set(u.x, l.y, l.z);
c.set(u.x, u.y, l.z);
d.set(l.x, u.y, u.z);
e.set(u.x, l.y, u.z);
f.set(l.x, u.y, l.z);
g.set(l.x, l.y, u.z);
h.copy(u);
}
/**
* Get the representation of an AABB in another frame.
* @return The "target" AABB object.
*/
toLocalFrame(frame, target) {
const corners = transformIntoFrame_corners;
const a = corners[0];
const b = corners[1];
const c = corners[2];
const d = corners[3];
const e = corners[4];
const f = corners[5];
const g = corners[6];
const h = corners[7]; // Get corners in current frame
this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame
for (let i = 0; i !== 8; i++) {
const corner = corners[i];
frame.pointToLocal(corner, corner);
}
return target.setFromPoints(corners);
}
/**
* Get the representation of an AABB in the global frame.
* @return The "target" AABB object.
*/
toWorldFrame(frame, target) {
const corners = transformIntoFrame_corners;
const a = corners[0];
const b = corners[1];
const c = corners[2];
const d = corners[3];
const e = corners[4];
const f = corners[5];
const g = corners[6];
const h = corners[7]; // Get corners in current frame
this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame
for (let i = 0; i !== 8; i++) {
const corner = corners[i];
frame.pointToWorld(corner, corner);
}
return target.setFromPoints(corners);
}
/**
* Check if the AABB is hit by a ray.
*/
overlapsRay(ray) {
const {
direction,
from
} = ray; // const t = 0
// ray.direction is unit direction vector of ray
const dirFracX = 1 / direction.x;
const dirFracY = 1 / direction.y;
const dirFracZ = 1 / direction.z; // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
const t1 = (this.lowerBound.x - from.x) * dirFracX;
const t2 = (this.upperBound.x - from.x) * dirFracX;
const t3 = (this.lowerBound.y - from.y) * dirFracY;
const t4 = (this.upperBound.y - from.y) * dirFracY;
const t5 = (this.lowerBound.z - from.z) * dirFracZ;
const t6 = (this.upperBound.z - from.z) * dirFracZ; // const tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)));
// const tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)));
const tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6));
const tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
if (tmax < 0) {
//t = tmax;
return false;
} // if tmin > tmax, ray doesn't intersect AABB
if (tmin > tmax) {
//t = tmax;
return false;
}
return true;
}
}
const tmp$1 = new Vec3();
const transformIntoFrame_corners = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()];
/**
* Collision "matrix".
* It's actually a triangular-shaped array of whether two bodies are touching this step, for reference next step
*/
class ArrayCollisionMatrix {
/**
* The matrix storage.
*/
constructor() {
this.matrix = [];
}
/**
* Get an element
*/
get(bi, bj) {
let {
index: i
} = bi;
let {
index: j
} = bj;
if (j > i) {
const temp = j;
j = i;
i = temp;
}
return this.matrix[(i * (i + 1) >> 1) + j - 1];
}
/**
* Set an element
*/
set(bi, bj, value) {
let {
index: i
} = bi;
let {
index: j
} = bj;
if (j > i) {
const temp = j;
j = i;
i = temp;
}
this.matrix[(i * (i + 1) >> 1) + j - 1] = value ? 1 : 0;
}
/**
* Sets all elements to zero
*/
reset() {
for (let i = 0, l = this.matrix.length; i !== l; i++) {
this.matrix[i] = 0;
}
}
/**
* Sets the max number of objects
*/
setNumObjects(n) {
this.matrix.length = n * (n - 1) >> 1;
}
}
/**
* Base class for objects that dispatches events.
*/
class EventTarget {
/**
* Add an event listener
* @return The self object, for chainability.
*/
addEventListener(type, listener) {
if (this._listeners === undefined) {
this._listeners = {};
}
const listeners = this._listeners;
if (listeners[type] === undefined) {
listeners[type] = [];
}
if (!listeners[type].includes(listener)) {
listeners[type].push(listener);
}
return this;
}
/**
* Check if an event listener is added
*/
hasEventListener(type, listener) {
if (this._listeners === undefined) {
return false;
}
const listeners = this._listeners;
if (listeners[type] !== undefined && listeners[type].includes(listener)) {
return true;
}
return false;
}
/**
* Check if any event listener of the given type is added
*/
hasAnyEventListener(type) {
if (this._listeners === undefined) {
return false;
}
const listeners = this._listeners;
return listeners[type] !== undefined;
}
/**
* Remove an event listener
* @return The self object, for chainability.
*/
removeEventListener(type, listener) {
if (this._listeners === undefined) {
return this;
}
const listeners = this._listeners;
if (listeners[type] === undefined) {
return this;
}
const index = listeners[type].indexOf(listener);
if (index !== -1) {
listeners[type].splice(index, 1);
}
return this;
}
/**
* Emit an event.
* @return The self object, for chainability.
*/
dispatchEvent(event) {
if (this._listeners === undefined) {
return this;
}
const listeners = this._listeners;
const listenerArray = listeners[event.type];
if (listenerArray !== undefined) {
event.target = this;
for (let i = 0, l = listenerArray.length; i < l; i++) {
listenerArray[i].call(this, event);
}
}
return this;
}
}
/**
* A Quaternion describes a rotation in 3D space. The Quaternion is mathematically defined as Q = x*i + y*j + z*k + w, where (i,j,k) are imaginary basis vectors. (x,y,z) can be seen as a vector related to the axis of rotation, while the real multiplier, w, is related to the amount of rotation.
* @param x Multiplier of the imaginary basis vector i.
* @param y Multiplier of the imaginary basis vector j.
* @param z Multiplier of the imaginary basis vector k.
* @param w Multiplier of the real part.
* @see http://en.wikipedia.org/wiki/Quaternion
*/
class Quaternion {
constructor(x, y, z, w) {
if (x === void 0) {
x = 0;
}
if (y === void 0) {
y = 0;
}
if (z === void 0) {
z = 0;
}
if (w === void 0) {
w = 1;
}
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
/**
* Set the value of the quaternion.
*/
set(x, y, z, w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return this;
}
/**
* Convert to a readable format
* @return "x,y,z,w"
*/
toString() {
return `${this.x},${this.y},${this.z},${this.w}`;
}
/**
* Convert to an Array
* @return [x, y, z, w]
*/
toArray() {
return [this.x, this.y, this.z, this.w];
}
/**
* Set the quaternion components given an axis and an angle in radians.
*/
setFromAxisAngle(vector, angle) {
const s = Math.sin(angle * 0.5);
this.x = vector.x * s;
this.y = vector.y * s;
this.z = vector.z * s;
this.w = Math.cos(angle * 0.5);
return this;
}
/**
* Converts the quaternion to [ axis, angle ] representation.
* @param targetAxis A vector object to reuse for storing the axis.
* @return An array, first element is the axis and the second is the angle in radians.
*/
toAxisAngle(targetAxis) {
if (targetAxis === void 0) {
targetAxis = new Vec3();
}
this.normalize(); // if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised
const angle = 2 * Math.acos(this.w);
const s = Math.sqrt(1 - this.w * this.w); // assuming quaternion normalised then w is less than 1, so term always positive.
if (s < 0.001) {
// test to avoid divide by zero, s is always positive due to sqrt
// if s close to zero then direction of axis not important
targetAxis.x = this.x; // if it is important that axis is normalised then replace with x=1; y=z=0;
targetAxis.y = this.y;
targetAxis.z = this.z;
} else {
targetAxis.x = this.x / s; // normalise axis
targetAxis.y = this.y / s;
targetAxis.z = this.z / s;
}
return [targetAxis, angle];
}
/**
* Set the quaternion value given two vectors. The resulting rotation will be the needed rotation to rotate u to v.
*/
setFromVectors(u, v) {
if (u.isAntiparallelTo(v)) {
const t1 = sfv_t1;
const t2 = sfv_t2;
u.tangents(t1, t2);
this.setFromAxisAngle(t1, Math.PI);
} else {
const a = u.cross(v);
this.x = a.x;
this.y = a.y;
this.z = a.z;
this.w = Math.sqrt(u.length() ** 2 * v.length() ** 2) + u.dot(v);
this.normalize();
}
return this;
}
/**
* Multiply the quaternion with an other quaternion.
*/
mult(quat, target) {
if (target === void 0) {
target = new Quaternion();
}
const ax = this.x;
const ay = this.y;
const az = this.z;
const aw = this.w;
const bx = quat.x;
const by = quat.y;
const bz = quat.z;
const bw = quat.w;
target.x = ax * bw + aw * bx + ay * bz - az * by;
target.y = ay * bw + aw * by + az * bx - ax * bz;
target.z = az * bw + aw * bz + ax * by - ay * bx;
target.w = aw * bw - ax * bx - ay * by - az * bz;
return target;
}
/**
* Get the inverse quaternion rotation.
*/
inverse(target) {
if (target === void 0) {
target = new Quaternion();
}
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
this.conjugate(target);
const inorm2 = 1 / (x * x + y * y + z * z + w * w);
target.x *= inorm2;
target.y *= inorm2;
target.z *= inorm2;
target.w *= inorm2;
return target;
}
/**
* Get the quaternion conjugate
*/
conjugate(target) {
if (target === void 0) {
target = new Quaternion();
}
target.x = -this.x;
target.y = -this.y;
target.z = -this.z;
target.w = this.w;
return target;
}
/**
* Normalize the quaternion. Note that this changes the values of the quaternion.
*/
normalize() {
let l = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
if (l === 0) {
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 0;
} else {
l = 1 / l;
this.x *= l;
this.y *= l;
this.z *= l;
this.w *= l;
}
return this;
}
/**
* Approximation of quaternion normalization. Works best when quat is already almost-normalized.
* @author unphased, https://github.com/unphased
*/
normalizeFast() {
const f = (3.0 - (this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w)) / 2.0;
if (f === 0) {
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 0;
} else {
this.x *= f;
this.y *= f;
this.z *= f;
this.w *= f;
}
return this;
}
/**
* Multiply the quaternion by a vector
*/
vmult(v, target) {
if (target === void 0) {
target = new Vec3();
}
const x = v.x;
const y = v.y;
const z = v.z;
const qx = this.x;
const qy = this.y;
const qz = this.z;
const qw = this.w; // q*v
const ix = qw * x + qy * z - qz * y;
const iy = qw * y + qz * x - qx * z;
const iz = qw * z + qx * y - qy * x;
const iw = -qx * x - qy * y - qz * z;
target.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
target.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
target.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
return target;
}
/**
* Copies value of source to this quaternion.
* @return this
*/
copy(quat) {
this.x = quat.x;
this.y = quat.y;
this.z = quat.z;
this.w = quat.w;
return this;
}
/**
* Convert the quaternion to euler angle representation. Order: YZX, as this page describes: https://www.euclideanspace.com/maths/standards/index.htm
* @param order Three-character string, defaults to "YZX"
*/
toEuler(target, order) {
if (order === void 0) {
order = 'YZX';
}
let heading;
let attitude;
let bank;
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
switch (order) {
case 'YZX':
const test = x * y + z * w;
if (test > 0.499) {
// singularity at north pole
heading = 2 * Math.atan2(x, w);
attitude = Math.PI / 2;
bank = 0;
}
if (test < -0.499) {
// singularity at south pole
heading = -2 * Math.atan2(x, w);
attitude = -Math.PI / 2;
bank = 0;
}
if (heading === undefined) {
const sqx = x * x;
const sqy = y * y;
const sqz = z * z;
heading = Math.atan2(2 * y * w - 2 * x * z, 1 - 2 * sqy - 2 * sqz); // Heading
attitude = Math.asin(2 * test); // attitude
bank = Math.atan2(2 * x * w - 2 * y * z, 1 - 2 * sqx - 2 * sqz); // bank
}
break;
default:
throw new Error(`Euler order ${order} not supported yet.`);
}
target.y = heading;
target.z = attitude;
target.x = bank;
}
/**
* Set the quaternion components given Euler angle representation.
*
* @param order The order to apply angles: 'XYZ' or 'YXZ' or any other combination.
*
* See {@link https://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors MathWorks} reference
*/
setFromEuler(x, y, z, order) {
if (order === void 0) {
order = 'XYZ';
}
const c1 = Math.cos(x / 2);
const c2 = Math.cos(y / 2);
const c3 = Math.cos(z / 2);
const s1 = Math.sin(x / 2);
const s2 = Math.sin(y / 2);
const s3 = Math.sin(z / 2);
if (order === 'XYZ') {
this.x = s1 * c2 * c3 + c1 * s2 * s3;
this.y = c1 * s2 * c3 - s1 * c2 * s3;
this.z = c1 * c2 * s3 + s1 * s2 * c3;
this.w = c1 * c2 * c3 - s1 * s2 * s3;
} else if (order === 'YXZ') {
this.x = s1 * c2 * c3 + c1 * s2 * s3;
this.y = c1 * s2 * c3 - s1 * c2 * s3;
this.z = c1 * c2 * s3 - s1 * s2 * c3;
this.w = c1 * c2 * c3 + s1 * s2 * s3;
} else if (order === 'ZXY') {
this.x = s1 * c2 * c3 - c1 * s2 * s3;
this.y = c1 * s2 * c3 + s1 * c2 * s3;
this.z = c1 * c2 * s3 + s1 * s2 * c3;
this.w = c1 * c2 * c3 - s1 * s2 * s3;
} else if (order === 'ZYX') {
this.x = s1 * c2 * c3 - c1 * s2 * s3;
this.y = c1 * s2 * c3 + s1 * c2 * s3;
this.z = c1 * c2 * s3 - s1 * s2 * c3;
this.w = c1 * c2 * c3 + s1 * s2 * s3;
} else if (order === 'YZX') {
this.x = s1 * c2 * c3 + c1 * s2 * s3;
this.y = c1 * s2 * c3 + s1 * c2 * s3;
this.z = c1 * c2 * s3 - s1 * s2 * c3;
this.w = c1 * c2 * c3 - s1 * s2 * s3;
} else if (order === 'XZY') {
this.x = s1 * c2 * c3 - c1 * s2 * s3;
this.y = c1 * s2 * c3 - s1 * c2 * s3;
this.z = c1 * c2 * s3 + s1 * s2 * c3;
this.w = c1 * c2 * c3 + s1 * s2 * s3;
}
return this;
}
clone() {
return new Quaternion(this.x, this.y, this.z, this.w);
}
/**
* Performs a spherical linear interpolation between two quat
*
* @param toQuat second operand
* @param t interpolation amount between the self quaternion and toQuat
* @param target A quaternion to store the result in. If not provided, a new one will be created.
* @returns {Quaternion} The "target" object
*/
slerp(toQuat, t, target) {
if (target === void 0) {
target = new Quaternion();
}
const ax = this.x;
const ay = this.y;
const az = this.z;
const aw = this.w;
let bx = toQuat.x;
let by = toQuat.y;
let bz = toQuat.z;
let bw = toQuat.w;
let omega;
let cosom;
let sinom;
let scale0;
let scale1; // calc cosine
cosom = ax * bx + ay * by + az * bz + aw * bw; // adjust signs (if necessary)
if (cosom < 0.0) {
cosom = -cosom;
bx = -bx;
by = -by;
bz = -bz;
bw = -bw;
} // calculate coefficients
if (1.0 - cosom > 0.000001) {
// standard case (slerp)
omega = Math.acos(cosom);
sinom = Math.sin(omega);
scale0 = Math.sin((1.0 - t) * omega) / sinom;
scale1 = Math.sin(t * omega) / sinom;
} else {
// "from" and "to" quaternions are very close
// ... so we can do a linear interpolation
scale0 = 1.0 - t;
scale1 = t;
} // calculate final values
target.x = scale0 * ax + scale1 * bx;
target.y = scale0 * ay + scale1 * by;
target.z = scale0 * az + scale1 * bz;
target.w = scale0 * aw + scale1 * bw;
return target;
}
/**
* Rotate an absolute orientation quaternion given an angular velocity and a time step.
*/
integrate(angularVelocity, dt, angularFactor, target) {
if (target === void 0) {
target = new Quaternion();
}
const ax = angularVelocity.x * angularFactor.x,
ay = angularVelocity.y * angularFactor.y,
az = angularVelocity.z * angularFactor.z,
bx = this.x,
by = this.y,
bz = this.z,
bw = this.w;
const half_dt = dt * 0.5;
target.x += half_dt * (ax * bw + ay * bz - az * by);
target.y += half_dt * (ay * bw + az * bx - ax * bz);
target.z += half_dt * (az * bw + ax * by - ay * bx);
target.w += half_dt * (-ax * bx - ay * by - az * bz);
return target;
}
}
const sfv_t1 = new Vec3();
const sfv_t2 = new Vec3();
/**
* The available shape types.
*/
const SHAPE_TYPES = {
/** SPHERE */
SPHERE: 1,
/** PLANE */
PLANE: 2,
/** BOX */
BOX: 4,
/** COMPOUND */
COMPOUND: 8,
/** CONVEXPOLYHEDRON */
CONVEXPOLYHEDRON: 16,
/** HEIGHTFIELD */
HEIGHTFIELD: 32,
/** PARTICLE */
PARTICLE: 64,
/** CYLINDER */
CYLINDER: 128,
/** TRIMESH */
TRIMESH: 256
};
/**
* ShapeType
*/
/**
* Base class for shapes
*/
class Shape {
/**
* Identifier of the Shape.
*/
/**
* The type of this shape. Must be set to an int > 0 by subclasses.
*/
/**
* The local bounding sphere radius of this shape.
*/
/**
* Whether to produce contact forces when in contact with other bodies. Note that contacts will be generated, but they will be disabled.
* @default true
*/
/**
* @default 1
*/
/**
* @default -1
*/
/**
* Optional material of the shape that regulates contact properties.
*/
/**
* The body to which the shape is added to.
*/
/**
* All the Shape types.
*/
constructor(options) {
if (options === void 0) {
options = {};
}
this.id = Shape.idCounter++;
this.type = options.type || 0;
this.boundingSphereRadius = 0;
this.collisionResponse = options.collisionResponse ? options.collisionResponse : true;
this.collisionFilterGroup = options.collisionFilterGroup !== undefined ? options.collisionFilterGroup : 1;
this.collisionFilterMask = options.collisionFilterMask !== undefined ? options.collisionFilterMask : -1;
this.material = options.material ? options.material : null;
this.body = null;
}
/**
* Computes the bounding sphere radius.
* The result is stored in the property `.boundingSphereRadius`
*/
updateBoundingSphereRadius() {
throw `computeBoundingSphereRadius() not implemented for shape type ${this.type}`;
}
/**
* Get the volume of this shape
*/
volume() {
throw `volume() not implemented for shape type ${this.type}`;
}
/**
* Calculates the inertia in the local frame for this shape.
* @see http://en.wikipedia.org/wiki/List_of_moments_of_inertia
*/
calculateLocalInertia(mass, target) {
throw `calculateLocalInertia() not implemented for shape type ${this.type}`;
}
/**
* @todo use abstract for these kind of methods
*/
calculateWorldAABB(pos, quat, min, max) {
throw `calculateWorldAABB() not implemented for shape type ${this.type}`;
}
}
Shape.idCounter = 0;
Shape.types = SHAPE_TYPES;
/**
* Transformation utilities.
*/
class Transform {
/**
* position
*/
/**
* quaternion
*/
constructor(options) {
if (options === void 0) {
options = {};
}
this.position = new Vec3();
this.quaternion = new Quaternion();
if (options.position) {
this.position.copy(options.position);
}
if (options.quaternion) {
this.quaternion.copy(options.quaternion);
}
}
/**
* Get a global point in local transform coordinates.
*/
pointToLocal(worldPoint, result) {
return Transform.pointToLocalFrame(this.position, this.quaternion, worldPoint, result);
}
/**
* Get a local point in global transform coordinates.
*/
pointToWorld(localPoint, result) {
return Transform.pointToWorldFrame(this.position, this.quaternion, localPoint, result);
}
/**
* vectorToWorldFrame
*/
vectorToWorldFrame(localVector, result) {
if (result === void 0) {
result = new Vec3();
}
this.quaternion.vmult(localVector, result);
return result;
}
/**
* pointToLocalFrame
*/
static pointToLocalFrame(position, quaternion, worldPoint, result) {
if (result === void 0) {
result = new Vec3();
}
worldPoint.vsub(position, result);
quaternion.conjugate(tmpQuat$1);
tmpQuat$1.vmult(result, result);
return result;
}
/**
* pointToWorldFrame
*/
static pointToWorldFrame(position, quaternion, localPoint, result) {
if (result === void 0) {
result = new Vec3();
}
quaternion.vmult(localPoint, result);
result.vadd(position, result);
return result;
}
/**
* vectorToWorldFrame
*/
static vectorToWorldFrame(quaternion, localVector, result) {
if (result === void 0) {
result = new Vec3();
}
quaternion.vmult(localVector, result);
return result;
}
/**
* vectorToLocalFrame
*/
static vectorToLocalFrame(position, quaternion, worldVector, result) {
if (result === void 0) {
result = new Vec3();
}
quaternion.w *= -1;
quaternion.vmult(worldVector, result);
quaternion.w *= -1;
return result;
}
}
const tmpQuat$1 = new Quaternion();
/**
* A set of polygons describing a convex shape.
*
* The shape MUST be convex for the code to work properly. No polygons may be coplanar (contained
* in the same 3D plane), instead these should be merged into one polygon.
*
* @author qiao / https://github.com/qiao (original author, see https://github.com/qiao/three.js/commit/85026f0c769e4000148a67d45a9e9b9c5108836f)
* @author schteppe / https://github.com/schteppe
* @see https://www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/
*
* @todo Move the clipping functions to ContactGenerator?
* @todo Automatically merge coplanar polygons in constructor.
* @example
* const convexShape = new CANNON.ConvexPolyhedron({ vertices, faces })
* const convexBody = new CANNON.Body({ mass: 1, shape: convexShape })
* world.addBody(convexBody)
*/
class ConvexPolyhedron extends Shape {
/** vertices */
/**
* Array of integer arrays, indicating which vertices each face consists of
*/
/** faceNormals */
/** worldVertices */
/** worldVerticesNeedsUpdate */
/** worldFaceNormals */
/** worldFaceNormalsNeedsUpdate */
/**
* If given, these locally defined, normalized axes are the only ones being checked when doing separating axis check.
*/
/** uniqueEdges */
/**
* @param vertices An array of Vec3's
* @param faces Array of integer arrays, describing which vertices that is included in each face.
*/
constructor(props) {
if (props === void 0) {
props = {};
}
const {
vertices = [],
faces = [],
normals = [],
axes,
boundingSphereRadius
} = props;
super({
type: Shape.types.CONVEXPOLYHEDRON
});
this.vertices = vertices;
this.faces = faces;
this.faceNormals = normals;
if (this.faceNormals.length === 0) {
this.computeNormals();
}
if (!boundingSphereRadius) {
this.updateBoundingSphereRadius();
} else {
this.boundingSphereRadius = boundingSphereRadius;
}
this.worldVertices = []; // World transformed version of .vertices
this.worldVerticesNeedsUpdate = true;
this.worldFaceNormals = []; // World transformed version of .faceNormals
this.worldFaceNormalsNeedsUpdate = true;
this.uniqueAxes = axes ? axes.slice() : null;
this.uniqueEdges = [];
this.computeEdges();
}
/**
* Computes uniqueEdges
*/
computeEdges() {
const faces = this.faces;
const vertices = this.vertices;
const edges = this.uniqueEdges;
edges.length = 0;
const edge = new Vec3();
for (let i = 0; i !== faces.length; i++) {
const face = faces[i];
const numVertices = face.length;
for (let j = 0; j !== numVertices; j++) {
const k = (j + 1) % numVertices;
vertices[face[j]].vsub(vertices[face[k]], edge);
edge.normalize();
let found = false;
for (let p = 0; p !== edges.length; p++) {
if (edges[p].almostEquals(edge) || edges[p].almostEquals(edge)) {
found = true;
break;
}
}
if (!found) {
edges.push(edge.clone());
}
}
}
}
/**
* Compute the normals of the faces.
* Will reuse existing Vec3 objects in the `faceNormals` array if they exist.
*/
computeNormals() {
thi