UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

230 lines (229 loc) 6.8 kB
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 };