├── .gitignore ├── CakeFile ├── LICENSE ├── README.md ├── compiled ├── base.js ├── behaviour │ ├── Attraction.js │ ├── Behaviour.js │ ├── Collision.js │ ├── ConstantForce.js │ ├── EdgeBounce.js │ ├── EdgeWrap.js │ └── Wander.js ├── demos │ ├── AttractionDemo.js │ ├── BalloonDemo.js │ ├── BoundsDemo.js │ ├── ChainDemo.js │ ├── ClothDemo.js │ ├── CollisionDemo.js │ ├── Demo.js │ └── renderer │ │ ├── CanvasRenderer.js │ │ ├── DOMRenderer.js │ │ ├── Renderer.js │ │ └── WebGLRenderer.js ├── engine │ ├── Particle.js │ ├── Physics.js │ ├── Spring.js │ └── integrator │ │ ├── Euler.js │ │ ├── ImprovedEuler.js │ │ ├── Integrator.js │ │ └── Verlet.js └── math │ ├── Random.js │ └── Vector.js ├── deploy ├── physics.js └── physics.min.js ├── index.html └── source ├── base.coffee ├── behaviour ├── Attraction.coffee ├── Behaviour.coffee ├── Collision.coffee ├── ConstantForce.coffee ├── EdgeBounce.coffee ├── EdgeWrap.coffee └── Wander.coffee ├── demos ├── AttractionDemo.coffee ├── BalloonDemo.coffee ├── BoundsDemo.coffee ├── ChainDemo.coffee ├── ClothDemo.coffee ├── CollisionDemo.coffee ├── Demo.coffee └── renderer │ ├── CanvasRenderer.coffee │ ├── DOMRenderer.coffee │ ├── Renderer.coffee │ └── WebGLRenderer.coffee ├── engine ├── Particle.coffee ├── Physics.coffee ├── Spring.coffee └── integrator │ ├── Euler.coffee │ ├── ImprovedEuler.coffee │ ├── Integrator.coffee │ └── Verlet.coffee └── math ├── Random.coffee └── Vector.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store? 3 | ehthumbs.db 4 | Icon? 5 | Thumbs.db -------------------------------------------------------------------------------- /CakeFile: -------------------------------------------------------------------------------- 1 | ### Build file for CoffeePhysics. ### 2 | fs = require 'fs' 3 | uglify = require 'uglify-js' 4 | {exec, spawn} = require 'child_process' 5 | 6 | config = 7 | compiled: 'compiled' 8 | deploy: 'deploy' 9 | source: 'source' 10 | 11 | output = 12 | 'physics': [ 13 | 'base' 14 | 'math/Random' 15 | 'math/Vector' 16 | 'engine/Particle' 17 | 'engine/Spring' 18 | 'engine/Physics' 19 | 'engine/integrator/Integrator' 20 | 'engine/integrator/Euler' 21 | 'engine/integrator/ImprovedEuler' 22 | 'engine/integrator/Verlet' 23 | 'behaviour/Behaviour' 24 | 'behaviour/Attraction' 25 | 'behaviour/Collision' 26 | 'behaviour/ConstantForce' 27 | 'behaviour/EdgeBounce' 28 | 'behaviour/EdgeWrap' 29 | 'behaviour/Wander' 30 | ] 31 | 32 | grr = (message = '') -> 33 | options = { 34 | title: 'CoffeeScript' 35 | image: '/Users/justin/node_modules/growl/lib/CoffeeScript.png' 36 | } 37 | try require('growl')(message, options) 38 | 39 | task 'watch', 'Watch & compile changes in source dir', -> 40 | 41 | watch = exec "coffee -o #{config.compiled}/ -cwb #{config.source}/" 42 | watch.stdout.on 'data', (data) -> 43 | process.stdout.write data 44 | #grr "#{data}" 45 | 46 | task 'build', '', -> 47 | 48 | for target, files of output 49 | 50 | # Map filenames. 51 | mapped = files.map (file) -> 52 | file = "#{config.source}/#{file}.coffee" 53 | 54 | # Concatenated file. 55 | file = "#{config.deploy}/#{target}.js" 56 | 57 | # Concatenate and compile. 58 | exec "coffee -j #{file} -cb #{(mapped.join ' ')}" 59 | console.log "coffee -j #{file} -cb #{(mapped.join ' ')}" 60 | 61 | # Uglify. 62 | jsp = uglify.parser 63 | pro = uglify.uglify 64 | 65 | fs.readFile file, 'utf8', (err, data) -> 66 | 67 | # Parse code and get the initial AST. 68 | ast = jsp.parse data 69 | 70 | # Get a new AST with mangled names. 71 | ast = pro.ast_mangle ast 72 | 73 | # Get an AST with compression optimizations. 74 | ast = pro.ast_squeeze ast 75 | 76 | # Compressed code. 77 | code = pro.gen_code ast 78 | 79 | # Write to file. 80 | fs.writeFile "#{config.deploy}/#{target}.min.js", code 81 | grr "Minfied: #{config.deploy}/#{target}.min.js" 82 | 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Justin Windle (justin@soulwire.co.uk) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Coffee Physics 2 | 3 | A lightweight physics engine, written in [CoffeeScript](http://coffeescript.org/). Why? Why not!? 4 | 5 | Early demos can be found here: [http://soulwire.github.com/Coffee-Physics/](http://soulwire.github.com/Coffee-Physics/) 6 | 7 | #### A Quick Example 8 | 9 | The CoffeePhysics API is designed to be very simple. Consider the following [example](http://jsfiddle.net/soulwire/Ra5Ve/): 10 | 11 | // Create a physics instance which uses the Verlet integration method 12 | var physics = new Physics(); 13 | physics.integrator = new Verlet(); 14 | 15 | // Design some behaviours for particles 16 | var avoidMouse = new Attraction(); 17 | var pullToCenter = new Attraction(); 18 | 19 | // Allow particle collisions to make things interesting 20 | var collision = new Collision(); 21 | 22 | // Use Sketch.js to make life much easier 23 | var example = Sketch.create({ container: document.body }); 24 | 25 | example.setup = function() { 26 | 27 | for ( var i = 0; i < 200; i++ ) { 28 | 29 | // Create a particle 30 | var particle = new Particle( Math.random() ); 31 | var position = new Vector( random( this.width ), random( this.height ) ); 32 | particle.setRadius( particle.mass * 10 ); 33 | particle.moveTo( position ); 34 | 35 | // Make it collidable 36 | collision.pool.push( particle ); 37 | 38 | // Apply behaviours 39 | particle.behaviours.push( avoidMouse, pullToCenter, collision ); 40 | 41 | // Add to the simulation 42 | physics.particles.push( particle ); 43 | } 44 | 45 | pullToCenter.target.x = this.width / 2; 46 | pullToCenter.target.y = this.height / 2; 47 | pullToCenter.strength = 120; 48 | 49 | avoidMouse.setRadius( 60 ); 50 | avoidMouse.strength = -1000; 51 | 52 | example.fillStyle = '#ff00ff'; 53 | } 54 | 55 | example.draw = function() { 56 | 57 | // Step the simulation 58 | physics.step(); 59 | 60 | // Render particles 61 | for ( var i = 0, n = physics.particles.length; i < n; i++ ) { 62 | 63 | var particle = physics.particles[i]; 64 | example.beginPath(); 65 | example.arc( particle.pos.x, particle.pos.y, particle.radius, 0, Math.PI * 2 ); 66 | example.fill(); 67 | } 68 | } 69 | 70 | example.mousemove = function() { 71 | avoidMouse.target.x = example.mouse.x; 72 | avoidMouse.target.y = example.mouse.y; 73 | } -------------------------------------------------------------------------------- /compiled/base.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Allows safe, dyamic creation of namespaces. 3 | */ 4 | 5 | var namespace; 6 | 7 | namespace = function(id) { 8 | var path, root, _i, _len, _ref, _ref1, _results; 9 | 10 | root = self; 11 | _ref = id.split('.'); 12 | _results = []; 13 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 14 | path = _ref[_i]; 15 | _results.push(root = (_ref1 = root[path]) != null ? _ref1 : root[path] = {}); 16 | } 17 | return _results; 18 | }; 19 | 20 | /* RequestAnimationFrame shim. 21 | */ 22 | 23 | 24 | (function() { 25 | var time, vendor, vendors, _i, _len; 26 | 27 | time = 0; 28 | vendors = ['ms', 'moz', 'webkit', 'o']; 29 | for (_i = 0, _len = vendors.length; _i < _len; _i++) { 30 | vendor = vendors[_i]; 31 | if (!(!window.requestAnimationFrame)) { 32 | continue; 33 | } 34 | window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame']; 35 | window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame']; 36 | } 37 | if (!window.requestAnimationFrame) { 38 | window.requestAnimationFrame = function(callback, element) { 39 | var delta, now, old; 40 | 41 | now = new Date().getTime(); 42 | delta = Math.max(0, 16 - (now - old)); 43 | setTimeout((function() { 44 | return callback(time + delta); 45 | }), delta); 46 | return old = now + delta; 47 | }; 48 | } 49 | if (!window.cancelAnimationFrame) { 50 | return window.cancelAnimationFrame = function(id) { 51 | return clearTimeout(id); 52 | }; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /compiled/behaviour/Attraction.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Attraction Behaviour 3 | */ 4 | 5 | var Attraction, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | Attraction = (function(_super) { 10 | __extends(Attraction, _super); 11 | 12 | function Attraction(target, radius, strength) { 13 | this.target = target != null ? target : new Vector(); 14 | this.radius = radius != null ? radius : 1000; 15 | this.strength = strength != null ? strength : 100.0; 16 | this._delta = new Vector(); 17 | this.setRadius(this.radius); 18 | Attraction.__super__.constructor.apply(this, arguments); 19 | } 20 | 21 | /* Sets the effective radius of the bahavious. 22 | */ 23 | 24 | 25 | Attraction.prototype.setRadius = function(radius) { 26 | this.radius = radius; 27 | return this.radiusSq = radius * radius; 28 | }; 29 | 30 | Attraction.prototype.apply = function(p, dt, index) { 31 | var distSq; 32 | 33 | (this._delta.copy(this.target)).sub(p.pos); 34 | distSq = this._delta.magSq(); 35 | if (distSq < this.radiusSq && distSq > 0.000001) { 36 | this._delta.norm().scale(1.0 - distSq / this.radiusSq); 37 | return p.acc.add(this._delta.scale(this.strength)); 38 | } 39 | }; 40 | 41 | return Attraction; 42 | 43 | })(Behaviour); 44 | -------------------------------------------------------------------------------- /compiled/behaviour/Behaviour.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Behaviour 3 | */ 4 | 5 | var Behaviour; 6 | 7 | Behaviour = (function() { 8 | Behaviour.GUID = 0; 9 | 10 | function Behaviour() { 11 | this.GUID = Behaviour.GUID++; 12 | this.interval = 1; 13 | } 14 | 15 | Behaviour.prototype.apply = function(p, dt, index) { 16 | var _name, _ref; 17 | 18 | return ((_ref = p[_name = '__behaviour' + this.GUID]) != null ? _ref : p[_name] = { 19 | counter: 0 20 | }).counter++; 21 | }; 22 | 23 | return Behaviour; 24 | 25 | })(); 26 | -------------------------------------------------------------------------------- /compiled/behaviour/Collision.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Collision Behaviour 3 | */ 4 | 5 | var Collision, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | Collision = (function(_super) { 10 | __extends(Collision, _super); 11 | 12 | function Collision(useMass, callback) { 13 | this.useMass = useMass != null ? useMass : true; 14 | this.callback = callback != null ? callback : null; 15 | this.pool = []; 16 | this._delta = new Vector(); 17 | Collision.__super__.constructor.apply(this, arguments); 18 | } 19 | 20 | Collision.prototype.apply = function(p, dt, index) { 21 | var dist, distSq, mt, o, overlap, r1, r2, radii, _i, _len, _ref, _results; 22 | 23 | _ref = this.pool.slice(index); 24 | _results = []; 25 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 26 | o = _ref[_i]; 27 | if (!(o !== p)) { 28 | continue; 29 | } 30 | (this._delta.copy(o.pos)).sub(p.pos); 31 | distSq = this._delta.magSq(); 32 | radii = p.radius + o.radius; 33 | if (distSq <= radii * radii) { 34 | dist = Math.sqrt(distSq); 35 | overlap = (p.radius + o.radius) - dist; 36 | overlap += 0.5; 37 | mt = p.mass + o.mass; 38 | r1 = this.useMass ? o.mass / mt : 0.5; 39 | r2 = this.useMass ? p.mass / mt : 0.5; 40 | p.pos.add(this._delta.clone().norm().scale(overlap * -r1)); 41 | o.pos.add(this._delta.norm().scale(overlap * r2)); 42 | _results.push(typeof this.callback === "function" ? this.callback(p, o, overlap) : void 0); 43 | } else { 44 | _results.push(void 0); 45 | } 46 | } 47 | return _results; 48 | }; 49 | 50 | return Collision; 51 | 52 | })(Behaviour); 53 | -------------------------------------------------------------------------------- /compiled/behaviour/ConstantForce.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Constant Force Behaviour 3 | */ 4 | 5 | var ConstantForce, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | ConstantForce = (function(_super) { 10 | __extends(ConstantForce, _super); 11 | 12 | function ConstantForce(force) { 13 | this.force = force != null ? force : new Vector(); 14 | ConstantForce.__super__.constructor.apply(this, arguments); 15 | } 16 | 17 | ConstantForce.prototype.apply = function(p, dt, index) { 18 | return p.acc.add(this.force); 19 | }; 20 | 21 | return ConstantForce; 22 | 23 | })(Behaviour); 24 | -------------------------------------------------------------------------------- /compiled/behaviour/EdgeBounce.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Edge Bounce Behaviour 3 | */ 4 | 5 | var EdgeBounce, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | EdgeBounce = (function(_super) { 10 | __extends(EdgeBounce, _super); 11 | 12 | function EdgeBounce(min, max) { 13 | this.min = min != null ? min : new Vector(); 14 | this.max = max != null ? max : new Vector(); 15 | EdgeBounce.__super__.constructor.apply(this, arguments); 16 | } 17 | 18 | EdgeBounce.prototype.apply = function(p, dt, index) { 19 | if (p.pos.x - p.radius < this.min.x) { 20 | p.pos.x = this.min.x + p.radius; 21 | } else if (p.pos.x + p.radius > this.max.x) { 22 | p.pos.x = this.max.x - p.radius; 23 | } 24 | if (p.pos.y - p.radius < this.min.y) { 25 | return p.pos.y = this.min.y + p.radius; 26 | } else if (p.pos.y + p.radius > this.max.y) { 27 | return p.pos.y = this.max.y - p.radius; 28 | } 29 | }; 30 | 31 | return EdgeBounce; 32 | 33 | })(Behaviour); 34 | -------------------------------------------------------------------------------- /compiled/behaviour/EdgeWrap.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Edge Wrap Behaviour 3 | */ 4 | 5 | var EdgeWrap, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | EdgeWrap = (function(_super) { 10 | __extends(EdgeWrap, _super); 11 | 12 | function EdgeWrap(min, max) { 13 | this.min = min != null ? min : new Vector(); 14 | this.max = max != null ? max : new Vector(); 15 | EdgeWrap.__super__.constructor.apply(this, arguments); 16 | } 17 | 18 | EdgeWrap.prototype.apply = function(p, dt, index) { 19 | if (p.pos.x + p.radius < this.min.x) { 20 | p.pos.x = this.max.x + p.radius; 21 | p.old.pos.x = p.pos.x; 22 | } else if (p.pos.x - p.radius > this.max.x) { 23 | p.pos.x = this.min.x - p.radius; 24 | p.old.pos.x = p.pos.x; 25 | } 26 | if (p.pos.y + p.radius < this.min.y) { 27 | p.pos.y = this.max.y + p.radius; 28 | return p.old.pos.y = p.pos.y; 29 | } else if (p.pos.y - p.radius > this.max.y) { 30 | p.pos.y = this.min.y - p.radius; 31 | return p.old.pos.y = p.pos.y; 32 | } 33 | }; 34 | 35 | return EdgeWrap; 36 | 37 | })(Behaviour); 38 | -------------------------------------------------------------------------------- /compiled/behaviour/Wander.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Wander Behaviour 3 | */ 4 | 5 | var Wander, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | Wander = (function(_super) { 10 | __extends(Wander, _super); 11 | 12 | function Wander(jitter, radius, strength) { 13 | this.jitter = jitter != null ? jitter : 0.5; 14 | this.radius = radius != null ? radius : 100; 15 | this.strength = strength != null ? strength : 1.0; 16 | this.theta = Math.random() * Math.PI * 2; 17 | Wander.__super__.constructor.apply(this, arguments); 18 | } 19 | 20 | Wander.prototype.apply = function(p, dt, index) { 21 | this.theta += (Math.random() - 0.5) * this.jitter * Math.PI * 2; 22 | p.acc.x += Math.cos(this.theta) * this.radius * this.strength; 23 | return p.acc.y += Math.sin(this.theta) * this.radius * this.strength; 24 | }; 25 | 26 | return Wander; 27 | 28 | })(Behaviour); 29 | -------------------------------------------------------------------------------- /compiled/demos/AttractionDemo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | var AttractionDemo, _ref, 3 | __hasProp = {}.hasOwnProperty, 4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 5 | 6 | AttractionDemo = (function(_super) { 7 | __extends(AttractionDemo, _super); 8 | 9 | function AttractionDemo() { 10 | _ref = AttractionDemo.__super__.constructor.apply(this, arguments); 11 | return _ref; 12 | } 13 | 14 | AttractionDemo.prototype.setup = function(full) { 15 | var attraction, bounds, collide, i, max, min, p, repulsion, _i, _results; 16 | 17 | if (full == null) { 18 | full = true; 19 | } 20 | AttractionDemo.__super__.setup.call(this, full); 21 | min = new Vector(0.0, 0.0); 22 | max = new Vector(this.width, this.height); 23 | bounds = new EdgeBounce(min, max); 24 | this.physics.integrator = new Verlet(); 25 | attraction = new Attraction(this.mouse.pos, 1200, 1200); 26 | repulsion = new Attraction(this.mouse.pos, 200, -2000); 27 | collide = new Collision(); 28 | max = full ? 400 : 200; 29 | _results = []; 30 | for (i = _i = 0; 0 <= max ? _i <= max : _i >= max; i = 0 <= max ? ++_i : --_i) { 31 | p = new Particle(Random(0.1, 3.0)); 32 | p.setRadius(p.mass * 4); 33 | p.moveTo(new Vector(Random(this.width), Random(this.height))); 34 | p.behaviours.push(attraction); 35 | p.behaviours.push(repulsion); 36 | p.behaviours.push(bounds); 37 | p.behaviours.push(collide); 38 | collide.pool.push(p); 39 | _results.push(this.physics.particles.push(p)); 40 | } 41 | return _results; 42 | }; 43 | 44 | return AttractionDemo; 45 | 46 | })(Demo); 47 | -------------------------------------------------------------------------------- /compiled/demos/BalloonDemo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* BalloonDemo 3 | */ 4 | 5 | var BalloonDemo, _ref, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | BalloonDemo = (function(_super) { 10 | __extends(BalloonDemo, _super); 11 | 12 | function BalloonDemo() { 13 | _ref = BalloonDemo.__super__.constructor.apply(this, arguments); 14 | return _ref; 15 | } 16 | 17 | BalloonDemo.prototype.setup = function(full) { 18 | var attraction, i, max, p, s, _i, _results; 19 | 20 | if (full == null) { 21 | full = true; 22 | } 23 | BalloonDemo.__super__.setup.apply(this, arguments); 24 | this.physics.integrator = new ImprovedEuler(); 25 | attraction = new Attraction(this.mouse.pos); 26 | max = full ? 400 : 200; 27 | _results = []; 28 | for (i = _i = 0; 0 <= max ? _i <= max : _i >= max; i = 0 <= max ? ++_i : --_i) { 29 | p = new Particle(Random(0.25, 4.0)); 30 | p.setRadius(p.mass * 8); 31 | p.behaviours.push(new Wander(0.2)); 32 | p.behaviours.push(attraction); 33 | p.moveTo(new Vector(Random(this.width), Random(this.height))); 34 | s = new Spring(this.mouse, p, Random(30, 300), 1.0); 35 | this.physics.particles.push(p); 36 | _results.push(this.physics.springs.push(s)); 37 | } 38 | return _results; 39 | }; 40 | 41 | return BalloonDemo; 42 | 43 | })(Demo); 44 | -------------------------------------------------------------------------------- /compiled/demos/BoundsDemo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* BoundsDemo 3 | */ 4 | 5 | var BoundsDemo, _ref, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | BoundsDemo = (function(_super) { 10 | __extends(BoundsDemo, _super); 11 | 12 | function BoundsDemo() { 13 | _ref = BoundsDemo.__super__.constructor.apply(this, arguments); 14 | return _ref; 15 | } 16 | 17 | BoundsDemo.prototype.setup = function() { 18 | var edge, i, max, min, p, _i, _results; 19 | 20 | BoundsDemo.__super__.setup.apply(this, arguments); 21 | min = new Vector(0.0, 0.0); 22 | max = new Vector(this.width, this.height); 23 | edge = new EdgeWrap(min, max); 24 | _results = []; 25 | for (i = _i = 0; _i <= 200; i = ++_i) { 26 | p = new Particle(Random(0.5, 4.0)); 27 | p.setRadius(p.mass * 5); 28 | p.moveTo(new Vector(Random(this.width), Random(this.height))); 29 | p.behaviours.push(new Wander(0.2, 120, Random(1.0, 2.0))); 30 | p.behaviours.push(edge); 31 | _results.push(this.physics.particles.push(p)); 32 | } 33 | return _results; 34 | }; 35 | 36 | return BoundsDemo; 37 | 38 | })(Demo); 39 | -------------------------------------------------------------------------------- /compiled/demos/ChainDemo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | var ChainDemo, _ref, 3 | __hasProp = {}.hasOwnProperty, 4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 5 | 6 | ChainDemo = (function(_super) { 7 | __extends(ChainDemo, _super); 8 | 9 | function ChainDemo() { 10 | _ref = ChainDemo.__super__.constructor.apply(this, arguments); 11 | return _ref; 12 | } 13 | 14 | ChainDemo.prototype.setup = function(full) { 15 | var center, edge, gap, i, max, min, op, p, s, wander, _i; 16 | 17 | if (full == null) { 18 | full = true; 19 | } 20 | ChainDemo.__super__.setup.apply(this, arguments); 21 | this.stiffness = 1.0; 22 | this.spacing = 2.0; 23 | this.physics.integrator = new Verlet(); 24 | this.physics.viscosity = 0.0001; 25 | this.mouse.setMass(1000); 26 | gap = 50.0; 27 | min = new Vector(-gap, -gap); 28 | max = new Vector(this.width + gap, this.height + gap); 29 | edge = new EdgeBounce(min, max); 30 | center = new Vector(this.width * 0.5, this.height * 0.5); 31 | wander = new Wander(0.05, 100.0, 80.0); 32 | max = full ? 2000 : 600; 33 | for (i = _i = 0; 0 <= max ? _i <= max : _i >= max; i = 0 <= max ? ++_i : --_i) { 34 | p = new Particle(6.0); 35 | p.colour = '#FFFFFF'; 36 | p.moveTo(center); 37 | p.setRadius(1.0); 38 | p.behaviours.push(wander); 39 | p.behaviours.push(edge); 40 | this.physics.particles.push(p); 41 | if (typeof op !== "undefined" && op !== null) { 42 | s = new Spring(op, p, this.spacing, this.stiffness); 43 | } else { 44 | s = new Spring(this.mouse, p, this.spacing, this.stiffness); 45 | } 46 | this.physics.springs.push(s); 47 | op = p; 48 | } 49 | return this.physics.springs.push(new Spring(this.mouse, p, this.spacing, this.stiffness)); 50 | }; 51 | 52 | return ChainDemo; 53 | 54 | })(Demo); 55 | -------------------------------------------------------------------------------- /compiled/demos/ClothDemo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | var ClothDemo, _ref, 3 | __hasProp = {}.hasOwnProperty, 4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 5 | 6 | ClothDemo = (function(_super) { 7 | __extends(ClothDemo, _super); 8 | 9 | function ClothDemo() { 10 | _ref = ClothDemo.__super__.constructor.apply(this, arguments); 11 | return _ref; 12 | } 13 | 14 | ClothDemo.prototype.setup = function(full) { 15 | var cell, cols, p, rows, s, size, stiffness, sx, sy, x, y, _i, _j; 16 | 17 | if (full == null) { 18 | full = true; 19 | } 20 | ClothDemo.__super__.setup.apply(this, arguments); 21 | this.renderer.renderParticles = false; 22 | this.physics.integrator = new Verlet(); 23 | this.physics.timestep = 1.0 / 200; 24 | this.mouse.setMass(10); 25 | this.gravity = new ConstantForce(new Vector(0.0, 80.0)); 26 | this.physics.behaviours.push(this.gravity); 27 | stiffness = 0.5; 28 | size = full ? 8 : 10; 29 | rows = full ? 30 : 25; 30 | cols = full ? 55 : 40; 31 | cell = []; 32 | sx = this.width * 0.5 - cols * size * 0.5; 33 | sy = this.height * 0.5 - rows * size * 0.5; 34 | for (x = _i = 0; 0 <= cols ? _i <= cols : _i >= cols; x = 0 <= cols ? ++_i : --_i) { 35 | cell[x] = []; 36 | for (y = _j = 0; 0 <= rows ? _j <= rows : _j >= rows; y = 0 <= rows ? ++_j : --_j) { 37 | p = new Particle(0.1); 38 | p.fixed = y === 0; 39 | p.moveTo(new Vector(sx + x * size, sy + y * size)); 40 | if (x > 0) { 41 | s = new Spring(p, cell[x - 1][y], size, stiffness); 42 | this.physics.springs.push(s); 43 | } 44 | if (y > 0) { 45 | s = new Spring(p, cell[x][y - 1], size, stiffness); 46 | this.physics.springs.push(s); 47 | } 48 | this.physics.particles.push(p); 49 | cell[x][y] = p; 50 | } 51 | } 52 | p = cell[Math.floor(cols / 2)][Math.floor(rows / 2)]; 53 | s = new Spring(this.mouse, p, 10, 1.0); 54 | this.physics.springs.push(s); 55 | cell[0][0].fixed = true; 56 | return cell[cols - 1][0].fixed = true; 57 | }; 58 | 59 | ClothDemo.prototype.step = function() { 60 | ClothDemo.__super__.step.apply(this, arguments); 61 | return this.gravity.force.x = 50 * Math.sin(new Date().getTime() * 0.0005); 62 | }; 63 | 64 | return ClothDemo; 65 | 66 | })(Demo); 67 | -------------------------------------------------------------------------------- /compiled/demos/CollisionDemo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* CollisionDemo 3 | */ 4 | 5 | var CollisionDemo, _ref, 6 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 7 | __hasProp = {}.hasOwnProperty, 8 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 9 | 10 | CollisionDemo = (function(_super) { 11 | __extends(CollisionDemo, _super); 12 | 13 | function CollisionDemo() { 14 | this.onCollision = __bind(this.onCollision, this); _ref = CollisionDemo.__super__.constructor.apply(this, arguments); 15 | return _ref; 16 | } 17 | 18 | CollisionDemo.prototype.setup = function(full) { 19 | var attraction, bounds, collide, i, max, min, p, prob, s, _i, _results; 20 | 21 | if (full == null) { 22 | full = true; 23 | } 24 | CollisionDemo.__super__.setup.apply(this, arguments); 25 | this.physics.integrator = new Verlet(); 26 | min = new Vector(0.0, 0.0); 27 | max = new Vector(this.width, this.height); 28 | bounds = new EdgeBounce(min, max); 29 | collide = new Collision; 30 | attraction = new Attraction(this.mouse.pos, 2000, 1400); 31 | max = full ? 350 : 150; 32 | prob = full ? 0.35 : 0.5; 33 | _results = []; 34 | for (i = _i = 0; 0 <= max ? _i <= max : _i >= max; i = 0 <= max ? ++_i : --_i) { 35 | p = new Particle(Random(0.5, 4.0)); 36 | p.setRadius(p.mass * 4); 37 | p.moveTo(new Vector(Random(this.width), Random(this.height))); 38 | if (Random.bool(prob)) { 39 | s = new Spring(this.mouse, p, Random(120, 180), 0.8); 40 | this.physics.springs.push(s); 41 | } else { 42 | p.behaviours.push(attraction); 43 | } 44 | collide.pool.push(p); 45 | p.behaviours.push(collide); 46 | p.behaviours.push(bounds); 47 | _results.push(this.physics.particles.push(p)); 48 | } 49 | return _results; 50 | }; 51 | 52 | CollisionDemo.prototype.onCollision = function(p1, p2) {}; 53 | 54 | return CollisionDemo; 55 | 56 | })(Demo); 57 | -------------------------------------------------------------------------------- /compiled/demos/Demo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Demo 3 | */ 4 | 5 | var Demo, 6 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 7 | 8 | Demo = (function() { 9 | Demo.COLOURS = ['DC0048', 'F14646', '4AE6A9', '7CFF3F', '4EC9D9', 'E4272E']; 10 | 11 | function Demo() { 12 | this.mousemove = __bind(this.mousemove, this); 13 | this.resize = __bind(this.resize, this); this.physics = new Physics(); 14 | this.mouse = new Particle(); 15 | this.mouse.fixed = true; 16 | this.height = window.innerHeight; 17 | this.width = window.innerWidth; 18 | this.renderTime = 0; 19 | this.counter = 0; 20 | } 21 | 22 | Demo.prototype.setup = function(full) { 23 | if (full == null) { 24 | return full = true; 25 | } 26 | /* Override and add paticles / springs here 27 | */ 28 | 29 | }; 30 | 31 | /* Initialise the demo (override). 32 | */ 33 | 34 | 35 | Demo.prototype.init = function(container, renderer) { 36 | var particle, _i, _len, _ref, _ref1; 37 | 38 | this.container = container; 39 | this.renderer = renderer != null ? renderer : new WebGLRenderer(); 40 | this.setup(renderer.gl != null); 41 | _ref = this.physics.particles; 42 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 43 | particle = _ref[_i]; 44 | if ((_ref1 = particle.colour) == null) { 45 | particle.colour = Random.item(Demo.COLOURS); 46 | } 47 | } 48 | document.addEventListener('touchmove', this.mousemove, false); 49 | document.addEventListener('mousemove', this.mousemove, false); 50 | document.addEventListener('resize', this.resize, false); 51 | this.container.appendChild(this.renderer.domElement); 52 | this.renderer.mouse = this.mouse; 53 | this.renderer.init(this.physics); 54 | return this.resize(); 55 | }; 56 | 57 | /* Handler for window resize event. 58 | */ 59 | 60 | 61 | Demo.prototype.resize = function(event) { 62 | this.width = window.innerWidth; 63 | this.height = window.innerHeight; 64 | return this.renderer.setSize(this.width, this.height); 65 | }; 66 | 67 | /* Update loop. 68 | */ 69 | 70 | 71 | Demo.prototype.step = function() { 72 | this.physics.step(); 73 | if ((this.renderer.gl != null) || ++this.counter % 3 === 0) { 74 | return this.renderer.render(this.physics); 75 | } 76 | }; 77 | 78 | /* Clean up after yourself. 79 | */ 80 | 81 | 82 | Demo.prototype.destroy = function() { 83 | var error; 84 | 85 | document.removeEventListener('touchmove', this.mousemove, false); 86 | document.removeEventListener('mousemove', this.mousemove, false); 87 | document.removeEventListener('resize', this.resize, false); 88 | try { 89 | container.removeChild(this.renderer.domElement); 90 | } catch (_error) { 91 | error = _error; 92 | } 93 | this.renderer.destroy(); 94 | this.physics.destroy(); 95 | this.renderer = null; 96 | this.physics = null; 97 | return this.mouse = null; 98 | }; 99 | 100 | /* Handler for window mousemove event. 101 | */ 102 | 103 | 104 | Demo.prototype.mousemove = function(event) { 105 | var touch; 106 | 107 | event.preventDefault(); 108 | if (event.touches && !!event.touches.length) { 109 | touch = event.touches[0]; 110 | return this.mouse.pos.set(touch.pageX, touch.pageY); 111 | } else { 112 | return this.mouse.pos.set(event.clientX, event.clientY); 113 | } 114 | }; 115 | 116 | return Demo; 117 | 118 | })(); 119 | -------------------------------------------------------------------------------- /compiled/demos/renderer/CanvasRenderer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Canvas Renderer 3 | */ 4 | 5 | var CanvasRenderer, 6 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 7 | __hasProp = {}.hasOwnProperty, 8 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 9 | 10 | CanvasRenderer = (function(_super) { 11 | __extends(CanvasRenderer, _super); 12 | 13 | function CanvasRenderer() { 14 | this.setSize = __bind(this.setSize, this); CanvasRenderer.__super__.constructor.apply(this, arguments); 15 | this.canvas = document.createElement('canvas'); 16 | this.ctx = this.canvas.getContext('2d'); 17 | this.domElement = this.canvas; 18 | } 19 | 20 | CanvasRenderer.prototype.init = function(physics) { 21 | return CanvasRenderer.__super__.init.call(this, physics); 22 | }; 23 | 24 | CanvasRenderer.prototype.render = function(physics) { 25 | var TWO_PI, dir, p, s, time, vel, _i, _j, _len, _len1, _ref, _ref1; 26 | 27 | CanvasRenderer.__super__.render.call(this, physics); 28 | time = new Date().getTime(); 29 | vel = new Vector(); 30 | dir = new Vector(); 31 | this.canvas.width = this.canvas.width; 32 | this.ctx.globalCompositeOperation = 'lighter'; 33 | this.ctx.lineWidth = 1; 34 | if (this.renderParticles) { 35 | TWO_PI = Math.PI * 2; 36 | _ref = physics.particles; 37 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 38 | p = _ref[_i]; 39 | this.ctx.beginPath(); 40 | this.ctx.arc(p.pos.x, p.pos.y, p.radius, 0, TWO_PI, false); 41 | this.ctx.fillStyle = '#' + (p.colour || 'FFFFFF'); 42 | this.ctx.fill(); 43 | } 44 | } 45 | if (this.renderSprings) { 46 | this.ctx.strokeStyle = 'rgba(255,255,255,0.1)'; 47 | this.ctx.beginPath(); 48 | _ref1 = physics.springs; 49 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 50 | s = _ref1[_j]; 51 | this.ctx.moveTo(s.p1.pos.x, s.p1.pos.y); 52 | this.ctx.lineTo(s.p2.pos.x, s.p2.pos.y); 53 | } 54 | this.ctx.stroke(); 55 | } 56 | if (this.renderMouse) { 57 | this.ctx.fillStyle = 'rgba(255,255,255,0.1)'; 58 | this.ctx.beginPath(); 59 | this.ctx.arc(this.mouse.pos.x, this.mouse.pos.y, 20, 0, TWO_PI); 60 | this.ctx.fill(); 61 | } 62 | return this.renderTime = new Date().getTime() - time; 63 | }; 64 | 65 | CanvasRenderer.prototype.setSize = function(width, height) { 66 | this.width = width; 67 | this.height = height; 68 | CanvasRenderer.__super__.setSize.call(this, this.width, this.height); 69 | this.canvas.width = this.width; 70 | return this.canvas.height = this.height; 71 | }; 72 | 73 | return CanvasRenderer; 74 | 75 | })(Renderer); 76 | -------------------------------------------------------------------------------- /compiled/demos/renderer/DOMRenderer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* DOM Renderer 3 | */ 4 | 5 | /* 6 | 7 | Updating styles: 8 | 9 | Nodes 10 | */ 11 | 12 | var DOMRenderer, 13 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 14 | __hasProp = {}.hasOwnProperty, 15 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 16 | 17 | DOMRenderer = (function(_super) { 18 | __extends(DOMRenderer, _super); 19 | 20 | function DOMRenderer() { 21 | this.setSize = __bind(this.setSize, this); DOMRenderer.__super__.constructor.apply(this, arguments); 22 | this.useGPU = true; 23 | this.domElement = document.createElement('div'); 24 | this.canvas = document.createElement('canvas'); 25 | this.ctx = this.canvas.getContext('2d'); 26 | this.canvas.style.position = 'absolute'; 27 | this.canvas.style.left = 0; 28 | this.canvas.style.top = 0; 29 | this.domElement.style.pointerEvents = 'none'; 30 | this.domElement.appendChild(this.canvas); 31 | } 32 | 33 | DOMRenderer.prototype.init = function(physics) { 34 | var el, mr, p, st, _i, _len, _ref; 35 | 36 | DOMRenderer.__super__.init.call(this, physics); 37 | _ref = physics.particles; 38 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 39 | p = _ref[_i]; 40 | el = document.createElement('span'); 41 | st = el.style; 42 | st.backgroundColor = p.colour; 43 | st.borderRadius = p.radius; 44 | st.marginLeft = -p.radius; 45 | st.marginTop = -p.radius; 46 | st.position = 'absolute'; 47 | st.display = 'block'; 48 | st.opacity = 0.85; 49 | st.height = p.radius * 2; 50 | st.width = p.radius * 2; 51 | this.domElement.appendChild(el); 52 | p.domElement = el; 53 | } 54 | el = document.createElement('span'); 55 | st = el.style; 56 | mr = 20; 57 | st.backgroundColor = '#ffffff'; 58 | st.borderRadius = mr; 59 | st.marginLeft = -mr; 60 | st.marginTop = -mr; 61 | st.position = 'absolute'; 62 | st.display = 'block'; 63 | st.opacity = 0.1; 64 | st.height = mr * 2; 65 | st.width = mr * 2; 66 | this.domElement.appendChild(el); 67 | return this.mouse.domElement = el; 68 | }; 69 | 70 | DOMRenderer.prototype.render = function(physics) { 71 | var p, s, time, _i, _j, _len, _len1, _ref, _ref1; 72 | 73 | DOMRenderer.__super__.render.call(this, physics); 74 | time = new Date().getTime(); 75 | if (this.renderParticles) { 76 | _ref = physics.particles; 77 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 78 | p = _ref[_i]; 79 | if (this.useGPU) { 80 | p.domElement.style.WebkitTransform = "translate3d(" + (p.pos.x | 0) + "px," + (p.pos.y | 0) + "px,0px)"; 81 | } else { 82 | p.domElement.style.left = p.pos.x; 83 | p.domElement.style.top = p.pos.y; 84 | } 85 | } 86 | } 87 | if (this.renderSprings) { 88 | this.canvas.width = this.canvas.width; 89 | this.ctx.strokeStyle = 'rgba(255,255,255,0.1)'; 90 | this.ctx.beginPath(); 91 | _ref1 = physics.springs; 92 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 93 | s = _ref1[_j]; 94 | this.ctx.moveTo(s.p1.pos.x, s.p1.pos.y); 95 | this.ctx.lineTo(s.p2.pos.x, s.p2.pos.y); 96 | } 97 | this.ctx.stroke(); 98 | } 99 | if (this.renderMouse) { 100 | if (this.useGPU) { 101 | this.mouse.domElement.style.WebkitTransform = "translate3d(" + (this.mouse.pos.x | 0) + "px," + (this.mouse.pos.y | 0) + "px,0px)"; 102 | } else { 103 | this.mouse.domElement.style.left = this.mouse.pos.x; 104 | this.mouse.domElement.style.top = this.mouse.pos.y; 105 | } 106 | } 107 | return this.renderTime = new Date().getTime() - time; 108 | }; 109 | 110 | DOMRenderer.prototype.setSize = function(width, height) { 111 | this.width = width; 112 | this.height = height; 113 | DOMRenderer.__super__.setSize.call(this, this.width, this.height); 114 | this.canvas.width = this.width; 115 | return this.canvas.height = this.height; 116 | }; 117 | 118 | DOMRenderer.prototype.destroy = function() { 119 | var _results; 120 | 121 | _results = []; 122 | while (this.domElement.hasChildNodes()) { 123 | _results.push(this.domElement.removeChild(this.domElement.lastChild)); 124 | } 125 | return _results; 126 | }; 127 | 128 | return DOMRenderer; 129 | 130 | })(Renderer); 131 | -------------------------------------------------------------------------------- /compiled/demos/renderer/Renderer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Base Renderer 3 | */ 4 | 5 | var Renderer, 6 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 7 | 8 | Renderer = (function() { 9 | function Renderer() { 10 | this.setSize = __bind(this.setSize, this); this.width = 0; 11 | this.height = 0; 12 | this.renderParticles = true; 13 | this.renderSprings = true; 14 | this.renderMouse = true; 15 | this.initialized = false; 16 | this.renderTime = 0; 17 | } 18 | 19 | Renderer.prototype.init = function(physics) { 20 | return this.initialized = true; 21 | }; 22 | 23 | Renderer.prototype.render = function(physics) { 24 | if (!this.initialized) { 25 | return this.init(physics); 26 | } 27 | }; 28 | 29 | Renderer.prototype.setSize = function(width, height) { 30 | this.width = width; 31 | this.height = height; 32 | }; 33 | 34 | Renderer.prototype.destroy = function() {}; 35 | 36 | return Renderer; 37 | 38 | })(); 39 | -------------------------------------------------------------------------------- /compiled/demos/renderer/WebGLRenderer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* WebGL Renderer 3 | */ 4 | 5 | var WebGLRenderer, 6 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 7 | __hasProp = {}.hasOwnProperty, 8 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 9 | 10 | WebGLRenderer = (function(_super) { 11 | __extends(WebGLRenderer, _super); 12 | 13 | WebGLRenderer.PARTICLE_VS = '\nuniform vec2 viewport;\nattribute vec3 position;\nattribute float radius;\nattribute vec4 colour;\nvarying vec4 tint;\n\nvoid main() {\n\n // convert the rectangle from pixels to 0.0 to 1.0\n vec2 zeroToOne = position.xy / viewport;\n zeroToOne.y = 1.0 - zeroToOne.y;\n\n // convert from 0->1 to 0->2\n vec2 zeroToTwo = zeroToOne * 2.0;\n\n // convert from 0->2 to -1->+1 (clipspace)\n vec2 clipSpace = zeroToTwo - 1.0;\n\n tint = colour;\n\n gl_Position = vec4(clipSpace, 0, 1);\n gl_PointSize = radius * 2.0;\n}'; 14 | 15 | WebGLRenderer.PARTICLE_FS = '\nprecision mediump float;\n\nuniform sampler2D texture;\nvarying vec4 tint;\n\nvoid main() {\n gl_FragColor = texture2D(texture, gl_PointCoord) * tint;\n}'; 16 | 17 | WebGLRenderer.SPRING_VS = '\nuniform vec2 viewport;\nattribute vec3 position;\n\nvoid main() {\n\n // convert the rectangle from pixels to 0.0 to 1.0\n vec2 zeroToOne = position.xy / viewport;\n zeroToOne.y = 1.0 - zeroToOne.y;\n\n // convert from 0->1 to 0->2\n vec2 zeroToTwo = zeroToOne * 2.0;\n\n // convert from 0->2 to -1->+1 (clipspace)\n vec2 clipSpace = zeroToTwo - 1.0;\n\n gl_Position = vec4(clipSpace, 0, 1);\n}'; 18 | 19 | WebGLRenderer.SPRING_FS = '\nvoid main() {\n gl_FragColor = vec4(1.0, 1.0, 1.0, 0.1);\n}'; 20 | 21 | function WebGLRenderer(usePointSprites) { 22 | var error; 23 | 24 | this.usePointSprites = usePointSprites != null ? usePointSprites : true; 25 | this.setSize = __bind(this.setSize, this); 26 | WebGLRenderer.__super__.constructor.apply(this, arguments); 27 | this.particlePositionBuffer = null; 28 | this.particleRadiusBuffer = null; 29 | this.particleColourBuffer = null; 30 | this.particleTexture = null; 31 | this.particleShader = null; 32 | this.springPositionBuffer = null; 33 | this.springShader = null; 34 | this.canvas = document.createElement('canvas'); 35 | try { 36 | this.gl = this.canvas.getContext('experimental-webgl'); 37 | } catch (_error) { 38 | error = _error; 39 | } finally { 40 | if (!this.gl) { 41 | return new CanvasRenderer(); 42 | } 43 | } 44 | this.domElement = this.canvas; 45 | } 46 | 47 | WebGLRenderer.prototype.init = function(physics) { 48 | WebGLRenderer.__super__.init.call(this, physics); 49 | this.initShaders(); 50 | this.initBuffers(physics); 51 | this.particleTexture = this.createParticleTextureData(); 52 | this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); 53 | return this.gl.enable(this.gl.BLEND); 54 | }; 55 | 56 | WebGLRenderer.prototype.initShaders = function() { 57 | this.particleShader = this.createShaderProgram(WebGLRenderer.PARTICLE_VS, WebGLRenderer.PARTICLE_FS); 58 | this.springShader = this.createShaderProgram(WebGLRenderer.SPRING_VS, WebGLRenderer.SPRING_FS); 59 | this.particleShader.uniforms = { 60 | viewport: this.gl.getUniformLocation(this.particleShader, 'viewport') 61 | }; 62 | this.springShader.uniforms = { 63 | viewport: this.gl.getUniformLocation(this.springShader, 'viewport') 64 | }; 65 | this.particleShader.attributes = { 66 | position: this.gl.getAttribLocation(this.particleShader, 'position'), 67 | radius: this.gl.getAttribLocation(this.particleShader, 'radius'), 68 | colour: this.gl.getAttribLocation(this.particleShader, 'colour') 69 | }; 70 | this.springShader.attributes = { 71 | position: this.gl.getAttribLocation(this.springShader, 'position') 72 | }; 73 | return console.log(this.particleShader); 74 | }; 75 | 76 | WebGLRenderer.prototype.initBuffers = function(physics) { 77 | var a, b, colours, g, particle, r, radii, rgba, _i, _len, _ref; 78 | 79 | colours = []; 80 | radii = []; 81 | this.particlePositionBuffer = this.gl.createBuffer(); 82 | this.springPositionBuffer = this.gl.createBuffer(); 83 | this.particleColourBuffer = this.gl.createBuffer(); 84 | this.particleRadiusBuffer = this.gl.createBuffer(); 85 | _ref = physics.particles; 86 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 87 | particle = _ref[_i]; 88 | rgba = (particle.colour || '#FFFFFF').match(/[\dA-F]{2}/gi); 89 | r = (parseInt(rgba[0], 16)) || 255; 90 | g = (parseInt(rgba[1], 16)) || 255; 91 | b = (parseInt(rgba[2], 16)) || 255; 92 | a = (parseInt(rgba[3], 16)) || 255; 93 | colours.push(r / 255, g / 255, b / 255, a / 255); 94 | radii.push(particle.radius || 32); 95 | } 96 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.particleColourBuffer); 97 | this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(colours), this.gl.STATIC_DRAW); 98 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.particleRadiusBuffer); 99 | return this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(radii), this.gl.STATIC_DRAW); 100 | }; 101 | 102 | WebGLRenderer.prototype.createParticleTextureData = function(size) { 103 | var canvas, ctx, rad, texture; 104 | 105 | if (size == null) { 106 | size = 128; 107 | } 108 | canvas = document.createElement('canvas'); 109 | canvas.width = canvas.height = size; 110 | ctx = canvas.getContext('2d'); 111 | rad = size * 0.5; 112 | ctx.beginPath(); 113 | ctx.arc(rad, rad, rad, 0, Math.PI * 2, false); 114 | ctx.closePath(); 115 | ctx.fillStyle = '#FFF'; 116 | ctx.fill(); 117 | texture = this.gl.createTexture(); 118 | this.setupTexture(texture, canvas); 119 | return texture; 120 | }; 121 | 122 | WebGLRenderer.prototype.loadTexture = function(source) { 123 | var texture, 124 | _this = this; 125 | 126 | texture = this.gl.createTexture(); 127 | texture.image = new Image(); 128 | texture.image.onload = function() { 129 | return _this.setupTexture(texture, texture.image); 130 | }; 131 | texture.image.src = source; 132 | return texture; 133 | }; 134 | 135 | WebGLRenderer.prototype.setupTexture = function(texture, data) { 136 | this.gl.bindTexture(this.gl.TEXTURE_2D, texture); 137 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, data); 138 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); 139 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); 140 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); 141 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); 142 | this.gl.generateMipmap(this.gl.TEXTURE_2D); 143 | this.gl.bindTexture(this.gl.TEXTURE_2D, null); 144 | return texture; 145 | }; 146 | 147 | WebGLRenderer.prototype.createShaderProgram = function(_vs, _fs) { 148 | var fs, prog, vs; 149 | 150 | vs = this.gl.createShader(this.gl.VERTEX_SHADER); 151 | fs = this.gl.createShader(this.gl.FRAGMENT_SHADER); 152 | this.gl.shaderSource(vs, _vs); 153 | this.gl.shaderSource(fs, _fs); 154 | this.gl.compileShader(vs); 155 | this.gl.compileShader(fs); 156 | if (!this.gl.getShaderParameter(vs, this.gl.COMPILE_STATUS)) { 157 | alert(this.gl.getShaderInfoLog(vs)); 158 | null; 159 | } 160 | if (!this.gl.getShaderParameter(fs, this.gl.COMPILE_STATUS)) { 161 | alert(this.gl.getShaderInfoLog(fs)); 162 | null; 163 | } 164 | prog = this.gl.createProgram(); 165 | this.gl.attachShader(prog, vs); 166 | this.gl.attachShader(prog, fs); 167 | this.gl.linkProgram(prog); 168 | return prog; 169 | }; 170 | 171 | WebGLRenderer.prototype.setSize = function(width, height) { 172 | this.width = width; 173 | this.height = height; 174 | WebGLRenderer.__super__.setSize.call(this, this.width, this.height); 175 | this.canvas.width = this.width; 176 | this.canvas.height = this.height; 177 | this.gl.viewport(0, 0, this.width, this.height); 178 | this.gl.useProgram(this.particleShader); 179 | this.gl.uniform2fv(this.particleShader.uniforms.viewport, new Float32Array([this.width, this.height])); 180 | this.gl.useProgram(this.springShader); 181 | return this.gl.uniform2fv(this.springShader.uniforms.viewport, new Float32Array([this.width, this.height])); 182 | }; 183 | 184 | WebGLRenderer.prototype.render = function(physics) { 185 | var p, s, vertices, _i, _j, _len, _len1, _ref, _ref1; 186 | 187 | WebGLRenderer.__super__.render.apply(this, arguments); 188 | this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); 189 | if (this.renderParticles) { 190 | vertices = []; 191 | _ref = physics.particles; 192 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 193 | p = _ref[_i]; 194 | vertices.push(p.pos.x, p.pos.y, 0.0); 195 | } 196 | this.gl.activeTexture(this.gl.TEXTURE0); 197 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.particleTexture); 198 | this.gl.useProgram(this.particleShader); 199 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.particlePositionBuffer); 200 | this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertices), this.gl.STATIC_DRAW); 201 | this.gl.vertexAttribPointer(this.particleShader.attributes.position, 3, this.gl.FLOAT, false, 0, 0); 202 | this.gl.enableVertexAttribArray(this.particleShader.attributes.position); 203 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.particleColourBuffer); 204 | this.gl.enableVertexAttribArray(this.particleShader.attributes.colour); 205 | this.gl.vertexAttribPointer(this.particleShader.attributes.colour, 4, this.gl.FLOAT, false, 0, 0); 206 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.particleRadiusBuffer); 207 | this.gl.enableVertexAttribArray(this.particleShader.attributes.radius); 208 | this.gl.vertexAttribPointer(this.particleShader.attributes.radius, 1, this.gl.FLOAT, false, 0, 0); 209 | this.gl.drawArrays(this.gl.POINTS, 0, vertices.length / 3); 210 | } 211 | if (this.renderSprings && physics.springs.length > 0) { 212 | vertices = []; 213 | _ref1 = physics.springs; 214 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 215 | s = _ref1[_j]; 216 | vertices.push(s.p1.pos.x, s.p1.pos.y, 0.0); 217 | vertices.push(s.p2.pos.x, s.p2.pos.y, 0.0); 218 | } 219 | this.gl.useProgram(this.springShader); 220 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.springPositionBuffer); 221 | this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertices), this.gl.STATIC_DRAW); 222 | this.gl.vertexAttribPointer(this.springShader.attributes.position, 3, this.gl.FLOAT, false, 0, 0); 223 | this.gl.enableVertexAttribArray(this.springShader.attributes.position); 224 | return this.gl.drawArrays(this.gl.LINES, 0, vertices.length / 3); 225 | } 226 | }; 227 | 228 | WebGLRenderer.prototype.destroy = function() {}; 229 | 230 | return WebGLRenderer; 231 | 232 | })(Renderer); 233 | -------------------------------------------------------------------------------- /compiled/engine/Particle.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Particle 3 | */ 4 | 5 | var Particle; 6 | 7 | Particle = (function() { 8 | Particle.GUID = 0; 9 | 10 | function Particle(mass) { 11 | this.mass = mass != null ? mass : 1.0; 12 | this.id = 'p' + Particle.GUID++; 13 | this.setMass(this.mass); 14 | this.setRadius(1.0); 15 | this.fixed = false; 16 | this.behaviours = []; 17 | this.pos = new Vector(); 18 | this.vel = new Vector(); 19 | this.acc = new Vector(); 20 | this.old = { 21 | pos: new Vector(), 22 | vel: new Vector(), 23 | acc: new Vector() 24 | }; 25 | } 26 | 27 | /* Moves the particle to a given location vector. 28 | */ 29 | 30 | 31 | Particle.prototype.moveTo = function(pos) { 32 | this.pos.copy(pos); 33 | return this.old.pos.copy(pos); 34 | }; 35 | 36 | /* Sets the mass of the particle. 37 | */ 38 | 39 | 40 | Particle.prototype.setMass = function(mass) { 41 | this.mass = mass != null ? mass : 1.0; 42 | return this.massInv = 1.0 / this.mass; 43 | }; 44 | 45 | /* Sets the radius of the particle. 46 | */ 47 | 48 | 49 | Particle.prototype.setRadius = function(radius) { 50 | this.radius = radius != null ? radius : 1.0; 51 | return this.radiusSq = this.radius * this.radius; 52 | }; 53 | 54 | /* Applies all behaviours to derive new force. 55 | */ 56 | 57 | 58 | Particle.prototype.update = function(dt, index) { 59 | var behaviour, _i, _len, _ref, _results; 60 | 61 | if (!this.fixed) { 62 | _ref = this.behaviours; 63 | _results = []; 64 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 65 | behaviour = _ref[_i]; 66 | _results.push(behaviour.apply(this, dt, index)); 67 | } 68 | return _results; 69 | } 70 | }; 71 | 72 | return Particle; 73 | 74 | })(); 75 | -------------------------------------------------------------------------------- /compiled/engine/Physics.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Physics Engine 3 | */ 4 | 5 | var Physics; 6 | 7 | Physics = (function() { 8 | function Physics(integrator) { 9 | this.integrator = integrator != null ? integrator : new Euler(); 10 | this.timestep = 1.0 / 60; 11 | this.viscosity = 0.005; 12 | this.behaviours = []; 13 | this._time = 0.0; 14 | this._step = 0.0; 15 | this._clock = null; 16 | this._buffer = 0.0; 17 | this._maxSteps = 4; 18 | this.particles = []; 19 | this.springs = []; 20 | } 21 | 22 | /* Performs a numerical integration step. 23 | */ 24 | 25 | 26 | Physics.prototype.integrate = function(dt) { 27 | var behaviour, drag, index, particle, spring, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; 28 | 29 | drag = 1.0 - this.viscosity; 30 | _ref = this.particles; 31 | for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) { 32 | particle = _ref[index]; 33 | _ref1 = this.behaviours; 34 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 35 | behaviour = _ref1[_j]; 36 | behaviour.apply(particle, dt, index); 37 | } 38 | particle.update(dt, index); 39 | } 40 | this.integrator.integrate(this.particles, dt, drag); 41 | _ref2 = this.springs; 42 | _results = []; 43 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { 44 | spring = _ref2[_k]; 45 | _results.push(spring.apply()); 46 | } 47 | return _results; 48 | }; 49 | 50 | /* Steps the system. 51 | */ 52 | 53 | 54 | Physics.prototype.step = function() { 55 | var delta, i, time, _ref; 56 | 57 | if ((_ref = this._clock) == null) { 58 | this._clock = new Date().getTime(); 59 | } 60 | time = new Date().getTime(); 61 | delta = time - this._clock; 62 | if (delta <= 0.0) { 63 | return; 64 | } 65 | delta *= 0.001; 66 | this._clock = time; 67 | this._buffer += delta; 68 | i = 0; 69 | while (this._buffer >= this.timestep && ++i < this._maxSteps) { 70 | this.integrate(this.timestep); 71 | this._buffer -= this.timestep; 72 | this._time += this.timestep; 73 | } 74 | return this._step = new Date().getTime() - time; 75 | }; 76 | 77 | /* Clean up after yourself. 78 | */ 79 | 80 | 81 | Physics.prototype.destroy = function() { 82 | this.integrator = null; 83 | this.particles = null; 84 | return this.springs = null; 85 | }; 86 | 87 | return Physics; 88 | 89 | })(); 90 | -------------------------------------------------------------------------------- /compiled/engine/Spring.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Spring 3 | */ 4 | 5 | var Spring; 6 | 7 | Spring = (function() { 8 | function Spring(p1, p2, restLength, stiffness) { 9 | this.p1 = p1; 10 | this.p2 = p2; 11 | this.restLength = restLength != null ? restLength : 100; 12 | this.stiffness = stiffness != null ? stiffness : 1.0; 13 | this._delta = new Vector(); 14 | } 15 | 16 | Spring.prototype.apply = function() { 17 | var dist, force; 18 | 19 | (this._delta.copy(this.p2.pos)).sub(this.p1.pos); 20 | dist = this._delta.mag() + 0.000001; 21 | force = (dist - this.restLength) / (dist * (this.p1.massInv + this.p2.massInv)) * this.stiffness; 22 | if (!this.p1.fixed) { 23 | this.p1.pos.add(this._delta.clone().scale(force * this.p1.massInv)); 24 | } 25 | if (!this.p2.fixed) { 26 | return this.p2.pos.add(this._delta.scale(-force * this.p2.massInv)); 27 | } 28 | }; 29 | 30 | return Spring; 31 | 32 | })(); 33 | -------------------------------------------------------------------------------- /compiled/engine/integrator/Euler.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Euler Integrator 3 | */ 4 | 5 | var Euler, _ref, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | Euler = (function(_super) { 10 | __extends(Euler, _super); 11 | 12 | function Euler() { 13 | _ref = Euler.__super__.constructor.apply(this, arguments); 14 | return _ref; 15 | } 16 | 17 | Euler.prototype.integrate = function(particles, dt, drag) { 18 | var p, vel, _i, _len, _results; 19 | 20 | vel = new Vector(); 21 | _results = []; 22 | for (_i = 0, _len = particles.length; _i < _len; _i++) { 23 | p = particles[_i]; 24 | if (!(!p.fixed)) { 25 | continue; 26 | } 27 | p.old.pos.copy(p.pos); 28 | p.acc.scale(p.massInv); 29 | vel.copy(p.vel); 30 | p.vel.add(p.acc.scale(dt)); 31 | p.pos.add(vel.scale(dt)); 32 | if (drag) { 33 | p.vel.scale(drag); 34 | } 35 | _results.push(p.acc.clear()); 36 | } 37 | return _results; 38 | }; 39 | 40 | return Euler; 41 | 42 | })(Integrator); 43 | -------------------------------------------------------------------------------- /compiled/engine/integrator/ImprovedEuler.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Improved Euler Integrator 3 | */ 4 | 5 | var ImprovedEuler, _ref, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | ImprovedEuler = (function(_super) { 10 | __extends(ImprovedEuler, _super); 11 | 12 | function ImprovedEuler() { 13 | _ref = ImprovedEuler.__super__.constructor.apply(this, arguments); 14 | return _ref; 15 | } 16 | 17 | ImprovedEuler.prototype.integrate = function(particles, dt, drag) { 18 | var acc, dtSq, p, vel, _i, _len, _results; 19 | 20 | acc = new Vector(); 21 | vel = new Vector(); 22 | dtSq = dt * dt; 23 | _results = []; 24 | for (_i = 0, _len = particles.length; _i < _len; _i++) { 25 | p = particles[_i]; 26 | if (!(!p.fixed)) { 27 | continue; 28 | } 29 | p.old.pos.copy(p.pos); 30 | p.acc.scale(p.massInv); 31 | vel.copy(p.vel); 32 | acc.copy(p.acc); 33 | p.pos.add((vel.scale(dt)).add(acc.scale(0.5 * dtSq))); 34 | p.vel.add(p.acc.scale(dt)); 35 | if (drag) { 36 | p.vel.scale(drag); 37 | } 38 | _results.push(p.acc.clear()); 39 | } 40 | return _results; 41 | }; 42 | 43 | return ImprovedEuler; 44 | 45 | })(Integrator); 46 | -------------------------------------------------------------------------------- /compiled/engine/integrator/Integrator.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Integrator 3 | */ 4 | 5 | var Integrator; 6 | 7 | Integrator = (function() { 8 | function Integrator() {} 9 | 10 | Integrator.prototype.integrate = function(particles, dt) {}; 11 | 12 | return Integrator; 13 | 14 | })(); 15 | -------------------------------------------------------------------------------- /compiled/engine/integrator/Verlet.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Velocity Verlet Integrator 3 | */ 4 | 5 | var Verlet, _ref, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | Verlet = (function(_super) { 10 | __extends(Verlet, _super); 11 | 12 | function Verlet() { 13 | _ref = Verlet.__super__.constructor.apply(this, arguments); 14 | return _ref; 15 | } 16 | 17 | Verlet.prototype.integrate = function(particles, dt, drag) { 18 | var dtSq, p, pos, _i, _len, _results; 19 | 20 | pos = new Vector(); 21 | dtSq = dt * dt; 22 | _results = []; 23 | for (_i = 0, _len = particles.length; _i < _len; _i++) { 24 | p = particles[_i]; 25 | if (!(!p.fixed)) { 26 | continue; 27 | } 28 | p.acc.scale(p.massInv); 29 | (p.vel.copy(p.pos)).sub(p.old.pos); 30 | if (drag) { 31 | p.vel.scale(drag); 32 | } 33 | (pos.copy(p.pos)).add(p.vel.add(p.acc.scale(dtSq))); 34 | p.old.pos.copy(p.pos); 35 | p.pos.copy(pos); 36 | _results.push(p.acc.clear()); 37 | } 38 | return _results; 39 | }; 40 | 41 | return Verlet; 42 | 43 | })(Integrator); 44 | -------------------------------------------------------------------------------- /compiled/math/Random.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* Random 3 | */ 4 | 5 | var Random; 6 | 7 | Random = function(min, max) { 8 | if (max == null) { 9 | max = min; 10 | min = 0; 11 | } 12 | return min + Math.random() * (max - min); 13 | }; 14 | 15 | Random.int = function(min, max) { 16 | if (max == null) { 17 | max = min; 18 | min = 0; 19 | } 20 | return Math.floor(min + Math.random() * (max - min)); 21 | }; 22 | 23 | Random.sign = function(prob) { 24 | if (prob == null) { 25 | prob = 0.5; 26 | } 27 | if (Math.random() < prob) { 28 | return 1; 29 | } else { 30 | return -1; 31 | } 32 | }; 33 | 34 | Random.bool = function(prob) { 35 | if (prob == null) { 36 | prob = 0.5; 37 | } 38 | return Math.random() < prob; 39 | }; 40 | 41 | Random.item = function(list) { 42 | return list[Math.floor(Math.random() * list.length)]; 43 | }; 44 | -------------------------------------------------------------------------------- /compiled/math/Vector.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /* 2D Vector 3 | */ 4 | 5 | var Vector; 6 | 7 | Vector = (function() { 8 | /* Adds two vectors and returns the product. 9 | */ 10 | Vector.add = function(v1, v2) { 11 | return new Vector(v1.x + v2.x, v1.y + v2.y); 12 | }; 13 | 14 | /* Subtracts v2 from v1 and returns the product. 15 | */ 16 | 17 | 18 | Vector.sub = function(v1, v2) { 19 | return new Vector(v1.x - v2.x, v1.y - v2.y); 20 | }; 21 | 22 | /* Projects one vector (v1) onto another (v2) 23 | */ 24 | 25 | 26 | Vector.project = function(v1, v2) { 27 | return v1.clone().scale((v1.dot(v2)) / v1.magSq()); 28 | }; 29 | 30 | /* Creates a new Vector instance. 31 | */ 32 | 33 | 34 | function Vector(x, y) { 35 | this.x = x != null ? x : 0.0; 36 | this.y = y != null ? y : 0.0; 37 | } 38 | 39 | /* Sets the components of this vector. 40 | */ 41 | 42 | 43 | Vector.prototype.set = function(x, y) { 44 | this.x = x; 45 | this.y = y; 46 | return this; 47 | }; 48 | 49 | /* Add a vector to this one. 50 | */ 51 | 52 | 53 | Vector.prototype.add = function(v) { 54 | this.x += v.x; 55 | this.y += v.y; 56 | return this; 57 | }; 58 | 59 | /* Subtracts a vector from this one. 60 | */ 61 | 62 | 63 | Vector.prototype.sub = function(v) { 64 | this.x -= v.x; 65 | this.y -= v.y; 66 | return this; 67 | }; 68 | 69 | /* Scales this vector by a value. 70 | */ 71 | 72 | 73 | Vector.prototype.scale = function(f) { 74 | this.x *= f; 75 | this.y *= f; 76 | return this; 77 | }; 78 | 79 | /* Computes the dot product between vectors. 80 | */ 81 | 82 | 83 | Vector.prototype.dot = function(v) { 84 | return this.x * v.x + this.y * v.y; 85 | }; 86 | 87 | /* Computes the cross product between vectors. 88 | */ 89 | 90 | 91 | Vector.prototype.cross = function(v) { 92 | return (this.x * v.y) - (this.y * v.x); 93 | }; 94 | 95 | /* Computes the magnitude (length). 96 | */ 97 | 98 | 99 | Vector.prototype.mag = function() { 100 | return Math.sqrt(this.x * this.x + this.y * this.y); 101 | }; 102 | 103 | /* Computes the squared magnitude (length). 104 | */ 105 | 106 | 107 | Vector.prototype.magSq = function() { 108 | return this.x * this.x + this.y * this.y; 109 | }; 110 | 111 | /* Computes the distance to another vector. 112 | */ 113 | 114 | 115 | Vector.prototype.dist = function(v) { 116 | var dx, dy; 117 | 118 | dx = v.x - this.x; 119 | dy = v.y - this.y; 120 | return Math.sqrt(dx * dx + dy * dy); 121 | }; 122 | 123 | /* Computes the squared distance to another vector. 124 | */ 125 | 126 | 127 | Vector.prototype.distSq = function(v) { 128 | var dx, dy; 129 | 130 | dx = v.x - this.x; 131 | dy = v.y - this.y; 132 | return dx * dx + dy * dy; 133 | }; 134 | 135 | /* Normalises the vector, making it a unit vector (of length 1). 136 | */ 137 | 138 | 139 | Vector.prototype.norm = function() { 140 | var m; 141 | 142 | m = Math.sqrt(this.x * this.x + this.y * this.y); 143 | this.x /= m; 144 | this.y /= m; 145 | return this; 146 | }; 147 | 148 | /* Limits the vector length to a given amount. 149 | */ 150 | 151 | 152 | Vector.prototype.limit = function(l) { 153 | var m, mSq; 154 | 155 | mSq = this.x * this.x + this.y * this.y; 156 | if (mSq > l * l) { 157 | m = Math.sqrt(mSq); 158 | this.x /= m; 159 | this.y /= m; 160 | this.x *= l; 161 | this.y *= l; 162 | return this; 163 | } 164 | }; 165 | 166 | /* Copies components from another vector. 167 | */ 168 | 169 | 170 | Vector.prototype.copy = function(v) { 171 | this.x = v.x; 172 | this.y = v.y; 173 | return this; 174 | }; 175 | 176 | /* Clones this vector to a new itentical one. 177 | */ 178 | 179 | 180 | Vector.prototype.clone = function() { 181 | return new Vector(this.x, this.y); 182 | }; 183 | 184 | /* Resets the vector to zero. 185 | */ 186 | 187 | 188 | Vector.prototype.clear = function() { 189 | this.x = 0.0; 190 | this.y = 0.0; 191 | return this; 192 | }; 193 | 194 | return Vector; 195 | 196 | })(); 197 | -------------------------------------------------------------------------------- /deploy/physics.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.3 2 | /* Allows safe, dyamic creation of namespaces. 3 | */ 4 | 5 | var Attraction, Behaviour, Collision, ConstantForce, EdgeBounce, EdgeWrap, Euler, ImprovedEuler, Integrator, Particle, Physics, Random, Spring, Vector, Verlet, Wander, namespace, 6 | __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | namespace = function(id) { 10 | var path, root, _i, _len, _ref, _ref1, _results; 11 | root = self; 12 | _ref = id.split('.'); 13 | _results = []; 14 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 15 | path = _ref[_i]; 16 | _results.push(root = (_ref1 = root[path]) != null ? _ref1 : root[path] = {}); 17 | } 18 | return _results; 19 | }; 20 | 21 | /* RequestAnimationFrame shim. 22 | */ 23 | 24 | 25 | (function() { 26 | var time, vendor, vendors, _i, _len; 27 | time = 0; 28 | vendors = ['ms', 'moz', 'webkit', 'o']; 29 | for (_i = 0, _len = vendors.length; _i < _len; _i++) { 30 | vendor = vendors[_i]; 31 | if (!(!window.requestAnimationFrame)) { 32 | continue; 33 | } 34 | window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame']; 35 | window.cancelRequestAnimationFrame = window[vendor + 'CancelRequestAnimationFrame']; 36 | } 37 | if (!window.requestAnimationFrame) { 38 | window.requestAnimationFrame = function(callback, element) { 39 | var delta, now, old; 40 | now = new Date().getTime(); 41 | delta = Math.max(0, 16 - (now - old)); 42 | setTimeout((function() { 43 | return callback(time + delta); 44 | }), delta); 45 | return old = now + delta; 46 | }; 47 | } 48 | if (!window.cancelAnimationFrame) { 49 | return window.cancelAnimationFrame = function(id) { 50 | return clearTimeout(id); 51 | }; 52 | } 53 | })(); 54 | 55 | /* Random 56 | */ 57 | 58 | 59 | Random = function(min, max) { 60 | if (!(max != null)) { 61 | max = min; 62 | min = 0; 63 | } 64 | return min + Math.random() * (max - min); 65 | }; 66 | 67 | Random.int = function(min, max) { 68 | if (!(max != null)) { 69 | max = min; 70 | min = 0; 71 | } 72 | return Math.floor(min + Math.random() * (max - min)); 73 | }; 74 | 75 | Random.sign = function(prob) { 76 | if (prob == null) { 77 | prob = 0.5; 78 | } 79 | if (Math.random() < prob) { 80 | return 1; 81 | } else { 82 | return -1; 83 | } 84 | }; 85 | 86 | Random.bool = function(prob) { 87 | if (prob == null) { 88 | prob = 0.5; 89 | } 90 | return Math.random() < prob; 91 | }; 92 | 93 | Random.item = function(list) { 94 | return list[Math.floor(Math.random() * list.length)]; 95 | }; 96 | 97 | /* 2D Vector 98 | */ 99 | 100 | 101 | Vector = (function() { 102 | /* Adds two vectors and returns the product. 103 | */ 104 | 105 | Vector.add = function(v1, v2) { 106 | return new Vector(v1.x + v2.x, v1.y + v2.y); 107 | }; 108 | 109 | /* Subtracts v2 from v1 and returns the product. 110 | */ 111 | 112 | 113 | Vector.sub = function(v1, v2) { 114 | return new Vector(v1.x - v2.x, v1.y - v2.y); 115 | }; 116 | 117 | /* Projects one vector (v1) onto another (v2) 118 | */ 119 | 120 | 121 | Vector.project = function(v1, v2) { 122 | return v1.clone().scale((v1.dot(v2)) / v1.magSq()); 123 | }; 124 | 125 | /* Creates a new Vector instance. 126 | */ 127 | 128 | 129 | function Vector(x, y) { 130 | this.x = x != null ? x : 0.0; 131 | this.y = y != null ? y : 0.0; 132 | } 133 | 134 | /* Sets the components of this vector. 135 | */ 136 | 137 | 138 | Vector.prototype.set = function(x, y) { 139 | this.x = x; 140 | this.y = y; 141 | return this; 142 | }; 143 | 144 | /* Add a vector to this one. 145 | */ 146 | 147 | 148 | Vector.prototype.add = function(v) { 149 | this.x += v.x; 150 | this.y += v.y; 151 | return this; 152 | }; 153 | 154 | /* Subtracts a vector from this one. 155 | */ 156 | 157 | 158 | Vector.prototype.sub = function(v) { 159 | this.x -= v.x; 160 | this.y -= v.y; 161 | return this; 162 | }; 163 | 164 | /* Scales this vector by a value. 165 | */ 166 | 167 | 168 | Vector.prototype.scale = function(f) { 169 | this.x *= f; 170 | this.y *= f; 171 | return this; 172 | }; 173 | 174 | /* Computes the dot product between vectors. 175 | */ 176 | 177 | 178 | Vector.prototype.dot = function(v) { 179 | return this.x * v.x + this.y * v.y; 180 | }; 181 | 182 | /* Computes the cross product between vectors. 183 | */ 184 | 185 | 186 | Vector.prototype.cross = function(v) { 187 | return (this.x * v.y) - (this.y * v.x); 188 | }; 189 | 190 | /* Computes the magnitude (length). 191 | */ 192 | 193 | 194 | Vector.prototype.mag = function() { 195 | return Math.sqrt(this.x * this.x + this.y * this.y); 196 | }; 197 | 198 | /* Computes the squared magnitude (length). 199 | */ 200 | 201 | 202 | Vector.prototype.magSq = function() { 203 | return this.x * this.x + this.y * this.y; 204 | }; 205 | 206 | /* Computes the distance to another vector. 207 | */ 208 | 209 | 210 | Vector.prototype.dist = function(v) { 211 | var dx, dy; 212 | dx = v.x - this.x; 213 | dy = v.y - this.y; 214 | return Math.sqrt(dx * dx + dy * dy); 215 | }; 216 | 217 | /* Computes the squared distance to another vector. 218 | */ 219 | 220 | 221 | Vector.prototype.distSq = function(v) { 222 | var dx, dy; 223 | dx = v.x - this.x; 224 | dy = v.y - this.y; 225 | return dx * dx + dy * dy; 226 | }; 227 | 228 | /* Normalises the vector, making it a unit vector (of length 1). 229 | */ 230 | 231 | 232 | Vector.prototype.norm = function() { 233 | var m; 234 | m = Math.sqrt(this.x * this.x + this.y * this.y); 235 | this.x /= m; 236 | this.y /= m; 237 | return this; 238 | }; 239 | 240 | /* Limits the vector length to a given amount. 241 | */ 242 | 243 | 244 | Vector.prototype.limit = function(l) { 245 | var m, mSq; 246 | mSq = this.x * this.x + this.y * this.y; 247 | if (mSq > l * l) { 248 | m = Math.sqrt(mSq); 249 | this.x /= m; 250 | this.y /= m; 251 | this.x *= l; 252 | this.y *= l; 253 | return this; 254 | } 255 | }; 256 | 257 | /* Copies components from another vector. 258 | */ 259 | 260 | 261 | Vector.prototype.copy = function(v) { 262 | this.x = v.x; 263 | this.y = v.y; 264 | return this; 265 | }; 266 | 267 | /* Clones this vector to a new itentical one. 268 | */ 269 | 270 | 271 | Vector.prototype.clone = function() { 272 | return new Vector(this.x, this.y); 273 | }; 274 | 275 | /* Resets the vector to zero. 276 | */ 277 | 278 | 279 | Vector.prototype.clear = function() { 280 | this.x = 0.0; 281 | this.y = 0.0; 282 | return this; 283 | }; 284 | 285 | return Vector; 286 | 287 | })(); 288 | 289 | /* Particle 290 | */ 291 | 292 | 293 | Particle = (function() { 294 | 295 | Particle.GUID = 0; 296 | 297 | function Particle(mass) { 298 | this.mass = mass != null ? mass : 1.0; 299 | this.id = 'p' + Particle.GUID++; 300 | this.setMass(this.mass); 301 | this.setRadius(1.0); 302 | this.fixed = false; 303 | this.behaviours = []; 304 | this.pos = new Vector(); 305 | this.vel = new Vector(); 306 | this.acc = new Vector(); 307 | this.old = { 308 | pos: new Vector(), 309 | vel: new Vector(), 310 | acc: new Vector() 311 | }; 312 | } 313 | 314 | /* Moves the particle to a given location vector. 315 | */ 316 | 317 | 318 | Particle.prototype.moveTo = function(pos) { 319 | this.pos.copy(pos); 320 | return this.old.pos.copy(pos); 321 | }; 322 | 323 | /* Sets the mass of the particle. 324 | */ 325 | 326 | 327 | Particle.prototype.setMass = function(mass) { 328 | this.mass = mass != null ? mass : 1.0; 329 | return this.massInv = 1.0 / this.mass; 330 | }; 331 | 332 | /* Sets the radius of the particle. 333 | */ 334 | 335 | 336 | Particle.prototype.setRadius = function(radius) { 337 | this.radius = radius != null ? radius : 1.0; 338 | return this.radiusSq = this.radius * this.radius; 339 | }; 340 | 341 | /* Applies all behaviours to derive new force. 342 | */ 343 | 344 | 345 | Particle.prototype.update = function(dt, index) { 346 | var behaviour, _i, _len, _ref, _results; 347 | if (!this.fixed) { 348 | _ref = this.behaviours; 349 | _results = []; 350 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 351 | behaviour = _ref[_i]; 352 | _results.push(behaviour.apply(this, dt, index)); 353 | } 354 | return _results; 355 | } 356 | }; 357 | 358 | return Particle; 359 | 360 | })(); 361 | 362 | /* Spring 363 | */ 364 | 365 | 366 | Spring = (function() { 367 | 368 | function Spring(p1, p2, restLength, stiffness) { 369 | this.p1 = p1; 370 | this.p2 = p2; 371 | this.restLength = restLength != null ? restLength : 100; 372 | this.stiffness = stiffness != null ? stiffness : 1.0; 373 | this._delta = new Vector(); 374 | } 375 | 376 | Spring.prototype.apply = function() { 377 | var dist, force; 378 | (this._delta.copy(this.p2.pos)).sub(this.p1.pos); 379 | dist = this._delta.mag() + 0.000001; 380 | force = (dist - this.restLength) / (dist * (this.p1.massInv + this.p2.massInv)) * this.stiffness; 381 | if (!this.p1.fixed) { 382 | this.p1.pos.add(this._delta.clone().scale(force * this.p1.massInv)); 383 | } 384 | if (!this.p2.fixed) { 385 | return this.p2.pos.add(this._delta.scale(-force * this.p2.massInv)); 386 | } 387 | }; 388 | 389 | return Spring; 390 | 391 | })(); 392 | 393 | /* Physics Engine 394 | */ 395 | 396 | 397 | Physics = (function() { 398 | 399 | function Physics(integrator) { 400 | this.integrator = integrator != null ? integrator : new Euler(); 401 | this.timestep = 1.0 / 60; 402 | this.viscosity = 0.005; 403 | this.behaviours = []; 404 | this._time = 0.0; 405 | this._step = 0.0; 406 | this._clock = null; 407 | this._buffer = 0.0; 408 | this._maxSteps = 4; 409 | this.particles = []; 410 | this.springs = []; 411 | } 412 | 413 | /* Performs a numerical integration step. 414 | */ 415 | 416 | 417 | Physics.prototype.integrate = function(dt) { 418 | var behaviour, drag, index, particle, spring, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; 419 | drag = 1.0 - this.viscosity; 420 | _ref = this.particles; 421 | for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) { 422 | particle = _ref[index]; 423 | _ref1 = this.behaviours; 424 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 425 | behaviour = _ref1[_j]; 426 | behaviour.apply(particle, dt, index); 427 | } 428 | particle.update(dt, index); 429 | } 430 | this.integrator.integrate(this.particles, dt, drag); 431 | _ref2 = this.springs; 432 | _results = []; 433 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { 434 | spring = _ref2[_k]; 435 | _results.push(spring.apply()); 436 | } 437 | return _results; 438 | }; 439 | 440 | /* Steps the system. 441 | */ 442 | 443 | 444 | Physics.prototype.step = function() { 445 | var delta, i, time, _ref; 446 | if ((_ref = this._clock) == null) { 447 | this._clock = new Date().getTime(); 448 | } 449 | time = new Date().getTime(); 450 | delta = time - this._clock; 451 | if (delta <= 0.0) { 452 | return; 453 | } 454 | delta *= 0.001; 455 | this._clock = time; 456 | this._buffer += delta; 457 | i = 0; 458 | while (this._buffer >= this.timestep && ++i < this._maxSteps) { 459 | this.integrate(this.timestep); 460 | this._buffer -= this.timestep; 461 | this._time += this.timestep; 462 | } 463 | return this._step = new Date().getTime() - time; 464 | }; 465 | 466 | /* Clean up after yourself. 467 | */ 468 | 469 | 470 | Physics.prototype.destroy = function() { 471 | this.integrator = null; 472 | this.particles = null; 473 | return this.springs = null; 474 | }; 475 | 476 | return Physics; 477 | 478 | })(); 479 | 480 | /* Integrator 481 | */ 482 | 483 | 484 | Integrator = (function() { 485 | 486 | function Integrator() {} 487 | 488 | Integrator.prototype.integrate = function(particles, dt) {}; 489 | 490 | return Integrator; 491 | 492 | })(); 493 | 494 | /* Euler Integrator 495 | */ 496 | 497 | 498 | Euler = (function(_super) { 499 | 500 | __extends(Euler, _super); 501 | 502 | function Euler() { 503 | return Euler.__super__.constructor.apply(this, arguments); 504 | } 505 | 506 | Euler.prototype.integrate = function(particles, dt, drag) { 507 | var p, vel, _i, _len, _results; 508 | vel = new Vector(); 509 | _results = []; 510 | for (_i = 0, _len = particles.length; _i < _len; _i++) { 511 | p = particles[_i]; 512 | if (!(!p.fixed)) { 513 | continue; 514 | } 515 | p.old.pos.copy(p.pos); 516 | p.acc.scale(p.massInv); 517 | vel.copy(p.vel); 518 | p.vel.add(p.acc.scale(dt)); 519 | p.pos.add(vel.scale(dt)); 520 | if (drag) { 521 | p.vel.scale(drag); 522 | } 523 | _results.push(p.acc.clear()); 524 | } 525 | return _results; 526 | }; 527 | 528 | return Euler; 529 | 530 | })(Integrator); 531 | 532 | /* Improved Euler Integrator 533 | */ 534 | 535 | 536 | ImprovedEuler = (function(_super) { 537 | 538 | __extends(ImprovedEuler, _super); 539 | 540 | function ImprovedEuler() { 541 | return ImprovedEuler.__super__.constructor.apply(this, arguments); 542 | } 543 | 544 | ImprovedEuler.prototype.integrate = function(particles, dt, drag) { 545 | var acc, dtSq, p, vel, _i, _len, _results; 546 | acc = new Vector(); 547 | vel = new Vector(); 548 | dtSq = dt * dt; 549 | _results = []; 550 | for (_i = 0, _len = particles.length; _i < _len; _i++) { 551 | p = particles[_i]; 552 | if (!(!p.fixed)) { 553 | continue; 554 | } 555 | p.old.pos.copy(p.pos); 556 | p.acc.scale(p.massInv); 557 | vel.copy(p.vel); 558 | acc.copy(p.acc); 559 | p.pos.add((vel.scale(dt)).add(acc.scale(0.5 * dtSq))); 560 | p.vel.add(p.acc.scale(dt)); 561 | if (drag) { 562 | p.vel.scale(drag); 563 | } 564 | _results.push(p.acc.clear()); 565 | } 566 | return _results; 567 | }; 568 | 569 | return ImprovedEuler; 570 | 571 | })(Integrator); 572 | 573 | /* Velocity Verlet Integrator 574 | */ 575 | 576 | 577 | Verlet = (function(_super) { 578 | 579 | __extends(Verlet, _super); 580 | 581 | function Verlet() { 582 | return Verlet.__super__.constructor.apply(this, arguments); 583 | } 584 | 585 | Verlet.prototype.integrate = function(particles, dt, drag) { 586 | var dtSq, p, pos, _i, _len, _results; 587 | pos = new Vector(); 588 | dtSq = dt * dt; 589 | _results = []; 590 | for (_i = 0, _len = particles.length; _i < _len; _i++) { 591 | p = particles[_i]; 592 | if (!(!p.fixed)) { 593 | continue; 594 | } 595 | p.acc.scale(p.massInv); 596 | (p.vel.copy(p.pos)).sub(p.old.pos); 597 | if (drag) { 598 | p.vel.scale(drag); 599 | } 600 | (pos.copy(p.pos)).add(p.vel.add(p.acc.scale(dtSq))); 601 | p.old.pos.copy(p.pos); 602 | p.pos.copy(pos); 603 | _results.push(p.acc.clear()); 604 | } 605 | return _results; 606 | }; 607 | 608 | return Verlet; 609 | 610 | })(Integrator); 611 | 612 | /* Behaviour 613 | */ 614 | 615 | 616 | Behaviour = (function() { 617 | 618 | Behaviour.GUID = 0; 619 | 620 | function Behaviour() { 621 | this.GUID = Behaviour.GUID++; 622 | this.interval = 1; 623 | } 624 | 625 | Behaviour.prototype.apply = function(p, dt, index) { 626 | var _name, _ref; 627 | return ((_ref = p[_name = '__behaviour' + this.GUID]) != null ? _ref : p[_name] = { 628 | counter: 0 629 | }).counter++; 630 | }; 631 | 632 | return Behaviour; 633 | 634 | })(); 635 | 636 | /* Attraction Behaviour 637 | */ 638 | 639 | 640 | Attraction = (function(_super) { 641 | 642 | __extends(Attraction, _super); 643 | 644 | function Attraction(target, radius, strength) { 645 | this.target = target != null ? target : new Vector(); 646 | this.radius = radius != null ? radius : 1000; 647 | this.strength = strength != null ? strength : 100.0; 648 | this._delta = new Vector(); 649 | this.setRadius(this.radius); 650 | Attraction.__super__.constructor.apply(this, arguments); 651 | } 652 | 653 | /* Sets the effective radius of the bahavious. 654 | */ 655 | 656 | 657 | Attraction.prototype.setRadius = function(radius) { 658 | this.radius = radius; 659 | return this.radiusSq = radius * radius; 660 | }; 661 | 662 | Attraction.prototype.apply = function(p, dt, index) { 663 | var distSq; 664 | (this._delta.copy(this.target)).sub(p.pos); 665 | distSq = this._delta.magSq(); 666 | if (distSq < this.radiusSq && distSq > 0.000001) { 667 | this._delta.norm().scale(1.0 - distSq / this.radiusSq); 668 | return p.acc.add(this._delta.scale(this.strength)); 669 | } 670 | }; 671 | 672 | return Attraction; 673 | 674 | })(Behaviour); 675 | 676 | /* Collision Behaviour 677 | */ 678 | 679 | 680 | Collision = (function(_super) { 681 | 682 | __extends(Collision, _super); 683 | 684 | function Collision(useMass, callback) { 685 | this.useMass = useMass != null ? useMass : true; 686 | this.callback = callback != null ? callback : null; 687 | this.pool = []; 688 | this._delta = new Vector(); 689 | Collision.__super__.constructor.apply(this, arguments); 690 | } 691 | 692 | Collision.prototype.apply = function(p, dt, index) { 693 | var dist, distSq, i, mt, o, overlap, r1, r2, radii, _i, _ref, _results; 694 | _results = []; 695 | for (i = _i = index, _ref = this.pool.length - 1; index <= _ref ? _i <= _ref : _i >= _ref; i = index <= _ref ? ++_i : --_i) { 696 | o = this.pool[i]; 697 | if (o !== p) { 698 | (this._delta.copy(o.pos)).sub(p.pos); 699 | distSq = this._delta.magSq(); 700 | radii = p.radius + o.radius; 701 | if (distSq <= radii * radii) { 702 | dist = Math.sqrt(distSq); 703 | overlap = (p.radius + o.radius) - dist; 704 | overlap += 0.5; 705 | mt = p.mass + o.mass; 706 | r1 = this.useMass ? o.mass / mt : 0.5; 707 | r2 = this.useMass ? p.mass / mt : 0.5; 708 | p.pos.add(this._delta.clone().norm().scale(overlap * -r1)); 709 | o.pos.add(this._delta.norm().scale(overlap * r2)); 710 | _results.push(typeof this.callback === "function" ? this.callback(p, o, overlap) : void 0); 711 | } else { 712 | _results.push(void 0); 713 | } 714 | } else { 715 | _results.push(void 0); 716 | } 717 | } 718 | return _results; 719 | }; 720 | 721 | return Collision; 722 | 723 | })(Behaviour); 724 | 725 | /* Constant Force Behaviour 726 | */ 727 | 728 | 729 | ConstantForce = (function(_super) { 730 | 731 | __extends(ConstantForce, _super); 732 | 733 | function ConstantForce(force) { 734 | this.force = force != null ? force : new Vector(); 735 | ConstantForce.__super__.constructor.apply(this, arguments); 736 | } 737 | 738 | ConstantForce.prototype.apply = function(p, dt, index) { 739 | return p.acc.add(this.force); 740 | }; 741 | 742 | return ConstantForce; 743 | 744 | })(Behaviour); 745 | 746 | /* Edge Bounce Behaviour 747 | */ 748 | 749 | 750 | EdgeBounce = (function(_super) { 751 | 752 | __extends(EdgeBounce, _super); 753 | 754 | function EdgeBounce(min, max) { 755 | this.min = min != null ? min : new Vector(); 756 | this.max = max != null ? max : new Vector(); 757 | EdgeBounce.__super__.constructor.apply(this, arguments); 758 | } 759 | 760 | EdgeBounce.prototype.apply = function(p, dt, index) { 761 | if (p.pos.x - p.radius < this.min.x) { 762 | p.pos.x = this.min.x + p.radius; 763 | } else if (p.pos.x + p.radius > this.max.x) { 764 | p.pos.x = this.max.x - p.radius; 765 | } 766 | if (p.pos.y - p.radius < this.min.y) { 767 | return p.pos.y = this.min.y + p.radius; 768 | } else if (p.pos.y + p.radius > this.max.y) { 769 | return p.pos.y = this.max.y - p.radius; 770 | } 771 | }; 772 | 773 | return EdgeBounce; 774 | 775 | })(Behaviour); 776 | 777 | /* Edge Wrap Behaviour 778 | */ 779 | 780 | 781 | EdgeWrap = (function(_super) { 782 | 783 | __extends(EdgeWrap, _super); 784 | 785 | function EdgeWrap(min, max) { 786 | this.min = min != null ? min : new Vector(); 787 | this.max = max != null ? max : new Vector(); 788 | EdgeWrap.__super__.constructor.apply(this, arguments); 789 | } 790 | 791 | EdgeWrap.prototype.apply = function(p, dt, index) { 792 | if (p.pos.x + p.radius < this.min.x) { 793 | p.pos.x = this.max.x + p.radius; 794 | p.old.pos.x = p.pos.x; 795 | } else if (p.pos.x - p.radius > this.max.x) { 796 | p.pos.x = this.min.x - p.radius; 797 | p.old.pos.x = p.pos.x; 798 | } 799 | if (p.pos.y + p.radius < this.min.y) { 800 | p.pos.y = this.max.y + p.radius; 801 | return p.old.pos.y = p.pos.y; 802 | } else if (p.pos.y - p.radius > this.max.y) { 803 | p.pos.y = this.min.y - p.radius; 804 | return p.old.pos.y = p.pos.y; 805 | } 806 | }; 807 | 808 | return EdgeWrap; 809 | 810 | })(Behaviour); 811 | 812 | /* Wander Behaviour 813 | */ 814 | 815 | 816 | Wander = (function(_super) { 817 | 818 | __extends(Wander, _super); 819 | 820 | function Wander(jitter, radius, strength) { 821 | this.jitter = jitter != null ? jitter : 0.5; 822 | this.radius = radius != null ? radius : 100; 823 | this.strength = strength != null ? strength : 1.0; 824 | this.theta = Math.random() * Math.PI * 2; 825 | Wander.__super__.constructor.apply(this, arguments); 826 | } 827 | 828 | Wander.prototype.apply = function(p, dt, index) { 829 | this.theta += (Math.random() - 0.5) * this.jitter * Math.PI * 2; 830 | p.acc.x += Math.cos(this.theta) * this.radius * this.strength; 831 | return p.acc.y += Math.sin(this.theta) * this.radius * this.strength; 832 | }; 833 | 834 | return Wander; 835 | 836 | })(Behaviour); 837 | -------------------------------------------------------------------------------- /deploy/physics.min.js: -------------------------------------------------------------------------------- 1 | var Attraction,Behaviour,Collision,ConstantForce,EdgeBounce,EdgeWrap,Euler,ImprovedEuler,Integrator,Particle,Physics,Random,Spring,Vector,Verlet,Wander,namespace,__hasProp={}.hasOwnProperty,__extends=function(child,parent){function ctor(){this.constructor=child}for(var key in parent)__hasProp.call(parent,key)&&(child[key]=parent[key]);return ctor.prototype=parent.prototype,child.prototype=new ctor,child.__super__=parent.prototype,child};namespace=function(id){var path,root,_i,_len,_ref,_ref1,_results;root=self,_ref=id.split("."),_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++)path=_ref[_i],_results.push(root=(_ref1=root[path])!=null?_ref1:root[path]={});return _results},function(){var time,vendor,vendors,_i,_len;time=0,vendors=["ms","moz","webkit","o"];for(_i=0,_len=vendors.length;_i<_len;_i++){vendor=vendors[_i];if(!!window.requestAnimationFrame)continue;window.requestAnimationFrame=window[vendor+"RequestAnimationFrame"],window.cancelRequestAnimationFrame=window[vendor+"CancelRequestAnimationFrame"]}window.requestAnimationFrame||(window.requestAnimationFrame=function(callback,element){var delta,now,old;return now=(new Date).getTime(),delta=Math.max(0,16-(now-old)),setTimeout(function(){return callback(time+delta)},delta),old=now+delta});if(!window.cancelAnimationFrame)return window.cancelAnimationFrame=function(id){return clearTimeout(id)}}(),Random=function(min,max){return max==null&&(max=min,min=0),min+Math.random()*(max-min)},Random.int=function(min,max){return max==null&&(max=min,min=0),Math.floor(min+Math.random()*(max-min))},Random.sign=function(prob){return prob==null&&(prob=.5),Math.random()l*l)return m=Math.sqrt(mSq),this.x/=m,this.y/=m,this.x*=l,this.y*=l,this},Vector.prototype.copy=function(v){return this.x=v.x,this.y=v.y,this},Vector.prototype.clone=function(){return new Vector(this.x,this.y)},Vector.prototype.clear=function(){return this.x=0,this.y=0,this},Vector}(),Particle=function(){function Particle(mass){this.mass=mass!=null?mass:1,this.id="p"+Particle.GUID++,this.setMass(this.mass),this.setRadius(1),this.fixed=!1,this.behaviours=[],this.pos=new Vector,this.vel=new Vector,this.acc=new Vector,this.old={pos:new Vector,vel:new Vector,acc:new Vector}}return Particle.GUID=0,Particle.prototype.moveTo=function(pos){return this.pos.copy(pos),this.old.pos.copy(pos)},Particle.prototype.setMass=function(mass){return this.mass=mass!=null?mass:1,this.massInv=1/this.mass},Particle.prototype.setRadius=function(radius){return this.radius=radius!=null?radius:1,this.radiusSq=this.radius*this.radius},Particle.prototype.update=function(dt,index){var behaviour,_i,_len,_ref,_results;if(!this.fixed){_ref=this.behaviours,_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++)behaviour=_ref[_i],_results.push(behaviour.apply(this,dt,index));return _results}},Particle}(),Spring=function(){function Spring(p1,p2,restLength,stiffness){this.p1=p1,this.p2=p2,this.restLength=restLength!=null?restLength:100,this.stiffness=stiffness!=null?stiffness:1,this._delta=new Vector}return Spring.prototype.apply=function(){var dist,force;this._delta.copy(this.p2.pos).sub(this.p1.pos),dist=this._delta.mag()+1e-6,force=(dist-this.restLength)/(dist*(this.p1.massInv+this.p2.massInv))*this.stiffness,this.p1.fixed||this.p1.pos.add(this._delta.clone().scale(force*this.p1.massInv));if(!this.p2.fixed)return this.p2.pos.add(this._delta.scale(-force*this.p2.massInv))},Spring}(),Physics=function(){function Physics(integrator){this.integrator=integrator!=null?integrator:new Euler,this.timestep=1/60,this.viscosity=.005,this.behaviours=[],this._time=0,this._step=0,this._clock=null,this._buffer=0,this._maxSteps=4,this.particles=[],this.springs=[]}return Physics.prototype.integrate=function(dt){var behaviour,drag,index,particle,spring,_i,_j,_k,_len,_len1,_len2,_ref,_ref1,_ref2,_results;drag=1-this.viscosity,_ref=this.particles;for(index=_i=0,_len=_ref.length;_i<_len;index=++_i){particle=_ref[index],_ref1=this.behaviours;for(_j=0,_len1=_ref1.length;_j<_len1;_j++)behaviour=_ref1[_j],behaviour.apply(particle,dt,index);particle.update(dt,index)}this.integrator.integrate(this.particles,dt,drag),_ref2=this.springs,_results=[];for(_k=0,_len2=_ref2.length;_k<_len2;_k++)spring=_ref2[_k],_results.push(spring.apply());return _results},Physics.prototype.step=function(){var delta,i,time,_ref;(_ref=this._clock)==null&&(this._clock=(new Date).getTime()),time=(new Date).getTime(),delta=time-this._clock;if(delta<=0)return;delta*=.001,this._clock=time,this._buffer+=delta,i=0;while(this._buffer>=this.timestep&&++i1e-6)return this._delta.norm().scale(1-distSq/this.radiusSq),p.acc.add(this._delta.scale(this.strength))},Attraction}(Behaviour),Collision=function(_super){function Collision(useMass,callback){this.useMass=useMass!=null?useMass:!0,this.callback=callback!=null?callback:null,this.pool=[],this._delta=new Vector,Collision.__super__.constructor.apply(this,arguments)}return __extends(Collision,_super),Collision.prototype.apply=function(p,dt,index){var dist,distSq,i,mt,o,overlap,r1,r2,radii,_i,_ref,_results;_results=[];for(i=_i=index,_ref=this.pool.length-1;index<=_ref?_i<=_ref:_i>=_ref;i=index<=_ref?++_i:--_i)o=this.pool[i],o!==p?(this._delta.copy(o.pos).sub(p.pos),distSq=this._delta.magSq(),radii=p.radius+o.radius,distSq<=radii*radii?(dist=Math.sqrt(distSq),overlap=p.radius+o.radius-dist,overlap+=.5,mt=p.mass+o.mass,r1=this.useMass?o.mass/mt:.5,r2=this.useMass?p.mass/mt:.5,p.pos.add(this._delta.clone().norm().scale(overlap*-r1)),o.pos.add(this._delta.norm().scale(overlap*r2)),_results.push(typeof this.callback=="function"?this.callback(p,o,overlap):void 0)):_results.push(void 0)):_results.push(void 0);return _results},Collision}(Behaviour),ConstantForce=function(_super){function ConstantForce(force){this.force=force!=null?force:new Vector,ConstantForce.__super__.constructor.apply(this,arguments)}return __extends(ConstantForce,_super),ConstantForce.prototype.apply=function(p,dt,index){return p.acc.add(this.force)},ConstantForce}(Behaviour),EdgeBounce=function(_super){function EdgeBounce(min,max){this.min=min!=null?min:new Vector,this.max=max!=null?max:new Vector,EdgeBounce.__super__.constructor.apply(this,arguments)}return __extends(EdgeBounce,_super),EdgeBounce.prototype.apply=function(p,dt,index){p.pos.x-p.radiusthis.max.x&&(p.pos.x=this.max.x-p.radius);if(p.pos.y-p.radiusthis.max.y)return p.pos.y=this.max.y-p.radius},EdgeBounce}(Behaviour),EdgeWrap=function(_super){function EdgeWrap(min,max){this.min=min!=null?min:new Vector,this.max=max!=null?max:new Vector,EdgeWrap.__super__.constructor.apply(this,arguments)}return __extends(EdgeWrap,_super),EdgeWrap.prototype.apply=function(p,dt,index){p.pos.x+p.radiusthis.max.x&&(p.pos.x=this.min.x-p.radius,p.old.pos.x=p.pos.x);if(p.pos.y+p.radiusthis.max.y)return p.pos.y=this.min.y-p.radius,p.old.pos.y=p.pos.y},EdgeWrap}(Behaviour),Wander=function(_super){function Wander(jitter,radius,strength){this.jitter=jitter!=null?jitter:.5,this.radius=radius!=null?radius:100,this.strength=strength!=null?strength:1,this.theta=Math.random()*Math.PI*2,Wander.__super__.constructor.apply(this,arguments)}return __extends(Wander,_super),Wander.prototype.apply=function(p,dt,index){return this.theta+=(Math.random()-.5)*this.jitter*Math.PI*2,p.acc.x+=Math.cos(this.theta)*this.radius*this.strength,p.acc.y+=Math.sin(this.theta)*this.radius*this.strength},Wander}(Behaviour) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CoffeePhysics 4 | 5 | 104 | 105 | 106 | 107 |
108 | 109 |
110 | 111 |
112 |
113 |

