js/Mobilizing/renderer/3D/three/physics/PhysicsEngine.js
import Component from "../../../../core/Component";
import Time from "../../../../time/Time";
import Vector3 from "../types/Vector3";
export default class PhysicsEngine extends Component {
/**
* @param {Object} params Parameters object, given by the constructor.
* @param {Number} params.iterations iterations of the physic engine when constrains are calculated
*/
constructor({
iterations = 5,
} = {}) {
super(...arguments);
this.iterations = iterations;
this.joints = [];
this.bodies = [];
this.G = 6.6742 * Math.pow(10, -11); //gravitationnal constant
this.ke = 8.99 * Math.pow(10, 9); //coulomb's constant
this.relaxationCoeff = 1;
this._time = new Time();
this.chain(this._time);
}
/**
* Set the number of iteration made by the physic engine
* @param {Number} val
*/
setIterations(val) {
this.iterations = val;
}
/**
* Initialization method
*/
setup() {
super.setup();
this._time.on();
}
update() {
if (this.active) {
let dt = this._time.delta;
//
if (dt === 0) {
return;
}
if (dt > 1) {
dt = 1;
}
//apply forces / acc / vel / pos
for (let i = 0; i < this.bodies.length; ++i) {
const body = this.bodies[i];
body.forces = new Vector3(0, 0, 0);
}
//springs
for (let j = 0; j < this.joints.length; ++j) {
const joint = this.joints[j];
if (!joint.enabled) {
continue; //ignore this joint
}
if (joint.type === "spring") {
const p1 = joint.body1.position;
const p2 = joint.body2.position;
const x = p2.x - p1.x;
const y = p2.y - p1.y;
const z = p2.z - p1.z;
const distance = Math.sqrt(x * x + y * y + z * z);
//compute spring force
const force = (distance - joint.distance) * joint.k;
joint.body1.forces.x += x * force;
joint.body1.forces.y += y * force;
joint.body1.forces.z += z * force;
joint.body2.forces.x -= x * force;
joint.body2.forces.y -= y * force;
joint.body2.forces.z -= z * force;
//damping force
const sx = joint.body2.velocity.x - joint.body1.velocity.x;
const sy = joint.body2.velocity.y - joint.body1.velocity.y;
const sz = joint.body2.velocity.z - joint.body1.velocity.z;
joint.body1.forces.x += sx * joint.damping;
joint.body1.forces.y += sy * joint.damping;
joint.body1.forces.z += sz * joint.damping;
joint.body2.forces.x -= sx * joint.damping;
joint.body2.forces.y -= sy * joint.damping;
joint.body2.forces.z -= sz * joint.damping;
}
else if (joint.type === "gravity") {
const p1 = joint.body1.position;
const p2 = joint.body2.position;
let x = p2.x - p1.x;
let y = p2.y - p1.y;
let z = p2.z - p1.z;
const distance = Math.sqrt(x * x + y * y + z * z);
//normalize
x /= distance;
y /= distance;
z /= distance;
//compute gravity force
const mass = joint.body1.mass + joint.body2.mass;
const force = this.G * mass / (distance * distance);
joint.body1.forces.x += x * force;
joint.body1.forces.y += y * force;
joint.body1.forces.z += z * force;
joint.body2.forces.x -= x * force;
joint.body2.forces.y -= y * force;
joint.body2.forces.z -= z * force;
}
else if (joint.type === "electrostatics") {
const p1 = joint.body1.position;
const p2 = joint.body2.position;
let x = p2.x - p1.x;
let y = p2.y - p1.y;
let z = p2.z - p1.z;
const distance = Math.sqrt(x * x + y * y + z * z);
//normalize
x /= distance;
y /= distance;
z /= distance;
//compute electrostatics force
const force = this.ke * joint.charge1 * joint.charge2 / (distance * distance);
joint.body1.forces.x -= x * force;
joint.body1.forces.y -= y * force;
joint.body1.forces.z -= z * force;
joint.body2.forces.x += x * force;
joint.body2.forces.y += y * force;
joint.body2.forces.z += z * force;
}
}
for (let i = 0; i < this.bodies.length; ++i) {
const body = this.bodies[i];
//quasi Verlet
if (body.speed !== undefined) {
body.velocity = new Vector3(body.speed.x, body.speed.y, body.speed.z);
body.speed = undefined;
}
else {
body.velocity = new Vector3(body.position.x - body.previousposition.x, body.position.y - body.previousposition.y, body.position.z - body.previousposition.z);
body.velocity.x /= dt;
body.velocity.y /= dt;
body.velocity.z /= dt;
}
body.previousposition = new Vector3(body.position.x, body.position.y, body.position.z);
//apply gravity (@FIXME, not accurate !!!)
if (this.globalgravity !== undefined) {
body.forces.x += this.globalgravity.x * body.mass;
body.forces.y += this.globalgravity.y * body.mass;
body.forces.z += this.globalgravity.z * body.mass;
}
if (this.globalwind !== undefined) {
body.forces.x += this.globalwind.x;
body.forces.y += this.globalwind.y;
body.forces.z += this.globalwind.z;
}
//acc
const acc = new Vector3(0, 0, 0);
acc.x = body.forces.x / body.mass;
acc.y = body.forces.y / body.mass;
acc.z = body.forces.z / body.mass;
//console.log("body acc", acc);
//speed
body.velocity.x += acc.x * dt;
body.velocity.y += acc.y * dt;
body.velocity.z += acc.z * dt;
//pos
body.position.x += body.velocity.x * dt;
body.position.y += body.velocity.y * dt;
body.position.z += body.velocity.z * dt;
//console.log("body position", body.position);
}
//satisfy constraints
for (let i = 0; i < this.iterations; ++i) {
for (let j = 0; j < this.joints.length; ++j) {
const joint = this.joints[j];
if (!joint.enabled) {
continue; //ignore this joint
}
if (joint.type === "stick" || joint.type === "string") {
const p1 = joint.body1.position;
const p2 = joint.body2.position;
const x = p2.x - p1.x;
const y = p2.y - p1.y;
const z = p2.z - p1.z;
const distance = Math.sqrt(x * x + y * y + z * z);
//satisfy distance constraint
const err = (distance - joint.distance) / distance * 1;
//console.log("err=" + err);
if (joint.type === "stick" || (joint.type === "string" && distance > joint.distance)) {
let err1 = err / 2;
let err2 = err / 2;
if (joint.body1.fixed) {
err1 = 0;
err2 = err;
}
else if (joint.body2.fixed) {
err1 = err;
err2 = 0;
}
p1.x = p1.x + x * err1 * this.relaxationCoeff;
p2.x = p2.x - x * err2 * this.relaxationCoeff;
p1.y = p1.y + y * err1 * this.relaxationCoeff;
p2.y = p2.y - y * err2 * this.relaxationCoeff;
p1.z = p1.z + z * err1 * this.relaxationCoeff;
p2.z = p2.z - z * err2 * this.relaxationCoeff;
}
}
else if (joint.type === "fixed") {
//console.log("fixed = " , joint.position);
joint.body1.position = new Vector3(joint.position.x, joint.position.y, joint.position.z);
joint.body1.velocity = new Vector3(0, 0, 0);
}
}
}
//apply new positions
for (let i = 0; i < this.bodies.length; ++i) {
const body = this.bodies[i];
if (body.object !== undefined) {
//console.log("new pos=" , body.position);
body.object.transform.setLocalPosition(body.position);
}
}
}
}
/**
* addBody
* @param {Vector3} position
* @param {Number} mass
* @param {Mesh} object the Mesh to attach this physics body to
* @return {Body} the added physic body
*/
addBody(position, mass, object) {
const body = {};
body.position = new Vector3(position.x, position.y, position.z);
//console.log("body position : ", body.position);
body.previousposition = new Vector3(position.x, position.y, position.z);
body.mass = mass;
body.object = object;
body.velocity = new Vector3(0, 0, 0);
this.bodies.push(body);
return body;
}
/**
* addFixedJoint
* @param {Mesh} body
* @param {Vector3} position
* @return {Body} the added joint body
*/
addFixedJoint(body, position) {
const joint = {};
joint.body1 = body;
joint.body1.fixed = true;
joint.type = "fixed";
joint.position = new Vector3(position.x, position.y, position.z);
joint.enabled = true;
this.joints.push(joint);
return joint;
}
/**
* addStickJoint
* @param {Mesh} body1
* @param {Mesh} body2
* @param {Number} distance
* @return {Body} the added joint body
*/
addStickJoint(body1, body2, distance) {
const joint = {};
joint.body1 = body1;
joint.body2 = body2;
joint.type = "stick";
joint.distance = distance;
joint.enabled = true;
this.joints.push(joint);
return joint;
}
/**
* addSpringJoint
* @param {Mesh} body1
* @param {Mesh} body2
* @param {Number} distance
* @param {Number} k
* @param {Number} damping
* @return {Body} the added joint body
*/
addSpringJoint(body1, body2, distance, k, damping) {
const joint = {};
joint.body1 = body1;
joint.body2 = body2;
joint.type = "spring";
joint.distance = distance;
joint.k = k;
joint.damping = damping;
joint.enabled = true;
this.joints.push(joint);
return joint;
}
/**
* addStringJoint
* @param {Mesh} body1
* @param {Mesh} body2
* @param {Number} distance
* @return {Body} the added joint body
*/
addStringJoint(body1, body2, distance) {
const joint = {};
joint.body1 = body1;
joint.body2 = body2;
joint.type = "string";
joint.distance = distance;
joint.enabled = true;
this.joints.push(joint);
return joint;
}
/**
* addGravityJoint
* @param {Mesh} body1
* @param {Mesh} body2
* @return {Body} the added joint body
*/
addGravityJoint(body1, body2) {
const joint = {};
joint.body1 = body1;
joint.body2 = body2;
joint.type = "gravity";
joint.enabled = true;
this.joints.push(joint);
return joint;
}
/**
* addElectrostaticsJoint
* @param {Mesh} body1
* @param {Mesh} body2
* @param {Number} charge1
* @param {Number} charge2
* @return {Body} the added joint body
*/
addElectrostaticsJoint(body1, body2, charge1, charge2) {
const joint = {};
joint.body1 = body1;
joint.body2 = body2;
joint.type = "electrostatics";
joint.charge1 = charge1;
joint.charge2 = charge2;
joint.enabled = true;
this.joints.push(joint);
return joint;
}
/**
* setGlobalGravity
* @param {Vector3} vector
*/
setGlobalGravity(vector) {
this.globalgravity = vector;
}
/**
* setGlobalWind
* @param {Vector3} vector
*/
setGlobalWind(vector) {
this.globalwind = vector;
}
}