@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
509 lines (485 loc) • 16.4 kB
text/typescript
import {TypeAssert} from './../../engine/poly/Assert';
// import {CorePhysicsUserData} from './PhysicsUserData';
import {PhysicsRBDColliderType, PhysicsRBDType, CorePhysicsAttribute} from './PhysicsAttribute';
import {Object3D, Vector3, Quaternion} from 'three';
import {World, RigidBodyType, RigidBodyDesc, RigidBody, ColliderDesc} from '@dimforge/rapier3d-compat';
import {CorePhysicsLoaded, PhysicsLib} from './CorePhysics';
import {createPhysicsSphere} from './shapes/RBDSphere';
import {createPhysicsCuboid} from './shapes/RBDCuboid';
import {createPhysicsCapsule} from './shapes/RBDCapsule';
import {createPhysicsCone} from './shapes/RBDCone';
import {createPhysicsCylinder} from './shapes/RBDCylinder';
import {createPhysicsTriMesh} from './shapes/RBDTrimesh';
import {createPhysicsConvexHull} from './shapes/ConvexHull';
import {createPhysicsHeightField} from './shapes/HeightField';
import {touchRBDProperties, touchRBDProperty} from '../reactivity/RBDPropertyReactivity';
import {OBJECT_TRANSFORM_PROPERTIES, touchObjectProperties} from '../reactivity/ObjectPropertyReactivity';
import {PolyScene} from '../../engine/scene/PolyScene';
import {removeFromParent} from '../../engine/poly/PolyOnObjectsAddRemoveHooksController';
import {getRBDFromId, object3DFromRBD} from './PhysicsWorld';
export enum RBDProperty {
ANGULAR_VELOCITY = 'angVel',
LINEAR_VELOCITY = 'linVel',
POSITION = 'position',
ROTATION = 'rotation',
LINEAR_DAMPING = 'linearDamping',
ANGULAR_DAMPING = 'angularDamping',
IS_SLEEPING = 'isSleeping',
IS_MOVING = 'isMoving',
}
const tmpV3 = new Vector3();
// const q1 = new Quaternion();
// const q2 = new Quaternion();
const currentPos = new Vector3();
const currentLinearVelocity = new Vector3();
const currentAngularVelocity = new Vector3();
const newPos = new Vector3();
const newLinearVelocity = new Vector3();
const newAngularVelocity = new Vector3();
const currentQuaternion = new Quaternion();
const newQuaternion = new Quaternion();
const linearVelocity = new Vector3();
const angularVelocity = new Vector3();
interface CollidescObjectPair {
object: Object3D;
colliderDesc: ColliderDesc;
}
const physicsRBDByRBDHandle: Map<number, RigidBody> = new Map();
// const physicsRBDByRBDId: Map<string, RigidBody> = new Map();
const worldByRBD: WeakMap<RigidBody, World> = new WeakMap();
function _createRBDFromDescAndId(world: World, rigidBodyDesc: RigidBodyDesc, rbdId: string) {
const rigidBody = world.createRigidBody(rigidBodyDesc);
const handle = rigidBody.handle;
worldByRBD.set(rigidBody, world);
physicsRBDByRBDHandle.set(handle, rigidBody);
// physicsRBDByRBDId.set(rbdId, rigidBody);
return rigidBody;
}
function _createRBDFromDescAndObject(world: World, rigidBodyDesc: RigidBodyDesc, object: Object3D) {
const rbdId = CorePhysicsAttribute.getRBDId(object);
const rigidBody = _createRBDFromDescAndId(world, rigidBodyDesc, rbdId);
CorePhysicsAttribute.setRBDHandle(object, rigidBody.handle);
return rigidBody;
}
interface CreateRBDFromAttributesOptions {
PhysicsLib: PhysicsLib;
world: World;
object: Object3D;
rigidBodyById: Map<string, RigidBody>;
objectsByRBD: WeakMap<RigidBody, Object3D>;
newRBDIds: Set<string>;
}
function _createRBDFromAttributes(options: CreateRBDFromAttributesOptions): RigidBody | undefined {
const {PhysicsLib, world, object, rigidBodyById, objectsByRBD, newRBDIds} = options;
const type = CorePhysicsAttribute.getRBDType(object);
if (type == null) {
return;
}
const rbdType: RigidBodyType | undefined = PhysicsRBDTypeToRigidBodyType(type);
if (rbdType == null) {
return;
}
// get attributes
const linearDamping = CorePhysicsAttribute.getLinearDamping(object);
const angularDamping = CorePhysicsAttribute.getAngularDamping(object);
linearVelocity.set(0, 0, 0);
angularVelocity.set(0, 0, 0);
CorePhysicsAttribute.getLinearVelocity(object, linearVelocity);
CorePhysicsAttribute.getAngularVelocity(object, angularVelocity);
const gravityScale = CorePhysicsAttribute.getGravityScale(object);
const canSleep = CorePhysicsAttribute.getCanSleep(object);
// create desc
const rigidBodyDesc = new PhysicsLib.RigidBodyDesc(rbdType);
rigidBodyDesc.setTranslation(object.position.x, object.position.y, object.position.z);
rigidBodyDesc.setRotation(object.quaternion);
rigidBodyDesc.setLinvel(linearVelocity.x, linearVelocity.y, linearVelocity.z);
rigidBodyDesc.setAngvel(angularVelocity);
if (linearDamping != null) {
rigidBodyDesc.setLinearDamping(linearDamping);
}
if (angularDamping != null) {
rigidBodyDesc.setAngularDamping(angularDamping);
}
if (gravityScale != null) {
// .setGravityScale seems to work when set on the rbd desc,
// but not on the RBD itself
rigidBodyDesc.setGravityScale(gravityScale);
}
if (canSleep != null) {
rigidBodyDesc.setCanSleep(canSleep);
}
// create RBD
const rigidBody = _createRBDFromDescAndObject(world, rigidBodyDesc, object);
objectsByRBD.set(rigidBody, object);
const rbdId = CorePhysicsAttribute.getRBDId(object);
if (rbdId) {
rigidBodyById.set(rbdId, rigidBody);
newRBDIds.add(rbdId);
}
return rigidBody;
}
function _createColliderDesc(PhysicsLib: PhysicsLib, object: Object3D) {
const colliderType = CorePhysicsAttribute.getColliderType(object);
if (!(colliderType != null)) {
return;
}
const colliderDesc = PhysicsRBDCollider(PhysicsLib, colliderType, object);
if (!colliderDesc) {
console.error('no collider', object);
return;
}
const restitution = CorePhysicsAttribute.getRestitution(object);
const friction = CorePhysicsAttribute.getFriction(object);
const density = CorePhysicsAttribute.getDensity(object);
if (restitution != null) {
colliderDesc.setRestitution(restitution);
}
if (friction != null) {
colliderDesc.setFriction(friction);
}
if (density != null) {
colliderDesc.setDensity(density);
}
return colliderDesc;
}
export function _getRBDFromId(rbdId: string): Object3D | undefined {
const rigidBody = getRBDFromId(rbdId);
if (!rigidBody) {
return;
}
return object3DFromRBD(rigidBody);
}
export function _getRBDFromObject(object: Object3D) {
const handle = CorePhysicsAttribute.getRBDHandle(object);
if (handle == null) {
return;
}
return physicsRBDByRBDHandle.get(handle);
}
export function _getPhysicsWorldFromRBD(object: Object3D) {
const rbd = _getRBDFromObject(object);
if (!rbd) {
return;
}
return worldByRBD.get(rbd);
}
export function _physicsRBDDelete(scene: PolyScene, object: Object3D) {
const handle = CorePhysicsAttribute.getRBDHandle(object);
if (handle == null) {
return;
}
const body = physicsRBDByRBDHandle.get(handle);
if (!body) {
return;
}
const world = worldByRBD.get(body);
if (!world) {
return;
}
world.removeRigidBody(body);
worldByRBD.delete(body);
physicsRBDByRBDHandle.delete(handle);
CorePhysicsAttribute.deleteRBDHandle(object);
removeFromParent(scene, object);
}
interface PhysicsCreateRBDOptions {
PhysicsLib: PhysicsLib;
world: World;
object: Object3D;
rigidBodyById: Map<string, RigidBody>;
objectsByRBD: WeakMap<RigidBody, Object3D>;
newRBDIds: Set<string>;
}
export function _physicsCreateRBD(options: PhysicsCreateRBDOptions) {
const {PhysicsLib, world, object, rigidBodyById, objectsByRBD, newRBDIds} = options;
const rigidBody = _createRBDFromAttributes({
PhysicsLib,
world,
object,
rigidBodyById,
objectsByRBD,
newRBDIds,
});
if (!rigidBody) {
// if not rbd created, we go through the children
for (const child of object.children) {
_physicsCreateRBD({PhysicsLib, world, rigidBodyById, objectsByRBD, object: child, newRBDIds});
}
return;
}
let childrenColliderDesc: CollidescObjectPair[] | undefined;
if (object.children.length > 0) {
object.traverse((child) => {
if (child != object) {
const childColliderDesc = _createColliderDesc(PhysicsLib, child);
if (childColliderDesc) {
childrenColliderDesc = childrenColliderDesc || [];
childrenColliderDesc.push({object: child, colliderDesc: childColliderDesc});
}
}
});
}
const colliderDesc = _createColliderDesc(PhysicsLib, object);
if (!(colliderDesc || childrenColliderDesc)) {
return;
}
if (colliderDesc) {
world.createCollider(colliderDesc, rigidBody);
}
if (childrenColliderDesc) {
// we must not alterate the hierarchy here,
// as it messes up with orders of children
// let currentParent = object.parent;
// currentParent?.remove(object);
// object.getWorldQuaternion(q1);
for (const childCollider of childrenColliderDesc) {
const collider = world.createCollider(childCollider.colliderDesc, rigidBody);
// t
tmpV3.copy(childCollider.object.position);
childCollider.object.localToWorld(tmpV3);
object.worldToLocal(tmpV3);
collider.setTranslationWrtParent(tmpV3);
// r
// TODO: here we are only getting the quaternion of the object,
// not its quaternion relative to the parent.
// It works if it is a direct child, but will fail with a deeper hierarchy
// let currentChildParent = childCollider.object.parent;
// object.attach(childCollider.object);
// object.matrix.decompose(t, q, s);
// it seems that computing .getWorldQuaternion
// messes up with .quaternion
// object.getWorldQuaternion(q1);
// childCollider.object.getWorldQuaternion(q2);
// q1.invert().multiply(q2);
// q2.multiply(q1.invert());
collider.setRotationWrtParent(childCollider.object.quaternion);
// currentChildParent?.attach(childCollider.object);
}
// currentParent?.add(object);
}
}
export function physicsUpdateRBD(object: Object3D, rigidBody: RigidBody) {
// physicsRBDSetInitMatrix(object);
const position = rigidBody.translation();
const rotation = rigidBody.rotation();
object.position.set(position.x, position.y, position.z);
object.quaternion.set(rotation.x, rotation.y, rotation.z, rotation.w);
object.updateMatrix();
updateRBDRefs(object);
}
const SIM_PROPERTIES = [
RBDProperty.ANGULAR_VELOCITY,
RBDProperty.LINEAR_VELOCITY,
RBDProperty.POSITION,
RBDProperty.ROTATION,
RBDProperty.IS_SLEEPING,
RBDProperty.IS_MOVING,
];
function updateRBDRefs(object: Object3D) {
touchRBDProperties(object, SIM_PROPERTIES);
touchObjectProperties(object, OBJECT_TRANSFORM_PROPERTIES);
}
// impulse
export function _physicsRBDApplyImpulse(object: Object3D, impulse: Vector3) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.applyImpulse(impulse, true);
}
export function _physicsRBDApplyImpulseAtPoint(object: Object3D, impulse: Vector3, point: Vector3) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.applyImpulseAtPoint(impulse, point, true);
}
export function _physicsRBDApplyTorqueImpulse(object: Object3D, impulse: Vector3) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.applyTorqueImpulse(impulse, true);
}
// add
export function _physicsRBDAddForce(object: Object3D, force: Vector3) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.addForce(force, true);
}
export function _physicsRBDAddForceAtPoint(object: Object3D, force: Vector3, point: Vector3) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.addForceAtPoint(force, point, true);
}
export function _physicsRBDAddTorque(object: Object3D, torque: Vector3) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.addTorque(torque, true);
}
// reset
export function _physicsRBDResetAll(object: Object3D, wakeup: boolean) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.resetForces(wakeup);
body.resetTorques(wakeup);
body.setLinvel({x: 0, y: 0, z: 0}, wakeup);
body.setAngvel({x: 0, y: 0, z: 0}, wakeup);
}
export function _physicsRBDResetForces(object: Object3D, wakeup: boolean) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.resetForces(wakeup);
}
export function _physicsRBDResetTorques(object: Object3D, wakeup: boolean) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
body.resetTorques(wakeup);
}
export function _setPhysicsRBDPosition(object: Object3D, targetPosition: Vector3, lerp: number) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
const translateFunc = body.isKinematic()
? body.setNextKinematicTranslation.bind(body)
: body.setTranslation.bind(body);
if (lerp < 1) {
const rbdPosition = body.translation();
currentPos.set(rbdPosition.x, rbdPosition.y, rbdPosition.z);
newPos.copy(targetPosition);
currentPos.lerp(newPos, lerp);
translateFunc(currentPos, true);
} else {
translateFunc(targetPosition, true);
}
touchRBDProperty(object, RBDProperty.POSITION);
}
export function _setPhysicsRBDRotation(object: Object3D, targetQuaternion: Quaternion, lerp: number) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
const rotateFunc = body.isKinematic() ? body.setNextKinematicRotation.bind(body) : body.setRotation.bind(body);
if (lerp < 1) {
const rbdRotation = body.rotation();
currentQuaternion.set(rbdRotation.x, rbdRotation.y, rbdRotation.z, rbdRotation.w);
newQuaternion.copy(currentQuaternion);
currentQuaternion.slerp(newQuaternion, lerp);
rotateFunc(currentQuaternion, true);
} else {
rotateFunc(targetQuaternion, true);
}
touchRBDProperty(object, RBDProperty.ROTATION);
}
export function _setPhysicsRBDLinearVelocity(object: Object3D, targetVelocity: Vector3, lerp: number) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
if (lerp < 1) {
const rbdLinearVelocity = body.linvel();
currentLinearVelocity.set(rbdLinearVelocity.x, rbdLinearVelocity.y, rbdLinearVelocity.z);
newLinearVelocity.copy(targetVelocity);
currentLinearVelocity.lerp(newPos, lerp);
body.setLinvel(currentLinearVelocity, true);
} else {
body.setLinvel(targetVelocity, true);
}
touchRBDProperty(object, RBDProperty.LINEAR_VELOCITY);
}
export function _setPhysicsRBDAngularVelocity(object: Object3D, targetVelocity: Vector3, lerp: number) {
const body = _getRBDFromObject(object);
if (!body) {
console.warn('no rbd found');
return;
}
if (lerp < 1) {
const rbdAngularVelocity = body.angvel();
currentAngularVelocity.set(rbdAngularVelocity.x, rbdAngularVelocity.y, rbdAngularVelocity.z);
newAngularVelocity.copy(targetVelocity);
currentAngularVelocity.lerp(newPos, lerp);
body.setAngvel(currentAngularVelocity, true);
} else {
body.setAngvel(targetVelocity, true);
}
touchRBDProperty(object, RBDProperty.ANGULAR_VELOCITY);
}
function PhysicsRBDTypeToRigidBodyType(type: PhysicsRBDType) {
const RigidBodyType = CorePhysicsLoaded()?.RigidBodyType;
if (!RigidBodyType) {
return;
}
switch (type) {
case PhysicsRBDType.FIXED: {
return RigidBodyType.Fixed;
}
case PhysicsRBDType.DYNAMIC: {
return RigidBodyType.Dynamic;
}
case PhysicsRBDType.KINEMATIC_POS: {
return RigidBodyType.KinematicPositionBased;
}
case PhysicsRBDType.KINEMATIC_VEL: {
return RigidBodyType.KinematicVelocityBased;
}
}
TypeAssert.unreachable(type);
}
function PhysicsRBDCollider(
PhysicsLib: PhysicsLib,
colliderType: PhysicsRBDColliderType,
object: Object3D
): ColliderDesc | null | undefined {
switch (colliderType) {
case PhysicsRBDColliderType.CAPSULE: {
return createPhysicsCapsule(PhysicsLib, object);
}
case PhysicsRBDColliderType.CUBOID: {
return createPhysicsCuboid(PhysicsLib, object);
}
case PhysicsRBDColliderType.CONE: {
return createPhysicsCone(PhysicsLib, object);
}
case PhysicsRBDColliderType.CONVEX_HULL: {
return createPhysicsConvexHull(PhysicsLib, object);
}
case PhysicsRBDColliderType.TRIMESH: {
return createPhysicsTriMesh(PhysicsLib, object);
}
case PhysicsRBDColliderType.CYLINDER: {
return createPhysicsCylinder(PhysicsLib, object);
}
case PhysicsRBDColliderType.SPHERE: {
return createPhysicsSphere(PhysicsLib, object);
}
case PhysicsRBDColliderType.HEIGHT_FIELD: {
return createPhysicsHeightField(PhysicsLib, object);
}
}
TypeAssert.unreachable(colliderType);
}