three-to-ammo
Version:
Convert THREE.Mesh to Ammo.btCollisionShape
784 lines (681 loc) • 26.3 kB
JavaScript
"use strict";
/* global Ammo */
import * as THREE from "three";
export const TYPE = {
BOX: "box",
CYLINDER: "cylinder",
SPHERE: "sphere",
CAPSULE: "capsule",
CONE: "cone",
HULL: "hull",
HACD: "hacd", //Hierarchical Approximate Convex Decomposition
VHACD: "vhacd", //Volumetric Hierarchical Approximate Convex Decomposition
MESH: "mesh",
HEIGHTFIELD: "heightfield"
};
export const FIT = {
ALL: "all", //A single shape is automatically sized to bound all meshes within the entity.
MANUAL: "manual" //A single shape is sized manually. Requires halfExtents or sphereRadius.
};
export const HEIGHTFIELD_DATA_TYPE = {
short: "short",
float: "float"
};
export const createCollisionShapes = function(vertices, matrices, indexes, matrixWorld, options = {}) {
switch (options.type) {
case TYPE.BOX:
return [createBoxShape(vertices, matrices, matrixWorld, options)];
case TYPE.CYLINDER:
return [createCylinderShape(vertices, matrices, matrixWorld, options)];
case TYPE.CAPSULE:
return [createCapsuleShape(vertices, matrices, matrixWorld, options)];
case TYPE.CONE:
return [createConeShape(vertices, matrices, matrixWorld, options)];
case TYPE.SPHERE:
return [createSphereShape(vertices, matrices, matrixWorld, options)];
case TYPE.HULL:
return [createHullShape(vertices, matrices, matrixWorld, options)];
case TYPE.HACD:
return createHACDShapes(vertices, matrices, indexes, matrixWorld, options);
case TYPE.VHACD:
return createVHACDShapes(vertices, matrices, indexes, matrixWorld, options);
case TYPE.MESH:
return [createTriMeshShape(vertices, matrices, indexes, matrixWorld, options)];
case TYPE.HEIGHTFIELD:
return [createHeightfieldTerrainShape(options)];
default:
console.warn(options.type + " is not currently supported");
return [];
}
};
//TODO: support gimpact (dynamic trimesh) and heightmap
export const createBoxShape = function(vertices, matrices, matrixWorld, options = {}) {
options.type = TYPE.BOX;
_setOptions(options);
if (options.fit === FIT.ALL) {
options.halfExtents = _computeHalfExtents(
_computeBounds(vertices, matrices),
options.minHalfExtent,
options.maxHalfExtent
);
}
const btHalfExtents = new Ammo.btVector3(options.halfExtents.x, options.halfExtents.y, options.halfExtents.z);
const collisionShape = new Ammo.btBoxShape(btHalfExtents);
Ammo.destroy(btHalfExtents);
_finishCollisionShape(collisionShape, options, _computeScale(matrixWorld, options));
return collisionShape;
};
export const createCylinderShape = function(vertices, matrices, matrixWorld, options = {}) {
options.type = TYPE.CYLINDER;
_setOptions(options);
if (options.fit === FIT.ALL) {
options.halfExtents = _computeHalfExtents(
_computeBounds(vertices, matrices),
options.minHalfExtent,
options.maxHalfExtent
);
}
const btHalfExtents = new Ammo.btVector3(options.halfExtents.x, options.halfExtents.y, options.halfExtents.z);
const collisionShape = (() => {
switch (options.cylinderAxis) {
case "y":
return new Ammo.btCylinderShape(btHalfExtents);
case "x":
return new Ammo.btCylinderShapeX(btHalfExtents);
case "z":
return new Ammo.btCylinderShapeZ(btHalfExtents);
}
return null;
})();
Ammo.destroy(btHalfExtents);
_finishCollisionShape(collisionShape, options, _computeScale(matrixWorld, options));
return collisionShape;
};
export const createCapsuleShape = function(vertices, matrices, matrixWorld, options = {}) {
options.type = TYPE.CAPSULE;
_setOptions(options);
if (options.fit === FIT.ALL) {
options.halfExtents = _computeHalfExtents(
_computeBounds(vertices, matrices),
options.minHalfExtent,
options.maxHalfExtent
);
}
const { x, y, z } = options.halfExtents;
const collisionShape = (() => {
switch (options.cylinderAxis) {
case "y":
return new Ammo.btCapsuleShape(Math.max(x, z), y * 2);
case "x":
return new Ammo.btCapsuleShapeX(Math.max(y, z), x * 2);
case "z":
return new Ammo.btCapsuleShapeZ(Math.max(x, y), z * 2);
}
return null;
})();
_finishCollisionShape(collisionShape, options, _computeScale(matrixWorld, options));
return collisionShape;
};
export const createConeShape = function(vertices, matrices, matrixWorld, options = {}) {
options.type = TYPE.CONE;
_setOptions(options);
if (options.fit === FIT.ALL) {
options.halfExtents = _computeHalfExtents(
_computeBounds(vertices, matrices),
options.minHalfExtent,
options.maxHalfExtent
);
}
const { x, y, z } = options.halfExtents;
const collisionShape = (() => {
switch (options.cylinderAxis) {
case "y":
return new Ammo.btConeShape(Math.max(x, z), y * 2);
case "x":
return new Ammo.btConeShapeX(Math.max(y, z), x * 2);
case "z":
return new Ammo.btConeShapeZ(Math.max(x, y), z * 2);
}
return null;
})();
_finishCollisionShape(collisionShape, options, _computeScale(matrixWorld, options));
return collisionShape;
};
export const createSphereShape = function(vertices, matrices, matrixWorld, options = {}) {
options.type = TYPE.SPHERE;
_setOptions(options);
let radius;
if (options.fit === FIT.MANUAL && !isNaN(options.sphereRadius)) {
radius = options.sphereRadius;
} else {
radius = _computeRadius(vertices, matrices, _computeBounds(vertices, matrices));
}
const collisionShape = new Ammo.btSphereShape(radius);
_finishCollisionShape(collisionShape, options, _computeScale(matrixWorld, options));
return collisionShape;
};
export const createHullShape = (function() {
const vertex = new THREE.Vector3();
const center = new THREE.Vector3();
const matrix = new THREE.Matrix4();
return function(vertices, matrices, matrixWorld, options = {}) {
options.type = TYPE.HULL;
_setOptions(options);
if (options.fit === FIT.MANUAL) {
console.warn("cannot use fit: manual with type: hull");
return null;
}
const bounds = _computeBounds(vertices, matrices);
const btVertex = new Ammo.btVector3();
const originalHull = new Ammo.btConvexHullShape();
originalHull.setMargin(options.margin);
center.addVectors(bounds.max, bounds.min).multiplyScalar(0.5);
let vertexCount = 0;
for (let i = 0; i < vertices.length; i++) {
vertexCount += vertices[i].length / 3;
}
const maxVertices = options.hullMaxVertices || 100000;
// todo: might want to implement this in a deterministic way that doesn't do O(verts) calls to Math.random
if (vertexCount > maxVertices) {
console.warn(`too many vertices for hull shape; sampling ~${maxVertices} from ~${vertexCount} vertices`);
}
const p = Math.min(1, maxVertices / vertexCount);
for (let i = 0; i < vertices.length; i++) {
const components = vertices[i];
matrix.fromArray(matrices[i]);
for (let j = 0; j < components.length; j += 3) {
const isLastVertex = i === vertices.length - 1 && j === components.length - 3;
if (Math.random() <= p || isLastVertex) {
// always include the last vertex
vertex
.set(components[j], components[j + 1], components[j + 2])
.applyMatrix4(matrix)
.sub(center);
btVertex.setValue(vertex.x, vertex.y, vertex.z);
originalHull.addPoint(btVertex, isLastVertex); // recalc AABB only on last geometry
}
}
}
let collisionShape = originalHull;
if (originalHull.getNumVertices() >= 100) {
//Bullet documentation says don't use convexHulls with 100 verts or more
const shapeHull = new Ammo.btShapeHull(originalHull);
shapeHull.buildHull(options.margin);
Ammo.destroy(originalHull);
collisionShape = new Ammo.btConvexHullShape(
Ammo.getPointer(shapeHull.getVertexPointer()),
shapeHull.numVertices()
);
Ammo.destroy(shapeHull); // btConvexHullShape makes a copy
}
Ammo.destroy(btVertex);
_finishCollisionShape(collisionShape, options, _computeScale(matrixWorld, options));
return collisionShape;
};
})();
export const createHACDShapes = (function() {
const vector = new THREE.Vector3();
const center = new THREE.Vector3();
const matrix = new THREE.Matrix4();
return function(vertices, matrices, indexes, matrixWorld, options = {}) {
options.type = TYPE.HACD;
_setOptions(options);
if (options.fit === FIT.MANUAL) {
console.warn("cannot use fit: manual with type: hacd");
return [];
}
if (!Ammo.hasOwnProperty("HACD")) {
console.warn(
"HACD unavailable in included build of Ammo.js. Visit https://github.com/mozillareality/ammo.js for the latest version."
);
return [];
}
const bounds = _computeBounds(vertices, matrices);
const scale = _computeScale(matrixWorld, options);
let vertexCount = 0;
let triCount = 0;
center.addVectors(bounds.max, bounds.min).multiplyScalar(0.5);
for (let i = 0; i < vertices.length; i++) {
vertexCount += vertices[i].length / 3;
if (indexes && indexes[i]) {
triCount += indexes[i].length / 3;
} else {
triCount += vertices[i].length / 9;
}
}
const hacd = new Ammo.HACD();
if (options.hasOwnProperty("compacityWeight")) hacd.SetCompacityWeight(options.compacityWeight);
if (options.hasOwnProperty("volumeWeight")) hacd.SetVolumeWeight(options.volumeWeight);
if (options.hasOwnProperty("nClusters")) hacd.SetNClusters(options.nClusters);
if (options.hasOwnProperty("nVerticesPerCH")) hacd.SetNVerticesPerCH(options.nVerticesPerCH);
if (options.hasOwnProperty("concavity")) hacd.SetConcavity(options.concavity);
const points = Ammo._malloc(vertexCount * 3 * 8);
const triangles = Ammo._malloc(triCount * 3 * 4);
hacd.SetPoints(points);
hacd.SetTriangles(triangles);
hacd.SetNPoints(vertexCount);
hacd.SetNTriangles(triCount);
let pptr = points / 8,
tptr = triangles / 4;
for (let i = 0; i < vertices.length; i++) {
const components = vertices[i];
matrix.fromArray(matrices[i]);
for (let j = 0; j < components.length; j += 3) {
vector
.set(components[j + 0], components[j + 1], components[j + 2])
.applyMatrix4(matrix)
.sub(center);
Ammo.HEAPF64[pptr + 0] = vector.x;
Ammo.HEAPF64[pptr + 1] = vector.y;
Ammo.HEAPF64[pptr + 2] = vector.z;
pptr += 3;
}
if (indexes[i]) {
const indices = indexes[i];
for (let j = 0; j < indices.length; j++) {
Ammo.HEAP32[tptr] = indices[j];
tptr++;
}
} else {
for (let j = 0; j < components.length / 3; j++) {
Ammo.HEAP32[tptr] = j;
tptr++;
}
}
}
hacd.Compute();
Ammo._free(points);
Ammo._free(triangles);
const nClusters = hacd.GetNClusters();
const shapes = [];
for (let i = 0; i < nClusters; i++) {
const hull = new Ammo.btConvexHullShape();
hull.setMargin(options.margin);
const nPoints = hacd.GetNPointsCH(i);
const nTriangles = hacd.GetNTrianglesCH(i);
const hullPoints = Ammo._malloc(nPoints * 3 * 8);
const hullTriangles = Ammo._malloc(nTriangles * 3 * 4);
hacd.GetCH(i, hullPoints, hullTriangles);
const pptr = hullPoints / 8;
for (let pi = 0; pi < nPoints; pi++) {
const btVertex = new Ammo.btVector3();
const px = Ammo.HEAPF64[pptr + pi * 3 + 0];
const py = Ammo.HEAPF64[pptr + pi * 3 + 1];
const pz = Ammo.HEAPF64[pptr + pi * 3 + 2];
btVertex.setValue(px, py, pz);
hull.addPoint(btVertex, pi === nPoints - 1);
Ammo.destroy(btVertex);
}
_finishCollisionShape(hull, options, scale);
shapes.push(hull);
}
return shapes;
};
})();
export const createVHACDShapes = (function() {
const vector = new THREE.Vector3();
const center = new THREE.Vector3();
const matrix = new THREE.Matrix4();
return function(vertices, matrices, indexes, matrixWorld, options = {}) {
options.type = TYPE.VHACD;
_setOptions(options);
if (options.fit === FIT.MANUAL) {
console.warn("cannot use fit: manual with type: vhacd");
return [];
}
if (!Ammo.hasOwnProperty("VHACD")) {
console.warn(
"VHACD unavailable in included build of Ammo.js. Visit https://github.com/mozillareality/ammo.js for the latest version."
);
return [];
}
const bounds = _computeBounds(vertices, matrices);
const scale = _computeScale(matrixWorld, options);
let vertexCount = 0;
let triCount = 0;
center.addVectors(bounds.max, bounds.min).multiplyScalar(0.5);
for (let i = 0; i < vertices.length; i++) {
vertexCount += vertices[i].length / 3;
if (indexes && indexes[i]) {
triCount += indexes[i].length / 3;
} else {
triCount += vertices[i].length / 9;
}
}
const vhacd = new Ammo.VHACD();
const params = new Ammo.Parameters();
//https://kmamou.blogspot.com/2014/12/v-hacd-20-parameters-description.html
if (options.hasOwnProperty("resolution")) params.set_m_resolution(options.resolution);
if (options.hasOwnProperty("depth")) params.set_m_depth(options.depth);
if (options.hasOwnProperty("concavity")) params.set_m_concavity(options.concavity);
if (options.hasOwnProperty("planeDownsampling")) params.set_m_planeDownsampling(options.planeDownsampling);
if (options.hasOwnProperty("convexhullDownsampling"))
params.set_m_convexhullDownsampling(options.convexhullDownsampling);
if (options.hasOwnProperty("alpha")) params.set_m_alpha(options.alpha);
if (options.hasOwnProperty("beta")) params.set_m_beta(options.beta);
if (options.hasOwnProperty("gamma")) params.set_m_gamma(options.gamma);
if (options.hasOwnProperty("pca")) params.set_m_pca(options.pca);
if (options.hasOwnProperty("mode")) params.set_m_mode(options.mode);
if (options.hasOwnProperty("maxNumVerticesPerCH")) params.set_m_maxNumVerticesPerCH(options.maxNumVerticesPerCH);
if (options.hasOwnProperty("minVolumePerCH")) params.set_m_minVolumePerCH(options.minVolumePerCH);
if (options.hasOwnProperty("convexhullApproximation"))
params.set_m_convexhullApproximation(options.convexhullApproximation);
if (options.hasOwnProperty("oclAcceleration")) params.set_m_oclAcceleration(options.oclAcceleration);
const points = Ammo._malloc(vertexCount * 3 * 8 + 3);
const triangles = Ammo._malloc(triCount * 3 * 4);
let pptr = points / 8,
tptr = triangles / 4;
for (let i = 0; i < vertices.length; i++) {
const components = vertices[i];
matrix.fromArray(matrices[i]);
for (let j = 0; j < components.length; j += 3) {
vector
.set(components[j + 0], components[j + 1], components[j + 2])
.applyMatrix4(matrix)
.sub(center);
Ammo.HEAPF64[pptr + 0] = vector.x;
Ammo.HEAPF64[pptr + 1] = vector.y;
Ammo.HEAPF64[pptr + 2] = vector.z;
pptr += 3;
}
if (indexes[i]) {
const indices = indexes[i];
for (let j = 0; j < indices.length; j++) {
Ammo.HEAP32[tptr] = indices[j];
tptr++;
}
} else {
for (let j = 0; j < components.length / 3; j++) {
Ammo.HEAP32[tptr] = j;
tptr++;
}
}
}
vhacd.Compute(points, 3, vertexCount, triangles, 3, triCount, params);
Ammo._free(points);
Ammo._free(triangles);
const nHulls = vhacd.GetNConvexHulls();
const shapes = [];
const ch = new Ammo.ConvexHull();
for (let i = 0; i < nHulls; i++) {
vhacd.GetConvexHull(i, ch);
const nPoints = ch.get_m_nPoints();
const hullPoints = ch.get_m_points();
const hull = new Ammo.btConvexHullShape();
hull.setMargin(options.margin);
for (let pi = 0; pi < nPoints; pi++) {
const btVertex = new Ammo.btVector3();
const px = ch.get_m_points(pi * 3 + 0);
const py = ch.get_m_points(pi * 3 + 1);
const pz = ch.get_m_points(pi * 3 + 2);
btVertex.setValue(px, py, pz);
hull.addPoint(btVertex, pi === nPoints - 1);
Ammo.destroy(btVertex);
}
_finishCollisionShape(hull, options, scale);
shapes.push(hull);
}
Ammo.destroy(ch);
Ammo.destroy(vhacd);
return shapes;
};
})();
export const createTriMeshShape = (function() {
const va = new THREE.Vector3();
const vb = new THREE.Vector3();
const vc = new THREE.Vector3();
const matrix = new THREE.Matrix4();
return function(vertices, matrices, indexes, matrixWorld, options = {}) {
options.type = TYPE.MESH;
_setOptions(options);
if (options.fit === FIT.MANUAL) {
console.warn("cannot use fit: manual with type: mesh");
return null;
}
const scale = _computeScale(matrixWorld, options);
const bta = new Ammo.btVector3();
const btb = new Ammo.btVector3();
const btc = new Ammo.btVector3();
const triMesh = new Ammo.btTriangleMesh(true, false);
for (let i = 0; i < vertices.length; i++) {
const components = vertices[i];
const index = indexes[i] ? indexes[i] : null;
matrix.fromArray(matrices[i]);
if (index) {
for (let j = 0; j < index.length; j += 3) {
const ai = index[j] * 3;
const bi = index[j + 1] * 3;
const ci = index[j + 2] * 3;
va.set(components[ai], components[ai + 1], components[ai + 2]).applyMatrix4(matrix);
vb.set(components[bi], components[bi + 1], components[bi + 2]).applyMatrix4(matrix);
vc.set(components[ci], components[ci + 1], components[ci + 2]).applyMatrix4(matrix);
bta.setValue(va.x, va.y, va.z);
btb.setValue(vb.x, vb.y, vb.z);
btc.setValue(vc.x, vc.y, vc.z);
triMesh.addTriangle(bta, btb, btc, false);
}
} else {
for (let j = 0; j < components.length; j += 9) {
va.set(components[j + 0], components[j + 1], components[j + 2]).applyMatrix4(matrix);
vb.set(components[j + 3], components[j + 4], components[j + 5]).applyMatrix4(matrix);
vc.set(components[j + 6], components[j + 7], components[j + 8]).applyMatrix4(matrix);
bta.setValue(va.x, va.y, va.z);
btb.setValue(vb.x, vb.y, vb.z);
btc.setValue(vc.x, vc.y, vc.z);
triMesh.addTriangle(bta, btb, btc, false);
}
}
}
const localScale = new Ammo.btVector3(scale.x, scale.y, scale.z);
triMesh.setScaling(localScale);
Ammo.destroy(localScale);
const collisionShape = new Ammo.btBvhTriangleMeshShape(triMesh, true, true);
collisionShape.resources = [triMesh];
Ammo.destroy(bta);
Ammo.destroy(btb);
Ammo.destroy(btc);
_finishCollisionShape(collisionShape, options);
return collisionShape;
};
})();
export const createHeightfieldTerrainShape = function(options = {}) {
_setOptions(options);
if (options.fit === FIT.ALL) {
console.warn("cannot use fit: all with type: heightfield");
return null;
}
const heightfieldDistance = options.heightfieldDistance || 1;
const heightfieldData = options.heightfieldData || [];
const heightScale = options.heightScale || 0;
const upAxis = options.hasOwnProperty("upAxis") ? options.upAxis : 1; // x = 0; y = 1; z = 2
const hdt = (() => {
switch (options.heightDataType) {
case "short":
return Ammo.PHY_SHORT;
case "float":
return Ammo.PHY_FLOAT;
default:
return Ammo.PHY_FLOAT;
}
})();
const flipQuadEdges = options.hasOwnProperty("flipQuadEdges") ? options.flipQuadEdges : true;
const heightStickLength = heightfieldData.length;
const heightStickWidth = heightStickLength > 0 ? heightfieldData[0].length : 0;
const data = Ammo._malloc(heightStickLength * heightStickWidth * 4);
const ptr = data / 4;
let minHeight = Number.POSITIVE_INFINITY;
let maxHeight = Number.NEGATIVE_INFINITY;
let index = 0;
for (let l = 0; l < heightStickLength; l++) {
for (let w = 0; w < heightStickWidth; w++) {
const height = heightfieldData[l][w];
Ammo.HEAPF32[ptr + index] = height;
index++;
minHeight = Math.min(minHeight, height);
maxHeight = Math.max(maxHeight, height);
}
}
const collisionShape = new Ammo.btHeightfieldTerrainShape(
heightStickWidth,
heightStickLength,
data,
heightScale,
minHeight,
maxHeight,
upAxis,
hdt,
flipQuadEdges
);
const scale = new Ammo.btVector3(heightfieldDistance, 1, heightfieldDistance);
collisionShape.setLocalScaling(scale);
Ammo.destroy(scale);
collisionShape.heightfieldData = data;
_finishCollisionShape(collisionShape, options);
return collisionShape;
};
function _setOptions(options) {
options.fit = options.hasOwnProperty("fit") ? options.fit : FIT.ALL;
options.type = options.type || TYPE.HULL;
options.minHalfExtent = options.hasOwnProperty("minHalfExtent") ? options.minHalfExtent : 0;
options.maxHalfExtent = options.hasOwnProperty("maxHalfExtent") ? options.maxHalfExtent : Number.POSITIVE_INFINITY;
options.cylinderAxis = options.cylinderAxis || "y";
options.margin = options.hasOwnProperty("margin") ? options.margin : 0.01;
options.includeInvisible = options.hasOwnProperty("includeInvisible") ? options.includeInvisible : false;
if (!options.offset) {
options.offset = new THREE.Vector3();
}
if (!options.orientation) {
options.orientation = new THREE.Quaternion();
}
}
const _finishCollisionShape = function(collisionShape, options, scale) {
collisionShape.type = options.type;
collisionShape.setMargin(options.margin);
collisionShape.destroy = () => {
for (let res of collisionShape.resources || []) {
Ammo.destroy(res);
}
if (collisionShape.heightfieldData) {
Ammo._free(collisionShape.heightfieldData);
}
Ammo.destroy(collisionShape);
};
const localTransform = new Ammo.btTransform();
const rotation = new Ammo.btQuaternion();
localTransform.setIdentity();
localTransform.getOrigin().setValue(options.offset.x, options.offset.y, options.offset.z);
rotation.setValue(options.orientation.x, options.orientation.y, options.orientation.z, options.orientation.w);
localTransform.setRotation(rotation);
Ammo.destroy(rotation);
if (scale) {
const localScale = new Ammo.btVector3(scale.x, scale.y, scale.z);
collisionShape.setLocalScaling(localScale);
Ammo.destroy(localScale);
}
collisionShape.localTransform = localTransform;
};
export const iterateGeometries = (function() {
const inverse = new THREE.Matrix4();
return function(root, options, cb) {
inverse.copy(root.matrixWorld).invert();
const scale = new THREE.Vector3();
scale.setFromMatrixScale(root.matrixWorld);
root.traverse(mesh => {
const transform = new THREE.Matrix4();
if (
mesh.isMesh &&
mesh.name !== "Sky" &&
(options.includeInvisible || (mesh.el && mesh.el.object3D.visible) || mesh.visible)
) {
if (mesh === root) {
transform.identity();
} else {
mesh.updateWorldMatrix(true);
transform.multiplyMatrices(inverse, mesh.matrixWorld);
}
// todo: might want to return null xform if this is the root so that callers can avoid multiplying
// things by the identity matrix
cb(
mesh.geometry.isBufferGeometry ? mesh.geometry.attributes.position.array : mesh.geometry.vertices,
transform.elements,
mesh.geometry.index ? mesh.geometry.index.array : null
);
}
});
};
})();
const _computeScale = (function() {
const matrix = new THREE.Matrix4();
return function(matrixWorld, options = {}) {
const scale = new THREE.Vector3(1, 1, 1);
if (options.fit === FIT.ALL) {
matrix.fromArray(matrixWorld);
scale.setFromMatrixScale(matrix);
}
return scale;
};
})();
const _computeRadius = (function() {
const center = new THREE.Vector3();
return function(vertices, matrices, bounds) {
let maxRadiusSq = 0;
let { x: cx, y: cy, z: cz } = bounds.getCenter(center);
_iterateVertices(vertices, matrices, v => {
const dx = cx - v.x;
const dy = cy - v.y;
const dz = cz - v.z;
maxRadiusSq = Math.max(maxRadiusSq, dx * dx + dy * dy + dz * dz);
});
return Math.sqrt(maxRadiusSq);
};
})();
const _computeHalfExtents = function(bounds, minHalfExtent, maxHalfExtent) {
const halfExtents = new THREE.Vector3();
return halfExtents
.subVectors(bounds.max, bounds.min)
.multiplyScalar(0.5)
.clampScalar(minHalfExtent, maxHalfExtent);
};
const _computeLocalOffset = function(matrix, bounds, target) {
target
.addVectors(bounds.max, bounds.min)
.multiplyScalar(0.5)
.applyMatrix4(matrix);
return target;
};
// returns the bounding box for the geometries underneath `root`.
const _computeBounds = function(vertices, matrices) {
const bounds = new THREE.Box3();
let minX = +Infinity;
let minY = +Infinity;
let minZ = +Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
let maxZ = -Infinity;
bounds.min.set(0, 0, 0);
bounds.max.set(0, 0, 0);
_iterateVertices(vertices, matrices, v => {
if (v.x < minX) minX = v.x;
if (v.y < minY) minY = v.y;
if (v.z < minZ) minZ = v.z;
if (v.x > maxX) maxX = v.x;
if (v.y > maxY) maxY = v.y;
if (v.z > maxZ) maxZ = v.z;
});
bounds.min.set(minX, minY, minZ);
bounds.max.set(maxX, maxY, maxZ);
return bounds;
};
const _iterateVertices = (function() {
const vertex = new THREE.Vector3();
const matrix = new THREE.Matrix4();
return function(vertices, matrices, cb) {
for (let i = 0; i < vertices.length; i++) {
matrix.fromArray(matrices[i]);
for (let j = 0; j < vertices[i].length; j += 3) {
vertex.set(vertices[i][j], vertices[i][j + 1], vertices[i][j + 2]).applyMatrix4(matrix);
cb(vertex);
}
}
};
})();