newton
Version:
A playful, particle-based physics engine designed from the ground up for JavaScript.
1,852 lines (1,481 loc) • 279 kB
JavaScript
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Newton=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
module.exports = {
Simulator: _dereq_('./lib/simulator'),
GLRenderer: _dereq_('./lib/renderers/gl-renderer'),
Particle: _dereq_('./lib/particle'),
Vector: _dereq_('./lib/vector'),
Body: _dereq_('./lib/body'),
Constraint: _dereq_('./lib/constraint'),
PinConstraint: _dereq_('./lib/constraints/pin-constraint'),
DistanceConstraint: _dereq_('./lib/constraints/distance-constraint'),
RopeConstraint: _dereq_('./lib/constraints/rope-constraint'),
BoxConstraint: _dereq_('./lib/constraints/box-constraint'),
Force: _dereq_('./lib/force'),
LinearForce: _dereq_('./lib/forces/linear-force')
};
},{"./lib/body":3,"./lib/constraint":4,"./lib/constraints/box-constraint":5,"./lib/constraints/distance-constraint":6,"./lib/constraints/pin-constraint":7,"./lib/constraints/rope-constraint":8,"./lib/force":9,"./lib/forces/linear-force":10,"./lib/particle":12,"./lib/renderers/gl-renderer":15,"./lib/simulator":17,"./lib/vector":18}],2:[function(_dereq_,module,exports){
function Accumulator(interval, max) {
this._interval = interval;
this._max = max;
this._total = 0;
this._lastTime = 0;
this._startTime = Date.now();
}
Accumulator.prototype.freeze = function() {
this._time = Date.now();
this._buffer = this._time - this._lastTime;
this._lastTime = this._time;
return this._interval;
};
Accumulator.prototype.next = function() {
if (this._buffer > this._max) {
this._buffer = 0;
return false;
}
if (this._buffer < this._interval) return false;
this._total += this._interval;
this._buffer -= this._interval;
return this._total;
};
module.exports = Accumulator;
},{}],3:[function(_dereq_,module,exports){
function Body() {
if (!(this instanceof Body)) return new Body();
this._entities = [];
this._sim = undefined;
}
Body.prototype.type = 'Body';
Body.prototype.add = function(entity) {
this._entities.push(entity);
if (this._sim) this._sim.add(entity);
return entity;
};
Body.prototype.setSimulator = function(sim) {
this._sim = sim;
for (var i = 0; i < this._entities.length; i++) {
sim.add(this._entities[i]);
}
};
module.exports = Body;
},{}],4:[function(_dereq_,module,exports){
var id = 0;
function Constraint() {
if (!(this instanceof Constraint)) return new Constraint();
this.id = id++;
}
Constraint.prototype.type = 'Constraint';
Constraint.prototype.priority = Infinity;
Constraint.prototype.correct = function() {};
Constraint.prototype.evaluate = function() {};
Constraint.prototype._deleted = false;
// todo: this should be run on the class, right?
// we're runnign this every time we add a constraint
Constraint.prototype.setPriority = function(types) {
for (var i = 0; i < types.length; i++) {
if (this instanceof types[i]) {
this.priority = i;
return;
}
}
this.priority = Infinity;
return;
};
module.exports = Constraint;
},{}],5:[function(_dereq_,module,exports){
var Constraint = _dereq_('../constraint');
var Vector = _dereq_('../vector');
function BoxConstraint(x, y, width, height) {
if (!(this instanceof BoxConstraint)) return new BoxConstraint(x, y, width, height);
Constraint.call(this);
this._min = Vector(x, y);
this._max = Vector(x + width, y + height);
}
BoxConstraint.prototype = Object.create(Constraint.prototype);
BoxConstraint.prototype.correct = function(time, particles) {
for (var i = 0; i < particles.length; i++) {
particles[i].bound(this._min, this._max);
}
};
module.exports = BoxConstraint;
},{"../constraint":4,"../vector":18}],6:[function(_dereq_,module,exports){
// TODO: make ordering smarter. detect chains
var Vector = _dereq_('../vector');
var Constraint = _dereq_('../constraint');
function DistanceConstraint(p1, p2) {
if (!(this instanceof DistanceConstraint)) return new DistanceConstraint(p1, p2);
Constraint.call(this);
this._p1 = p1;
this._p2 = p2;
this._distance = this.getDistance();
this._stiffness = 1;
}
DistanceConstraint.prototype = Object.create(Constraint.prototype);
DistanceConstraint.prototype.getDistance = function() {
return Vector.getDistance(this._p1.position, this._p2.position);
};
DistanceConstraint.prototype.correct = function(time, particles) {
var pos1 = this._p1.position;
var pos2 = this._p2.position;
var delta = pos2.pool().sub(pos1);
var length = delta.getLength();
var offBy = length - this._distance;
// TODO: handle different masses
var factor = offBy / length * this._stiffness;
var correction1 = delta.pool().scale(factor * 1);
var correction2 = delta.scale(-factor * 1);
this._p1.move(correction1);
this._p2.move(correction2);
delta.free();
correction1.free();
};
module.exports = DistanceConstraint;
},{"../constraint":4,"../vector":18}],7:[function(_dereq_,module,exports){
var Constraint = _dereq_('../constraint');
function PinConstraint(particle, position) {
if (!(this instanceof PinConstraint)) return new PinConstraint(particle, position);
Constraint.call(this);
this._particle = particle;
this._position = position || particle.position.clone();
}
PinConstraint.prototype = Object.create(Constraint.prototype);
PinConstraint.prototype.correct = function(time, particles) {
this._particle.place(this._position);
};
PinConstraint.prototype.setPosition = function(position) {
this._position = position;
};
module.exports = PinConstraint;
},{"../constraint":4}],8:[function(_dereq_,module,exports){
// TODO: make ordering smarter. detect chains
// TODO: inherit from distanceconstraint, or just give distanceconstraint some options
var Vector = _dereq_('../vector');
var Constraint = _dereq_('../constraint');
// avoid dividing by zero
var NO_DBZ = 0.000001;
function RopeConstraint(p1, p2, options) {
if (!(this instanceof RopeConstraint)) return new RopeConstraint(p1, p2, options);
Constraint.call(this);
options = options || {};
this._p1 = p1;
this._p2 = p2;
this._distance = options.length || this.getDistance();
this._stiffness = options.stiffness || 1;
this._expansion = 0.001;
this._compression = 1;
this._strength = options.strength || Infinity;
this._deleted = false;
}
RopeConstraint.prototype = Object.create(Constraint.prototype);
RopeConstraint.prototype.getDistance = function() {
return Vector.getDistance(this._p1.position, this._p2.position);
};
RopeConstraint.prototype.correct = function(time, particles, i, iterations) {
var pos1 = this._p1.position;
var pos2 = this._p2.position;
var delta = pos2.pool().sub(pos1);
var length = delta.getLength() || NO_DBZ;
var offBy = length - this._distance;
var multiplier = offBy <= 0 ? this._expansion : this._compression;
// TODO: handle different masses
var factor = offBy / length * this._stiffness * multiplier;
var correction1 = delta.pool().scale(factor * 1);
var correction2 = delta.scale(-factor * 1);
this._p1.move(correction1);
this._p2.move(correction2);
delta.free();
correction1.free();
};
RopeConstraint.prototype.evaluate = function(time, particles) {
var pos1 = this._p1.position;
var pos2 = this._p2.position;
var delta = pos2.pool().sub(pos1);
var length = delta.getLength();
var offBy = length - this._distance;
var stress = offBy / this._distance;
if (stress > this._strength) this._deleted = true;
};
module.exports = RopeConstraint;
},{"../constraint":4,"../vector":18}],9:[function(_dereq_,module,exports){
function Force() {
}
Force.prototype.type = 'Force';
Force.prototype.apply = function() {};
module.exports = Force;
},{}],10:[function(_dereq_,module,exports){
var Force = _dereq_('../force');
var Vector = _dereq_('../vector');
function LinearForce(strength, angle) {
if (!(this instanceof LinearForce)) return new LinearForce(strength, angle);
Force.call(this);
this._vector = Vector(strength, 0).rotate(angle);
}
LinearForce.prototype = Object.create(Force.prototype);
LinearForce.prototype.applyTo = function(particle) {
particle.accelerate(this._vector);
};
module.exports = LinearForce;
},{"../force":9,"../vector":18}],11:[function(_dereq_,module,exports){
module.exports = getFrame();
function getFrame() {
var lastTime = 0;
// Browsers
if (typeof window !== 'undefined') {
var vendors = ['ms', 'moz', 'webkit', 'o'];
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isFirefox = typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
var isChrome = !!window.chrome && !isOpera;
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = timeoutFrame;
window.cancelAnimationFrame = cancelTimeoutFrame;
}
return {
onFrame: window.requestAnimationFrame.bind(window),
cancelFrame: window.cancelAnimationFrame.bind(window)
};
}
// Node
return {
onFrame: timeoutFrame,
cancelFrame: cancelTimeoutFrame
};
function timeoutFrame(simulator, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = setTimeout(function() { simulator(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
}
function cancelTimeoutFrame(id) {
clearTimeout(id);
}
}
},{}],12:[function(_dereq_,module,exports){
var Vector = _dereq_('./vector');
function Particle(x, y, size) {
if (!(this instanceof Particle)) return new Particle(x, y, size);
this.size = size || 1;
this.position = Vector(x, y);
this.lastPosition = Vector(x, y);
this.acceleration = Vector(0, 0);
this._velocityBuffer = Vector(0, 0);
}
Particle.prototype.type = 'Particle';
Particle.prototype.accelerate = function(v) {
this.acceleration.add(v);
};
Particle.prototype.bound = function(min, max) {
if (this.position.x < min.x) {
this.position.x = this.lastPosition.x = min.x;
}
else if (this.position.x > max.x) {
this.position.x = this.lastPosition.x = max.x;
}
if (this.position.y < min.y) {
this.position.y = this.lastPosition.y = min.y;
}
else if (this.position.y > max.y) {
this.position.y = this.lastPosition.y = max.y;
}
};
Particle.prototype.getPoint = function() {
return {
x: this.position.x,
y: this.position.y
};
};
Particle.prototype.getVelocity = function() {
return this.position.clone().sub(this.lastPosition);
};
Particle.prototype.integrate = function(time) {
this._velocityBuffer
.copy(this.position)
.sub(this.lastPosition);
this.acceleration
.scale(time * time * 0.001); // scale to units / second / second
this.lastPosition.copy(this.position);
this.position
.add(this._velocityBuffer)
.add(this.acceleration);
this.acceleration.zero();
};
Particle.prototype.move = function(v) {
this.position.add(v);
};
Particle.prototype.place = function(v) {
this.position.copy(v);
this.lastPosition.copy(this.position);
return this;
};
Particle.prototype.setVelocity = function(v) {
this.lastPosition.copy(this.position).sub(v);
};
module.exports = Particle;
},{"./vector":18}],13:[function(_dereq_,module,exports){
function Renderer() {
}
Renderer.prototype.render = function(sim) {
this._sim = sim;
};
module.exports = Renderer;
},{}],14:[function(_dereq_,module,exports){
var POINT_VS = [
'uniform vec2 viewport;',
'attribute vec3 position;',
'attribute float size;',
'void main() {',
'vec2 scaled = ((position.xy / viewport) * 2.0) - 1.0;',
'vec2 flipped = vec2(scaled.x, -scaled.y);',
'gl_Position = vec4(flipped, 0, 1);',
'gl_PointSize = size + 1.0;',
'}'
].join('\n');
var CIRCLE_FS = [
'precision mediump float;',
'uniform sampler2D texture;',
'void main() {',
'gl_FragColor = texture2D(texture, gl_PointCoord);',
'}'
].join('\n');
module.exports = {
getGLContext: getGLContext,
createCircleTexture: createCircleTexture,
createCircleShader: createCircleShader,
createShaderProgram: createShaderProgram,
createTexture: createTexture
};
function getGLContext(canvas) {
var names = [
'webgl',
'experimental-webgl',
'webkit-3d',
'moz-webgl'
];
var i = 0, gl;
while (!gl && i++ < names.length) {
try {
gl = canvas.getContext(names[i]);
} catch(e) {}
}
return gl;
}
function createCircleTexture(gl, size) {
size = size || 32;
var canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
var ctx = canvas.getContext('2d');
var rad = size * 0.5;
ctx.beginPath();
ctx.arc(rad, rad, rad, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fillStyle = '#fff';
ctx.fill();
return createTexture(gl, canvas);
}
function createTexture(gl, data) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
function createShaderProgram(gl, vsText, fsText) {
var vs = gl.createShader(gl.VERTEX_SHADER);
var fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vs, vsText);
gl.shaderSource(fs, fsText);
gl.compileShader(vs);
gl.compileShader(fs);
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
console.error('error compiling VS shaders:', gl.getShaderInfoLog(vs));
throw new Error('shader failure');
}
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
console.error('error compiling FS shaders:', gl.getShaderInfoLog(fs));
throw new Error('shader failure');
}
var program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
return program;
}
function createCircleShader(gl, viewportArray, viewportAttr, positionAttr, sizeAttr) {
viewportAttr = viewportAttr || 'viewport';
positionAttr = positionAttr || 'position';
sizeAttr = sizeAttr || 'size';
var shader = createShaderProgram(gl, POINT_VS, CIRCLE_FS);
shader.uniforms = {
viewport: gl.getUniformLocation(shader, viewportAttr)
};
shader.attributes = {
position: gl.getAttribLocation(shader, positionAttr),
size: gl.getAttribLocation(shader, sizeAttr)
};
gl.useProgram(shader);
gl.uniform2fv(shader.uniforms.viewport, viewportArray);
return shader;
}
},{}],15:[function(_dereq_,module,exports){
var Renderer = _dereq_('../../renderer');
var GLUtil = _dereq_('./gl-util');
var onFrame = _dereq_('../../frame').onFrame;
var Vector = _dereq_('../../vector');
var PointRenderer = _dereq_('./point-renderer');
var Emitter = _dereq_('eventemitter2').EventEmitter2;
var extend = _dereq_('lodash').extend;
var MAX_PARTICLES = 10000;
function GLRenderer(el) {
if (!(this instanceof GLRenderer)) return new GLRenderer(el);
Emitter.call(this);
this._el = el;
this._drawFrame = this._drawFrame.bind(this);
this._gl = GLUtil.getGLContext(el);
this._gl.clearColor(0, 0, 0, 0);
this._viewportArray = new Float32Array([el.width, el.height]);
this._pointRenderer = new PointRenderer(this._gl, this._viewportArray);
this._listen();
}
GLRenderer.prototype = Object.create(Renderer.prototype);
extend(GLRenderer.prototype, Emitter.prototype);
GLRenderer.prototype.render = function(sim) {
this._sim = sim;
onFrame(this._drawFrame);
};
GLRenderer.prototype._drawFrame = function() {
this._clear();
this._pointRenderer.draw(this._sim.getParticles());
onFrame(this._drawFrame);
};
GLRenderer.prototype._clear = function() {
var gl = this._gl;
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
};
GLRenderer.prototype._listen = function() {
var self = this;
var canvas = this._el;
var down = false;
canvas.addEventListener('mousedown', onDown);
canvas.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
function onDown(e) {
down = true;
self.emit('pointerdown', getVector(e, canvas));
}
function onMove(e) {
var vector = getVector(e, canvas);
self.emit('pointermove', vector);
if (down) self.emit('pointerdrag', vector);
}
function onUp(e) {
if (!down) return;
self.emit('pointerup', getVector(e, canvas));
}
};
module.exports = GLRenderer;
function getVector(event, canvas) {
var x = event.clientX - document.documentElement.scrollLeft - canvas.offsetLeft;
var y = event.clientY - document.documentElement.scrollTop - canvas.offsetTop;
return Vector(x, y);
}
},{"../../frame":11,"../../renderer":13,"../../vector":18,"./gl-util":14,"./point-renderer":16,"eventemitter2":20,"lodash":21}],16:[function(_dereq_,module,exports){
var GLUtil = _dereq_('./gl-util');
var MAX_POINTS = 10000;
var VERTEX_SHADER = [
'uniform vec2 viewport;',
'attribute vec3 position;',
'attribute float size;',
'void main() {',
'vec2 scaled = ((position.xy / viewport) * 2.0) - 1.0;',
'vec2 flipped = vec2(scaled.x, -scaled.y);',
'gl_Position = vec4(flipped, 0, 1);',
'gl_PointSize = size + 1.0;',
'}'
].join('\n');
var FRAGMENT_SHADER = [
'precision mediump float;',
'uniform sampler2D texture;',
'void main() {',
'gl_FragColor = texture2D(texture, gl_PointCoord);',
'}'
].join('\n');
function PointRenderer(gl, viewportArray) {
this._gl = gl;
this._viewportArray = viewportArray;
this._verticesCache = [];
this._sizesCache = [];
this._vArray = new Float32Array(MAX_POINTS * 2);
this._sArray = new Float32Array(MAX_POINTS);
this._texture = createCircleTexture(gl);
this._shader = createCircleShader(gl, viewportArray);
this._positionBuffer = gl.createBuffer();
this._sizeBuffer = gl.createBuffer();
}
PointRenderer.prototype.draw = function(points) {
var gl = this._gl;
var vertices = this._verticesCache;
var sizes = this._sizesCache;
var vArray = this._vArray;
var sArray = this._sArray;
var attributes = this._shader.attributes;
var point;
vertices.length = 0;
sizes.length = 0;
for (var i = 0; i < points.length; i++) {
point = points[i];
vertices.push(point.position.x, point.position.y);
sizes.push(4);
}
vArray.set(vertices, 0);
sArray.set(sizes, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._texture);
// position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vArray, gl.STATIC_DRAW);
gl.vertexAttribPointer(attributes.position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(attributes.position);
// size buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this._sizeBuffer);
gl.bufferData(gl.ARRAY_BUFFER, sArray, gl.STATIC_DRAW);
gl.vertexAttribPointer(attributes.size, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(attributes.size);
gl.drawArrays(gl.POINTS, 0, vertices.length / 2);
};
module.exports = PointRenderer;
function createCircleTexture(gl, size) {
size = size || 128;
var canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
var ctx = canvas.getContext('2d');
var rad = size * 0.5;
ctx.beginPath();
ctx.arc(rad, rad, rad, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fillStyle = '#fff';
ctx.fill();
return GLUtil.createTexture(gl, canvas);
}
function createCircleShader(gl, viewportArray) {
var vs = gl.createShader(gl.VERTEX_SHADER);
var fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vs, VERTEX_SHADER);
gl.shaderSource(fs, FRAGMENT_SHADER);
gl.compileShader(vs);
gl.compileShader(fs);
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
console.error('error compiling VS shaders:', gl.getShaderInfoLog(vs));
throw new Error('shader failure');
}
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
console.error('error compiling FS shaders:', gl.getShaderInfoLog(fs));
throw new Error('shader failure');
}
var program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
program.uniforms = {
viewport: gl.getUniformLocation(program, 'viewport')
};
program.attributes = {
position: gl.getAttribLocation(program, 'position'),
size: gl.getAttribLocation(program, 'size')
};
gl.useProgram(program);
gl.uniform2fv(program.uniforms.viewport, viewportArray);
return program;
}
},{"./gl-util":14}],17:[function(_dereq_,module,exports){
var Emitter = _dereq_('eventemitter2').EventEmitter2;
var onFrame = _dereq_('./frame').onFrame;
var Accumulator = _dereq_('./accumulator');
function Simulator(options) {
if (!(this instanceof Simulator)) return new Simulator(options);
Emitter.call(this);
options = options || {};
this._step = this._step.bind(this);
this._stepInterval = 1000 / 60; // TODO: option
this._running = false;
this._accumulator = undefined;
this._particles = [];
this._bodies = [];
this._forces = [];
this._constraints = [];
this._priorities = options.solve || [];
this._iterations = 10; // TODO: option
}
Simulator.prototype = Object.create(Emitter.prototype);
Simulator.prototype.start = function() {
this._running = true;
this._accumulator = new Accumulator(this._stepInterval, 100);
onFrame(this._step);
};
Simulator.prototype.add = function(entity) {
if (entity.type === 'Particle') this._particles.push(entity);
else if (entity.type === 'Force') this._forces.push(entity);
else if (entity.type === 'Constraint') {
entity.setPriority(this._priorities);
this._constraints.push(entity);
this._constraints.sort(prioritySort);
}
else if (entity.type === 'Body') {
this._bodies.push(entity);
entity.setSimulator(this);
}
return entity;
};
Simulator.prototype.findNearest = function(point, radius) {
var nearestDistance = radius * radius;
var nearest, distance;
for (var i = 0; i < this._particles.length; i++) {
distance = this._particles[i].position.getDistance2(point);
if (distance < nearestDistance) {
nearest = this._particles[i];
nearestDistance = distance;
}
}
return nearest;
};
Simulator.prototype.getParticles = function() {
return this._particles;
};
Simulator.prototype.remove = function(entity) {
if (entity.type === 'Constraint') {
var index = this._constraints.indexOf(entity);
if (index === -1) throw new Error('entity not found');
this._constraints.splice(index, 1);
}
};
Simulator.prototype._step = function() {
if (!this._running) return;
var time;
var interval = this._accumulator.freeze();
while (time = this._accumulator.next()) {
this._simulate(interval, time);
}
onFrame(this._step);
};
Simulator.prototype._simulate = function(time, totalTime) {
this._cull();
this._integrate(time);
this._constrain(time);
};
Simulator.prototype._cull = function() {
var i = 0;
while (i < this._constraints.length) {
if (this._constraints[i]._deleted) {
this._constraints.splice(i, 1);
}
else i++;
}
};
Simulator.prototype._integrate = function(time) {
var particles = this._particles;
var forces = this._forces;
var particle, force;
for (var p = 0; p < particles.length; p++) {
particle = particles[p];
for (var f = 0; f < forces.length; f++) {
force = forces[f];
force.applyTo(particle);
}
particle.integrate(time);
}
};
Simulator.prototype._constrain = function(time) {
var constraints = this._constraints;
var particles = this._particles;
for (var i = 0; i < this._iterations; i++) {
for (var c = 0; c < constraints.length; c++) {
constraints[c].correct(time, particles, i, this._iterations);
}
}
for (var c = 0; c < constraints.length; c++) {
constraints[c].evaluate(time, particles);
}
};
module.exports = Simulator;
function prioritySort(a, b) {
return b.priority - a.priority || b.id - a.id;
}
},{"./accumulator":2,"./frame":11,"eventemitter2":20}],18:[function(_dereq_,module,exports){
var pool = []; // Vector object pooling (avoid GC)
function Vector(x, y) {
if (!(this instanceof Vector)) return new Vector(x, y);
this.x = x || 0;
this.y = y || 0;
}
Vector.claim = function() {
return pool.pop() || Vector();
};
Vector.getDistance = function(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
};
Vector.pool = function(size) {
if (typeof size !== 'undefined') {
pool.length = 0;
for (var i = 0; i < size; i++) {
pool.push(Vector());
}
}
else {
return pool.length;
}
};
Vector.prototype.add = function(v) {
this.x += v.x;
this.y += v.y;
return this;
};
Vector.prototype.clone = function() {
return Vector(this.x, this.y);
};
Vector.prototype.copy = function(v) {
this.x = v.x;
this.y = v.y;
return this;
};
Vector.prototype.getDistance2 = function(v) {
var dx = v.x - this.x;
var dy = v.y - this.y;
return dx * dx + dy * dy;
};
Vector.prototype.equals = function(v) {
return this.x === v.x && this.y === v.y;
};
Vector.prototype.pool = function() {
return Vector.claim().copy(this);
};
Vector.prototype.free = function() {
pool.push(this);
return this;
};
Vector.prototype.getLength = function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
Vector.prototype.min = function(v) {
if (this.x < v.x) this.x = v.x;
if (this.y < v.y) this.y = v.y;
else return false;
};
Vector.prototype.max = function(v) {
if (this.x > v.x) this.x = v.x;
if (this.y > v.y) this.y = v.y;
return this;
};
Vector.prototype.rotate = function(angle) {
var x = this.x;
var y = -this.y;
var sin = Math.sin(angle);
var cos = Math.cos(angle);
this.x = x * cos - y * sin;
this.y = -(x * sin + y * cos);
return this;
};
Vector.prototype.scale = function(scalar) {
this.x *= scalar;
this.y *= scalar;
return this;
};
Vector.prototype.sub = function(v) {
this.x -= v.x;
this.y -= v.y;
return this;
};
Vector.prototype.zero = function() {
this.x = this.y = 0;
return this;
};
module.exports = Vector;
/*
// One-off vector for single computes
Vector.scratch = new Vector();
// Static methods
// New instances
// Setters
Vector.prototype.set = function(x, y) {
this.x = x;
this.y = y;
return this;
};
// Add
Vector.prototype.add = function(v) {
this.x += v.x;
this.y += v.y;
return this;
};
Vector.prototype.addXY = function(x, y) {
this.x += x;
this.y += y;
return this;
};
Vector.prototype.subXY = function(x, y) {
this.x -= x;
this.y -= y;
return this;
};
Vector.prototype.merge = function(v) {
var dx = v.x - this.x;
var dy = v.y - this.y;
if (dx > 0 && this.x >= 0) this.x += dx;
else if (dx < 0 && this.x <= 0) this.x += dx;
if (dy > 0 && this.y >= 0) this.y += dy;
else if (dy < 0 && this.y <= 0) this.y += dy;
return this;
};
// Scale
Vector.prototype.mult = function(v) {
this.x *= v.x;
this.y *= v.y;
return this;
};
Vector.prototype.div = function(v) {
this.x /= v.x;
this.y /= v.y;
return this;
};
Vector.prototype.reverse = function() {
this.x = -this.x;
this.y = -this.y;
return this;
};
Vector.prototype.unit = function() {
this.scale(1 / this.getLength());
return this;
};
// Rotate
Vector.prototype.turnRight = function() {
var x = this.x;
var y = this.y;
this.x = -y;
this.y = x;
return this;
};
Vector.prototype.turnLeft = function() {
var x = this.x;
var y = this.y;
this.x = y;
this.y = -x;
return this;
};
Vector.prototype.rotateAbout = function(pivot, angle) {
this.sub(pivot).rotateBy(angle).add(pivot);
return this;
};
// Get
Vector.prototype.getDot = function(v) {
return this.x * v.x + this.y * v.y;
};
Vector.prototype.getCross = function(v) {
return this.x * v.y + this.y * v.x;
};
Vector.prototype.getLength = function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
Vector.prototype.getLength2 = function() {
// Squared length
return this.x * this.x + this.y * this.y;
};
Vector.prototype.getAngle = function() {
return Math.atan2(-this.y, this.x);
};
Vector.prototype.getAngleTo = function(v) {
// The nearest angle between two vectors
// (origin of 0,0 for both)
var cos = this.x * v.x + this.y * v.y;
var sin = this.y * v.x - this.x * v.y;
return Math.atan2(sin, cos);
};
// If projection >= 0, it's in this half plane
// Otherwise, it isn't
Vector.prototype.getProjection = function(vPoint, vDir) {
return this.clone().sub(vPoint).getDot(vDir);
};
Vector.prototype.applyProjection = function(projection, vDir) {
this.sub(dir.clone().scale(projection));
return this;
};
Vector.prototype.projectOnto = function(vPoint, vDir) {
var projection = this.clone().sub(vPoint).getDot(vDir);
this.sub(vDir.clone().scale(projection));
return this;
};
Vector.prototype.projectSegment = function(vA, vB) {
var normal = vB.clone().sub(vA).turnLeft().unit();
var projection = this.clone().sub(vA).getDot(normal);
this.sub(normal.scale(projection));
if (this.x > vA.x && this.x > vB.x) this.x = Math.max(vA.x, vB.x);
else if (this.x < vA.x && this.x < vB.x) this.x = Math.min(vA.x, vB.x);
if (this.y > vA.y && this.y > vB.y) this.y = Math.max(vA.y, vB.y);
else if (this.y < vA.y && this.y < vB.y) this.y = Math.min(vA.y, vB.y);
return this;
};
*/
},{}],19:[function(_dereq_,module,exports){
// shim for using process in browser
var process = module.exports = {};
process.nextTick = (function () {
var canSetImmediate = typeof window !== 'undefined'
&& window.setImmediate;
var canPost = typeof window !== 'undefined'
&& window.postMessage && window.addEventListener
;
if (canSetImmediate) {
return function (f) { return window.setImmediate(f) };
}
if (canPost) {
var queue = [];
window.addEventListener('message', function (ev) {
var source = ev.source;
if ((source === window || source === null) && ev.data === 'process-tick') {
ev.stopPropagation();
if (queue.length > 0) {
var fn = queue.shift();
fn();
}
}
}, true);
return function nextTick(fn) {
queue.push(fn);
window.postMessage('process-tick', '*');
};
}
return function nextTick(fn) {
setTimeout(fn, 0);
};
})();
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
function noop() {}
process.on = noop;
process.once = noop;
process.off = noop;
process.emit = noop;
process.binding = function (name) {
throw new Error('process.binding is not supported');
}
// TODO(shtylman)
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
},{}],20:[function(_dereq_,module,exports){
(function (process){
;!function(exports, undefined) {
var isArray = Array.isArray ? Array.isArray : function _isArray(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
var defaultMaxListeners = 10;
function init() {
this._events = {};
if (this._conf) {
configure.call(this, this._conf);
}
}
function configure(conf) {
if (conf) {
this._conf = conf;
conf.delimiter && (this.delimiter = conf.delimiter);
conf.maxListeners && (this._events.maxListeners = conf.maxListeners);
conf.wildcard && (this.wildcard = conf.wildcard);
conf.newListener && (this.newListener = conf.newListener);
if (this.wildcard) {
this.listenerTree = {};
}
}
}
function EventEmitter(conf) {
this._events = {};
this.newListener = false;
configure.call(this, conf);
}
//
// Attention, function return type now is array, always !
// It has zero elements if no any matches found and one or more
// elements (leafs) if there are matches
//
function searchListenerTree(handlers, type, tree, i) {
if (!tree) {
return [];
}
var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached,
typeLength = type.length, currentType = type[i], nextType = type[i+1];
if (i === typeLength && tree._listeners) {
//
// If at the end of the event(s) list and the tree has listeners
// invoke those listeners.
//
if (typeof tree._listeners === 'function') {
handlers && handlers.push(tree._listeners);
return [tree];
} else {
for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
handlers && handlers.push(tree._listeners[leaf]);
}
return [tree];
}
}
if ((currentType === '*' || currentType === '**') || tree[currentType]) {
//
// If the event emitted is '*' at this part
// or there is a concrete match at this patch
//
if (currentType === '*') {
for (branch in tree) {
if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1));
}
}
return listeners;
} else if(currentType === '**') {
endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*'));
if(endReached && tree._listeners) {
// The next element has a _listeners, add it to the handlers.
listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength));
}
for (branch in tree) {
if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
if(branch === '*' || branch === '**') {
if(tree[branch]._listeners && !endReached) {
listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength));
}
listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
} else if(branch === nextType) {
listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2));
} else {
// No match on this one, shift into the tree but not in the type array.
listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
}
}
}
return listeners;
}
listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1));
}
xTree = tree['*'];
if (xTree) {
//
// If the listener tree will allow any match for this part,
// then recursively explore all branches of the tree
//
searchListenerTree(handlers, type, xTree, i+1);
}
xxTree = tree['**'];
if(xxTree) {
if(i < typeLength) {
if(xxTree._listeners) {
// If we have a listener on a '**', it will catch all, so add its handler.
searchListenerTree(handlers, type, xxTree, typeLength);
}
// Build arrays of matching next branches and others.
for(branch in xxTree) {
if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {
if(branch === nextType) {
// We know the next element will match, so jump twice.
searchListenerTree(handlers, type, xxTree[branch], i+2);
} else if(branch === currentType) {
// Current node matches, move into the tree.
searchListenerTree(handlers, type, xxTree[branch], i+1);
} else {
isolatedBranch = {};
isolatedBranch[branch] = xxTree[branch];
searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1);
}
}
}
} else if(xxTree._listeners) {
// We have reached the end and still on a '**'
searchListenerTree(handlers, type, xxTree, typeLength);
} else if(xxTree['*'] && xxTree['*']._listeners) {
searchListenerTree(handlers, type, xxTree['*'], typeLength);
}
}
return listeners;
}
function growListenerTree(type, listener) {
type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
//
// Looks for two consecutive '**', if so, don't add the event at all.
//
for(var i = 0, len = type.length; i+1 < len; i++) {
if(type[i] === '**' && type[i+1] === '**') {
return;
}
}
var tree = this.listenerTree;
var name = type.shift();
while (name) {
if (!tree[name]) {
tree[name] = {};
}
tree = tree[name];
if (type.length === 0) {
if (!tree._listeners) {
tree._listeners = listener;
}
else if(typeof tree._listeners === 'function') {
tree._listeners = [tree._listeners, listener];
}
else if (isArray(tree._listeners)) {
tree._listeners.push(listener);
if (!tree._listeners.warned) {
var m = defaultMaxListeners;
if (typeof this._events.maxListeners !== 'undefined') {
m = this._events.maxListeners;
}
if (m > 0 && tree._listeners.length > m) {
tree._listeners.warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
tree._listeners.length);
console.trace();
}
}
}
return true;
}
name = type.shift();
}
return true;
}
// By default EventEmitters will print a warning if more than
// 10 listeners are added to it. This is a useful default which
// helps finding memory leaks.
//
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.delimiter = '.';
EventEmitter.prototype.setMaxListeners = function(n) {
this._events || init.call(this);
this._events.maxListeners = n;
if (!this._conf) this._conf = {};
this._conf.maxListeners = n;
};
EventEmitter.prototype.event = '';
EventEmitter.prototype.once = function(event, fn) {
this.many(event, 1, fn);
return this;
};
EventEmitter.prototype.many = function(event, ttl, fn) {
var self = this;
if (typeof fn !== 'function') {
throw new Error('many only accepts instances of Function');
}
function listener() {
if (--ttl === 0) {
self.off(event, listener);
}
fn.apply(this, arguments);
}
listener._origin = fn;
this.on(event, listener);
return self;
};
EventEmitter.prototype.emit = function() {
this._events || init.call(this);
var type = arguments[0];
if (type === 'newListener' && !this.newListener) {
if (!this._events.newListener) { return false; }
}
// Loop through the *_all* functions and invoke them.
if (this._all) {
var l = arguments.length;
var args = new Array(l - 1);
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
for (i = 0, l = this._all.length; i < l; i++) {
this.event = type;
this._all[i].apply(this, args);
}
}
// If there is no 'error' event listener then throw.
if (type === 'error') {
if (!this._all &&
!this._events.error &&
!(this.wildcard && this.listenerTree.error)) {
if (arguments[1] instanceof Error) {
throw arguments[1]; // Unhandled 'error' event
} else {
throw new Error("Uncaught, unspecified 'error' event.");
}
return false;
}
}
var handler;
if(this.wildcard) {
handler = [];
var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
}
else {
handler = this._events[type];
}
if (typeof handler === 'function') {
this.event = type;
if (arguments.length === 1) {
handler.call(this);
}
else if (arguments.length > 1)
switch (arguments.length) {
case 2:
handler.call(this, arguments[1]);
break;
case 3:
handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
var l = arguments.length;
var args = new Array(l - 1);
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
handler.apply(this, args);
}
return true;
}
else if (handler) {
var l = arguments.length;
var args = new Array(l - 1);
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
var listeners = handler.slice();
for (var i = 0, l = listeners.length; i < l; i++) {
this.event = type;
listeners[i].apply(this, args);
}
return (listeners.length > 0) || this._all;
}
else {
return this._all;
}
};
EventEmitter.prototype.on = function(type, listener) {
if (typeof type === 'function') {
this.onAny(type);
return this;
}
if (typeof listener !== 'function') {
throw new Error('on only accepts instances of Function');
}
this._events || init.call(this);
// To avoid recursion in the case that type == "newListeners"! Before
// adding it to the listeners, first emit "newListeners".
this.emit('newListener', type, listener);
if(this.wildcard) {
growListenerTree.call(this, type, listener);
return this;
}
if (!this._events[type]) {
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
}
else if(typeof this._events[type] === 'function') {
// Adding the second element, need to change to array.
this._events[type] = [this._events[type], listener];
}
else if (isArray(this._events[type])) {
// If we've already got an array, just append.
this._events[type].push(listener);
// Check for listener leak
if (!this._events[type].warned) {
var m = defaultMaxListeners;
if (typeof this._events.maxListeners !== 'undefined') {
m = this._events.maxListeners;
}
if (m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
this._events[type].length);
console.trace();
}
}
}
return this;
};
EventEmitter.prototype.onAny = function(fn) {
if(!this._all) {
this._all = [];
}
if (typeof fn !== 'function') {
throw new Error('onAny only accepts instances of Function');
}
// Add the function to the event listener collection.
this._all.push(fn);
return this;
};
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
EventEmitter.prototype.off = function(type, listener) {
if (typeof listener !== 'function') {
throw new Error('removeListener only takes instances of Function');
}
var handlers,leafs=[];
if(this.wildcard) {
var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
}
else {
// does not use listeners(), so no side effect of creating _events[type]
if (!this._events[type]) return this;
handlers = this._events[type];
leafs.push({_listeners:handlers});
}
for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
var leaf = leafs[iLeaf];
handlers = leaf._listeners;
if (isArray(handlers)) {
var position = -1;
for (var i = 0, length = handlers.length; i < length; i++) {
if (handlers[i] === listener ||
(handlers[i].listener && handlers[i].listener === listener) ||
(handlers[i]._origin && handlers[i]._origin === listener)) {
position = i;
break;
}
}
if (position < 0) {
continue;
}
if(this.wildcard) {
leaf._listeners.splice(position, 1);
}
else {
this._events[type].splice(position, 1);
}
if (handlers.length === 0) {
if(this.wildcard) {
delete leaf._listeners;
}
else {
delete this._events[type];
}
}
return this;
}
else if (handlers === listener ||
(handlers.listener && handlers.listener === listener) ||
(handlers._origin && handlers._origin === listener)) {
if(this.wildcard) {
delete leaf._listeners;
}
else {
delete this._events[type];
}
}
}
return this;
};
EventEmitter.prototype.offAny = function(fn) {
var i = 0, l = 0, fns;
if (fn && this._all && this._all.length > 0) {
fns = this._all;
for(i = 0, l = fns.length; i < l; i++) {
if(fn === fns[i]) {
fns.splice(i, 1);
return this;
}
}
} else {
this._all = [];
}
return this;
};
EventEmitter.prototype.removeListener = EventEmitter.prototype.off;
EventEmitter.prototype.removeAllListeners = function(type) {
if (arguments.length === 0) {
!this._events || init.call(this);
return this;
}
if(this.wildcard) {
var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {
var leaf = leafs[iLeaf];
leaf._listeners = null;
}
}
else {
if (!this._events[type]) return this;
this._events[type] = null;
}
return this;
};
EventEmitter.prototype.listeners = function(type) {
if(this.wildcard) {
var handlers = [];
var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);
return handlers;
}
this._events || init.call(this);
if (!this._events[type]) this._events[type] = [];
if (!isArray(this._events[type])) {
this._events[type] = [this._events[type]];
}
return this._events[type];
};
EventEmitter.prototype.listenersAny = function() {
if(this._all) {
return this._all;
}
else {
return [];
}
};
if (typeof define === 'function' && define.amd) {
define(function() {
return EventEmitter;
});
} else {
exports.EventEmitter2 = EventEmitter;
}
}(typeof process !== 'undefined' && typeof process.title !== 'undefined' && typeof exports !== 'undefined' ? exports : window);
}).call(this,_dereq_("/Users/hunter/code/newton/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js"))
},{"/Users/hunter/code/newton/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":19}],21:[function(_dereq_,module,exports){
(function (global){
/**
* @license
* Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
* Build: `lodash modern -o ./dist/lodash.js`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
;(function() {
/** Used as a safe reference for `undefined` in pre ES5 environments */
var undefined;
/** Used to pool arrays and objects used internally */
var arrayPool = [],
objectPool = [];
/** Used to generate unique IDs */
var idCounter = 0;
/** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
var keyPrefix = +new Date + '';
/** Used as the size when optimizations are enabled for large arrays */
var largeArraySize = 75;
/** Used as the max size of the `arrayPool` and `objectPool` */
var maxPoolSize = 40;
/** Used to detect and test whitespace */
var whitespace = (
// whitespace
' \t\x0B\f\xA0\ufeff' +
// line terminators
'\n\r\u2028\u2029' +
// unicode category "Zs" space separators
'\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
);
/** Used to match empty string literals in compile