├── .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 |
24 | 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 |
17 | Press UP key to drop more objects
20 | 21 |
26 | 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 |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 |
135 | 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 | *
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 | * Theupdate() 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