CoffeePhysics

114 |

A Lightweight CoffeeScript Physics Engine

115 |
116 | Download 117 | View on Github 118 |
119 | 120 | 121 | 126 | 127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 316 | 317 | 318 | -------------------------------------------------------------------------------- /source/base.coffee: -------------------------------------------------------------------------------- 1 | ### Allows safe, dyamic creation of namespaces. ### 2 | 3 | namespace = (id) -> 4 | root = self 5 | root = root[path] ?= {} for path in id.split '.' 6 | 7 | ### RequestAnimationFrame shim. ### 8 | do -> 9 | 10 | time = 0 11 | vendors = ['ms', 'moz', 'webkit', 'o'] 12 | 13 | for vendor in vendors when not window.requestAnimationFrame 14 | window.requestAnimationFrame = window[ vendor + 'RequestAnimationFrame'] 15 | window.cancelAnimationFrame = window[ vendor + 'CancelAnimationFrame'] 16 | 17 | if not window.requestAnimationFrame 18 | 19 | window.requestAnimationFrame = (callback, element) -> 20 | now = new Date().getTime() 21 | delta = Math.max 0, 16 - (now - old) 22 | setTimeout (-> callback(time + delta)), delta 23 | old = now + delta 24 | 25 | if not window.cancelAnimationFrame 26 | 27 | window.cancelAnimationFrame = (id) -> 28 | clearTimeout id 29 | -------------------------------------------------------------------------------- /source/behaviour/Attraction.coffee: -------------------------------------------------------------------------------- 1 | ### Attraction Behaviour ### 2 | 3 | class Attraction extends Behaviour 4 | 5 | constructor: (@target = new Vector(), @radius = 1000, @strength = 100.0) -> 6 | 7 | @_delta = new Vector() 8 | @setRadius @radius 9 | 10 | super 11 | 12 | ### Sets the effective radius of the bahavious. ### 13 | setRadius: (radius) -> 14 | 15 | @radius = radius 16 | @radiusSq = radius * radius 17 | 18 | apply: (p, dt, index) -> 19 | 20 | #super p, dt, index 21 | 22 | # Vector pointing from particle to target. 23 | (@_delta.copy @target).sub p.pos 24 | 25 | # Squared distance to target. 26 | distSq = @_delta.magSq() 27 | 28 | # Limit force to behaviour radius. 29 | if distSq < @radiusSq and distSq > 0.000001 30 | 31 | # Calculate force vector. 32 | @_delta.norm().scale (1.0 - distSq / @radiusSq) 33 | 34 | #Apply force. 35 | p.acc.add @_delta.scale @strength 36 | -------------------------------------------------------------------------------- /source/behaviour/Behaviour.coffee: -------------------------------------------------------------------------------- 1 | ### Behaviour ### 2 | 3 | class Behaviour 4 | 5 | # Each behaviour has a unique id 6 | @GUID = 0 7 | 8 | constructor: -> 9 | 10 | @GUID = Behaviour.GUID++ 11 | @interval = 1 12 | 13 | ## console.log @, @GUID 14 | 15 | apply: (p, dt, index) -> 16 | 17 | # Store some data in each particle. 18 | (p['__behaviour' + @GUID] ?= {counter: 0}).counter++ -------------------------------------------------------------------------------- /source/behaviour/Collision.coffee: -------------------------------------------------------------------------------- 1 | ### Collision Behaviour ### 2 | 3 | # TODO: Collision response for non Verlet integrators. 4 | 5 | class Collision extends Behaviour 6 | 7 | constructor: (@useMass = yes, @callback = null) -> 8 | 9 | # Pool of collidable particles. 10 | @pool = [] 11 | 12 | # Delta between particle positions. 13 | @_delta = new Vector() 14 | 15 | super 16 | 17 | apply: (p, dt, index) -> 18 | 19 | #super p, dt, index 20 | 21 | # Check pool for collisions. 22 | for o in @pool[index..] when o isnt p 23 | 24 | # Delta between particles positions. 25 | (@_delta.copy o.pos).sub p.pos 26 | 27 | # Squared distance between particles. 28 | distSq = @_delta.magSq() 29 | 30 | # Sum of both radii. 31 | radii = p.radius + o.radius 32 | 33 | # Check if particles collide. 34 | if distSq <= radii * radii 35 | 36 | # Compute real distance. 37 | dist = Math.sqrt distSq 38 | 39 | # Determine overlap. 40 | overlap = radii - dist 41 | overlap += 0.5 42 | 43 | # Total mass. 44 | mt = p.mass + o.mass 45 | 46 | # Distribute collision responses. 47 | r1 = if @useMass then o.mass / mt else 0.5 48 | r2 = if @useMass then p.mass / mt else 0.5 49 | 50 | # Move particles so they no longer overlap. 51 | p.pos.add (@_delta.clone().norm().scale overlap * -r1) 52 | o.pos.add (@_delta.norm().scale overlap * r2) 53 | 54 | # Fire callback if defined. 55 | @callback?(p, o, overlap) -------------------------------------------------------------------------------- /source/behaviour/ConstantForce.coffee: -------------------------------------------------------------------------------- 1 | ### Constant Force Behaviour ### 2 | 3 | class ConstantForce extends Behaviour 4 | 5 | constructor: (@force = new Vector()) -> 6 | 7 | super 8 | 9 | apply: (p, dt,index) -> 10 | 11 | #super p, dt, index 12 | 13 | p.acc.add @force -------------------------------------------------------------------------------- /source/behaviour/EdgeBounce.coffee: -------------------------------------------------------------------------------- 1 | ### Edge Bounce Behaviour ### 2 | 3 | class EdgeBounce extends Behaviour 4 | 5 | constructor: (@min = new Vector(), @max = new Vector()) -> 6 | 7 | super 8 | 9 | apply: (p, dt, index) -> 10 | 11 | #super p, dt, index 12 | 13 | if p.pos.x - p.radius < @min.x 14 | 15 | p.pos.x = @min.x + p.radius 16 | 17 | else if p.pos.x + p.radius > @max.x 18 | 19 | p.pos.x = @max.x - p.radius 20 | 21 | if p.pos.y - p.radius < @min.y 22 | 23 | p.pos.y = @min.y + p.radius 24 | 25 | else if p.pos.y + p.radius > @max.y 26 | 27 | p.pos.y = @max.y - p.radius 28 | 29 | -------------------------------------------------------------------------------- /source/behaviour/EdgeWrap.coffee: -------------------------------------------------------------------------------- 1 | ### Edge Wrap Behaviour ### 2 | 3 | class EdgeWrap extends Behaviour 4 | 5 | constructor: (@min = new Vector(), @max = new Vector()) -> 6 | 7 | super 8 | 9 | apply: (p, dt, index) -> 10 | 11 | #super p, dt, index 12 | 13 | if p.pos.x + p.radius < @min.x 14 | 15 | p.pos.x = @max.x + p.radius 16 | p.old.pos.x = p.pos.x 17 | 18 | else if p.pos.x - p.radius > @max.x 19 | 20 | p.pos.x = @min.x - p.radius 21 | p.old.pos.x = p.pos.x 22 | 23 | if p.pos.y + p.radius < @min.y 24 | 25 | p.pos.y = @max.y + p.radius 26 | p.old.pos.y = p.pos.y 27 | 28 | else if p.pos.y - p.radius > @max.y 29 | 30 | p.pos.y = @min.y - p.radius 31 | p.old.pos.y = p.pos.y 32 | 33 | -------------------------------------------------------------------------------- /source/behaviour/Wander.coffee: -------------------------------------------------------------------------------- 1 | ### Wander Behaviour ### 2 | 3 | class Wander extends Behaviour 4 | 5 | constructor: (@jitter = 0.5, @radius = 100, @strength = 1.0) -> 6 | 7 | @theta = Math.random() * Math.PI * 2 8 | 9 | super 10 | 11 | apply: (p, dt, index) -> 12 | 13 | #super p, dt, index 14 | 15 | @theta += (Math.random() - 0.5) * @jitter * Math.PI * 2 16 | 17 | p.acc.x += Math.cos(@theta) * @radius * @strength 18 | p.acc.y += Math.sin(@theta) * @radius * @strength 19 | 20 | -------------------------------------------------------------------------------- /source/demos/AttractionDemo.coffee: -------------------------------------------------------------------------------- 1 | class AttractionDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super full 6 | 7 | min = new Vector 0.0, 0.0 8 | max = new Vector @width, @height 9 | 10 | bounds = new EdgeBounce min, max 11 | 12 | @physics.integrator = new Verlet() 13 | 14 | attraction = new Attraction @mouse.pos, 1200, 1200 15 | repulsion = new Attraction @mouse.pos, 200, -2000 16 | collide = new Collision() 17 | 18 | max = if full then 400 else 200 19 | 20 | for i in [0..max] 21 | 22 | p = new Particle (Random 0.1, 3.0) 23 | p.setRadius p.mass * 4 24 | 25 | p.moveTo new Vector (Random @width), (Random @height) 26 | 27 | p.behaviours.push attraction 28 | p.behaviours.push repulsion 29 | p.behaviours.push bounds 30 | p.behaviours.push collide 31 | 32 | collide.pool.push p 33 | 34 | @physics.particles.push p -------------------------------------------------------------------------------- /source/demos/BalloonDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BalloonDemo ### 2 | class BalloonDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | @physics.integrator = new ImprovedEuler() 9 | attraction = new Attraction @mouse.pos 10 | 11 | max = if full then 400 else 200 12 | 13 | for i in [0..max] 14 | 15 | p = new Particle (Random 0.25, 4.0) 16 | p.setRadius p.mass * 8 17 | 18 | p.behaviours.push new Wander 0.2 19 | p.behaviours.push attraction 20 | 21 | p.moveTo new Vector (Random @width), (Random @height) 22 | 23 | s = new Spring @mouse, p, (Random 30, 300), 1.0 24 | 25 | @physics.particles.push p 26 | @physics.springs.push s 27 | 28 | -------------------------------------------------------------------------------- /source/demos/BoundsDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BoundsDemo ### 2 | class BoundsDemo extends Demo 3 | 4 | setup: -> 5 | 6 | super 7 | 8 | min = new Vector 0.0, 0.0 9 | max = new Vector @width, @height 10 | 11 | edge = new EdgeWrap min, max 12 | 13 | for i in [0..200] 14 | 15 | p = new Particle (Random 0.5, 4.0) 16 | p.setRadius p.mass * 5 17 | 18 | p.moveTo new Vector (Random @width), (Random @height) 19 | 20 | p.behaviours.push new Wander 0.2, 120, Random 1.0, 2.0 21 | p.behaviours.push edge 22 | 23 | @physics.particles.push p 24 | 25 | -------------------------------------------------------------------------------- /source/demos/ChainDemo.coffee: -------------------------------------------------------------------------------- 1 | class ChainDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | @stiffness = 1.0 8 | @spacing = 2.0 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.viscosity = 0.0001 12 | @mouse.setMass 1000 13 | 14 | gap = 50.0 15 | min = new Vector -gap, -gap 16 | max = new Vector @width + gap, @height + gap 17 | 18 | edge = new EdgeBounce min, max 19 | 20 | center = new Vector @width * 0.5, @height * 0.5 21 | 22 | #@renderer.renderParticles = no 23 | 24 | wander = new Wander 0.05, 100.0, 80.0 25 | 26 | max = if full then 2000 else 600 27 | 28 | for i in [0..max] 29 | 30 | p = new Particle 6.0 31 | p.colour = '#FFFFFF' 32 | p.moveTo center 33 | p.setRadius 1.0 34 | 35 | p.behaviours.push wander 36 | p.behaviours.push edge 37 | 38 | @physics.particles.push p 39 | 40 | if op? then s = new Spring op, p, @spacing, @stiffness 41 | else s = new Spring @mouse, p, @spacing, @stiffness 42 | 43 | @physics.springs.push s 44 | 45 | op = p 46 | 47 | @physics.springs.push new Spring @mouse, p, @spacing, @stiffness -------------------------------------------------------------------------------- /source/demos/ClothDemo.coffee: -------------------------------------------------------------------------------- 1 | class ClothDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | # Only render springs. 8 | @renderer.renderParticles = false 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.timestep = 1.0 / 200 12 | @mouse.setMass 10 13 | 14 | # Add gravity to the simulation. 15 | @gravity = new ConstantForce new Vector 0.0, 80.0 16 | @physics.behaviours.push @gravity 17 | 18 | stiffness = 0.5 19 | size = if full then 8 else 10 20 | rows = if full then 30 else 25 21 | cols = if full then 55 else 40 22 | cell = [] 23 | 24 | sx = @width * 0.5 - cols * size * 0.5 25 | sy = @height * 0.5 - rows * size * 0.5 26 | 27 | for x in [0..cols] 28 | 29 | cell[x] = [] 30 | 31 | for y in [0..rows] 32 | 33 | p = new Particle(0.1) 34 | 35 | p.fixed = (y is 0) 36 | 37 | # Always set initial position using moveTo for Verlet 38 | p.moveTo new Vector (sx + x * size), (sy + y * size) 39 | 40 | if x > 0 41 | s = new Spring p, cell[x-1][y], size, stiffness 42 | @physics.springs.push s 43 | 44 | if y > 0 45 | s = new Spring p, cell[x][y - 1], size, stiffness 46 | @physics.springs.push s 47 | 48 | @physics.particles.push p 49 | cell[x][y] = p 50 | 51 | p = cell[Math.floor cols / 2][Math.floor rows / 2] 52 | s = new Spring @mouse, p, 10, 1.0 53 | @physics.springs.push s 54 | 55 | cell[0][0].fixed = true 56 | cell[cols - 1][0].fixed = true 57 | 58 | step: -> 59 | 60 | super 61 | 62 | @gravity.force.x = 50 * Math.sin new Date().getTime() * 0.0005 63 | -------------------------------------------------------------------------------- /source/demos/CollisionDemo.coffee: -------------------------------------------------------------------------------- 1 | ### CollisionDemo ### 2 | class CollisionDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | # Verlet gives us collision responce for free! 9 | @physics.integrator = new Verlet() 10 | 11 | min = new Vector 0.0, 0.0 12 | max = new Vector @width, @height 13 | 14 | bounds = new EdgeBounce min, max 15 | collide = new Collision 16 | attraction = new Attraction @mouse.pos, 2000, 1400 17 | 18 | max = if full then 350 else 150 19 | prob = if full then 0.35 else 0.5 20 | 21 | for i in [0..max] 22 | 23 | p = new Particle (Random 0.5, 4.0) 24 | p.setRadius p.mass * 4 25 | 26 | p.moveTo new Vector (Random @width), (Random @height) 27 | 28 | # Connect to spring or move free. 29 | if Random.bool prob 30 | s = new Spring @mouse, p, (Random 120, 180), 0.8 31 | @physics.springs.push s 32 | else 33 | p.behaviours.push attraction 34 | 35 | # Add particle to collision pool. 36 | collide.pool.push p 37 | 38 | # Allow particle to collide. 39 | p.behaviours.push collide 40 | p.behaviours.push bounds 41 | 42 | @physics.particles.push p 43 | 44 | onCollision: (p1, p2) => 45 | 46 | # Respond to collision. 47 | 48 | -------------------------------------------------------------------------------- /source/demos/Demo.coffee: -------------------------------------------------------------------------------- 1 | ### Demo ### 2 | class Demo 3 | 4 | @COLOURS = ['DC0048', 'F14646', '4AE6A9', '7CFF3F', '4EC9D9', 'E4272E'] 5 | 6 | constructor: -> 7 | 8 | @physics = new Physics() 9 | @mouse = new Particle() 10 | @mouse.fixed = true 11 | @height = window.innerHeight 12 | @width = window.innerWidth 13 | 14 | @renderTime = 0 15 | @counter = 0 16 | 17 | setup: (full = yes) -> 18 | 19 | ### Override and add paticles / springs here ### 20 | 21 | ### Initialise the demo (override). ### 22 | init: (@container, @renderer = new WebGLRenderer()) -> 23 | 24 | # Build the scene. 25 | @setup renderer.gl? 26 | 27 | # Give the particles random colours. 28 | for particle in @physics.particles 29 | particle.colour ?= Random.item Demo.COLOURS 30 | 31 | # Add event handlers. 32 | document.addEventListener 'touchmove', @mousemove, false 33 | document.addEventListener 'mousemove', @mousemove, false 34 | document.addEventListener 'resize', @resize, false 35 | 36 | # Add to render output to the DOM. 37 | @container.appendChild @renderer.domElement 38 | 39 | # Prepare the renderer. 40 | @renderer.mouse = @mouse 41 | @renderer.init @physics 42 | 43 | # Resize for the sake of the renderer. 44 | do @resize 45 | 46 | ### Handler for window resize event. ### 47 | resize: (event) => 48 | 49 | @width = window.innerWidth 50 | @height = window.innerHeight 51 | @renderer.setSize @width, @height 52 | 53 | ### Update loop. ### 54 | step: -> 55 | 56 | #console.profile 'physics' 57 | 58 | # Step physics. 59 | do @physics.step 60 | 61 | #console.profileEnd() 62 | 63 | #console.profile 'render' 64 | 65 | # Render. 66 | 67 | # Render every frame for WebGL, or every 3 frames for canvas. 68 | @renderer.render @physics if @renderer.gl? or ++@counter % 3 is 0 69 | 70 | #console.profileEnd() 71 | 72 | ### Clean up after yourself. ### 73 | destroy: -> 74 | 75 | ## console.log @, 'destroy' 76 | 77 | # Remove event handlers. 78 | document.removeEventListener 'touchmove', @mousemove, false 79 | document.removeEventListener 'mousemove', @mousemove, false 80 | document.removeEventListener 'resize', @resize, false 81 | 82 | # Remove the render output from the DOM. 83 | try container.removeChild @renderer.domElement 84 | catch error 85 | 86 | do @renderer.destroy 87 | do @physics.destroy 88 | 89 | @renderer = null 90 | @physics = null 91 | @mouse = null 92 | 93 | ### Handler for window mousemove event. ### 94 | mousemove: (event) => 95 | 96 | do event.preventDefault 97 | 98 | if event.touches and !!event.touches.length 99 | 100 | touch = event.touches[0] 101 | @mouse.pos.set touch.pageX, touch.pageY 102 | 103 | else 104 | 105 | @mouse.pos.set event.clientX, event.clientY 106 | -------------------------------------------------------------------------------- /source/demos/renderer/CanvasRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### Canvas Renderer ### 2 | class CanvasRenderer extends Renderer 3 | 4 | constructor: -> 5 | 6 | super 7 | 8 | @canvas = document.createElement 'canvas' 9 | @ctx = @canvas.getContext '2d' 10 | 11 | # Set the DOM element. 12 | @domElement = @canvas 13 | 14 | init: (physics) -> 15 | 16 | super physics 17 | 18 | render: (physics) -> 19 | 20 | super physics 21 | 22 | time = new Date().getTime() 23 | 24 | # Draw velocity. 25 | vel = new Vector() 26 | 27 | # Draw heading. 28 | dir = new Vector() 29 | 30 | # Clear canvas. 31 | @canvas.width = @canvas.width 32 | 33 | @ctx.globalCompositeOperation = 'lighter' 34 | @ctx.lineWidth = 1 35 | 36 | # Draw particles. 37 | if @renderParticles 38 | 39 | TWO_PI = Math.PI * 2 40 | 41 | for p in physics.particles 42 | 43 | @ctx.beginPath() 44 | @ctx.arc(p.pos.x, p.pos.y, p.radius, 0, TWO_PI, no) 45 | 46 | @ctx.fillStyle = '#' + (p.colour or 'FFFFFF') 47 | @ctx.fill() 48 | 49 | if @renderSprings 50 | 51 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 52 | @ctx.beginPath() 53 | 54 | for s in physics.springs 55 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 56 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 57 | 58 | @ctx.stroke() 59 | 60 | if @renderMouse 61 | 62 | # Draw mouse. 63 | @ctx.fillStyle = 'rgba(255,255,255,0.1)' 64 | @ctx.beginPath() 65 | @ctx.arc(@mouse.pos.x, @mouse.pos.y, 20, 0, TWO_PI) 66 | @ctx.fill() 67 | 68 | @renderTime = new Date().getTime() - time 69 | 70 | setSize: (@width, @height) => 71 | 72 | super @width, @height 73 | 74 | @canvas.width = @width 75 | @canvas.height = @height 76 | -------------------------------------------------------------------------------- /source/demos/renderer/DOMRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### DOM Renderer ### 2 | ### 3 | 4 | Updating styles: 5 | 6 | Nodes 7 | 8 | ### 9 | class DOMRenderer extends Renderer 10 | 11 | constructor: -> 12 | 13 | super 14 | 15 | @useGPU = yes 16 | 17 | @domElement = document.createElement 'div' 18 | @canvas = document.createElement 'canvas' 19 | @ctx = @canvas.getContext '2d' 20 | 21 | @canvas.style.position = 'absolute' 22 | @canvas.style.left = 0 23 | @canvas.style.top = 0 24 | 25 | @domElement.style.pointerEvents = 'none' 26 | @domElement.appendChild @canvas 27 | 28 | init: (physics) -> 29 | 30 | super physics 31 | 32 | # Set up particle DOM elements 33 | for p in physics.particles 34 | 35 | el = document.createElement 'span' 36 | st = el.style 37 | 38 | st.backgroundColor = p.colour 39 | st.borderRadius = p.radius 40 | st.marginLeft = -p.radius 41 | st.marginTop = -p.radius 42 | st.position = 'absolute' 43 | st.display = 'block' 44 | st.opacity = 0.85 45 | st.height = p.radius * 2 46 | st.width = p.radius * 2 47 | 48 | @domElement.appendChild el 49 | p.domElement = el 50 | 51 | # Set up mouse DOM element 52 | el = document.createElement 'span' 53 | st = el.style 54 | mr = 20 55 | 56 | st.backgroundColor = '#ffffff' 57 | st.borderRadius = mr 58 | st.marginLeft = -mr 59 | st.marginTop = -mr 60 | st.position = 'absolute' 61 | st.display = 'block' 62 | st.opacity = 0.1 63 | st.height = mr * 2 64 | st.width = mr * 2 65 | 66 | @domElement.appendChild el 67 | @mouse.domElement = el 68 | 69 | render: (physics) -> 70 | 71 | super physics 72 | 73 | time = new Date().getTime() 74 | 75 | if @renderParticles 76 | 77 | for p in physics.particles 78 | 79 | if @useGPU 80 | 81 | p.domElement.style.WebkitTransform = """ 82 | translate3d(#{p.pos.x|0}px,#{p.pos.y|0}px,0px) 83 | """ 84 | else 85 | 86 | p.domElement.style.left = p.pos.x 87 | p.domElement.style.top = p.pos.y 88 | 89 | if @renderSprings 90 | 91 | @canvas.width = @canvas.width 92 | 93 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 94 | @ctx.beginPath() 95 | 96 | for s in physics.springs 97 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 98 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 99 | 100 | @ctx.stroke() 101 | 102 | if @renderMouse 103 | 104 | if @useGPU 105 | 106 | @mouse.domElement.style.WebkitTransform = """ 107 | translate3d(#{@mouse.pos.x|0}px,#{@mouse.pos.y|0}px,0px) 108 | """ 109 | else 110 | 111 | @mouse.domElement.style.left = @mouse.pos.x 112 | @mouse.domElement.style.top = @mouse.pos.y 113 | 114 | @renderTime = new Date().getTime() - time 115 | 116 | setSize: (@width, @height) => 117 | 118 | super @width, @height 119 | 120 | @canvas.width = @width 121 | @canvas.height = @height 122 | 123 | destroy: -> 124 | 125 | while @domElement.hasChildNodes() 126 | @domElement.removeChild @domElement.lastChild 127 | -------------------------------------------------------------------------------- /source/demos/renderer/Renderer.coffee: -------------------------------------------------------------------------------- 1 | ### Base Renderer ### 2 | class Renderer 3 | 4 | constructor: -> 5 | 6 | @width = 0 7 | @height = 0 8 | 9 | @renderParticles = true 10 | @renderSprings = true 11 | @renderMouse = true 12 | @initialized = false 13 | @renderTime = 0 14 | 15 | init: (physics) -> 16 | 17 | @initialized = true 18 | 19 | render: (physics) -> 20 | 21 | if not @initialized then @init physics 22 | 23 | setSize: (@width, @height) => 24 | 25 | destroy: -> 26 | 27 | 28 | -------------------------------------------------------------------------------- /source/demos/renderer/WebGLRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### WebGL Renderer ### 2 | 3 | class WebGLRenderer extends Renderer 4 | 5 | # Particle vertex shader source. 6 | @PARTICLE_VS = ''' 7 | 8 | uniform vec2 viewport; 9 | attribute vec3 position; 10 | attribute float radius; 11 | attribute vec4 colour; 12 | varying vec4 tint; 13 | 14 | void main() { 15 | 16 | // convert the rectangle from pixels to 0.0 to 1.0 17 | vec2 zeroToOne = position.xy / viewport; 18 | zeroToOne.y = 1.0 - zeroToOne.y; 19 | 20 | // convert from 0->1 to 0->2 21 | vec2 zeroToTwo = zeroToOne * 2.0; 22 | 23 | // convert from 0->2 to -1->+1 (clipspace) 24 | vec2 clipSpace = zeroToTwo - 1.0; 25 | 26 | tint = colour; 27 | 28 | gl_Position = vec4(clipSpace, 0, 1); 29 | gl_PointSize = radius * 2.0; 30 | } 31 | ''' 32 | 33 | # Particle fragent shader source. 34 | @PARTICLE_FS = ''' 35 | 36 | precision mediump float; 37 | 38 | uniform sampler2D texture; 39 | varying vec4 tint; 40 | 41 | void main() { 42 | gl_FragColor = texture2D(texture, gl_PointCoord) * tint; 43 | } 44 | ''' 45 | 46 | # Spring vertex shader source. 47 | @SPRING_VS = ''' 48 | 49 | uniform vec2 viewport; 50 | attribute vec3 position; 51 | 52 | void main() { 53 | 54 | // convert the rectangle from pixels to 0.0 to 1.0 55 | vec2 zeroToOne = position.xy / viewport; 56 | zeroToOne.y = 1.0 - zeroToOne.y; 57 | 58 | // convert from 0->1 to 0->2 59 | vec2 zeroToTwo = zeroToOne * 2.0; 60 | 61 | // convert from 0->2 to -1->+1 (clipspace) 62 | vec2 clipSpace = zeroToTwo - 1.0; 63 | 64 | gl_Position = vec4(clipSpace, 0, 1); 65 | } 66 | ''' 67 | 68 | # Spring fragent shader source. 69 | @SPRING_FS = ''' 70 | 71 | void main() { 72 | gl_FragColor = vec4(1.0, 1.0, 1.0, 0.1); 73 | } 74 | ''' 75 | 76 | constructor: (@usePointSprites = true) -> 77 | 78 | super 79 | 80 | @particlePositionBuffer = null 81 | @particleRadiusBuffer = null 82 | @particleColourBuffer = null 83 | @particleTexture = null 84 | @particleShader = null 85 | 86 | @springPositionBuffer = null 87 | @springShader = null 88 | 89 | @canvas = document.createElement 'canvas' 90 | 91 | # Init WebGL. 92 | try @gl = @canvas.getContext 'experimental-webgl' catch error 93 | finally return new CanvasRenderer() if not @gl 94 | 95 | # Set the DOM element. 96 | @domElement = @canvas 97 | 98 | init: (physics) -> 99 | 100 | super physics 101 | 102 | @initShaders() 103 | @initBuffers physics 104 | 105 | # Create particle texture from canvas. 106 | @particleTexture = do @createParticleTextureData 107 | 108 | # Use additive blending. 109 | @gl.blendFunc @gl.SRC_ALPHA, @gl.ONE 110 | 111 | # Enable the other shit we need from WebGL. 112 | #@gl.enable @gl.VERTEX_PROGRAM_POINT_SIZE 113 | #@gl.enable @gl.TEXTURE_2D 114 | @gl.enable @gl.BLEND 115 | 116 | initShaders: -> 117 | 118 | # Create shaders. 119 | @particleShader = @createShaderProgram WebGLRenderer.PARTICLE_VS, WebGLRenderer.PARTICLE_FS 120 | @springShader = @createShaderProgram WebGLRenderer.SPRING_VS, WebGLRenderer.SPRING_FS 121 | 122 | # Store particle shader uniform locations. 123 | @particleShader.uniforms = 124 | viewport: @gl.getUniformLocation @particleShader, 'viewport' 125 | 126 | # Store spring shader uniform locations. 127 | @springShader.uniforms = 128 | viewport: @gl.getUniformLocation @springShader, 'viewport' 129 | 130 | # Store particle shader attribute locations. 131 | @particleShader.attributes = 132 | position: @gl.getAttribLocation @particleShader, 'position' 133 | radius: @gl.getAttribLocation @particleShader, 'radius' 134 | colour: @gl.getAttribLocation @particleShader, 'colour' 135 | 136 | # Store spring shader attribute locations. 137 | @springShader.attributes = 138 | position: @gl.getAttribLocation @springShader, 'position' 139 | 140 | console.log @particleShader 141 | 142 | initBuffers: (physics) -> 143 | 144 | colours = [] 145 | radii = [] 146 | 147 | # Create buffers. 148 | @particlePositionBuffer = do @gl.createBuffer 149 | @springPositionBuffer = do @gl.createBuffer 150 | @particleColourBuffer = do @gl.createBuffer 151 | @particleRadiusBuffer = do @gl.createBuffer 152 | 153 | # Create attribute arrays. 154 | for particle in physics.particles 155 | 156 | # Break the colour string into RGBA components. 157 | rgba = (particle.colour or '#FFFFFF').match(/[\dA-F]{2}/gi) 158 | 159 | # Parse into integers. 160 | r = (parseInt rgba[0], 16) or 255 161 | g = (parseInt rgba[1], 16) or 255 162 | b = (parseInt rgba[2], 16) or 255 163 | a = (parseInt rgba[3], 16) or 255 164 | 165 | # Prepare for adding to the colour buffer. 166 | colours.push r / 255, g / 255, b / 255, a / 255 167 | 168 | # Prepare for adding to the radius buffer. 169 | radii.push particle.radius or 32 170 | 171 | # Init Particle colour buffer. 172 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleColourBuffer 173 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(colours), @gl.STATIC_DRAW 174 | 175 | # Init Particle radius buffer. 176 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleRadiusBuffer 177 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(radii), @gl.STATIC_DRAW 178 | 179 | ## console.log @particleColourBuffer 180 | 181 | # Creates a generic texture for particles. 182 | createParticleTextureData: (size = 128) -> 183 | 184 | canvas = document.createElement 'canvas' 185 | canvas.width = canvas.height = size 186 | ctx = canvas.getContext '2d' 187 | rad = size * 0.5 188 | 189 | ctx.beginPath() 190 | ctx.arc rad, rad, rad, 0, Math.PI * 2, false 191 | ctx.closePath() 192 | 193 | ctx.fillStyle = '#FFF' 194 | ctx.fill() 195 | 196 | texture = @gl.createTexture() 197 | @setupTexture texture, canvas 198 | 199 | texture 200 | 201 | # Creates a WebGL texture from an image path or data. 202 | loadTexture: (source) -> 203 | 204 | texture = @gl.createTexture() 205 | texture.image = new Image() 206 | 207 | texture.image.onload = => 208 | 209 | @setupTexture texture, texture.image 210 | 211 | texture.image.src = source 212 | texture 213 | 214 | setupTexture: (texture, data) -> 215 | 216 | @gl.bindTexture @gl.TEXTURE_2D, texture 217 | @gl.texImage2D @gl.TEXTURE_2D, 0, @gl.RGBA, @gl.RGBA, @gl.UNSIGNED_BYTE, data 218 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_MIN_FILTER, @gl.LINEAR 219 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_MAG_FILTER, @gl.LINEAR 220 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_WRAP_S, @gl.CLAMP_TO_EDGE 221 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_WRAP_T, @gl.CLAMP_TO_EDGE 222 | @gl.generateMipmap @gl.TEXTURE_2D 223 | @gl.bindTexture @gl.TEXTURE_2D, null 224 | 225 | texture 226 | 227 | # Creates a shader program from vertex and fragment shader sources. 228 | createShaderProgram: (_vs, _fs) -> 229 | 230 | vs = @gl.createShader @gl.VERTEX_SHADER 231 | fs = @gl.createShader @gl.FRAGMENT_SHADER 232 | 233 | @gl.shaderSource vs, _vs 234 | @gl.shaderSource fs, _fs 235 | 236 | @gl.compileShader vs 237 | @gl.compileShader fs 238 | 239 | if not @gl.getShaderParameter vs, @gl.COMPILE_STATUS 240 | alert @gl.getShaderInfoLog vs 241 | null 242 | 243 | if not @gl.getShaderParameter fs, @gl.COMPILE_STATUS 244 | alert @gl.getShaderInfoLog fs 245 | null 246 | 247 | prog = do @gl.createProgram 248 | 249 | @gl.attachShader prog, vs 250 | @gl.attachShader prog, fs 251 | @gl.linkProgram prog 252 | 253 | ## console.log 'Vertex Shader Compiled', @gl.getShaderParameter vs, @gl.COMPILE_STATUS 254 | ## console.log 'Fragment Shader Compiled', @gl.getShaderParameter fs, @gl.COMPILE_STATUS 255 | ## console.log 'Program Linked', @gl.getProgramParameter prog, @gl.LINK_STATUS 256 | 257 | prog 258 | 259 | # Sets the size of the viewport. 260 | setSize: (@width, @height) => 261 | 262 | ## console.log 'resize', @width, @height 263 | 264 | super @width, @height 265 | 266 | @canvas.width = @width 267 | @canvas.height = @height 268 | @gl.viewport 0, 0, @width, @height 269 | 270 | # Update shader uniforms. 271 | @gl.useProgram @particleShader 272 | @gl.uniform2fv @particleShader.uniforms.viewport, new Float32Array [@width, @height] 273 | 274 | # Update shader uniforms. 275 | @gl.useProgram @springShader 276 | @gl.uniform2fv @springShader.uniforms.viewport, new Float32Array [@width, @height] 277 | 278 | # Renders the current physics state. 279 | render: (physics) -> 280 | 281 | super 282 | 283 | # Clear the viewport. 284 | @gl.clear @gl.COLOR_BUFFER_BIT | @gl.DEPTH_BUFFER_BIT 285 | 286 | # Draw particles. 287 | if @renderParticles 288 | 289 | vertices = [] 290 | 291 | # Update particle positions. 292 | for p in physics.particles 293 | vertices.push p.pos.x, p.pos.y, 0.0 294 | 295 | # Bind the particle texture. 296 | @gl.activeTexture @gl.TEXTURE0 297 | @gl.bindTexture @gl.TEXTURE_2D, @particleTexture 298 | 299 | # Use the particle program. 300 | @gl.useProgram @particleShader 301 | 302 | # Setup vertices. 303 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particlePositionBuffer 304 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(vertices), @gl.STATIC_DRAW 305 | @gl.vertexAttribPointer @particleShader.attributes.position, 3, @gl.FLOAT, false, 0, 0 306 | @gl.enableVertexAttribArray @particleShader.attributes.position 307 | 308 | # Setup colours. 309 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleColourBuffer 310 | @gl.enableVertexAttribArray @particleShader.attributes.colour 311 | @gl.vertexAttribPointer @particleShader.attributes.colour, 4, @gl.FLOAT, false, 0, 0 312 | 313 | # Setup radii. 314 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleRadiusBuffer 315 | @gl.enableVertexAttribArray @particleShader.attributes.radius 316 | @gl.vertexAttribPointer @particleShader.attributes.radius, 1, @gl.FLOAT, false, 0, 0 317 | 318 | # Draw particles. 319 | @gl.drawArrays @gl.POINTS, 0, vertices.length / 3 320 | 321 | # Draw springs. 322 | if @renderSprings and physics.springs.length > 0 323 | 324 | vertices = [] 325 | 326 | # Update spring positions. 327 | for s in physics.springs 328 | vertices.push s.p1.pos.x, s.p1.pos.y, 0.0 329 | vertices.push s.p2.pos.x, s.p2.pos.y, 0.0 330 | 331 | # Use the spring program. 332 | @gl.useProgram @springShader 333 | 334 | # Setup vertices. 335 | @gl.bindBuffer @gl.ARRAY_BUFFER, @springPositionBuffer 336 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(vertices), @gl.STATIC_DRAW 337 | @gl.vertexAttribPointer @springShader.attributes.position, 3, @gl.FLOAT, false, 0, 0 338 | @gl.enableVertexAttribArray @springShader.attributes.position 339 | 340 | # Draw springs. 341 | @gl.drawArrays @gl.LINES, 0, vertices.length / 3 342 | 343 | destroy: -> 344 | 345 | ## console.log 'Destroy' 346 | -------------------------------------------------------------------------------- /source/engine/Particle.coffee: -------------------------------------------------------------------------------- 1 | ### Particle ### 2 | class Particle 3 | 4 | @GUID = 0 5 | 6 | constructor: (@mass = 1.0) -> 7 | 8 | # Set a unique id. 9 | @id = 'p' + Particle.GUID++ 10 | 11 | # Set initial mass. 12 | @setMass @mass 13 | 14 | # Set initial radius. 15 | @setRadius 1.0 16 | 17 | # Apply forces. 18 | @fixed = false 19 | 20 | # Behaviours to be applied. 21 | @behaviours = [] 22 | 23 | # Current position. 24 | @pos = new Vector() 25 | 26 | # Current velocity. 27 | @vel = new Vector() 28 | 29 | # Current force. 30 | @acc = new Vector() 31 | 32 | # Previous state. 33 | @old = 34 | pos: new Vector() 35 | vel: new Vector() 36 | acc: new Vector() 37 | 38 | ### Moves the particle to a given location vector. ### 39 | moveTo: (pos) -> 40 | 41 | @pos.copy pos 42 | @old.pos.copy pos 43 | 44 | ### Sets the mass of the particle. ### 45 | setMass: (@mass = 1.0) -> 46 | 47 | # The inverse mass. 48 | @massInv = 1.0 / @mass 49 | 50 | ### Sets the radius of the particle. ### 51 | setRadius: (@radius = 1.0) -> 52 | 53 | @radiusSq = @radius * @radius 54 | 55 | ### Applies all behaviours to derive new force. ### 56 | update: (dt, index) -> 57 | 58 | # Apply all behaviours. 59 | 60 | if not @fixed 61 | 62 | for behaviour in @behaviours 63 | 64 | behaviour.apply @, dt, index -------------------------------------------------------------------------------- /source/engine/Physics.coffee: -------------------------------------------------------------------------------- 1 | ### Physics Engine ### 2 | 3 | class Physics 4 | 5 | constructor: (@integrator = new Euler()) -> 6 | 7 | # Fixed timestep. 8 | @timestep = 1.0 / 60 9 | 10 | # Friction within the system. 11 | @viscosity = 0.005 12 | 13 | # Global behaviours. 14 | @behaviours = [] 15 | 16 | # Time in seconds. 17 | @_time = 0.0 18 | 19 | # Last step duration. 20 | @_step = 0.0 21 | 22 | # Current time. 23 | @_clock = null 24 | 25 | # Time buffer. 26 | @_buffer = 0.0 27 | 28 | # Max iterations per step. 29 | @_maxSteps = 4 30 | 31 | # Particles in system. 32 | @particles = [] 33 | 34 | # Springs in system. 35 | @springs = [] 36 | 37 | ### Performs a numerical integration step. ### 38 | integrate: (dt) -> 39 | 40 | # Drag is inversely proportional to viscosity. 41 | drag = 1.0 - @viscosity 42 | 43 | # Update particles / apply behaviours. 44 | 45 | for particle, index in @particles 46 | 47 | for behaviour in @behaviours 48 | 49 | behaviour.apply particle, dt, index 50 | 51 | particle.update dt, index 52 | 53 | # Integrate motion. 54 | 55 | @integrator.integrate @particles, dt, drag 56 | 57 | # Compute all springs. 58 | 59 | for spring in @springs 60 | 61 | spring.apply() 62 | 63 | ### Steps the system. ### 64 | step: -> 65 | 66 | # Initialise the clock on first step. 67 | @_clock ?= new Date().getTime() 68 | 69 | # Compute delta time since last step. 70 | time = new Date().getTime() 71 | delta = time - @_clock 72 | 73 | # No sufficient change. 74 | return if delta <= 0.0 75 | 76 | # Convert time to seconds. 77 | delta *= 0.001 78 | 79 | # Update the clock. 80 | @_clock = time 81 | 82 | # Increment time buffer. 83 | @_buffer += delta 84 | 85 | # Integrate until the buffer is empty or until the 86 | # maximum amount of iterations per step is reached. 87 | 88 | i = 0 89 | 90 | while @_buffer >= @timestep and ++i < @_maxSteps 91 | 92 | # Integrate motion by fixed timestep. 93 | @integrate @timestep 94 | 95 | # Reduce buffer by one timestep. 96 | @_buffer -= @timestep 97 | 98 | # Increment running time. 99 | @_time += @timestep 100 | 101 | # Store step time for debugging. 102 | @_step = new Date().getTime() - time 103 | 104 | ### Clean up after yourself. ### 105 | destroy: -> 106 | 107 | @integrator = null 108 | @particles = null 109 | @springs = null 110 | 111 | -------------------------------------------------------------------------------- /source/engine/Spring.coffee: -------------------------------------------------------------------------------- 1 | ### Spring ### 2 | 3 | class Spring 4 | 5 | constructor: (@p1, @p2, @restLength = 100, @stiffness = 1.0) -> 6 | 7 | @_delta = new Vector() 8 | 9 | # F = -kx 10 | 11 | apply: -> 12 | 13 | (@_delta.copy @p2.pos).sub @p1.pos 14 | 15 | dist = @_delta.mag() + 0.000001 16 | force = (dist - @restLength) / (dist * (@p1.massInv + @p2.massInv)) * @stiffness 17 | 18 | if not @p1.fixed 19 | 20 | @p1.pos.add (@_delta.clone().scale force * @p1.massInv) 21 | 22 | if not @p2.fixed 23 | 24 | @p2.pos.add (@_delta.scale -force * @p2.massInv) -------------------------------------------------------------------------------- /source/engine/integrator/Euler.coffee: -------------------------------------------------------------------------------- 1 | ### Euler Integrator ### 2 | 3 | class Euler extends Integrator 4 | 5 | # v += a * dt 6 | # x += v * dt 7 | 8 | integrate: (particles, dt, drag) -> 9 | 10 | vel = new Vector() 11 | 12 | for p in particles when not p.fixed 13 | 14 | # Store previous location. 15 | p.old.pos.copy p.pos 16 | 17 | # Scale force to mass. 18 | p.acc.scale p.massInv 19 | 20 | # Duplicate velocity to preserve momentum. 21 | vel.copy p.vel 22 | 23 | # Add force to velocity. 24 | p.vel.add p.acc.scale dt 25 | 26 | # Add velocity to position. 27 | p.pos.add vel.scale dt 28 | 29 | # Apply friction. 30 | if drag then p.vel.scale drag 31 | 32 | # Reset forces. 33 | p.acc.clear() -------------------------------------------------------------------------------- /source/engine/integrator/ImprovedEuler.coffee: -------------------------------------------------------------------------------- 1 | ### Improved Euler Integrator ### 2 | 3 | class ImprovedEuler extends Integrator 4 | 5 | # x += (v * dt) + (a * 0.5 * dt * dt) 6 | # v += a * dt 7 | 8 | integrate: (particles, dt, drag) -> 9 | 10 | acc = new Vector() 11 | vel = new Vector() 12 | 13 | dtSq = dt * dt 14 | 15 | for p in particles when not p.fixed 16 | 17 | # Store previous location. 18 | p.old.pos.copy p.pos 19 | 20 | # Scale force to mass. 21 | p.acc.scale p.massInv 22 | 23 | # Duplicate velocity to preserve momentum. 24 | vel.copy p.vel 25 | 26 | # Duplicate force. 27 | acc.copy p.acc 28 | 29 | # Update position. 30 | p.pos.add (vel.scale dt).add (acc.scale 0.5 * dtSq) 31 | 32 | # Update velocity. 33 | p.vel.add p.acc.scale dt 34 | 35 | # Apply friction. 36 | if drag then p.vel.scale drag 37 | 38 | # Reset forces. 39 | p.acc.clear() 40 | -------------------------------------------------------------------------------- /source/engine/integrator/Integrator.coffee: -------------------------------------------------------------------------------- 1 | ### Integrator ### 2 | 3 | class Integrator 4 | 5 | integrate: (particles, dt) -> 6 | 7 | # Override. -------------------------------------------------------------------------------- /source/engine/integrator/Verlet.coffee: -------------------------------------------------------------------------------- 1 | ### Velocity Verlet Integrator ### 2 | 3 | class Verlet extends Integrator 4 | 5 | # v = x - ox 6 | # x = x + (v + a * dt * dt) 7 | 8 | integrate: (particles, dt, drag) -> 9 | 10 | pos = new Vector() 11 | 12 | dtSq = dt * dt 13 | 14 | for p in particles when not p.fixed 15 | 16 | # Scale force to mass. 17 | p.acc.scale p.massInv 18 | 19 | # Derive velocity. 20 | (p.vel.copy p.pos).sub p.old.pos 21 | 22 | # Apply friction. 23 | if drag then p.vel.scale drag 24 | 25 | # Apply forces to new position. 26 | (pos.copy p.pos).add (p.vel.add p.acc.scale dtSq) 27 | 28 | # Store old position. 29 | p.old.pos.copy p.pos 30 | 31 | # update position. 32 | p.pos.copy pos 33 | 34 | # Reset forces. 35 | p.acc.clear() 36 | 37 | -------------------------------------------------------------------------------- /source/math/Random.coffee: -------------------------------------------------------------------------------- 1 | ### Random ### 2 | 3 | Random = (min, max) -> 4 | 5 | if not max? 6 | max = min 7 | min = 0 8 | 9 | min + Math.random() * (max - min) 10 | 11 | Random.int = (min, max) -> 12 | 13 | if not max? 14 | max = min 15 | min = 0 16 | 17 | Math.floor min + Math.random() * (max - min) 18 | 19 | Random.sign = (prob = 0.5) -> 20 | 21 | if do Math.random < prob then 1 else -1 22 | 23 | Random.bool = (prob = 0.5) -> 24 | 25 | do Math.random < prob 26 | 27 | Random.item = (list) -> 28 | 29 | list[ Math.floor Math.random() * list.length ] -------------------------------------------------------------------------------- /source/math/Vector.coffee: -------------------------------------------------------------------------------- 1 | ### 2D Vector ### 2 | 3 | class Vector 4 | 5 | ### Adds two vectors and returns the product. ### 6 | @add: (v1, v2) -> 7 | new Vector v1.x + v2.x, v1.y + v2.y 8 | 9 | ### Subtracts v2 from v1 and returns the product. ### 10 | @sub: (v1, v2) -> 11 | new Vector v1.x - v2.x, v1.y - v2.y 12 | 13 | ### Projects one vector (v1) onto another (v2) ### 14 | @project: (v1, v2) -> 15 | v1.clone().scale ((v1.dot v2) / v1.magSq()) 16 | 17 | ### Creates a new Vector instance. ### 18 | constructor: (@x = 0.0, @y = 0.0) -> 19 | 20 | ### Sets the components of this vector. ### 21 | set: (@x, @y) -> 22 | @ 23 | 24 | ### Add a vector to this one. ### 25 | add: (v) -> 26 | @x += v.x; @y += v.y; @ 27 | 28 | ### Subtracts a vector from this one. ### 29 | sub: (v) -> 30 | @x -= v.x; @y -= v.y; @ 31 | 32 | ### Scales this vector by a value. ### 33 | scale: (f) -> 34 | @x *= f; @y *= f; @ 35 | 36 | ### Computes the dot product between vectors. ### 37 | dot: (v) -> 38 | @x * v.x + @y * v.y 39 | 40 | ### Computes the cross product between vectors. ### 41 | cross: (v) -> 42 | (@x * v.y) - (@y * v.x) 43 | 44 | ### Computes the magnitude (length). ### 45 | mag: -> 46 | Math.sqrt @x*@x + @y*@y 47 | 48 | ### Computes the squared magnitude (length). ### 49 | magSq: -> 50 | @x*@x + @y*@y 51 | 52 | ### Computes the distance to another vector. ### 53 | dist: (v) -> 54 | dx = v.x - @x; dy = v.y - @y 55 | Math.sqrt dx*dx + dy*dy 56 | 57 | ### Computes the squared distance to another vector. ### 58 | distSq: (v) -> 59 | dx = v.x - @x; dy = v.y - @y 60 | dx*dx + dy*dy 61 | 62 | ### Normalises the vector, making it a unit vector (of length 1). ### 63 | norm: -> 64 | m = Math.sqrt @x*@x + @y*@y 65 | @x /= m 66 | @y /= m 67 | @ 68 | 69 | ### Limits the vector length to a given amount. ### 70 | limit: (l) -> 71 | mSq = @x*@x + @y*@y 72 | if mSq > l*l 73 | m = Math.sqrt mSq 74 | @x /= m; @y /= m 75 | @x *= l; @y *= l 76 | @ 77 | 78 | ### Copies components from another vector. ### 79 | copy: (v) -> 80 | @x = v.x; @y = v.y; @ 81 | 82 | ### Clones this vector to a new itentical one. ### 83 | clone: -> 84 | new Vector @x, @y 85 | 86 | ### Resets the vector to zero. ### 87 | clear: -> 88 | @x = 0.0; @y = 0.0; @ 89 | --------------------------------------------------------------------------------