├── .gitignore ├── lib ├── yuicompressor-2.4.2.jar ├── keymaster.min.js └── yepnope.1.0.2-min.js ├── examples ├── Domino │ ├── assets │ │ ├── bg.jpg │ │ └── banner.png │ ├── index.html │ └── Domino.js ├── CarDemo │ ├── Capsule.js │ ├── Car.js │ ├── CarDemo.html │ ├── Rotator.js │ ├── SwingDoor.js │ ├── Bridge.js │ ├── Surfaces.js │ ├── RectComposite.js │ └── CarDemo.js ├── RigidDemo │ ├── index.html │ └── RigidDemo.js └── RobotDemo │ ├── RobotDemo.html │ ├── Body.js │ ├── RobotDemo.js │ ├── Motor.js │ ├── Leg.js │ └── Robot.js ├── src ├── Interval.js ├── Collision.js ├── IForce.js ├── MathUtil.js ├── AbstractConstraint.js ├── VectorForce.js ├── CircleParticle.js ├── RigidCollisionResolver.js ├── CollisionResolver.js ├── Renderer.js ├── RigidCircle.js ├── Composite.js ├── Signal.js ├── RimParticle.js ├── Vector.js ├── Engine.js ├── RigidItem.js ├── AbstractItem.js ├── RectangleParticle.js ├── RigidRectangle.js ├── WheelParticle.js ├── Group.js ├── AbstractCollection.js ├── JPE.js ├── SpringConstraint.js ├── EaselRenderer.js ├── AbstractParticle.js ├── SpringConstraintParticle.js └── CollisionDetector.js ├── ant ├── build.properties └── build.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | src-deprecated 2 | photowall 3 | photowall/* -------------------------------------------------------------------------------- /lib/yuicompressor-2.4.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colorhook/JPE/HEAD/lib/yuicompressor-2.4.2.jar -------------------------------------------------------------------------------- /examples/Domino/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colorhook/JPE/HEAD/examples/Domino/assets/bg.jpg -------------------------------------------------------------------------------- /examples/Domino/assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colorhook/JPE/HEAD/examples/Domino/assets/banner.png -------------------------------------------------------------------------------- /src/Interval.js: -------------------------------------------------------------------------------- 1 | define("JPE/Interval", function(require, exports, module) { 2 | module.exports = function(min, max) { 3 | this.min = min; 4 | this.max = max; 5 | }; 6 | }); -------------------------------------------------------------------------------- /src/Collision.js: -------------------------------------------------------------------------------- 1 | define("JPE/Collision", function(require, exports, module) { 2 | 3 | module.exports = function(vn, vt) { 4 | this.vn = vn; 5 | this.vt = vt; 6 | } 7 | 8 | }); -------------------------------------------------------------------------------- /src/IForce.js: -------------------------------------------------------------------------------- 1 | define("JPE/IForce", function(require, exports, module) { 2 | var IForce = function() {}; 3 | IForce.prototype.getValue = function(invMass) { 4 | return null; 5 | }; 6 | module.exports = IForce; 7 | }); -------------------------------------------------------------------------------- /src/MathUtil.js: -------------------------------------------------------------------------------- 1 | define("JPE/MathUtil", function(require, exports, module) { 2 | 3 | exports.ONE_EIGHTY_OVER_PI = 180 / Math.PI; 4 | exports.PI_OVER_ONE_EIGHTY = Math.PI / 180; 5 | 6 | exports.clamp = function(n, min, max) { 7 | if (n < min) return min; 8 | if (n > max) return max; 9 | return n; 10 | }; 11 | exports.sign = function(val) { 12 | if (val < 0) return -1 13 | return 1; 14 | }; 15 | 16 | }); -------------------------------------------------------------------------------- /src/AbstractConstraint.js: -------------------------------------------------------------------------------- 1 | define("JPE/AbstractConstraint", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var AbstractItem = require("JPE/AbstractItem"); 5 | var Signal = require("JPE/Signal"); 6 | 7 | var AbstractConstraint = function(stiffness) { 8 | this.stiffness = stiffness; 9 | this.setStyle(); 10 | this._pool = {}; 11 | this.beforeRenderSignal = new Signal(); 12 | this.afterRenderSignal = new Signal(); 13 | }; 14 | 15 | JPE.extend(AbstractConstraint, AbstractItem, { 16 | resolve: function() {} 17 | }); 18 | 19 | module.exports = AbstractConstraint; 20 | }); -------------------------------------------------------------------------------- /examples/CarDemo/Capsule.js: -------------------------------------------------------------------------------- 1 | define("Capsule", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Group = require("JPE/Group"); 5 | var CircleParticle = require("JPE/CircleParticle"); 6 | var SpringConstraint = require("JPE/SpringConstraint"); 7 | 8 | var Capsule = function(colC){ 9 | 10 | Group.prototype.constructor.apply(this); 11 | 12 | var capsuleP1 = new CircleParticle(300, 10, 14, false, 1.3, 0.4); 13 | capsuleP1.setStyle(0, colC, 1, colC); 14 | this.addParticle(capsuleP1); 15 | 16 | var capsuleP2 = new CircleParticle(325, 35, 14, false, 1.3, 0.4); 17 | capsuleP2.setStyle(0, colC, 1, colC); 18 | this.addParticle(capsuleP2); 19 | 20 | var capsule = new SpringConstraint(capsuleP1, capsuleP2, 1, true, 24); 21 | capsule.setStyle(5, colC, 1, colC, 1); 22 | this.addConstraint(capsule); 23 | } 24 | 25 | JPE.extend(Capsule, Group); 26 | 27 | module.exports = Capsule; 28 | }); 29 | -------------------------------------------------------------------------------- /ant/build.properties: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | ### Build settings 3 | ###################################################################### 4 | 5 | build.version = 2.0.0 6 | build.name = JPE-${build.version} 7 | build.yui-name = ${build.name}-min 8 | build.gcc-name = ${build.name}-gcc 9 | build.files = JPE.js, Signal.js, Engine.js, Vector.js, Interval.js, Collision.js,\ 10 | MathUtil.js, \ 11 | AbstractItem.js, CollisionResolver.js, CollisionDetector.js, AbstractCollection.js, AbstractParticle.js, Group.js,\ 12 | Composite.js, CircleParticle.js, RectangleParticle.js, RimParticle.js, WheelParticle.js, AbstractConstraint.js,\ 13 | SpringConstraint.js, SpringConstraintParticle.js, Renderer.js, EaselRenderer.js 14 | 15 | dir.src = ${basedir}/src 16 | dir.lib = ${basedir}/lib 17 | dir.build = ${basedir}/build 18 | dir.ant = ${basedir}/ant 19 | 20 | compress.yui = ${dir.lib}/yuicompressor-2.4.2.jar 21 | compress.gcc = ${dir.lib}/compiler.jar 22 | -------------------------------------------------------------------------------- /src/VectorForce.js: -------------------------------------------------------------------------------- 1 | define("JPE/VectorForce", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var IForce = require("JPE/IForce"); 5 | var Vector = require("JPE/Vector"); 6 | 7 | var VectorForce = function(useMass, vx, vy) { 8 | this.fvx = vx; 9 | this.fvy = vy; 10 | this.scaleMass = useMass; 11 | this.value = new Vector(vx, vy); 12 | }; 13 | 14 | JPE.extend(VectorForce, IForce, { 15 | setVx: function(x) { 16 | this.fvx = x; 17 | this.value.x = x; 18 | }, 19 | setVy: function(y) { 20 | this.fvy = y; 21 | this.value.y = y; 22 | }, 23 | setUseMass: function(b) { 24 | this.scaleMass = b; 25 | }, 26 | getValue: function(invMass) { 27 | if (this.scaleMass) { 28 | this.value.setTo(this.fvx * invMass, this.fvy * invMass); 29 | } 30 | return this.value; 31 | } 32 | }); 33 | 34 | module.exports = VectorForce; 35 | }); -------------------------------------------------------------------------------- /examples/CarDemo/Car.js: -------------------------------------------------------------------------------- 1 | define("Car", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Group = require("JPE/Group"); 5 | var Vector = require("JPE/Vector"); 6 | var CircleParticle = require("JPE/CircleParticle"); 7 | var WheelParticle = require("JPE/WheelParticle"); 8 | var SpringConstraint = require("JPE/SpringConstraint"); 9 | 10 | var Car = function(colC, colE){ 11 | 12 | Group.prototype.constructor.apply(this); 13 | 14 | var wheelParticleA = new WheelParticle(140,10,14,false,2); 15 | wheelParticleA.setStyle(0, colC, 1, colE); 16 | this.addParticle(wheelParticleA); 17 | 18 | var wheelParticleB = new WheelParticle(200,10,14,false,2); 19 | wheelParticleB.setStyle(1, colC, 1, colE); 20 | this.addParticle(wheelParticleB); 21 | 22 | var wheelConnector = new SpringConstraint(wheelParticleA, wheelParticleB, 0.5, true, 8); 23 | wheelConnector.setStyle(1, colC, 1, colE); 24 | this.addConstraint(wheelConnector); 25 | 26 | this.wheelParticleA = wheelParticleA; 27 | this.wheelParticleB = wheelParticleB; 28 | } 29 | 30 | 31 | JPE.extend(Car, Group, { 32 | wheelParticleA: null, 33 | wheelParticleB: null, 34 | setSpeed: function(s){ 35 | this.wheelParticleA.setAngularVelocity(s); 36 | this.wheelParticleB.setAngularVelocity(s); 37 | } 38 | }); 39 | 40 | module.exports = Car; 41 | }); 42 | -------------------------------------------------------------------------------- /examples/CarDemo/CarDemo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JPE Example - Car Demo 6 | 7 | 21 | 22 | 23 | Fork me on GitHub 24 |

Car - JPE demo

25 | 26 | 27 | 28 | 29 |
30 |

Click inside the demo, then use:

31 |

A key to move the car left

32 |

D key to move the car right

33 |

APE version: http://www.cove.org/ape/demo1.htm

34 |
35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | -------------------------------------------------------------------------------- /examples/RigidDemo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rigid Demo - JPE demos 7 | 14 | 15 | 16 | Fork me on GitHub 17 |
18 |

Rigid Demo - JPE demo

19 |

Press UP key to drop more objects

20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JPE - JavaScript Physics Engine 2 | ======================= 3 | 4 | JPE (JavaScript Physics Engine) is used for games, creative arts or just fun. 5 | 6 | Licence 7 | -------------- 8 | JPE is free to use under MIT license. 9 | 10 | Copyright (c) 2013 http://colorhook.com 11 | author: colorhook@gmail.com 12 | version: 1.0.0 13 | license: Released under the MIT License. 14 | 15 | Transplant from Flash AS3 APE Engine 16 | http://www.cove.org/ape/ 17 | Copyright (c) 2006, 2007 Alec Cove 18 | Released under the MIT Licenses. 19 | 20 | Wiki 21 | ----------------- 22 | * [JPE Core API](https://github.com/colorhook/JPE/wiki/Core) 23 | * [EaselRenderer](https://github.com/colorhook/JPE/wiki/EaselRenderer) 24 | * [Group & Composite](https://github.com/colorhook/JPE/wiki/Group-&-Composite) 25 | * [Particle](https://github.com/colorhook/JPE/wiki/Particle) 26 | * [Signal](https://github.com/colorhook/JPE/wiki/Signal) 27 | 28 | Demo 29 | -------------------- 30 | * [CarDemo](http://colorhook.github.io/JPE/examples/CarDemo/CarDemo.html) 31 | * [RobotDemo](http://colorhook.github.io/JPE/examples/RobotDemo/RobotDemo.html) 32 | * [RigidDemo](http://colorhook.github.io/JPE/examples/RigidDemo/index.html) 33 | * [Domino Game](http://colorhook.github.io/JPE/examples/Domino/index.html) 34 | 35 | Bugs & Feedback 36 | ---------------- 37 | 38 | Please feel free to report bugs or feature requests. 39 | You can send me private message on `github`, or send me an email to: [colorhook@gmail.com] 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/RobotDemo/RobotDemo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JPE Example - Robot Demo 6 | 7 | 8 | 23 | 24 | 25 | Fork me on GitHub 26 |

Robot - JPE demo

27 | 28 | 29 |
30 |

Click inside the demo, then use:

31 |

P key to toggle power

32 |

R key to reverse direction

33 |

H key to toggle hiding the back legs

34 |

APE version: http://www.cove.org/ape/demo2.htm

35 |
36 | 37 | 38 | 39 | 48 | 49 | -------------------------------------------------------------------------------- /src/CircleParticle.js: -------------------------------------------------------------------------------- 1 | define("JPE/CircleParticle", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var AbstractParticle = require("JPE/AbstractParticle"); 5 | 6 | var CircleParticle = function(x, y, radius, fixed, mass, elasticity, friction) { 7 | mass = mass || 1; 8 | elasticity = elasticity || 0.3; 9 | friction = friction || 0; 10 | this._radius = radius; 11 | AbstractParticle.prototype.constructor.call(this, x, y, fixed, mass, elasticity, friction); 12 | }; 13 | 14 | JPE.extend(CircleParticle, AbstractParticle, { 15 | 16 | getRadius: function() { 17 | return this._radius; 18 | }, 19 | 20 | /** 21 | * @private 22 | */ 23 | setRadius: function(t) { 24 | this._radius = t; 25 | }, 26 | 27 | /** 28 | * @private 29 | */ 30 | getProjection: function(axis) { 31 | 32 | var c = this.samp.dot(axis); 33 | 34 | this.interval.min = c - this._radius; 35 | this.interval.max = c + this._radius; 36 | return this.interval; 37 | }, 38 | /** 39 | * @private 40 | */ 41 | getIntervalX: function() { 42 | this.interval.min = this.curr.x - this._radius; 43 | this.interval.max = this.curr.x + this._radius; 44 | return this.interval; 45 | }, 46 | 47 | getIntervalY: function() { 48 | this.interval.min = this.curr.y - this._radius; 49 | this.interval.max = this.curr.y + this._radius; 50 | return this.interval; 51 | } 52 | }); 53 | 54 | module.exports = CircleParticle; 55 | }); -------------------------------------------------------------------------------- /src/RigidCollisionResolver.js: -------------------------------------------------------------------------------- 1 | define("JPE/RigidCollisionResolver", function(require, exports, module) { 2 | 3 | module.exports = { 4 | 5 | resolve: function(pa, pb, hitpoint, normal, depth) { 6 | 7 | var mtd = normal.mult(depth); 8 | var te = pa._elasticity + pb._elasticity; 9 | var sumInvMass = pa.getInvMass() + pb.getInvMass(); 10 | 11 | //rewrite collision resolve 12 | var vap = pa.getVelocityOn(hitpoint); 13 | var vbp = pb.getVelocityOn(hitpoint); 14 | var vabp = vap.minus(vbp); 15 | var vn = normal.mult(vabp.dot(normal)); 16 | var l = vabp.minus(vn).normalize(); 17 | var n = normal.plus(l.mult(-0.1)).normalize(); 18 | var ra = hitpoint.minus(pa.samp); 19 | var rb = hitpoint.minus(pb.samp); 20 | 21 | var raxn = ra.cross(n); 22 | var rbxn = rb.cross(n); 23 | var j = -vabp.dot(n) * (1 + te / 2) / (sumInvMass + raxn * raxn / pa.mi + rbxn * rbxn / pb.mi); 24 | 25 | var vna = pa.getVelocity().plus(n.mult(j * pa.getInvMass())); 26 | var vnb = pb.getVelocity().plus(n.mult(-j * pb.getInvMass())); 27 | 28 | var aaa = j * raxn / pa.mi; 29 | var aab = -j * rbxn / pb.mi; 30 | if (Math.abs(aaa) > 0.1 || Math.abs(aab) > 0.1) {} 31 | 32 | pa.resolveRigidCollision(aaa, pb); 33 | pb.resolveRigidCollision(aab, pa); 34 | var mtdA = mtd.mult(pa.getInvMass() / sumInvMass); 35 | var mtdB = mtd.mult(-pb.getInvMass() / sumInvMass); 36 | pa.resolveCollision(mtdA, vna, normal, depth, -1, pb); 37 | pb.resolveCollision(mtdB, vnb, normal, depth, 1, pa); 38 | } 39 | } 40 | 41 | }); -------------------------------------------------------------------------------- /examples/CarDemo/Rotator.js: -------------------------------------------------------------------------------- 1 | define("Rotator", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Group = require("JPE/Group"); 5 | var Vector = require("JPE/Vector"); 6 | var CircleParticle = require("JPE/CircleParticle"); 7 | var RectangleParticle = require("JPE/RectangleParticle"); 8 | var SpringConstraint = require("JPE/SpringConstraint"); 9 | 10 | 11 | var RectComposite = require("./RectComposite"); 12 | 13 | var Rotator = function(colA, colB){ 14 | 15 | Group.prototype.constructor.apply(this, [true]); 16 | 17 | this.collideInternal = true; 18 | 19 | var ctr = this.ctr = new Vector(555,175); 20 | this.rectComposite = new RectComposite(ctr, colA, colB); 21 | this.addComposite(this.rectComposite); 22 | 23 | var circA = new CircleParticle(ctr.x, ctr.y, 5); 24 | circA.setStyle(1, colA, 1, colB); 25 | this.addParticle(circA); 26 | 27 | var rectA = new RectangleParticle(555,160,10,10,0,false,3); 28 | rectA.setStyle(1, colA, 1, colB); 29 | this.addParticle(rectA); 30 | 31 | var connectorA = new SpringConstraint(this.rectComposite.getPa(), rectA, 1); 32 | connectorA.setStyle(2, colB); 33 | this.addConstraint(connectorA); 34 | 35 | var rectB = new RectangleParticle(555,190,10,10,0,false,3); 36 | rectB.setStyle(1, colA, 1, colB); 37 | this.addParticle(rectB); 38 | 39 | var connectorB = new SpringConstraint(this.rectComposite.getPc(), rectB, 1); 40 | connectorB.setStyle(2, colB); 41 | this.addConstraint(connectorB); 42 | 43 | } 44 | 45 | JPE.extend(Rotator, Group, { 46 | ctr: null, 47 | rectComposite: null, 48 | rotateByRadian: function(a){ 49 | this.rectComposite.rotateByRadian(a, this.ctr); 50 | } 51 | }); 52 | 53 | module.exports = Rotator; 54 | 55 | }); -------------------------------------------------------------------------------- /examples/CarDemo/SwingDoor.js: -------------------------------------------------------------------------------- 1 | define("SwingDoor", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Group = require("JPE/Group"); 5 | var CircleParticle = require("JPE/CircleParticle"); 6 | var RectangleParticle = require("JPE/RectangleParticle"); 7 | var SpringConstraint = require("JPE/SpringConstraint"); 8 | 9 | var SwingDoor = function(colE){ 10 | 11 | Group.prototype.constructor.apply(this); 12 | // setting collideInternal allows the arm to hit the hidden stoppers. 13 | // you could also make the stoppers its own group and tell it to collide 14 | // with the SwingDoor 15 | this.collideInternal = true; 16 | 17 | var swingDoorP1 = new CircleParticle(543,55,7); 18 | swingDoorP1.setMass(0.001); 19 | swingDoorP1.setStyle(1, colE, 1, colE); 20 | this.addParticle(swingDoorP1); 21 | 22 | var swingDoorP2 = new CircleParticle(620,55,7,true); 23 | swingDoorP2.setStyle(1, colE, 1, colE); 24 | this.addParticle(swingDoorP2); 25 | 26 | var swingDoor = new SpringConstraint(swingDoorP1, swingDoorP2, 1, true, 13); 27 | swingDoor.setStyle(2, colE, 1, colE); 28 | this.addConstraint(swingDoor); 29 | 30 | var swingDoorAnchor = new CircleParticle(543,5, 2, true); 31 | swingDoorAnchor.setVisible(false); 32 | swingDoorAnchor.setCollidable(false); 33 | this.addParticle(swingDoorAnchor); 34 | 35 | var swingDoorSpring = new SpringConstraint(swingDoorP1, swingDoorAnchor, 0.02); 36 | swingDoorSpring.setRestLength(40); 37 | swingDoorSpring.pp = true; 38 | swingDoorSpring.setVisible(false); 39 | this.addConstraint(swingDoorSpring); 40 | 41 | var stopperA = new CircleParticle(550,-60,70,true); 42 | stopperA.setVisible(false); 43 | this.addParticle(stopperA); 44 | 45 | var stopperB = new RectangleParticle(650,130,42,70,0,true); 46 | stopperB.setVisible(false); 47 | this.addParticle(stopperB); 48 | } 49 | 50 | JPE.extend(SwingDoor, Group); 51 | 52 | module.exports = SwingDoor; 53 | 54 | }); -------------------------------------------------------------------------------- /src/CollisionResolver.js: -------------------------------------------------------------------------------- 1 | define("JPE/CollisionResolver", function(require, exports, module) { 2 | 3 | exports.resolve = function(pa, pb, normal, depth) { 4 | 5 | // a collision has occured. set the current positions to sample locations 6 | //pa.curr.copy(pa.samp); 7 | //pb.curr.copy(pb.samp); 8 | var mtd = normal.mult(depth); 9 | var te = pa.getElasticity() + pb.getElasticity(); 10 | 11 | var sumInvMass = pa.getInvMass() + pb.getInvMass(); 12 | // the total friction in a collision is combined but clamped to [0,1] 13 | var tf = this.clamp(1 - (pa.getFriction() + pb.getFriction()), 0, 1); 14 | 15 | // get the collision components, vn and vt 16 | var ca = pa.getComponents(normal); 17 | var cb = pb.getComponents(normal); 18 | 19 | // calculate the coefficient of restitution based on the mass, as the normal component 20 | var vnA = (cb.vn.mult((te + 1) * pa.getInvMass()).plus( 21 | ca.vn.mult(pb.getInvMass() - te * pa.getInvMass()))).divEquals(sumInvMass); 22 | 23 | var vnB = (ca.vn.mult((te + 1) * pb.getInvMass()).plus( 24 | cb.vn.mult(pa.getInvMass() - te * pb.getInvMass()))).divEquals(sumInvMass); 25 | 26 | // apply friction to the tangental component 27 | ca.vt.multEquals(tf); 28 | cb.vt.multEquals(tf); 29 | 30 | // scale the mtd by the ratio of the masses. heavier particles move less 31 | var mtdA = mtd.mult(pa.getInvMass() / sumInvMass); 32 | var mtdB = mtd.mult(-pb.getInvMass() / sumInvMass); 33 | 34 | // add the tangental component to the normal component for the new velocity 35 | vnA.plusEquals(ca.vt); 36 | vnB.plusEquals(cb.vt); 37 | 38 | 39 | pa.resolveCollision(mtdA, vnA, normal, depth, -1, pb); 40 | pb.resolveCollision(mtdB, vnB, normal, depth, 1, pa); 41 | }; 42 | 43 | exports.clamp = function(input, min, max) { 44 | if (input > max) return max; 45 | if (input < min) return min; 46 | return input; 47 | }; 48 | 49 | }); -------------------------------------------------------------------------------- /src/Renderer.js: -------------------------------------------------------------------------------- 1 | define("JPE/Renderer", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | 5 | var Renderer = function() { 6 | this._items = {}; 7 | this._delegates = {}; 8 | }; 9 | 10 | JPE.mix(Renderer.prototype, { 11 | 12 | registerDelegate: function(type, itemCls, delegate) { 13 | this._items[type] = itemCls; 14 | this._delegates[type] = delegate; 15 | }, 16 | getParticleClass: function(type) { 17 | return this._items[type]; 18 | }, 19 | getDelegateClass: function(type) { 20 | return this._delegates[type]; 21 | }, 22 | findDelegateByParticle: function(item) { 23 | var ic = item.constructor, 24 | ps = this._items, 25 | ds = this._delegates; 26 | while (ic.superclass) { 27 | for (var prop in ps) { 28 | if (ps[prop] === ic) { 29 | return ds[prop] 30 | } 31 | } 32 | ic = ic.superclass; 33 | } 34 | return null; 35 | }, 36 | getRenderDelegate: function(item) { 37 | var rd = item.get('renderDelegate'); 38 | if (rd == undefined) { 39 | rd = this.findDelegateByParticle(item) 40 | item.set('renderDelegate', rd); 41 | } 42 | return rd; 43 | }, 44 | initSelf: function(item) { 45 | var rd = this.getRenderDelegate(item); 46 | rd && rd.initSelf(item); 47 | }, 48 | cleanup: function(item) { 49 | var rd = this.getRenderDelegate(item); 50 | rd && rd.cleanup(item); 51 | }, 52 | render: function(item) { 53 | var rd = this.getRenderDelegate(item); 54 | rd && rd.render(item); 55 | }, 56 | setVisible: function(item) { 57 | var rd = this.getRenderDelegate(item); 58 | rd && rd.setVisible(item); 59 | } 60 | }); 61 | 62 | module.exports = Renderer; 63 | }); -------------------------------------------------------------------------------- /src/RigidCircle.js: -------------------------------------------------------------------------------- 1 | define("JPE/RigidCircle", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var RigidItem = require("JPE/RigidItem"); 5 | 6 | 7 | var RigidCircle = function(x, y, radius, isFixed, mass, elasticity, friction, radian, angularVelocity) { 8 | if (mass == null) { 9 | mass = -1; 10 | } 11 | if (mass == -1) { 12 | mass = Math.PI * radius * radius; 13 | } 14 | if(elasticity == null){ 15 | elasticity = 0.3; 16 | } 17 | if(friction == null){ 18 | friction = 0.2; 19 | } 20 | if(radian == null){ 21 | radian = 0; 22 | } 23 | if(angularVelocity == null){ 24 | angularVelocity = 0; 25 | } 26 | this._radius = radius; 27 | RigidItem.prototype.constructor.call(this, x, y, radius, 28 | isFixed, mass, mass * radius * radius / 2, elasticity, friction, radian, angularVelocity); 29 | }; 30 | 31 | JPE.extend(RigidCircle, RigidItem, { 32 | 33 | 34 | getRadius: function() { 35 | return this._radius; 36 | }, 37 | getVertices: function(axis) { 38 | var vertices = []; 39 | for (var i = 0; i < axis.length; i++) { 40 | vertices.push(axis[i].mult(this._radius).plusEquals(this.samp)); 41 | } 42 | return vertices; 43 | }, 44 | getProjection: function(axis) { 45 | var c = this.samp.dot(axis); 46 | this.interval.min = c - this._radius; 47 | this.interval.max = c + this._radius; 48 | 49 | return this.interval; 50 | }, 51 | getIntervalX: function() { 52 | this.interval.min = this.samp.x - this._radius; 53 | this.interval.max = this.samp.x + this._radius; 54 | return this.interval; 55 | }, 56 | getIntervalY: function() { 57 | this.interval.min = this.samp.y - this._radius; 58 | this.interval.max = this.samp.y + this._radius; 59 | return this.interval; 60 | } 61 | }); 62 | 63 | module.exports = RigidCircle; 64 | 65 | }); -------------------------------------------------------------------------------- /ant/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ${dir.src} 11 | 12 | 13 | 14 | 15 | JavaScript Bundles Done! 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | YUI Compress Done! 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Google Closure Compiler Compress Done! 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Delete Build Folder 62 | 63 | Create Build Folder 64 | 65 | 66 | 67 | 68 | Create Temp Folder 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/Composite.js: -------------------------------------------------------------------------------- 1 | define("JPE/Composite", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Vector = require("JPE/Vector"); 5 | var MathUtil = require("JPE/MathUtil"); 6 | var AbstractCollection = require("JPE/AbstractCollection"); 7 | 8 | var Composite = function() { 9 | AbstractCollection.prototype.constructor.apply(this); 10 | this.delta = new Vector(); 11 | }; 12 | 13 | JPE.extend(Composite, AbstractCollection, { 14 | 15 | rotateByRadian: function(angleRadians, center) { 16 | var p, 17 | pa = this.particles, 18 | len = pa.length; 19 | 20 | for (var i = 0; i < len; i++) { 21 | p = pa[i]; 22 | var radius = p.getCenter().distance(center); 23 | var angle = this.getRelativeAngle(center, p.getCenter()) + angleRadians; 24 | p.setPx(Math.cos(angle) * radius + center.x); 25 | p.setPy(Math.sin(angle) * radius + center.y); 26 | } 27 | }, 28 | /** 29 | * Rotates the Composite to an angle specified in degrees, around a given center 30 | */ 31 | rotateByAngle: function(angleDegrees, center) { 32 | var angleRadians = angleDegrees * MathUtil.PI_OVER_ONE_EIGHTY; 33 | this.rotateByRadian(angleRadians, center); 34 | }, 35 | 36 | 37 | /** 38 | * The fixed state of the Composite. Setting this value to true or false will 39 | * set all of this Composite's component particles to that value. Getting this 40 | * value will return false if any of the component particles are not fixed. 41 | */ 42 | getFixed: function() { 43 | for (var i = 0, l = this.particles.length; i < l; i++) { 44 | if (!particles[i].getFixed()) return false; 45 | } 46 | return true; 47 | }, 48 | 49 | 50 | /** 51 | * @private 52 | */ 53 | setFixed: function(b) { 54 | for (var i = 0, l = this.particles.length; i < l; i++) { 55 | this.particles[i].setFixed(b); 56 | } 57 | }, 58 | 59 | 60 | getRelativeAngle: function(center, p) { 61 | this.delta.setTo(p.x - center.x, p.y - center.y); 62 | return Math.atan2(this.delta.y, this.delta.x); 63 | } 64 | }); 65 | 66 | module.exports = Composite; 67 | }); -------------------------------------------------------------------------------- /src/Signal.js: -------------------------------------------------------------------------------- 1 | define("JPE/Signal", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | 5 | var Signal = function() { 6 | this.__bindings = []; 7 | this.__bindingsOnce = []; 8 | }; 9 | 10 | JPE.mix(Signal.prototype, { 11 | add: function(listener) { 12 | return this.registerListener(listener); 13 | }, 14 | addOnce: function(listener) { 15 | return this.registerListener(listener, true); 16 | }, 17 | remove: function(listener) { 18 | var index = this.find(listener), 19 | oIndex = this.find(listener, this.__bindingsOnce); 20 | if (index >= 0) { 21 | this.__bindings.splice(index, 1); 22 | } 23 | if (oIndex >= 0) { 24 | this.__bindingsOnce.splice(oIndex, 1); 25 | } 26 | }, 27 | removeAll: function() { 28 | this.__bindings.length = 0; 29 | this.__bindingsOnce.length = 0; 30 | }, 31 | dispatch: function() { 32 | var args = Array.prototype.slice.call(arguments), 33 | list = this.__bindings, 34 | copyList = list.concat(), 35 | listener; 36 | 37 | for (var i = 0, l = copyList.length; i < l; i++) { 38 | listener = copyList[i]; 39 | listener.apply(null, args); 40 | if (this.find(listener, this.__bindingsOnce) != -1) { 41 | this.remove(listener); 42 | } 43 | } 44 | }, 45 | getNumListeners: function() { 46 | return this.__bindings.length; 47 | }, 48 | registerListener: function(listener, once) { 49 | var index = this.find(listener); 50 | if (index == -1 || !once) { 51 | this.__bindings.push(listener); 52 | if (once) { 53 | this.__bindingsOnce.push(listener); 54 | } 55 | } 56 | 57 | }, 58 | find: function(listener, arr) { 59 | arr = arr || this.__bindings; 60 | if (arr.indexOf) { 61 | return arr.indexOf(listener); 62 | } 63 | for (var i = 0, l = arr.length; i < l; i++) { 64 | if (arr[i] === listener) { 65 | return i; 66 | } 67 | } 68 | return -1; 69 | } 70 | }); 71 | 72 | module.exports = Signal; 73 | 74 | }); -------------------------------------------------------------------------------- /src/RimParticle.js: -------------------------------------------------------------------------------- 1 | define("JPE/RimParticle", function(require, exports, module) { 2 | 3 | 4 | var JPE = require("JPE/JPE"); 5 | var Engine = require("JPE/Engine"); 6 | var Vector = require("JPE/Vector"); 7 | 8 | /** 9 | * The RimParticle is really just a second component of the wheel model. 10 | * The rim particle is simulated in a coordsystem relative to the wheel's 11 | * center, not in worldspace. 12 | * 13 | * Origins of this code are from Raigan Burns, Metanet Software 14 | */ 15 | var RimParticle = function(r, mt) { 16 | 17 | this.sp = 0; 18 | this.av = 0; 19 | this.wr = r; 20 | this.maxTorque = mt; 21 | this.curr = new Vector(r, 0); 22 | this.prev = new Vector(0, 0); 23 | }; 24 | 25 | JPE.mix(RimParticle.prototype, { 26 | 27 | getSpeed: function() { 28 | return this.sp; 29 | }, 30 | 31 | setSpeed: function(s) { 32 | this.sp = s; 33 | }, 34 | 35 | getAngularVelocity: function() { 36 | return this.av; 37 | }, 38 | 39 | setAngularVelocity: function(s) { 40 | this.av = s; 41 | }, 42 | 43 | /** 44 | * Origins of this code are from Raigan Burns, Metanet Software 45 | */ 46 | update: function(dt) { 47 | 48 | //clamp torques to valid range 49 | this.sp = Math.max(-this.maxTorque, Math.min(this.maxTorque, this.sp + this.av)); 50 | //apply torque 51 | //this is the tangent vector at the rim particle 52 | var dx = -this.curr.y; 53 | var dy = this.curr.x; 54 | 55 | //normalize so we can scale by the rotational speed 56 | var len = Math.sqrt(dx * dx + dy * dy); 57 | dx /= len; 58 | dy /= len; 59 | 60 | this.curr.x += this.sp * dx; 61 | this.curr.y += this.sp * dy; 62 | 63 | var ox = this.prev.x; 64 | var oy = this.prev.y; 65 | var px = this.prev.x = this.curr.x; 66 | var py = this.prev.y = this.curr.y; 67 | 68 | this.curr.x += Engine.damping * (px - ox); 69 | this.curr.y += Engine.damping * (py - oy); 70 | 71 | // hold the rim particle in place 72 | var clen = Math.sqrt(this.curr.x * this.curr.x + this.curr.y * this.curr.y); 73 | var diff = (clen - this.wr) / clen; 74 | 75 | this.curr.x -= this.curr.x * diff; 76 | this.curr.y -= this.curr.y * diff; 77 | } 78 | }); 79 | 80 | module.exports = RimParticle; 81 | 82 | }); -------------------------------------------------------------------------------- /src/Vector.js: -------------------------------------------------------------------------------- 1 | define("JPE/Vector", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | 5 | var Vector = function(px, py) { 6 | this.x = px || 0; 7 | this.y = py || 0; 8 | }; 9 | 10 | JPE.mix(Vector.prototype, { 11 | 12 | setTo: function(px, py) { 13 | this.x = px || 0; 14 | this.y = py || 0; 15 | }, 16 | copy: function(v) { 17 | this.x = v.x; 18 | this.y = v.y; 19 | }, 20 | dot: function(v) { 21 | return this.x * v.x + this.y * v.y; 22 | }, 23 | cross: function(v) { 24 | return this.x * v.y - this.y * v.x; 25 | }, 26 | plus: function(v) { 27 | return new Vector(this.x + v.x, this.y + v.y); 28 | }, 29 | plusEquals: function(v) { 30 | this.x += v.x; 31 | this.y += v.y; 32 | return this; 33 | }, 34 | minus: function(v) { 35 | return new Vector(this.x - v.x, this.y - v.y); 36 | }, 37 | minusEquals: function(v) { 38 | this.x -= v.x; 39 | this.y -= v.y; 40 | return this; 41 | }, 42 | mult: function(s) { 43 | return new Vector(this.x * s, this.y * s); 44 | }, 45 | multEquals: function(s) { 46 | this.x *= s; 47 | this.y *= s; 48 | return this; 49 | }, 50 | times: function(v) { 51 | return new Vector(this.x * v.x, this.y * v.y); 52 | }, 53 | divEquals: function(s) { 54 | if (s == 0) { 55 | s = 0.0001; 56 | } 57 | this.x /= s; 58 | this.y /= s; 59 | return this; 60 | }, 61 | magnitude: function() { 62 | return Math.sqrt(this.x * this.x + this.y * this.y); 63 | }, 64 | distance: function(v) { 65 | var delta = this.minus(v); 66 | return delta.magnitude(); 67 | }, 68 | normalize: function() { 69 | var m = this.magnitude(); 70 | if (m == 0) { 71 | m = 0.0001; 72 | } 73 | return this.mult(1 / m); 74 | }, 75 | normalizeEquals: function() { 76 | var m = this.magnitude(); 77 | if (m == 0) { 78 | m = 0.0001; 79 | } 80 | return this.multEquals(1 / m); 81 | }, 82 | toString: function() { 83 | return Math.floor(this.x * 100) / 100 + " : " + Math.floor(this.y * 100) / 100; 84 | } 85 | }); 86 | 87 | module.exports = Vector; 88 | }); -------------------------------------------------------------------------------- /src/Engine.js: -------------------------------------------------------------------------------- 1 | define("JPE/Engine", function(require, exports, module) { 2 | 3 | module.exports = { 4 | forces: null, 5 | masslessForce: null, 6 | groups: null, 7 | numGroups: 0, 8 | timeStep: 0, 9 | renderer: null, 10 | damping: 1, 11 | constraintCycles: 0, 12 | constraintCollisionCycles: 1, 13 | 14 | init: function(dt) { 15 | if (isNaN(dt)) { 16 | dt = 0.25; 17 | } 18 | this.timeStep = dt * dt; 19 | this.groups = []; 20 | this.forces = []; 21 | }, 22 | addForce: function(v) { 23 | this.forces.push(v); 24 | }, 25 | removeForce: function(v) { 26 | JPE.Array.remove(this.forces, v); 27 | }, 28 | removeAllForce: function() { 29 | this.forces = []; 30 | }, 31 | addGroup: function(g) { 32 | this.groups.push(g); 33 | g.isParented = true; 34 | this.numGroups++; 35 | g.initSelf(); 36 | }, 37 | removeGroup: function(g) { 38 | var gpos = JPE.Array.indexOf(this.groups, g); 39 | if (gpos == -1) { 40 | return; 41 | } 42 | this.groups.splice(gpos, 1); 43 | g.isParented = false; 44 | this.numGroups--; 45 | g.cleanup(); 46 | }, 47 | step: function() { 48 | this.integrate(); 49 | for (var j = 0; j < this.constraintCycles; j++) { 50 | this.satisfyConstraints(); 51 | } 52 | for (var i = 0; i < this.constraintCollisionCycles; i++) { 53 | this.satisfyConstraints(); 54 | this.checkCollisions(); 55 | } 56 | }, 57 | paint: function() { 58 | for (var j = 0; j < this.numGroups; j++) { 59 | var g = this.groups[j]; 60 | g.paint(); 61 | } 62 | }, 63 | integrate: function() { 64 | for (var j = 0; j < this.numGroups; j++) { 65 | var g = this.groups[j]; 66 | g.integrate(this.timeStep); 67 | } 68 | }, 69 | satisfyConstraints: function() { 70 | for (var j = 0; j < this.numGroups; j++) { 71 | var g = this.groups[j]; 72 | g.satisfyConstraints(); 73 | } 74 | }, 75 | checkCollisions: function() { 76 | for (var j = 0; j < this.numGroups; j++) { 77 | var g = this.groups[j]; 78 | g.checkCollisions(); 79 | } 80 | } 81 | } 82 | }); -------------------------------------------------------------------------------- /examples/CarDemo/Bridge.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Group = require("JPE/Group"); 5 | var CircleParticle = require("JPE/CircleParticle"); 6 | var SpringConstraint = require("JPE/SpringConstraint"); 7 | 8 | var Bridge = function(colB, colC, colD){ 9 | 10 | Group.prototype.constructor.apply(this); 11 | 12 | var bx = 170; 13 | var by = 40; 14 | var bsize = 51.5; 15 | var yslope = 2.4; 16 | var particleSize = 4; 17 | 18 | 19 | var bridgePAA = new CircleParticle(bx,by,particleSize,true); 20 | 21 | bridgePAA.setStyle(1, colC, 1, colB); 22 | 23 | this.addParticle(bridgePAA); 24 | 25 | bx += bsize; 26 | by += yslope; 27 | var bridgePA = new CircleParticle(bx,by,particleSize); 28 | bridgePA.setStyle(1, colC, 1, colB); 29 | this.addParticle(bridgePA); 30 | 31 | bx += bsize; 32 | by += yslope; 33 | var bridgePB = new CircleParticle(bx,by,particleSize); 34 | bridgePB.setStyle(1, colC, 1, colB); 35 | this.addParticle(bridgePB); 36 | 37 | bx += bsize; 38 | by += yslope; 39 | var bridgePC = new CircleParticle(bx,by,particleSize); 40 | bridgePC.setStyle(1, colC, 1, colB); 41 | this.addParticle(bridgePC); 42 | 43 | bx += bsize; 44 | by += yslope; 45 | var bridgePD = new CircleParticle(bx,by,particleSize); 46 | bridgePD.setStyle(1, colC, 1, colB); 47 | this.addParticle(bridgePD); 48 | 49 | bx += bsize; 50 | by += yslope; 51 | var bridgePDD = new CircleParticle(bx,by,particleSize,true); 52 | bridgePDD.setStyle(1, colC, 1, colB); 53 | this.addParticle(bridgePDD); 54 | 55 | 56 | var bridgeConnA = new SpringConstraint(bridgePAA, bridgePA, 57 | 0.9, true, 10, 0.8); 58 | 59 | bridgeConnA.setFixedEndLimit(0.25); 60 | bridgeConnA.setStyle(1, colC, 1, colB); 61 | this.addConstraint(bridgeConnA); 62 | 63 | var bridgeConnB = new SpringConstraint(bridgePA, bridgePB, 64 | 0.9, true, 10, 0.8); 65 | bridgeConnB.setStyle(1, colC, 1, colB); 66 | this.addConstraint(bridgeConnB); 67 | 68 | var bridgeConnC = new SpringConstraint(bridgePB, bridgePC, 69 | 0.9, true, 10, 0.8); 70 | bridgeConnC.setStyle(1, colC, 1, colB); 71 | this.addConstraint(bridgeConnC); 72 | 73 | var bridgeConnD = new SpringConstraint(bridgePC, bridgePD, 74 | 0.9, true, 10, 0.8); 75 | bridgeConnD.setStyle(1, colC, 1, colB); 76 | this.addConstraint(bridgeConnD); 77 | 78 | var bridgeConnE = new SpringConstraint(bridgePD, bridgePDD, 79 | 0.9, true, 10, 0.8); 80 | bridgeConnE.setFixedEndLimit(0.25); 81 | bridgeConnE.setStyle(1, colC, 1, colB); 82 | this.addConstraint(bridgeConnE); 83 | } 84 | 85 | JPE.extend(Bridge, Group); 86 | 87 | module.exports = Bridge; 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /examples/RobotDemo/Body.js: -------------------------------------------------------------------------------- 1 | define("Body", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Composite = require("JPE/Composite"); 5 | var CircleParticle = require("JPE/CircleParticle"); 6 | var SpringConstraint = require("JPE/SpringConstraint"); 7 | 8 | var Body = function (left, right, height, lineWeight, lineColor, lineAlpha){ 9 | 10 | Composite.prototype.constructor.apply(this); 11 | 12 | 13 | var cpx = (right.getPx() + left.getPx()) / 2; 14 | var cpy = right.getPy(); 15 | 16 | 17 | var rgt = this.rgt = new CircleParticle(right.getPx(), right.getPy(), 1); 18 | rgt.setStyle(3, lineColor, 1, lineColor, 1); 19 | 20 | var lft = this.lft = new CircleParticle(left.getPx(), left.getPy(), 1); 21 | lft.setStyle(3, lineColor, 1, lineColor, 1); 22 | 23 | var ctr = this.ctr = new CircleParticle(cpx, cpy, 1); 24 | ctr.setVisible(false); 25 | 26 | var top = this.top = new CircleParticle(cpx, cpy - height / 2, 1); 27 | top.setVisible(false); 28 | 29 | var bot = this.bot =new CircleParticle(cpx, cpy + height / 2, 1); 30 | bot.setVisible(false); 31 | 32 | 33 | 34 | // outer constraints 35 | var tr = new SpringConstraint(top,rgt,1); 36 | tr.setVisible(false); 37 | var rb = new SpringConstraint(rgt,bot,1); 38 | rb.setVisible(false); 39 | var bl = new SpringConstraint(bot,lft,1); 40 | bl.setVisible(false); 41 | var lt = new SpringConstraint(lft,top,1); 42 | lt.setVisible(false); 43 | 44 | // inner constrainst 45 | var ct = new SpringConstraint(top, this.getCenter(),1); 46 | ct.setVisible(false); 47 | var cr = new SpringConstraint(rgt, this.getCenter(),1); 48 | cr.setLine(lineWeight, lineColor, lineAlpha); 49 | var cb = new SpringConstraint(bot, this.getCenter(),1); 50 | cb.setVisible(false); 51 | var cl = new SpringConstraint(lft,this.getCenter(),1); 52 | cl.setLine(lineWeight, lineColor, lineAlpha); 53 | 54 | ctr.setCollidable(false) 55 | top.setCollidable(false) 56 | rgt.setCollidable(false) 57 | bot.setCollidable(false) 58 | lft.setCollidable(false) 59 | 60 | this.addParticle(ctr); 61 | this.addParticle(top); 62 | this.addParticle(rgt); 63 | this.addParticle(bot); 64 | this.addParticle(lft); 65 | 66 | this.addConstraint(tr); 67 | this.addConstraint(rb); 68 | this.addConstraint(bl); 69 | this.addConstraint(lt); 70 | 71 | this.addConstraint(ct); 72 | this.addConstraint(cr); 73 | this.addConstraint(cb); 74 | this.addConstraint(cl); 75 | 76 | 77 | this.left = lft; 78 | this.center = ctr; 79 | this.right = rgt; 80 | 81 | }; 82 | 83 | 84 | JPE.extend(Body, Composite, { 85 | 86 | getCenter: function(){ 87 | return this.ctr; 88 | } 89 | 90 | }); 91 | 92 | module.exports = Body; 93 | 94 | }); -------------------------------------------------------------------------------- /lib/keymaster.min.js: -------------------------------------------------------------------------------- 1 | // keymaster.js 2 | // (c) 2011-2012 Thomas Fuchs 3 | // keymaster.js may be freely distributed under the MIT license. 4 | (function(e){function f(e,t){var n=e.length;while(n--)if(e[n]===t)return n;return-1}function l(e,t){if(e.length!=t.length)return!1;for(var n=0;n0;for(u in r)if(!r[u]&&f(o.mods,+u)>-1||r[u]&&f(o.mods,+u)==-1)c=!1;(o.mods.length==0&&!r[16]&&!r[18]&&!r[17]&&!r[91]||c)&&o.method(e,o)===!1&&(e.preventDefault?e.preventDefault():e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),e.cancelBubble&&(e.cancelBubble=!0))}}}function d(e){var t=e.keyCode,n,i=f(a,t);i>=0&&a.splice(i,1);if(t==93||t==224)t=91;if(t in r){r[t]=!1;for(n in s)s[n]==t&&(m[n]=!1)}}function v(){for(t in r)r[t]=!1;for(t in s)m[t]=!1}function m(e,t,r){var i,s,o,a;i=T(e),r===undefined&&(r=t,t="all");for(o=0;o1&&(s=N(e),e=[e[e.length-1]]),e=e[0],e=u(e),e in n||(n[e]=[]),n[e].push({shortcut:i[o],scope:t,method:r,key:i[o],mods:s})}function g(e,t){var r=e.split("+"),i=[],s,o;r.length>1&&(i=N(r),e=r[r.length-1]),e=u(e),t===undefined&&(t=S());if(!n[e])return;for(s in n[e])o=n[e][s],o.scope===t&&l(o.mods,i)&&(n[e][s]={})}function y(e){return typeof e=="string"&&(e=u(e)),f(a,e)!=-1}function b(){return a.slice(0)}function w(e){var t=(e.target||e.srcElement).tagName;return t!="INPUT"&&t!="SELECT"&&t!="TEXTAREA"}function E(e){i=e||"all"}function S(){return i||"all"}function x(e){var t,r,i;for(t in n){r=n[t];for(i=0;icolorhook 4 | */ 5 | define("CarDemo", function(require, exports, module){ 6 | 7 | var JPE = require("JPE/JPE"); 8 | var Vector = require("JPE/Vector"); 9 | var VectorForce = require("JPE/VectorForce"); 10 | var Engine = require("JPE/Engine"); 11 | var EaselRenderer = require("JPE/EaselRenderer"); 12 | var CircleParticle = require("JPE/CircleParticle"); 13 | var WheelParticle = require("JPE/WheelParticle"); 14 | var SpringConstraint = require("JPE/SpringConstraint"); 15 | 16 | var Surfaces = require("./Surfaces"); 17 | var Bridge = require("./Bridge"); 18 | var Capsule = require("./Capsule"); 19 | var Rotator = require("./Rotator"); 20 | var SwingDoor = require("./SwingDoor"); 21 | var Car = require("./Car"); 22 | 23 | module.exports = { 24 | 25 | car: null, 26 | rotator: null, 27 | 28 | /** 29 | * @method init 30 | * 初始化程序, 建立canvas, 初始化engine 31 | */ 32 | init: function(){ 33 | var canvas = document.getElementById("myCanvas"); 34 | var stage = this.stage = new Stage(canvas); 35 | 36 | var colA = 0x334433; 37 | var colB = 0x3366aa; 38 | var colC = 0xaabbbb; 39 | var colD = 0x6699aa; 40 | var colE = 0x778877; 41 | 42 | Engine.init(1/4); 43 | 44 | //渲染引擎 45 | Engine.renderer = new EaselRenderer(stage); 46 | 47 | //重力 48 | Engine.addForce(new VectorForce(false, 0, 3)); 49 | 50 | //场景的地形 51 | var surfaces = new Surfaces(colA, colB, colC, colD, colE); 52 | Engine.addGroup(surfaces); 53 | 54 | //桥状物体 55 | var bridge = new Bridge(colB, colC, colD); 56 | Engine.addGroup(bridge); 57 | 58 | //胶囊状物体 59 | var capsule = new Capsule(colC); 60 | Engine.addGroup(capsule); 61 | 62 | //旋转物体 63 | rotator = new Rotator(colB, colE); 64 | Engine.addGroup(rotator); 65 | 66 | //弹性门 67 | var swingDoor = new SwingDoor(colC); 68 | Engine.addGroup(swingDoor); 69 | 70 | //小车 71 | var car = new Car(colC, colE); 72 | Engine.addGroup(car); 73 | 74 | //设置触碰规则 75 | car.addCollidableList([surfaces, bridge, rotator, swingDoor, capsule]); 76 | capsule.addCollidableList([surfaces, bridge, rotator, swingDoor]); 77 | 78 | this.car = car; 79 | this.rotator = rotator; 80 | 81 | 82 | //循环 83 | var owner = this; 84 | setInterval(function(){ 85 | owner.run(); 86 | }, 10); 87 | 88 | //事件绑定 89 | this.addEvent(document, 'keydown', function(event) { 90 | owner.keyDownHandler(event); 91 | }); 92 | this.addEvent(document, 'keyup', function(event){ 93 | owner.keyUpHandler(event); 94 | }); 95 | }, 96 | 97 | /** 98 | * @method run 99 | * step by step运行程序 100 | */ 101 | run: function(s){ 102 | Engine.step(); 103 | Engine.paint(); 104 | this.stage.update(); 105 | this.rotator.rotateByRadian(.02); 106 | }, 107 | 108 | /** 109 | * @method addEvent 110 | * 跨浏览器的事件绑定 111 | */ 112 | addEvent: function addEvent(object, event, method) { 113 | if (object.addEventListener) 114 | object.addEventListener(event, method, false); 115 | else if(object.attachEvent) 116 | object.attachEvent('on'+event, function(){ method(window.event) }); 117 | }, 118 | /** 119 | * @method keyDownHandler 120 | * 键盘按键按下的事件处理函数 121 | */ 122 | keyDownHandler: function(keyEvt) { 123 | var keySpeed = 0.2; 124 | if (keyEvt.keyCode == 65) { 125 | this.car.setSpeed(-keySpeed); 126 | } else if (keyEvt.keyCode == 68) { 127 | this.car.setSpeed(keySpeed); 128 | } 129 | }, 130 | /** 131 | * @method keyUpHandler 132 | * 键盘按键弹起的事件处理函数 133 | */ 134 | keyUpHandler: function(keyEvt) { 135 | this.car.setSpeed(0); 136 | } 137 | } 138 | 139 | }); -------------------------------------------------------------------------------- /examples/RigidDemo/RigidDemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 http://colorhook.com 3 | * @author: colorhook 4 | */ 5 | 6 | /** 7 | * Provides requestAnimationFrame in a cross browser way. 8 | * @see https://gist.github.com/838785 9 | */ 10 | if ( !window.requestAnimationFrame ) { 11 | window.requestAnimationFrame = ( function() { 12 | return window.webkitRequestAnimationFrame || 13 | window.mozRequestAnimationFrame || 14 | window.oRequestAnimationFrame || 15 | window.msRequestAnimationFrame || 16 | function(callback, element ) { 17 | window.setTimeout( callback, 1000/60); 18 | }; 19 | } )(); 20 | } 21 | 22 | define(function(require, exports, module){ 23 | var canvas = document.getElementById("myCanvas"); 24 | var stage = new Stage(canvas); 25 | 26 | var JPE = require("JPE/JPE"); 27 | var Vector = require("JPE/Vector"); 28 | var VectorForce = require("JPE/VectorForce"); 29 | var Engine = require("JPE/Engine"); 30 | var RectangleParticle = require("JPE/RectangleParticle"); 31 | var RigidRectangle = require("JPE/RigidRectangle"); 32 | var RigidCircle = require("JPE/RigidCircle"); 33 | var Group = require("JPE/Group"); 34 | var EaselRenderer = require("JPE/EaselRenderer"); 35 | 36 | 37 | var RigidDemo = JPE.newClass(function(){ 38 | this.init(); 39 | }, 40 | null, 41 | { 42 | init: function(){ 43 | Engine.init(1/4); 44 | Engine.renderer = new EaselRenderer(stage); 45 | 46 | Engine.addForce(new VectorForce(false, 0, 0.5)); 47 | 48 | 49 | var group = new Group(); 50 | 51 | var wallRight = new RigidRectangle(800, 240, 100, 480, 0, true); 52 | var wallLeft = new RigidRectangle(0, 240, 100, 480, 0, true); 53 | var ground = new RigidRectangle(320, 480, 900, 100, 0, true); 54 | var surfaces = new Group(); 55 | surfaces.addParticle(wallRight); 56 | surfaces.addParticle(wallLeft); 57 | surfaces.addParticle(ground); 58 | surfaces.addParticle(new RigidRectangle(520, 200, 280, 5, -0.3, true)); 59 | surfaces.addParticle(new RigidRectangle(320, 300, 280, 5, 0.3, true)); 60 | Engine.addGroup(surfaces); 61 | 62 | var balls = new Group(); 63 | Engine.addGroup(balls); 64 | 65 | var randomX = 400; 66 | balls.addParticle(new RigidCircle(randomX, 30, 20)); 67 | balls.addParticle(new RigidCircle(randomX+150, 0, 20)); 68 | balls.addParticle(new RigidCircle(randomX-100, 30, 20)); 69 | 70 | 71 | balls.addParticle(new RigidRectangle(randomX, 30, 40, 20,0.1,false,-1,0.3,0.2,0)); 72 | balls.addParticle(new RigidRectangle(randomX, 90, 40, 20,0.2,false,-1,0.3,0.2,0)); 73 | balls.addParticle(new RigidRectangle(randomX, 150, 40, 20,0.3,false,-1,0.3,0.2,0)); 74 | 75 | balls.addCollidableList([surfaces]); 76 | balls.collideInternal = true; 77 | 78 | key("up", function(){ 79 | if(Math.random()<0.5){ 80 | balls.addParticle(new RigidRectangle(Math.random()*550+100, 20, Math.random()*20+20,Math.random()*20+20,Math.random()*1.6)); 81 | }else{ 82 | balls.addParticle(new RigidCircle(Math.random()*550+100, 20, Math.random()*10+10)); 83 | } 84 | }); 85 | 86 | }, 87 | 88 | update: function(){ 89 | var self = this; 90 | requestAnimationFrame(function(){ 91 | Engine.step(); 92 | Engine.paint(); 93 | stage.update(); 94 | self.update(); 95 | }); 96 | } 97 | }); 98 | 99 | //entry point 100 | exports.init = function(){ 101 | var demo = new RigidDemo(); 102 | demo.update(); 103 | } 104 | }); 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /examples/RobotDemo/RobotDemo.js: -------------------------------------------------------------------------------- 1 | define("RobotDemo", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Engine = require("JPE/Engine"); 5 | var VectorForce = require("JPE/VectorForce"); 6 | var EaselRenderer = require("JPE/EaselRenderer"); 7 | var Group = require("JPE/Group"); 8 | var RectangleParticle = require("JPE/RectangleParticle"); 9 | 10 | var Robot = require("./Robot"); 11 | 12 | module.exports = { 13 | 14 | robot: null, 15 | halfStageWidth: 325, 16 | 17 | init: function(){ 18 | var canvas = document.getElementById("myCanvas"); 19 | var stage = this.stage = new Stage(canvas); 20 | 21 | Engine.init(1/4); 22 | 23 | // set up the default diplay container 24 | Engine.renderer = new EaselRenderer(stage); 25 | 26 | Engine.addForce(new VectorForce(true, 0, 4)); 27 | Engine.damping = .99; 28 | Engine.constraintCollisionCycles = 10; 29 | 30 | var robot = this.robot = new Robot(1250, 260, 1.6, 0.02); 31 | 32 | var terrainA = new Group(); 33 | var terrainB = new Group(true); 34 | var terrainC = new Group(); 35 | 36 | var floor = new RectangleParticle(600,390,1700,100,0,true,1,0,1); 37 | floor.setStyle(0,0,0,0x999999); 38 | terrainA.addParticle(floor); 39 | 40 | 41 | // pyramid of boxes 42 | var box0 = new RectangleParticle(600,337,600,7,0,true,10,0,1); 43 | box0.setStyle(1,0x999999,1,0x336699); 44 | terrainA.addParticle(box0); 45 | 46 | var box1 = new RectangleParticle(600,330,500,7,0,true,10,0,1); 47 | box1.setStyle(1,0x999999,1,0x336699); 48 | terrainA.addParticle(box1); 49 | 50 | var box2 = new RectangleParticle(600,323,400,7,0,true,10,0,1); 51 | box2.setStyle(1,0x999999,1,0x336699); 52 | terrainA.addParticle(box2); 53 | 54 | var box3 = new RectangleParticle(600,316,300,7,0,true,10,0,1); 55 | box3.setStyle(1,0x999999,1,0x336699); 56 | terrainA.addParticle(box3); 57 | 58 | var box4 = new RectangleParticle(600,309,200,7,0,true,10,0,1); 59 | box4.setStyle(1,0x999999,1,0x336699); 60 | terrainA.addParticle(box4); 61 | 62 | var box5 = new RectangleParticle(600,302,100,7,0,true,10,0,1); 63 | box5.setStyle(1,0x999999,1,0x336699); 64 | terrainA.addParticle(box5); 65 | 66 | 67 | // left side floor 68 | var floor2 = new RectangleParticle(-100,390,1100,100,0.3,true,1,0,1); 69 | floor2.setStyle(0,0,0,0x999999); 70 | terrainB.addParticle(floor2); 71 | 72 | var floor3 = new RectangleParticle(-959,232,700,100,0,true,1,0,1); 73 | floor3.setStyle(0,0,0,0x999999); 74 | terrainB.addParticle(floor3); 75 | 76 | var box6 = new RectangleParticle(-990,12,50,25,0); 77 | box6.setStyle(1,0x999999,1,0x336699); 78 | terrainB.addParticle(box6); 79 | 80 | var floor5 = new RectangleParticle(-1284,170,50,100,0,true); 81 | floor5.setStyle(0,0,0,0x999999); 82 | terrainB.addParticle(floor5); 83 | 84 | 85 | // right side floor 86 | var floor6 = new RectangleParticle(1430,320,50,60,0,true); 87 | floor6.setStyle(0,0,0,0x999999); 88 | terrainC.addParticle(floor6); 89 | 90 | 91 | Engine.addGroup(robot); 92 | Engine.addGroup(terrainA); 93 | Engine.addGroup(terrainB); 94 | Engine.addGroup(terrainC); 95 | 96 | robot.addCollidable(terrainA); 97 | robot.addCollidable(terrainB); 98 | robot.addCollidable(terrainC); 99 | 100 | robot.togglePower(); 101 | 102 | //loop 103 | var owner = this; 104 | setInterval(function(){ 105 | owner.run(); 106 | }, 10); 107 | 108 | key('p', function(){ 109 | robot.togglePower(); 110 | }); 111 | key('r', function(){ 112 | robot.toggleDirection(); 113 | }); 114 | key('h', function(){ 115 | robot.toggleLegs(); 116 | }); 117 | 118 | }, 119 | 120 | run: function(s){ 121 | var robot = this.robot; 122 | Engine.step(); 123 | robot.run(); 124 | Engine.paint(); 125 | this.stage.update(); 126 | Engine.renderer.stage.x = -robot.getPx() + this.halfStageWidth; 127 | 128 | } 129 | } 130 | }) 131 | -------------------------------------------------------------------------------- /examples/RobotDemo/Motor.js: -------------------------------------------------------------------------------- 1 | define("Motor", function(require, exports, module){ 2 | 3 | var ONE_THIRD = (Math.PI * 2) / 3; 4 | 5 | var JPE = require("JPE/JPE"); 6 | var Engine = require("JPE/Engine"); 7 | var Composite = require("JPE/Composite"); 8 | var WheelParticle = require("JPE/WheelParticle"); 9 | var CircleParticle = require("JPE/CircleParticle"); 10 | var SpringConstraint = require("JPE/SpringConstraint"); 11 | 12 | var Motor = function (attach, radius, color){ 13 | 14 | Composite.prototype.constructor.apply(this); 15 | 16 | var wheel = this.wheel = new WheelParticle(attach.getPx(), attach.getPy() - .01, radius); 17 | wheel.setStyle(0,0xFFF00,0, 0xFFF00,0.5); 18 | wheel.setVisible(false); 19 | var axle = new SpringConstraint(wheel, attach); 20 | 21 | var _rimA = this._rimA = new CircleParticle(0,0,20, true); 22 | var _rimB = this._rimB = new CircleParticle(0,0,2, true); 23 | var _rimC = this._rimC = new CircleParticle(0,0,2, true); 24 | 25 | wheel.setCollidable(false); 26 | _rimA.setCollidable(false); 27 | _rimB.setCollidable(false); 28 | _rimC.setCollidable(false); 29 | 30 | this.addParticle(_rimA); 31 | this.addParticle(_rimB); 32 | this.addParticle(_rimC); 33 | this.addParticle(wheel); 34 | this.addConstraint(axle); 35 | 36 | this.color = color; 37 | this.radius = radius; 38 | 39 | // run it once to make sure the rim particles are in the right place 40 | this.run(); 41 | 42 | }; 43 | 44 | JPE.extend(Motor, Composite, { 45 | radius:null, 46 | color:null, 47 | 48 | setPower: function (p) { 49 | this.wheel.setSpeed (p); 50 | }, 51 | 52 | 53 | getPower: function () { 54 | return this.wheel.getSpeed(); 55 | }, 56 | 57 | 58 | getRimA: function () { 59 | return this._rimA; 60 | }, 61 | 62 | 63 | getRimB: function () { 64 | return this._rimB; 65 | }, 66 | 67 | 68 | getRimC: function () { 69 | return this._rimC; 70 | }, 71 | 72 | run: function(){ 73 | var theta = this.wheel.getRadian(), 74 | radius = this.radius, 75 | wheel = this.wheel, 76 | wx = wheel.getPx(), 77 | wy = wheel.getPy(), 78 | _rimA = this._rimA, 79 | _rimB = this._rimB, 80 | _rimC = this._rimC; 81 | 82 | _rimA.setPx( -radius * Math.sin(theta) + wx); 83 | _rimA.setPy( radius * Math.cos(theta) + wy); 84 | 85 | theta += ONE_THIRD; 86 | _rimB.setPx( -radius * Math.sin(theta) + wx); 87 | _rimB.setPy( radius * Math.cos(theta) + wy); 88 | 89 | theta += ONE_THIRD; 90 | _rimC.setPx( -radius * Math.sin(theta) + wx); 91 | _rimC.setPy( radius * Math.cos(theta) + wy); 92 | }, 93 | 94 | initSelf: function (){ 95 | var sprite = new Container(), 96 | shape = new Shape(), 97 | sg = shape.graphics, 98 | radius = this.radius, 99 | color = this.color; 100 | 101 | sprite.addChild(shape); 102 | Engine.renderer.stage.addChild(sprite); 103 | this.sprite = sprite; 104 | this.shape = shape; 105 | 106 | sg.beginStroke(Graphics.getRGB(0xff000, 1)); 107 | sg.beginFill(Graphics.getRGB(color, 1)); 108 | 109 | sg.drawCircle(0, 0, 3); 110 | sg.endFill(); 111 | 112 | var theta = 0; 113 | var cx = -radius * Math.sin(theta); 114 | var cy = radius * Math.cos(theta); 115 | 116 | sg.beginStroke(Graphics.getRGB(0xff000, 1)); 117 | sg.moveTo(0,0); 118 | sg.lineTo(cx,cy); 119 | sg.drawCircle(cx, cy, 2); 120 | 121 | theta += ONE_THIRD; 122 | cx = -radius * Math.sin(theta); 123 | cy = radius * Math.cos(theta); 124 | sg.moveTo(0,0); 125 | sg.lineTo(cx,cy); 126 | sg.drawCircle(cx, cy, 2); 127 | 128 | theta += ONE_THIRD; 129 | cx = -radius * Math.sin(theta); 130 | cy = radius * Math.cos(theta); 131 | sg.moveTo(0,0); 132 | sg.lineTo(cx,cy); 133 | sg.drawCircle(cx, cy, 2); 134 | sg.endFill(); 135 | }, 136 | 137 | paint: function () { 138 | var sprite = this.sprite, 139 | wheel = this.wheel; 140 | 141 | sprite.x = wheel.getPx(); 142 | sprite.y = wheel.getPy(); 143 | sprite.rotation = wheel.getAngle(); 144 | } 145 | 146 | }); 147 | 148 | 149 | module.exports = Motor; 150 | 151 | }); 152 | -------------------------------------------------------------------------------- /src/RectangleParticle.js: -------------------------------------------------------------------------------- 1 | define("JPE/RectangleParticle", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var AbstractParticle = require("JPE/AbstractParticle"); 5 | var Vector = require("JPE/Vector"); 6 | var MathUtil = require("JPE/MathUtil"); 7 | 8 | /** 9 | * @param x The initial x position. 10 | * @param y The initial y position. 11 | * @param width The width of this particle. 12 | * @param height The height of this particle. 13 | * @param rotation The rotation of this particle in radians. 14 | * @param fixed Determines if the particle is fixed or not. Fixed particles 15 | * are not affected by forces or collisions and are good to use as surfaces. 16 | * Non-fixed particles move freely in response to collision and forces. 17 | * @param mass The mass of the particle 18 | * @param elasticity The elasticity of the particle. Higher values mean more elasticity. 19 | * @param friction The surface friction of the particle. 20 | *

21 | * Note that RectangleParticles can be fixed but still have their rotation property 22 | * changed. 23 | *

24 | */ 25 | var RectangleParticle = function(x, y, width, height, rotation, fixed, mass, elasticity, friction) { 26 | rotation = rotation || 0; 27 | mass = mass || 1; 28 | elasticity = elasticity || 0.3; 29 | friction = friction || 0; 30 | this._extents = [width / 2, height / 2]; 31 | this._axes = [new Vector(0, 0), new Vector(0, 0)]; 32 | this.setRadian(rotation); 33 | AbstractParticle.prototype.constructor.call(this, x, y, fixed, mass, elasticity, friction); 34 | }; 35 | 36 | JPE.extend(RectangleParticle, AbstractParticle, { 37 | 38 | getRadian: function() { 39 | return this._radian; 40 | }, 41 | 42 | /** 43 | * @private 44 | */ 45 | setRadian: function(t) { 46 | this._radian = t; 47 | this.setAxes(t); 48 | }, 49 | 50 | /** 51 | * The rotation of the RectangleParticle in degrees. 52 | */ 53 | getAngle: function() { 54 | return this.getRadian() * MathUtil.ONE_EIGHTY_OVER_PI; 55 | }, 56 | 57 | /** 58 | * @private 59 | */ 60 | setAngle: function(a) { 61 | this.setRadian(a * MathUtil.PI_OVER_ONE_EIGHTY); 62 | }, 63 | 64 | 65 | setWidth: function(w) { 66 | this._extents[0] = w / 2; 67 | }, 68 | 69 | 70 | getWidth: function() { 71 | return this._extents[0] * 2; 72 | }, 73 | 74 | 75 | setHeight: function(h) { 76 | this._extents[1] = h / 2; 77 | }, 78 | 79 | 80 | getHeight: function() { 81 | return this._extents[1] * 2; 82 | }, 83 | 84 | /** 85 | * @private 86 | */ 87 | getExtents: function() { 88 | return this._extents; 89 | }, 90 | 91 | 92 | /** 93 | * @private 94 | */ 95 | getProjection: function(axis) { 96 | var axes = this.getAxes(), 97 | extents = this.getExtents(), 98 | radius = extents[0] * Math.abs(axis.dot(axes[0])) + 99 | extents[1] * Math.abs(axis.dot(axes[1])); 100 | 101 | var c = this.samp.dot(axis); 102 | this.interval.min = c - radius; 103 | this.interval.max = c + radius; 104 | return this.interval; 105 | }, 106 | 107 | /** 108 | * @private 109 | */ 110 | getAxes: function() { 111 | return this._axes; 112 | }, 113 | 114 | /** 115 | * 116 | */ 117 | setAxes: function(t) { 118 | var s = Math.sin(t), 119 | c = Math.cos(t), 120 | axes = this.getAxes(); 121 | 122 | axes[0].x = c; 123 | axes[0].y = s; 124 | axes[1].x = -s; 125 | axes[1].y = c; 126 | } 127 | }); 128 | 129 | module.exports = RectangleParticle; 130 | 131 | }); -------------------------------------------------------------------------------- /src/RigidRectangle.js: -------------------------------------------------------------------------------- 1 | define("JPE/RigidRectangle", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Vector = require("JPE/Vector"); 5 | var RigidItem = require("JPE/RigidItem"); 6 | 7 | var RigidRectangle = function(x, y, width, height, radian, isFixed, mass, elasticity, friction, angularVelocity) { 8 | if(mass == null){ 9 | mass = -1; 10 | } 11 | if (mass == -1) { 12 | mass = width * height; 13 | } 14 | radian = radian || 0; 15 | if (elasticity == null) { 16 | elasticity = 0.3; 17 | } 18 | if (friction == null) { 19 | friction = 0.2; 20 | } 21 | 22 | angularVelocity = angularVelocity || 0; 23 | 24 | RigidItem.prototype.constructor.call(this, x, y, Math.sqrt(width * width / 4 + height * height / 4), 25 | isFixed, mass, mass * (width * width + height * height) / 12, elasticity, friction, radian, angularVelocity); 26 | 27 | this._extents = [width / 2, height / 2]; 28 | this._axes = [new Vector(0, 0), new Vector(0, 0)]; 29 | this._normals = []; 30 | this._marginCenters = []; 31 | this._vertices = []; 32 | for (var i = 0; i < 4; i++) { 33 | this._normals.push(new Vector(0, 0)); 34 | this._marginCenters.push(new Vector(0, 0)); 35 | this._vertices.push(new Vector(0, 0)); 36 | } 37 | }; 38 | 39 | JPE.extend(RigidRectangle, RigidItem, { 40 | getProjection: function(axis) { 41 | var axes = this.getAxes(), 42 | extents = this.getExtents(), 43 | radius = extents[0] * Math.abs(axis.dot(axes[0])) + 44 | extents[1] * Math.abs(axis.dot(axes[1])); 45 | 46 | var c = this.samp.dot(axis); 47 | this.interval.min = c - radius; 48 | this.interval.max = c + radius; 49 | return this.interval; 50 | }, 51 | 52 | captures: function(vertex) { 53 | var marginCenters = this.getMarginCenters(); 54 | for (var i = 0; i < marginCenters.length; i++) { 55 | var x = vertex.minus(marginCenters[i].plus(this.samp)).dot(this.getNormals()[i]); 56 | if (x > 0.01) { 57 | return false; 58 | } 59 | } 60 | return true; 61 | }, 62 | getMarginCenters: function() { 63 | return this._marginCenters; 64 | }, 65 | 66 | getNormals: function() { 67 | return this._normals; 68 | }, 69 | 70 | getExtents: function() { 71 | return this._extents; 72 | }, 73 | 74 | getAxes: function() { 75 | return this._axes; 76 | }, 77 | 78 | setAxes: function(n) { 79 | var s = Math.sin(n); 80 | var c = Math.cos(n); 81 | var axes = this.getAxes(); 82 | var extents = this.getExtents(); 83 | 84 | var _normals = this._normals; 85 | var _marginCenters = this._marginCenters; 86 | var _vertices = this._vertices; 87 | axes[0].x = c; 88 | axes[0].y = s; 89 | axes[1].x = -s; 90 | axes[1].y = c; 91 | // 92 | _normals[0].copy(axes[0]); 93 | _normals[1].copy(axes[1]); 94 | _normals[2] = axes[0].mult(-1); 95 | _normals[3] = axes[1].mult(-1); 96 | 97 | //.plusEquals(curr) 98 | _marginCenters[0] = axes[0].mult(extents[0]); 99 | _marginCenters[1] = axes[1].mult(extents[1]); 100 | _marginCenters[2] = axes[0].mult(-extents[0]); 101 | _marginCenters[3] = axes[1].mult(-extents[1]); 102 | 103 | //.minusEquals(curr) 104 | _vertices[0] = _marginCenters[0].plus(_marginCenters[1]); 105 | _vertices[1] = _marginCenters[1].plus(_marginCenters[2]); 106 | _vertices[2] = _marginCenters[2].plus(_marginCenters[3]); 107 | _vertices[3] = _marginCenters[3].plus(_marginCenters[0]); 108 | }, 109 | getVertices: function() { 110 | var r = []; 111 | var _vertices = this._vertices; 112 | for (var i = 0; i < _vertices.length; i++) { 113 | r.push(_vertices[i].plus(this.samp)); 114 | } 115 | return r; 116 | } 117 | }); 118 | 119 | module.exports = RigidRectangle; 120 | }); -------------------------------------------------------------------------------- /examples/Domino/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Donimo Game 6 | 129 | 130 | 131 |
132 |

Domino - JPE Game

133 |
134 | Fork me on GitHub 135 |
136 | 137 | 138 | 139 | 145 | 152 | 158 | 164 | 171 |
172 | 175 | 176 | 177 | 178 | 182 | 183 | -------------------------------------------------------------------------------- /src/WheelParticle.js: -------------------------------------------------------------------------------- 1 | define("JPE/WheelParticle", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var RimParticle = require("JPE/RimParticle"); 5 | var CircleParticle = require("JPE/CircleParticle"); 6 | var MathUtil = require("JPE/MathUtil"); 7 | var Vector = require("JPE/Vector"); 8 | 9 | /** 10 | * @param x The initial x position of this particle. 11 | * @param y The initial y position of this particle. 12 | * @param radius The radius of this particle. 13 | * @param fixed Determines if the particle is fixed or not. Fixed particles 14 | * are not affected by forces or collisions and are good to use as surfaces. 15 | * Non-fixed particles move freely in response to collision and forces. 16 | * @param mass The mass of the particle. 17 | * @param elasticity The elasticity of the particle. Higher values mean more elasticity or 'bounciness'. 18 | * @param friction The surface friction of the particle. 19 | */ 20 | var WheelParticle = function(x, y, radius, fixed, mass, elasticity, friction, traction) { 21 | traction = traction || 1; 22 | mass = mass || 1; 23 | elasticity = elasticity || 0.3; 24 | friction = friction || 0; 25 | this.lineThickness = 1; 26 | CircleParticle.prototype.constructor.apply(this, arguments); 27 | this.tan = new Vector(0, 0); 28 | this.normSlip = new Vector(0, 0); 29 | this.rp = new RimParticle(radius, 2); 30 | this.orientation = new Vector(); 31 | this.setTraction(traction); 32 | }; 33 | 34 | JPE.extend(WheelParticle, CircleParticle, { 35 | 36 | getSpeed: function() { 37 | return this.rp.getSpeed(); 38 | }, 39 | 40 | 41 | setSpeed: function(t) { 42 | this.rp.setSpeed(t); 43 | }, 44 | 45 | getAngularVelocity: function() { 46 | return this.rp.getAngularVelocity(); 47 | }, 48 | 49 | 50 | setAngularVelocity: function(t) { 51 | this.rp.setAngularVelocity(t); 52 | }, 53 | 54 | getTraction: function() { 55 | return 1 - this._traction; 56 | }, 57 | 58 | setTraction: function(t) { 59 | this._traction = 1 - t; 60 | }, 61 | 62 | update: function(dt) { 63 | CircleParticle.prototype.update.call(this, dt); 64 | this.rp.update(dt); 65 | }, 66 | 67 | /** 68 | * The rotation of the wheel in radians. 69 | */ 70 | getRadian: function() { 71 | this.orientation.setTo(this.rp.curr.x, this.rp.curr.y); 72 | return Math.atan2(this.orientation.y, this.orientation.x) + Math.PI; 73 | }, 74 | 75 | 76 | /** 77 | * The rotation of the wheel in degrees. 78 | */ 79 | getAngle: function() { 80 | return this.getRadian() * MathUtil.ONE_EIGHTY_OVER_PI; 81 | }, 82 | 83 | 84 | /** 85 | * @private 86 | */ 87 | resolveCollision: function(mtd, vel, n, d, o, p) { 88 | CircleParticle.prototype.resolveCollision.apply(this, arguments); 89 | // review the o (order) need here - its a hack fix 90 | this.resolve(n.mult(MathUtil.sign(d * o))); 91 | }, 92 | 93 | 94 | /** 95 | * simulates torque/wheel-ground interaction - n is the surface normal 96 | * Origins of this code thanks to Raigan Burns, Metanet software 97 | */ 98 | resolve: function(n) { 99 | 100 | // this is the tangent vector at the rim particle 101 | var rp = this.rp, 102 | tan = this.tan; 103 | 104 | tan.setTo(-rp.curr.y, rp.curr.x); 105 | // normalize so we can scale by the rotational speed 106 | tan = tan.normalize(); 107 | 108 | // velocity of the wheel's surface 109 | var wheelSurfaceVelocity = tan.mult(rp.getSpeed()); 110 | 111 | // the velocity of the wheel's surface relative to the ground 112 | var combinedVelocity = this.getVelocity().plusEquals(wheelSurfaceVelocity); 113 | 114 | // the wheel's comb velocity projected onto the contact normal 115 | var cp = combinedVelocity.cross(n); 116 | 117 | // set the wheel's spinspeed to track the ground 118 | tan.multEquals(cp); 119 | rp.prev.copy(rp.curr.minus(tan)); 120 | 121 | // some of the wheel's torque is removed and converted into linear displacement 122 | var slipSpeed = (1 - this._traction) * rp.getSpeed(); 123 | this.normSlip.setTo(slipSpeed * n.y, slipSpeed * n.x); 124 | this.curr.plusEquals(this.normSlip); 125 | rp.setSpeed(rp.getSpeed() * this._traction); 126 | } 127 | 128 | }); 129 | 130 | module.exports = WheelParticle; 131 | }); -------------------------------------------------------------------------------- /examples/RobotDemo/Leg.js: -------------------------------------------------------------------------------- 1 | define("Leg", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Engine = require("JPE/Engine"); 5 | var Composite = require("JPE/Composite"); 6 | var CircleParticle = require("JPE/CircleParticle"); 7 | var WheelParticle = require("JPE/WheelParticle"); 8 | var SpringConstraint = require("JPE/SpringConstraint"); 9 | 10 | var Leg = function (px, py, orientation, scale, lineWeight, lineColor, lineAlpha, fillColor, fillAlpha) { 11 | 12 | Composite.prototype.constructor.apply(this); 13 | 14 | this.lineColor = lineColor; 15 | this.lineAlpha = lineAlpha; 16 | this.lineWeight = lineWeight; 17 | 18 | this.fillColor = fillColor; 19 | this.fillAlpha = fillAlpha; 20 | 21 | 22 | // top triangle -- pa is the attach point to the body 23 | var os = orientation * scale; 24 | 25 | var pa = this.pa = new CircleParticle(px + 31 * os, py - 8 * scale, 1); 26 | var pb = this.pb = new CircleParticle(px + 25 * os, py - 37 * scale, 1); 27 | var pc = this.pc = new CircleParticle(px + 60 * os, py - 15 * scale, 1); 28 | 29 | // bottom triangle particles -- pf is the foot 30 | var pd = this.pd = new CircleParticle(px + 72 * os, py + 12 * scale, 1); 31 | var pe = this.pe = new CircleParticle(px + 43 * os, py + 19 * scale, 1); 32 | var pf = this.pf = new CircleParticle(px + 54 * os, py + 61 * scale, 2); 33 | 34 | // strut attach point particle 35 | var ph = this.ph = new CircleParticle(px, py, 3); 36 | 37 | // top triangle constraints 38 | var cAB = new SpringConstraint(pa,pb,1); 39 | var cBC = new SpringConstraint(pb,pc,1); 40 | var cCA = new SpringConstraint(pc,pa,1); 41 | 42 | // middle leg constraints 43 | var cCD = new SpringConstraint(pc,pd,1); 44 | var cAE = new SpringConstraint(pa,pe,1); 45 | 46 | // bottom leg constraints 47 | var cDE = new SpringConstraint(pd,pe,1); 48 | var cDF = new SpringConstraint(pd,pf,1); 49 | var cEF = new SpringConstraint(pe,pf,1); 50 | 51 | // cam constraints 52 | var cBH = new SpringConstraint(pb,ph,1); 53 | var cEH = new SpringConstraint(pe,ph,1); 54 | 55 | this.addParticle(pa); 56 | this.addParticle(pb); 57 | this.addParticle(pc); 58 | this.addParticle(pd); 59 | this.addParticle(pe); 60 | this.addParticle(pf); 61 | this.addParticle(ph); 62 | 63 | this.addConstraint(cAB); 64 | this.addConstraint(cBC); 65 | this.addConstraint(cCA); 66 | this.addConstraint(cCD); 67 | this.addConstraint(cAE); 68 | this.addConstraint(cDE); 69 | this.addConstraint(cDF); 70 | this.addConstraint(cEF); 71 | this.addConstraint(cBH); 72 | this.addConstraint(cEH); 73 | 74 | // for added efficiency, only test the feet (pf) for collision. these 75 | // selective tweaks should always be considered for best performance. 76 | pa.setCollidable(false); 77 | pb.setCollidable(false); 78 | pc.setCollidable(false); 79 | pd.setCollidable(false); 80 | pe.setCollidable(false); 81 | ph.setCollidable(false); 82 | 83 | this._visible = true; 84 | 85 | }; 86 | 87 | 88 | JPE.extend(Leg, Composite, { 89 | lineWeight:null, 90 | lineColor:null, 91 | lineAlpha:null, 92 | fillColor:null, 93 | fillAlpha:null, 94 | sg:null, 95 | _visible:null, 96 | 97 | pa:null, 98 | pb:null, 99 | pc:null, 100 | pd:null, 101 | pe:null, 102 | pf:null, 103 | ph:null, 104 | 105 | 106 | getCam:function() { 107 | return this.ph; 108 | }, 109 | 110 | getFix:function(){ 111 | return this.pa; 112 | }, 113 | 114 | initSelf: function(){ 115 | var sprite = new Container(), 116 | shape = new Shape(); 117 | 118 | sprite.addChild(shape); 119 | Engine.renderer.stage.addChild(sprite); 120 | this.sprite = sprite; 121 | this.shape = shape; 122 | }, 123 | paint: function() { 124 | var shape = this.shape, 125 | sg = shape.graphics, 126 | pa = this.pa, 127 | pb = this.pb, 128 | pc = this.pc, 129 | pd = this.pd, 130 | pe = this.pe, 131 | pf = this.pf, 132 | ph = this.ph; 133 | 134 | sg.clear(); 135 | if (! this._visible) return; 136 | 137 | 138 | if(this.lineWeight){ 139 | sg.setStrokeStyle(this.lineWeight) 140 | sg.beginStroke(Graphics.getRGB(this.lineColor, this.lineAlpha)); 141 | } 142 | sg.beginFill(Graphics.getRGB(this.fillColor, this.fillAlpha)); 143 | 144 | sg.moveTo(pa.getPx(), pa.getPy()); 145 | sg.lineTo(pb.getPx(), pb.getPy()); 146 | sg.lineTo(pc.getPx(), pc.getPy()); 147 | sg.lineTo(pa.getPx(), pa.getPy()); 148 | 149 | sg.moveTo(pd.getPx(), pd.getPy()); 150 | sg.lineTo(pe.getPx(), pe.getPy()); 151 | sg.lineTo(pf.getPx(), pf.getPy()); 152 | sg.lineTo(pd.getPx(), pd.getPy()); 153 | sg.endFill(); 154 | 155 | // triangle to triangle 156 | sg.moveTo(pa.getPx(), pa.getPy()); 157 | sg.lineTo(pe.getPx(), pe.getPy()); 158 | sg.moveTo(pc.getPx(), pc.getPy()); 159 | sg.lineTo(pd.getPx(), pd.getPy()); 160 | 161 | // leg motor attachments 162 | sg.moveTo(pb.getPx(), pb.getPy()); 163 | sg.lineTo(ph.getPx(), ph.getPy()); 164 | sg.moveTo(pe.getPx(), pe.getPy()); 165 | sg.lineTo(ph.getPx(), ph.getPy()); 166 | 167 | sg.drawCircle(pf.getPx(), pf.getPy(), pf.getRadius()); 168 | }, 169 | 170 | 171 | setVisible: function(b) { 172 | this._visible = b; 173 | } 174 | 175 | }); 176 | 177 | 178 | module.exports = Leg; 179 | }); 180 | -------------------------------------------------------------------------------- /examples/RobotDemo/Robot.js: -------------------------------------------------------------------------------- 1 | define("Robot", function(require, exports, module){ 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Engine = require("JPE/Engine"); 5 | var Group = require("JPE/Group"); 6 | var SpringConstraint = require("JPE/SpringConstraint"); 7 | 8 | var Body = require("./Body"); 9 | var Motor = require("./Motor"); 10 | var Leg = require("./Leg"); 11 | 12 | var Robot = function(px, py, scale, power){ 13 | 14 | Group.prototype.constructor.apply(this); 15 | 16 | // legs 17 | var legLA = this.legLA = new Leg(px, py, -1, scale, 2, 0x444444, 1, 0x222222, 1); 18 | var legRA = this.legRA = new Leg(px, py, 1, scale, 2, 0x444444, 1, 0x222222, 1); 19 | var legLB = this.legLB = new Leg(px, py, -1, scale, 2, 0x666666, 1, 0x444444, 1); 20 | var legRB = this.legRB = new Leg(px, py, 1, scale, 2, 0x666666, 1, 0x444444, 1); 21 | var legLC = this.legLC = new Leg(px, py, -1, scale, 2, 0x888888, 1, 0x666666, 1); 22 | var legRC = this.legRC = new Leg(px, py, 1, scale, 2, 0x888888, 1, 0x666666, 1); 23 | 24 | // body 25 | var body = this.body = new Body(legLA.getFix(), legRA.getFix(), 30 * scale, 2, 0x336699, 1); 26 | 27 | // motor 28 | var motor = this.motor = new Motor(body.center, 8 * scale, 0x336699); 29 | 30 | // connect the body to the legs 31 | var connLA = new SpringConstraint(body.left, legLA.getFix(), 1); 32 | var connRA = new SpringConstraint(body.right, legRA.getFix(), 1); 33 | var connLB = new SpringConstraint(body.left, legLB.getFix(), 1); 34 | var connRB = new SpringConstraint(body.right, legRB.getFix(), 1); 35 | var connLC = new SpringConstraint(body.left, legLC.getFix(), 1); 36 | var connRC = new SpringConstraint(body.right, legRC.getFix(), 1); 37 | 38 | 39 | // connect the legs to the motor 40 | legLA.getCam().setPosition(motor.getRimA().getPosition()); 41 | legRA.getCam().setPosition(motor.getRimA().getPosition()); 42 | var connLAA = new SpringConstraint(legLA.getCam(), motor.getRimA(), 1); 43 | var connRAA = new SpringConstraint(legRA.getCam(), motor.getRimA(), 1); 44 | 45 | legLB.getCam().setPosition(motor.getRimB().getPosition()); 46 | legRB.getCam().setPosition(motor.getRimB().getPosition()); 47 | var connLBB = new SpringConstraint(legLB.getCam(), motor.getRimB(), 1); 48 | var connRBB = new SpringConstraint(legRB.getCam(), motor.getRimB(), 1); 49 | 50 | legLC.getCam().setPosition(motor.getRimC().getPosition()); 51 | legRC.getCam().setPosition(motor.getRimC().getPosition()); 52 | var connLCC = new SpringConstraint(legLC.getCam(), motor.getRimC(), 1); 53 | var connRCC = new SpringConstraint(legRC.getCam(), motor.getRimC(), 1); 54 | 55 | connLAA.setLine(2,0x999999); 56 | connRAA.setLine(2,0x999999); 57 | connLBB.setLine(2,0x999999); 58 | connRBB.setLine(2,0x999999); 59 | connLCC.setLine(2,0x999999); 60 | connRCC.setLine(2,0x999999); 61 | 62 | // add to the engine 63 | this.addComposite(legLA); 64 | this.addComposite(legRA); 65 | this.addComposite(legLB); 66 | this.addComposite(legRB); 67 | this.addComposite(legLC); 68 | this.addComposite(legRC); 69 | 70 | this.addComposite(body); 71 | this.addComposite(motor); 72 | 73 | this.addConstraint(connLA); 74 | this.addConstraint(connRA); 75 | this.addConstraint(connLB); 76 | this.addConstraint(connRB); 77 | this.addConstraint(connLC); 78 | this.addConstraint(connRC); 79 | 80 | this.addConstraint(connLAA); 81 | this.addConstraint(connRAA); 82 | this.addConstraint(connLBB); 83 | this.addConstraint(connRBB); 84 | this.addConstraint(connLCC); 85 | this.addConstraint(connRCC); 86 | 87 | this.direction = -1 88 | this.powerLevel = power; 89 | 90 | this.powered = true; 91 | this.legsVisible=true; 92 | }; 93 | 94 | JPE.extend(Robot, Group, { 95 | body:null, 96 | motor:null, 97 | 98 | direction:null, 99 | powerLevel:null, 100 | 101 | powered:null, 102 | legsVisible:null, 103 | 104 | legLA:null, 105 | legRA:null, 106 | legLB:null, 107 | legRB:null, 108 | legLC:null, 109 | legRC:null, 110 | 111 | getPx: function () { 112 | return this.body.center.getPx(); 113 | }, 114 | 115 | 116 | getPy: function () { 117 | return this.body.center.getPy(); 118 | }, 119 | 120 | 121 | run: function () { 122 | this.motor.run(); 123 | }, 124 | 125 | 126 | togglePower: function () { 127 | 128 | this.powered = !this.powered 129 | 130 | if (this.powered) { 131 | this.motor.setPower(this.powerLevel * this.direction); 132 | this.setStiffness(1); 133 | Engine.damping = 0.99; 134 | } else { 135 | this.motor.setPower(0); 136 | this.setStiffness (0.2); 137 | Engine.damping = 0.35; 138 | } 139 | }, 140 | 141 | 142 | toggleDirection: function () { 143 | this.direction *= -1; 144 | this.motor.setPower(this.powerLevel * this.direction); 145 | }, 146 | 147 | toggleLegs: function (){ 148 | this.legsVisible = ! this.legsVisible; 149 | 150 | if (!this.legsVisible) { 151 | this.legLA.setVisible(false); 152 | this.legRA.setVisible(false); 153 | this.legLB.setVisible(false); 154 | this.legRB.setVisible(false); 155 | } else { 156 | this.legLA.setVisible(true); 157 | this.legRA.setVisible(true); 158 | this.legLB.setVisible(true); 159 | this.legRB.setVisible(true); 160 | } 161 | }, 162 | 163 | 164 | setStiffness: function (s) { 165 | 166 | // top level constraints in the group 167 | for (var i = 0, l = this.constraints.length;i < l; i++) { 168 | var sp = this.constraints[i]; 169 | sp.stiffness = s; 170 | } 171 | 172 | // constraints within this groups composites 173 | for (var j = 0, m= this.composites.length; j < m; j++) { 174 | for (i = 0; i < this.composites[j].constraints.length; i++) { 175 | sp = this.composites[j].constraints[i]; 176 | sp.stiffness = s; 177 | } 178 | } 179 | } 180 | }); 181 | 182 | module.exports = Robot; 183 | 184 | }); 185 | 186 | -------------------------------------------------------------------------------- /src/Group.js: -------------------------------------------------------------------------------- 1 | define("JPE/Group", function(require, exports, module) { 2 | 3 | var AbstractCollection = require("JPE/AbstractCollection"); 4 | var JPE = require("JPE/JPE"); 5 | /** 6 | * @param collideInternal {Boolean} Determines if the members 7 | * if this Group are checked for collision with one another. 8 | */ 9 | var Group = function(collideInternal) { 10 | AbstractCollection.prototype.constructor.call(this); 11 | this.composites = []; 12 | this.collisionList = []; 13 | this.collideInternal = collideInternal; 14 | }; 15 | 16 | JPE.extend(Group, AbstractCollection, { 17 | 18 | initSelf: function() { 19 | AbstractCollection.prototype.initSelf.apply(this, arguments); 20 | for (var i = 0, l = this.composites.length; i < l; i++) { 21 | this.composites[i].initSelf(); 22 | } 23 | }, 24 | /** 25 | * Adds a Composite to the Group 26 | * @param c {Composite} The Composite to be added. 27 | */ 28 | addComposite: function(c) { 29 | this.composites.push(c); 30 | c.isParented = true; 31 | if (this.isParented) { 32 | c.initSelf(); 33 | } 34 | }, 35 | /** 36 | * Remove a Composite from the Group 37 | * @param c {Composite} The Composite to be removed. 38 | */ 39 | removeComposite: function(c) { 40 | var cpos = JPE.Array.indexOf(this.composites, c); 41 | if (cpos === -1) { 42 | return; 43 | } 44 | this.composites.splice(cpos, 1); 45 | c.isParented = false; 46 | c.cleanup(); 47 | }, 48 | /** 49 | * Paints all members of this group. This method is called 50 | * automatically by JPE. 51 | */ 52 | paint: function() { 53 | AbstractCollection.prototype.paint.apply(this, arguments); 54 | var cs = this.composites, 55 | i = 0, 56 | c, 57 | len = this.composites.length; 58 | for (; i < len; i++) { 59 | c = cs[i]; 60 | c.paint(); 61 | } 62 | }, 63 | /** 64 | * Adds a Group instance to be checked for collision against 65 | * this one. 66 | * @param g {Group} 67 | */ 68 | addCollidable: function(g) { 69 | this.collisionList.push(g); 70 | }, 71 | /** 72 | * Removes a Group from the collidable list of this Group. 73 | * @param g {Group} 74 | */ 75 | removeCollidable: function(g) { 76 | var pos = JPE.Array.indexOf(this.collisionList, g); 77 | if (pos == -1) { 78 | return; 79 | } 80 | this.collisionList.splice(pos, 1); 81 | }, 82 | /** 83 | * Adds an array of AbstractCollection instances to be 84 | * checked for collision against this one. 85 | * @param list {Array} 86 | */ 87 | addCollidableList: function(list) { 88 | var i = 0, 89 | l = list.length, 90 | cl = this.collisionList; 91 | for (; i < l; i++) { 92 | cl.push(list[i]); 93 | } 94 | }, 95 | 96 | /** 97 | * Return an array of every particle, constraint, and 98 | * composite added to the Group. 99 | */ 100 | getAll: function() { 101 | return this.particles.concat(this.constraints).concat(this.composites); 102 | }, 103 | /** 104 | * Calls the cleanup() methid if every member if 105 | * this Group. 106 | * The cleanup() method is called automatically when a Group is 107 | * removed form the JPE. 108 | */ 109 | cleanup: function() { 110 | AbstractCollection.prototype.cleanup.apply(this, arguments); 111 | var cs = this.composites, 112 | cl = cs.length, 113 | i = 0; 114 | for (; i < cl; i++) { 115 | cs[i].cleanup(); 116 | } 117 | }, 118 | integrate: function(dt2) { 119 | AbstractCollection.prototype.integrate.apply(this, arguments); 120 | var cs = this.composites, 121 | cl = cs.length, 122 | i = 0; 123 | for (; i < cl; i++) { 124 | cs[i].integrate(dt2); 125 | } 126 | 127 | }, 128 | satisfyConstraints: function() { 129 | AbstractCollection.prototype.satisfyConstraints.apply(this, null); 130 | var cs = this.composites, 131 | cl = cs.length, 132 | i = 0; 133 | for (; i < cl; i++) { 134 | cs[i].satisfyConstraints(); 135 | } 136 | }, 137 | checkCollisions: function() { 138 | if (this.collideInternal) { 139 | this.checkCollisionGroupInternal(); 140 | } 141 | 142 | var cl = this.collisionList, 143 | cllen = cl.length, 144 | i = 0; 145 | 146 | for (; i < cllen; i++) { 147 | this.checkCollisionVsGroup(cl[i]); 148 | } 149 | }, 150 | checkCollisionGroupInternal: function() { 151 | this.checkInternalCollisions(); 152 | 153 | var cs = this.composites, 154 | c, 155 | c2, 156 | clen = cs.length, 157 | i = 0, 158 | j; 159 | 160 | for (; i < clen; i++) { 161 | c = cs[i]; 162 | c.checkCollisionsVsCollection(this); 163 | 164 | for (j = i + 1; j < clen; j++) { 165 | c2 = cs[j]; 166 | c.checkCollisionsVsCollection(c2); 167 | } 168 | } 169 | }, 170 | /** 171 | * @param g {Group} 172 | */ 173 | checkCollisionVsGroup: function(g) { 174 | this.checkCollisionsVsCollection(g); 175 | 176 | var cs = this.composites, 177 | c, 178 | gc, 179 | clen = cs.length, 180 | gclen = g.composites.length, 181 | i = 0, 182 | j; 183 | 184 | for (; i < clen; i++) { 185 | c = cs[i]; 186 | c.checkCollisionsVsCollection(g); 187 | for (j = 0; j < gclen; j++) { 188 | gc = g.composites[j]; 189 | c.checkCollisionsVsCollection(gc); 190 | } 191 | } 192 | 193 | for (j = 0; j < gclen; j++) { 194 | gc = g.composites[j]; 195 | this.checkCollisionsVsCollection(gc); 196 | } 197 | } 198 | }); 199 | 200 | module.exports = Group; 201 | }); -------------------------------------------------------------------------------- /src/AbstractCollection.js: -------------------------------------------------------------------------------- 1 | define("JPE/AbstractCollection", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var CollisionDetector = require("JPE/CollisionDetector"); 5 | 6 | var AbstractCollection = function() { 7 | this.isParented = false; 8 | this.container = null; 9 | this.particles = []; 10 | this.constraints = []; 11 | }; 12 | 13 | JPE.mix(AbstractCollection.prototype, { 14 | 15 | initSelf: function() { 16 | var ps = this.particles, 17 | cs = this.constraints, 18 | pl = ps.length, 19 | cl = cs.length, 20 | i; 21 | 22 | for (i = 0; i < pl; i++) { 23 | ps[i].initSelf(); 24 | } 25 | for (i = 0; i < cl; i++) { 26 | cs[i].initSelf(); 27 | } 28 | }, 29 | /** 30 | * @param p {AbstractParticle} 31 | */ 32 | addParticle: function(p) { 33 | this.particles.push(p); 34 | if (this.isParented) { 35 | p.initSelf(); 36 | } 37 | }, 38 | removeParticle: function(p) { 39 | var ppos = JPE.Array.indexOf(this.particles, p); 40 | if (ppos == -1) { 41 | return; 42 | } 43 | this.particles.splice(ppos, 1); 44 | p.cleanup(); 45 | }, 46 | /** 47 | * Adds a Composite to the Group 48 | * @param c {Composite} The Composite to be added. 49 | */ 50 | addConstraint: function(c) { 51 | this.constraints.push(c); 52 | c.isParented = true; 53 | if (this.isParented) { 54 | c.initSelf(); 55 | } 56 | }, 57 | /** 58 | * Remove a Composite from the Group 59 | * @param c {Composite} The Composite to be removed. 60 | */ 61 | removeConstraint: function(c) { 62 | var cpos = JPE.Array.indexOf(this.constraints, c); 63 | if (cpos === -1) { 64 | return; 65 | } 66 | this.constraints.splice(cpos, 1); 67 | c.cleanup(); 68 | }, 69 | /** 70 | * Paints every members of this AbstractCollection by calling each members 71 | * paint() method. 72 | */ 73 | paint: function() { 74 | var ps = this.particles, 75 | cs = this.constraints, 76 | pl = ps.length, 77 | cl = cs.length, 78 | p, 79 | c, 80 | i; 81 | 82 | for (i = 0; i < pl; i++) { 83 | p = ps[i]; 84 | if ((!p.getFixed()) || p.getAlwaysRepaint()) { 85 | p.paint(); 86 | } 87 | } 88 | for (i = 0; i < cl; i++) { 89 | c = cs[i]; 90 | if ((!c.getFixed()) || c.getAlwaysRepaint()) { 91 | c.paint(); 92 | } 93 | } 94 | }, 95 | 96 | getAll: function() { 97 | return this.particles.concat(this.constraints); 98 | }, 99 | /** 100 | * Calls the cleanup() methid if every member if 101 | * this Group. 102 | * The cleanup() method is called automatically when a Group is 103 | * removed form the JPE. 104 | */ 105 | cleanup: function() { 106 | var ps = this.particles, 107 | cs = this.constraints, 108 | pl = ps.length, 109 | cl = cs.length, 110 | i; 111 | 112 | for (i = 0; i < pl; i++) { 113 | ps[i].cleanup(); 114 | } 115 | for (i = 0; i < cl; i++) { 116 | cs[i].cleanup(); 117 | } 118 | }, 119 | integrate: function(dt2) { 120 | 121 | var ps = this.particles, 122 | pl = ps.length, 123 | i = 0; 124 | for (; i < pl; i++) { 125 | ps[i].update(dt2); 126 | } 127 | 128 | }, 129 | satisfyConstraints: function() { 130 | var cs = this.constraints, 131 | cl = cs.length, 132 | i = 0; 133 | for (; i < cl; i++) { 134 | cs[i].resolve(); 135 | } 136 | }, 137 | checkInternalCollisions: function() { 138 | var ps = this.particles, 139 | cs = this.constraints, 140 | pl = ps.length, 141 | cl = cs.length, 142 | p, 143 | p2, 144 | c, 145 | i, 146 | j, 147 | k; 148 | 149 | for (i = 0; i < pl; i++) { 150 | 151 | p = ps[i]; 152 | if (!p.getCollidable()) continue; 153 | 154 | for (j = i + 1; j < pl; j++) { 155 | p2 = ps[j]; 156 | if (p2.getCollidable()) { 157 | CollisionDetector.test(p, p2); 158 | } 159 | } 160 | 161 | for (k = 0; k < cl; k++) { 162 | c = cs[k]; 163 | if (c.getCollidable() && !c.isConnectedTo(p)) { 164 | c.scp.updatePosition(); 165 | CollisionDetector.test(p, c.scp); 166 | } 167 | } 168 | } 169 | }, 170 | /** 171 | * @param ac {AbstractCollection} 172 | */ 173 | checkCollisionsVsCollection: function(ac) { 174 | var ps = this.particles, 175 | acps = ac.particles, 176 | accs = ac.constraints, 177 | pl = ps.length, 178 | acpl = acps.length, 179 | accl = accs.length, 180 | p, 181 | p2, 182 | c, 183 | i, 184 | j, 185 | k; 186 | 187 | for (i = 0; i < pl; i++) { 188 | p = ps[i]; 189 | if (!p.getCollidable()) continue; 190 | 191 | for (j = 0; j < acpl; j++) { 192 | p2 = acps[j]; 193 | if (p2.getCollidable()) { 194 | CollisionDetector.test(p, p2); 195 | } 196 | } 197 | 198 | for (k = 0; k < accl; k++) { 199 | c = accs[k]; 200 | if (c.getCollidable() && !c.isConnectedTo(p)) { 201 | c.scp.updatePosition(); 202 | CollisionDetector.test(p, c.scp); 203 | 204 | } 205 | } 206 | } 207 | 208 | //constraints start 209 | // every constraint in this collection... 210 | var _constraints = this.constraints, 211 | clen = _constraints.length, 212 | n, 213 | cga; 214 | 215 | for (j = 0; j < clen; j++) { 216 | cga = _constraints[j]; 217 | if (!cga.getCollidable()) continue; 218 | 219 | for (n = 0; n < acpl; n++) { 220 | p = acps[n]; 221 | if (p.getCollidable() && !cga.isConnectedTo(p)) { 222 | cga.scp.updatePosition(); 223 | CollisionDetector.test(p, cga.scp); 224 | } 225 | } 226 | } 227 | //constraints end 228 | } 229 | }, true); 230 | 231 | module.exports = AbstractCollection; 232 | }); -------------------------------------------------------------------------------- /src/JPE.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2013 http://colorhook.com 3 | * @author: colorhook 4 | * @version: 2.0.0 5 | * @license: Released under the MIT License. 6 | * 7 | * Transplant from Flash AS3 APE Engine 8 | * http://www.cove.org/ape/ 9 | * Copyright (c) 2006, 2007 Alec Cove 10 | * Released under the MIT License. 11 | */ 12 | 13 | //if no CMD or AMD used, use builtin define and require function. 14 | ;(function (scope) { 15 | 16 | if(scope.define || scope.require){ 17 | return; 18 | } 19 | 20 | var modules = {}; 21 | 22 | function build(module) { 23 | var factory = module.factory; 24 | module.exports = {}; 25 | delete module.factory; 26 | factory(require, module.exports, module); 27 | return module.exports; 28 | } 29 | 30 | //引入模块 31 | var require = function (id) { 32 | if (!modules[id]) { 33 | throw 'module ' + id + ' not found'; 34 | } 35 | return modules[id].factory ? build(modules[id]) : modules[id].exports; 36 | }; 37 | 38 | //定义模块 39 | var define = function (id, factory) { 40 | if (modules[id]) { 41 | throw 'module ' + id + ' already defined'; 42 | } 43 | modules[id] = { 44 | id: id, 45 | factory: factory 46 | }; 47 | }; 48 | 49 | define.remove = function (id) { 50 | delete modules[id]; 51 | }; 52 | 53 | scope.require = require; 54 | scope.define = define; 55 | 56 | })(this); 57 | 58 | define("JPE/JPE", function(require, exports, module) { 59 | 60 | var _guid = 0, 61 | _forceEnum = [ 62 | 'hasOwnProperty', 63 | 'isPrototypeOf', 64 | 'propertyIsEnumerable', 65 | 'toString', 66 | 'toLocaleString', 67 | 'valueOf' 68 | ], 69 | _hasEnumBug = !{ 70 | valueOf: 0 71 | }.propertyIsEnumerable('valueOf'), 72 | hasOwn = Object.prototype.hasOwnProperty, 73 | TO_STRING = Object.prototype.toString, 74 | isFunction = function(o) { 75 | return TO_STRING.call(o) === '[object Function]'; 76 | }, 77 | isObject = function(o, failfn) { 78 | var t = typeof o; 79 | return (o && (t === 'object' || 80 | (!failfn && (t === 'function' || isFunction(o))))) || false; 81 | }, 82 | 83 | mix = function(receiver, supplier, overwrite, whitelist, mode, merge) { 84 | var alwaysOverwrite, exists, from, i, key, len, to; 85 | 86 | if (!receiver || !supplier) { 87 | return receiver; 88 | } 89 | 90 | if (mode) { 91 | if (mode === 2) { 92 | mix(receiver.prototype, supplier.prototype, overwrite, 93 | whitelist, 0, merge); 94 | } 95 | 96 | from = mode === 1 || mode === 3 ? supplier.prototype : supplier; 97 | to = mode === 1 || mode === 4 ? receiver.prototype : receiver; 98 | 99 | if (!from || !to) { 100 | return receiver; 101 | } 102 | } else { 103 | from = supplier; 104 | to = receiver; 105 | } 106 | 107 | alwaysOverwrite = overwrite && !merge; 108 | 109 | if (whitelist) { 110 | for (i = 0, len = whitelist.length; i < len; ++i) { 111 | key = whitelist[i]; 112 | 113 | if (!hasOwn.call(from, key)) { 114 | continue; 115 | } 116 | exists = alwaysOverwrite ? false : key in to; 117 | 118 | if (merge && exists && isObject(to[key], true) && isObject(from[key], true)) { 119 | mix(to[key], from[key], overwrite, null, 0, merge); 120 | } else if (overwrite || !exists) { 121 | to[key] = from[key]; 122 | } 123 | } 124 | } else { 125 | for (key in from) { 126 | if (!hasOwn.call(from, key)) { 127 | continue; 128 | } 129 | exists = alwaysOverwrite ? false : key in to; 130 | 131 | if (merge && exists && isObject(to[key], true) && isObject(from[key], true)) { 132 | mix(to[key], from[key], overwrite, null, 0, merge); 133 | } else if (overwrite || !exists) { 134 | to[key] = from[key]; 135 | } 136 | } 137 | if (_hasEnumBug) { 138 | mix(to, from, overwrite, _forceEnum, mode, merge); 139 | } 140 | } 141 | 142 | return receiver; 143 | }; 144 | 145 | mix(exports, { 146 | VERSION: '2.0.0', 147 | mix: mix, 148 | guid: function(pre) { 149 | var id = (_guid++) + ""; 150 | return pre ? pre + id : id; 151 | }, 152 | isFunction: isFunction, 153 | isObject: isObject, 154 | isUndefined: function(o) { 155 | return o === undefined; 156 | }, 157 | 158 | isBoolean: function(o) { 159 | return TO_STRING.call(o) === '[object Boolean]'; 160 | }, 161 | 162 | isString: function(o) { 163 | return TO_STRING.call(o) === '[object String]'; 164 | }, 165 | 166 | isNumber: function(o) { 167 | return TO_STRING.call(o) === '[object Number]' && isFinite(o); 168 | }, 169 | 170 | isArray: function(o) { 171 | return TO_STRING.call(o) === '[object Array]'; 172 | }, 173 | 'Array': { 174 | indexOf: function(arr, item) { 175 | if (arr.indexOf) { 176 | return arr.indexOf(item); 177 | } 178 | for (var i = 0, l = arr.length; i < l; i++) { 179 | if (arr[i] === item) { 180 | return i; 181 | } 182 | } 183 | return -1; 184 | }, 185 | each: function(arr, callback) { 186 | for (var i = 0, l = arr.length; i < l; i++) { 187 | callback(arr[i], i, arr); 188 | } 189 | }, 190 | remove: function(arr, item) { 191 | var index = this.indexOf(arr, item); 192 | if (index != -1) { 193 | return arr.splice(index, 1); 194 | } 195 | } 196 | }, 197 | merge: function() { 198 | var a = arguments, 199 | o = {}, i, l = a.length; 200 | for (i = 0; i < l; i = i + 1) { 201 | mix(o, a[i], true); 202 | } 203 | return o; 204 | }, 205 | extend: function(r, s, px, sx) { 206 | if (!s || !r) { 207 | return r; 208 | } 209 | var OP = Object.prototype, 210 | O = function(o) { 211 | function F() {} 212 | 213 | F.prototype = o; 214 | return new F(); 215 | }, 216 | sp = s.prototype, 217 | rp = O(sp); 218 | 219 | 220 | r.prototype = rp; 221 | rp.constructor = r; 222 | r.superclass = sp; 223 | 224 | if (s != Object && sp.constructor == OP.constructor) { 225 | sp.constructor = s; 226 | } 227 | 228 | if (px) { 229 | mix(rp, px, true); 230 | } 231 | 232 | if (sx) { 233 | mix(r, sx, true); 234 | } 235 | r.superclass = s; 236 | return r; 237 | }, 238 | 239 | newClass: function(classDef, superclass, prop, statics) { 240 | var f = classDef; 241 | if (!f) { 242 | f = function() { 243 | if (superclass) { 244 | superclass.prototype.constructor.apply(this, arguments); 245 | } 246 | } 247 | } 248 | if (superclass) { 249 | this.extend(f, superclass, prop, statics); 250 | } else { 251 | this.mix(f.prototype, prop); 252 | this.mix(f, statics); 253 | } 254 | return f; 255 | } 256 | 257 | }); 258 | 259 | 260 | }); -------------------------------------------------------------------------------- /src/SpringConstraint.js: -------------------------------------------------------------------------------- 1 | define("JPE/SpringConstraint", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var AbstractConstraint = require("JPE/AbstractConstraint"); 5 | var MathUtil = require("JPE/MathUtil"); 6 | var SpringConstraintParticle = require("JPE/SpringConstraintParticle"); 7 | 8 | /** 9 | * @param p1 The first particle this constraint is connected to. 10 | * @param p2 The second particle this constraint is connected to. 11 | * @param stiffness The strength of the spring. Valid values are between 0 and 1. Lower values 12 | * result in softer springs. Higher values result in stiffer, stronger springs. 13 | * @param collidable Determines if the constraint will be checked for collision 14 | * @param rectHeight If the constraint is collidable, the height of the collidable area 15 | * can be set in pixels. The height is perpendicular to the two attached particles. 16 | * @param rectScale If the constraint is collidable, the scale of the collidable area 17 | * can be set in value from 0 to 1. The scale is percentage of the distance between 18 | * the the two attached particles. 19 | * @param scaleToLength If the constraint is collidable and this value is true, the 20 | * collidable area will scale based on changes in the distance of the two particles. 21 | */ 22 | var SpringConstraint = function(p1, p2, stiffness, collidable, rectHeight, rectScale, scaleToLength) { 23 | stiffness = stiffness || 0.5; 24 | rectHeight = rectHeight || 1; 25 | rectScale = rectScale || 1; 26 | AbstractConstraint.prototype.constructor.call(this, stiffness); 27 | this.p1 = p1; 28 | this.p2 = p2; 29 | this.checkParticlesLocation(); 30 | this._restLength = this.getCurrLength(); 31 | this.inited = false; 32 | this.setCollidable(collidable, rectHeight, rectScale, scaleToLength); 33 | }; 34 | 35 | JPE.extend(SpringConstraint, AbstractConstraint, { 36 | 37 | getRadian: function() { 38 | var d = this.getDelta(); 39 | return Math.atan2(d.y, d.x); 40 | }, 41 | 42 | 43 | /** 44 | * The rotational value created by the positions of the two particles attached to this 45 | * SpringConstraint. You can use this property to in your own painting methods, along with the 46 | * center property. 47 | * 48 | * @returns A Number representing the rotation of this SpringConstraint in degrees 49 | */ 50 | getAngle: function() { 51 | return this.getRadian() * MathUtil.ONE_EIGHTY_OVER_PI; 52 | }, 53 | 54 | 55 | /** 56 | * The center position created by the relative positions of the two particles attached to this 57 | * SpringConstraint. You can use this property to in your own painting methods, along with the 58 | * rotation property. 59 | * 60 | * @returns A Vector representing the center of this SpringConstraint 61 | */ 62 | getCenter: function() { 63 | return (this.p1.curr.plus(this.p2.curr)).divEquals(2); 64 | }, 65 | 66 | 67 | /** 68 | * If the collidable property is true, you can set the scale of the collidible area 69 | * between the two attached particles. Valid values are from 0 to 1. If you set the value to 1, then 70 | * the collision area will extend all the way to the two attached particles. Setting the value lower 71 | * will result in an collision area that spans a percentage of that distance. Setting the value 72 | * higher will cause the collision rectangle to extend past the two end particles. 73 | */ 74 | setRectScale: function(s) { 75 | if (this.scp == null) return; 76 | this.scp.setRectScale(s); 77 | }, 78 | 79 | 80 | /** 81 | * @private 82 | */ 83 | getRectScale: function() { 84 | return this.scp.getRectScale(); 85 | }, 86 | 87 | 88 | /** 89 | * Returns the length of the SpringConstraint, the distance between its two 90 | * attached particles. 91 | */ 92 | getCurrLength: function() { 93 | return this.p1.curr.distance(this.p2.curr); 94 | }, 95 | 96 | 97 | /** 98 | * If the collidable property is true, you can set the height of the 99 | * collidible rectangle between the two attached particles. Valid values are greater 100 | * than 0. If you set the value to 10, then the collision rect will be 10 pixels high. 101 | * The height is perpendicular to the line connecting the two particles 102 | */ 103 | getRectHeight: function() { 104 | return this.scp.getRectHeight(); 105 | }, 106 | 107 | 108 | /** 109 | * @private 110 | */ 111 | setRectHeight: function(h) { 112 | if (this.scp == null) return; 113 | this.scp.setRectHeight(h); 114 | }, 115 | 116 | 117 | /** 118 | * The restLength property sets the length of SpringConstraint. This value will be 119 | * the distance between the two particles unless their position is altered by external forces. 120 | * The SpringConstraint will always try to keep the particles this distance apart. Values must 121 | * be > 0. 122 | */ 123 | getRestLength: function() { 124 | return this._restLength; 125 | }, 126 | 127 | 128 | /** 129 | * @private 130 | */ 131 | setRestLength: function(r) { 132 | if (r <= 0) throw new Error("restLength must be greater than 0"); 133 | this._restLength = r; 134 | }, 135 | 136 | 137 | /** 138 | * Determines if the area between the two particles is tested for collision. If this value is on 139 | * you can set the rectHeight and rectScale properties 140 | * to alter the dimensions of the collidable area. 141 | */ 142 | 143 | 144 | 145 | /** 146 | * For cases when the SpringConstraint is collidable and only one of the 147 | * two end particles are fixed. This value will dispose of collisions near the 148 | * fixed particle, to correct for situations where the collision could never be 149 | * resolved. Values must be between 0.0 and 1.0. 150 | */ 151 | getFixedEndLimit: function() { 152 | return this.scp.getFixedEndLimit(); 153 | }, 154 | 155 | 156 | /** 157 | * @private 158 | */ 159 | setFixedEndLimit: function(f) { 160 | if (this.scp == null) return; 161 | this.scp.setFixedEndLimit(f); 162 | }, 163 | 164 | getCollidable: function() { 165 | return this._collidable; 166 | }, 167 | /** 168 | * 169 | */ 170 | setCollidable: function(b, rectHeight, rectScale, scaleToLength) { 171 | this._collidable = b; 172 | this.scp = null; 173 | 174 | if (this._collidable) { 175 | if (this.scp) { 176 | this.scp.cleanup(); 177 | } 178 | this.scp = new SpringConstraintParticle(this.p1, this.p2, this, rectHeight, rectScale, scaleToLength); 179 | if (this.inited) { 180 | this.scp.initSelf(); 181 | } 182 | } 183 | }, 184 | 185 | 186 | /** 187 | * Returns true if the passed particle is one of the two particles attached to this SpringConstraint. 188 | */ 189 | isConnectedTo: function(p) { 190 | return (p == this.p1 || p == this.p2); 191 | }, 192 | 193 | 194 | /** 195 | * Returns true if both connected particle's fixed property is true. 196 | */ 197 | getFixed: function() { 198 | return this.p1.getFixed() && this.p2.getFixed(); 199 | }, 200 | 201 | 202 | /** 203 | * Sets up the visual representation of this SpringContraint. This method is called 204 | * automatically when an instance of this SpringContraint's parent Group is added to 205 | * the APEngine, when this SpringContraint's Composite is added to a Group, or this 206 | * SpringContraint is added to a Composite or Group. 207 | */ 208 | initSelf: function() { 209 | if (this.getCollidable()) { 210 | this.scp.initSelf(); 211 | } 212 | this.inited = true; 213 | }, 214 | cleanup: function() { 215 | if (this.getCollidable()) { 216 | this.scp.cleanup(); 217 | } 218 | this.inited = false 219 | }, 220 | 221 | 222 | getDelta: function() { 223 | return this.p1.curr.minus(this.p2.curr); 224 | }, 225 | 226 | 227 | 228 | /** 229 | * @private 230 | */ 231 | resolve: function() { 232 | 233 | var p1 = this.p1, 234 | p2 = this.p2; 235 | 236 | if (p1.getFixed() && p2.getFixed()) return; 237 | 238 | var deltaLength = this.getCurrLength(); 239 | var diff = (deltaLength - this.getRestLength()) / (deltaLength * (p1.getInvMass() + p2.getInvMass())); 240 | var dmds = this.getDelta().mult(diff * this.stiffness); 241 | this.p1.curr.minusEquals(dmds.mult(p1.getInvMass())); 242 | this.p2.curr.plusEquals(dmds.mult(p2.getInvMass())); 243 | }, 244 | 245 | 246 | /** 247 | * if the two particles are at the same location offset slightly 248 | */ 249 | checkParticlesLocation: function() { 250 | if (this.p1.curr.x == this.p2.curr.x && this.p1.curr.y == this.p2.curr.y) { 251 | this.p2.curr.x += 0.0001; 252 | } 253 | } 254 | }); 255 | 256 | 257 | module.exports = SpringConstraint; 258 | }); -------------------------------------------------------------------------------- /src/EaselRenderer.js: -------------------------------------------------------------------------------- 1 | define("JPE/EaselRenderer", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Renderer = require("JPE/Renderer"); 5 | var RectangleParticle = require("JPE/RectangleParticle"); 6 | var RigidRectangle = require("JPE/RigidRectangle"); 7 | var CircleParticle = require("JPE/CircleParticle"); 8 | var RigidCircle = require("JPE/RigidCircle"); 9 | var WheelParticle = require("JPE/WheelParticle"); 10 | var SpringConstraintParticle = require("JPE/SpringConstraintParticle"); 11 | var SpringConstraint = require("JPE/SpringConstraint"); 12 | 13 | 14 | var EaselRenderer = function(stage) { 15 | Renderer.prototype.constructor.apply(this, arguments); 16 | this.stage = stage; 17 | this.registerDelegate('RectangleParticle', RectangleParticle, new EaselRenderer.RectangleParticleDelegate(this)); 18 | this.registerDelegate('RigidRectangle', RigidRectangle, new EaselRenderer.RectangleParticleDelegate(this)); 19 | this.registerDelegate('CircleParticle', CircleParticle, new EaselRenderer.CircleParticleDelegate(this)); 20 | this.registerDelegate('RigidCircle', RigidCircle, new EaselRenderer.WheelParticleDelegate(this)); 21 | this.registerDelegate('WheelParticle', WheelParticle, new EaselRenderer.WheelParticleDelegate(this)); 22 | this.registerDelegate('SpringConstraintParticle', SpringConstraintParticle, new EaselRenderer.SpringConstraintParticleDelegate(this)); 23 | this.registerDelegate('SpringConstraint', SpringConstraint, new EaselRenderer.SpringConstraintDelegate(this)); 24 | }; 25 | 26 | JPE.extend(EaselRenderer, Renderer); 27 | 28 | EaselRenderer.AbstractDelegate = function(renderer) { 29 | this.renderer = renderer; 30 | this.stage = renderer.stage; 31 | }; 32 | 33 | JPE.mix(EaselRenderer.AbstractDelegate.prototype, { 34 | initSelf: function(item) { 35 | var sprite = new Container(), 36 | shape = new Shape(); 37 | sprite.addChild(shape); 38 | if (!item.getVisible()) { 39 | sprite.visible = false; 40 | } 41 | this.stage.addChild(sprite); 42 | item.set('sprite', sprite); 43 | item.set('shape', shape); 44 | this.drawShape(item); 45 | }, 46 | cleanup: function(item) { 47 | var s = item.get('sprite'); 48 | if (s) { 49 | this.stage.removeChild(s); 50 | } 51 | }, 52 | drawShape: function(item) {}, 53 | setVisible: function(item) { 54 | var sprite = item.get('sprite'); 55 | if (sprite) { 56 | sprite.visible = item.getVisible(); 57 | } 58 | }, 59 | render: function(item) {} 60 | }, true); 61 | 62 | EaselRenderer.RectangleParticleDelegate = function() { 63 | EaselRenderer.AbstractDelegate.apply(this, arguments); 64 | } 65 | JPE.extend(EaselRenderer.RectangleParticleDelegate, EaselRenderer.AbstractDelegate, { 66 | drawShape: function(item) { 67 | var shape = item.get('shape'), 68 | g = shape.graphics, 69 | w = item.getExtents()[0] * 2, 70 | h = item.getExtents()[1] * 2; 71 | shape.x = -w / 2; 72 | shape.y = -h / 2; 73 | g.clear(); 74 | if (item.lineThickness) { 75 | g.setStrokeStyle(item.lineThickness) 76 | g.beginStroke(Graphics.getRGB(item.lineColor, item.lineAlpha)); 77 | } 78 | g.beginFill(Graphics.getRGB(item.fillColor, item.fillAlpha)); 79 | g.drawRect(0, 0, w, h); 80 | g.endFill(); 81 | }, 82 | render: function(item) { 83 | var sprite = item.get('sprite'), 84 | x = item.curr.x, 85 | y = item.curr.y, 86 | w = item.getExtents()[0] * 2, 87 | h = item.getExtents()[1] * 2, 88 | r = item.getAngle(); 89 | 90 | if (sprite) { 91 | this.drawShape(item); 92 | sprite.rotation = r; 93 | sprite.x = x; 94 | sprite.y = y; 95 | } 96 | } 97 | }); 98 | 99 | EaselRenderer.CircleParticleDelegate = function() { 100 | EaselRenderer.AbstractDelegate.apply(this, arguments); 101 | } 102 | JPE.extend(EaselRenderer.CircleParticleDelegate, EaselRenderer.AbstractDelegate, { 103 | drawShape: function(item) { 104 | var r = item.getRadius(), 105 | shape = item.get('shape'), 106 | g = shape.graphics; 107 | 108 | g.clear(); 109 | if (item.lineThickness) { 110 | g.setStrokeStyle(item.lineThickness) 111 | g.beginStroke(Graphics.getRGB(item.lineColor, item.lineAlpha)); 112 | } 113 | g.beginFill(Graphics.getRGB(item.fillColor, item.fillAlpha)); 114 | g.drawCircle(0, 0, r); 115 | g.endFill(); 116 | }, 117 | render: function(item) { 118 | var x = item.curr.x, 119 | y = item.curr.y, 120 | sprite = item.get('sprite'); 121 | 122 | if (sprite) { 123 | this.drawShape(item); 124 | sprite.x = x; 125 | sprite.y = y; 126 | } 127 | } 128 | }); 129 | 130 | 131 | EaselRenderer.WheelParticleDelegate = function() { 132 | EaselRenderer.AbstractDelegate.apply(this, arguments); 133 | } 134 | JPE.extend(EaselRenderer.WheelParticleDelegate, EaselRenderer.AbstractDelegate, { 135 | drawShape: function(item) { 136 | 137 | var r = item.getRadius(), 138 | shape = item.get('shape'), 139 | g = shape.graphics; 140 | 141 | g.clear(); 142 | if (item.lineThickness) { 143 | g.setStrokeStyle(item.lineThickness); 144 | g.beginStroke(Graphics.getRGB(item.lineColor, item.lineAlpha)); 145 | } 146 | g.beginFill(Graphics.getRGB(item.fillColor, item.fillAlpha)); 147 | g.drawCircle(0, 0, r); 148 | 149 | g.setStrokeStyle(1); 150 | g.beginStroke(Graphics.getRGB(0xffffff - item.lineColor)); 151 | g.moveTo(-r, 0); 152 | g.lineTo(r, 0); 153 | g.moveTo(0, -r); 154 | g.lineTo(0, r); 155 | g.endFill(); 156 | }, 157 | render: function(item) { 158 | var x = item.curr.x, 159 | y = item.curr.y, 160 | r = item.getAngle(), 161 | sprite = item.get('sprite'); 162 | 163 | if (sprite) { 164 | this.drawShape(item); 165 | sprite.rotation = r; 166 | sprite.x = x; 167 | sprite.y = y; 168 | } 169 | 170 | } 171 | }); 172 | 173 | 174 | EaselRenderer.SpringConstraintParticleDelegate = function() { 175 | EaselRenderer.AbstractDelegate.apply(this, arguments); 176 | } 177 | JPE.extend(EaselRenderer.SpringConstraintParticleDelegate, EaselRenderer.AbstractDelegate, { 178 | initSelf: function(item) { 179 | var inner = new Container(), 180 | shape = new Shape(), 181 | parent = item.parent, 182 | parentSprite = parent.get('sprite'); 183 | if (!parentSprite) { 184 | parentSprite = new Container(); 185 | parent.set('sprite', parentSprite); 186 | } 187 | item.set('sprite', inner); 188 | item.set('shape', shape); 189 | if (!item.getVisible()) { 190 | sprite.visible = false; 191 | } 192 | inner.addChild(shape); 193 | parentSprite.addChild(inner); 194 | this.drawShape(item); 195 | this.stage.addChild(parentSprite); 196 | }, 197 | cleanup: function(item) { 198 | var parent = item.parent; 199 | this.stage.removeChild(parent.get('sprite')); 200 | }, 201 | drawShape: function(item) { 202 | var shape = item.get('shape'), 203 | g = shape.graphics, 204 | parent = item.parent, 205 | c = parent.getCenter(), 206 | w = parent.getCurrLength() * item.getRectScale(), 207 | h = item.getRectHeight(); 208 | 209 | 210 | g.clear(); 211 | if (parent.lineThickness) { 212 | g.setStrokeStyle(parent.lineThickness); 213 | g.beginStroke(Graphics.getRGB(parent.lineColor, parent.lineAlpha)); 214 | } 215 | g.beginFill(Graphics.getRGB(parent.fillColor, parent.fillAlpha)); 216 | g.drawRect(-w / 2, -h / 2, w, h); 217 | g.endFill(); 218 | }, 219 | render: function(item) { 220 | 221 | var parent = item.parent, 222 | c = parent.getCenter(), 223 | s = item.get('sprite'), 224 | shape = item.get('shape'); 225 | 226 | s.x = c.x; 227 | s.y = c.y; 228 | if (item.scaleToLength) { 229 | s.width = parent.getCurrLength() * item.getRectScale(); 230 | } 231 | s.rotation = parent.getAngle(); 232 | 233 | 234 | } 235 | }); 236 | 237 | EaselRenderer.SpringConstraintDelegate = function() { 238 | EaselRenderer.AbstractDelegate.apply(this, arguments); 239 | } 240 | JPE.extend(EaselRenderer.SpringConstraintDelegate, EaselRenderer.AbstractDelegate, { 241 | initSelf: function(item) {}, 242 | cleanup: function(item) { 243 | var sprite = item.get('sprite'); 244 | if (sprite) { 245 | this.stage.removeChild(sprite); 246 | } 247 | }, 248 | initShape: function(item) { 249 | var sprite = new Container(), 250 | shape = new Shape(); 251 | 252 | item.set('sprite', sprite); 253 | item.set('shape', shape); 254 | sprite.addChild(shape); 255 | this.stage.addChild(sprite); 256 | }, 257 | drawShape: function(item) { 258 | 259 | var shape = item.get('shape'), 260 | g = shape.graphics, 261 | p1 = item.p1, 262 | p2 = item.p2; 263 | 264 | g.clear(); 265 | if (item.lineThickness) { 266 | g.setStrokeStyle(item.lineThickness); 267 | g.beginStroke(Graphics.getRGB(item.lineColor, item.lineAlpha)); 268 | } 269 | g.moveTo(p1.getPx(), p1.getPy()); 270 | g.lineTo(p2.getPx(), p2.getPy()); 271 | g.endFill(); 272 | }, 273 | render: function(item) { 274 | if (item.getCollidable()) { 275 | item.scp.paint(); 276 | } else { 277 | if (!item.get('shape')) { 278 | this.initShape(item); 279 | } 280 | this.drawShape(item); 281 | } 282 | } 283 | }); 284 | 285 | module.exports = EaselRenderer; 286 | }); -------------------------------------------------------------------------------- /src/AbstractParticle.js: -------------------------------------------------------------------------------- 1 | define("JPE/AbstractParticle", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var Engine = require("JPE/Engine"); 5 | var Collision = require("JPE/Collision"); 6 | var AbstractItem = require("JPE/AbstractItem"); 7 | var Interval = require("JPE/Interval"); 8 | var Vector = require("JPE/Vector"); 9 | var Signal = require("JPE/Signal"); 10 | 11 | var AbstractParticle = function(x, y, isFixed, mass, elasticity, friction) { 12 | 13 | AbstractItem.prototype.constructor.apply(this, null); 14 | 15 | this.interval = new Interval(0, 0); 16 | 17 | this.curr = new Vector(x, y); 18 | this.prev = new Vector(x, y); 19 | this.samp = new Vector(); 20 | this.temp = new Vector(); 21 | this.smashable = false; 22 | this.maxExitVelocity = 0; 23 | this.smashSignal = new Signal(); 24 | 25 | this.forces = new Vector(); 26 | this.forceList = []; 27 | this.collision = new Collision(new Vector(), new Vector()); 28 | this.firstCollision = false; 29 | this._fixed = isFixed; 30 | this._collidable = true; 31 | this.setMass(mass); 32 | this._elasticity = elasticity; 33 | this._friction = friction; 34 | this._center = new Vector(); 35 | this._multisample = 0; 36 | this.setStyle(); 37 | 38 | } 39 | 40 | JPE.extend(AbstractParticle, AbstractItem, { 41 | 42 | getElasticity: function() { 43 | return this._elasticity; 44 | }, 45 | setElasticity: function(value) { 46 | this._elasticity = value; 47 | }, 48 | /** 49 | * multisample getter & setter 50 | */ 51 | getMultisample: function() { 52 | return this._multisample; 53 | }, 54 | setMultisample: function(value) { 55 | return this._multisample = value; 56 | }, 57 | /** 58 | * collidable getter & setter 59 | */ 60 | getCollidable: function() { 61 | return this._collidable; 62 | }, 63 | setCollidable: function(collidable) { 64 | this._collidable = collidable; 65 | }, 66 | /** 67 | * fixed getter & setter 68 | */ 69 | getFixed: function() { 70 | return this._fixed; 71 | }, 72 | setFixed: function(fixed) { 73 | this._fixed = fixed; 74 | }, 75 | /** 76 | * The mass of the particle. Valid values are greater than zero. By default, all particles 77 | * have a mass of 1. The mass property has no relation to the size of the particle. 78 | * 79 | * @throws ArgumentError ArgumentError if the mass is set less than zero. 80 | */ 81 | getMass: function() { 82 | return this._mass; 83 | }, 84 | 85 | 86 | /** 87 | * @private 88 | */ 89 | setMass: function(m) { 90 | if (m <= 0) throw new Error("mass may not be set <= 0"); 91 | this._mass = m; 92 | this._invMass = 1 / this._mass; 93 | }, 94 | 95 | 96 | /** 97 | * The elasticity of the particle. Standard values are between 0 and 1. 98 | * The higher the value, the greater the elasticity. 99 | * 100 | *

101 | * During collisions the elasticity values are combined. If one particle's 102 | * elasticity is set to 0.4 and the other is set to 0.4 then the collision will 103 | * be have a total elasticity of 0.8. The result will be the same if one particle 104 | * has an elasticity of 0 and the other 0.8. 105 | *

106 | * 107 | *

108 | * Setting the elasticity to greater than 1 (of a single particle, or in a combined 109 | * collision) will cause particles to bounce with energy greater than naturally 110 | * possible. 111 | *

112 | */ 113 | 114 | 115 | 116 | /** 117 | * Returns A Vector of the current location of the particle 118 | */ 119 | getCenter: function() { 120 | this._center.setTo(this.getPx(), this.getPy()) 121 | return this._center; 122 | }, 123 | 124 | 125 | /** 126 | * The surface friction of the particle. Values must be in the range of 0 to 1. 127 | * 128 | *

129 | * 0 is no friction (slippery), 1 is full friction (sticky). 130 | *

131 | * 132 | *

133 | * During collisions, the friction values are summed, but are clamped between 1 and 0. 134 | * For example, If two particles have 0.7 as their surface friction, then the resulting 135 | * friction between the two particles will be 1 (full friction). 136 | *

137 | * 138 | *

139 | * In the current release, only dynamic friction is calculated. Static friction 140 | * is planned for a later release. 141 | *

142 | * 143 | *

144 | * There is a bug in the current release where colliding non-fixed particles with friction 145 | * greater than 0 will behave erratically. A workaround is to only set the friction of 146 | * fixed particles. 147 | *

148 | * @throws ArgumentError ArgumentError if the friction is set less than zero or greater than 1 149 | */ 150 | getFriction: function() { 151 | return this._friction; 152 | }, 153 | 154 | 155 | /** 156 | * @private 157 | */ 158 | setFriction: function(f) { 159 | if (f < 0 || f > 1) throw new Error("Legal friction must be >= 0 and <=1"); 160 | this._friction = f; 161 | }, 162 | 163 | 164 | 165 | 166 | /** 167 | * The position of the particle. Getting the position of the particle is useful 168 | * for drawing it or testing it for some custom purpose. 169 | * 170 | *

171 | * When you get the position of a particle you are given a copy of the current 172 | * location. Because of this you cannot change the position of a particle by 173 | * altering the x and y components of the Vector you have retrieved from the position property. 174 | * You have to do something instead like: position = new Vector(100,100), or 175 | * you can use the px and py properties instead. 176 | *

177 | * 178 | *

179 | * You can alter the position of a particle three ways: change its position, set 180 | * its velocity, or apply a force to it. Setting the position of a non-fixed particle 181 | * is not the same as setting its fixed property to true. A particle held in place by 182 | * its position will behave as if it's attached there by a 0 length spring constraint. 183 | *

184 | */ 185 | getPosition: function() { 186 | return new Vector(this.curr.x, this.curr.y); 187 | }, 188 | 189 | 190 | /** 191 | * @private 192 | */ 193 | setPosition: function(p) { 194 | this.curr.copy(p); 195 | this.prev.copy(p); 196 | }, 197 | 198 | 199 | /** 200 | * The x position of this particle 201 | */ 202 | getPx: function() { 203 | return this.curr.x; 204 | }, 205 | 206 | 207 | /** 208 | * @private 209 | */ 210 | setPx: function(x) { 211 | this.curr.x = x; 212 | this.prev.x = x; 213 | }, 214 | 215 | 216 | /** 217 | * The y position of this particle 218 | */ 219 | getPy: function() { 220 | return this.curr.y; 221 | }, 222 | 223 | 224 | /** 225 | * @private 226 | */ 227 | setPy: function(y) { 228 | this.curr.y = y; 229 | this.prev.y = y; 230 | }, 231 | 232 | 233 | /** 234 | * The velocity of the particle. If you need to change the motion of a particle, 235 | * you should either use this property, or one of the addForce methods. Generally, 236 | * the addForce methods are best for slowly altering the motion. The velocity property 237 | * is good for instantaneously setting the velocity, e.g., for projectiles. 238 | * 239 | */ 240 | getVelocity: function() { 241 | return this.curr.minus(this.prev); 242 | }, 243 | 244 | 245 | /** 246 | * @private 247 | */ 248 | setVelocity: function(v) { 249 | this.prev = this.curr.minus(v); 250 | }, 251 | /** 252 | * Adds a force to the particle. The mass of the particle is taken into 253 | * account when using this method, so it is useful for adding forces 254 | * that simulate effects like wind. Particles with larger masses will 255 | * not be affected as greatly as those with smaller masses. Note that the 256 | * size (not to be confused with mass) of the particle has no effect 257 | * on its physical behavior with respect to forces. 258 | * 259 | * @param f A Vector represeting the force added. 260 | */ 261 | addForce: function(f) { 262 | this.forceList.push(f); 263 | }, 264 | 265 | 266 | accumulateForces: function() { 267 | var f; 268 | var len = this.forceList.length; 269 | for (var i = 0; i < len; i++) { 270 | f = this.forceList[i]; 271 | this.forces.plusEquals(f.getValue(this._invMass)); 272 | } 273 | 274 | var globalForces = Engine.forces; 275 | len = globalForces.length; 276 | for (i = 0; i < len; i++) { 277 | f = globalForces[i]; 278 | this.forces.plusEquals(f.getValue(this._invMass)); 279 | } 280 | }, 281 | 282 | clearForces: function() { 283 | this.forceList.length = 0; 284 | this.forces.setTo(0, 0); 285 | }, 286 | 287 | /** 288 | * The update() method is called automatically during the 289 | * APEngine.step() cycle. This method integrates the particle. 290 | */ 291 | update: function(dt2) { 292 | 293 | if (this.getFixed()) return; 294 | 295 | this.accumulateForces(); 296 | 297 | // integrate 298 | this.temp.copy(this.curr); 299 | 300 | var nv = this.getVelocity().plus(this.forces.multEquals(dt2)); 301 | 302 | this.curr.plusEquals(nv.multEquals(Engine.damping)); 303 | this.prev.copy(this.temp); 304 | 305 | // clear the forces 306 | this.clearForces(); 307 | }, 308 | 309 | resetFirstCollision: function() { 310 | this.firstCollision = false; 311 | }, 312 | 313 | getComponents: function(collisionNormal) { 314 | var vel = this.getVelocity(); 315 | var vdotn = collisionNormal.dot(vel); 316 | this.collision.vn = collisionNormal.mult(vdotn); 317 | this.collision.vt = vel.minus(this.collision.vn); 318 | return this.collision; 319 | }, 320 | 321 | 322 | resolveCollision: function(mtd, vel, n, d, o, p) { 323 | if (this.smashable) { 324 | var ev = vel.magnitude(); 325 | if (ev > this.maxExitVelocity) { 326 | this.smashSignal.dispatch("collision"); 327 | } 328 | } 329 | if (this.getFixed() || !this.solid || !p.solid) { 330 | return; 331 | } 332 | this.curr.copy(this.samp); 333 | this.curr.plusEquals(mtd); 334 | this.setVelocity(vel); 335 | 336 | }, 337 | 338 | getInvMass: function() { 339 | return (this.getFixed()) ? 0 : this._invMass; 340 | } 341 | }); 342 | 343 | module.exports = AbstractParticle; 344 | 345 | }); -------------------------------------------------------------------------------- /src/SpringConstraintParticle.js: -------------------------------------------------------------------------------- 1 | define("JPE/SpringConstraintParticle", function(require, exports, module) { 2 | 3 | var JPE = require("JPE/JPE"); 4 | var RectangleParticle = require("JPE/RectangleParticle"); 5 | var Vector = require("JPE/Vector"); 6 | var MathUtil = require("JPE/MathUtil"); 7 | var RectangleParticle = require("JPE/RectangleParticle"); 8 | var CircleParticle = require("JPE/CircleParticle"); 9 | 10 | /** 11 | * @param p1 The first particle this constraint is connected to. 12 | * @param p2 The second particle this constraint is connected to. 13 | * @param stiffness The strength of the spring. Valid values are between 0 and 1. Lower values 14 | * result in softer springs. Higher values result in stiffer, stronger springs. 15 | * @param collidable Determines if the constraint will be checked for collision 16 | * @param rectHeight If the constraint is collidable, the height of the collidable area 17 | * can be set in pixels. The height is perpendicular to the two attached particles. 18 | * @param rectScale If the constraint is collidable, the scale of the collidable area 19 | * can be set in value from 0 to 1. The scale is percentage of the distance between 20 | * the the two attached particles. 21 | * @param scaleToLength If the constraint is collidable and this value is true, the 22 | * collidable area will scale based on changes in the distance of the two particles. 23 | */ 24 | var SpringConstraintParticle = function(p1, p2, p, rectHeight, rectScale, scaleToLength) { 25 | 26 | this.p1 = p1; 27 | this.p2 = p2; 28 | 29 | this.lambda = new Vector(0, 0); 30 | this.avgVelocity = new Vector(0, 0); 31 | 32 | this.parent = p; 33 | RectangleParticle.prototype.constructor.call(this, 0, 0, 0, 0, 0, false); 34 | this._rectScale = rectScale; 35 | this._rectHeight = rectHeight; 36 | this.scaleToLength = scaleToLength; 37 | 38 | this._fixedEndLimit = 0; 39 | this.rca = new Vector(); 40 | this.rcb = new Vector(); 41 | 42 | 43 | }; 44 | 45 | JPE.extend(SpringConstraintParticle, RectangleParticle, { 46 | 47 | setRectScale: function(s) { 48 | this._rectScale = s; 49 | }, 50 | 51 | 52 | /** 53 | * @private 54 | */ 55 | getRectScale: function() { 56 | return this._rectScale; 57 | }, 58 | 59 | 60 | setRectHeight: function(r) { 61 | this._rectHeight = r; 62 | }, 63 | 64 | 65 | /** 66 | * @private 67 | */ 68 | getRectHeight: function() { 69 | return this._rectHeight; 70 | }, 71 | 72 | 73 | /** 74 | * For cases when the SpringConstraint is both collidable and only one of the 75 | * two end particles are fixed, this value will dispose of collisions near the 76 | * fixed particle, to correct for situations where the collision could never be 77 | * resolved. 78 | */ 79 | setFixedEndLimit: function(f) { 80 | this._fixedEndLimit = f; 81 | }, 82 | 83 | 84 | /** 85 | * @private 86 | */ 87 | getFixedEndLimit: function() { 88 | return this._fixedEndLimit; 89 | }, 90 | 91 | 92 | /** 93 | * returns the average mass of the two connected particles 94 | */ 95 | getMass: function() { 96 | return (this.p1.getMass() + this.p2.getMass()) / 2; 97 | }, 98 | 99 | 100 | /** 101 | * returns the average elasticity of the two connected particles 102 | */ 103 | getElasticity: function() { 104 | return (this.p1.getElasticity() + this.p2.getElasticity()) / 2; 105 | }, 106 | 107 | 108 | /** 109 | * returns the average friction of the two connected particles 110 | */ 111 | getFriction: function() { 112 | return (this.p1.getFriction() + this.p2.getFriction()) / 2; 113 | }, 114 | 115 | 116 | /** 117 | * returns the average velocity of the two connected particles 118 | */ 119 | getVelocity: function() { 120 | var p1v = this.p1.getVelocity(); 121 | var p2v = this.p2.getVelocity(); 122 | 123 | this.avgVelocity.setTo(((p1v.x + p2v.x) / 2), ((p1v.y + p2v.y) / 2)); 124 | return this.avgVelocity; 125 | }, 126 | 127 | /** 128 | * @private 129 | * returns the average inverse mass. 130 | */ 131 | getInvMass: function() { 132 | if (this.p1.getFixed() && this.p2.getFixed()) return 0; 133 | return 1 / ((this.p1.getMass() + this.p2.getMass()) / 2); 134 | }, 135 | 136 | 137 | /** 138 | * called only on collision 139 | */ 140 | updatePosition: function() { 141 | 142 | var c = this.parent.getCenter(); 143 | this.curr.setTo(c.x, c.y); 144 | 145 | this.setWidth((this.scaleToLength) ? this.parent.getCurrLength() * this._rectScale : this.parent.getRestLength() * this._rectScale); 146 | this.setHeight(this.getRectHeight()); 147 | this.setRadian(this.parent.getRadian()); 148 | 149 | }, 150 | 151 | 152 | resolveCollision: function(mtd, vel, n, d, o, p) { 153 | 154 | var t = this.getContactPointParam(p); 155 | var c1 = (1 - t); 156 | var c2 = t; 157 | var p1 = this.p1; 158 | var p2 = this.p2; 159 | var fixedEndLimit = this.getFixedEndLimit(); 160 | var lambda = this.lambda; 161 | 162 | // if one is fixed then move the other particle the entire way out of collision. 163 | // also, dispose of collisions at the sides of the scp. The higher the fixedEndLimit 164 | // value, the more of the scp not be effected by collision. 165 | if (p1.getFixed()) { 166 | if (c2 <= fixedEndLimit) return; 167 | lambda.setTo(mtd.x / c2, mtd.y / c2); 168 | p2.curr.plusEquals(lambda); 169 | p2.setVelocity(vel); 170 | 171 | } else if (p2.getFixed()) { 172 | if (c1 <= fixedEndLimit) return; 173 | lambda.setTo(mtd.x / c1, mtd.y / c1); 174 | p1.curr.plusEquals(lambda); 175 | p1.setVelocity(vel); 176 | 177 | // else both non fixed - move proportionally out of collision 178 | } else { 179 | var denom = (c1 * c1 + c2 * c2); 180 | if (denom == 0) return; 181 | lambda.setTo(mtd.x / denom, mtd.y / denom); 182 | 183 | p1.curr.plusEquals(lambda.mult(c1)); 184 | p2.curr.plusEquals(lambda.mult(c2)); 185 | 186 | // if collision is in the middle of SCP set the velocity of both end particles 187 | if (t == 0.5) { 188 | p1.setVelocity(vel); 189 | p2.setVelocity(vel); 190 | 191 | // otherwise change the velocity of the particle closest to contact 192 | } else { 193 | var corrParticle = (t < 0.5) ? p1 : p2; 194 | corrParticle.setVelocity(vel); 195 | } 196 | } 197 | }, 198 | 199 | 200 | /** 201 | * given point c, returns a parameterized location on this SCP. Note 202 | * this is just treating the SCP as if it were a line segment (ab). 203 | */ 204 | closestParamPoint: function(c) { 205 | var ab = this.p2.curr.minus(this.p1.curr); 206 | var t = (ab.dot(c.minus(this.p1.curr))) / (ab.dot(ab)); 207 | return MathUtil.clamp(t, 0, 1); 208 | }, 209 | 210 | 211 | /** 212 | * returns a contact location on this SCP expressed as a parametric value in [0,1] 213 | */ 214 | getContactPointParam: function(p) { 215 | 216 | var t; 217 | 218 | if (p instanceof CircleParticle) { 219 | t = this.closestParamPoint(p.curr); 220 | } else if (p instanceof RectangleParticle) { 221 | 222 | // go through the sides of the colliding rectangle as line segments 223 | var shortestIndex; 224 | var paramList = new Array(4); 225 | var shortestDistance = Number.POSITIVE_INFINITY; 226 | 227 | for (var i = 0; i < 4; i++) { 228 | this.setCorners(p, i); 229 | 230 | // check for closest points on SCP to side of rectangle 231 | var d = this.closestPtSegmentSegment(); 232 | if (d < shortestDistance) { 233 | shortestDistance = d; 234 | shortestIndex = i; 235 | paramList[i] = s; 236 | } 237 | } 238 | t = paramList[shortestIndex]; 239 | } 240 | return t; 241 | }, 242 | 243 | 244 | /** 245 | * 246 | */ 247 | setCorners: function(r, i) { 248 | 249 | var rx = r.curr.x; 250 | var ry = r.curr.y; 251 | 252 | var axes = r.getAxes(); 253 | var extents = r.getExtents(); 254 | 255 | var ae0_x = axes[0].x * extents[0]; 256 | var ae0_y = axes[0].y * extents[0]; 257 | var ae1_x = axes[1].x * extents[1]; 258 | var ae1_y = axes[1].y * extents[1]; 259 | 260 | var emx = ae0_x - ae1_x; 261 | var emy = ae0_y - ae1_y; 262 | var epx = ae0_x + ae1_x; 263 | var epy = ae0_y + ae1_y; 264 | 265 | var rca = this.rca; 266 | var rcb = this.rcb; 267 | 268 | if (i == 0) { 269 | // 0 and 1 270 | rca.x = rx - epx; 271 | rca.y = ry - epy; 272 | rcb.x = rx + emx; 273 | rcb.y = ry + emy; 274 | 275 | } else if (i == 1) { 276 | // 1 and 2 277 | rca.x = rx + emx; 278 | rca.y = ry + emy; 279 | rcb.x = rx + epx; 280 | rcb.y = ry + epy; 281 | 282 | } else if (i == 2) { 283 | // 2 and 3 284 | rca.x = rx + epx; 285 | rca.y = ry + epy; 286 | rcb.x = rx - emx; 287 | rcb.y = ry - emy; 288 | 289 | } else if (i == 3) { 290 | // 3 and 0 291 | rca.x = rx - emx; 292 | rca.y = ry - emy; 293 | rcb.x = rx - epx; 294 | rcb.y = ry - epy; 295 | } 296 | }, 297 | 298 | 299 | /** 300 | * pp1-pq1 will be the SCP line segment on which we need parameterized s. 301 | */ 302 | closestPtSegmentSegment: function() { 303 | 304 | var pp1 = this.p1.curr, 305 | pq1 = this.p2.curr, 306 | pp2 = this.rca, 307 | pq2 = this.rcb, 308 | 309 | d1 = pq1.minus(pp1), 310 | d2 = pq2.minus(pp2), 311 | r = pp1.minus(pp2), 312 | 313 | t, 314 | a = d1.dot(d1), 315 | e = d2.dot(d2), 316 | f = d2.dot(r), 317 | 318 | c = d1.dot(r), 319 | b = d1.dot(d2), 320 | denom = a * e - b * b; 321 | 322 | if (denom != 0.0) { 323 | s = MathUtil.clamp((b * f - c * e) / denom, 0, 1); 324 | } else { 325 | s = 0.5 // give the midpoint for parallel lines 326 | } 327 | t = (b * s + f) / e; 328 | 329 | if (t < 0) { 330 | t = 0; 331 | s = MathUtil.clamp(-c / a, 0, 1); 332 | } else if (t > 0) { 333 | t = 1; 334 | s = MathUtil.clamp((b - c) / a, 0, 1); 335 | } 336 | 337 | var c1 = pp1.plus(d1.mult(s)); 338 | var c2 = pp2.plus(d2.mult(t)); 339 | var c1mc2 = c1.minus(c2); 340 | return c1mc2.dot(c1mc2); 341 | } 342 | 343 | }); 344 | 345 | module.exports = SpringConstraintParticle; 346 | }); -------------------------------------------------------------------------------- /src/CollisionDetector.js: -------------------------------------------------------------------------------- 1 | define("JPE/CollisionDetector", function(require, exports, module) { 2 | 3 | var POSITIVE_INFINITY = Number.POSITIVE_INFINITY; 4 | var Vector = require("JPE/Vector"); 5 | var CollisionResolver = require("JPE/CollisionResolver"); 6 | var RigidItem = require("JPE/RigidItem"); 7 | var RectangleParticle = require("JPE/RectangleParticle"); 8 | var CircleParticle = require("JPE/CircleParticle"); 9 | var RigidRectangle = require("JPE/RigidRectangle"); 10 | var RigidCircle = require("JPE/RigidCircle"); 11 | var RigidCollisionResolver = require("JPE/RigidCollisionResolver"); 12 | 13 | var CollisionDetector = { 14 | 15 | cpa: null, 16 | cpb: null, 17 | hitpoint: [], 18 | hp: new Vector(), 19 | collNormal: null, 20 | collDepth: null, 21 | /** 22 | * Tests the collision between two objects. If there is a collision 23 | * it is passsed off to the CollisionResolver class. 24 | * @param objA {AbstractParticle} 25 | * @param objB {AbstractParticle} 26 | */ 27 | test: function(objA, objB) { 28 | if (objA.getFixed() && objB.getFixed()) return; 29 | if (objA.getMultisample() == 0 && objB.getMultisample() == 0) { 30 | this.normVsNorm(objA, objB); 31 | } else if (objA.getMultisample() > 0 && objB.getMultisample() == 0) { 32 | this.sampVsNorm(objA, objB); 33 | } else if (objB.getMultisample() > 0 && objA.getMultisample() == 0) { 34 | this.sampVsNorm(objB, objA); 35 | } else if (objA.getMultisample() == objB.getMultisample()) { 36 | this.sampVsSamp(objA, objB); 37 | } else { 38 | this.normVsNorm(objA, objB); 39 | } 40 | }, 41 | /** 42 | * default test for two non-multisampled particles 43 | */ 44 | normVsNorm: function(objA, objB) { 45 | objA.samp.copy(objA.curr); 46 | objB.samp.copy(objB.curr); 47 | if (this.testTypes(objA, objB)) { 48 | CollisionResolver.resolve(this.cpa, this.cpb, this.collNormal, this.collDepth); 49 | return true; 50 | } 51 | return false; 52 | }, 53 | 54 | /** 55 | * Tests two particles where one is multisampled and the 56 | * other is not. Let objectA be the mulitsampled particle. 57 | */ 58 | sampVsNorm: function(objA, objB) { 59 | 60 | if (this.normVsNorm(objA, objB)) return; 61 | 62 | var objAsamples = objA.getMultisample(), 63 | s = 1 / (objAsamples + 1), 64 | t = s, 65 | i; 66 | //objB.samp.copy(objB.curr); 67 | 68 | for (i = 0; i <= objAsamples; i++) { 69 | objA.samp.setTo(objA.prev.x + t * (objA.curr.x - objA.prev.x), 70 | objA.prev.y + t * (objA.curr.y - objA.prev.y)); 71 | if (this.testTypes(objA, objB)) { 72 | CollisionResolver.resolve(this.cpa, this.cpb, this.collNormal, this.collDepth); 73 | return; 74 | } 75 | t += s; 76 | } 77 | 78 | }, 79 | /** 80 | * Tests two particles where both are of equal multisample rate 81 | */ 82 | sampVsSamp: function(objA, objB) { 83 | 84 | if (this.normVsNorm(objA, objB)) return; 85 | 86 | var objAsamples = objA.getMultisample(), 87 | s = 1 / (objAsamples + 1), 88 | t = s, 89 | i; 90 | 91 | for (i = 0; i <= objAsamples; i++) { 92 | objA.samp.setTo(objA.prev.x + t * (objA.curr.x - objA.prev.x), 93 | objA.prev.y + t * (objA.curr.y - objA.prev.y)); 94 | objB.samp.setTo(objB.prev.x + t * (objB.curr.x - objB.prev.x), 95 | objB.prev.y + t * (objB.curr.y - objB.prev.y)); 96 | if (this.testTypes(objA, objB)) { 97 | CollisionResolver.resolve(this.cpa, this.cpb, this.collNormal, this.collDepth); 98 | return; 99 | } 100 | t += s; 101 | } 102 | }, 103 | testTypes: function(objA, objB) { 104 | 105 | 106 | if (objA instanceof RigidItem && objB instanceof RigidItem) { 107 | this.testTypes2(objA, objB); 108 | return false; 109 | } 110 | 111 | if ((objA instanceof RectangleParticle) && (objB instanceof RectangleParticle)) { 112 | var r = this.testOBBvsOBB(objA, objB); 113 | return r; 114 | } else if ((objA instanceof CircleParticle) && (objB instanceof CircleParticle)) { 115 | return this.testCirclevsCircle(objA, objB); 116 | } else if ((objA instanceof RectangleParticle) && (objB instanceof CircleParticle)) { 117 | return this.testOBBvsCircle(objA, objB); 118 | } else if ((objA instanceof CircleParticle) && (objB instanceof RectangleParticle)) { 119 | return this.testOBBvsCircle(objB, objA); 120 | } 121 | return false; 122 | }, 123 | testTypes2: function(objA, objB) { 124 | var result = false; 125 | var result2 = false; 126 | this.hitpoint = []; 127 | 128 | 129 | if (objA instanceof RigidRectangle && objB instanceof RigidRectangle) { 130 | result = this.testOBBvsOBB(objA, objB); 131 | if (result) { 132 | result2 = this.findHitPointRR(objA, objB); 133 | } 134 | } else if (objA instanceof RigidCircle && objB instanceof RigidCircle) { 135 | result = this.testCirclevsCircle(objA, objB); 136 | if (result) { 137 | result2 = this.findHitPointCC(objA, objB); 138 | } 139 | } else if (objA instanceof RigidRectangle && objB instanceof RigidCircle) { 140 | result = this.testOBBvsCircle(objA, objB); 141 | if (result) { 142 | result2 = this.findHitPointRC(objA, objB); 143 | } 144 | } else if (objA instanceof RigidCircle && objB instanceof RigidRectangle) { 145 | result = this.testOBBvsCircle(objB, objA); 146 | if (result) { 147 | result2 = this.findHitPointRC(objB, objA); 148 | if (result2) { 149 | this.getHP(); 150 | RigidCollisionResolver.resolve(objB, objA, this.hp, this.collNormal, this.collDepth); 151 | return false; 152 | } 153 | } 154 | } 155 | if (result2) { 156 | this.getHP(); 157 | RigidCollisionResolver.resolve(objA, objB, this.hp, this.collNormal, this.collDepth); 158 | return false; 159 | } else { 160 | return result; 161 | } 162 | }, 163 | getHP: function() { 164 | this.hp = new Vector(); 165 | for (var i = 0; i < this.hitpoint.length; i++) { 166 | this.hp.plusEquals(this.hitpoint[i]); 167 | } 168 | if (this.hitpoint.length > 1) { 169 | this.hp.multEquals(1 / this.hitpoint.length); 170 | } 171 | }, 172 | captures: function(r, vertices) { 173 | var re = false; 174 | for (var i = 0; i < vertices.length; i++) { 175 | if (r.captures(vertices[i])) { 176 | this.hitpoint.push(vertices[i]); 177 | re = true; 178 | } 179 | } 180 | return re; 181 | }, 182 | findHitPointRR: function(a, b) { 183 | var r = false; 184 | if (this.captures(a, b.getVertices())) { 185 | r = true; 186 | } 187 | if (this.captures(b, a.getVertices())) { 188 | r = true; 189 | } 190 | return r; 191 | }, 192 | findHitPointRC: function(a, b) { 193 | var r = false; 194 | if (this.captures(b, a.getVertices())) { 195 | r = true; 196 | } 197 | if (this.captures(a, b.getVertices(a.getNormals()))) { 198 | r = true; 199 | } 200 | return r; 201 | }, 202 | findHitPointCC: function(a, b) { 203 | var d = b.samp.minus(a.samp); 204 | if (d.magnitude() <= (a.range + b.range)) { 205 | this.hitpoint.push(d.normalize().multEquals(a.range).plusEquals(a.samp)); 206 | return true; 207 | } else { 208 | return false; 209 | } 210 | }, 211 | /** 212 | * Tests the collision between two RectangleParticles (aka OBBs). 213 | * If there is a collision it determines its axis and depth, and 214 | * then passes it off to the CollisionResolver for handling. 215 | * 216 | * @param ra {RectangleParticle} 217 | * @param rb {RectangleParticle} 218 | */ 219 | testOBBvsOBB: function(ra, rb) { 220 | 221 | this.collDepth = POSITIVE_INFINITY; 222 | 223 | for (var i = 0; i < 2; i++) { 224 | var axisA = ra.getAxes()[i], 225 | rai = ra.getProjection(axisA), 226 | rbi = rb.getProjection(axisA), 227 | depthA = this.testIntervals(rai, rbi); 228 | 229 | if (depthA == 0) return false; 230 | 231 | var axisB = rb.getAxes()[i], 232 | depthB = this.testIntervals(ra.getProjection(axisB), rb.getProjection(axisB)); 233 | 234 | if (depthB == 0) return false; 235 | 236 | var absA = Math.abs(depthA), 237 | absB = Math.abs(depthB); 238 | 239 | if (absA < Math.abs(this.collDepth) || absB < Math.abs(this.collDepth)) { 240 | var altb = absA < absB; 241 | this.collNormal = altb ? axisA : axisB; 242 | this.collDepth = altb ? depthA : depthB; 243 | } 244 | 245 | } 246 | 247 | this.cpa = ra; 248 | this.cpb = rb; 249 | return true; 250 | }, 251 | /** 252 | * Tests the collision between a RectangleParticle (aka an OBB) and a 253 | * CircleParticle. If thereis a collision it determines its axis and 254 | * depth, and then passws it off to the CollisionResolver. 255 | * @param ra {RectangleParticle} 256 | * @param ca {CircleParticle} 257 | */ 258 | testOBBvsCircle: function(ra, ca) { 259 | this.collDepth = POSITIVE_INFINITY; 260 | var depths = new Array(2), 261 | i = 0, 262 | boxAxis, 263 | depth, 264 | r; 265 | 266 | for (; i < 2; i++) { 267 | boxAxis = ra.getAxes()[i]; 268 | depth = this.testIntervals(ra.getProjection(boxAxis), ca.getProjection(boxAxis)); 269 | if (depth == 0) return false; 270 | 271 | if (Math.abs(depth) < Math.abs(this.collDepth)) { 272 | this.collNormal = boxAxis; 273 | this.collDepth = depth; 274 | } 275 | depths[i] = depth; 276 | } 277 | 278 | r = ca.getRadius(); 279 | 280 | if (Math.abs(depths[0]) < r && Math.abs(depths[1]) < r) { 281 | var vertex = this.closestVertexOnOBB(ca.samp, ra); 282 | 283 | this.collNormal = vertex.minus(ca.samp); 284 | var mag = this.collNormal.magnitude(); 285 | this.collDepth = r - mag; 286 | if (this.collDepth > 0) { 287 | this.collNormal.divEquals(mag); 288 | } else { 289 | return false; 290 | } 291 | } 292 | this.cpa = ra; 293 | this.cpb = ca; 294 | return true; 295 | }, 296 | /** 297 | * Tests the colision between two CircleParticles. 298 | * If there is a collision it determines its axis and depth, 299 | * and then passes it off to the CollisionResolver for handing. 300 | */ 301 | testCirclevsCircle: function(ca, cb) { 302 | var depthX = this.testIntervals(ca.getIntervalX(), cb.getIntervalX()); 303 | if (depthX == 0) return false; 304 | 305 | var depthY = this.testIntervals(ca.getIntervalY(), cb.getIntervalY()); 306 | if (depthY == 0) return false; 307 | 308 | this.collNormal = ca.samp.minus(cb.samp); 309 | var mag = this.collNormal.magnitude(); 310 | this.collDepth = ca.getRadius() + cb.getRadius() - mag; 311 | 312 | if (this.collDepth > 0) { 313 | this.collNormal.divEquals(mag); 314 | this.cpa = ca; 315 | this.cpb = cb; 316 | return true; 317 | } 318 | return false; 319 | }, 320 | /** 321 | * Return 0 if the intervals do not overlap. 322 | * Return smallest depth if they do. 323 | * @param intervalA {Interval} 324 | * @param intervalB {Interval} 325 | */ 326 | testIntervals: function(intervalA, intervalB) { 327 | if (intervalA.max < intervalB.min) return 0; 328 | if (intervalB.max < intervalA.min) return 0; 329 | 330 | var lenA = intervalB.max - intervalA.min, 331 | lenB = intervalB.min - intervalA.max; 332 | 333 | return (Math.abs(lenA) < Math.abs(lenB)) ? lenA : lenB; 334 | }, 335 | /** 336 | * Returns the location of the closest vertex on r to point p 337 | * @param p {Vector} 338 | * @param r {RectangleParticle} 339 | */ 340 | closestVertexOnOBB: function(p, r) { 341 | var d = p.minus(r.samp), 342 | q = new Vector(r.samp.x, r.samp.y), 343 | i = 0; 344 | 345 | for (; i < 2; i++) { 346 | var dist = d.dot(r.getAxes()[i]); 347 | if (dist >= 0) { 348 | dist = r.getExtents()[i]; 349 | } else if (dist < 0) { 350 | dist = -r.getExtents()[i]; 351 | } 352 | q.plusEquals(r.getAxes()[i].mult(dist)); 353 | } 354 | return q; 355 | } 356 | }; 357 | 358 | module.exports = CollisionDetector; 359 | 360 | }); -------------------------------------------------------------------------------- /examples/Domino/Domino.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 http://colorhook.com 3 | * @author: colorhook 4 | */ 5 | 6 | /** 7 | * Provides requestAnimationFrame in a cross browser way. 8 | * @see https://gist.github.com/838785 9 | */ 10 | if ( !window.requestAnimationFrame ) { 11 | window.requestAnimationFrame = ( function() { 12 | return window.webkitRequestAnimationFrame || 13 | window.mozRequestAnimationFrame || 14 | window.oRequestAnimationFrame || 15 | window.msRequestAnimationFrame || 16 | function(callback, element ) { 17 | window.setTimeout( callback, 1000/60); 18 | }; 19 | } )(); 20 | } 21 | 22 | define("Domino", function(require, exports, module){ 23 | 24 | var JPE = require("JPE/JPE"); 25 | var Engine = require("JPE/Engine"); 26 | var Vector = require("JPE/Vector"); 27 | var Signal = require("JPE/Signal"); 28 | var EaselRenderer = require("JPE/EaselRenderer"); 29 | var Group = require("JPE/Group"); 30 | var CircleParticle = require("JPE/CircleParticle"); 31 | 32 | //数据模型 33 | var GameModel = { 34 | levelData:[[1, 5],[2, 10],[4,15],[6,20],[10,25],[15,30],[18,35],[22,40],[30,45],[37,50],[48,55],[54,60]], 35 | level: 0, 36 | levelScore: 0, 37 | totalScore: 0, 38 | maxRadius: 40, 39 | chainCount: 0, 40 | growing: false, 41 | mouseEnabled: false, 42 | ballRadius: 10, 43 | welcomeRadius: 60, 44 | collisionSignal: new Signal(), 45 | growStartSignal: new Signal(), 46 | growOverSignal: new Signal(), 47 | levelChangedSignal: new Signal(), 48 | mouseSignal: new Signal(), 49 | computeScore: function(count){ 50 | return 100 * count; 51 | }, 52 | BEST_SCORE_KEY: 'domino:best_score', 53 | init: function(){ 54 | this.level = 0; 55 | this.chainCount = 0; 56 | this.levelScore = 0; 57 | this.totalScore = 0; 58 | this.mouseEnabled = false; 59 | this.bestScore = localStorage.getItem(this.BEST_SCORE_KEY) || 0; 60 | }, 61 | saveScore: function(score){ 62 | localStorage.setItem(this.BEST_SCORE_KEY, score) 63 | } 64 | } 65 | 66 | /** 67 | * 游戏中的小球 68 | */ 69 | var Ball = JPE.newClass(null, CircleParticle, { 70 | 71 | update: function(){ 72 | CircleParticle.prototype.update.apply(this, arguments); 73 | var pos = this.getPosition(), 74 | vel = this.getVelocity(), 75 | r = this.getRadius(), 76 | model = GameModel, 77 | frame = model.frame; 78 | if((pos.x-r) < frame.x || (pos.x+r) > frame.width){ 79 | vel.x *= -1; 80 | } 81 | if((pos.y-r) < frame.y || (pos.y+r) > frame.height){ 82 | vel.y *= -1; 83 | } 84 | this.setVelocity(vel); 85 | } 86 | }); 87 | 88 | /** 89 | * 碰到小球后能变大的球 90 | */ 91 | var GrowBall = JPE.newClass(function(minRadius, maxRadius){ 92 | this.minRadius = minRadius; 93 | this.maxRadius = maxRadius; 94 | this.destroySignal = new Signal(); 95 | CircleParticle.prototype.constructor.apply(this, null); 96 | this.setRadius(GameModel.ballRadius); 97 | }, CircleParticle, { 98 | 99 | minRadius: null, 100 | maxRadius: null, 101 | growed:false, 102 | inHolding: false, 103 | update: function(){ 104 | var self = this; 105 | r = this.getRadius(); 106 | if(this.growed && !this.inHolding){ 107 | if(r > (this.minRadius)){ 108 | this.setRadius(r -1); 109 | }else{ 110 | this.setVisible(false); 111 | this.destroySignal.dispatch(); 112 | } 113 | }else if(!this.growed){ 114 | if(r < (this.maxRadius-0.1)){ 115 | this.setRadius(r+ (this.maxRadius-r)*0.15); 116 | }else{ 117 | this.growed = true; 118 | this.inHolding = true; 119 | setTimeout(function(){ 120 | self.inHolding = false; 121 | }, 2000); 122 | } 123 | } 124 | CircleParticle.prototype.update.call(this, arguments); 125 | } 126 | }); 127 | 128 | /** 129 | * 小球的Group 130 | */ 131 | var BallGroup = JPE.newClass(null, Group, { 132 | 133 | maxSpeed: 1.6, 134 | 135 | initializeBalls: function(count, radius){ 136 | this.clearBalls(); 137 | for(var i = 0; i < count; i++){ 138 | this.addBall(radius); 139 | } 140 | }, 141 | getRandomColor: function(){ 142 | return Math.random() * 0xffffff; 143 | }, 144 | addBall: function(radius){ 145 | var frame = GameModel.frame, 146 | width = frame.width, 147 | height = frame.height, 148 | speed = this.maxSpeed, 149 | r = radius || GameModel.ballRadius, 150 | randomColor = this.getRandomColor(), 151 | px = 2*r + Math.random()*(width-4*r), 152 | py = 2*r + Math.random()*(height-4*r), 153 | sx = Math.random()*speed*2 - speed, 154 | sy = Math.sqrt(speed* speed - sx * sx) * (Math.random()>0.5?1:-1), 155 | vel = new Vector(sx, sy), 156 | ball = new Ball(px, py, r); 157 | 158 | ball.setFill(randomColor); 159 | ball.setVelocity(vel); 160 | ball.setCollidable(false); 161 | this.addParticle(ball); 162 | }, 163 | clearBalls: function(){ 164 | while(this.particles.length > 0){ 165 | this.removeParticle(this.particles[0]); 166 | } 167 | } 168 | }); 169 | 170 | /** 171 | * 大球的Group 172 | */ 173 | var EffectGroup = JPE.newClass(null, Group, { 174 | 175 | minRadius: 1, 176 | maxRadius: GameModel.maxRadius, 177 | duration: 5000, 178 | timer: null, 179 | 180 | addGrow: function(options){ 181 | var self = this, 182 | ball = new GrowBall(this.minRadius, this.maxRadius); 183 | 184 | ball.setPosition(new Vector(options.x, options.y)); 185 | ball.setFill(options.color, 0.8); 186 | ball.setCollidable(false); 187 | ball.destroySignal.add(function(){ 188 | setTimeout(function(){ 189 | self.removeParticle(ball); 190 | if(self.particles.length == 0){ 191 | GameModel.growing = false; 192 | GameModel.growOverSignal.dispatch(); 193 | } 194 | }, 0); 195 | }); 196 | this.addParticle(ball); 197 | }, 198 | clearBalls: function(){ 199 | while(this.particles.length > 0){ 200 | this.removeParticle(this.particles[0]); 201 | } 202 | } 203 | }); 204 | 205 | 206 | /** 207 | * Domino Game 208 | */ 209 | var Domino = JPE.newClass(function(){ 210 | 211 | var self = this, 212 | canvas = this.canvas = document.getElementById("myCanvas"), 213 | stage = this.stage = new Stage(canvas), 214 | width = this.width = canvas.clientWidth, 215 | height = this.height = canvas.clientHeight; 216 | 217 | Engine.init(1/4); 218 | 219 | GameModel.frame = { 220 | x: 0, 221 | y: 0, 222 | width: width, 223 | height: height 224 | } 225 | // set the renderer to easel renderer 226 | Engine.renderer = new EaselRenderer(stage); 227 | 228 | var ballGroup = this.ballGroup = new BallGroup(); 229 | Engine.addGroup(ballGroup); 230 | 231 | var effectGroup = this.effectGroup = new EffectGroup(); 232 | Engine.addGroup(effectGroup); 233 | this.init(); 234 | 235 | }, null, { 236 | 237 | init: function(){ 238 | var self = this, 239 | animate = function(){ 240 | window.requestAnimationFrame(function(){ 241 | if(self.running){ 242 | self.update(); 243 | } 244 | animate(); 245 | }); 246 | } 247 | animate(); 248 | this.addEvents(); 249 | }, 250 | addEvents: function(){ 251 | var globalXY = { 252 | x:this.canvas.offsetParent.offsetLeft, 253 | y:this.canvas.offsetParent.offsetTop 254 | }; 255 | var onClicked = function(e){ 256 | if(!GameModel.mouseEnabled){ 257 | return; 258 | } 259 | GameModel.growing = true; 260 | GameModel.mouseEnabled = false; 261 | GameModel.mouseSignal.dispatch({ 262 | x: e.clientX - globalXY.x, 263 | y: e.clientY - globalXY.y 264 | }); 265 | 266 | GameModel.growStartSignal.dispatch(); 267 | } 268 | this.canvas.addEventListener("click", onClicked, false); 269 | this.canvas.addEventListener("touch", onClicked, false); 270 | }, 271 | addGrow: function(options){ 272 | this.effectGroup.addGrow(options); 273 | }, 274 | start: function(level){ 275 | level = level || 0; 276 | var levelData = GameModel.levelData[level]; 277 | GameModel.level = level; 278 | GameModel.chainCount = 0; 279 | GameModel.mouseEnabled = true; 280 | this.ballGroup.initializeBalls(levelData[1]); 281 | this.running = true; 282 | }, 283 | welcome: function(){ 284 | this.ballGroup.initializeBalls(3, GameModel.welcomeRadius); 285 | this.running = true; 286 | }, 287 | isCollision: function(p1, p2){ 288 | var r1 = p1.getRadius(), 289 | r2 = p2.getRadius(), 290 | p1x = p1.getPx(), 291 | p1y = p1.getPy(), 292 | p2x = p2.getPx(), 293 | p2y = p2.getPy(), 294 | dx = p1x - p2x, 295 | dy = p1y - p2y, 296 | d = r1+r2; 297 | 298 | return (dx*dx + dy*dy) <= d * d; 299 | }, 300 | checkCollision: function(){ 301 | var balls = this.ballGroup.particles, 302 | effectBalls = this.effectGroup.particles, 303 | p1, 304 | p2, 305 | temp = []; 306 | 307 | for(var i = 0, l = balls.length; i= requiredCount); 506 | 507 | if(passed){ 508 | gameModel.levelScore = gameModel.computeScore(chainCount); 509 | gameModel.totalScore += gameModel.levelScore; 510 | if(gameModel.level == gameModel.levelData.length-1){ 511 | var bestScore = gameModel.bestScore; 512 | if(gameModel.totalScore > bestScore){ 513 | gameModel.saveScore(gameModel.totalScore); 514 | } 515 | gameView.showGameEndPage({ 516 | totalScore: gameModel.totalScore 517 | }); 518 | return; 519 | }else{ 520 | gameModel.level++; 521 | } 522 | } 523 | gameView.showLevelEndPage({ 524 | passed: passed, 525 | chainCount: chainCount, 526 | requiredCount: requiredCount, 527 | totalScore: gameModel.totalScore 528 | }); 529 | }); 530 | gameModel.collisionSignal.add(updateViewState); 531 | //view signal 532 | gameView.enterGameSignal.add(function(){ 533 | gameModel.ballRadius = 10; 534 | showLevelAt(0); 535 | }); 536 | gameView.levelEndSignal.add(function(){ 537 | showLevelAt(gameModel.level); 538 | }); 539 | gameView.levelStartSignal.add(function(){ 540 | domino.start(gameModel.level); 541 | updateViewState(); 542 | }); 543 | gameView.gameOverSignal.add(function(){ 544 | gameModel.init(); 545 | gameView.showGameStartPage({bestScore: gameModel.bestScore}); 546 | domino.welcome(); 547 | }); 548 | gameView.gameOverSignal.dispatch(); 549 | } 550 | 551 | }); 552 | 553 | 554 | 555 | --------------------------------------------------------------------------------