UNPKG

cyclone-physics

Version:

Pure Javascript physics engine based on http://procyclone.com/

627 lines (588 loc) 22.5 kB
/** * forces */ elation.require(['physics.common'], function() { elation.extend("physics.forces.gravity", function(body, args) { this.type = 'gravity'; this.others = []; this.accel = (args instanceof THREE.Vector3 ? args : new THREE.Vector3()); this.timescale = args.timescale || 1; this.gravsum = new THREE.Vector3(); let _tmpvec = new THREE.Vector3(); this.apply = function() { if (this.others.length > 0) { this.gravsum.copy(this.getForceAtPoint(body.position, body.mass)); } else { this.gravsum.copy(this.accel).multiplyScalar(body.mass); } //console.log("Gravity force: " + [this.gravsum.x, this.gravsum.y, this.gravsum.z] + " m/s^2"); //return [this.gravsum, false]; body.applyForce(this.gravsum); elation.events.fire({type: 'physics_force_apply', element: this}); } this.update = function(updateargs) { if (updateargs instanceof THREE.Vector3) { //this.gravsum.copy(updateargs).multiplyScalar(body.mass); this.accel.copy(updateargs); } else { if (typeof updateargs.timescale != 'undefined') { this.timescale = updateargs.timescale; } if (typeof updateargs.others != 'undefined') { this.others = updateargs.others; } } elation.events.fire({type: 'physics_force_update', element: this}); } this.getOrbitalVelocity = function(point) { _tmpvec.copy(point).normalize().cross(new THREE.Vector3(0,1,0)).normalize(); //foo.multiplyScalar(2 * Math.PI * this.position.length()); var m = this.others[0].mass; _tmpvec.multiplyScalar(Math.sqrt((m * m * 6.67384e-11) / ((m + body.mass) * point.length()))); return _tmpvec; } this.getForceAtPoint = (function() { var _gravsum = new THREE.Vector3(); return function(point, mass) { if (typeof mass == 'undefined') { mass = body.mass; } if (this.others.length > 0) { _gravsum.set(0,0,0); for (var i = 0; i < this.others.length; i++) { // Calculate gravity force for all objects in the set, excluding ourselves if (this.others[i] && this.others[i] !== body) { _tmpvec.subVectors(this.others[i].position, point); var rsq = _tmpvec.lengthSq(); var r = Math.sqrt(rsq); var a = (6.67384e-11 * mass * this.others[i].mass) / rsq; _gravsum.x += a * _tmpvec.x / r; _gravsum.y += a * _tmpvec.y / r; _gravsum.z += a * _tmpvec.z / r; } } } else { _gravsum.copy(this.accel); } return _gravsum; } })(); this.sleepstate = function() { return (this.gravsum.lengthSq() <= 1e-6); } this.toJSON = function() { return { type: this.type, others: false, // FIXME - should serialize other objects based on their body ids accel: {x: this.accel.x, y: this.accel.y, z: this.accel.z}, timescale: this.timescale, }; } this.update(args); }); elation.extend("physics.forces.static", function(body, args) { this.type = 'static'; this.force = args.force || (args instanceof THREE.Vector3 ? args : new THREE.Vector3()); this.point = args.point || false; this.absolute = elation.utils.any(args.absolute, false); this.relative = !this.absolute; if (!(this.force instanceof THREE.Vector3)) this.force = new THREE.Vector3().copy(this.force); if (this.point && !(this.point instanceof THREE.Vector3)) this.point = new THREE.Vector3().copy(this.point); let _tmpvec = new THREE.Vector3(); let _tmpvec2 = new THREE.Vector3(); this.apply = function() { _tmpvec.copy(this.force); if (this.point) { _tmpvec2.copy(this.point); //var torque = this.point.clone().crossSelf(this.accel); //body.momentInverse.multiplyVector3(torque); //return [_tmpvec.clone(), torque]; body.applyForceAtPoint(_tmpvec.clone(), _tmpvec2.clone(), !this.absolute); } else { body.applyForce(_tmpvec, !this.absolute); } elation.events.fire({type: 'physics_force_apply', element: this}); } this.update = function(updateargs) { let changed = false; if (updateargs instanceof THREE.Vector3) { if (!this.force.equals(updateargs)) { this.force.copy(updateargs); changed = true; } } else { if (!this.force.equals(updateargs.force)) { this.force.copy(updateargs.force); changed = true; } if (updateargs.point instanceof THREE.Vector3 && !this.point.equals(updateargs.point)) { this.point = updateargs.point; changed = true; } this.relative = updateargs.relative; } //if (changed) { elation.events.fire({type: 'physics_force_update', element: this}); //} } this.sleepstate = function() { return (this.force.lengthSq() <= 1e-6); } this.toJSON = function() { return { type: this.type, force: {x: this.force.x, y: this.force.y, z: this.force.z}, point: (this.point ? {x: this.point.x, y: this.point.y, z: this.point.z} : false), absolute: this.absolute, relative: this.relative, }; } }); elation.extend("physics.forces.friction", function(body, args) { this.type = 'friction'; this.friction = args.friction || args; let _force = new THREE.Vector3(); this.apply = function() { _force.set(0,0,0); if (this.friction > 0) { var vsq = body.velocity.lengthSq(); _force.copy(body.velocity).multiplyScalar(-1 * this.friction * body.mass); } body.applyForce(_force); elation.events.fire({type: 'physics_force_apply', element: this}); } this.update = function(updateargs) { let friction = elation.utils.any(updateargs.friction, updateargs); if (friction != this.friction) { this.friction = friction; elation.events.fire({type: 'physics_force_update', element: this}); } } this.computeVelocityForDistance = function(dist, friction, mass) { var a = friction; //var v0 = (dist - .5*a*time^2) / time; var v0 = Math.sqrt((a * dist) / (.5 * mass)); return v0; } this.sleepstate = function() { return !(this.friction > 1e-6 && body.velocity.lengthSq() > 1e-6); } this.toJSON = function() { return { type: this.type, friction: this.friction, }; } }); elation.extend("physics.forces.anisotropicfriction", function(body, args) { this.type = 'anisotropicfriction'; this.friction = args; let _force = new THREE.Vector3(); this.apply = function() { _force.set(0,0,0); if (this.friction) { var relvel = body.worldToLocalDir(body.velocity.clone()); _force.x = relvel.x * -1 * this.friction.x * body.mass; _force.y = relvel.y * -1 * this.friction.y * body.mass; _force.z = relvel.z * -1 * this.friction.z * body.mass; body.localToWorldDir(_force); } body.applyForce(_force); elation.events.fire({type: 'physics_force_apply', element: this}); } this.update = function(updateargs) { this.friction = updateargs; elation.events.fire({type: 'physics_force_update', element: this}); } this.sleepstate = function() { return !(this.friction.lengthSq() > 1e-6 && body.velocity.lengthSq() > 1e-6); } this.toJSON = function() { return { type: this.type, friction: this.friction, }; } }); elation.extend("physics.forces.drag", function(body, args) { this.type = 'drag'; this.drag = args; let _force = new THREE.Vector3(); this.sleeping = false; this.apply = function() { _force.set(0,0,0); if (this.drag > 0) { var v = body.velocity.length(); _force.copy(body.velocity).multiplyScalar(-.5*this.drag*v); } body.applyForce(_force); elation.events.fire({type: 'physics_force_apply', element: this}); } this.update = function(updateargs) { this.drag = updateargs; console.log('set drag to ', this.drag); } this.toJSON = function() { return { type: this.type, drag: this.drag, }; } this.update(args); }); /* elation.extend("physics.forces.aero", function(body, args) { this.type = 'aero'; if (!args) args = {}; this.body = body; this.tensor = args.tensor || new THREE.Matrix4(); this.position = args.position || new THREE.Vector3(); this.sleeping = false; let _tmpvec = new THREE.Vector3(); this.apply = function() { var force = this.getForceFromTensor(this.getTensor()); this.body.applyForceAtPoint(force, this.position, true); elation.events.fire({type: 'physics_force_apply', element: this}); } this.getForceFromTensor = function(tensor) { this.body.worldToLocalDir(_tmpvec.copy(this.body.velocity)).negate(); //var before = _tmpvec.clone(); this.getTensor().multiplyVector3(_tmpvec).multiplyScalar(.5); //console.log('parent: ' + VECDUMP(this.body.velocity) + 'before: ' + VECDUMP(before) + ', after: ' + VECDUMP(_tmpvec)); return _tmpvec; } this.getTensor = function() { return this.tensor; } this.updateTensor = function(tensor) { this.tensor.copy(tensor); } this.update = function(updateargs) { if (updateargs.tensor) { this.updateTensor(updateargs.tensor); } if (args.position) { if (args.position instanceof THREE.Vector3) { this.position.copy(args.position); } else { this.position.set(args.position[0], args.position[1], args.position[2]); } } elation.events.fire({type: 'physics_force_update', element: this}); } this.toJSON = function() { return { type: this.type, drag: this.drag, tensor: this.tensor, position: {x: this.position.x, y: this.position.y, z: this.position.z}, }; } }); elation.extend("physics.forces.aerocontrol", function(body, args) { this.type = 'aerocontrol'; this.body = body; this.tensor = args.tensor || new THREE.Matrix4(); this.tensor_min = args.tensor_min || new THREE.Matrix4(); this.tensor_max = args.tensor_max || new THREE.Matrix4(); this.position = args.position || new THREE.Vector3(); this.control = 0; this.sleeping = false; let _tmpmat = new THREE.Matrix4(); this.getTensor = function() { if (this.control <= -1) { return this.tensor_min; } else if (this.control >= 1) { return this.tensor_max; } else if (this.control < 0) { return this.interpolate(this.tensor_min, this.tensor, this.control+1); } else if (this.control > 0) { return this.interpolate(this.tensor, this.tensor_max, this.control); } return this.tensor; } this.interpolate = function(a, b, prop) { _tmpmat.set( a.n11 * (1 - prop) + b.n11 * prop, a.n12 * (1 - prop) + b.n12 * prop, a.n13 * (1 - prop) + b.n13 * prop, 0, a.n21 * (1 - prop) + b.n21 * prop, a.n22 * (1 - prop) + b.n22 * prop, a.n23 * (1 - prop) + b.n23 * prop, 0, a.n31 * (1 - prop) + b.n31 * prop, a.n32 * (1 - prop) + b.n32 * prop, a.n33 * (1 - prop) + b.n33 * prop, 0, 0, 0, 0, 1 ); return _tmpmat; } this.setControl = function(c) { this.control = c; } this.toJSON = function() { return { type: this.type, drag: this.drag, tensor: this.tensor, position: {x: this.position.x, y: this.position.y, z: this.position.z}, }; } }); elation.physics.forces.aerocontrol.prototype = new elation.physics.forces.aero(); */ elation.extend("physics.forces.buoyancy", function(body, args) { this.type = 'buoyancy'; this.body = body; this.density = args.density || 1000; // water = 1000 kg/m^3 this.volume = args.volume || 1; this.maxdepth = args.maxdepth || 10; this.waterheight = args.waterheight || 0; this.position = args.position || new THREE.Vector3(0,0,0); this.submerged = 0; this.force = new THREE.Vector3(); this.positionworld = new THREE.Vector3(); this.sleeping = false; this.apply = function() { var point = this.body.localToWorldPos(this.position.clone()); var depth = point.y - this.maxdepth / 2; if (depth >= this.waterheight) { this.submerged = 0; this.force.y = 0; } else { if (depth <= this.waterheight - this.maxdepth) { this.force.y = this.density * this.volume; this.submerged = 1; } else { //ratio = depth / (this.waterheight - this.maxdepth); //this.submerged = depth / (this.waterheight - this.maxdepth) / 2 + this.maxdepth; //this.submerged = -(depth - this.maxdepth - this.waterheight) / 2 * this.maxdepth; this.submerged = -(depth / (this.maxdepth)); //force.y = this.density * this.volume * -1 / ((depth - this.maxdepth - this.waterheight) / 2 * this.maxdepth); this.force.y = this.density * this.volume * this.submerged; } //this.force.y *= 0.0098; //force.y = this.density * this.volume * ratio * .0098; //this.body.applyForceAtPoint(this.force, this.position, false); this.positionworld.copy(this.position); this.body.applyForceAtPoint(this.force, this.body.localToWorldDir(this.positionworld), false); } elation.events.fire({type: 'physics_force_apply', element: this}); } this.update = function() { elation.events.fire({type: 'physics_force_update', element: this}); } this.toJSON = function() { return { type: this.type, density: this.density, volume: this.volume, maxdepth: this.maxdepth, waterheight: this.waterheight, position: {x: this.position.x, y: this.position.y, z: this.position.z}, }; } }); elation.extend("physics.forces.spring", function(body, args) { this.type = 'spring'; this.body = body; this.connectionpoint = args.connectionpoint || new THREE.Vector3(0,0,0); this.otherconnectionpoint = args.otherconnectionpoint || new THREE.Vector3(0,0,0); this.other = args.other || false; this.anchor = args.anchor || false; this.strength = elation.utils.any(args.strength, 1); this.midpoint = args.midpoint || false; this.restlength = elation.utils.any(args.restlength, 0); this.bungee = args.bungee || false; this.hard = args.hard || false; this.force = new THREE.Vector3(); var _tmpvec1 = new THREE.Vector3(); var _tmpvec2 = new THREE.Vector3(); this.apply = function() { if (this.disabled) return; var lws = this.body.localToWorldPos(_tmpvec1.copy(this.connectionpoint)); var ows = (this.other ? this.other.localToWorldPos(_tmpvec2.copy(this.otherconnectionpoint)) : this.anchor); this.body.worldToLocalDir(this.force.subVectors(lws, ows)); if (this.midpoint) { this.force.divideScalar(2); } //var magnitude = Math.abs(this.force.length() - this.restlength) * this.strength; var magnitude = this.force.length() + 1e-5; if (this.bungee && magnitude <= this.restlength) { this.force.set(0,0,0); } else if (this.hard && magnitude <= this.restlength) { this.force.set(0,0,0); } else { this.force.divideScalar(magnitude); magnitude = this.strength * (magnitude - this.restlength); this.force.multiplyScalar(-magnitude); this.force = this.body.worldToLocalDir(this.force); this.body.applyForceAtPoint(this.force, this.connectionpoint, true); if (this.other && this.other.mass) { this.other.applyForceAtPoint(this.force.multiplyScalar(-1), this.otherconnectionpoint, true); } } elation.events.fire({type: 'physics_force_apply', element: this}); } this.update = function(updateargs) { for (var k in updateargs) { this[k] = updateargs[k]; } elation.events.fire({type: 'physics_force_update', element: this}); } this.sleepstate = function() { var lws = this.body.localToWorldPos(_tmpvec1.copy(this.connectionpoint)); var ows = (this.other ? this.other.localToWorldPos(_tmpvec2.copy(this.otherconnectionpoint)) : this.anchor); return (lws.distanceToSquared(ows) <= 1e6); } this.toJSON = function() { return { type: this.type, connectionpoint: this.connectionpoint, otherconnectionpoint: this.otherconnectionpoint, other: this.other, anchor: this.anchor, strength: this.strength, midpoint: this.midpoint, restlength: this.restlength, bungee: this.bungee, hard: this.hard, }; } }); elation.extend("physics.forces.magnet", function(body, args) { this.type = 'magnet'; this.force = new THREE.Vector3(); this.anchor = args.anchor; this.strength = args.strength || 1; this.sleeping = false; this.apply = (function() { var tmpvec = new THREE.Vector3(); return function() { var mu = 1.256636e-6; var qm1 = 1, qm2 = this.strength; this.force.subVectors(body.position, this.anchor); var rsq = this.force.lengthSq(); var F = (mu * qm1 * qm2) / (4 * Math.PI * rsq); this.force.normalize().multiplyScalar(F); //console.log(this.force.toArray().toString()); body.applyForce(this.force); elation.events.fire({type: 'physics_force_apply', element: this}); } })(); this.update = function(updateargs) { if (updateargs.anchor) { this.anchor = updateargs.anchor; } if (updateargs.strength) { this.strength = updateargs.strength; } elation.events.fire({type: 'physics_force_update', element: this}); } this.toJSON = function() { return { type: this.type, anchor: this.anchor, strength: this.strength, }; } }); elation.extend("physics.forces.repel", function(body, args) { this.type = 'repel'; this.force = new THREE.Vector3(); this.anchor = args.anchor; this.strength = args.strength || 1; this.sleeping = false; this.apply = (function() { var tmpvec = new THREE.Vector3(); return function() { this.force.subVectors(body.position, this.anchor); var rsq = this.force.lengthSq(); this.force.normalize().multiplyScalar(this.strength); //console.log(this.force.toArray().toString()); body.applyForce(this.force); elation.events.fire({type: 'physics_force_apply', element: this}); } })(); this.update = function(updateargs) { if (updateargs.anchor) { this.anchor = updateargs.anchor; } if (updateargs.strength) { this.strength = updateargs.strength; } elation.events.fire({type: 'physics_force_update', element: this}); } this.toJSON = function() { return { type: this.type, anchor: this.anchor, strength: this.strength, }; } }); elation.extend("physics.forces.electrostatic", function(body, args) { this.type = 'electrostatic'; this.force = new THREE.Vector3(); this.charge = elation.utils.any(args.charge, 1); this.maxdist = elation.utils.any(args.maxdist, Infinity); this.others = args.others || false; this.sleeping = false; this.apply = (function() { var tmpvec = new THREE.Vector3(); // TODO - the constant Ke is actually dependent on the electric permittivity of the material the charges are immersed in, but this is good enough for most cases var Ke = 8.9875517873681764e9; return function(framedata) { var nearby = this.others;// || body.parent.children; if (!nearby) return; if (typeof framedata['electrostaticID'] == 'undefined') { framedata['electrostaticID'] = 0; framedata['electrostaticMatrix'] = {}; framedata['electrostaticSeen'] = {}; } var thisID = framedata['electrostaticID']; var matrix = framedata['electrostaticMatrix']; var seen = framedata['electrostaticSeen']; this.force.set(0,0,0); var mycharge = Ke * this.charge; for (var i = 0; i < nearby.length; i++) { var other = nearby[i]; if (other === body) continue; if (other.position.distanceToSquared(body.position) > this.maxdist * this.maxdist) { continue; } var pairID = Math.min(thisID, i) + '_' + Math.max(thisID, i); if (matrix[pairID]) { console.log('found an existing solution', pairID, matrix[pairID]); //matrix[pairID] = tmpvec.toArray(); this.force.add(tmpvec.fromArray(matrix[pairID]).multiplyScalar(1)); } else { var forces = other.getForces('electrostatic'); if (forces) { tmpvec.subVectors(body.position, other.position); var r = tmpvec.length(); var rsq = Math.pow(r + 1e6, 2); //var rsq = tmpvec.lengthSq() + 1e6; //console.log(rsq, body.position.toArray(), other.position.toArray()); //this.force.add(tmpvec.normalize().multiplyScalar(mycharge * forces[0].charge / rsq)); //this.force.add(tmpvec.normalize().multiplyScalar(Ke * this.charge * forces[0].charge / rsq)); this.force.add(tmpvec.multiplyScalar((mycharge * forces[0].charge) / (rsq * r))); //matrix[pairID] = tmpvec.toArray(); } } //console.log(this.force.toArray().toString()); } framedata['electrostaticID']++; body.applyForce(this.force); elation.events.fire({type: 'physics_force_apply', element: this}); } })(); this.update = function(updateargs) { for (var k in updateargs) { this[k] = updateargs[k]; } elation.events.fire({type: 'physics_force_update', element: this}); } this.toJSON = function() { return { type: this.type, charge: this.charge, maxdist: this.maxdist, }; } }); });