playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
230 lines (229 loc) • 6.8 kB
JavaScript
function getTransform(world, position, rotation) {
const transform = world._btTransform;
const vec = world._btVec1;
const quat = world._btQuat;
vec.setValue(position.x, position.y, position.z);
quat.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
transform.setOrigin(vec);
transform.setRotation(quat);
return transform;
}
function getTriMesh(world, source) {
let triMesh = world._triMeshCache.get(source.id);
if (!triMesh) {
const positions = source.positions;
const stride = source.stride;
const indices = source.indices;
const base = source.base;
const numTriangles = source.count / 3;
const checkDupes = source.checkDuplicates;
const v1 = new Ammo.btVector3();
let i1, i2, i3;
triMesh = new Ammo.btTriangleMesh();
world._triMeshCache.set(source.id, triMesh);
const vertexCache = /* @__PURE__ */ new Map();
const indexedArray = triMesh.getIndexedMeshArray();
indexedArray.at(0).m_numTriangles = numTriangles;
const bakeScale = source.bakeScale;
const sx = bakeScale ? bakeScale.x : 1;
const sy = bakeScale ? bakeScale.y : 1;
const sz = bakeScale ? bakeScale.z : 1;
const addVertex = (index) => {
const x = positions[index * stride] * sx;
const y = positions[index * stride + 1] * sy;
const z = positions[index * stride + 2] * sz;
let idx;
if (checkDupes) {
const str = `${x}:${y}:${z}`;
idx = vertexCache.get(str);
if (idx !== void 0) {
return idx;
}
v1.setValue(x, y, z);
idx = triMesh.findOrAddVertex(v1, false);
vertexCache.set(str, idx);
} else {
v1.setValue(x, y, z);
idx = triMesh.findOrAddVertex(v1, false);
}
return idx;
};
for (let i = 0; i < numTriangles; i++) {
i1 = addVertex(indices[base + i * 3]);
i2 = addVertex(indices[base + i * 3 + 1]);
i3 = addVertex(indices[base + i * 3 + 2]);
triMesh.addIndex(i1);
triMesh.addIndex(i2);
triMesh.addIndex(i3);
}
Ammo.destroy(v1);
}
return triMesh;
}
function createHullChild(world, compound, source) {
const hull = new Ammo.btConvexHullShape();
const point = new Ammo.btVector3();
const positions = source.positions;
const stride = source.stride;
const bakeScale = source.bakeScale;
const sx = bakeScale ? bakeScale.x : 1;
const sy = bakeScale ? bakeScale.y : 1;
const sz = bakeScale ? bakeScale.z : 1;
for (let i = 0; i < positions.length; i += stride) {
point.setValue(positions[i] * sx, positions[i + 1] * sy, positions[i + 2] * sz);
hull.addPoint(point, false);
}
Ammo.destroy(point);
hull.recalcLocalAabb();
hull.setMargin(0.01);
compound.addChildShape(getTransform(world, source.position, source.rotation), hull);
}
function createTriMeshChild(world, compound, source) {
const triMesh = getTriMesh(world, source);
const triMeshShape = new Ammo.btBvhTriangleMeshShape(
triMesh,
true
/* useQuantizedAabbCompression */
);
const shapeScale = source.shapeScale;
if (shapeScale) {
const vec = world._btVec1;
vec.setValue(shapeScale.x, shapeScale.y, shapeScale.z);
triMeshShape.setLocalScaling(vec);
}
compound.addChildShape(getTransform(world, source.position, source.rotation), triMeshShape);
}
const shapeFactories = {
box: (world, desc) => {
const he = desc.halfExtents;
const ammoHe = new Ammo.btVector3(he.x, he.y, he.z);
const shape = new Ammo.btBoxShape(ammoHe);
Ammo.destroy(ammoHe);
return shape;
},
sphere: (world, desc) => {
return new Ammo.btSphereShape(desc.radius);
},
capsule: (world, desc) => {
const radius = desc.radius;
const height = Math.max(desc.height - 2 * radius, 0);
switch (desc.axis) {
case 0:
return new Ammo.btCapsuleShapeX(radius, height);
case 2:
return new Ammo.btCapsuleShapeZ(radius, height);
default:
return new Ammo.btCapsuleShape(radius, height);
}
},
cylinder: (world, desc) => {
const radius = desc.radius;
const height = desc.height;
let halfExtents = null;
let shape = null;
switch (desc.axis) {
case 0:
halfExtents = new Ammo.btVector3(height * 0.5, radius, radius);
shape = new Ammo.btCylinderShapeX(halfExtents);
break;
case 2:
halfExtents = new Ammo.btVector3(radius, radius, height * 0.5);
shape = new Ammo.btCylinderShapeZ(halfExtents);
break;
default:
halfExtents = new Ammo.btVector3(radius, height * 0.5, radius);
shape = new Ammo.btCylinderShape(halfExtents);
break;
}
Ammo.destroy(halfExtents);
return shape;
},
cone: (world, desc) => {
switch (desc.axis) {
case 0:
return new Ammo.btConeShapeX(desc.radius, desc.height);
case 2:
return new Ammo.btConeShapeZ(desc.radius, desc.height);
default:
return new Ammo.btConeShape(desc.radius, desc.height);
}
},
mesh: (world, desc) => {
const shape = new Ammo.btCompoundShape();
const sources = desc.sources;
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
if (source.convexHull) {
createHullChild(world, shape, source);
} else {
createTriMeshChild(world, shape, source);
}
}
if (desc.scale) {
const vec = new Ammo.btVector3(desc.scale.x, desc.scale.y, desc.scale.z);
shape.setLocalScaling(vec);
Ammo.destroy(vec);
}
return shape;
},
compound: (world, desc) => {
return new Ammo.btCompoundShape();
}
};
function createShape(world, desc) {
const factory = shapeFactories[desc.type];
const shape = factory(world, desc);
shape._shapeType = desc.type;
return shape;
}
function destroyShape(shape) {
if (shape._shapeType === "mesh") {
const numShapes = shape.getNumChildShapes();
for (let i = 0; i < numShapes; i++) {
Ammo.destroy(shape.getChildShape(i));
}
}
Ammo.destroy(shape);
}
function indexOfCompoundChild(compound, child) {
const childPointer = Ammo.getPointer(child);
const numShapes = compound.getNumChildShapes();
for (let i = 0; i < numShapes; i++) {
if (Ammo.getPointer(compound.getChildShape(i)) === childPointer) {
return i;
}
}
return -1;
}
function addCompoundChild(world, compound, child, position, rotation) {
compound.addChildShape(getTransform(world, position, rotation), child);
}
function updateCompoundChild(world, compound, child, position, rotation) {
const transform = getTransform(world, position, rotation);
const idx = indexOfCompoundChild(compound, child);
if (idx < 0) {
compound.addChildShape(transform, child);
} else {
compound.updateChildTransform(idx, transform, true);
}
}
function removeCompoundChild(compound, child) {
if (compound.getNumChildShapes() === 0) {
return;
}
if (compound.removeChildShape) {
compound.removeChildShape(child);
} else {
const idx = indexOfCompoundChild(compound, child);
if (idx >= 0) {
compound.removeChildShapeByIndex(idx);
}
}
}
export {
addCompoundChild,
createShape,
destroyShape,
removeCompoundChild,
updateCompoundChild
};