@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
451 lines • 19.2 kB
JavaScript
import { PhysicsImpostor } from "../physicsImpostor.js";
import { PhysicsJoint } from "../physicsJoint.js";
import { Vector3, Quaternion } from "../../../Maths/math.vector.js";
import { Logger } from "../../../Misc/logger.js";
import { PhysicsRaycastResult } from "../../physicsRaycastResult.js";
import { Epsilon } from "../../../Maths/math.constants.js";
/** @internal */
export class OimoJSPlugin {
constructor(_useDeltaForWorldStep = true, iterations, oimoInjection = OIMO) {
this._useDeltaForWorldStep = _useDeltaForWorldStep;
this.name = "OimoJSPlugin";
this._fixedTimeStep = 1 / 60;
this._tmpImpostorsArray = [];
this._tmpPositionVector = Vector3.Zero();
this.BJSOIMO = oimoInjection;
this.world = new this.BJSOIMO.World({
iterations: iterations,
});
this.world.clear();
this._raycastResult = new PhysicsRaycastResult();
}
/**
*
* @returns plugin version
*/
getPluginVersion() {
return 1;
}
setGravity(gravity) {
this.world.gravity.set(gravity.x, gravity.y, gravity.z);
}
setTimeStep(timeStep) {
this.world.timeStep = timeStep;
}
getTimeStep() {
return this.world.timeStep;
}
executeStep(delta, impostors) {
for (const impostor of impostors) {
impostor.beforeStep();
}
this.world.timeStep = this._useDeltaForWorldStep ? delta : this._fixedTimeStep;
this.world.step();
for (const impostor of impostors) {
impostor.afterStep();
//update the ordered impostors array
this._tmpImpostorsArray[impostor.uniqueId] = impostor;
}
//check for collisions
let contact = this.world.contacts;
while (contact !== null) {
if (contact.touching && !contact.body1.sleeping && !contact.body2.sleeping) {
contact = contact.next;
continue;
}
//is this body colliding with any other? get the impostor
const mainImpostor = this._tmpImpostorsArray[+contact.body1.name];
const collidingImpostor = this._tmpImpostorsArray[+contact.body2.name];
if (!mainImpostor || !collidingImpostor) {
contact = contact.next;
continue;
}
mainImpostor.onCollide({ body: collidingImpostor.physicsBody, point: null, distance: 0, impulse: 0, normal: null });
collidingImpostor.onCollide({ body: mainImpostor.physicsBody, point: null, distance: 0, impulse: 0, normal: null });
contact = contact.next;
}
}
applyImpulse(impostor, force, contactPoint) {
const mass = impostor.physicsBody.mass;
impostor.physicsBody.applyImpulse(contactPoint.scale(this.world.invScale), force.scale(this.world.invScale * mass));
}
applyForce(impostor, force, contactPoint) {
Logger.Warn("Oimo doesn't support applying force. Using impulse instead.");
this.applyImpulse(impostor, force, contactPoint);
}
generatePhysicsBody(impostor) {
//parent-child relationship. Does this impostor has a parent impostor?
if (impostor.parent) {
if (impostor.physicsBody) {
this.removePhysicsBody(impostor);
//TODO is that needed?
impostor.forceUpdate();
}
return;
}
if (impostor.isBodyInitRequired()) {
const bodyConfig = {
name: impostor.uniqueId,
//Oimo must have mass, also for static objects.
config: [impostor.getParam("mass") || 0.001, impostor.getParam("friction"), impostor.getParam("restitution")],
size: [],
type: [],
pos: [],
posShape: [],
rot: [],
rotShape: [],
move: impostor.getParam("mass") !== 0,
density: impostor.getParam("mass"),
friction: impostor.getParam("friction"),
restitution: impostor.getParam("restitution"),
//Supporting older versions of Oimo
world: this.world,
};
const impostors = [impostor];
const addToArray = (parent) => {
if (!parent.getChildMeshes) {
return;
}
const meshes = parent.getChildMeshes();
for (const m of meshes) {
if (m.physicsImpostor) {
impostors.push(m.physicsImpostor);
//m.physicsImpostor._init();
}
}
};
addToArray(impostor.object);
const checkWithEpsilon = (value) => {
return Math.max(value, Epsilon);
};
const globalQuaternion = new Quaternion();
for (const i of impostors) {
if (!i.object.rotationQuaternion) {
return;
}
//get the correct bounding box
const oldQuaternion = i.object.rotationQuaternion;
globalQuaternion.copyFrom(oldQuaternion);
i.object.rotationQuaternion.set(0, 0, 0, 1);
i.object.computeWorldMatrix(true);
const rot = globalQuaternion.toEulerAngles();
const impostorExtents = i.getObjectExtents();
// eslint-disable-next-line no-loss-of-precision
const radToDeg = 57.295779513082320876;
if (i === impostor) {
const center = impostor.getObjectCenter();
impostor.object.getAbsolutePivotPoint().subtractToRef(center, this._tmpPositionVector);
this._tmpPositionVector.divideInPlace(impostor.object.scaling);
//Can also use Array.prototype.push.apply
bodyConfig.pos.push(center.x);
bodyConfig.pos.push(center.y);
bodyConfig.pos.push(center.z);
bodyConfig.posShape.push(0, 0, 0);
bodyConfig.rotShape.push(0, 0, 0);
}
else {
const localPosition = i.object.position.clone();
bodyConfig.posShape.push(localPosition.x);
bodyConfig.posShape.push(localPosition.y);
bodyConfig.posShape.push(localPosition.z);
// bodyConfig.pos.push(0, 0, 0);
bodyConfig.rotShape.push(rot.x * radToDeg, rot.y * radToDeg, rot.z * radToDeg);
}
i.object.rotationQuaternion.copyFrom(globalQuaternion);
// register mesh
switch (i.type) {
case PhysicsImpostor.ParticleImpostor:
Logger.Warn("No Particle support in OIMO.js. using SphereImpostor instead");
// eslint-disable-next-line no-fallthrough
case PhysicsImpostor.SphereImpostor: {
const radiusX = impostorExtents.x;
const radiusY = impostorExtents.y;
const radiusZ = impostorExtents.z;
const size = Math.max(checkWithEpsilon(radiusX), checkWithEpsilon(radiusY), checkWithEpsilon(radiusZ)) / 2;
bodyConfig.type.push("sphere");
//due to the way oimo works with compounds, add 3 times
bodyConfig.size.push(size);
bodyConfig.size.push(size);
bodyConfig.size.push(size);
break;
}
case PhysicsImpostor.CylinderImpostor: {
const sizeX = checkWithEpsilon(impostorExtents.x) / 2;
const sizeY = checkWithEpsilon(impostorExtents.y);
bodyConfig.type.push("cylinder");
bodyConfig.size.push(sizeX);
bodyConfig.size.push(sizeY);
//due to the way oimo works with compounds, add one more value.
bodyConfig.size.push(sizeY);
break;
}
case PhysicsImpostor.PlaneImpostor:
case PhysicsImpostor.BoxImpostor:
default: {
const sizeX = checkWithEpsilon(impostorExtents.x);
const sizeY = checkWithEpsilon(impostorExtents.y);
const sizeZ = checkWithEpsilon(impostorExtents.z);
bodyConfig.type.push("box");
//if (i === impostor) {
bodyConfig.size.push(sizeX);
bodyConfig.size.push(sizeY);
bodyConfig.size.push(sizeZ);
//} else {
// bodyConfig.size.push(0,0,0);
//}
break;
}
}
//actually not needed, but hey...
i.object.rotationQuaternion = oldQuaternion;
}
impostor.physicsBody = this.world.add(bodyConfig);
// set the quaternion, ignoring the previously defined (euler) rotation
impostor.physicsBody.resetQuaternion(globalQuaternion);
// update with delta 0, so the body will receive the new rotation.
impostor.physicsBody.updatePosition(0);
}
else {
this._tmpPositionVector.copyFromFloats(0, 0, 0);
}
impostor.setDeltaPosition(this._tmpPositionVector);
//this._tmpPositionVector.addInPlace(impostor.mesh.getBoundingInfo().boundingBox.center);
//this.setPhysicsBodyTransformation(impostor, this._tmpPositionVector, impostor.mesh.rotationQuaternion);
}
removePhysicsBody(impostor) {
//impostor.physicsBody.dispose();
this.world.removeRigidBody(impostor.physicsBody);
}
generateJoint(impostorJoint) {
const mainBody = impostorJoint.mainImpostor.physicsBody;
const connectedBody = impostorJoint.connectedImpostor.physicsBody;
if (!mainBody || !connectedBody) {
return;
}
const jointData = impostorJoint.joint.jointData;
const options = jointData.nativeParams || {};
let type;
const nativeJointData = {
body1: mainBody,
body2: connectedBody,
axe1: options.axe1 || (jointData.mainAxis ? jointData.mainAxis.asArray() : null),
axe2: options.axe2 || (jointData.connectedAxis ? jointData.connectedAxis.asArray() : null),
pos1: options.pos1 || (jointData.mainPivot ? jointData.mainPivot.asArray() : null),
pos2: options.pos2 || (jointData.connectedPivot ? jointData.connectedPivot.asArray() : null),
min: options.min,
max: options.max,
collision: options.collision || jointData.collision,
spring: options.spring,
//supporting older version of Oimo
world: this.world,
};
switch (impostorJoint.joint.type) {
case PhysicsJoint.BallAndSocketJoint:
type = "jointBall";
break;
case PhysicsJoint.SpringJoint: {
Logger.Warn("OIMO.js doesn't support Spring Constraint. Simulating using DistanceJoint instead");
const springData = jointData;
nativeJointData.min = springData.length || nativeJointData.min;
//Max should also be set, just make sure it is at least min
nativeJointData.max = Math.max(nativeJointData.min, nativeJointData.max);
}
// eslint-disable-next-line no-fallthrough
case PhysicsJoint.DistanceJoint:
type = "jointDistance";
nativeJointData.max = jointData.maxDistance;
break;
case PhysicsJoint.PrismaticJoint:
type = "jointPrisme";
break;
case PhysicsJoint.SliderJoint:
type = "jointSlide";
break;
case PhysicsJoint.WheelJoint:
type = "jointWheel";
break;
case PhysicsJoint.HingeJoint:
default:
type = "jointHinge";
break;
}
nativeJointData.type = type;
impostorJoint.joint.physicsJoint = this.world.add(nativeJointData);
}
removeJoint(impostorJoint) {
//Bug in Oimo prevents us from disposing a joint in the playground
//joint.joint.physicsJoint.dispose();
//So we will bruteforce it!
try {
this.world.removeJoint(impostorJoint.joint.physicsJoint);
}
catch (e) {
Logger.Warn(e);
}
}
isSupported() {
return this.BJSOIMO !== undefined;
}
setTransformationFromPhysicsBody(impostor) {
if (!impostor.physicsBody.sleeping) {
if (impostor.physicsBody.shapes.next) {
let parent = impostor.physicsBody.shapes;
while (parent.next) {
parent = parent.next;
}
impostor.object.position.set(parent.position.x, parent.position.y, parent.position.z);
}
else {
const pos = impostor.physicsBody.getPosition();
impostor.object.position.set(pos.x, pos.y, pos.z);
}
if (impostor.object.rotationQuaternion) {
const quat = impostor.physicsBody.getQuaternion();
impostor.object.rotationQuaternion.set(quat.x, quat.y, quat.z, quat.w);
}
}
}
setPhysicsBodyTransformation(impostor, newPosition, newRotation) {
const body = impostor.physicsBody;
// disable bidirectional for compound meshes
if (impostor.physicsBody.shapes.next) {
return;
}
body.position.set(newPosition.x, newPosition.y, newPosition.z);
body.orientation.set(newRotation.x, newRotation.y, newRotation.z, newRotation.w);
body.syncShapes();
body.awake();
}
/*private _getLastShape(body: any): any {
var lastShape = body.shapes;
while (lastShape.next) {
lastShape = lastShape.next;
}
return lastShape;
}*/
setLinearVelocity(impostor, velocity) {
impostor.physicsBody.linearVelocity.set(velocity.x, velocity.y, velocity.z);
}
setAngularVelocity(impostor, velocity) {
impostor.physicsBody.angularVelocity.set(velocity.x, velocity.y, velocity.z);
}
getLinearVelocity(impostor) {
const v = impostor.physicsBody.linearVelocity;
if (!v) {
return null;
}
return new Vector3(v.x, v.y, v.z);
}
getAngularVelocity(impostor) {
const v = impostor.physicsBody.angularVelocity;
if (!v) {
return null;
}
return new Vector3(v.x, v.y, v.z);
}
setBodyMass(impostor, mass) {
const staticBody = mass === 0;
//this will actually set the body's density and not its mass.
//But this is how oimo treats the mass variable.
impostor.physicsBody.shapes.density = staticBody ? 1 : mass;
impostor.physicsBody.setupMass(staticBody ? 0x2 : 0x1);
}
getBodyMass(impostor) {
return impostor.physicsBody.shapes.density;
}
getBodyFriction(impostor) {
return impostor.physicsBody.shapes.friction;
}
setBodyFriction(impostor, friction) {
impostor.physicsBody.shapes.friction = friction;
}
getBodyRestitution(impostor) {
return impostor.physicsBody.shapes.restitution;
}
setBodyRestitution(impostor, restitution) {
impostor.physicsBody.shapes.restitution = restitution;
}
sleepBody(impostor) {
impostor.physicsBody.sleep();
}
wakeUpBody(impostor) {
impostor.physicsBody.awake();
}
updateDistanceJoint(joint, maxDistance, minDistance) {
joint.physicsJoint.limitMotor.upperLimit = maxDistance;
if (minDistance !== void 0) {
joint.physicsJoint.limitMotor.lowerLimit = minDistance;
}
}
setMotor(joint, speed, force, motorIndex) {
if (force !== undefined) {
Logger.Warn("OimoJS plugin currently has unexpected behavior when using setMotor with force parameter");
}
else {
force = 1e6;
}
speed *= -1;
//TODO separate rotational and transational motors.
const motor = motorIndex
? joint.physicsJoint.rotationalLimitMotor2
: joint.physicsJoint.rotationalLimitMotor1 || joint.physicsJoint.rotationalLimitMotor || joint.physicsJoint.limitMotor;
if (motor) {
motor.setMotor(speed, force);
}
}
setLimit(joint, upperLimit, lowerLimit, motorIndex) {
//TODO separate rotational and transational motors.
const motor = motorIndex
? joint.physicsJoint.rotationalLimitMotor2
: joint.physicsJoint.rotationalLimitMotor1 || joint.physicsJoint.rotationalLimitMotor || joint.physicsJoint.limitMotor;
if (motor) {
motor.setLimit(upperLimit, lowerLimit === void 0 ? -upperLimit : lowerLimit);
}
}
syncMeshWithImpostor(mesh, impostor) {
const body = impostor.physicsBody;
mesh.position.x = body.position.x;
mesh.position.y = body.position.y;
mesh.position.z = body.position.z;
if (mesh.rotationQuaternion) {
mesh.rotationQuaternion.x = body.orientation.x;
mesh.rotationQuaternion.y = body.orientation.y;
mesh.rotationQuaternion.z = body.orientation.z;
mesh.rotationQuaternion.w = body.orientation.w;
}
}
getRadius(impostor) {
return impostor.physicsBody.shapes.radius;
}
getBoxSizeToRef(impostor, result) {
const shape = impostor.physicsBody.shapes;
result.x = shape.halfWidth * 2;
result.y = shape.halfHeight * 2;
result.z = shape.halfDepth * 2;
}
dispose() {
this.world.clear();
}
/**
* Does a raycast in the physics world
* @param from when should the ray start?
* @param to when should the ray end?
* @returns PhysicsRaycastResult
*/
raycast(from, to) {
Logger.Warn("raycast is not currently supported by the Oimo physics plugin");
this._raycastResult.reset(from, to);
return this._raycastResult;
}
/**
* Does a raycast in the physics world
* @param from when should the ray start?
* @param to when should the ray end?
* @param result resulting PhysicsRaycastResult
*/
raycastToRef(from, to, result) {
Logger.Warn("raycast is not currently supported by the Oimo physics plugin");
result.reset(from, to);
}
}
//# sourceMappingURL=oimoJSPlugin.js.map