playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
358 lines (357 loc) • 13.9 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { Debug } from "../../../core/debug.js";
import { Vec3 } from "../../../core/math/vec3.js";
import {
BODYFLAG_KINEMATIC_OBJECT,
BODYFLAG_NORESPONSE_OBJECT,
BODYSTATE_ACTIVE_TAG,
BODYSTATE_DISABLE_DEACTIVATION,
BODYSTATE_DISABLE_SIMULATION,
BODYTYPE_KINEMATIC
} from "../../components/rigid-body/constants.js";
import { RaycastResult } from "../../components/rigid-body/raycast-result.js";
import { PhysicsWorld } from "../physics-world.js";
import { AmmoPhysicsBody } from "./ammo-physics-body.js";
import { createJoint, destroyJoint, destroyFixedBody } from "./ammo-physics-joint.js";
import {
createShape,
destroyShape,
addCompoundChild,
updateCompoundChild,
removeCompoundChild
} from "./ammo-physics-shape.js";
class AmmoContactPair {
constructor() {
__publicField(this, "entityA", null);
__publicField(this, "entityB", null);
__publicField(this, "triggerA", false);
__publicField(this, "triggerB", false);
__publicField(this, "contactCount", 0);
/**
* The native btPersistentManifold currently being reported.
*
* @private
*/
__publicField(this, "_manifold", null);
}
/**
* @param {number} index - The contact point index.
* @param {ContactPoint} out - The contact point to fill, from body A's perspective.
*/
readContact(index, out) {
const contactPoint = this._manifold.getContactPoint(index);
const localPointA = contactPoint.get_m_localPointA();
const localPointB = contactPoint.get_m_localPointB();
const positionWorldOnA = contactPoint.getPositionWorldOnA();
const positionWorldOnB = contactPoint.getPositionWorldOnB();
const normalWorldOnB = contactPoint.get_m_normalWorldOnB();
out.localPoint.set(localPointA.x(), localPointA.y(), localPointA.z());
out.localPointOther.set(localPointB.x(), localPointB.y(), localPointB.z());
out.point.set(positionWorldOnA.x(), positionWorldOnA.y(), positionWorldOnA.z());
out.pointOther.set(positionWorldOnB.x(), positionWorldOnB.y(), positionWorldOnB.z());
out.normal.set(normalWorldOnB.x(), normalWorldOnB.y(), normalWorldOnB.z());
out.impulse = contactPoint.getAppliedImpulse();
}
}
class AmmoPhysicsWorld extends PhysicsWorld {
/**
* Create a new AmmoPhysicsWorld instance.
*
* @param {object} [options] - The world options. See {@link PhysicsWorld}.
*/
constructor(options = {}) {
super(options);
/** @private */
__publicField(this, "_gravityFloat32", new Float32Array(3));
/**
* Built triangle data cached per geometry source id, shared by all mesh shapes created
* from the same geometry. Entries live until the world is destroyed.
*
* @type {Map<number, object>}
* @ignore
*/
__publicField(this, "_triMeshCache", /* @__PURE__ */ new Map());
/**
* The shared static body world-pinned joints attach to, lazily created.
*
* @type {object|null}
* @ignore
*/
__publicField(this, "_fixedBody", null);
/**
* The fixed timestep of the last simulation step, used by joint motor conversions.
*
* @ignore
*/
__publicField(this, "_fixedTimeStep", 1 / 60);
this.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
this.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration);
this.overlappingPairCache = new Ammo.btDbvtBroadphase();
this.solver = new Ammo.btSequentialImpulseConstraintSolver();
this.nativeWorld = new Ammo.btDiscreteDynamicsWorld(this.dispatcher, this.overlappingPairCache, this.solver, this.collisionConfiguration);
this._useTickCallback = !!this.nativeWorld.setInternalTickCallback;
if (this._useTickCallback) {
const checkForCollisionsPointer = Ammo.addFunction(() => this._walkContacts(), "vif");
this.nativeWorld.setInternalTickCallback(checkForCollisionsPointer);
} else {
Debug.warn("WARNING: This version of ammo.js can potentially fail to report contacts. Please update it to the latest version.");
}
this._contactPair = new AmmoContactPair();
this._btVec1 = new Ammo.btVector3();
this._btVec2 = new Ammo.btVector3();
this._btQuat = new Ammo.btQuaternion();
this._btTransform = new Ammo.btTransform();
this._btRayStart = new Ammo.btVector3();
this._btRayEnd = new Ammo.btVector3();
}
destroy() {
this._triMeshCache.forEach((triMesh) => Ammo.destroy(triMesh));
this._triMeshCache.clear();
destroyFixedBody(this);
Ammo.destroy(this._btVec1);
Ammo.destroy(this._btVec2);
Ammo.destroy(this._btQuat);
Ammo.destroy(this._btTransform);
Ammo.destroy(this._btRayStart);
Ammo.destroy(this._btRayEnd);
this._btVec1 = null;
this._btVec2 = null;
this._btQuat = null;
this._btTransform = null;
this._btRayStart = null;
this._btRayEnd = null;
Ammo.destroy(this.nativeWorld);
Ammo.destroy(this.solver);
Ammo.destroy(this.overlappingPairCache);
Ammo.destroy(this.dispatcher);
Ammo.destroy(this.collisionConfiguration);
this.nativeWorld = null;
this.solver = null;
this.overlappingPairCache = null;
this.dispatcher = null;
this.collisionConfiguration = null;
}
/**
* @param {PhysicsBodyDesc} desc - The body descriptor.
* @returns {AmmoPhysicsBody} The new body.
*/
createBody(desc) {
const { type, mass, shape, position, rotation, entity } = desc;
const noContactResponse = !!desc.noContactResponse;
this._btVec1.setValue(position.x, position.y, position.z);
this._btQuat.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
this._btTransform.setOrigin(this._btVec1);
this._btTransform.setRotation(this._btQuat);
const localInertia = new Ammo.btVector3(0, 0, 0);
if (mass !== 0) {
shape.calculateLocalInertia(mass, localInertia);
}
const motionState = new Ammo.btDefaultMotionState(this._btTransform);
const bodyInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
const nativeBody = new Ammo.btRigidBody(bodyInfo);
Ammo.destroy(bodyInfo);
Ammo.destroy(localInertia);
if (type === BODYTYPE_KINEMATIC) {
nativeBody.setCollisionFlags(nativeBody.getCollisionFlags() | BODYFLAG_KINEMATIC_OBJECT);
nativeBody.setActivationState(BODYSTATE_DISABLE_DEACTIVATION);
}
if (noContactResponse) {
nativeBody.setCollisionFlags(nativeBody.getCollisionFlags() | BODYFLAG_NORESPONSE_OBJECT);
}
nativeBody.entity = entity;
const body = new AmmoPhysicsBody(this, nativeBody, type, noContactResponse);
body.entity = entity;
return body;
}
destroyBody(body) {
const nativeBody = body.nativeBody;
const motionState = nativeBody.getMotionState();
if (motionState) {
Ammo.destroy(motionState);
}
Ammo.destroy(nativeBody);
body.nativeBody = null;
}
addBody(body, group, mask) {
const nativeBody = body.nativeBody;
if (group !== void 0 && mask !== void 0) {
this.nativeWorld.addRigidBody(nativeBody, group, mask);
} else {
this.nativeWorld.addRigidBody(nativeBody);
}
nativeBody.forceActivationState(body._type === BODYTYPE_KINEMATIC ? BODYSTATE_DISABLE_DEACTIVATION : BODYSTATE_ACTIVE_TAG);
}
removeBody(body) {
const nativeBody = body.nativeBody;
this.nativeWorld.removeRigidBody(nativeBody);
nativeBody.forceActivationState(BODYSTATE_DISABLE_SIMULATION);
}
/**
* @param {PhysicsShapeDesc} desc - The shape descriptor.
* @returns {object} The opaque shape handle (the native btCollisionShape).
*/
createShape(desc) {
return createShape(this, desc);
}
destroyShape(shape) {
destroyShape(shape);
}
addCompoundChild(compound, child, position, rotation) {
addCompoundChild(this, compound, child, position, rotation);
}
updateCompoundChild(compound, child, position, rotation) {
updateCompoundChild(this, compound, child, position, rotation);
}
removeCompoundChild(compound, child) {
removeCompoundChild(compound, child);
}
getCompoundChildCount(compound) {
return compound.getNumChildShapes();
}
/**
* @param {PhysicsJointDesc} desc - The joint descriptor.
* @returns {AmmoPhysicsJoint} The new joint.
*/
createJoint(desc) {
return createJoint(this, desc);
}
destroyJoint(joint) {
destroyJoint(this, joint);
}
/**
* @param {Vec3} gravity - The world space gravity.
*/
setGravity(gravity) {
this._gravityFloat32[0] = gravity.x;
this._gravityFloat32[1] = gravity.y;
this._gravityFloat32[2] = gravity.z;
const current = this.nativeWorld.getGravity();
if (current.x() !== this._gravityFloat32[0] || current.y() !== this._gravityFloat32[1] || current.z() !== this._gravityFloat32[2]) {
current.setValue(gravity.x, gravity.y, gravity.z);
this.nativeWorld.setGravity(current);
}
}
step(dt, maxSubSteps, fixedTimeStep) {
this._fixedTimeStep = fixedTimeStep;
this.nativeWorld.stepSimulation(dt, maxSubSteps, fixedTimeStep);
}
flushContacts() {
if (!this._useTickCallback) {
this._walkContacts();
}
}
/**
* Walks the dispatcher's contact manifolds and reports each contacting pair with entities
* on both bodies to the contact listener.
*
* @private
*/
_walkContacts() {
const listener = this.contactListener;
if (!listener) {
return;
}
listener.onContactsBegin();
const dispatcher = this.dispatcher;
const numManifolds = dispatcher.getNumManifolds();
const pair = this._contactPair;
for (let i = 0; i < numManifolds; i++) {
const manifold = dispatcher.getManifoldByIndexInternal(i);
const wb0 = Ammo.castObject(manifold.getBody0(), Ammo.btRigidBody);
const wb1 = Ammo.castObject(manifold.getBody1(), Ammo.btRigidBody);
const e0 = wb0.entity;
const e1 = wb1.entity;
if (!e0 || !e1) {
continue;
}
const numContacts = manifold.getNumContacts();
if (numContacts > 0) {
pair.entityA = e0;
pair.entityB = e1;
pair.triggerA = (wb0.getCollisionFlags() & BODYFLAG_NORESPONSE_OBJECT) !== 0;
pair.triggerB = (wb1.getCollisionFlags() & BODYFLAG_NORESPONSE_OBJECT) !== 0;
pair.contactCount = numContacts;
pair._manifold = manifold;
listener.onContactPair(pair);
}
}
pair.entityA = null;
pair.entityB = null;
pair._manifold = null;
listener.onContactsEnd();
}
raycastFirst(start, end, options = {}) {
let result = null;
this._btRayStart.setValue(start.x, start.y, start.z);
this._btRayEnd.setValue(end.x, end.y, end.z);
const rayCallback = new Ammo.ClosestRayResultCallback(this._btRayStart, this._btRayEnd);
if (typeof options.filterCollisionGroup === "number") {
rayCallback.set_m_collisionFilterGroup(options.filterCollisionGroup);
}
if (typeof options.filterCollisionMask === "number") {
rayCallback.set_m_collisionFilterMask(options.filterCollisionMask);
}
this.nativeWorld.rayTest(this._btRayStart, this._btRayEnd, rayCallback);
if (rayCallback.hasHit()) {
const collisionObj = rayCallback.get_m_collisionObject();
const body = Ammo.castObject(collisionObj, Ammo.btRigidBody);
if (body) {
const point = rayCallback.get_m_hitPointWorld();
const normal = rayCallback.get_m_hitNormalWorld();
result = new RaycastResult(
body.entity,
new Vec3(point.x(), point.y(), point.z()),
new Vec3(normal.x(), normal.y(), normal.z()),
rayCallback.get_m_closestHitFraction()
);
}
}
Ammo.destroy(rayCallback);
return result;
}
raycastAll(start, end, options = {}) {
Debug.assert(Ammo.AllHitsRayResultCallback, "AmmoPhysicsWorld#raycastAll: Your version of ammo.js does not expose Ammo.AllHitsRayResultCallback. Update it to latest.");
const results = [];
this._btRayStart.setValue(start.x, start.y, start.z);
this._btRayEnd.setValue(end.x, end.y, end.z);
const rayCallback = new Ammo.AllHitsRayResultCallback(this._btRayStart, this._btRayEnd);
if (typeof options.filterCollisionGroup === "number") {
rayCallback.set_m_collisionFilterGroup(options.filterCollisionGroup);
}
if (typeof options.filterCollisionMask === "number") {
rayCallback.set_m_collisionFilterMask(options.filterCollisionMask);
}
this.nativeWorld.rayTest(this._btRayStart, this._btRayEnd, rayCallback);
if (rayCallback.hasHit()) {
const collisionObjs = rayCallback.get_m_collisionObjects();
const points = rayCallback.get_m_hitPointWorld();
const normals = rayCallback.get_m_hitNormalWorld();
const hitFractions = rayCallback.get_m_hitFractions();
const numHits = collisionObjs.size();
for (let i = 0; i < numHits; i++) {
const body = Ammo.castObject(collisionObjs.at(i), Ammo.btRigidBody);
if (body && body.entity) {
if (options.filterTags && !body.entity.tags.has(...options.filterTags) || options.filterCallback && !options.filterCallback(body.entity)) {
continue;
}
const point = points.at(i);
const normal = normals.at(i);
const result = new RaycastResult(
body.entity,
new Vec3(point.x(), point.y(), point.z()),
new Vec3(normal.x(), normal.y(), normal.z()),
hitFractions.at(i)
);
results.push(result);
}
}
}
Ammo.destroy(rayCallback);
return results;
}
}
export {
AmmoPhysicsWorld
};