diff --git a/11.风扇.html b/11.风扇.html index 526ebe9..2f20258 100644 --- a/11.风扇.html +++ b/11.风扇.html @@ -32,8 +32,8 @@ stroke("rgba(128,128,255,0.8)"); fill("rgba(128,128,255,0.8)"); beginShape(); - for (let θ = 0; θ < 360; θ+=3) { - let d = r * sin(θ * 3); + for (let θ = 0; θ < 360; θ += 3) { + let d = r * sin(θ * 2); vertex(cos(θ) * d, -sin(θ) * d); } endShape(CLOSE); diff --git a/README.md b/README.md index 62815c8..9bc5579 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,29 @@ # p5js 学习仓库 +## 待做 + +- [分形树1](https://www.youtube.com/watch?v=0jjeOYMjmDU) +- [分形树5](https://www.youtube.com/watch?v=JcopTKXt8L8) +- [最小跨越树](https://www.youtube.com/watch?v=BxabnKrOjT0) +- [迷宫](https://www.youtube.com/watch?v=HyK_Q5rrcr4) +- [三维地形](https://www.youtube.com/watch?v=IKB1hWWedMk) +- [洛伦兹吸引子](https://www.youtube.com/watch?v=f0lkz2gSsIk) +- [反应扩散算法](https://www.youtube.com/watch?v=BV9ny785UNc) +- [超级椭圆](https://www.youtube.com/watch?v=z86cx2A4_3E) +- [3D织物](https://www.youtube.com/watch?v=jrk_lOg_pVA) +- [曼波集](https://www.youtube.com/watch?v=6z7GQewK-Ks) +- [茱莉亚集](https://www.youtube.com/watch?v=fAsaSkmbF5s) +- [合成器](https://www.youtube.com/watch?v=Bk8rLzzSink) +- [焰火](https://www.youtube.com/watch?v=ATWOjXdRIoo) +- [焰火2](https://www.youtube.com/watch?v=CKeyIbT3vXI) +- [计算机视觉](https://www.youtube.com/watch?v=nCVZHROb_dE) +- [WEBGL](https://www.youtube.com/watch?v=BrFZ5RkywcY) +- [遗传算法](https://www.youtube.com/watch?v=bGz7mv2vD6g) +- [Phyllotaxis](https://www.youtube.com/watch?v=KWoJgHFYWxY) +- [Flappy Bird and Agar.io Clones](https://www.youtube.com/watch?v=eOii4P7WYbY) +- [遗传算法:连续进化系统](https://www.youtube.com/watch?v=Sx_l2GxBC5w) +- [邮差算法](https://www.youtube.com/watch?v=r_SpBy9fQuo) + ## 链接 - [p5js官方网站](https://p5js.org/zh-Hans/) diff --git a/TEST.html b/TEST.html index 85b5b22..75e6307 100644 --- a/TEST.html +++ b/TEST.html @@ -5,19 +5,34 @@ - 风扇 + 测试 diff --git a/lib/box2d-js/LICENSE.txt b/lib/box2d-js/LICENSE.txt new file mode 100644 index 0000000..9af2a7f --- /dev/null +++ b/lib/box2d-js/LICENSE.txt @@ -0,0 +1,14 @@ +The zlib/libpng License + +Copyright (c) 2008 ANDO Yasushi + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + diff --git a/lib/box2d-js/README.txt b/lib/box2d-js/README.txt new file mode 100644 index 0000000..3b77d0f --- /dev/null +++ b/lib/box2d-js/README.txt @@ -0,0 +1,3 @@ +How to use +1. put js/ and lib/ in your app dir +2. add script tags refering the header of index.html diff --git a/lib/box2d-js/demos/compound.js b/lib/box2d-js/demos/compound.js new file mode 100644 index 0000000..2e72b90 --- /dev/null +++ b/lib/box2d-js/demos/compound.js @@ -0,0 +1,57 @@ +demos.compound = {}; +demos.compound.createCompoundBall = function(world, x, y) { + var ballSd1 = new b2CircleDef(); + ballSd1.density = 1.0; + ballSd1.radius = 20; + ballSd1.restitution = 0.2; + ballSd1.localPosition.Set(-20, 0); + var ballSd2 = new b2CircleDef(); + ballSd2.density = 1.0; + ballSd2.radius = 20; + ballSd2.restitution = 0.2; + ballSd2.localPosition.Set(20, 0); + var ballBd = new b2BodyDef(); + ballBd.AddShape(ballSd1); + ballBd.AddShape(ballSd2); + ballBd.position.Set(x, y); + return world.CreateBody(ballBd); +} + +demos.compound.createCompoundPoly = function(world, x, y) { + var points = [[-30, 0], [30, 0], [0, 15]]; + var polySd1 = new b2PolyDef(); + polySd1.vertexCount = points.length; + for (var i = 0; i < points.length; i++) { + polySd1.vertices[i].Set(points[i][0], points[i][1]); + } + polySd1.localRotation = 0.3524 * Math.PI; + var R1 = new b2Mat22(polySd1.localRotation); + polySd1.localPosition = b2Math.b2MulMV(R1, new b2Vec2(30, 0)); + polySd1.density = 1.0; + var polySd2 = new b2PolyDef(); + polySd2.vertexCount = points.length; + for (var i = 0; i < points.length; i++) { + polySd2.vertices[i].Set(points[i][0], points[i][1]); + } + polySd2.localRotation = -0.3524 * Math.PI; + var R2 = new b2Mat22(polySd2.localRotation); + polySd2.localPosition = b2Math.b2MulMV(R2, new b2Vec2(-30, 0)); + var polyBd = new b2BodyDef(); + polyBd.AddShape(polySd1); + polyBd.AddShape(polySd2); + polyBd.position.Set(x,y); + return world.CreateBody(polyBd) +} + +demos.compound.initWorld = function(world) { + var i; + for (i = 1; i <= 4; i++) { + demos.compound.createCompoundPoly(world, 150 + 3 * Math.random(), 40 * i); + } + for (i = 1; i <= 4; i++) { + demos.compound.createCompoundBall(world, 350 + 3 * Math.random(), 45 * i); + } +} +demos.InitWorlds.push(demos.compound.initWorld); + + diff --git a/lib/box2d-js/demos/crank.js b/lib/box2d-js/demos/crank.js new file mode 100644 index 0000000..fbf7e66 --- /dev/null +++ b/lib/box2d-js/demos/crank.js @@ -0,0 +1,69 @@ +demos.crank = {}; +demos.crank.initWorld = function(world) { + var ground = world.m_groundBody; + + // Define crank. + var sd = new b2BoxDef(); + sd.extents.Set(5, 25); + sd.density = 1.0; + + var bd = new b2BodyDef(); + bd.AddShape(sd); + + var rjd = new b2RevoluteJointDef(); + + var prevBody = ground; + + bd.position.Set(500/2, 210); + var body = world.CreateBody(bd); + + rjd.anchorPoint.Set(500/2, 235); + rjd.body1 = prevBody; + rjd.body2 = body; + rjd.motorSpeed = -1.0 * Math.PI; + rjd.motorTorque = 500000000.0; + rjd.enableMotor = true; + world.CreateJoint(rjd); + + prevBody = body; + + // Define follower. + sd.extents.Set(5, 45); + bd.position.Set(500/2, 140); + body = world.CreateBody(bd); + + rjd.anchorPoint.Set(500/2, 185); + rjd.body1 = prevBody; + rjd.body2 = body; + rjd.enableMotor = false; + world.CreateJoint(rjd); + + prevBody = body; + + // Define piston + sd.extents.Set(20, 20); + bd.position.Set(500/2, 95); + body = world.CreateBody(bd); + + rjd.anchorPoint.Set(500/2, 95); + rjd.body1 = prevBody; + rjd.body2 = body; + world.CreateJoint(rjd); + + var pjd = new b2PrismaticJointDef(); + pjd.anchorPoint.Set(500/2, 95); + pjd.body1 = ground; + pjd.body2 = body; + pjd.axis.Set(0.0, 1.0); + pjd.motorSpeed = 0.0; // joint friction + pjd.motorForce = 100000.0; + pjd.enableMotor = true; + + world.CreateJoint(pjd); + + // Create a payload + sd.density = 2.0; + bd.position.Set(500/2, 10); + world.CreateBody(bd); +} +demos.InitWorlds.push(demos.crank.initWorld); diff --git a/lib/box2d-js/demos/demo_base.js b/lib/box2d-js/demos/demo_base.js new file mode 100644 index 0000000..69a5590 --- /dev/null +++ b/lib/box2d-js/demos/demo_base.js @@ -0,0 +1,48 @@ +function createWorld() { + var worldAABB = new b2AABB(); + worldAABB.minVertex.Set(-1000, -1000); + worldAABB.maxVertex.Set(1000, 1000); + var gravity = new b2Vec2(0, 300); + var doSleep = true; + var world = new b2World(worldAABB, gravity, doSleep); + createGround(world); + createBox(world, 0, 125, 10, 250); + createBox(world, 500, 125, 10, 250); + return world; +} + +function createGround(world) { + var groundSd = new b2BoxDef(); + groundSd.extents.Set(1000, 50); + groundSd.restitution = 0.2; + var groundBd = new b2BodyDef(); + groundBd.AddShape(groundSd); + groundBd.position.Set(-500, 340); + return world.CreateBody(groundBd) +} + +function createBall(world, x, y) { + var ballSd = new b2CircleDef(); + ballSd.density = 1.0; + ballSd.radius = 20; + ballSd.restitution = 1.0; + ballSd.friction = 0; + var ballBd = new b2BodyDef(); + ballBd.AddShape(ballSd); + ballBd.position.Set(x,y); + return world.CreateBody(ballBd); +} + +function createBox(world, x, y, width, height, fixed) { + if (typeof(fixed) == 'undefined') fixed = true; + var boxSd = new b2BoxDef(); + if (!fixed) boxSd.density = 1.0; + boxSd.extents.Set(width, height); + var boxBd = new b2BodyDef(); + boxBd.AddShape(boxSd); + boxBd.position.Set(x,y); + return world.CreateBody(boxBd) +} + +var demos = {}; +demos.InitWorlds = []; diff --git a/lib/box2d-js/demos/demos.js b/lib/box2d-js/demos/demos.js new file mode 100644 index 0000000..93643eb --- /dev/null +++ b/lib/box2d-js/demos/demos.js @@ -0,0 +1,49 @@ +var initId = 0; +var world = createWorld(); +var ctx; +var canvasWidth; +var canvasHeight; +var canvasTop; +var canvasLeft; + +function setupWorld(did) { + if (!did) did = 0; + world = createWorld(); + initId += did; + initId %= demos.InitWorlds.length; + if (initId < 0) initId = demos.InitWorlds.length + initId; + demos.InitWorlds[initId](world); +} +function setupNextWorld() { setupWorld(1); } +function setupPrevWorld() { setupWorld(-1); } +function step(cnt) { + var stepping = false; + var timeStep = 1.0/60; + var iteration = 1; + world.Step(timeStep, iteration); + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + drawWorld(world, ctx); + setTimeout('step(' + (cnt || 0) + ')', 10); +} +Event.observe(window, 'load', function() { + setupWorld(); + ctx = $('canvas').getContext('2d'); + var canvasElm = $('canvas'); + canvasWidth = parseInt(canvasElm.width); + canvasHeight = parseInt(canvasElm.height); + canvasTop = parseInt(canvasElm.style.top); + canvasLeft = parseInt(canvasElm.style.left); + Event.observe('canvas', 'click', function(e) { + //setupNextWorld(); + if (Math.random() < 0.5) + demos.top.createBall(world, Event.pointerX(e) - canvasLeft, Event.pointerY(e) - canvasTop); + else + createBox(world, Event.pointerX(e) - canvasLeft, Event.pointerY(e) - canvasTop, 10, 10, false); + }); + Event.observe('canvas', 'contextmenu', function(e) { + if (e.preventDefault) e.preventDefault(); + setupPrevWorld(); + return false; + }); + step(); +}); diff --git a/lib/box2d-js/demos/draw_world.js b/lib/box2d-js/demos/draw_world.js new file mode 100644 index 0000000..3991a64 --- /dev/null +++ b/lib/box2d-js/demos/draw_world.js @@ -0,0 +1,93 @@ +function drawWorld(world, context) { + for (var j = world.m_jointList; j; j = j.m_next) { + drawJoint(j, context); + } + for (var b = world.m_bodyList; b; b = b.m_next) { + for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { + drawShape(s, context); + } + } +} +function drawJoint(joint, context) { + var b1 = joint.m_body1; + var b2 = joint.m_body2; + var x1 = b1.m_position; + var x2 = b2.m_position; + var p1 = joint.GetAnchor1(); + var p2 = joint.GetAnchor2(); + context.strokeStyle = '#00eeee'; + context.beginPath(); + switch (joint.m_type) { + case b2Joint.e_distanceJoint: + context.moveTo(p1.x, p1.y); + context.lineTo(p2.x, p2.y); + break; + + case b2Joint.e_pulleyJoint: + // TODO + break; + + default: + if (b1 == world.m_groundBody) { + context.moveTo(p1.x, p1.y); + context.lineTo(x2.x, x2.y); + } + else if (b2 == world.m_groundBody) { + context.moveTo(p1.x, p1.y); + context.lineTo(x1.x, x1.y); + } + else { + context.moveTo(x1.x, x1.y); + context.lineTo(p1.x, p1.y); + context.lineTo(x2.x, x2.y); + context.lineTo(p2.x, p2.y); + } + break; + } + context.stroke(); +} +function drawShape(shape, context) { + context.strokeStyle = '#ffffff'; + context.beginPath(); + switch (shape.m_type) { + case b2Shape.e_circleShape: + { + var circle = shape; + var pos = circle.m_position; + var r = circle.m_radius; + var segments = 16.0; + var theta = 0.0; + var dtheta = 2.0 * Math.PI / segments; + // draw circle + context.moveTo(pos.x + r, pos.y); + for (var i = 0; i < segments; i++) { + var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); + var v = b2Math.AddVV(pos, d); + context.lineTo(v.x, v.y); + theta += dtheta; + } + context.lineTo(pos.x + r, pos.y); + + // draw radius + context.moveTo(pos.x, pos.y); + var ax = circle.m_R.col1; + var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); + context.lineTo(pos2.x, pos2.y); + } + break; + case b2Shape.e_polyShape: + { + var poly = shape; + var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); + context.moveTo(tV.x, tV.y); + for (var i = 0; i < poly.m_vertexCount; i++) { + var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); + context.lineTo(v.x, v.y); + } + context.lineTo(tV.x, tV.y); + } + break; + } + context.stroke(); +} + diff --git a/lib/box2d-js/demos/pendulum.js b/lib/box2d-js/demos/pendulum.js new file mode 100644 index 0000000..f7939ae --- /dev/null +++ b/lib/box2d-js/demos/pendulum.js @@ -0,0 +1,20 @@ +demos.pendulum = {}; +demos.pendulum.initWorld = function(world) { + var i; + var ground = world.GetGroundBody(); + var jointDef = new b2RevoluteJointDef(); + var L = 150; + for (i = 0; i < 4; i++) { + jointDef.anchorPoint.Set(250 + 40 * i, 200 - L); + jointDef.body1 = ground; + jointDef.body2 = createBall(world, 250 + 40 * i, 200); + world.CreateJoint(jointDef); + } + jointDef.anchorPoint.Set(250 - 40, 200 - L); + jointDef.body1 = ground; + jointDef.body2 = createBall(world, 250 - 40 - L, 200 - L); + world.CreateJoint(jointDef); +} +demos.InitWorlds.push(demos.pendulum.initWorld); + + diff --git a/lib/box2d-js/demos/stack.js b/lib/box2d-js/demos/stack.js new file mode 100644 index 0000000..65c052e --- /dev/null +++ b/lib/box2d-js/demos/stack.js @@ -0,0 +1,26 @@ +demos.stack = {}; +demos.stack.initWorld = function(world) { + var sd = new b2BoxDef(); + var bd = new b2BodyDef(); + bd.AddShape(sd); + sd.density = 1.0; + sd.friction = 0.5; + sd.extents.Set(10, 10); + + var i; + for (i = 0; i < 8; i++) { + bd.position.Set(500/2-Math.random()*2-1, (250-5-i*22)); + world.CreateBody(bd); + } + for (i = 0; i < 8; i++) { + bd.position.Set(500/2-100-Math.random()*5+i, (250-5-i*22)); + world.CreateBody(bd); + } + for (i = 0; i < 8; i++) { + bd.position.Set(500/2+100+Math.random()*5-i, (250-5-i*22)); + world.CreateBody(bd); + } +} +demos.InitWorlds.push(demos.stack.initWorld); + + diff --git a/lib/box2d-js/demos/top.js b/lib/box2d-js/demos/top.js new file mode 100644 index 0000000..6a33ce6 --- /dev/null +++ b/lib/box2d-js/demos/top.js @@ -0,0 +1,42 @@ +demos.top = {}; +demos.top.createBall = function(world, x, y, rad, fixed) { + var ballSd = new b2CircleDef(); + if (!fixed) ballSd.density = 1.0; + ballSd.radius = rad || 10; + ballSd.restitution = 0.2; + var ballBd = new b2BodyDef(); + ballBd.AddShape(ballSd); + ballBd.position.Set(x,y); + return world.CreateBody(ballBd); +}; +demos.top.createPoly = function(world, x, y, points, fixed) { + var polySd = new b2PolyDef(); + if (!fixed) polySd.density = 1.0; + polySd.vertexCount = points.length; + for (var i = 0; i < points.length; i++) { + polySd.vertices[i].Set(points[i][0], points[i][1]); + } + var polyBd = new b2BodyDef(); + polyBd.AddShape(polySd); + polyBd.position.Set(x,y); + return world.CreateBody(polyBd) +}; +demos.top.initWorld = function(world) { + demos.top.createBall(world, 350, 100, 50, true); + demos.top.createPoly(world, 100, 100, [[0, 0], [10, 30], [-10, 30]], true); + demos.top.createPoly(world, 150, 150, [[0, 0], [10, 30], [-10, 30]], true); + var pendulum = createBox(world, 150, 100, 20, 20, false); + var jointDef = new b2RevoluteJointDef(); + jointDef.body1 = pendulum; + jointDef.body2 = world.GetGroundBody(); + jointDef.anchorPoint = pendulum.GetCenterPosition(); + world.CreateJoint(jointDef); + + var seesaw = demos.top.createPoly(world, 300, 200, [[0, 0], [100, 30], [-100, 30]]); + jointDef.body1 = seesaw; + jointDef.anchorPoint = seesaw.GetCenterPosition(); + world.CreateJoint(jointDef); +}; +demos.InitWorlds.push(demos.top.initWorld); + + diff --git a/lib/box2d-js/index.html b/lib/box2d-js/index.html new file mode 100644 index 0000000..9bff25e --- /dev/null +++ b/lib/box2d-js/index.html @@ -0,0 +1,213 @@ + + + + + Box2DJS - Physics Engine for JavaScript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ +
+

+ Left-click to create an object. + Right-click to show next example. +

+ + +

What's this

+

+ Box2DJS is a JavaScript port of Box2D Physics Engine. + To tell the truth, this is converted from Box2DFlashAS3_1.4.3.1 in an automatic manner. + (The reason why not Box2DFlashAS3_2.0.0 based is simply because I overlooked the renewal.) +

+ + +

How to use

+

+ Because this libray does not have a lazy-loading system now, you should load all classes before starting your simulation. + To make things worse, each library has a bit complecated dependency each other so that loading libraries in wrong order may cause a fatal error. + To avoid such a trouble, it is strongly recomended to copy the header part of this file or `index.html' including the downloaded zip file. +

+

+ Concerning the Box2D APIs of the library, they are completely same as Box2DFlashAS3. Please google information about it. +

+ +

Sample code

+

Create a world

+
var worldAABB = new b2AABB();
+worldAABB.minVertex.Set(-1000, -1000);
+worldAABB.maxVertex.Set(1000, 1000);
+var gravity = new b2Vec2(0, 300);
+var doSleep = true;
+var world = new b2World(worldAABB, gravity, doSleep); 
+ +

Create a circle body

+
var circleSd = new b2CircleDef();
+circleSd.density = 1.0;
+circleSd.radius = 20;
+circleSd.restitution = 1.0;
+circleSd.friction = 0;
+var circleBd = new b2BodyDef();
+circleBd.AddShape(circleSd);
+circleBd.position.Set(x,y);
+var circleBody = world.CreateBody(circleBd);
+ +

Create a revolute joint

+
var jointDef = new b2RevoluteJointDef();
+jointDef.anchorPoint.Set(250, 200);
+jointDef.body1 = world.GetGroundBody();
+jointDef.body2 = circleBody;
+world.CreateJoint(jointDef);
+ +

Step a world

+
world.Step();
+ + +

Dependencies

+
    +
  • prototype.js
  • +
  • IECanvas (when you use a canvas tag to display the result of your physics simulation)
  • +
+ + +

Links

+ + + +

Contact

+

+ If you have some requests or find any bugs, please tell me about them.
+

+ E-mail: andyjpn _at_ gmail _dot_ com
+ Blog: http://d.hatena.ne.jp/technohippy/ (written in Japanese) +
+

+ +
+
+ +
+
+ + diff --git a/lib/box2d-js/js/box2d/collision/ClipVertex.js b/lib/box2d-js/js/box2d/collision/ClipVertex.js new file mode 100644 index 0000000..fcb3df3 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/ClipVertex.js @@ -0,0 +1,35 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var ClipVertex = Class.create(); +ClipVertex.prototype = +{ + v: new b2Vec2(), + id: new b2ContactID(), + initialize: function() { + // initialize instance variables for references + this.v = new b2Vec2(); + this.id = new b2ContactID(); + // +}}; + + diff --git a/lib/box2d-js/js/box2d/collision/Features.js b/lib/box2d-js/js/box2d/collision/Features.js new file mode 100644 index 0000000..773272b --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/Features.js @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + +// We use contact ids to facilitate warm starting. +var Features = Class.create(); +Features.prototype = +{ + // + set_referenceFace: function(value){ + this._referenceFace = value; + this._m_id._key = (this._m_id._key & 0xffffff00) | (this._referenceFace & 0x000000ff) + }, + get_referenceFace: function(){ + return this._referenceFace; + }, + _referenceFace: 0, + // + set_incidentEdge: function(value){ + this._incidentEdge = value; + this._m_id._key = (this._m_id._key & 0xffff00ff) | ((this._incidentEdge << 8) & 0x0000ff00) + }, + get_incidentEdge: function(){ + return this._incidentEdge; + }, + _incidentEdge: 0, + // + set_incidentVertex: function(value){ + this._incidentVertex = value; + this._m_id._key = (this._m_id._key & 0xff00ffff) | ((this._incidentVertex << 16) & 0x00ff0000) + }, + get_incidentVertex: function(){ + return this._incidentVertex; + }, + _incidentVertex: 0, + // + set_flip: function(value){ + this._flip = value; + this._m_id._key = (this._m_id._key & 0x00ffffff) | ((this._flip << 24) & 0xff000000) + }, + get_flip: function(){ + return this._flip; + }, + _flip: 0, + _m_id: null, + initialize: function() {}}; diff --git a/lib/box2d-js/js/box2d/collision/b2AABB.js b/lib/box2d-js/js/box2d/collision/b2AABB.js new file mode 100644 index 0000000..75b8827 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2AABB.js @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +// A manifold for two touching convex shapes. +var b2AABB = Class.create(); +b2AABB.prototype = +{ + IsValid: function(){ + //var d = b2Math.SubtractVV(this.maxVertex, this.minVertex); + var dX = this.maxVertex.x; + var dY = this.maxVertex.y; + dX = this.maxVertex.x; + dY = this.maxVertex.y; + dX -= this.minVertex.x; + dY -= this.minVertex.y; + var valid = dX >= 0.0 && dY >= 0.0; + valid = valid && this.minVertex.IsValid() && this.maxVertex.IsValid(); + return valid; + }, + + minVertex: new b2Vec2(), + maxVertex: new b2Vec2(), + initialize: function() { + // initialize instance variables for references + this.minVertex = new b2Vec2(); + this.maxVertex = new b2Vec2(); + // +}}; diff --git a/lib/box2d-js/js/box2d/collision/b2Bound.js b/lib/box2d-js/js/box2d/collision/b2Bound.js new file mode 100644 index 0000000..b15883a --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2Bound.js @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2Bound = Class.create(); +b2Bound.prototype = { + IsLower: function(){ return (this.value & 1) == 0; }, + IsUpper: function(){ return (this.value & 1) == 1; }, + Swap: function(b){ + var tempValue = this.value; + var tempProxyId = this.proxyId; + var tempStabbingCount = this.stabbingCount; + + this.value = b.value; + this.proxyId = b.proxyId; + this.stabbingCount = b.stabbingCount; + + b.value = tempValue; + b.proxyId = tempProxyId; + b.stabbingCount = tempStabbingCount; + }, + + value: 0, + proxyId: 0, + stabbingCount: 0, + + initialize: function() {}} diff --git a/lib/box2d-js/js/box2d/collision/b2BoundValues.js b/lib/box2d-js/js/box2d/collision/b2BoundValues.js new file mode 100644 index 0000000..db04cdc --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2BoundValues.js @@ -0,0 +1,31 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2BoundValues = Class.create(); +b2BoundValues.prototype = { + lowerValues: [0,0], + upperValues: [0,0], + + initialize: function() { + // initialize instance variables for references + this.lowerValues = [0,0]; + this.upperValues = [0,0]; + // +}} diff --git a/lib/box2d-js/js/box2d/collision/b2BroadPhase.js b/lib/box2d-js/js/box2d/collision/b2BroadPhase.js new file mode 100644 index 0000000..4b7b5d0 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2BroadPhase.js @@ -0,0 +1,898 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +/* +This broad phase uses the Sweep and Prune algorithm in: +Collision Detection in Interactive 3D Environments by Gino van den Bergen +Also, some ideas, such integral values for fast compares comes from +Bullet (http:/www.bulletphysics.com). +*/ + + +// Notes: +// - we use bound arrays instead of linked lists for cache coherence. +// - we use quantized integral values for fast compares. +// - we use short indices rather than pointers to save memory. +// - we use a stabbing count for fast overlap queries (less than order N). +// - we also use a time stamp on each proxy to speed up the registration of +// overlap query results. +// - where possible, we compare bound indices instead of values to reduce +// cache misses (TODO_ERIN). +// - no broadphase is perfect and neither is this one: it is not great for huge +// worlds (use a multi-SAP instead), it is not great for large objects. + +var b2BroadPhase = Class.create(); +b2BroadPhase.prototype = +{ +//public: + initialize: function(worldAABB, callback){ + // initialize instance variables for references + this.m_pairManager = new b2PairManager(); + this.m_proxyPool = new Array(b2Settings.b2_maxPairs); + this.m_bounds = new Array(2*b2Settings.b2_maxProxies); + this.m_queryResults = new Array(b2Settings.b2_maxProxies); + this.m_quantizationFactor = new b2Vec2(); + // + + //b2Settings.b2Assert(worldAABB.IsValid()); + var i = 0; + + this.m_pairManager.Initialize(this, callback); + + this.m_worldAABB = worldAABB; + + this.m_proxyCount = 0; + + // query results + for (i = 0; i < b2Settings.b2_maxProxies; i++){ + this.m_queryResults[i] = 0; + } + + // bounds array + this.m_bounds = new Array(2); + for (i = 0; i < 2; i++){ + this.m_bounds[i] = new Array(2*b2Settings.b2_maxProxies); + for (var j = 0; j < 2*b2Settings.b2_maxProxies; j++){ + this.m_bounds[i][j] = new b2Bound(); + } + } + + //var d = b2Math.SubtractVV(worldAABB.maxVertex, worldAABB.minVertex); + var dX = worldAABB.maxVertex.x; + var dY = worldAABB.maxVertex.y; + dX -= worldAABB.minVertex.x; + dY -= worldAABB.minVertex.y; + + this.m_quantizationFactor.x = b2Settings.USHRT_MAX / dX; + this.m_quantizationFactor.y = b2Settings.USHRT_MAX / dY; + + var tProxy; + for (i = 0; i < b2Settings.b2_maxProxies - 1; ++i) + { + tProxy = new b2Proxy(); + this.m_proxyPool[i] = tProxy; + tProxy.SetNext(i + 1); + tProxy.timeStamp = 0; + tProxy.overlapCount = b2BroadPhase.b2_invalid; + tProxy.userData = null; + } + tProxy = new b2Proxy(); + this.m_proxyPool[b2Settings.b2_maxProxies-1] = tProxy; + tProxy.SetNext(b2Pair.b2_nullProxy); + tProxy.timeStamp = 0; + tProxy.overlapCount = b2BroadPhase.b2_invalid; + tProxy.userData = null; + this.m_freeProxy = 0; + + this.m_timeStamp = 1; + this.m_queryResultCount = 0; + }, + //~b2BroadPhase(); + + // Use this to see if your proxy is in range. If it is not in range, + // it should be destroyed. Otherwise you may get O(m^2) pairs, where m + // is the number of proxies that are out of range. + InRange: function(aabb){ + //var d = b2Math.b2MaxV(b2Math.SubtractVV(aabb.minVertex, this.m_worldAABB.maxVertex), b2Math.SubtractVV(this.m_worldAABB.minVertex, aabb.maxVertex)); + var dX; + var dY; + var d2X; + var d2Y; + + dX = aabb.minVertex.x; + dY = aabb.minVertex.y; + dX -= this.m_worldAABB.maxVertex.x; + dY -= this.m_worldAABB.maxVertex.y; + + d2X = this.m_worldAABB.minVertex.x; + d2Y = this.m_worldAABB.minVertex.y; + d2X -= aabb.maxVertex.x; + d2Y -= aabb.maxVertex.y; + + dX = b2Math.b2Max(dX, d2X); + dY = b2Math.b2Max(dY, d2Y); + + return b2Math.b2Max(dX, dY) < 0.0; + }, + + // Get a single proxy. Returns NULL if the id is invalid. + GetProxy: function(proxyId){ + if (proxyId == b2Pair.b2_nullProxy || this.m_proxyPool[proxyId].IsValid() == false) + { + return null; + } + + return this.m_proxyPool[ proxyId ]; + }, + + // Create and destroy proxies. These call Flush first. + CreateProxy: function(aabb, userData){ + var index = 0; + var proxy; + + //b2Settings.b2Assert(this.m_proxyCount < b2_maxProxies); + //b2Settings.b2Assert(this.m_freeProxy != b2Pair.b2_nullProxy); + + var proxyId = this.m_freeProxy; + proxy = this.m_proxyPool[ proxyId ]; + this.m_freeProxy = proxy.GetNext(); + + proxy.overlapCount = 0; + proxy.userData = userData; + + var boundCount = 2 * this.m_proxyCount; + + var lowerValues = new Array(); + var upperValues = new Array(); + this.ComputeBounds(lowerValues, upperValues, aabb); + + for (var axis = 0; axis < 2; ++axis) + { + var bounds = this.m_bounds[axis]; + var lowerIndex = 0; + var upperIndex = 0; + var lowerIndexOut = [lowerIndex]; + var upperIndexOut = [upperIndex]; + this.Query(lowerIndexOut, upperIndexOut, lowerValues[axis], upperValues[axis], bounds, boundCount, axis); + lowerIndex = lowerIndexOut[0]; + upperIndex = upperIndexOut[0]; + + // Replace memmove calls + //memmove(bounds + upperIndex + 2, bounds + upperIndex, (edgeCount - upperIndex) * sizeof(b2Bound)); + var tArr = new Array(); + var j = 0; + var tEnd = boundCount - upperIndex + var tBound1; + var tBound2; + // make temp array + for (j = 0; j < tEnd; j++){ + tArr[j] = new b2Bound(); + tBound1 = tArr[j]; + tBound2 = bounds[upperIndex+j]; + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + // move temp array back in to bounds + tEnd = tArr.length; + var tIndex = upperIndex+2; + for (j = 0; j < tEnd; j++){ + //bounds[tIndex+j] = tArr[j]; + tBound2 = tArr[j]; + tBound1 = bounds[tIndex+j] + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + //memmove(bounds + lowerIndex + 1, bounds + lowerIndex, (upperIndex - lowerIndex) * sizeof(b2Bound)); + // make temp array + tArr = new Array(); + tEnd = upperIndex - lowerIndex; + for (j = 0; j < tEnd; j++){ + tArr[j] = new b2Bound(); + tBound1 = tArr[j]; + tBound2 = bounds[lowerIndex+j]; + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + // move temp array back in to bounds + tEnd = tArr.length; + tIndex = lowerIndex+1; + for (j = 0; j < tEnd; j++){ + //bounds[tIndex+j] = tArr[j]; + tBound2 = tArr[j]; + tBound1 = bounds[tIndex+j] + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + + // The upper index has increased because of the lower bound insertion. + ++upperIndex; + + // Copy in the new bounds. + bounds[lowerIndex].value = lowerValues[axis]; + bounds[lowerIndex].proxyId = proxyId; + bounds[upperIndex].value = upperValues[axis]; + bounds[upperIndex].proxyId = proxyId; + + bounds[lowerIndex].stabbingCount = lowerIndex == 0 ? 0 : bounds[lowerIndex-1].stabbingCount; + bounds[upperIndex].stabbingCount = bounds[upperIndex-1].stabbingCount; + + // Adjust the stabbing count between the new bounds. + for (index = lowerIndex; index < upperIndex; ++index) + { + bounds[index].stabbingCount++; + } + + // Adjust the all the affected bound indices. + for (index = lowerIndex; index < boundCount + 2; ++index) + { + var proxy2 = this.m_proxyPool[ bounds[index].proxyId ]; + if (bounds[index].IsLower()) + { + proxy2.lowerBounds[axis] = index; + } + else + { + proxy2.upperBounds[axis] = index; + } + } + } + + ++this.m_proxyCount; + + //b2Settings.b2Assert(this.m_queryResultCount < b2Settings.b2_maxProxies); + + for (var i = 0; i < this.m_queryResultCount; ++i) + { + //b2Settings.b2Assert(this.m_queryResults[i] < b2_maxProxies); + //b2Settings.b2Assert(this.m_proxyPool[this.m_queryResults[i]].IsValid()); + + this.m_pairManager.AddBufferedPair(proxyId, this.m_queryResults[i]); + } + + this.m_pairManager.Commit(); + + // Prepare for next query. + this.m_queryResultCount = 0; + this.IncrementTimeStamp(); + + return proxyId; + }, + + DestroyProxy: function(proxyId){ + + //b2Settings.b2Assert(0 < this.m_proxyCount && this.m_proxyCount <= b2_maxProxies); + + var proxy = this.m_proxyPool[ proxyId ]; + //b2Settings.b2Assert(proxy.IsValid()); + + var boundCount = 2 * this.m_proxyCount; + + for (var axis = 0; axis < 2; ++axis) + { + var bounds = this.m_bounds[axis]; + + var lowerIndex = proxy.lowerBounds[axis]; + var upperIndex = proxy.upperBounds[axis]; + var lowerValue = bounds[lowerIndex].value; + var upperValue = bounds[upperIndex].value; + + // replace memmove calls + //memmove(bounds + lowerIndex, bounds + lowerIndex + 1, (upperIndex - lowerIndex - 1) * sizeof(b2Bound)); + var tArr = new Array(); + var j = 0; + var tEnd = upperIndex - lowerIndex - 1; + var tBound1; + var tBound2; + // make temp array + for (j = 0; j < tEnd; j++){ + tArr[j] = new b2Bound(); + tBound1 = tArr[j]; + tBound2 = bounds[lowerIndex+1+j]; + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + // move temp array back in to bounds + tEnd = tArr.length; + var tIndex = lowerIndex; + for (j = 0; j < tEnd; j++){ + //bounds[tIndex+j] = tArr[j]; + tBound2 = tArr[j]; + tBound1 = bounds[tIndex+j] + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + //memmove(bounds + upperIndex-1, bounds + upperIndex + 1, (edgeCount - upperIndex - 1) * sizeof(b2Bound)); + // make temp array + tArr = new Array(); + tEnd = boundCount - upperIndex - 1; + for (j = 0; j < tEnd; j++){ + tArr[j] = new b2Bound(); + tBound1 = tArr[j]; + tBound2 = bounds[upperIndex+1+j]; + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + // move temp array back in to bounds + tEnd = tArr.length; + tIndex = upperIndex-1; + for (j = 0; j < tEnd; j++){ + //bounds[tIndex+j] = tArr[j]; + tBound2 = tArr[j]; + tBound1 = bounds[tIndex+j] + tBound1.value = tBound2.value; + tBound1.proxyId = tBound2.proxyId; + tBound1.stabbingCount = tBound2.stabbingCount; + } + + // Fix bound indices. + tEnd = boundCount - 2; + for (var index = lowerIndex; index < tEnd; ++index) + { + var proxy2 = this.m_proxyPool[ bounds[index].proxyId ]; + if (bounds[index].IsLower()) + { + proxy2.lowerBounds[axis] = index; + } + else + { + proxy2.upperBounds[axis] = index; + } + } + + // Fix stabbing count. + tEnd = upperIndex - 1; + for (var index2 = lowerIndex; index2 < tEnd; ++index2) + { + bounds[index2].stabbingCount--; + } + + // this.Query for pairs to be removed. lowerIndex and upperIndex are not needed. + // make lowerIndex and upper output using an array and do this for others if compiler doesn't pick them up + this.Query([0], [0], lowerValue, upperValue, bounds, boundCount - 2, axis); + } + + //b2Settings.b2Assert(this.m_queryResultCount < b2Settings.b2_maxProxies); + + for (var i = 0; i < this.m_queryResultCount; ++i) + { + //b2Settings.b2Assert(this.m_proxyPool[this.m_queryResults[i]].IsValid()); + + this.m_pairManager.RemoveBufferedPair(proxyId, this.m_queryResults[i]); + } + + this.m_pairManager.Commit(); + + // Prepare for next query. + this.m_queryResultCount = 0; + this.IncrementTimeStamp(); + + // Return the proxy to the pool. + proxy.userData = null; + proxy.overlapCount = b2BroadPhase.b2_invalid; + proxy.lowerBounds[0] = b2BroadPhase.b2_invalid; + proxy.lowerBounds[1] = b2BroadPhase.b2_invalid; + proxy.upperBounds[0] = b2BroadPhase.b2_invalid; + proxy.upperBounds[1] = b2BroadPhase.b2_invalid; + + proxy.SetNext(this.m_freeProxy); + this.m_freeProxy = proxyId; + --this.m_proxyCount; + }, + + + // Call this.MoveProxy times like, then when you are done + // call this.Commit to finalized the proxy pairs (for your time step). + MoveProxy: function(proxyId, aabb){ + var axis = 0; + var index = 0; + var bound; + var prevBound + var nextBound + var nextProxyId = 0; + var nextProxy; + + if (proxyId == b2Pair.b2_nullProxy || b2Settings.b2_maxProxies <= proxyId) + { + //b2Settings.b2Assert(false); + return; + } + + if (aabb.IsValid() == false) + { + //b2Settings.b2Assert(false); + return; + } + + var boundCount = 2 * this.m_proxyCount; + + var proxy = this.m_proxyPool[ proxyId ]; + // Get new bound values + var newValues = new b2BoundValues(); + this.ComputeBounds(newValues.lowerValues, newValues.upperValues, aabb); + + // Get old bound values + var oldValues = new b2BoundValues(); + for (axis = 0; axis < 2; ++axis) + { + oldValues.lowerValues[axis] = this.m_bounds[axis][proxy.lowerBounds[axis]].value; + oldValues.upperValues[axis] = this.m_bounds[axis][proxy.upperBounds[axis]].value; + } + + for (axis = 0; axis < 2; ++axis) + { + var bounds = this.m_bounds[axis]; + + var lowerIndex = proxy.lowerBounds[axis]; + var upperIndex = proxy.upperBounds[axis]; + + var lowerValue = newValues.lowerValues[axis]; + var upperValue = newValues.upperValues[axis]; + + var deltaLower = lowerValue - bounds[lowerIndex].value; + var deltaUpper = upperValue - bounds[upperIndex].value; + + bounds[lowerIndex].value = lowerValue; + bounds[upperIndex].value = upperValue; + + // + // Expanding adds overlaps + // + + // Should we move the lower bound down? + if (deltaLower < 0) + { + index = lowerIndex; + while (index > 0 && lowerValue < bounds[index-1].value) + { + bound = bounds[index]; + prevBound = bounds[index - 1]; + + var prevProxyId = prevBound.proxyId; + var prevProxy = this.m_proxyPool[ prevBound.proxyId ]; + + prevBound.stabbingCount++; + + if (prevBound.IsUpper() == true) + { + if (this.TestOverlap(newValues, prevProxy)) + { + this.m_pairManager.AddBufferedPair(proxyId, prevProxyId); + } + + prevProxy.upperBounds[axis]++; + bound.stabbingCount++; + } + else + { + prevProxy.lowerBounds[axis]++; + bound.stabbingCount--; + } + + proxy.lowerBounds[axis]--; + + // swap + //var temp = bound; + //bound = prevEdge; + //prevEdge = temp; + bound.Swap(prevBound); + //b2Math.b2Swap(bound, prevEdge); + --index; + } + } + + // Should we move the upper bound up? + if (deltaUpper > 0) + { + index = upperIndex; + while (index < boundCount-1 && bounds[index+1].value <= upperValue) + { + bound = bounds[ index ]; + nextBound = bounds[ index + 1 ]; + nextProxyId = nextBound.proxyId; + nextProxy = this.m_proxyPool[ nextProxyId ]; + + nextBound.stabbingCount++; + + if (nextBound.IsLower() == true) + { + if (this.TestOverlap(newValues, nextProxy)) + { + this.m_pairManager.AddBufferedPair(proxyId, nextProxyId); + } + + nextProxy.lowerBounds[axis]--; + bound.stabbingCount++; + } + else + { + nextProxy.upperBounds[axis]--; + bound.stabbingCount--; + } + + proxy.upperBounds[axis]++; + // swap + //var temp = bound; + //bound = nextEdge; + //nextEdge = temp; + bound.Swap(nextBound); + //b2Math.b2Swap(bound, nextEdge); + index++; + } + } + + // + // Shrinking removes overlaps + // + + // Should we move the lower bound up? + if (deltaLower > 0) + { + index = lowerIndex; + while (index < boundCount-1 && bounds[index+1].value <= lowerValue) + { + bound = bounds[ index ]; + nextBound = bounds[ index + 1 ]; + + nextProxyId = nextBound.proxyId; + nextProxy = this.m_proxyPool[ nextProxyId ]; + + nextBound.stabbingCount--; + + if (nextBound.IsUpper()) + { + if (this.TestOverlap(oldValues, nextProxy)) + { + this.m_pairManager.RemoveBufferedPair(proxyId, nextProxyId); + } + + nextProxy.upperBounds[axis]--; + bound.stabbingCount--; + } + else + { + nextProxy.lowerBounds[axis]--; + bound.stabbingCount++; + } + + proxy.lowerBounds[axis]++; + // swap + //var temp = bound; + //bound = nextEdge; + //nextEdge = temp; + bound.Swap(nextBound); + //b2Math.b2Swap(bound, nextEdge); + index++; + } + } + + // Should we move the upper bound down? + if (deltaUpper < 0) + { + index = upperIndex; + while (index > 0 && upperValue < bounds[index-1].value) + { + bound = bounds[index]; + prevBound = bounds[index - 1]; + + prevProxyId = prevBound.proxyId; + prevProxy = this.m_proxyPool[ prevProxyId ]; + + prevBound.stabbingCount--; + + if (prevBound.IsLower() == true) + { + if (this.TestOverlap(oldValues, prevProxy)) + { + this.m_pairManager.RemoveBufferedPair(proxyId, prevProxyId); + } + + prevProxy.lowerBounds[axis]++; + bound.stabbingCount--; + } + else + { + prevProxy.upperBounds[axis]++; + bound.stabbingCount++; + } + + proxy.upperBounds[axis]--; + // swap + //var temp = bound; + //bound = prevEdge; + //prevEdge = temp; + bound.Swap(prevBound); + //b2Math.b2Swap(bound, prevEdge); + index--; + } + } + } + }, + + Commit: function(){ + this.m_pairManager.Commit(); + }, + + // this.Query an AABB for overlapping proxies, returns the user data and + // the count, up to the supplied maximum count. + QueryAABB: function(aabb, userData, maxCount){ + var lowerValues = new Array(); + var upperValues = new Array(); + this.ComputeBounds(lowerValues, upperValues, aabb); + + var lowerIndex = 0; + var upperIndex = 0; + var lowerIndexOut = [lowerIndex]; + var upperIndexOut = [upperIndex]; + this.Query(lowerIndexOut, upperIndexOut, lowerValues[0], upperValues[0], this.m_bounds[0], 2*this.m_proxyCount, 0); + this.Query(lowerIndexOut, upperIndexOut, lowerValues[1], upperValues[1], this.m_bounds[1], 2*this.m_proxyCount, 1); + + //b2Settings.b2Assert(this.m_queryResultCount < b2Settings.b2_maxProxies); + + var count = 0; + for (var i = 0; i < this.m_queryResultCount && count < maxCount; ++i, ++count) + { + //b2Settings.b2Assert(this.m_queryResults[i] < b2Settings.b2_maxProxies); + var proxy = this.m_proxyPool[ this.m_queryResults[i] ]; + //b2Settings.b2Assert(proxy.IsValid()); + userData[i] = proxy.userData; + } + + // Prepare for next query. + this.m_queryResultCount = 0; + this.IncrementTimeStamp(); + + return count; + }, + + Validate: function(){ + var pair; + var proxy1; + var proxy2; + var overlap; + + for (var axis = 0; axis < 2; ++axis) + { + var bounds = this.m_bounds[axis]; + + var boundCount = 2 * this.m_proxyCount; + var stabbingCount = 0; + + for (var i = 0; i < boundCount; ++i) + { + var bound = bounds[i]; + //b2Settings.b2Assert(i == 0 || bounds[i-1].value <= bound->value); + //b2Settings.b2Assert(bound->proxyId != b2_nullProxy); + //b2Settings.b2Assert(this.m_proxyPool[bound->proxyId].IsValid()); + + if (bound.IsLower() == true) + { + //b2Settings.b2Assert(this.m_proxyPool[bound.proxyId].lowerBounds[axis] == i); + stabbingCount++; + } + else + { + //b2Settings.b2Assert(this.m_proxyPool[bound.proxyId].upperBounds[axis] == i); + stabbingCount--; + } + + //b2Settings.b2Assert(bound.stabbingCount == stabbingCount); + } + } + + }, + +//private: + ComputeBounds: function(lowerValues, upperValues, aabb) + { + //b2Settings.b2Assert(aabb.maxVertex.x > aabb.minVertex.x); + //b2Settings.b2Assert(aabb.maxVertex.y > aabb.minVertex.y); + + //var minVertex = b2Math.b2ClampV(aabb.minVertex, this.m_worldAABB.minVertex, this.m_worldAABB.maxVertex); + var minVertexX = aabb.minVertex.x; + var minVertexY = aabb.minVertex.y; + minVertexX = b2Math.b2Min(minVertexX, this.m_worldAABB.maxVertex.x); + minVertexY = b2Math.b2Min(minVertexY, this.m_worldAABB.maxVertex.y); + minVertexX = b2Math.b2Max(minVertexX, this.m_worldAABB.minVertex.x); + minVertexY = b2Math.b2Max(minVertexY, this.m_worldAABB.minVertex.y); + + //var maxVertex = b2Math.b2ClampV(aabb.maxVertex, this.m_worldAABB.minVertex, this.m_worldAABB.maxVertex); + var maxVertexX = aabb.maxVertex.x; + var maxVertexY = aabb.maxVertex.y; + maxVertexX = b2Math.b2Min(maxVertexX, this.m_worldAABB.maxVertex.x); + maxVertexY = b2Math.b2Min(maxVertexY, this.m_worldAABB.maxVertex.y); + maxVertexX = b2Math.b2Max(maxVertexX, this.m_worldAABB.minVertex.x); + maxVertexY = b2Math.b2Max(maxVertexY, this.m_worldAABB.minVertex.y); + + // Bump lower bounds downs and upper bounds up. This ensures correct sorting of + // lower/upper bounds that would have equal values. + // TODO_ERIN implement fast float to uint16 conversion. + lowerValues[0] = /*uint*/(this.m_quantizationFactor.x * (minVertexX - this.m_worldAABB.minVertex.x)) & (b2Settings.USHRT_MAX - 1); + upperValues[0] = (/*uint*/(this.m_quantizationFactor.x * (maxVertexX - this.m_worldAABB.minVertex.x))& 0x0000ffff) | 1; + + lowerValues[1] = /*uint*/(this.m_quantizationFactor.y * (minVertexY - this.m_worldAABB.minVertex.y)) & (b2Settings.USHRT_MAX - 1); + upperValues[1] = (/*uint*/(this.m_quantizationFactor.y * (maxVertexY - this.m_worldAABB.minVertex.y))& 0x0000ffff) | 1; + }, + + // This one is only used for validation. + TestOverlapValidate: function(p1, p2){ + + for (var axis = 0; axis < 2; ++axis) + { + var bounds = this.m_bounds[axis]; + + //b2Settings.b2Assert(p1.lowerBounds[axis] < 2 * this.m_proxyCount); + //b2Settings.b2Assert(p1.upperBounds[axis] < 2 * this.m_proxyCount); + //b2Settings.b2Assert(p2.lowerBounds[axis] < 2 * this.m_proxyCount); + //b2Settings.b2Assert(p2.upperBounds[axis] < 2 * this.m_proxyCount); + + if (bounds[p1.lowerBounds[axis]].value > bounds[p2.upperBounds[axis]].value) + return false; + + if (bounds[p1.upperBounds[axis]].value < bounds[p2.lowerBounds[axis]].value) + return false; + } + + return true; + }, + + TestOverlap: function(b, p) + { + for (var axis = 0; axis < 2; ++axis) + { + var bounds = this.m_bounds[axis]; + + //b2Settings.b2Assert(p.lowerBounds[axis] < 2 * this.m_proxyCount); + //b2Settings.b2Assert(p.upperBounds[axis] < 2 * this.m_proxyCount); + + if (b.lowerValues[axis] > bounds[p.upperBounds[axis]].value) + return false; + + if (b.upperValues[axis] < bounds[p.lowerBounds[axis]].value) + return false; + } + + return true; + }, + + Query: function(lowerQueryOut, upperQueryOut, lowerValue, upperValue, bounds, boundCount, axis){ + + var lowerQuery = b2BroadPhase.BinarySearch(bounds, boundCount, lowerValue); + var upperQuery = b2BroadPhase.BinarySearch(bounds, boundCount, upperValue); + + // Easy case: lowerQuery <= lowerIndex(i) < upperQuery + // Solution: search query range for min bounds. + for (var j = lowerQuery; j < upperQuery; ++j) + { + if (bounds[j].IsLower()) + { + this.IncrementOverlapCount(bounds[j].proxyId); + } + } + + // Hard case: lowerIndex(i) < lowerQuery < upperIndex(i) + // Solution: use the stabbing count to search down the bound array. + if (lowerQuery > 0) + { + var i = lowerQuery - 1; + var s = bounds[i].stabbingCount; + + // Find the s overlaps. + while (s) + { + //b2Settings.b2Assert(i >= 0); + + if (bounds[i].IsLower()) + { + var proxy = this.m_proxyPool[ bounds[i].proxyId ]; + if (lowerQuery <= proxy.upperBounds[axis]) + { + this.IncrementOverlapCount(bounds[i].proxyId); + --s; + } + } + --i; + } + } + + lowerQueryOut[0] = lowerQuery; + upperQueryOut[0] = upperQuery; + }, + + + IncrementOverlapCount: function(proxyId){ + var proxy = this.m_proxyPool[ proxyId ]; + if (proxy.timeStamp < this.m_timeStamp) + { + proxy.timeStamp = this.m_timeStamp; + proxy.overlapCount = 1; + } + else + { + proxy.overlapCount = 2; + //b2Settings.b2Assert(this.m_queryResultCount < b2Settings.b2_maxProxies); + this.m_queryResults[this.m_queryResultCount] = proxyId; + ++this.m_queryResultCount; + } + }, + IncrementTimeStamp: function(){ + if (this.m_timeStamp == b2Settings.USHRT_MAX) + { + for (var i = 0; i < b2Settings.b2_maxProxies; ++i) + { + this.m_proxyPool[i].timeStamp = 0; + } + this.m_timeStamp = 1; + } + else + { + ++this.m_timeStamp; + } + }, + +//public: + m_pairManager: new b2PairManager(), + + m_proxyPool: new Array(b2Settings.b2_maxPairs), + m_freeProxy: 0, + + m_bounds: new Array(2*b2Settings.b2_maxProxies), + + m_queryResults: new Array(b2Settings.b2_maxProxies), + m_queryResultCount: 0, + + m_worldAABB: null, + m_quantizationFactor: new b2Vec2(), + m_proxyCount: 0, + m_timeStamp: 0}; +b2BroadPhase.s_validate = false; +b2BroadPhase.b2_invalid = b2Settings.USHRT_MAX; +b2BroadPhase.b2_nullEdge = b2Settings.USHRT_MAX; +b2BroadPhase.BinarySearch = function(bounds, count, value) + { + var low = 0; + var high = count - 1; + while (low <= high) + { + var mid = Math.floor((low + high) / 2); + if (bounds[mid].value > value) + { + high = mid - 1; + } + else if (bounds[mid].value < value) + { + low = mid + 1; + } + else + { + return /*uint*/(mid); + } + } + + return /*uint*/(low); + }; diff --git a/lib/box2d-js/js/box2d/collision/b2BufferedPair.js b/lib/box2d-js/js/box2d/collision/b2BufferedPair.js new file mode 100644 index 0000000..15789c0 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2BufferedPair.js @@ -0,0 +1,26 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2BufferedPair = Class.create(); +b2BufferedPair.prototype = { + proxyId1: 0, + proxyId2: 0, + + initialize: function() {}} diff --git a/lib/box2d-js/js/box2d/collision/b2Collision.js b/lib/box2d-js/js/box2d/collision/b2Collision.js new file mode 100644 index 0000000..ec86ad4 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2Collision.js @@ -0,0 +1,738 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + +var b2Collision = Class.create(); +b2Collision.prototype = { + + // Null feature + + + + + // Find the separation between poly1 and poly2 for a give edge normal on poly1. + + + + + // Find the max separation between poly1 and poly2 using edge normals + // from poly1. + + + + + + + + // Find edge normal of max separation on A - return if separating axis is found + // Find edge normal of max separation on B - return if separation axis is found + // Choose reference edge(minA, minB) + // Find incident edge + // Clip + // The normal points from 1 to 2 + + + + + + + + + + + + + + + + initialize: function() {}} +b2Collision.b2_nullFeature = 0x000000ff; +b2Collision.ClipSegmentToLine = function(vOut, vIn, normal, offset) + { + // Start with no output points + var numOut = 0; + + var vIn0 = vIn[0].v; + var vIn1 = vIn[1].v; + + // Calculate the distance of end points to the line + var distance0 = b2Math.b2Dot(normal, vIn[0].v) - offset; + var distance1 = b2Math.b2Dot(normal, vIn[1].v) - offset; + + // If the points are behind the plane + if (distance0 <= 0.0) vOut[numOut++] = vIn[0]; + if (distance1 <= 0.0) vOut[numOut++] = vIn[1]; + + // If the points are on different sides of the plane + if (distance0 * distance1 < 0.0) + { + // Find intersection point of edge and plane + var interp = distance0 / (distance0 - distance1); + // expanded for performance + var tVec = vOut[numOut].v; + tVec.x = vIn0.x + interp * (vIn1.x - vIn0.x); + tVec.y = vIn0.y + interp * (vIn1.y - vIn0.y); + if (distance0 > 0.0) + { + vOut[numOut].id = vIn[0].id; + } + else + { + vOut[numOut].id = vIn[1].id; + } + ++numOut; + } + + return numOut; + }; +b2Collision.EdgeSeparation = function(poly1, edge1, poly2) + { + var vert1s = poly1.m_vertices; + var count2 = poly2.m_vertexCount; + var vert2s = poly2.m_vertices; + + // Convert normal from into poly2's frame. + //b2Settings.b2Assert(edge1 < poly1.m_vertexCount); + + //var normal = b2Math.b2MulMV(poly1.m_R, poly1->m_normals[edge1]); + var normalX = poly1.m_normals[edge1].x; + var normalY = poly1.m_normals[edge1].y; + var tX = normalX; + var tMat = poly1.m_R; + normalX = tMat.col1.x * tX + tMat.col2.x * normalY; + normalY = tMat.col1.y * tX + tMat.col2.y * normalY; + // ^^^^^^^ normal.MulM(poly1.m_R); + + //var normalLocal2 = b2Math.b2MulTMV(poly2.m_R, normal); + var normalLocal2X = normalX; + var normalLocal2Y = normalY; + tMat = poly2.m_R; + tX = normalLocal2X * tMat.col1.x + normalLocal2Y * tMat.col1.y; + normalLocal2Y = normalLocal2X * tMat.col2.x + normalLocal2Y * tMat.col2.y; + normalLocal2X = tX; + // ^^^^^ normalLocal2.MulTM(poly2.m_R); + + // Find support vertex on poly2 for -normal. + var vertexIndex2 = 0; + var minDot = Number.MAX_VALUE; + for (var i = 0; i < count2; ++i) + { + //var dot = b2Math.b2Dot(vert2s[i], normalLocal2); + var tVec = vert2s[i]; + var dot = tVec.x * normalLocal2X + tVec.y * normalLocal2Y; + if (dot < minDot) + { + minDot = dot; + vertexIndex2 = i; + } + } + + //b2Vec2 v1 = poly1->m_position + b2Mul(poly1->m_R, vert1s[edge1]); + tMat = poly1.m_R; + var v1X = poly1.m_position.x + (tMat.col1.x * vert1s[edge1].x + tMat.col2.x * vert1s[edge1].y) + var v1Y = poly1.m_position.y + (tMat.col1.y * vert1s[edge1].x + tMat.col2.y * vert1s[edge1].y) + + //b2Vec2 v2 = poly2->m_position + b2Mul(poly2->m_R, vert2s[vertexIndex2]); + tMat = poly2.m_R; + var v2X = poly2.m_position.x + (tMat.col1.x * vert2s[vertexIndex2].x + tMat.col2.x * vert2s[vertexIndex2].y) + var v2Y = poly2.m_position.y + (tMat.col1.y * vert2s[vertexIndex2].x + tMat.col2.y * vert2s[vertexIndex2].y) + + //var separation = b2Math.b2Dot( b2Math.SubtractVV( v2, v1 ) , normal); + v2X -= v1X; + v2Y -= v1Y; + //var separation = b2Math.b2Dot( v2 , normal); + var separation = v2X * normalX + v2Y * normalY; + return separation; + }; +b2Collision.FindMaxSeparation = function(edgeIndex /*int ptr*/, poly1, poly2, conservative) + { + var count1 = poly1.m_vertexCount; + + // Vector pointing from the origin of poly1 to the origin of poly2. + //var d = b2Math.SubtractVV( poly2.m_position, poly1.m_position ); + var dX = poly2.m_position.x - poly1.m_position.x; + var dY = poly2.m_position.y - poly1.m_position.y; + + //var dLocal1 = b2Math.b2MulTMV(poly1.m_R, d); + var dLocal1X = (dX * poly1.m_R.col1.x + dY * poly1.m_R.col1.y); + var dLocal1Y = (dX * poly1.m_R.col2.x + dY * poly1.m_R.col2.y); + + // Get support vertex hint for our search + var edge = 0; + var maxDot = -Number.MAX_VALUE; + for (var i = 0; i < count1; ++i) + { + //var dot = b2Math.b2Dot(poly.m_normals[i], dLocal1); + var dot = (poly1.m_normals[i].x * dLocal1X + poly1.m_normals[i].y * dLocal1Y); + if (dot > maxDot) + { + maxDot = dot; + edge = i; + } + } + + // Get the separation for the edge normal. + var s = b2Collision.EdgeSeparation(poly1, edge, poly2); + if (s > 0.0 && conservative == false) + { + return s; + } + + // Check the separation for the neighboring edges. + var prevEdge = edge - 1 >= 0 ? edge - 1 : count1 - 1; + var sPrev = b2Collision.EdgeSeparation(poly1, prevEdge, poly2); + if (sPrev > 0.0 && conservative == false) + { + return sPrev; + } + + var nextEdge = edge + 1 < count1 ? edge + 1 : 0; + var sNext = b2Collision.EdgeSeparation(poly1, nextEdge, poly2); + if (sNext > 0.0 && conservative == false) + { + return sNext; + } + + // Find the best edge and the search direction. + var bestEdge = 0; + var bestSeparation; + var increment = 0; + if (sPrev > s && sPrev > sNext) + { + increment = -1; + bestEdge = prevEdge; + bestSeparation = sPrev; + } + else if (sNext > s) + { + increment = 1; + bestEdge = nextEdge; + bestSeparation = sNext; + } + else + { + // pointer out + edgeIndex[0] = edge; + return s; + } + + while (true) + { + + if (increment == -1) + edge = bestEdge - 1 >= 0 ? bestEdge - 1 : count1 - 1; + else + edge = bestEdge + 1 < count1 ? bestEdge + 1 : 0; + + s = b2Collision.EdgeSeparation(poly1, edge, poly2); + if (s > 0.0 && conservative == false) + { + return s; + } + + if (s > bestSeparation) + { + bestEdge = edge; + bestSeparation = s; + } + else + { + break; + } + } + + // pointer out + edgeIndex[0] = bestEdge; + return bestSeparation; + }; +b2Collision.FindIncidentEdge = function(c, poly1, edge1, poly2) + { + var count1 = poly1.m_vertexCount; + var vert1s = poly1.m_vertices; + var count2 = poly2.m_vertexCount; + var vert2s = poly2.m_vertices; + + // Get the vertices associated with edge1. + var vertex11 = edge1; + var vertex12 = edge1 + 1 == count1 ? 0 : edge1 + 1; + + // Get the normal of edge1. + var tVec = vert1s[vertex12]; + //var normal1Local1 = b2Math.b2CrossVF( b2Math.SubtractVV( vert1s[vertex12], vert1s[vertex11] ), 1.0); + var normal1Local1X = tVec.x; + var normal1Local1Y = tVec.y; + tVec = vert1s[vertex11]; + normal1Local1X -= tVec.x; + normal1Local1Y -= tVec.y; + var tX = normal1Local1X; + normal1Local1X = normal1Local1Y; + normal1Local1Y = -tX; + // ^^^^ normal1Local1.CrossVF(1.0); + + var invLength = 1.0 / Math.sqrt(normal1Local1X*normal1Local1X + normal1Local1Y*normal1Local1Y); + normal1Local1X *= invLength; + normal1Local1Y *= invLength; + // ^^^^normal1Local1.Normalize(); + //var normal1 = b2Math.b2MulMV(poly1.m_R, normal1Local1); + var normal1X = normal1Local1X; + var normal1Y = normal1Local1Y; + + tX = normal1X; + var tMat = poly1.m_R; + normal1X = tMat.col1.x * tX + tMat.col2.x * normal1Y; + normal1Y = tMat.col1.y * tX + tMat.col2.y * normal1Y; + // ^^^^ normal1.MulM(poly1.m_R); + + //var normal1Local2 = b2Math.b2MulTMV(poly2.m_R, normal1); + var normal1Local2X = normal1X; + var normal1Local2Y = normal1Y; + tMat = poly2.m_R; + tX = normal1Local2X * tMat.col1.x + normal1Local2Y * tMat.col1.y; + normal1Local2Y = normal1Local2X * tMat.col2.x + normal1Local2Y * tMat.col2.y; + normal1Local2X = tX; + // ^^^^ normal1Local2.MulTM(poly2.m_R); + + // Find the incident edge on poly2. + var vertex21 = 0; + var vertex22 = 0; + var minDot = Number.MAX_VALUE; + for (var i = 0; i < count2; ++i) + { + var i1 = i; + var i2 = i + 1 < count2 ? i + 1 : 0; + + //var normal2Local2 = b2Math.b2CrossVF( b2Math.SubtractVV( vert2s[i2], vert2s[i1] ), 1.0); + tVec = vert2s[i2]; + var normal2Local2X = tVec.x; + var normal2Local2Y = tVec.y; + tVec = vert2s[i1]; + normal2Local2X -= tVec.x; + normal2Local2Y -= tVec.y; + tX = normal2Local2X; + normal2Local2X = normal2Local2Y; + normal2Local2Y = -tX; + // ^^^^ normal2Local2.CrossVF(1.0); + + invLength = 1.0 / Math.sqrt(normal2Local2X*normal2Local2X + normal2Local2Y*normal2Local2Y); + normal2Local2X *= invLength; + normal2Local2Y *= invLength; + // ^^^^ normal2Local2.Normalize(); + + //var dot = b2Math.b2Dot(normal2Local2, normal1Local2); + var dot = normal2Local2X * normal1Local2X + normal2Local2Y * normal1Local2Y; + if (dot < minDot) + { + minDot = dot; + vertex21 = i1; + vertex22 = i2; + } + } + + var tClip; + // Build the clip vertices for the incident edge. + tClip = c[0]; + //tClip.v = b2Math.AddVV(poly2.m_position, b2Math.b2MulMV(poly2.m_R, vert2s[vertex21])); + tVec = tClip.v; + tVec.SetV(vert2s[vertex21]); + tVec.MulM(poly2.m_R); + tVec.Add(poly2.m_position); + + tClip.id.features.referenceFace = edge1; + tClip.id.features.incidentEdge = vertex21; + tClip.id.features.incidentVertex = vertex21; + + tClip = c[1]; + //tClip.v = b2Math.AddVV(poly2.m_position, b2Math.b2MulMV(poly2.m_R, vert2s[vertex22])); + tVec = tClip.v; + tVec.SetV(vert2s[vertex22]); + tVec.MulM(poly2.m_R); + tVec.Add(poly2.m_position); + tClip.id.features.referenceFace = edge1; + tClip.id.features.incidentEdge = vertex21; + tClip.id.features.incidentVertex = vertex22; + }; +b2Collision.b2CollidePolyTempVec = new b2Vec2(); +b2Collision.b2CollidePoly = function(manifold, polyA, polyB, conservative) + { + manifold.pointCount = 0; + + var edgeA = 0; + var edgeAOut = [edgeA]; + var separationA = b2Collision.FindMaxSeparation(edgeAOut, polyA, polyB, conservative); + edgeA = edgeAOut[0]; + if (separationA > 0.0 && conservative == false) + return; + + var edgeB = 0; + var edgeBOut = [edgeB]; + var separationB = b2Collision.FindMaxSeparation(edgeBOut, polyB, polyA, conservative); + edgeB = edgeBOut[0]; + if (separationB > 0.0 && conservative == false) + return; + + var poly1; + var poly2; + var edge1 = 0; + var flip = 0; + var k_relativeTol = 0.98; + var k_absoluteTol = 0.001; + + // TODO_ERIN use "radius" of poly for absolute tolerance. + if (separationB > k_relativeTol * separationA + k_absoluteTol) + { + poly1 = polyB; + poly2 = polyA; + edge1 = edgeB; + flip = 1; + } + else + { + poly1 = polyA; + poly2 = polyB; + edge1 = edgeA; + flip = 0; + } + + var incidentEdge = [new ClipVertex(), new ClipVertex()]; + b2Collision.FindIncidentEdge(incidentEdge, poly1, edge1, poly2); + + var count1 = poly1.m_vertexCount; + var vert1s = poly1.m_vertices; + + var v11 = vert1s[edge1]; + var v12 = edge1 + 1 < count1 ? vert1s[edge1+1] : vert1s[0]; + + //var dv = b2Math.SubtractVV(v12, v11); + var dvX = v12.x - v11.x; + var dvY = v12.y - v11.y; + + //var sideNormal = b2Math.b2MulMV(poly1.m_R, b2Math.SubtractVV(v12, v11)); + var sideNormalX = v12.x - v11.x; + var sideNormalY = v12.y - v11.y; + + var tX = sideNormalX; + var tMat = poly1.m_R; + sideNormalX = tMat.col1.x * tX + tMat.col2.x * sideNormalY; + sideNormalY = tMat.col1.y * tX + tMat.col2.y * sideNormalY; + // ^^^^ sideNormal.MulM(poly1.m_R); + + var invLength = 1.0 / Math.sqrt(sideNormalX*sideNormalX + sideNormalY*sideNormalY); + sideNormalX *= invLength; + sideNormalY *= invLength; + // ^^^^ sideNormal.Normalize(); + + //var frontNormal = b2Math.b2CrossVF(sideNormal, 1.0); + var frontNormalX = sideNormalX; + var frontNormalY = sideNormalY; + tX = frontNormalX; + frontNormalX = frontNormalY; + frontNormalY = -tX; + // ^^^^ frontNormal.CrossVF(1.0); + + // Expanded for performance + //v11 = b2Math.AddVV(poly1.m_position, b2Math.b2MulMV(poly1.m_R, v11)); + var v11X = v11.x; + var v11Y = v11.y; + tX = v11X; + tMat = poly1.m_R; + v11X = tMat.col1.x * tX + tMat.col2.x * v11Y; + v11Y = tMat.col1.y * tX + tMat.col2.y * v11Y; + // ^^^^ v11.MulM(poly1.m_R); + v11X += poly1.m_position.x; + v11Y += poly1.m_position.y; + //v12 = b2Math.AddVV(poly1.m_position, b2Math.b2MulMV(poly1.m_R, v12)); + var v12X = v12.x; + var v12Y = v12.y; + tX = v12X; + tMat = poly1.m_R; + v12X = tMat.col1.x * tX + tMat.col2.x * v12Y; + v12Y = tMat.col1.y * tX + tMat.col2.y * v12Y; + // ^^^^ v12.MulM(poly1.m_R); + v12X += poly1.m_position.x; + v12Y += poly1.m_position.y; + + //var frontOffset = b2Math.b2Dot(frontNormal, v11); + var frontOffset = frontNormalX * v11X + frontNormalY * v11Y; + //var sideOffset1 = -b2Math.b2Dot(sideNormal, v11); + var sideOffset1 = -(sideNormalX * v11X + sideNormalY * v11Y); + //var sideOffset2 = b2Math.b2Dot(sideNormal, v12); + var sideOffset2 = sideNormalX * v12X + sideNormalY * v12Y; + + // Clip incident edge against extruded edge1 side edges. + var clipPoints1 = [new ClipVertex(), new ClipVertex()]; + var clipPoints2 = [new ClipVertex(), new ClipVertex()]; + + var np = 0; + + // Clip to box side 1 + b2Collision.b2CollidePolyTempVec.Set(-sideNormalX, -sideNormalY); + np = b2Collision.ClipSegmentToLine(clipPoints1, incidentEdge, b2Collision.b2CollidePolyTempVec, sideOffset1); + + if (np < 2) + return; + + // Clip to negative box side 1 + b2Collision.b2CollidePolyTempVec.Set(sideNormalX, sideNormalY); + np = b2Collision.ClipSegmentToLine(clipPoints2, clipPoints1, b2Collision.b2CollidePolyTempVec, sideOffset2); + + if (np < 2) + return; + + // Now clipPoints2 contains the clipped points. + if (flip){ + manifold.normal.Set(-frontNormalX, -frontNormalY); + } + else{ + manifold.normal.Set(frontNormalX, frontNormalY); + } + // ^^^^ manifold.normal = flip ? frontNormal.Negative() : frontNormal; + + var pointCount = 0; + for (var i = 0; i < b2Settings.b2_maxManifoldPoints; ++i) + { + //var separation = b2Math.b2Dot(frontNormal, clipPoints2[i].v) - frontOffset; + var tVec = clipPoints2[i].v; + var separation = (frontNormalX * tVec.x + frontNormalY * tVec.y) - frontOffset; + + if (separation <= 0.0 || conservative == true) + { + var cp = manifold.points[ pointCount ]; + cp.separation = separation; + cp.position.SetV( clipPoints2[i].v ); + cp.id.Set( clipPoints2[i].id ); + cp.id.features.flip = flip; + ++pointCount; + } + } + + manifold.pointCount = pointCount; + }; +b2Collision.b2CollideCircle = function(manifold, circle1, circle2, conservative) + { + manifold.pointCount = 0; + + //var d = b2Math.SubtractVV(circle2.m_position, circle1.m_position); + var dX = circle2.m_position.x - circle1.m_position.x; + var dY = circle2.m_position.y - circle1.m_position.y; + //var distSqr = b2Math.b2Dot(d, d); + var distSqr = dX * dX + dY * dY; + var radiusSum = circle1.m_radius + circle2.m_radius; + if (distSqr > radiusSum * radiusSum && conservative == false) + { + return; + } + + var separation; + if (distSqr < Number.MIN_VALUE) + { + separation = -radiusSum; + manifold.normal.Set(0.0, 1.0); + } + else + { + var dist = Math.sqrt(distSqr); + separation = dist - radiusSum; + var a = 1.0 / dist; + manifold.normal.x = a * dX; + manifold.normal.y = a * dY; + } + + manifold.pointCount = 1; + var tPoint = manifold.points[0]; + tPoint.id.set_key(0); + tPoint.separation = separation; + //tPoint.position = b2Math.SubtractVV(circle2.m_position, b2Math.MulFV(circle2.m_radius, manifold.normal)); + tPoint.position.x = circle2.m_position.x - (circle2.m_radius * manifold.normal.x); + tPoint.position.y = circle2.m_position.y - (circle2.m_radius * manifold.normal.y); + }; +b2Collision.b2CollidePolyAndCircle = function(manifold, poly, circle, conservative) + { + manifold.pointCount = 0; + var tPoint; + + var dX; + var dY; + + // Compute circle position in the frame of the polygon. + //var xLocal = b2Math.b2MulTMV(poly.m_R, b2Math.SubtractVV(circle.m_position, poly.m_position)); + var xLocalX = circle.m_position.x - poly.m_position.x; + var xLocalY = circle.m_position.y - poly.m_position.y; + var tMat = poly.m_R; + var tX = xLocalX * tMat.col1.x + xLocalY * tMat.col1.y; + xLocalY = xLocalX * tMat.col2.x + xLocalY * tMat.col2.y; + xLocalX = tX; + + var dist; + + // Find the min separating edge. + var normalIndex = 0; + var separation = -Number.MAX_VALUE; + var radius = circle.m_radius; + for (var i = 0; i < poly.m_vertexCount; ++i) + { + //var s = b2Math.b2Dot(poly.m_normals[i], b2Math.SubtractVV(xLocal, poly.m_vertices[i])); + var s = poly.m_normals[i].x * (xLocalX-poly.m_vertices[i].x) + poly.m_normals[i].y * (xLocalY-poly.m_vertices[i].y); + if (s > radius) + { + // Early out. + return; + } + + if (s > separation) + { + separation = s; + normalIndex = i; + } + } + + // If the center is inside the polygon ... + if (separation < Number.MIN_VALUE) + { + manifold.pointCount = 1; + //manifold.normal = b2Math.b2MulMV(poly.m_R, poly.m_normals[normalIndex]); + var tVec = poly.m_normals[normalIndex]; + manifold.normal.x = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + manifold.normal.y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + tPoint = manifold.points[0]; + tPoint.id.features.incidentEdge = normalIndex; + tPoint.id.features.incidentVertex = b2Collision.b2_nullFeature; + tPoint.id.features.referenceFace = b2Collision.b2_nullFeature; + tPoint.id.features.flip = 0; + tPoint.position.x = circle.m_position.x - radius * manifold.normal.x; + tPoint.position.y = circle.m_position.y - radius * manifold.normal.y; + //tPoint.position = b2Math.SubtractVV(circle.m_position , b2Math.MulFV(radius , manifold.normal)); + tPoint.separation = separation - radius; + return; + } + + // Project the circle center onto the edge segment. + var vertIndex1 = normalIndex; + var vertIndex2 = vertIndex1 + 1 < poly.m_vertexCount ? vertIndex1 + 1 : 0; + //var e = b2Math.SubtractVV(poly.m_vertices[vertIndex2] , poly.m_vertices[vertIndex1]); + var eX = poly.m_vertices[vertIndex2].x - poly.m_vertices[vertIndex1].x; + var eY = poly.m_vertices[vertIndex2].y - poly.m_vertices[vertIndex1].y; + //var length = e.Normalize(); + var length = Math.sqrt(eX*eX + eY*eY); + eX /= length; + eY /= length; + + // If the edge length is zero ... + if (length < Number.MIN_VALUE) + { + //d = b2Math.SubtractVV(xLocal , poly.m_vertices[vertIndex1]); + dX = xLocalX - poly.m_vertices[vertIndex1].x; + dY = xLocalY - poly.m_vertices[vertIndex1].y; + //dist = d.Normalize(); + dist = Math.sqrt(dX*dX + dY*dY); + dX /= dist; + dY /= dist; + if (dist > radius) + { + return; + } + + manifold.pointCount = 1; + //manifold.normal = b2Math.b2MulMV(poly.m_R, d); + manifold.normal.Set(tMat.col1.x * dX + tMat.col2.x * dY, tMat.col1.y * dX + tMat.col2.y * dY); + tPoint = manifold.points[0]; + tPoint.id.features.incidentEdge = b2Collision.b2_nullFeature; + tPoint.id.features.incidentVertex = vertIndex1; + tPoint.id.features.referenceFace = b2Collision.b2_nullFeature; + tPoint.id.features.flip = 0; + //tPoint.position = b2Math.SubtractVV(circle.m_position , b2Math.MulFV(radius , manifold.normal)); + tPoint.position.x = circle.m_position.x - radius * manifold.normal.x; + tPoint.position.y = circle.m_position.y - radius * manifold.normal.y; + tPoint.separation = dist - radius; + return; + } + + // Project the center onto the edge. + //var u = b2Math.b2Dot(b2Math.SubtractVV(xLocal , poly.m_vertices[vertIndex1]) , e); + var u = (xLocalX-poly.m_vertices[vertIndex1].x) * eX + (xLocalY-poly.m_vertices[vertIndex1].y) * eY; + + tPoint = manifold.points[0]; + tPoint.id.features.incidentEdge = b2Collision.b2_nullFeature; + tPoint.id.features.incidentVertex = b2Collision.b2_nullFeature; + tPoint.id.features.referenceFace = b2Collision.b2_nullFeature; + tPoint.id.features.flip = 0; + + var pX, pY; + if (u <= 0.0) + { + pX = poly.m_vertices[vertIndex1].x; + pY = poly.m_vertices[vertIndex1].y; + tPoint.id.features.incidentVertex = vertIndex1; + } + else if (u >= length) + { + pX = poly.m_vertices[vertIndex2].x; + pY = poly.m_vertices[vertIndex2].y; + tPoint.id.features.incidentVertex = vertIndex2; + } + else + { + //p = b2Math.AddVV(poly.m_vertices[vertIndex1] , b2Math.MulFV(u, e)); + pX = eX * u + poly.m_vertices[vertIndex1].x; + pY = eY * u + poly.m_vertices[vertIndex1].y; + tPoint.id.features.incidentEdge = vertIndex1; + } + + //d = b2Math.SubtractVV(xLocal , p); + dX = xLocalX - pX; + dY = xLocalY - pY; + //dist = d.Normalize(); + dist = Math.sqrt(dX*dX + dY*dY); + dX /= dist; + dY /= dist; + if (dist > radius) + { + return; + } + + manifold.pointCount = 1; + //manifold.normal = b2Math.b2MulMV(poly.m_R, d); + manifold.normal.Set(tMat.col1.x * dX + tMat.col2.x * dY, tMat.col1.y * dX + tMat.col2.y * dY); + //tPoint.position = b2Math.SubtractVV(circle.m_position , b2Math.MulFV(radius , manifold.normal)); + tPoint.position.x = circle.m_position.x - radius * manifold.normal.x; + tPoint.position.y = circle.m_position.y - radius * manifold.normal.y; + tPoint.separation = dist - radius; + }; +b2Collision.b2TestOverlap = function(a, b) + { + var t1 = b.minVertex; + var t2 = a.maxVertex; + //d1 = b2Math.SubtractVV(b.minVertex, a.maxVertex); + var d1X = t1.x - t2.x; + var d1Y = t1.y - t2.y; + //d2 = b2Math.SubtractVV(a.minVertex, b.maxVertex); + t1 = a.minVertex; + t2 = b.maxVertex; + var d2X = t1.x - t2.x; + var d2Y = t1.y - t2.y; + + if (d1X > 0.0 || d1Y > 0.0) + return false; + + if (d2X > 0.0 || d2Y > 0.0) + return false; + + return true; + }; diff --git a/lib/box2d-js/js/box2d/collision/b2ContactID.js b/lib/box2d-js/js/box2d/collision/b2ContactID.js new file mode 100644 index 0000000..3a792bd --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2ContactID.js @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +// We use contact ids to facilitate warm starting. +var b2ContactID = Class.create(); +b2ContactID.prototype = +{ + initialize: function(){ + // initialize instance variables for references + this.features = new Features(); + // + + this.features._m_id = this; + + }, + Set: function(id){ + this.set_key(id._key); + }, + Copy: function(){ + var id = new b2ContactID(); + id.set_key(this._key); + return id; + }, + get_key: function(){ + return this._key; + }, + set_key: function(value) { + this._key = value; + this.features._referenceFace = this._key & 0x000000ff; + this.features._incidentEdge = ((this._key & 0x0000ff00) >> 8) & 0x000000ff; + this.features._incidentVertex = ((this._key & 0x00ff0000) >> 16) & 0x000000ff; + this.features._flip = ((this._key & 0xff000000) >> 24) & 0x000000ff; + }, + features: new Features(), + _key: 0}; diff --git a/lib/box2d-js/js/box2d/collision/b2ContactPoint.js b/lib/box2d-js/js/box2d/collision/b2ContactPoint.js new file mode 100644 index 0000000..1d14c07 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2ContactPoint.js @@ -0,0 +1,35 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +// We use contact ids to facilitate warm starting. +var b2ContactPoint = Class.create(); +b2ContactPoint.prototype = +{ + position: new b2Vec2(), + separation: null, + normalImpulse: null, + tangentImpulse: null, + id: new b2ContactID(), + initialize: function() { + // initialize instance variables for references + this.position = new b2Vec2(); + this.id = new b2ContactID(); + // +}}; diff --git a/lib/box2d-js/js/box2d/collision/b2Distance.js b/lib/box2d-js/js/box2d/collision/b2Distance.js new file mode 100644 index 0000000..b4f1543 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2Distance.js @@ -0,0 +1,333 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2Distance = Class.create(); +b2Distance.prototype = +{ + + // GJK using Voronoi regions (Christer Ericson) and region selection + // optimizations (Casey Muratori). + + // The origin is either in the region of points[1] or in the edge region. The origin is + // not in region of points[0] because that is the old point. + + // Possible regions: + // - points[2] + // - edge points[0]-points[2] + // - edge points[1]-points[2] + // - inside the triangle + + + + + + + initialize: function() {}}; +b2Distance.ProcessTwo = function(p1Out, p2Out, p1s, p2s, points) + { + // If in point[1] region + //b2Vec2 r = -points[1]; + var rX = -points[1].x; + var rY = -points[1].y; + //b2Vec2 d = points[1] - points[0]; + var dX = points[0].x - points[1].x; + var dY = points[0].y - points[1].y; + //float32 length = d.Normalize(); + var length = Math.sqrt(dX*dX + dY*dY); + dX /= length; + dY /= length; + + //float32 lambda = b2Dot(r, d); + var lambda = rX * dX + rY * dY; + if (lambda <= 0.0 || length < Number.MIN_VALUE) + { + // The simplex is reduced to a point. + //*p1Out = p1s[1]; + p1Out.SetV(p1s[1]); + //*p2Out = p2s[1]; + p2Out.SetV(p2s[1]); + //p1s[0] = p1s[1]; + p1s[0].SetV(p1s[1]); + //p2s[0] = p2s[1]; + p2s[0].SetV(p2s[1]); + points[0].SetV(points[1]); + return 1; + } + + // Else in edge region + lambda /= length; + //*p1Out = p1s[1] + lambda * (p1s[0] - p1s[1]); + p1Out.x = p1s[1].x + lambda * (p1s[0].x - p1s[1].x); + p1Out.y = p1s[1].y + lambda * (p1s[0].y - p1s[1].y); + //*p2Out = p2s[1] + lambda * (p2s[0] - p2s[1]); + p2Out.x = p2s[1].x + lambda * (p2s[0].x - p2s[1].x); + p2Out.y = p2s[1].y + lambda * (p2s[0].y - p2s[1].y); + return 2; + }; +b2Distance.ProcessThree = function(p1Out, p2Out, p1s, p2s, points) + { + //b2Vec2 a = points[0]; + var aX = points[0].x; + var aY = points[0].y; + //b2Vec2 b = points[1]; + var bX = points[1].x; + var bY = points[1].y; + //b2Vec2 c = points[2]; + var cX = points[2].x; + var cY = points[2].y; + + //b2Vec2 ab = b - a; + var abX = bX - aX; + var abY = bY - aY; + //b2Vec2 ac = c - a; + var acX = cX - aX; + var acY = cY - aY; + //b2Vec2 bc = c - b; + var bcX = cX - bX; + var bcY = cY - bY; + + //float32 sn = -b2Dot(a, ab), sd = b2Dot(b, ab); + var sn = -(aX * abX + aY * abY); + var sd = (bX * abX + bY * abY); + //float32 tn = -b2Dot(a, ac), td = b2Dot(c, ac); + var tn = -(aX * acX + aY * acY); + var td = (cX * acX + cY * acY); + //float32 un = -b2Dot(b, bc), ud = b2Dot(c, bc); + var un = -(bX * bcX + bY * bcY); + var ud = (cX * bcX + cY * bcY); + + // In vertex c region? + if (td <= 0.0 && ud <= 0.0) + { + // Single point + //*p1Out = p1s[2]; + p1Out.SetV(p1s[2]); + //*p2Out = p2s[2]; + p2Out.SetV(p2s[2]); + //p1s[0] = p1s[2]; + p1s[0].SetV(p1s[2]); + //p2s[0] = p2s[2]; + p2s[0].SetV(p2s[2]); + points[0].SetV(points[2]); + return 1; + } + + // Should not be in vertex a or b region. + //b2Settings.b2Assert(sn > 0.0 || tn > 0.0); + //b2Settings.b2Assert(sd > 0.0 || un > 0.0); + + //float32 n = b2Cross(ab, ac); + var n = abX * acY - abY * acX; + + // Should not be in edge ab region. + //float32 vc = n * b2Cross(a, b); + var vc = n * (aX * bY - aY * bX); + //b2Settings.b2Assert(vc > 0.0 || sn > 0.0 || sd > 0.0); + + // In edge bc region? + //float32 va = n * b2Cross(b, c); + var va = n * (bX * cY - bY * cX); + if (va <= 0.0 && un >= 0.0 && ud >= 0.0) + { + //b2Settings.b2Assert(un + ud > 0.0); + + //float32 lambda = un / (un + ud); + var lambda = un / (un + ud); + //*p1Out = p1s[1] + lambda * (p1s[2] - p1s[1]); + p1Out.x = p1s[1].x + lambda * (p1s[2].x - p1s[1].x); + p1Out.y = p1s[1].y + lambda * (p1s[2].y - p1s[1].y); + //*p2Out = p2s[1] + lambda * (p2s[2] - p2s[1]); + p2Out.x = p2s[1].x + lambda * (p2s[2].x - p2s[1].x); + p2Out.y = p2s[1].y + lambda * (p2s[2].y - p2s[1].y); + //p1s[0] = p1s[2]; + p1s[0].SetV(p1s[2]); + //p2s[0] = p2s[2]; + p2s[0].SetV(p2s[2]); + //points[0] = points[2]; + points[0].SetV(points[2]); + return 2; + } + + // In edge ac region? + //float32 vb = n * b2Cross(c, a); + var vb = n * (cX * aY - cY * aX); + if (vb <= 0.0 && tn >= 0.0 && td >= 0.0) + { + //b2Settings.b2Assert(tn + td > 0.0); + + //float32 lambda = tn / (tn + td); + var lambda = tn / (tn + td); + //*p1Out = p1s[0] + lambda * (p1s[2] - p1s[0]); + p1Out.x = p1s[0].x + lambda * (p1s[2].x - p1s[0].x); + p1Out.y = p1s[0].y + lambda * (p1s[2].y - p1s[0].y); + //*p2Out = p2s[0] + lambda * (p2s[2] - p2s[0]); + p2Out.x = p2s[0].x + lambda * (p2s[2].x - p2s[0].x); + p2Out.y = p2s[0].y + lambda * (p2s[2].y - p2s[0].y); + //p1s[1] = p1s[2]; + p1s[1].SetV(p1s[2]); + //p2s[1] = p2s[2]; + p2s[1].SetV(p2s[2]); + //points[1] = points[2]; + points[1].SetV(points[2]); + return 2; + } + + // Inside the triangle, compute barycentric coordinates + //float32 denom = va + vb + vc; + var denom = va + vb + vc; + //b2Settings.b2Assert(denom > 0.0); + denom = 1.0 / denom; + //float32 u = va * denom; + var u = va * denom; + //float32 v = vb * denom; + var v = vb * denom; + //float32 w = 1.0f - u - v; + var w = 1.0 - u - v; + //*p1Out = u * p1s[0] + v * p1s[1] + w * p1s[2]; + p1Out.x = u * p1s[0].x + v * p1s[1].x + w * p1s[2].x; + p1Out.y = u * p1s[0].y + v * p1s[1].y + w * p1s[2].y; + //*p2Out = u * p2s[0] + v * p2s[1] + w * p2s[2]; + p2Out.x = u * p2s[0].x + v * p2s[1].x + w * p2s[2].x; + p2Out.y = u * p2s[0].y + v * p2s[1].y + w * p2s[2].y; + return 3; + }; +b2Distance.InPoinsts = function(w, points, pointCount) + { + for (var i = 0; i < pointCount; ++i) + { + if (w.x == points[i].x && w.y == points[i].y) + { + return true; + } + } + + return false; + }; +b2Distance.Distance = function(p1Out, p2Out, shape1, shape2) + { + //b2Vec2 p1s[3], p2s[3]; + var p1s = new Array(3); + var p2s = new Array(3); + //b2Vec2 points[3]; + var points = new Array(3); + //int32 pointCount = 0; + var pointCount = 0; + + //*p1Out = shape1->m_position; + p1Out.SetV(shape1.m_position); + //*p2Out = shape2->m_position; + p2Out.SetV(shape2.m_position); + + var vSqr = 0.0; + var maxIterations = 20; + for (var iter = 0; iter < maxIterations; ++iter) + { + //b2Vec2 v = *p2Out - *p1Out; + var vX = p2Out.x - p1Out.x; + var vY = p2Out.y - p1Out.y; + //b2Vec2 w1 = shape1->Support(v); + var w1 = shape1.Support(vX, vY); + //b2Vec2 w2 = shape2->Support(-v); + var w2 = shape2.Support(-vX, -vY); + //float32 vSqr = b2Dot(v, v); + vSqr = (vX*vX + vY*vY); + //b2Vec2 w = w2 - w1; + var wX = w2.x - w1.x; + var wY = w2.y - w1.y; + //float32 vw = b2Dot(v, w); + var vw = (vX*wX + vY*wY); + //if (vSqr - b2Dot(v, w) <= 0.01f * vSqr) + if (vSqr - b2Dot(vX * wX + vY * wY) <= 0.01 * vSqr) + { + if (pointCount == 0) + { + //*p1Out = w1; + p1Out.SetV(w1); + //*p2Out = w2; + p2Out.SetV(w2); + } + b2Distance.g_GJK_Iterations = iter; + return Math.sqrt(vSqr); + } + + switch (pointCount) + { + case 0: + //p1s[0] = w1; + p1s[0].SetV(w1); + //p2s[0] = w2; + p2s[0].SetV(w2); + points[0] = w; + //*p1Out = p1s[0]; + p1Out.SetV(p1s[0]); + //*p2Out = p2s[0]; + p2Out.SetV(p2s[0]); + ++pointCount; + break; + + case 1: + //p1s[1] = w1; + p1s[1].SetV(w1); + //p2s[1] = w2; + p2s[1].SetV(w2); + //points[1] = w; + points[1].x = wX; + points[1].y = wY; + pointCount = b2Distance.ProcessTwo(p1Out, p2Out, p1s, p2s, points); + break; + + case 2: + //p1s[2] = w1; + p1s[2].SetV(w1); + //p2s[2] = w2; + p2s[2].SetV(w2); + //points[2] = w; + points[2].x = wX; + points[2].y = wY; + pointCount = b2Distance.ProcessThree(p1Out, p2Out, p1s, p2s, points); + break; + } + + // If we have three points, then the origin is in the corresponding triangle. + if (pointCount == 3) + { + b2Distance.g_GJK_Iterations = iter; + return 0.0; + } + + //float32 maxSqr = -FLT_MAX; + var maxSqr = -Number.MAX_VALUE; + for (var i = 0; i < pointCount; ++i) + { + //maxSqr = b2Math.b2Max(maxSqr, b2Dot(points[i], points[i])); + maxSqr = b2Math.b2Max(maxSqr, (points[i].x*points[i].x + points[i].y*points[i].y)); + } + + if (pointCount == 3 || vSqr <= 100.0 * Number.MIN_VALUE * maxSqr) + { + b2Distance.g_GJK_Iterations = iter; + return Math.sqrt(vSqr); + } + } + + b2Distance.g_GJK_Iterations = maxIterations; + return Math.sqrt(vSqr); + }; +b2Distance.g_GJK_Iterations = 0; diff --git a/lib/box2d-js/js/box2d/collision/b2Manifold.js b/lib/box2d-js/js/box2d/collision/b2Manifold.js new file mode 100644 index 0000000..5928151 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2Manifold.js @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +// A manifold for two touching convex shapes. +var b2Manifold = Class.create(); +b2Manifold.prototype = +{ + initialize: function(){ + this.points = new Array(b2Settings.b2_maxManifoldPoints); + for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++){ + this.points[i] = new b2ContactPoint(); + } + this.normal = new b2Vec2(); + }, + points: null, + normal: null, + pointCount: 0}; diff --git a/lib/box2d-js/js/box2d/collision/b2OBB.js b/lib/box2d-js/js/box2d/collision/b2OBB.js new file mode 100644 index 0000000..6de140f --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2OBB.js @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +// A manifold for two touching convex shapes. +var b2OBB = Class.create(); +b2OBB.prototype = +{ + R: new b2Mat22(), + center: new b2Vec2(), + extents: new b2Vec2(), + initialize: function() { + // initialize instance variables for references + this.R = new b2Mat22(); + this.center = new b2Vec2(); + this.extents = new b2Vec2(); + // +}}; diff --git a/lib/box2d-js/js/box2d/collision/b2Pair.js b/lib/box2d-js/js/box2d/collision/b2Pair.js new file mode 100644 index 0000000..d9cb087 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2Pair.js @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +// The pair manager is used by the broad-phase to quickly add/remove/find pairs +// of overlapping proxies. It is based closely on code provided by Pierre Terdiman. +// http: + + + + + +var b2Pair = Class.create(); +b2Pair.prototype = +{ + + + SetBuffered: function() { this.status |= b2Pair.e_pairBuffered; }, + ClearBuffered: function() { this.status &= ~b2Pair.e_pairBuffered; }, + IsBuffered: function(){ return (this.status & b2Pair.e_pairBuffered) == b2Pair.e_pairBuffered; }, + + SetRemoved: function() { this.status |= b2Pair.e_pairRemoved; }, + ClearRemoved: function() { this.status &= ~b2Pair.e_pairRemoved; }, + IsRemoved: function(){ return (this.status & b2Pair.e_pairRemoved) == b2Pair.e_pairRemoved; }, + + SetFinal: function() { this.status |= b2Pair.e_pairFinal; }, + IsFinal: function(){ return (this.status & b2Pair.e_pairFinal) == b2Pair.e_pairFinal; }, + + userData: null, + proxyId1: 0, + proxyId2: 0, + next: 0, + status: 0, + + // STATIC + + // enum + + initialize: function() {}}; +b2Pair.b2_nullPair = b2Settings.USHRT_MAX; +b2Pair.b2_nullProxy = b2Settings.USHRT_MAX; +b2Pair.b2_tableCapacity = b2Settings.b2_maxPairs; +b2Pair.b2_tableMask = b2Pair.b2_tableCapacity - 1; +b2Pair.e_pairBuffered = 0x0001; +b2Pair.e_pairRemoved = 0x0002; +b2Pair.e_pairFinal = 0x0004; diff --git a/lib/box2d-js/js/box2d/collision/b2PairCallback.js b/lib/box2d-js/js/box2d/collision/b2PairCallback.js new file mode 100644 index 0000000..8f0ab92 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2PairCallback.js @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2PairCallback = Class.create(); +b2PairCallback.prototype = +{ + //virtual ~b2PairCallback() {} + + // This returns the new pair user data. + PairAdded: function(proxyUserData1, proxyUserData2){return null}, + + // This should free the pair's user data. In extreme circumstances, it is possible + // this will be called with null pairUserData because the pair never existed. + PairRemoved: function(proxyUserData1, proxyUserData2, pairUserData){}, + initialize: function() {}}; + + diff --git a/lib/box2d-js/js/box2d/collision/b2PairManager.js b/lib/box2d-js/js/box2d/collision/b2PairManager.js new file mode 100644 index 0000000..7a22560 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2PairManager.js @@ -0,0 +1,386 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +// The pair manager is used by the broad-phase to quickly add/remove/find pairs +// of overlapping proxies. It is based closely on code provided by Pierre Terdiman. +// http: + + + + + +var b2PairManager = Class.create(); +b2PairManager.prototype = +{ +//public: + initialize: function(){ + var i = 0; + //b2Settings.b2Assert(b2Math.b2IsPowerOfTwo(b2Pair.b2_tableCapacity) == true); + //b2Settings.b2Assert(b2Pair.b2_tableCapacity >= b2Settings.b2_maxPairs); + this.m_hashTable = new Array(b2Pair.b2_tableCapacity); + for (i = 0; i < b2Pair.b2_tableCapacity; ++i) + { + this.m_hashTable[i] = b2Pair.b2_nullPair; + } + this.m_pairs = new Array(b2Settings.b2_maxPairs); + for (i = 0; i < b2Settings.b2_maxPairs; ++i) + { + this.m_pairs[i] = new b2Pair(); + } + this.m_pairBuffer = new Array(b2Settings.b2_maxPairs); + for (i = 0; i < b2Settings.b2_maxPairs; ++i) + { + this.m_pairBuffer[i] = new b2BufferedPair(); + } + + for (i = 0; i < b2Settings.b2_maxPairs; ++i) + { + this.m_pairs[i].proxyId1 = b2Pair.b2_nullProxy; + this.m_pairs[i].proxyId2 = b2Pair.b2_nullProxy; + this.m_pairs[i].userData = null; + this.m_pairs[i].status = 0; + this.m_pairs[i].next = (i + 1); + } + this.m_pairs[b2Settings.b2_maxPairs-1].next = b2Pair.b2_nullPair; + this.m_pairCount = 0; + }, + //~b2PairManager(); + + Initialize: function(broadPhase, callback){ + this.m_broadPhase = broadPhase; + this.m_callback = callback; + }, + + /* + As proxies are created and moved, many pairs are created and destroyed. Even worse, the same + pair may be added and removed multiple times in a single time step of the physics engine. To reduce + traffic in the pair manager, we try to avoid destroying pairs in the pair manager until the + end of the physics step. This is done by buffering all the this.RemovePair requests. this.AddPair + requests are processed immediately because we need the hash table entry for quick lookup. + + All user user callbacks are delayed until the buffered pairs are confirmed in this.Commit. + This is very important because the user callbacks may be very expensive and client logic + may be harmed if pairs are added and removed within the same time step. + + Buffer a pair for addition. + We may add a pair that is not in the pair manager or pair buffer. + We may add a pair that is already in the pair manager and pair buffer. + If the added pair is not a new pair, then it must be in the pair buffer (because this.RemovePair was called). + */ + AddBufferedPair: function(proxyId1, proxyId2){ + //b2Settings.b2Assert(id1 != b2_nullProxy && id2 != b2_nullProxy); + //b2Settings.b2Assert(this.m_pairBufferCount < b2_maxPairs); + + var pair = this.AddPair(proxyId1, proxyId2); + + // If this pair is not in the pair buffer ... + if (pair.IsBuffered() == false) + { + // This must be a newly added pair. + //b2Settings.b2Assert(pair.IsFinal() == false); + + // Add it to the pair buffer. + pair.SetBuffered(); + this.m_pairBuffer[this.m_pairBufferCount].proxyId1 = pair.proxyId1; + this.m_pairBuffer[this.m_pairBufferCount].proxyId2 = pair.proxyId2; + ++this.m_pairBufferCount; + + //b2Settings.b2Assert(this.m_pairBufferCount <= this.m_pairCount); + } + + // Confirm this pair for the subsequent call to this.Commit. + pair.ClearRemoved(); + + if (b2BroadPhase.s_validate) + { + this.ValidateBuffer(); + } + }, + + // Buffer a pair for removal. + RemoveBufferedPair: function(proxyId1, proxyId2){ + //b2Settings.b2Assert(id1 != b2_nullProxy && id2 != b2_nullProxy); + //b2Settings.b2Assert(this.m_pairBufferCount < b2_maxPairs); + + var pair = this.Find(proxyId1, proxyId2); + + if (pair == null) + { + // The pair never existed. This is legal (due to collision filtering). + return; + } + + // If this pair is not in the pair buffer ... + if (pair.IsBuffered() == false) + { + // This must be an old pair. + //b2Settings.b2Assert(pair.IsFinal() == true); + + pair.SetBuffered(); + this.m_pairBuffer[this.m_pairBufferCount].proxyId1 = pair.proxyId1; + this.m_pairBuffer[this.m_pairBufferCount].proxyId2 = pair.proxyId2; + ++this.m_pairBufferCount; + + //b2Settings.b2Assert(this.m_pairBufferCount <= this.m_pairCount); + } + + pair.SetRemoved(); + + if (b2BroadPhase.s_validate) + { + this.ValidateBuffer(); + } + }, + + Commit: function(){ + var i = 0; + + var removeCount = 0; + + var proxies = this.m_broadPhase.m_proxyPool; + + for (i = 0; i < this.m_pairBufferCount; ++i) + { + var pair = this.Find(this.m_pairBuffer[i].proxyId1, this.m_pairBuffer[i].proxyId2); + //b2Settings.b2Assert(pair.IsBuffered()); + pair.ClearBuffered(); + + //b2Settings.b2Assert(pair.proxyId1 < b2Settings.b2_maxProxies && pair.proxyId2 < b2Settings.b2_maxProxies); + + var proxy1 = proxies[ pair.proxyId1 ]; + var proxy2 = proxies[ pair.proxyId2 ]; + + //b2Settings.b2Assert(proxy1.IsValid()); + //b2Settings.b2Assert(proxy2.IsValid()); + + if (pair.IsRemoved()) + { + // It is possible a pair was added then removed before a commit. Therefore, + // we should be careful not to tell the user the pair was removed when the + // the user didn't receive a matching add. + if (pair.IsFinal() == true) + { + this.m_callback.PairRemoved(proxy1.userData, proxy2.userData, pair.userData); + } + + // Store the ids so we can actually remove the pair below. + this.m_pairBuffer[removeCount].proxyId1 = pair.proxyId1; + this.m_pairBuffer[removeCount].proxyId2 = pair.proxyId2; + ++removeCount; + } + else + { + //b2Settings.b2Assert(this.m_broadPhase.TestOverlap(proxy1, proxy2) == true); + + if (pair.IsFinal() == false) + { + pair.userData = this.m_callback.PairAdded(proxy1.userData, proxy2.userData); + pair.SetFinal(); + } + } + } + + for (i = 0; i < removeCount; ++i) + { + this.RemovePair(this.m_pairBuffer[i].proxyId1, this.m_pairBuffer[i].proxyId2); + } + + this.m_pairBufferCount = 0; + + if (b2BroadPhase.s_validate) + { + this.ValidateTable(); + } + }, + +//private: + + // Add a pair and return the new pair. If the pair already exists, + // no new pair is created and the old one is returned. + AddPair: function(proxyId1, proxyId2){ + + if (proxyId1 > proxyId2){ + var temp = proxyId1; + proxyId1 = proxyId2; + proxyId2 = temp; + //b2Math.b2Swap(p1, p2); + } + + var hash = b2PairManager.Hash(proxyId1, proxyId2) & b2Pair.b2_tableMask; + + //var pairIndex = this.FindHash(proxyId1, proxyId2, hash); + var pair = pair = this.FindHash(proxyId1, proxyId2, hash); + + if (pair != null) + { + return pair; + } + + //b2Settings.b2Assert(this.m_pairCount < b2Settings.b2_maxPairs && this.m_freePair != b2_nullPair); + + var pIndex = this.m_freePair; + pair = this.m_pairs[pIndex]; + this.m_freePair = pair.next; + + pair.proxyId1 = proxyId1; + pair.proxyId2 = proxyId2; + pair.status = 0; + pair.userData = null; + pair.next = this.m_hashTable[hash]; + + this.m_hashTable[hash] = pIndex; + + ++this.m_pairCount; + + return pair; + }, + + // Remove a pair, return the pair's userData. + RemovePair: function(proxyId1, proxyId2){ + + //b2Settings.b2Assert(this.m_pairCount > 0); + + if (proxyId1 > proxyId2){ + var temp = proxyId1; + proxyId1 = proxyId2; + proxyId2 = temp; + //b2Math.b2Swap(proxyId1, proxyId2); + } + + var hash = b2PairManager.Hash(proxyId1, proxyId2) & b2Pair.b2_tableMask; + + var node = this.m_hashTable[hash]; + var pNode = null; + + while (node != b2Pair.b2_nullPair) + { + if (b2PairManager.Equals(this.m_pairs[node], proxyId1, proxyId2)) + { + var index = node; + + //*node = this.m_pairs[*node].next; + if (pNode){ + pNode.next = this.m_pairs[node].next; + } + else{ + this.m_hashTable[hash] = this.m_pairs[node].next; + } + + var pair = this.m_pairs[ index ]; + var userData = pair.userData; + + // Scrub + pair.next = this.m_freePair; + pair.proxyId1 = b2Pair.b2_nullProxy; + pair.proxyId2 = b2Pair.b2_nullProxy; + pair.userData = null; + pair.status = 0; + + this.m_freePair = index; + --this.m_pairCount; + return userData; + } + else + { + //node = &this.m_pairs[*node].next; + pNode = this.m_pairs[node]; + node = pNode.next; + } + } + + //b2Settings.b2Assert(false); + return null; + }, + + Find: function(proxyId1, proxyId2){ + + if (proxyId1 > proxyId2){ + var temp = proxyId1; + proxyId1 = proxyId2; + proxyId2 = temp; + //b2Math.b2Swap(proxyId1, proxyId2); + } + + var hash = b2PairManager.Hash(proxyId1, proxyId2) & b2Pair.b2_tableMask; + + return this.FindHash(proxyId1, proxyId2, hash); + }, + FindHash: function(proxyId1, proxyId2, hash){ + var index = this.m_hashTable[hash]; + + while( index != b2Pair.b2_nullPair && b2PairManager.Equals(this.m_pairs[index], proxyId1, proxyId2) == false) + { + index = this.m_pairs[index].next; + } + + if ( index == b2Pair.b2_nullPair ) + { + return null; + } + + //b2Settings.b2Assert(index < b2_maxPairs); + + return this.m_pairs[ index ]; + }, + + ValidateBuffer: function(){ + // DEBUG + }, + + ValidateTable: function(){ + // DEBUG + }, + +//public: + m_broadPhase: null, + m_callback: null, + m_pairs: null, + m_freePair: 0, + m_pairCount: 0, + + m_pairBuffer: null, + m_pairBufferCount: 0, + + m_hashTable: null + + +// static + // Thomas Wang's hash, see: http: + + + +}; +b2PairManager.Hash = function(proxyId1, proxyId2) + { + var key = ((proxyId2 << 16) & 0xffff0000) | proxyId1; + key = ~key + ((key << 15) & 0xFFFF8000); + key = key ^ ((key >> 12) & 0x000fffff); + key = key + ((key << 2) & 0xFFFFFFFC); + key = key ^ ((key >> 4) & 0x0fffffff); + key = key * 2057; + key = key ^ ((key >> 16) & 0x0000ffff); + return key; + }; +b2PairManager.Equals = function(pair, proxyId1, proxyId2) + { + return (pair.proxyId1 == proxyId1 && pair.proxyId2 == proxyId2); + }; +b2PairManager.EqualsPair = function(pair1, pair2) + { + return pair1.proxyId1 == pair2.proxyId1 && pair1.proxyId2 == pair2.proxyId2; + }; diff --git a/lib/box2d-js/js/box2d/collision/b2Proxy.js b/lib/box2d-js/js/box2d/collision/b2Proxy.js new file mode 100644 index 0000000..625d75d --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/b2Proxy.js @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2Proxy = Class.create(); +b2Proxy.prototype = { + GetNext: function(){ return this.lowerBounds[0]; }, + SetNext: function(next) { this.lowerBounds[0] = next /*& 0x0000ffff*/; }, + + IsValid: function(){ return this.overlapCount != b2BroadPhase.b2_invalid; }, + + lowerBounds: [/*uint*/(0), /*uint*/(0)], + upperBounds: [/*uint*/(0), /*uint*/(0)], + overlapCount: 0, + timeStamp: 0, + + userData: null, + + initialize: function() { + // initialize instance variables for references + this.lowerBounds = [/*uint*/(0), /*uint*/(0)]; + this.upperBounds = [/*uint*/(0), /*uint*/(0)]; + // +}} diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2BoxDef.js b/lib/box2d-js/js/box2d/collision/shapes/b2BoxDef.js new file mode 100644 index 0000000..18bf313 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2BoxDef.js @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2BoxDef = Class.create(); +Object.extend(b2BoxDef.prototype, b2ShapeDef.prototype); +Object.extend(b2BoxDef.prototype, +{ + initialize: function() + { + // The constructor for b2ShapeDef + this.type = b2Shape.e_unknownShape; + this.userData = null; + this.localPosition = new b2Vec2(0.0, 0.0); + this.localRotation = 0.0; + this.friction = 0.2; + this.restitution = 0.0; + this.density = 0.0; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.groupIndex = 0; + // + + this.type = b2Shape.e_boxShape; + this.extents = new b2Vec2(1.0, 1.0); + }, + + extents: null}); + diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2CircleDef.js b/lib/box2d-js/js/box2d/collision/shapes/b2CircleDef.js new file mode 100644 index 0000000..1b01b60 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2CircleDef.js @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2CircleDef = Class.create(); +Object.extend(b2CircleDef.prototype, b2ShapeDef.prototype); +Object.extend(b2CircleDef.prototype, +{ + initialize: function() + { + // The constructor for b2ShapeDef + this.type = b2Shape.e_unknownShape; + this.userData = null; + this.localPosition = new b2Vec2(0.0, 0.0); + this.localRotation = 0.0; + this.friction = 0.2; + this.restitution = 0.0; + this.density = 0.0; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.groupIndex = 0; + // + + this.type = b2Shape.e_circleShape; + this.radius = 1.0; + }, + + radius: null}); + diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2CircleShape.js b/lib/box2d-js/js/box2d/collision/shapes/b2CircleShape.js new file mode 100644 index 0000000..8daa45c --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2CircleShape.js @@ -0,0 +1,198 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2CircleShape = Class.create(); +Object.extend(b2CircleShape.prototype, b2Shape.prototype); +Object.extend(b2CircleShape.prototype, +{ + TestPoint: function(p){ + //var d = b2Math.SubtractVV(p, this.m_position); + var d = new b2Vec2(); + d.SetV(p); + d.Subtract(this.m_position); + return b2Math.b2Dot(d, d) <= this.m_radius * this.m_radius; + }, + + //--------------- Internals Below ------------------- + + initialize: function(def, body, localCenter){ + // initialize instance variables for references + this.m_R = new b2Mat22(); + this.m_position = new b2Vec2(); + // + + // The constructor for b2Shape + this.m_userData = def.userData; + + this.m_friction = def.friction; + this.m_restitution = def.restitution; + this.m_body = body; + + this.m_proxyId = b2Pair.b2_nullProxy; + + this.m_maxRadius = 0.0; + + this.m_categoryBits = def.categoryBits; + this.m_maskBits = def.maskBits; + this.m_groupIndex = def.groupIndex; + // + + // initialize instance variables for references + this.m_localPosition = new b2Vec2(); + // + + //super(def, body); + + //b2Settings.b2Assert(def.type == b2Shape.e_circleShape); + var circle = def; + + //this.m_localPosition = def.localPosition - localCenter; + this.m_localPosition.Set(def.localPosition.x - localCenter.x, def.localPosition.y - localCenter.y); + this.m_type = b2Shape.e_circleShape; + this.m_radius = circle.radius; + + this.m_R.SetM(this.m_body.m_R); + //b2Vec2 r = b2Mul(this.m_body->this.m_R, this.m_localPosition); + var rX = this.m_R.col1.x * this.m_localPosition.x + this.m_R.col2.x * this.m_localPosition.y; + var rY = this.m_R.col1.y * this.m_localPosition.x + this.m_R.col2.y * this.m_localPosition.y; + //this.m_position = this.m_body->this.m_position + r; + this.m_position.x = this.m_body.m_position.x + rX; + this.m_position.y = this.m_body.m_position.y + rY; + //this.m_maxRadius = r.Length() + this.m_radius; + this.m_maxRadius = Math.sqrt(rX*rX+rY*rY) + this.m_radius; + + var aabb = new b2AABB(); + aabb.minVertex.Set(this.m_position.x - this.m_radius, this.m_position.y - this.m_radius); + aabb.maxVertex.Set(this.m_position.x + this.m_radius, this.m_position.y + this.m_radius); + + var broadPhase = this.m_body.m_world.m_broadPhase; + if (broadPhase.InRange(aabb)) + { + this.m_proxyId = broadPhase.CreateProxy(aabb, this); + } + else + { + this.m_proxyId = b2Pair.b2_nullProxy; + } + + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + this.m_body.Freeze(); + } + }, + + Synchronize: function(position1, R1, position2, R2){ + this.m_R.SetM(R2); + //this.m_position = position2 + b2Mul(R2, this.m_localPosition); + this.m_position.x = (R2.col1.x * this.m_localPosition.x + R2.col2.x * this.m_localPosition.y) + position2.x; + this.m_position.y = (R2.col1.y * this.m_localPosition.x + R2.col2.y * this.m_localPosition.y) + position2.y; + + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + return; + } + + // Compute an AABB that covers the swept shape (may miss some rotation effect). + //b2Vec2 p1 = position1 + b2Mul(R1, this.m_localPosition); + var p1X = position1.x + (R1.col1.x * this.m_localPosition.x + R1.col2.x * this.m_localPosition.y); + var p1Y = position1.y + (R1.col1.y * this.m_localPosition.x + R1.col2.y * this.m_localPosition.y); + //b2Vec2 lower = b2Min(p1, this.m_position); + var lowerX = Math.min(p1X, this.m_position.x); + var lowerY = Math.min(p1Y, this.m_position.y); + //b2Vec2 upper = b2Max(p1, this.m_position); + var upperX = Math.max(p1X, this.m_position.x); + var upperY = Math.max(p1Y, this.m_position.y); + + var aabb = new b2AABB(); + aabb.minVertex.Set(lowerX - this.m_radius, lowerY - this.m_radius); + aabb.maxVertex.Set(upperX + this.m_radius, upperY + this.m_radius); + + var broadPhase = this.m_body.m_world.m_broadPhase; + if (broadPhase.InRange(aabb)) + { + broadPhase.MoveProxy(this.m_proxyId, aabb); + } + else + { + this.m_body.Freeze(); + } + }, + + QuickSync: function(position, R){ + this.m_R.SetM(R); + //this.m_position = position + b2Mul(R, this.m_localPosition); + this.m_position.x = (R.col1.x * this.m_localPosition.x + R.col2.x * this.m_localPosition.y) + position.x; + this.m_position.y = (R.col1.y * this.m_localPosition.x + R.col2.y * this.m_localPosition.y) + position.y; + }, + + + ResetProxy: function(broadPhase) + { + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + return; + } + + var proxy = broadPhase.GetProxy(this.m_proxyId); + + broadPhase.DestroyProxy(this.m_proxyId); + proxy = null; + + var aabb = new b2AABB(); + aabb.minVertex.Set(this.m_position.x - this.m_radius, this.m_position.y - this.m_radius); + aabb.maxVertex.Set(this.m_position.x + this.m_radius, this.m_position.y + this.m_radius); + + if (broadPhase.InRange(aabb)) + { + this.m_proxyId = broadPhase.CreateProxy(aabb, this); + } + else + { + this.m_proxyId = b2Pair.b2_nullProxy; + } + + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + this.m_body.Freeze(); + } + }, + + + Support: function(dX, dY, out) + { + //b2Vec2 u = d; + //u.Normalize(); + var len = Math.sqrt(dX*dX + dY*dY); + dX /= len; + dY /= len; + //return this.m_position + this.m_radius * u; + out.Set( this.m_position.x + this.m_radius*dX, + this.m_position.y + this.m_radius*dY); + }, + + + // Local position in parent body + m_localPosition: new b2Vec2(), + m_radius: null}); + diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2MassData.js b/lib/box2d-js/js/box2d/collision/shapes/b2MassData.js new file mode 100644 index 0000000..08a773f --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2MassData.js @@ -0,0 +1,36 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2MassData = Class.create(); +b2MassData.prototype = +{ + mass: 0.0, + center: new b2Vec2(0,0), + I: 0.0, + + initialize: function() { + // initialize instance variables for references + this.center = new b2Vec2(0,0); + // +}} diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2PolyDef.js b/lib/box2d-js/js/box2d/collision/shapes/b2PolyDef.js new file mode 100644 index 0000000..5b95b35 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2PolyDef.js @@ -0,0 +1,58 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2PolyDef = Class.create(); +Object.extend(b2PolyDef.prototype, b2ShapeDef.prototype); +Object.extend(b2PolyDef.prototype, +{ + initialize: function() + { + // The constructor for b2ShapeDef + this.type = b2Shape.e_unknownShape; + this.userData = null; + this.localPosition = new b2Vec2(0.0, 0.0); + this.localRotation = 0.0; + this.friction = 0.2; + this.restitution = 0.0; + this.density = 0.0; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.groupIndex = 0; + // + + // initialize instance variables for references + this.vertices = new Array(b2Settings.b2_maxPolyVertices); + // + + this.type = b2Shape.e_polyShape; + this.vertexCount = 0; + + for (var i = 0; i < b2Settings.b2_maxPolyVertices; i++){ + this.vertices[i] = new b2Vec2(); + } + }, + + vertices: new Array(b2Settings.b2_maxPolyVertices), + vertexCount: 0}); + diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2PolyShape.js b/lib/box2d-js/js/box2d/collision/shapes/b2PolyShape.js new file mode 100644 index 0000000..085dae4 --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2PolyShape.js @@ -0,0 +1,492 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +// A convex polygon. The position of the polygon (m_position) is the +// position of the centroid. The vertices of the incoming polygon are pre-rotated +// according to the local rotation. The vertices are also shifted to be centered +// on the centroid. Since the local rotation is absorbed into the vertex +// coordinates, the polygon rotation is equal to the body rotation. However, +// the polygon position is centered on the polygon centroid. This simplifies +// some collision algorithms. + +var b2PolyShape = Class.create(); +Object.extend(b2PolyShape.prototype, b2Shape.prototype); +Object.extend(b2PolyShape.prototype, +{ + TestPoint: function(p){ + + //var pLocal = b2Math.b2MulTMV(this.m_R, b2Math.SubtractVV(p, this.m_position)); + var pLocal = new b2Vec2(); + pLocal.SetV(p); + pLocal.Subtract(this.m_position); + pLocal.MulTM(this.m_R); + + for (var i = 0; i < this.m_vertexCount; ++i) + { + //var dot = b2Math.b2Dot(this.m_normals[i], b2Math.SubtractVV(pLocal, this.m_vertices[i])); + var tVec = new b2Vec2(); + tVec.SetV(pLocal); + tVec.Subtract(this.m_vertices[i]); + + var dot = b2Math.b2Dot(this.m_normals[i], tVec); + if (dot > 0.0) + { + return false; + } + } + + return true; + }, + + //--------------- Internals Below ------------------- + // Temp vec for b2Shape.PolyCentroid + + initialize: function(def, body, newOrigin){ + // initialize instance variables for references + this.m_R = new b2Mat22(); + this.m_position = new b2Vec2(); + // + + // The constructor for b2Shape + this.m_userData = def.userData; + + this.m_friction = def.friction; + this.m_restitution = def.restitution; + this.m_body = body; + + this.m_proxyId = b2Pair.b2_nullProxy; + + this.m_maxRadius = 0.0; + + this.m_categoryBits = def.categoryBits; + this.m_maskBits = def.maskBits; + this.m_groupIndex = def.groupIndex; + // + + // initialize instance variables for references + this.syncAABB = new b2AABB(); + this.syncMat = new b2Mat22(); + this.m_localCentroid = new b2Vec2(); + this.m_localOBB = new b2OBB(); + // + + + //super(def, body); + + var i = 0; + + + var hX; + var hY; + + var tVec; + + var aabb = new b2AABB(); + + // Vertices + this.m_vertices = new Array(b2Settings.b2_maxPolyVertices); + this.m_coreVertices = new Array(b2Settings.b2_maxPolyVertices); + //for (i = 0; i < b2Settings.b2_maxPolyVertices; i++) + // this.m_vertices[i] = new b2Vec2(); + + // Normals + this.m_normals = new Array(b2Settings.b2_maxPolyVertices); + //for (i = 0; i < b2Settings.b2_maxPolyVertices; i++) + // this.m_normals[i] = new b2Vec2(); + + //b2Settings.b2Assert(def.type == b2Shape.e_boxShape || def.type == b2Shape.e_polyShape); + this.m_type = b2Shape.e_polyShape; + + var localR = new b2Mat22(def.localRotation); + + // Get the vertices transformed into the body frame. + if (def.type == b2Shape.e_boxShape) + { + //this.m_localCentroid = def.localPosition - newOrigin; + this.m_localCentroid.x = def.localPosition.x - newOrigin.x; + this.m_localCentroid.y = def.localPosition.y - newOrigin.y; + + var box = def; + this.m_vertexCount = 4; + hX = box.extents.x; + hY = box.extents.y; + + //hc.x = b2Max(0.0f, h.x - 2.0f * b2_linearSlop); + var hcX = Math.max(0.0, hX - 2.0 * b2Settings.b2_linearSlop); + //hc.y = b2Max(0.0f, h.y - 2.0f * b2_linearSlop); + var hcY = Math.max(0.0, hY - 2.0 * b2Settings.b2_linearSlop); + + //this.m_vertices[0] = b2Mul(localR, b2Vec2(h.x, h.y)); + tVec = this.m_vertices[0] = new b2Vec2(); + tVec.x = localR.col1.x * hX + localR.col2.x * hY; + tVec.y = localR.col1.y * hX + localR.col2.y * hY; + //this.m_vertices[1] = b2Mul(localR, b2Vec2(-h.x, h.y)); + tVec = this.m_vertices[1] = new b2Vec2(); + tVec.x = localR.col1.x * -hX + localR.col2.x * hY; + tVec.y = localR.col1.y * -hX + localR.col2.y * hY; + //this.m_vertices[2] = b2Mul(localR, b2Vec2(-h.x, -h.y)); + tVec = this.m_vertices[2] = new b2Vec2(); + tVec.x = localR.col1.x * -hX + localR.col2.x * -hY; + tVec.y = localR.col1.y * -hX + localR.col2.y * -hY; + //this.m_vertices[3] = b2Mul(localR, b2Vec2(h.x, -h.y)); + tVec = this.m_vertices[3] = new b2Vec2(); + tVec.x = localR.col1.x * hX + localR.col2.x * -hY; + tVec.y = localR.col1.y * hX + localR.col2.y * -hY; + + //this.m_coreVertices[0] = b2Mul(localR, b2Vec2(hc.x, hc.y)); + tVec = this.m_coreVertices[0] = new b2Vec2(); + tVec.x = localR.col1.x * hcX + localR.col2.x * hcY; + tVec.y = localR.col1.y * hcX + localR.col2.y * hcY; + //this.m_coreVertices[1] = b2Mul(localR, b2Vec2(-hc.x, hc.y)); + tVec = this.m_coreVertices[1] = new b2Vec2(); + tVec.x = localR.col1.x * -hcX + localR.col2.x * hcY; + tVec.y = localR.col1.y * -hcX + localR.col2.y * hcY; + //this.m_coreVertices[2] = b2Mul(localR, b2Vec2(-hc.x, -hc.y)); + tVec = this.m_coreVertices[2] = new b2Vec2(); + tVec.x = localR.col1.x * -hcX + localR.col2.x * -hcY; + tVec.y = localR.col1.y * -hcX + localR.col2.y * -hcY; + //this.m_coreVertices[3] = b2Mul(localR, b2Vec2(hc.x, -hc.y)); + tVec = this.m_coreVertices[3] = new b2Vec2(); + tVec.x = localR.col1.x * hcX + localR.col2.x * -hcY; + tVec.y = localR.col1.y * hcX + localR.col2.y * -hcY; + } + else + { + var poly = def; + + this.m_vertexCount = poly.vertexCount; + //b2Settings.b2Assert(3 <= this.m_vertexCount && this.m_vertexCount <= b2Settings.b2_maxPolyVertices); + //b2Vec2 centroid = b2Shape.PolyCentroid(poly->vertices, poly->vertexCount); + b2Shape.PolyCentroid(poly.vertices, poly.vertexCount, b2PolyShape.tempVec); + var centroidX = b2PolyShape.tempVec.x; + var centroidY = b2PolyShape.tempVec.y; + //this.m_localCentroid = def->localPosition + b2Mul(localR, centroid) - newOrigin; + this.m_localCentroid.x = def.localPosition.x + (localR.col1.x * centroidX + localR.col2.x * centroidY) - newOrigin.x; + this.m_localCentroid.y = def.localPosition.y + (localR.col1.y * centroidX + localR.col2.y * centroidY) - newOrigin.y; + + for (i = 0; i < this.m_vertexCount; ++i) + { + this.m_vertices[i] = new b2Vec2(); + this.m_coreVertices[i] = new b2Vec2(); + + //this.m_vertices[i] = b2Mul(localR, poly->vertices[i] - centroid); + hX = poly.vertices[i].x - centroidX; + hY = poly.vertices[i].y - centroidY; + this.m_vertices[i].x = localR.col1.x * hX + localR.col2.x * hY; + this.m_vertices[i].y = localR.col1.y * hX + localR.col2.y * hY; + + //b2Vec2 u = this.m_vertices[i]; + var uX = this.m_vertices[i].x; + var uY = this.m_vertices[i].y; + //float32 length = u.Length(); + var length = Math.sqrt(uX*uX + uY*uY); + if (length > Number.MIN_VALUE) + { + uX *= 1.0 / length; + uY *= 1.0 / length; + } + + //this.m_coreVertices[i] = this.m_vertices[i] - 2.0f * b2_linearSlop * u; + this.m_coreVertices[i].x = this.m_vertices[i].x - 2.0 * b2Settings.b2_linearSlop * uX; + this.m_coreVertices[i].y = this.m_vertices[i].y - 2.0 * b2Settings.b2_linearSlop * uY; + } + + } + + // Compute bounding box. TODO_ERIN optimize OBB + //var minVertex = new b2Vec2(Number.MAX_VALUE, Number.MAX_VALUE); + var minVertexX = Number.MAX_VALUE; + var minVertexY = Number.MAX_VALUE; + var maxVertexX = -Number.MAX_VALUE; + var maxVertexY = -Number.MAX_VALUE; + this.m_maxRadius = 0.0; + for (i = 0; i < this.m_vertexCount; ++i) + { + var v = this.m_vertices[i]; + //minVertex = b2Math.b2MinV(minVertex, this.m_vertices[i]); + minVertexX = Math.min(minVertexX, v.x); + minVertexY = Math.min(minVertexY, v.y); + //maxVertex = b2Math.b2MaxV(maxVertex, this.m_vertices[i]); + maxVertexX = Math.max(maxVertexX, v.x); + maxVertexY = Math.max(maxVertexY, v.y); + //this.m_maxRadius = b2Max(this.m_maxRadius, v.Length()); + this.m_maxRadius = Math.max(this.m_maxRadius, v.Length()); + } + + this.m_localOBB.R.SetIdentity(); + //this.m_localOBB.center = 0.5 * (minVertex + maxVertex); + this.m_localOBB.center.Set((minVertexX + maxVertexX) * 0.5, (minVertexY + maxVertexY) * 0.5); + //this.m_localOBB.extents = 0.5 * (maxVertex - minVertex); + this.m_localOBB.extents.Set((maxVertexX - minVertexX) * 0.5, (maxVertexY - minVertexY) * 0.5); + + // Compute the edge normals and next index map. + var i1 = 0; + var i2 = 0; + for (i = 0; i < this.m_vertexCount; ++i) + { + this.m_normals[i] = new b2Vec2(); + i1 = i; + i2 = i + 1 < this.m_vertexCount ? i + 1 : 0; + //b2Vec2 edge = this.m_vertices[i2] - this.m_vertices[i1]; + //var edgeX = this.m_vertices[i2].x - this.m_vertices[i1].x; + //var edgeY = this.m_vertices[i2].y - this.m_vertices[i1].y; + //this.m_normals[i] = b2Cross(edge, 1.0f); + this.m_normals[i].x = this.m_vertices[i2].y - this.m_vertices[i1].y; + this.m_normals[i].y = -(this.m_vertices[i2].x - this.m_vertices[i1].x); + this.m_normals[i].Normalize(); + } + + // Ensure the polygon in convex. TODO_ERIN compute convex hull. + for (i = 0; i < this.m_vertexCount; ++i) + { + i1 = i; + i2 = i + 1 < this.m_vertexCount ? i + 1 : 0; + + //b2Settings.b2Assert(b2Math.b2CrossVV(this.m_normals[i1], this.m_normals[i2]) > Number.MIN_VALUE); + } + + this.m_R.SetM(this.m_body.m_R); + //this.m_position.SetV( this.m_body.m_position + b2Mul(this.m_body->this.m_R, this.m_localCentroid) ); + this.m_position.x = this.m_body.m_position.x + (this.m_R.col1.x * this.m_localCentroid.x + this.m_R.col2.x * this.m_localCentroid.y); + this.m_position.y = this.m_body.m_position.y + (this.m_R.col1.y * this.m_localCentroid.x + this.m_R.col2.y * this.m_localCentroid.y); + + //var R = b2Math.b2MulMM(this.m_R, this.m_localOBB.R); + //R.col1 = b2MulMV(this.m_R, this.m_localOBB.R.col1); + b2PolyShape.tAbsR.col1.x = this.m_R.col1.x * this.m_localOBB.R.col1.x + this.m_R.col2.x * this.m_localOBB.R.col1.y; + b2PolyShape.tAbsR.col1.y = this.m_R.col1.y * this.m_localOBB.R.col1.x + this.m_R.col2.y * this.m_localOBB.R.col1.y; + //R.col2 = b2MulMV(this.m_R, this.m_localOBB.R.col2) + b2PolyShape.tAbsR.col2.x = this.m_R.col1.x * this.m_localOBB.R.col2.x + this.m_R.col2.x * this.m_localOBB.R.col2.y; + b2PolyShape.tAbsR.col2.y = this.m_R.col1.y * this.m_localOBB.R.col2.x + this.m_R.col2.y * this.m_localOBB.R.col2.y; + //var absR = b2Math.b2AbsM(R); + b2PolyShape.tAbsR.Abs() + + //h = b2Math.b2MulMV(b2PolyShape.tAbsR, this.m_localOBB.extents); + hX = b2PolyShape.tAbsR.col1.x * this.m_localOBB.extents.x + b2PolyShape.tAbsR.col2.x * this.m_localOBB.extents.y; + hY = b2PolyShape.tAbsR.col1.y * this.m_localOBB.extents.x + b2PolyShape.tAbsR.col2.y * this.m_localOBB.extents.y; + + //var position = this.m_position + b2Mul(this.m_R, this.m_localOBB.center); + var positionX = this.m_position.x + (this.m_R.col1.x * this.m_localOBB.center.x + this.m_R.col2.x * this.m_localOBB.center.y); + var positionY = this.m_position.y + (this.m_R.col1.y * this.m_localOBB.center.x + this.m_R.col2.y * this.m_localOBB.center.y); + + //aabb.minVertex = b2Math.SubtractVV(this.m_position, h); + aabb.minVertex.x = positionX - hX; + aabb.minVertex.y = positionY - hY; + //aabb.maxVertex = b2Math.AddVV(this.m_position, h); + aabb.maxVertex.x = positionX + hX; + aabb.maxVertex.y = positionY + hY; + + var broadPhase = this.m_body.m_world.m_broadPhase; + if (broadPhase.InRange(aabb)) + { + this.m_proxyId = broadPhase.CreateProxy(aabb, this); + } + else + { + this.m_proxyId = b2Pair.b2_nullProxy; + } + + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + this.m_body.Freeze(); + } + }, + + // Temp AABB for Synch function + syncAABB: new b2AABB(), + syncMat: new b2Mat22(), + Synchronize: function(position1, R1, position2, R2){ + // The body transform is copied for convenience. + this.m_R.SetM(R2); + //this.m_position = this.m_body->this.m_position + b2Mul(this.m_body->this.m_R, this.m_localCentroid) + this.m_position.x = this.m_body.m_position.x + (R2.col1.x * this.m_localCentroid.x + R2.col2.x * this.m_localCentroid.y); + this.m_position.y = this.m_body.m_position.y + (R2.col1.y * this.m_localCentroid.x + R2.col2.y * this.m_localCentroid.y); + + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + return; + } + + //b2AABB aabb1, aabb2; + var hX; + var hY; + + //b2Mat22 obbR = b2Mul(R1, this.m_localOBB.R); + var v1 = R1.col1; + var v2 = R1.col2; + var v3 = this.m_localOBB.R.col1; + var v4 = this.m_localOBB.R.col2; + //this.syncMat.col1 = b2MulMV(R1, this.m_localOBB.R.col1); + this.syncMat.col1.x = v1.x * v3.x + v2.x * v3.y; + this.syncMat.col1.y = v1.y * v3.x + v2.y * v3.y; + //this.syncMat.col2 = b2MulMV(R1, this.m_localOBB.R.col2); + this.syncMat.col2.x = v1.x * v4.x + v2.x * v4.y; + this.syncMat.col2.y = v1.y * v4.x + v2.y * v4.y; + //b2Mat22 absR = b2Abs(obbR); + this.syncMat.Abs(); + //b2Vec2 center = position1 + b2Mul(R1, this.m_localCentroid + this.m_localOBB.center); + hX = this.m_localCentroid.x + this.m_localOBB.center.x; + hY = this.m_localCentroid.y + this.m_localOBB.center.y; + var centerX = position1.x + (R1.col1.x * hX + R1.col2.x * hY); + var centerY = position1.y + (R1.col1.y * hX + R1.col2.y * hY); + //b2Vec2 h = b2Mul(this.syncMat, this.m_localOBB.extents); + hX = this.syncMat.col1.x * this.m_localOBB.extents.x + this.syncMat.col2.x * this.m_localOBB.extents.y; + hY = this.syncMat.col1.y * this.m_localOBB.extents.x + this.syncMat.col2.y * this.m_localOBB.extents.y; + //aabb1.minVertex = center - h; + this.syncAABB.minVertex.x = centerX - hX; + this.syncAABB.minVertex.y = centerY - hY; + //aabb1.maxVertex = center + h; + this.syncAABB.maxVertex.x = centerX + hX; + this.syncAABB.maxVertex.y = centerY + hY; + + //b2Mat22 obbR = b2Mul(R2, this.m_localOBB.R); + v1 = R2.col1; + v2 = R2.col2; + v3 = this.m_localOBB.R.col1; + v4 = this.m_localOBB.R.col2; + //this.syncMat.col1 = b2MulMV(R1, this.m_localOBB.R.col1); + this.syncMat.col1.x = v1.x * v3.x + v2.x * v3.y; + this.syncMat.col1.y = v1.y * v3.x + v2.y * v3.y; + //this.syncMat.col2 = b2MulMV(R1, this.m_localOBB.R.col2); + this.syncMat.col2.x = v1.x * v4.x + v2.x * v4.y; + this.syncMat.col2.y = v1.y * v4.x + v2.y * v4.y; + //b2Mat22 absR = b2Abs(obbR); + this.syncMat.Abs(); + //b2Vec2 center = position2 + b2Mul(R2, this.m_localCentroid + this.m_localOBB.center); + hX = this.m_localCentroid.x + this.m_localOBB.center.x; + hY = this.m_localCentroid.y + this.m_localOBB.center.y; + centerX = position2.x + (R2.col1.x * hX + R2.col2.x * hY); + centerY = position2.y + (R2.col1.y * hX + R2.col2.y * hY); + //b2Vec2 h = b2Mul(absR, this.m_localOBB.extents); + hX = this.syncMat.col1.x * this.m_localOBB.extents.x + this.syncMat.col2.x * this.m_localOBB.extents.y; + hY = this.syncMat.col1.y * this.m_localOBB.extents.x + this.syncMat.col2.y * this.m_localOBB.extents.y; + //aabb2.minVertex = center - h; + //aabb2.maxVertex = center + h; + + //aabb.minVertex = b2Min(aabb1.minVertex, aabb2.minVertex); + this.syncAABB.minVertex.x = Math.min(this.syncAABB.minVertex.x, centerX - hX); + this.syncAABB.minVertex.y = Math.min(this.syncAABB.minVertex.y, centerY - hY); + //aabb.maxVertex = b2Max(aabb1.maxVertex, aabb2.maxVertex); + this.syncAABB.maxVertex.x = Math.max(this.syncAABB.maxVertex.x, centerX + hX); + this.syncAABB.maxVertex.y = Math.max(this.syncAABB.maxVertex.y, centerY + hY); + + var broadPhase = this.m_body.m_world.m_broadPhase; + if (broadPhase.InRange(this.syncAABB)) + { + broadPhase.MoveProxy(this.m_proxyId, this.syncAABB); + } + else + { + this.m_body.Freeze(); + } + }, + + QuickSync: function(position, R){ + //this.m_R = R; + this.m_R.SetM(R); + //this.m_position = position + b2Mul(R, this.m_localCentroid); + this.m_position.x = position.x + (R.col1.x * this.m_localCentroid.x + R.col2.x * this.m_localCentroid.y); + this.m_position.y = position.y + (R.col1.y * this.m_localCentroid.x + R.col2.y * this.m_localCentroid.y); + }, + + ResetProxy: function(broadPhase){ + + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + return; + } + + var proxy = broadPhase.GetProxy(this.m_proxyId); + + broadPhase.DestroyProxy(this.m_proxyId); + proxy = null; + + var R = b2Math.b2MulMM(this.m_R, this.m_localOBB.R); + var absR = b2Math.b2AbsM(R); + var h = b2Math.b2MulMV(absR, this.m_localOBB.extents); + //var position = this.m_position + b2Mul(this.m_R, this.m_localOBB.center); + var position = b2Math.b2MulMV(this.m_R, this.m_localOBB.center); + position.Add(this.m_position); + + var aabb = new b2AABB(); + //aabb.minVertex = position - h; + aabb.minVertex.SetV(position); + aabb.minVertex.Subtract(h); + //aabb.maxVertex = position + h; + aabb.maxVertex.SetV(position); + aabb.maxVertex.Add(h); + + if (broadPhase.InRange(aabb)) + { + this.m_proxyId = broadPhase.CreateProxy(aabb, this); + } + else + { + this.m_proxyId = b2Pair.b2_nullProxy; + } + + if (this.m_proxyId == b2Pair.b2_nullProxy) + { + this.m_body.Freeze(); + } + }, + + + Support: function(dX, dY, out) + { + //b2Vec2 dLocal = b2MulT(this.m_R, d); + var dLocalX = (dX*this.m_R.col1.x + dY*this.m_R.col1.y); + var dLocalY = (dX*this.m_R.col2.x + dY*this.m_R.col2.y); + + var bestIndex = 0; + //float32 bestValue = b2Dot(this.m_vertices[0], dLocal); + var bestValue = (this.m_coreVertices[0].x * dLocalX + this.m_coreVertices[0].y * dLocalY); + for (var i = 1; i < this.m_vertexCount; ++i) + { + //float32 value = b2Dot(this.m_vertices[i], dLocal); + var value = (this.m_coreVertices[i].x * dLocalX + this.m_coreVertices[i].y * dLocalY); + if (value > bestValue) + { + bestIndex = i; + bestValue = value; + } + } + + //return this.m_position + b2Mul(this.m_R, this.m_vertices[bestIndex]); + out.Set( this.m_position.x + (this.m_R.col1.x * this.m_coreVertices[bestIndex].x + this.m_R.col2.x * this.m_coreVertices[bestIndex].y), + this.m_position.y + (this.m_R.col1.y * this.m_coreVertices[bestIndex].x + this.m_R.col2.y * this.m_coreVertices[bestIndex].y)); + + }, + + + // Local position of the shape centroid in parent body frame. + m_localCentroid: new b2Vec2(), + + // Local position oriented bounding box. The OBB center is relative to + // shape centroid. + m_localOBB: new b2OBB(), + m_vertices: null, + m_coreVertices: null, + m_vertexCount: 0, + m_normals: null}); + +b2PolyShape.tempVec = new b2Vec2(); +b2PolyShape.tAbsR = new b2Mat22(); diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2Shape.js b/lib/box2d-js/js/box2d/collision/shapes/b2Shape.js new file mode 100644 index 0000000..4c1f09e --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2Shape.js @@ -0,0 +1,339 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + + +// Shapes are created automatically when a body is created. +// Client code does not normally interact with shapes. +var b2Shape = Class.create(); +b2Shape.prototype = +{ + TestPoint: function(p){return false}, + + GetUserData: function(){return this.m_userData;}, + + GetType: function(){ + return this.m_type; + }, + + // Get the parent body of this shape. + GetBody: function(){ + return this.m_body; + }, + + GetPosition: function(){ + return this.m_position; + }, + GetRotationMatrix: function(){ + return this.m_R; + }, + + // Remove and then add proxy from the broad-phase. + // This is used to refresh the collision filters. + ResetProxy: function(broadPhase){}, + + // Get the next shape in the parent body's shape list. + GetNext: function(){ + return this.m_next; + }, + + //--------------- Internals Below ------------------- + + + + + initialize: function(def, body){ + // initialize instance variables for references + this.m_R = new b2Mat22(); + this.m_position = new b2Vec2(); + // + + this.m_userData = def.userData; + + this.m_friction = def.friction; + this.m_restitution = def.restitution; + this.m_body = body; + + this.m_proxyId = b2Pair.b2_nullProxy; + + this.m_maxRadius = 0.0; + + this.m_categoryBits = def.categoryBits; + this.m_maskBits = def.maskBits; + this.m_groupIndex = def.groupIndex; + }, + + // Internal use only. Do not call. + //b2Shape::~b2Shape() + //{ + // this.m_body->m_world->m_broadPhase->this.DestroyProxy(this.m_proxyId); + //} + + + DestroyProxy: function() + { + if (this.m_proxyId != b2Pair.b2_nullProxy) + { + this.m_body.m_world.m_broadPhase.DestroyProxy(this.m_proxyId); + this.m_proxyId = b2Pair.b2_nullProxy; + } + }, + + + // Internal use only. Do not call. + Synchronize: function(position1, R1, position2, R2){}, + QuickSync: function(position, R){}, + Support: function(dX, dY, out){}, + GetMaxRadius: function(){ + return this.m_maxRadius; + }, + + m_next: null, + + m_R: new b2Mat22(), + m_position: new b2Vec2(), + + m_type: 0, + + m_userData: null, + + m_body: null, + + m_friction: null, + m_restitution: null, + + m_maxRadius: null, + + m_proxyId: 0, + m_categoryBits: 0, + m_maskBits: 0, + m_groupIndex: 0 + + + + // b2ShapeType + + + + + + + + + + + + +}; + + +b2Shape.Create = function(def, body, center){ + switch (def.type) + { + case b2Shape.e_circleShape: + { + //void* mem = body->m_world->m_blockAllocator.Allocate(sizeof(b2CircleShape)); + return new b2CircleShape(def, body, center); + } + + case b2Shape.e_boxShape: + case b2Shape.e_polyShape: + { + //void* mem = body->m_world->m_blockAllocator.Allocate(sizeof(b2PolyShape)); + return new b2PolyShape(def, body, center); + } + } + + //b2Settings.b2Assert(false); + return null; + }; +b2Shape.Destroy = function(shape) + { + /*b2BlockAllocator& allocator = shape->m_body->m_world->m_blockAllocator; + + switch (shape.m_type) + { + case b2Shape.e_circleShape: + shape->~b2Shape(); + allocator.Free(shape, sizeof(b2CircleShape)); + break; + + case b2Shape.e_polyShape: + shape->~b2Shape(); + allocator.Free(shape, sizeof(b2PolyShape)); + break; + + default: + b2Assert(false); + } + + shape = NULL;*/ + + // FROM DESTRUCTOR + if (shape.m_proxyId != b2Pair.b2_nullProxy) + shape.m_body.m_world.m_broadPhase.DestroyProxy(shape.m_proxyId); + }; +b2Shape.e_unknownShape = -1; +b2Shape.e_circleShape = 0; +b2Shape.e_boxShape = 1; +b2Shape.e_polyShape = 2; +b2Shape.e_meshShape = 3; +b2Shape.e_shapeTypeCount = 4; +b2Shape.PolyMass = function(massData, vs, count, rho) + { + //b2Settings.b2Assert(count >= 3); + + //var center = new b2Vec2(0.0, 0.0); + var center = new b2Vec2(); + center.SetZero(); + + var area = 0.0; + var I = 0.0; + + // pRef is the reference point for forming triangles. + // It's location doesn't change the result (except for rounding error). + var pRef = new b2Vec2(0.0, 0.0); + + var inv3 = 1.0 / 3.0; + + for (var i = 0; i < count; ++i) + { + // Triangle vertices. + var p1 = pRef; + var p2 = vs[i]; + var p3 = i + 1 < count ? vs[i+1] : vs[0]; + + var e1 = b2Math.SubtractVV(p2, p1); + var e2 = b2Math.SubtractVV(p3, p1); + + var D = b2Math.b2CrossVV(e1, e2); + + var triangleArea = 0.5 * D; + area += triangleArea; + + // Area weighted centroid + // center += triangleArea * inv3 * (p1 + p2 + p3); + var tVec = new b2Vec2(); + tVec.SetV(p1); + tVec.Add(p2); + tVec.Add(p3); + tVec.Multiply(inv3*triangleArea); + center.Add(tVec); + + var px = p1.x; + var py = p1.y; + var ex1 = e1.x; + var ey1 = e1.y; + var ex2 = e2.x; + var ey2 = e2.y; + + var intx2 = inv3 * (0.25 * (ex1*ex1 + ex2*ex1 + ex2*ex2) + (px*ex1 + px*ex2)) + 0.5*px*px; + var inty2 = inv3 * (0.25 * (ey1*ey1 + ey2*ey1 + ey2*ey2) + (py*ey1 + py*ey2)) + 0.5*py*py; + + I += D * (intx2 + inty2); + } + + // Total mass + massData.mass = rho * area; + + // Center of mass + //b2Settings.b2Assert(area > Number.MIN_VALUE); + center.Multiply( 1.0 / area ); + massData.center = center; + + // Inertia tensor relative to the center. + I = rho * (I - area * b2Math.b2Dot(center, center)); + massData.I = I; + }; +b2Shape.PolyCentroid = function(vs, count, out) + { + //b2Settings.b2Assert(count >= 3); + + //b2Vec2 c; c.Set(0.0f, 0.0f); + var cX = 0.0; + var cY = 0.0; + //float32 area = 0.0f; + var area = 0.0; + + // pRef is the reference point for forming triangles. + // It's location doesn't change the result (except for rounding error). + //b2Vec2 pRef(0.0f, 0.0f); + var pRefX = 0.0; + var pRefY = 0.0; + /* + // This code would put the reference point inside the polygon. + for (var i = 0; i < count; ++i) + { + //pRef += vs[i]; + pRef.x += vs[i].x; + pRef.y += vs[i].y; + } + pRef.x *= 1.0 / count; + pRef.y *= 1.0 / count; + */ + + //const float32 inv3 = 1.0f / 3.0f; + var inv3 = 1.0 / 3.0; + + for (var i = 0; i < count; ++i) + { + // Triangle vertices. + //b2Vec2 p1 = pRef; + var p1X = pRefX; + var p1Y = pRefY; + //b2Vec2 p2 = vs[i]; + var p2X = vs[i].x; + var p2Y = vs[i].y; + //b2Vec2 p3 = i + 1 < count ? vs[i+1] : vs[0]; + var p3X = i + 1 < count ? vs[i+1].x : vs[0].x; + var p3Y = i + 1 < count ? vs[i+1].y : vs[0].y; + + //b2Vec2 e1 = p2 - p1; + var e1X = p2X - p1X; + var e1Y = p2Y - p1Y; + //b2Vec2 e2 = p3 - p1; + var e2X = p3X - p1X; + var e2Y = p3Y - p1Y; + + //float32 D = b2Cross(e1, e2); + var D = (e1X * e2Y - e1Y * e2X); + + //float32 triangleArea = 0.5f * D; + var triangleArea = 0.5 * D; + area += triangleArea; + + // Area weighted centroid + //c += triangleArea * inv3 * (p1 + p2 + p3); + cX += triangleArea * inv3 * (p1X + p2X + p3X); + cY += triangleArea * inv3 * (p1Y + p2Y + p3Y); + } + + // Centroid + //b2Settings.b2Assert(area > Number.MIN_VALUE); + cX *= 1.0 / area; + cY *= 1.0 / area; + + // Replace return with 'out' vector + //return c; + out.Set(cX, cY); + }; diff --git a/lib/box2d-js/js/box2d/collision/shapes/b2ShapeDef.js b/lib/box2d-js/js/box2d/collision/shapes/b2ShapeDef.js new file mode 100644 index 0000000..8a0416e --- /dev/null +++ b/lib/box2d-js/js/box2d/collision/shapes/b2ShapeDef.js @@ -0,0 +1,109 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2ShapeDef = Class.create(); +b2ShapeDef.prototype = +{ + initialize: function() + { + this.type = b2Shape.e_unknownShape; + this.userData = null; + this.localPosition = new b2Vec2(0.0, 0.0); + this.localRotation = 0.0; + this.friction = 0.2; + this.restitution = 0.0; + this.density = 0.0; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.groupIndex = 0; + }, + + //virtual ~b2ShapeDef() {} + + ComputeMass: function(massData) + { + + massData.center = new b2Vec2(0.0, 0.0) + + if (this.density == 0.0) + { + massData.mass = 0.0; + massData.center.Set(0.0, 0.0); + massData.I = 0.0; + }; + + switch (this.type) + { + case b2Shape.e_circleShape: + { + var circle = this; + massData.mass = this.density * b2Settings.b2_pi * circle.radius * circle.radius; + massData.center.Set(0.0, 0.0); + massData.I = 0.5 * (massData.mass) * circle.radius * circle.radius; + } + break; + + case b2Shape.e_boxShape: + { + var box = this; + massData.mass = 4.0 * this.density * box.extents.x * box.extents.y; + massData.center.Set(0.0, 0.0); + massData.I = massData.mass / 3.0 * b2Math.b2Dot(box.extents, box.extents); + } + break; + + case b2Shape.e_polyShape: + { + var poly = this; + b2Shape.PolyMass(massData, poly.vertices, poly.vertexCount, this.density); + } + break; + + default: + massData.mass = 0.0; + massData.center.Set(0.0, 0.0); + massData.I = 0.0; + break; + } + }, + + type: 0, + userData: null, + localPosition: null, + localRotation: null, + friction: null, + restitution: null, + density: null, + + // The collision category bits. Normally you would just set one bit. + categoryBits: 0, + + // The collision mask bits. This states the categories that this + // shape would accept for collision. + maskBits: 0, + + // Collision groups allow a certain group of objects to never collide (negative) + // or always collide (positive). Zero means no collision group. Non-zero group + // filtering always wins against the mask bits. + groupIndex: 0}; diff --git a/lib/box2d-js/js/box2d/common/b2Settings.js b/lib/box2d-js/js/box2d/common/b2Settings.js new file mode 100644 index 0000000..eac5e5f --- /dev/null +++ b/lib/box2d-js/js/box2d/common/b2Settings.js @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2Settings = Class.create(); +b2Settings.prototype = { + + + + // Define your unit system here. The default system is + // meters-kilograms-seconds. For the tuning to work well, + // your dynamic objects should be bigger than a pebble and smaller + // than a house. + //static public const b2Settings.b2_lengthUnitsPerMeter = 1.0; + + // Use this for pixels: + + // Global tuning constants based on MKS units. + + // Collision + + // Dynamics + + // Sleep + + // assert + + initialize: function() {}} +b2Settings.USHRT_MAX = 0x0000ffff; +b2Settings.b2_pi = Math.PI; +b2Settings.b2_massUnitsPerKilogram = 1.0; +b2Settings.b2_timeUnitsPerSecond = 1.0; +b2Settings.b2_lengthUnitsPerMeter = 30.0; +b2Settings.b2_maxManifoldPoints = 2; +b2Settings.b2_maxShapesPerBody = 64; +b2Settings.b2_maxPolyVertices = 8; +b2Settings.b2_maxProxies = 1024; +b2Settings.b2_maxPairs = 8 * b2Settings.b2_maxProxies; +b2Settings.b2_linearSlop = 0.005 * b2Settings.b2_lengthUnitsPerMeter; +b2Settings.b2_angularSlop = 2.0 / 180.0 * b2Settings.b2_pi; +b2Settings.b2_velocityThreshold = 1.0 * b2Settings.b2_lengthUnitsPerMeter / b2Settings.b2_timeUnitsPerSecond; +b2Settings.b2_maxLinearCorrection = 0.2 * b2Settings.b2_lengthUnitsPerMeter; +b2Settings.b2_maxAngularCorrection = 8.0 / 180.0 * b2Settings.b2_pi; +b2Settings.b2_contactBaumgarte = 0.2; +b2Settings.b2_timeToSleep = 0.5 * b2Settings.b2_timeUnitsPerSecond; +b2Settings.b2_linearSleepTolerance = 0.01 * b2Settings.b2_lengthUnitsPerMeter / b2Settings.b2_timeUnitsPerSecond; +b2Settings.b2_angularSleepTolerance = 2.0 / 180.0 / b2Settings.b2_timeUnitsPerSecond; +b2Settings.b2Assert = function(a) + { + if (!a){ + var nullVec; + nullVec.x++; + } + }; diff --git a/lib/box2d-js/js/box2d/common/math/b2Mat22.js b/lib/box2d-js/js/box2d/common/math/b2Mat22.js new file mode 100644 index 0000000..b32b110 --- /dev/null +++ b/lib/box2d-js/js/box2d/common/math/b2Mat22.js @@ -0,0 +1,130 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2Mat22 = Class.create(); +b2Mat22.prototype = +{ + initialize: function(angle, c1, c2) + { + if (angle==null) angle = 0; + // initialize instance variables for references + this.col1 = new b2Vec2(); + this.col2 = new b2Vec2(); + // + + if (c1!=null && c2!=null){ + this.col1.SetV(c1); + this.col2.SetV(c2); + } + else{ + var c = Math.cos(angle); + var s = Math.sin(angle); + this.col1.x = c; this.col2.x = -s; + this.col1.y = s; this.col2.y = c; + } + }, + + Set: function(angle) + { + var c = Math.cos(angle); + var s = Math.sin(angle); + this.col1.x = c; this.col2.x = -s; + this.col1.y = s; this.col2.y = c; + }, + + SetVV: function(c1, c2) + { + this.col1.SetV(c1); + this.col2.SetV(c2); + }, + + Copy: function(){ + return new b2Mat22(0, this.col1, this.col2); + }, + + SetM: function(m) + { + this.col1.SetV(m.col1); + this.col2.SetV(m.col2); + }, + + AddM: function(m) + { + this.col1.x += m.col1.x; + this.col1.y += m.col1.y; + this.col2.x += m.col2.x; + this.col2.y += m.col2.y; + }, + + SetIdentity: function() + { + this.col1.x = 1.0; this.col2.x = 0.0; + this.col1.y = 0.0; this.col2.y = 1.0; + }, + + SetZero: function() + { + this.col1.x = 0.0; this.col2.x = 0.0; + this.col1.y = 0.0; this.col2.y = 0.0; + }, + + Invert: function(out) + { + var a = this.col1.x; + var b = this.col2.x; + var c = this.col1.y; + var d = this.col2.y; + //var B = new b2Mat22(); + var det = a * d - b * c; + //b2Settings.b2Assert(det != 0.0); + det = 1.0 / det; + out.col1.x = det * d; out.col2.x = -det * b; + out.col1.y = -det * c; out.col2.y = det * a; + return out; + }, + + // this.Solve A * x = b + Solve: function(out, bX, bY) + { + //float32 a11 = this.col1.x, a12 = this.col2.x, a21 = this.col1.y, a22 = this.col2.y; + var a11 = this.col1.x; + var a12 = this.col2.x; + var a21 = this.col1.y; + var a22 = this.col2.y; + //float32 det = a11 * a22 - a12 * a21; + var det = a11 * a22 - a12 * a21; + //b2Settings.b2Assert(det != 0.0); + det = 1.0 / det; + out.x = det * (a22 * bX - a12 * bY); + out.y = det * (a11 * bY - a21 * bX); + + return out; + }, + + Abs: function() + { + this.col1.Abs(); + this.col2.Abs(); + }, + + col1: new b2Vec2(), + col2: new b2Vec2()}; diff --git a/lib/box2d-js/js/box2d/common/math/b2Math.js b/lib/box2d-js/js/box2d/common/math/b2Math.js new file mode 100644 index 0000000..dca2364 --- /dev/null +++ b/lib/box2d-js/js/box2d/common/math/b2Math.js @@ -0,0 +1,218 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2Math = Class.create(); +b2Math.prototype = { + + + /*static public function b2InvSqrt(x) + { + float32 xhalf = 0.5f * x; + int32 i = *(int32*)&x; + i = 0x5f3759df - (i >> 1); + x = *(float32*)&i; + x = x * (1.5f - xhalf * x * x); + return x; + }*/ + + + + + + + + + + + + // A * B + + // A^T * B + + + + + + + + + + + + // b2Math.b2Random number in range [-1,1] + + /*inline float32 b2Math.b2Random(float32 lo, float32 hi) + { + float32 r = (float32)rand(); + r /= RAND_MAX; + r = (hi - lo) * r + lo; + return r; + }*/ + + // "Next Largest Power of 2 + // Given a binary integer value x, the next largest power of 2 can be computed by a SWAR algorithm + // that recursively "folds" the upper bits into the lower bits. This process yields a bit vector with + // the same most significant 1, but all 1's below it. Adding 1 to that value yields the next + // largest power of 2. For a 32-bit value:" + + + + // Temp vector functions to reduce calls to 'new' + /*static public var tempVec = new b2Vec2(); + + + static public var tempAABB = new b2AABB(); */ + + + + initialize: function() {}} +b2Math.b2IsValid = function(x) + { + return isFinite(x); + }; +b2Math.b2Dot = function(a, b) + { + return a.x * b.x + a.y * b.y; + }; +b2Math.b2CrossVV = function(a, b) + { + return a.x * b.y - a.y * b.x; + }; +b2Math.b2CrossVF = function(a, s) + { + var v = new b2Vec2(s * a.y, -s * a.x); + return v; + }; +b2Math.b2CrossFV = function(s, a) + { + var v = new b2Vec2(-s * a.y, s * a.x); + return v; + }; +b2Math.b2MulMV = function(A, v) + { + var u = new b2Vec2(A.col1.x * v.x + A.col2.x * v.y, A.col1.y * v.x + A.col2.y * v.y); + return u; + }; +b2Math.b2MulTMV = function(A, v) + { + var u = new b2Vec2(b2Math.b2Dot(v, A.col1), b2Math.b2Dot(v, A.col2)); + return u; + }; +b2Math.AddVV = function(a, b) + { + var v = new b2Vec2(a.x + b.x, a.y + b.y); + return v; + }; +b2Math.SubtractVV = function(a, b) + { + var v = new b2Vec2(a.x - b.x, a.y - b.y); + return v; + }; +b2Math.MulFV = function(s, a) + { + var v = new b2Vec2(s * a.x, s * a.y); + return v; + }; +b2Math.AddMM = function(A, B) + { + var C = new b2Mat22(0, b2Math.AddVV(A.col1, B.col1), b2Math.AddVV(A.col2, B.col2)); + return C; + }; +b2Math.b2MulMM = function(A, B) + { + var C = new b2Mat22(0, b2Math.b2MulMV(A, B.col1), b2Math.b2MulMV(A, B.col2)); + return C; + }; +b2Math.b2MulTMM = function(A, B) + { + var c1 = new b2Vec2(b2Math.b2Dot(A.col1, B.col1), b2Math.b2Dot(A.col2, B.col1)); + var c2 = new b2Vec2(b2Math.b2Dot(A.col1, B.col2), b2Math.b2Dot(A.col2, B.col2)); + var C = new b2Mat22(0, c1, c2); + return C; + }; +b2Math.b2Abs = function(a) + { + return a > 0.0 ? a : -a; + }; +b2Math.b2AbsV = function(a) + { + var b = new b2Vec2(b2Math.b2Abs(a.x), b2Math.b2Abs(a.y)); + return b; + }; +b2Math.b2AbsM = function(A) + { + var B = new b2Mat22(0, b2Math.b2AbsV(A.col1), b2Math.b2AbsV(A.col2)); + return B; + }; +b2Math.b2Min = function(a, b) + { + return a < b ? a : b; + }; +b2Math.b2MinV = function(a, b) + { + var c = new b2Vec2(b2Math.b2Min(a.x, b.x), b2Math.b2Min(a.y, b.y)); + return c; + }; +b2Math.b2Max = function(a, b) + { + return a > b ? a : b; + }; +b2Math.b2MaxV = function(a, b) + { + var c = new b2Vec2(b2Math.b2Max(a.x, b.x), b2Math.b2Max(a.y, b.y)); + return c; + }; +b2Math.b2Clamp = function(a, low, high) + { + return b2Math.b2Max(low, b2Math.b2Min(a, high)); + }; +b2Math.b2ClampV = function(a, low, high) + { + return b2Math.b2MaxV(low, b2Math.b2MinV(a, high)); + }; +b2Math.b2Swap = function(a, b) + { + var tmp = a[0]; + a[0] = b[0]; + b[0] = tmp; + }; +b2Math.b2Random = function() + { + return Math.random() * 2 - 1; + }; +b2Math.b2NextPowerOfTwo = function(x) + { + x |= (x >> 1) & 0x7FFFFFFF; + x |= (x >> 2) & 0x3FFFFFFF; + x |= (x >> 4) & 0x0FFFFFFF; + x |= (x >> 8) & 0x00FFFFFF; + x |= (x >> 16)& 0x0000FFFF; + return x + 1; + }; +b2Math.b2IsPowerOfTwo = function(x) + { + var result = x > 0 && (x & (x - 1)) == 0; + return result; + }; +b2Math.tempVec2 = new b2Vec2(); +b2Math.tempVec3 = new b2Vec2(); +b2Math.tempVec4 = new b2Vec2(); +b2Math.tempVec5 = new b2Vec2(); +b2Math.tempMat = new b2Mat22(); diff --git a/lib/box2d-js/js/box2d/common/math/b2Vec2.js b/lib/box2d-js/js/box2d/common/math/b2Vec2.js new file mode 100644 index 0000000..611d1d9 --- /dev/null +++ b/lib/box2d-js/js/box2d/common/math/b2Vec2.js @@ -0,0 +1,131 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +// b2Vec2 has no constructor so that it +// can be placed in a union. +var b2Vec2 = Class.create(); +b2Vec2.prototype = +{ + initialize: function(x_, y_) {this.x=x_; this.y=y_;}, + + SetZero: function() { this.x = 0.0; this.y = 0.0; }, + Set: function(x_, y_) {this.x=x_; this.y=y_;}, + SetV: function(v) {this.x=v.x; this.y=v.y;}, + + Negative: function(){ return new b2Vec2(-this.x, -this.y); }, + + + Copy: function(){ + return new b2Vec2(this.x,this.y); + }, + + Add: function(v) + { + this.x += v.x; this.y += v.y; + }, + + Subtract: function(v) + { + this.x -= v.x; this.y -= v.y; + }, + + Multiply: function(a) + { + this.x *= a; this.y *= a; + }, + + MulM: function(A) + { + var tX = this.x; + this.x = A.col1.x * tX + A.col2.x * this.y; + this.y = A.col1.y * tX + A.col2.y * this.y; + }, + + MulTM: function(A) + { + var tX = b2Math.b2Dot(this, A.col1); + this.y = b2Math.b2Dot(this, A.col2); + this.x = tX; + }, + + CrossVF: function(s) + { + var tX = this.x; + this.x = s * this.y; + this.y = -s * tX; + }, + + CrossFV: function(s) + { + var tX = this.x; + this.x = -s * this.y; + this.y = s * tX; + }, + + MinV: function(b) + { + this.x = this.x < b.x ? this.x : b.x; + this.y = this.y < b.y ? this.y : b.y; + }, + + MaxV: function(b) + { + this.x = this.x > b.x ? this.x : b.x; + this.y = this.y > b.y ? this.y : b.y; + }, + + Abs: function() + { + this.x = Math.abs(this.x); + this.y = Math.abs(this.y); + }, + + Length: function() + { + return Math.sqrt(this.x * this.x + this.y * this.y); + }, + + Normalize: function() + { + var length = this.Length(); + if (length < Number.MIN_VALUE) + { + return 0.0; + } + var invLength = 1.0 / length; + this.x *= invLength; + this.y *= invLength; + + return length; + }, + + IsValid: function() + { + return b2Math.b2IsValid(this.x) && b2Math.b2IsValid(this.y); + }, + + x: null, + y: null}; +b2Vec2.Make = function(x_, y_) + { + return new b2Vec2(x_, y_); + }; diff --git a/lib/box2d-js/js/box2d/dynamics/b2Body.js b/lib/box2d-js/js/box2d/dynamics/b2Body.js new file mode 100644 index 0000000..b721a7b --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2Body.js @@ -0,0 +1,469 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + +// A rigid body. Internal computation are done in terms +// of the center of mass position. The center of mass may +// be offset from the body's origin. +var b2Body = Class.create(); +b2Body.prototype = +{ + // Set the position of the body's origin and rotation (radians). + // This breaks any contacts and wakes the other bodies. + SetOriginPosition: function(position, rotation){ + if (this.IsFrozen()) + { + return; + } + + this.m_rotation = rotation; + this.m_R.Set(this.m_rotation); + this.m_position = b2Math.AddVV(position , b2Math.b2MulMV(this.m_R, this.m_center)); + + this.m_position0.SetV(this.m_position); + this.m_rotation0 = this.m_rotation; + + for (var s = this.m_shapeList; s != null; s = s.m_next) + { + s.Synchronize(this.m_position, this.m_R, this.m_position, this.m_R); + } + + this.m_world.m_broadPhase.Commit(); + }, + + // Get the position of the body's origin. The body's origin does not + // necessarily coincide with the center of mass. It depends on how the + // shapes are created. + GetOriginPosition: function(){ + return b2Math.SubtractVV(this.m_position, b2Math.b2MulMV(this.m_R, this.m_center)); + }, + + // Set the position of the body's center of mass and rotation (radians). + // This breaks any contacts and wakes the other bodies. + SetCenterPosition: function(position, rotation){ + if (this.IsFrozen()) + { + return; + } + + this.m_rotation = rotation; + this.m_R.Set(this.m_rotation); + this.m_position.SetV( position ); + + this.m_position0.SetV(this.m_position); + this.m_rotation0 = this.m_rotation; + + for (var s = this.m_shapeList; s != null; s = s.m_next) + { + s.Synchronize(this.m_position, this.m_R, this.m_position, this.m_R); + } + + this.m_world.m_broadPhase.Commit(); + }, + + // Get the position of the body's center of mass. The body's center of mass + // does not necessarily coincide with the body's origin. It depends on how the + // shapes are created. + GetCenterPosition: function(){ + return this.m_position; + }, + + // Get the rotation in radians. + GetRotation: function(){ + return this.m_rotation; + }, + + GetRotationMatrix: function(){ + return this.m_R; + }, + + // Set/Get the linear velocity of the center of mass. + SetLinearVelocity: function(v){ + this.m_linearVelocity.SetV(v); + }, + GetLinearVelocity: function(){ + return this.m_linearVelocity; + }, + + // Set/Get the angular velocity. + SetAngularVelocity: function(w){ + this.m_angularVelocity = w; + }, + GetAngularVelocity: function(){ + return this.m_angularVelocity; + }, + + // Apply a force at a world point. Additive. + ApplyForce: function(force, point) + { + if (this.IsSleeping() == false) + { + this.m_force.Add( force ); + this.m_torque += b2Math.b2CrossVV(b2Math.SubtractVV(point, this.m_position), force); + } + }, + + // Apply a torque. Additive. + ApplyTorque: function(torque) + { + if (this.IsSleeping() == false) + { + this.m_torque += torque; + } + }, + + // Apply an impulse at a point. This immediately modifies the velocity. + ApplyImpulse: function(impulse, point) + { + if (this.IsSleeping() == false) + { + this.m_linearVelocity.Add( b2Math.MulFV(this.m_invMass, impulse) ); + this.m_angularVelocity += ( this.m_invI * b2Math.b2CrossVV( b2Math.SubtractVV(point, this.m_position), impulse) ); + } + }, + + GetMass: function(){ + return this.m_mass; + }, + + GetInertia: function(){ + return this.m_I; + }, + + // Get the world coordinates of a point give the local coordinates + // relative to the body's center of mass. + GetWorldPoint: function(localPoint){ + return b2Math.AddVV(this.m_position , b2Math.b2MulMV(this.m_R, localPoint)); + }, + + // Get the world coordinates of a vector given the local coordinates. + GetWorldVector: function(localVector){ + return b2Math.b2MulMV(this.m_R, localVector); + }, + + // Returns a local point relative to the center of mass given a world point. + GetLocalPoint: function(worldPoint){ + return b2Math.b2MulTMV(this.m_R, b2Math.SubtractVV(worldPoint, this.m_position)); + }, + + // Returns a local vector given a world vector. + GetLocalVector: function(worldVector){ + return b2Math.b2MulTMV(this.m_R, worldVector); + }, + + // Is this body static (immovable)? + IsStatic: function(){ + return (this.m_flags & b2Body.e_staticFlag) == b2Body.e_staticFlag; + }, + + IsFrozen: function() + { + return (this.m_flags & b2Body.e_frozenFlag) == b2Body.e_frozenFlag; + }, + + // Is this body sleeping (not simulating). + IsSleeping: function(){ + return (this.m_flags & b2Body.e_sleepFlag) == b2Body.e_sleepFlag; + }, + + // You can disable sleeping on this particular body. + AllowSleeping: function(flag) + { + if (flag) + { + this.m_flags |= b2Body.e_allowSleepFlag; + } + else + { + this.m_flags &= ~b2Body.e_allowSleepFlag; + this.WakeUp(); + } + }, + + // Wake up this body so it will begin simulating. + WakeUp: function(){ + this.m_flags &= ~b2Body.e_sleepFlag; + this.m_sleepTime = 0.0; + }, + + // Get the list of all shapes attached to this body. + GetShapeList: function(){ + return this.m_shapeList; + }, + + GetContactList: function() + { + return this.m_contactList; + }, + + GetJointList: function() + { + return this.m_jointList; + }, + + // Get the next body in the world's body list. + GetNext: function(){ + return this.m_next; + }, + + GetUserData: function(){ + return this.m_userData; + }, + + //--------------- Internals Below ------------------- + + initialize: function(bd, world){ + // initialize instance variables for references + this.sMat0 = new b2Mat22(); + this.m_position = new b2Vec2(); + this.m_R = new b2Mat22(0); + this.m_position0 = new b2Vec2(); + // + + var i = 0; + var sd; + var massData; + + this.m_flags = 0; + this.m_position.SetV( bd.position ); + this.m_rotation = bd.rotation; + this.m_R.Set(this.m_rotation); + this.m_position0.SetV(this.m_position); + this.m_rotation0 = this.m_rotation; + this.m_world = world; + + this.m_linearDamping = b2Math.b2Clamp(1.0 - bd.linearDamping, 0.0, 1.0); + this.m_angularDamping = b2Math.b2Clamp(1.0 - bd.angularDamping, 0.0, 1.0); + + this.m_force = new b2Vec2(0.0, 0.0); + this.m_torque = 0.0; + + this.m_mass = 0.0; + + var massDatas = new Array(b2Settings.b2_maxShapesPerBody); + for (i = 0; i < b2Settings.b2_maxShapesPerBody; i++){ + massDatas[i] = new b2MassData(); + } + + // Compute the shape mass properties, the bodies total mass and COM. + this.m_shapeCount = 0; + this.m_center = new b2Vec2(0.0, 0.0); + for (i = 0; i < b2Settings.b2_maxShapesPerBody; ++i) + { + sd = bd.shapes[i]; + if (sd == null) break; + massData = massDatas[ i ]; + sd.ComputeMass(massData); + this.m_mass += massData.mass; + //this.m_center += massData->mass * (sd->localPosition + massData->center); + this.m_center.x += massData.mass * (sd.localPosition.x + massData.center.x); + this.m_center.y += massData.mass * (sd.localPosition.y + massData.center.y); + ++this.m_shapeCount; + } + + // Compute center of mass, and shift the origin to the COM. + if (this.m_mass > 0.0) + { + this.m_center.Multiply( 1.0 / this.m_mass ); + this.m_position.Add( b2Math.b2MulMV(this.m_R, this.m_center) ); + } + else + { + this.m_flags |= b2Body.e_staticFlag; + } + + // Compute the moment of inertia. + this.m_I = 0.0; + for (i = 0; i < this.m_shapeCount; ++i) + { + sd = bd.shapes[i]; + massData = massDatas[ i ]; + this.m_I += massData.I; + var r = b2Math.SubtractVV( b2Math.AddVV(sd.localPosition, massData.center), this.m_center ); + this.m_I += massData.mass * b2Math.b2Dot(r, r); + } + + if (this.m_mass > 0.0) + { + this.m_invMass = 1.0 / this.m_mass; + } + else + { + this.m_invMass = 0.0; + } + + if (this.m_I > 0.0 && bd.preventRotation == false) + { + this.m_invI = 1.0 / this.m_I; + } + else + { + this.m_I = 0.0; + this.m_invI = 0.0; + } + + // Compute the center of mass velocity. + this.m_linearVelocity = b2Math.AddVV(bd.linearVelocity, b2Math.b2CrossFV(bd.angularVelocity, this.m_center)); + this.m_angularVelocity = bd.angularVelocity; + + this.m_jointList = null; + this.m_contactList = null; + this.m_prev = null; + this.m_next = null; + + // Create the shapes. + this.m_shapeList = null; + for (i = 0; i < this.m_shapeCount; ++i) + { + sd = bd.shapes[i]; + var shape = b2Shape.Create(sd, this, this.m_center); + shape.m_next = this.m_shapeList; + this.m_shapeList = shape; + } + + this.m_sleepTime = 0.0; + if (bd.allowSleep) + { + this.m_flags |= b2Body.e_allowSleepFlag; + } + if (bd.isSleeping) + { + this.m_flags |= b2Body.e_sleepFlag; + } + + if ((this.m_flags & b2Body.e_sleepFlag) || this.m_invMass == 0.0) + { + this.m_linearVelocity.Set(0.0, 0.0); + this.m_angularVelocity = 0.0; + } + + this.m_userData = bd.userData; + }, + // does not support destructors + /*~b2Body(){ + b2Shape* s = this.m_shapeList; + while (s) + { + b2Shape* s0 = s; + s = s->this.m_next; + + b2Shape::this.Destroy(s0); + } + }*/ + + Destroy: function(){ + var s = this.m_shapeList; + while (s) + { + var s0 = s; + s = s.m_next; + + b2Shape.Destroy(s0); + } + }, + + // Temp mat + sMat0: new b2Mat22(), + SynchronizeShapes: function(){ + //b2Mat22 R0(this.m_rotation0); + this.sMat0.Set(this.m_rotation0); + for (var s = this.m_shapeList; s != null; s = s.m_next) + { + s.Synchronize(this.m_position0, this.sMat0, this.m_position, this.m_R); + } + }, + + QuickSyncShapes: function(){ + for (var s = this.m_shapeList; s != null; s = s.m_next) + { + s.QuickSync(this.m_position, this.m_R); + } + }, + + // This is used to prevent connected bodies from colliding. + // It may lie, depending on the collideConnected flag. + IsConnected: function(other){ + for (var jn = this.m_jointList; jn != null; jn = jn.next) + { + if (jn.other == other) + return jn.joint.m_collideConnected == false; + } + + return false; + }, + + Freeze: function(){ + this.m_flags |= b2Body.e_frozenFlag; + this.m_linearVelocity.SetZero(); + this.m_angularVelocity = 0.0; + + for (var s = this.m_shapeList; s != null; s = s.m_next) + { + s.DestroyProxy(); + } + }, + + m_flags: 0, + + m_position: new b2Vec2(), + m_rotation: null, + m_R: new b2Mat22(0), + + // Conservative advancement data. + m_position0: new b2Vec2(), + m_rotation0: null, + + m_linearVelocity: null, + m_angularVelocity: null, + + m_force: null, + m_torque: null, + + m_center: null, + + m_world: null, + m_prev: null, + m_next: null, + + m_shapeList: null, + m_shapeCount: 0, + + m_jointList: null, + m_contactList: null, + + m_mass: null, + m_invMass: null, + m_I: null, + m_invI: null, + + m_linearDamping: null, + m_angularDamping: null, + + m_sleepTime: null, + + m_userData: null}; +b2Body.e_staticFlag = 0x0001; +b2Body.e_frozenFlag = 0x0002; +b2Body.e_islandFlag = 0x0004; +b2Body.e_sleepFlag = 0x0008; +b2Body.e_allowSleepFlag = 0x0010; +b2Body.e_destroyFlag = 0x0020; diff --git a/lib/box2d-js/js/box2d/dynamics/b2BodyDef.js b/lib/box2d-js/js/box2d/dynamics/b2BodyDef.js new file mode 100644 index 0000000..04debc3 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2BodyDef.js @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2BodyDef = Class.create(); +b2BodyDef.prototype = +{ + initialize: function() + { + // initialize instance variables for references + this.shapes = new Array(); + // + + this.userData = null; + for (var i = 0; i < b2Settings.b2_maxShapesPerBody; i++){ + this.shapes[i] = null; + } + this.position = new b2Vec2(0.0, 0.0); + this.rotation = 0.0; + this.linearVelocity = new b2Vec2(0.0, 0.0); + this.angularVelocity = 0.0; + this.linearDamping = 0.0; + this.angularDamping = 0.0; + this.allowSleep = true; + this.isSleeping = false; + this.preventRotation = false; + }, + + userData: null, + shapes: new Array(), + position: null, + rotation: null, + linearVelocity: null, + angularVelocity: null, + linearDamping: null, + angularDamping: null, + allowSleep: null, + isSleeping: null, + preventRotation: null, + + AddShape: function(shape) + { + for (var i = 0; i < b2Settings.b2_maxShapesPerBody; ++i) + { + if (this.shapes[i] == null) + { + this.shapes[i] = shape; + break; + } + } + }}; diff --git a/lib/box2d-js/js/box2d/dynamics/b2CollisionFilter.js b/lib/box2d-js/js/box2d/dynamics/b2CollisionFilter.js new file mode 100644 index 0000000..a3861fc --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2CollisionFilter.js @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2CollisionFilter = Class.create(); +b2CollisionFilter.prototype = +{ + + // Return true if contact calculations should be performed between these two shapes. + ShouldCollide: function(shape1, shape2){ + if (shape1.m_groupIndex == shape2.m_groupIndex && shape1.m_groupIndex != 0) + { + return shape1.m_groupIndex > 0; + } + + var collide = (shape1.m_maskBits & shape2.m_categoryBits) != 0 && (shape1.m_categoryBits & shape2.m_maskBits) != 0; + return collide; + }, + + + initialize: function() {}}; +b2CollisionFilter.b2_defaultFilter = new b2CollisionFilter; diff --git a/lib/box2d-js/js/box2d/dynamics/b2ContactManager.js b/lib/box2d-js/js/box2d/dynamics/b2ContactManager.js new file mode 100644 index 0000000..2cda912 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2ContactManager.js @@ -0,0 +1,337 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2ContactManager = Class.create(); +Object.extend(b2ContactManager.prototype, b2PairCallback.prototype); +Object.extend(b2ContactManager.prototype, +{ + initialize: function(){ + // The constructor for b2PairCallback + // + + // initialize instance variables for references + this.m_nullContact = new b2NullContact(); + // + + this.m_world = null; + this.m_destroyImmediate = false; + }, + + // This is a callback from the broadphase when two AABB proxies begin + // to overlap. We create a b2Contact to manage the narrow phase. + PairAdded: function(proxyUserData1, proxyUserData2){ + var shape1 = proxyUserData1; + var shape2 = proxyUserData2; + + var body1 = shape1.m_body; + var body2 = shape2.m_body; + + if (body1.IsStatic() && body2.IsStatic()) + { + return this.m_nullContact; + } + + if (shape1.m_body == shape2.m_body) + { + return this.m_nullContact; + } + + if (body2.IsConnected(body1)) + { + return this.m_nullContact; + } + + if (this.m_world.m_filter != null && this.m_world.m_filter.ShouldCollide(shape1, shape2) == false) + { + return this.m_nullContact; + } + + // Ensure that body2 is dynamic (body1 is static or dynamic). + if (body2.m_invMass == 0.0) + { + var tempShape = shape1; + shape1 = shape2; + shape2 = tempShape; + //b2Math.b2Swap(shape1, shape2); + var tempBody = body1; + body1 = body2; + body2 = tempBody; + //b2Math.b2Swap(body1, body2); + } + + // Call the factory. + var contact = b2Contact.Create(shape1, shape2, this.m_world.m_blockAllocator); + + if (contact == null) + { + return this.m_nullContact; + } + else + { + // Insert into the world. + contact.m_prev = null; + contact.m_next = this.m_world.m_contactList; + if (this.m_world.m_contactList != null) + { + this.m_world.m_contactList.m_prev = contact; + } + this.m_world.m_contactList = contact; + this.m_world.m_contactCount++; + } + + return contact; + }, + + // This is a callback from the broadphase when two AABB proxies cease + // to overlap. We destroy the b2Contact. + PairRemoved: function(proxyUserData1, proxyUserData2, pairUserData){ + + if (pairUserData == null) + { + return; + } + + var c = pairUserData; + if (c != this.m_nullContact) + { + //b2Settings.b2Assert(this.m_world.m_contactCount > 0); + if (this.m_destroyImmediate == true) + { + this.DestroyContact(c); + c = null; + } + else + { + c.m_flags |= b2Contact.e_destroyFlag; + } + } + }, + + DestroyContact: function(c) + { + + //b2Settings.b2Assert(this.m_world.m_contactCount > 0); + + // Remove from the world. + if (c.m_prev) + { + c.m_prev.m_next = c.m_next; + } + + if (c.m_next) + { + c.m_next.m_prev = c.m_prev; + } + + if (c == this.m_world.m_contactList) + { + this.m_world.m_contactList = c.m_next; + } + + // If there are contact points, then disconnect from the island graph. + if (c.GetManifoldCount() > 0) + { + var body1 = c.m_shape1.m_body; + var body2 = c.m_shape2.m_body; + var node1 = c.m_node1; + var node2 = c.m_node2; + + // Wake up touching bodies. + body1.WakeUp(); + body2.WakeUp(); + + // Remove from body 1 + if (node1.prev) + { + node1.prev.next = node1.next; + } + + if (node1.next) + { + node1.next.prev = node1.prev; + } + + if (node1 == body1.m_contactList) + { + body1.m_contactList = node1.next; + } + + node1.prev = null; + node1.next = null; + + // Remove from body 2 + if (node2.prev) + { + node2.prev.next = node2.next; + } + + if (node2.next) + { + node2.next.prev = node2.prev; + } + + if (node2 == body2.m_contactList) + { + body2.m_contactList = node2.next; + } + + node2.prev = null; + node2.next = null; + } + + // Call the factory. + b2Contact.Destroy(c, this.m_world.m_blockAllocator); + --this.m_world.m_contactCount; + }, + + + // Destroy any contacts marked for deferred destruction. + CleanContactList: function() + { + var c = this.m_world.m_contactList; + while (c != null) + { + var c0 = c; + c = c.m_next; + + if (c0.m_flags & b2Contact.e_destroyFlag) + { + this.DestroyContact(c0); + c0 = null; + } + } + }, + + + // This is the top level collision call for the time step. Here + // all the narrow phase collision is processed for the world + // contact list. + Collide: function() + { + var body1; + var body2; + var node1; + var node2; + + for (var c = this.m_world.m_contactList; c != null; c = c.m_next) + { + if (c.m_shape1.m_body.IsSleeping() && + c.m_shape2.m_body.IsSleeping()) + { + continue; + } + + var oldCount = c.GetManifoldCount(); + c.Evaluate(); + + var newCount = c.GetManifoldCount(); + + if (oldCount == 0 && newCount > 0) + { + //b2Settings.b2Assert(c.GetManifolds().pointCount > 0); + + // Connect to island graph. + body1 = c.m_shape1.m_body; + body2 = c.m_shape2.m_body; + node1 = c.m_node1; + node2 = c.m_node2; + + // Connect to body 1 + node1.contact = c; + node1.other = body2; + + node1.prev = null; + node1.next = body1.m_contactList; + if (node1.next != null) + { + node1.next.prev = c.m_node1; + } + body1.m_contactList = c.m_node1; + + // Connect to body 2 + node2.contact = c; + node2.other = body1; + + node2.prev = null; + node2.next = body2.m_contactList; + if (node2.next != null) + { + node2.next.prev = node2; + } + body2.m_contactList = node2; + } + else if (oldCount > 0 && newCount == 0) + { + // Disconnect from island graph. + body1 = c.m_shape1.m_body; + body2 = c.m_shape2.m_body; + node1 = c.m_node1; + node2 = c.m_node2; + + // Remove from body 1 + if (node1.prev) + { + node1.prev.next = node1.next; + } + + if (node1.next) + { + node1.next.prev = node1.prev; + } + + if (node1 == body1.m_contactList) + { + body1.m_contactList = node1.next; + } + + node1.prev = null; + node1.next = null; + + // Remove from body 2 + if (node2.prev) + { + node2.prev.next = node2.next; + } + + if (node2.next) + { + node2.next.prev = node2.prev; + } + + if (node2 == body2.m_contactList) + { + body2.m_contactList = node2.next; + } + + node2.prev = null; + node2.next = null; + } + } + }, + + m_world: null, + + // This lets us provide broadphase proxy pair user data for + // contacts that shouldn't exist. + m_nullContact: new b2NullContact(), + m_destroyImmediate: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/b2Island.js b/lib/box2d-js/js/box2d/dynamics/b2Island.js new file mode 100644 index 0000000..306bcaa --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2Island.js @@ -0,0 +1,331 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +/* +Position Correction Notes +========================= +I tried the several algorithms for position correction of the 2D revolute joint. +I looked at these systems: +- simple pendulum (1m diameter sphere on massless 5m stick) with initial angular velocity of 100 rad/s. +- suspension bridge with 30 1m long planks of length 1m. +- multi-link chain with 30 1m long links. + +Here are the algorithms: + +Baumgarte - A fraction of the position error is added to the velocity error. There is no +separate position solver. + +Pseudo Velocities - After the velocity solver and position integration, +the position error, Jacobian, and effective mass are recomputed. Then +the velocity constraints are solved with pseudo velocities and a fraction +of the position error is added to the pseudo velocity error. The pseudo +velocities are initialized to zero and there is no warm-starting. After +the position solver, the pseudo velocities are added to the positions. +This is also called the First Order World method or the Position LCP method. + +Modified Nonlinear Gauss-Seidel (NGS) - Like Pseudo Velocities except the +position error is re-computed for each constraint and the positions are updated +after the constraint is solved. The radius vectors (aka Jacobians) are +re-computed too (otherwise the algorithm has horrible instability). The pseudo +velocity states are not needed because they are effectively zero at the beginning +of each iteration. Since we have the current position error, we allow the +iterations to terminate early if the error becomes smaller than b2_linearSlop. + +Full NGS or just NGS - Like Modified NGS except the effective mass are re-computed +each time a constraint is solved. + +Here are the results: +Baumgarte - this is the cheapest algorithm but it has some stability problems, +especially with the bridge. The chain links separate easily close to the root +and they jitter struggle to pull together. This is one of the most common +methods in the field. The big drawback is that the position correction artificially +affects the momentum, thus leading to instabilities and false bounce. I used a +bias factor of 0.2. A larger bias factor makes the bridge less stable, a smaller +factor makes joints and contacts more spongy. + +Pseudo Velocities - the is more stable than the Baumgarte method. The bridge is +stable. However, joints still separate with large angular velocities. Drag the +simple pendulum in a circle quickly and the joint will separate. The chain separates +easily and does not recover. I used a bias factor of 0.2. A larger value lead to +the bridge collapsing when a heavy cube drops on it. + +Modified NGS - this algorithm is better in some ways than Baumgarte and Pseudo +Velocities, but in other ways it is worse. The bridge and chain are much more +stable, but the simple pendulum goes unstable at high angular velocities. + +Full NGS - stable in all tests. The joints display good stiffness. The bridge +still sags, but this is better than infinite forces. + +Recommendations +Pseudo Velocities are not really worthwhile because the bridge and chain cannot +recover from joint separation. In other cases the benefit over Baumgarte is small. + +Modified NGS is not a robust method for the revolute joint due to the violent +instability seen in the simple pendulum. Perhaps it is viable with other constraint +types, especially scalar constraints where the effective mass is a scalar. + +This leaves Baumgarte and Full NGS. Baumgarte has small, but manageable instabilities +and is very fast. I don't think we can escape Baumgarte, especially in highly +demanding cases where high constraint fidelity is not needed. + +Full NGS is robust and easy on the eyes. I recommend this option for +higher fidelity simulation and certainly for suspension bridges and long chains. +Full NGS might be a good choice for ragdolls, especially motorized ragdolls where +joint separation can be problematic. The number of NGS iterations can be reduced +for better performance without harming robustness much. + +Each joint in a can be handled differently in the position solver. So I recommend +a system where the user can select the algorithm on a per joint basis. I would +probably default to the slower Full NGS and let the user select the faster +Baumgarte method in performance critical scenarios. +*/ + + +var b2Island = Class.create(); +b2Island.prototype = +{ + initialize: function(bodyCapacity, contactCapacity, jointCapacity, allocator) + { + var i = 0; + + this.m_bodyCapacity = bodyCapacity; + this.m_contactCapacity = contactCapacity; + this.m_jointCapacity = jointCapacity; + this.m_bodyCount = 0; + this.m_contactCount = 0; + this.m_jointCount = 0; + + + //this.m_bodies = (b2Body**)allocator->Allocate(bodyCapacity * sizeof(b2Body*)); + this.m_bodies = new Array(bodyCapacity); + for (i = 0; i < bodyCapacity; i++) + this.m_bodies[i] = null; + + //this.m_contacts = (b2Contact**)allocator->Allocate(contactCapacity * sizeof(b2Contact*)); + this.m_contacts = new Array(contactCapacity); + for (i = 0; i < contactCapacity; i++) + this.m_contacts[i] = null; + + //this.m_joints = (b2Joint**)allocator->Allocate(jointCapacity * sizeof(b2Joint*)); + this.m_joints = new Array(jointCapacity); + for (i = 0; i < jointCapacity; i++) + this.m_joints[i] = null; + + this.m_allocator = allocator; + }, + //~b2Island(); + + Clear: function() + { + this.m_bodyCount = 0; + this.m_contactCount = 0; + this.m_jointCount = 0; + }, + + Solve: function(step, gravity) + { + var i = 0; + var b; + + for (i = 0; i < this.m_bodyCount; ++i) + { + b = this.m_bodies[i]; + + if (b.m_invMass == 0.0) + continue; + + b.m_linearVelocity.Add( b2Math.MulFV (step.dt, b2Math.AddVV(gravity, b2Math.MulFV( b.m_invMass, b.m_force ) ) ) ); + b.m_angularVelocity += step.dt * b.m_invI * b.m_torque; + + //b.m_linearVelocity *= b.m_linearDamping; + b.m_linearVelocity.Multiply(b.m_linearDamping); + b.m_angularVelocity *= b.m_angularDamping; + + // Store positions for conservative advancement. + b.m_position0.SetV(b.m_position); + b.m_rotation0 = b.m_rotation; + } + + var contactSolver = new b2ContactSolver(this.m_contacts, this.m_contactCount, this.m_allocator); + + // Pre-solve + contactSolver.PreSolve(); + + for (i = 0; i < this.m_jointCount; ++i) + { + this.m_joints[i].PrepareVelocitySolver(); + } + + // this.Solve velocity constraints. + for (i = 0; i < step.iterations; ++i) + { + contactSolver.SolveVelocityConstraints(); + + for (var j = 0; j < this.m_jointCount; ++j) + { + this.m_joints[j].SolveVelocityConstraints(step); + } + } + + // Integrate positions. + for (i = 0; i < this.m_bodyCount; ++i) + { + b = this.m_bodies[i]; + + if (b.m_invMass == 0.0) + continue; + + //b.m_position.Add( b2Math.MulFV (step.dt, b.m_linearVelocity) ); + b.m_position.x += step.dt * b.m_linearVelocity.x; + b.m_position.y += step.dt * b.m_linearVelocity.y; + b.m_rotation += step.dt * b.m_angularVelocity; + + b.m_R.Set(b.m_rotation); + } + + for (i = 0; i < this.m_jointCount; ++i) + { + this.m_joints[i].PreparePositionSolver(); + } + + // this.Solve position constraints. + if (b2World.s_enablePositionCorrection) + { + for (b2Island.m_positionIterationCount = 0; b2Island.m_positionIterationCount < step.iterations; ++b2Island.m_positionIterationCount) + { + var contactsOkay = contactSolver.SolvePositionConstraints(b2Settings.b2_contactBaumgarte); + + var jointsOkay = true; + for (i = 0; i < this.m_jointCount; ++i) + { + var jointOkay = this.m_joints[i].SolvePositionConstraints(); + jointsOkay = jointsOkay && jointOkay; + } + + if (contactsOkay && jointsOkay) + { + break; + } + } + } + + // Post-solve. + contactSolver.PostSolve(); + + // Synchronize shapes and reset forces. + for (i = 0; i < this.m_bodyCount; ++i) + { + b = this.m_bodies[i]; + + if (b.m_invMass == 0.0) + continue; + + b.m_R.Set(b.m_rotation); + + b.SynchronizeShapes(); + b.m_force.Set(0.0, 0.0); + b.m_torque = 0.0; + } + }, + + UpdateSleep: function(dt) + { + var i = 0; + var b; + + var minSleepTime = Number.MAX_VALUE; + + var linTolSqr = b2Settings.b2_linearSleepTolerance * b2Settings.b2_linearSleepTolerance; + var angTolSqr = b2Settings.b2_angularSleepTolerance * b2Settings.b2_angularSleepTolerance; + + for (i = 0; i < this.m_bodyCount; ++i) + { + b = this.m_bodies[i]; + if (b.m_invMass == 0.0) + { + continue; + } + + if ((b.m_flags & b2Body.e_allowSleepFlag) == 0) + { + b.m_sleepTime = 0.0; + minSleepTime = 0.0; + } + + if ((b.m_flags & b2Body.e_allowSleepFlag) == 0 || + b.m_angularVelocity * b.m_angularVelocity > angTolSqr || + b2Math.b2Dot(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr) + { + b.m_sleepTime = 0.0; + minSleepTime = 0.0; + } + else + { + b.m_sleepTime += dt; + minSleepTime = b2Math.b2Min(minSleepTime, b.m_sleepTime); + } + } + + if (minSleepTime >= b2Settings.b2_timeToSleep) + { + for (i = 0; i < this.m_bodyCount; ++i) + { + b = this.m_bodies[i]; + b.m_flags |= b2Body.e_sleepFlag; + } + } + }, + + AddBody: function(body) + { + //b2Settings.b2Assert(this.m_bodyCount < this.m_bodyCapacity); + this.m_bodies[this.m_bodyCount++] = body; + }, + + AddContact: function(contact) + { + //b2Settings.b2Assert(this.m_contactCount < this.m_contactCapacity); + this.m_contacts[this.m_contactCount++] = contact; + }, + + AddJoint: function(joint) + { + //b2Settings.b2Assert(this.m_jointCount < this.m_jointCapacity); + this.m_joints[this.m_jointCount++] = joint; + }, + + m_allocator: null, + + m_bodies: null, + m_contacts: null, + m_joints: null, + + m_bodyCount: 0, + m_jointCount: 0, + m_contactCount: 0, + + m_bodyCapacity: 0, + m_contactCapacity: 0, + m_jointCapacity: 0, + + m_positionError: null}; +b2Island.m_positionIterationCount = 0; diff --git a/lib/box2d-js/js/box2d/dynamics/b2TimeStep.js b/lib/box2d-js/js/box2d/dynamics/b2TimeStep.js new file mode 100644 index 0000000..b6ba52a --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2TimeStep.js @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2TimeStep = Class.create(); +b2TimeStep.prototype = +{ + dt: null, + inv_dt: null, + iterations: 0, + initialize: function() {}}; diff --git a/lib/box2d-js/js/box2d/dynamics/b2World.js b/lib/box2d-js/js/box2d/dynamics/b2World.js new file mode 100644 index 0000000..153ab1e --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2World.js @@ -0,0 +1,522 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + +var b2World = Class.create(); +b2World.prototype = +{ + initialize: function(worldAABB, gravity, doSleep){ + // initialize instance variables for references + this.step = new b2TimeStep(); + this.m_contactManager = new b2ContactManager(); + // + + + this.m_listener = null; + this.m_filter = b2CollisionFilter.b2_defaultFilter; + + this.m_bodyList = null; + this.m_contactList = null; + this.m_jointList = null; + + this.m_bodyCount = 0; + this.m_contactCount = 0; + this.m_jointCount = 0; + + this.m_bodyDestroyList = null; + + this.m_allowSleep = doSleep; + + this.m_gravity = gravity; + + this.m_contactManager.m_world = this; + this.m_broadPhase = new b2BroadPhase(worldAABB, this.m_contactManager); + + var bd = new b2BodyDef(); + this.m_groundBody = this.CreateBody(bd); + }, + //~b2World(){ + // this.DestroyBody(this.m_groundBody); + // delete this.m_broadPhase; + //} + + // Set a callback to notify you when a joint is implicitly destroyed + // when an attached body is destroyed. + SetListener: function(listener){ + this.m_listener = listener; + }, + + // Register a collision filter to provide specific control over collision. + // Otherwise the default filter is used (b2CollisionFilter). + SetFilter: function(filter){ + this.m_filter = filter; + }, + + // Create and destroy rigid bodies. Destruction is deferred until the + // the next call to this.Step. This is done so that bodies may be destroyed + // while you iterate through the contact list. + CreateBody: function(def){ + //void* mem = this.m_blockAllocator.Allocate(sizeof(b2Body)); + var b = new b2Body(def, this); + b.m_prev = null; + + b.m_next = this.m_bodyList; + if (this.m_bodyList) + { + this.m_bodyList.m_prev = b; + } + this.m_bodyList = b; + ++this.m_bodyCount; + + return b; + }, + // Body destruction is deferred to make contact processing more robust. + DestroyBody: function(b) + { + + if (b.m_flags & b2Body.e_destroyFlag) + { + return; + } + + // Remove from normal body list. + if (b.m_prev) + { + b.m_prev.m_next = b.m_next; + } + + if (b.m_next) + { + b.m_next.m_prev = b.m_prev; + } + + if (b == this.m_bodyList) + { + this.m_bodyList = b.m_next; + } + + b.m_flags |= b2Body.e_destroyFlag; + //b2Settings.b2Assert(this.m_bodyCount > 0); + --this.m_bodyCount; + + //b->~b2Body(); + //b.Destroy(); + // Add to the deferred destruction list. + b.m_prev = null; + b.m_next = this.m_bodyDestroyList; + this.m_bodyDestroyList = b; + }, + + CleanBodyList: function() + { + this.m_contactManager.m_destroyImmediate = true; + + var b = this.m_bodyDestroyList; + while (b) + { + //b2Settings.b2Assert((b.m_flags & b2Body.e_destroyFlag) != 0); + + // Preserve the next pointer. + var b0 = b; + b = b.m_next; + + // Delete the attached joints + var jn = b0.m_jointList; + while (jn) + { + var jn0 = jn; + jn = jn.next; + + if (this.m_listener) + { + this.m_listener.NotifyJointDestroyed(jn0.joint); + } + + this.DestroyJoint(jn0.joint); + } + + b0.Destroy(); + //this.m_blockAllocator.Free(b0, sizeof(b2Body)); + } + + // Reset the list. + this.m_bodyDestroyList = null; + + this.m_contactManager.m_destroyImmediate = false; + }, + + CreateJoint: function(def){ + var j = b2Joint.Create(def, this.m_blockAllocator); + + // Connect to the world list. + j.m_prev = null; + j.m_next = this.m_jointList; + if (this.m_jointList) + { + this.m_jointList.m_prev = j; + } + this.m_jointList = j; + ++this.m_jointCount; + + // Connect to the bodies + j.m_node1.joint = j; + j.m_node1.other = j.m_body2; + j.m_node1.prev = null; + j.m_node1.next = j.m_body1.m_jointList; + if (j.m_body1.m_jointList) j.m_body1.m_jointList.prev = j.m_node1; + j.m_body1.m_jointList = j.m_node1; + + j.m_node2.joint = j; + j.m_node2.other = j.m_body1; + j.m_node2.prev = null; + j.m_node2.next = j.m_body2.m_jointList; + if (j.m_body2.m_jointList) j.m_body2.m_jointList.prev = j.m_node2; + j.m_body2.m_jointList = j.m_node2; + + // If the joint prevents collisions, then reset collision filtering. + if (def.collideConnected == false) + { + // Reset the proxies on the body with the minimum number of shapes. + var b = def.body1.m_shapeCount < def.body2.m_shapeCount ? def.body1 : def.body2; + for (var s = b.m_shapeList; s; s = s.m_next) + { + s.ResetProxy(this.m_broadPhase); + } + } + + return j; + }, + DestroyJoint: function(j) + { + + var collideConnected = j.m_collideConnected; + + // Remove from the world. + if (j.m_prev) + { + j.m_prev.m_next = j.m_next; + } + + if (j.m_next) + { + j.m_next.m_prev = j.m_prev; + } + + if (j == this.m_jointList) + { + this.m_jointList = j.m_next; + } + + // Disconnect from island graph. + var body1 = j.m_body1; + var body2 = j.m_body2; + + // Wake up touching bodies. + body1.WakeUp(); + body2.WakeUp(); + + // Remove from body 1 + if (j.m_node1.prev) + { + j.m_node1.prev.next = j.m_node1.next; + } + + if (j.m_node1.next) + { + j.m_node1.next.prev = j.m_node1.prev; + } + + if (j.m_node1 == body1.m_jointList) + { + body1.m_jointList = j.m_node1.next; + } + + j.m_node1.prev = null; + j.m_node1.next = null; + + // Remove from body 2 + if (j.m_node2.prev) + { + j.m_node2.prev.next = j.m_node2.next; + } + + if (j.m_node2.next) + { + j.m_node2.next.prev = j.m_node2.prev; + } + + if (j.m_node2 == body2.m_jointList) + { + body2.m_jointList = j.m_node2.next; + } + + j.m_node2.prev = null; + j.m_node2.next = null; + + b2Joint.Destroy(j, this.m_blockAllocator); + + //b2Settings.b2Assert(this.m_jointCount > 0); + --this.m_jointCount; + + // If the joint prevents collisions, then reset collision filtering. + if (collideConnected == false) + { + // Reset the proxies on the body with the minimum number of shapes. + var b = body1.m_shapeCount < body2.m_shapeCount ? body1 : body2; + for (var s = b.m_shapeList; s; s = s.m_next) + { + s.ResetProxy(this.m_broadPhase); + } + } + }, + + // The world provides a single ground body with no collision shapes. You + // can use this to simplify the creation of joints. + GetGroundBody: function(){ + return this.m_groundBody; + }, + + + step: new b2TimeStep(), + // this.Step + Step: function(dt, iterations){ + + var b; + var other; + + + this.step.dt = dt; + this.step.iterations = iterations; + if (dt > 0.0) + { + this.step.inv_dt = 1.0 / dt; + } + else + { + this.step.inv_dt = 0.0; + } + + this.m_positionIterationCount = 0; + + // Handle deferred contact destruction. + this.m_contactManager.CleanContactList(); + + // Handle deferred body destruction. + this.CleanBodyList(); + + // Update contacts. + this.m_contactManager.Collide(); + + // Size the island for the worst case. + var island = new b2Island(this.m_bodyCount, this.m_contactCount, this.m_jointCount, this.m_stackAllocator); + + // Clear all the island flags. + for (b = this.m_bodyList; b != null; b = b.m_next) + { + b.m_flags &= ~b2Body.e_islandFlag; + } + for (var c = this.m_contactList; c != null; c = c.m_next) + { + c.m_flags &= ~b2Contact.e_islandFlag; + } + for (var j = this.m_jointList; j != null; j = j.m_next) + { + j.m_islandFlag = false; + } + + // Build and simulate all awake islands. + var stackSize = this.m_bodyCount; + //var stack = (b2Body**)this.m_stackAllocator.Allocate(stackSize * sizeof(b2Body*)); + var stack = new Array(this.m_bodyCount); + for (var k = 0; k < this.m_bodyCount; k++) + stack[k] = null; + + for (var seed = this.m_bodyList; seed != null; seed = seed.m_next) + { + if (seed.m_flags & (b2Body.e_staticFlag | b2Body.e_islandFlag | b2Body.e_sleepFlag | b2Body.e_frozenFlag)) + { + continue; + } + + // Reset island and stack. + island.Clear(); + var stackCount = 0; + stack[stackCount++] = seed; + seed.m_flags |= b2Body.e_islandFlag;; + + // Perform a depth first search (DFS) on the constraint graph. + while (stackCount > 0) + { + // Grab the next body off the stack and add it to the island. + b = stack[--stackCount]; + island.AddBody(b); + + // Make sure the body is awake. + b.m_flags &= ~b2Body.e_sleepFlag; + + // To keep islands, we don't + // propagate islands across static bodies. + if (b.m_flags & b2Body.e_staticFlag) + { + continue; + } + + // Search all contacts connected to this body. + for (var cn = b.m_contactList; cn != null; cn = cn.next) + { + if (cn.contact.m_flags & b2Contact.e_islandFlag) + { + continue; + } + + island.AddContact(cn.contact); + cn.contact.m_flags |= b2Contact.e_islandFlag; + + other = cn.other; + if (other.m_flags & b2Body.e_islandFlag) + { + continue; + } + + //b2Settings.b2Assert(stackCount < stackSize); + stack[stackCount++] = other; + other.m_flags |= b2Body.e_islandFlag; + } + + // Search all joints connect to this body. + for (var jn = b.m_jointList; jn != null; jn = jn.next) + { + if (jn.joint.m_islandFlag == true) + { + continue; + } + + island.AddJoint(jn.joint); + jn.joint.m_islandFlag = true; + + other = jn.other; + if (other.m_flags & b2Body.e_islandFlag) + { + continue; + } + + //b2Settings.b2Assert(stackCount < stackSize); + stack[stackCount++] = other; + other.m_flags |= b2Body.e_islandFlag; + } + } + + island.Solve(this.step, this.m_gravity); + + this.m_positionIterationCount = b2Math.b2Max(this.m_positionIterationCount, b2Island.m_positionIterationCount); + + if (this.m_allowSleep) + { + island.UpdateSleep(dt); + } + + // Post solve cleanup. + for (var i = 0; i < island.m_bodyCount; ++i) + { + // Allow static bodies to participate in other islands. + b = island.m_bodies[i]; + if (b.m_flags & b2Body.e_staticFlag) + { + b.m_flags &= ~b2Body.e_islandFlag; + } + + // Handle newly frozen bodies. + if (b.IsFrozen() && this.m_listener) + { + var response = this.m_listener.NotifyBoundaryViolated(b); + if (response == b2WorldListener.b2_destroyBody) + { + this.DestroyBody(b); + b = null; + island.m_bodies[i] = null; + } + } + } + } + + this.m_broadPhase.Commit(); + + //this.m_stackAllocator.Free(stack); + }, + + // this.Query the world for all shapes that potentially overlap the + // provided AABB. You provide a shape pointer buffer of specified + // size. The number of shapes found is returned. + Query: function(aabb, shapes, maxCount){ + + //void** results = (void**)this.m_stackAllocator.Allocate(maxCount * sizeof(void*)); + var results = new Array(); + var count = this.m_broadPhase.QueryAABB(aabb, results, maxCount); + + for (var i = 0; i < count; ++i) + { + shapes[i] = results[i]; + } + + //this.m_stackAllocator.Free(results); + return count; + }, + + // You can use these to iterate over all the bodies, joints, and contacts. + GetBodyList: function(){ + return this.m_bodyList; + }, + GetJointList: function(){ + return this.m_jointList; + }, + GetContactList: function(){ + return this.m_contactList; + }, + + //--------------- Internals Below ------------------- + + m_blockAllocator: null, + m_stackAllocator: null, + + m_broadPhase: null, + m_contactManager: new b2ContactManager(), + + m_bodyList: null, + m_contactList: null, + m_jointList: null, + + m_bodyCount: 0, + m_contactCount: 0, + m_jointCount: 0, + + // These bodies will be destroyed at the next time this.step. + m_bodyDestroyList: null, + + m_gravity: null, + m_allowSleep: null, + + m_groundBody: null, + + m_listener: null, + m_filter: null, + + m_positionIterationCount: 0}; +b2World.s_enablePositionCorrection = 1; +b2World.s_enableWarmStarting = 1; diff --git a/lib/box2d-js/js/box2d/dynamics/b2WorldListener.js b/lib/box2d-js/js/box2d/dynamics/b2WorldListener.js new file mode 100644 index 0000000..f612f45 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/b2WorldListener.js @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2WorldListener = Class.create(); +b2WorldListener.prototype = +{ + + // If a body is destroyed, then any joints attached to it are also destroyed. + // This prevents memory leaks, but you may unexpectedly be left with an + // orphaned joint pointer. + // Box2D will notify you when a joint is implicitly destroyed. + // It is NOT called if you directly destroy a joint. + // Implement this abstract class and provide it to b2World via + // b2World::SetListener(). + // DO NOT modify the Box2D world inside this callback. + NotifyJointDestroyed: function(joint){}, + + // This is called when a body's shape passes outside of the world boundary. If you + // override this and pass back e_destroyBody, you must nullify your copies of the + // body pointer. + NotifyBoundaryViolated: function(body) + { + //NOT_USED(body); + return b2WorldListener.b2_freezeBody; + }, + + + + initialize: function() {}}; +b2WorldListener.b2_freezeBody = 0; +b2WorldListener.b2_destroyBody = 1; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2CircleContact.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2CircleContact.js new file mode 100644 index 0000000..f6fbe61 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2CircleContact.js @@ -0,0 +1,102 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + +var b2CircleContact = Class.create(); +Object.extend(b2CircleContact.prototype, b2Contact.prototype); +Object.extend(b2CircleContact.prototype, +{ + + initialize: function(s1, s2) { + // The constructor for b2Contact + // initialize instance variables for references + this.m_node1 = new b2ContactNode(); + this.m_node2 = new b2ContactNode(); + // + this.m_flags = 0; + + if (!s1 || !s2){ + this.m_shape1 = null; + this.m_shape2 = null; + return; + } + + this.m_shape1 = s1; + this.m_shape2 = s2; + + this.m_manifoldCount = 0; + + this.m_friction = Math.sqrt(this.m_shape1.m_friction * this.m_shape2.m_friction); + this.m_restitution = b2Math.b2Max(this.m_shape1.m_restitution, this.m_shape2.m_restitution); + + this.m_prev = null; + this.m_next = null; + + this.m_node1.contact = null; + this.m_node1.prev = null; + this.m_node1.next = null; + this.m_node1.other = null; + + this.m_node2.contact = null; + this.m_node2.prev = null; + this.m_node2.next = null; + this.m_node2.other = null; + // + + // initialize instance variables for references + this.m_manifold = [new b2Manifold()]; + // + + //super(shape1, shape2); + + //b2Settings.b2Assert(this.m_shape1.m_type == b2Shape.e_circleShape); + //b2Settings.b2Assert(this.m_shape2.m_type == b2Shape.e_circleShape); + this.m_manifold[0].pointCount = 0; + this.m_manifold[0].points[0].normalImpulse = 0.0; + this.m_manifold[0].points[0].tangentImpulse = 0.0; + }, + //~b2CircleContact() {} + + Evaluate: function(){ + b2Collision.b2CollideCircle(this.m_manifold[0], this.m_shape1, this.m_shape2, false); + + if (this.m_manifold[0].pointCount > 0) + { + this.m_manifoldCount = 1; + } + else + { + this.m_manifoldCount = 0; + } + }, + + GetManifolds: function() + { + return this.m_manifold; + }, + + m_manifold: [new b2Manifold()]}); + +b2CircleContact.Create = function(shape1, shape2, allocator){ + return new b2CircleContact(shape1, shape2); + }; +b2CircleContact.Destroy = function(contact, allocator){ + // + }; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2Conservative.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2Conservative.js new file mode 100644 index 0000000..e39e8b2 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2Conservative.js @@ -0,0 +1,228 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2Conservative = Class.create(); +b2Conservative.prototype = { + + // Temp vars + + + + initialize: function() {}} +b2Conservative.R1 = new b2Mat22(); +b2Conservative.R2 = new b2Mat22(); +b2Conservative.x1 = new b2Vec2(); +b2Conservative.x2 = new b2Vec2(); +b2Conservative.Conservative = function(shape1, shape2){ + var body1 = shape1.GetBody(); + var body2 = shape2.GetBody(); + + //b2Vec2 v1 = body1->m_position - body1->m_position0; + var v1X = body1.m_position.x - body1.m_position0.x; + var v1Y = body1.m_position.y - body1.m_position0.y; + //float32 omega1 = body1->m_rotation - body1->m_rotation0; + var omega1 = body1.m_rotation - body1.m_rotation0; + //b2Vec2 v2 = body2->m_position - body2->m_position0; + var v2X = body2.m_position.x - body2.m_position0.x; + var v2Y = body2.m_position.y - body2.m_position0.y; + //float32 omega2 = body2->m_rotation - body2->m_rotation0; + var omega2 = body2.m_rotation - body2.m_rotation0; + + //float32 r1 = shape1->GetMaxRadius(); + var r1 = shape1.GetMaxRadius(); + //float32 r2 = shape2->GetMaxRadius(); + var r2 = shape2.GetMaxRadius(); + + //b2Vec2 p1Start = body1->m_position0; + var p1StartX = body1.m_position0.x; + var p1StartY = body1.m_position0.y; + //float32 a1Start = body1->m_rotation0; + var a1Start = body1.m_rotation0; + + //b2Vec2 p2Start = body2->m_position0; + var p2StartX = body2.m_position0.x; + var p2StartY = body2.m_position0.y; + //float32 a2Start = body2->m_rotation0; + var a2Start = body2.m_rotation0; + + //b2Vec2 p1 = p1Start; + var p1X = p1StartX; + var p1Y = p1StartY; + //float32 a1 = a1Start; + var a1 = a1Start; + //b2Vec2 p2 = p2Start; + var p2X = p2StartX; + var p2Y = p2StartY; + //float32 a2 = a2Start; + var a2 = a2Start; + + //b2Mat22 b2Conservative.R1(a1), b2Conservative.R2(a2); + b2Conservative.R1.Set(a1); + b2Conservative.R2.Set(a2); + + //shape1->QuickSync(p1, b2Conservative.R1); + shape1.QuickSync(p1, b2Conservative.R1); + //shape2->QuickSync(p2, b2Conservative.R2); + shape2.QuickSync(p2, b2Conservative.R2); + + //float32 s1 = 0.0f; + var s1 = 0.0; + //const int32 maxIterations = 10; + var maxIterations = 10; + //b2Vec2 d; + var dX; + var dY; + //float32 invRelativeVelocity = 0.0f; + var invRelativeVelocity = 0.0; + //bool hit = true; + var hit = true; + //b2Vec2 b2Conservative.x1, b2Conservative.x2; moved to static var + for (var iter = 0; iter < maxIterations; ++iter) + { + // Get the accurate distance between shapes. + //float32 distance = b2Distance.Distance(&b2Conservative.x1, &b2Conservative.x2, shape1, shape2); + var distance = b2Distance.Distance(b2Conservative.x1, b2Conservative.x2, shape1, shape2); + if (distance < b2Settings.b2_linearSlop) + { + if (iter == 0) + { + hit = false; + } + else + { + hit = true; + } + break; + } + + if (iter == 0) + { + //b2Vec2 d = b2Conservative.x2 - b2Conservative.x1; + dX = b2Conservative.x2.x - b2Conservative.x1.x; + dY = b2Conservative.x2.y - b2Conservative.x1.y; + //d.Normalize(); + var dLen = Math.sqrt(dX*dX + dY*dY); + //float32 relativeVelocity = b2Dot(d, v1 - v2) + b2Abs(omega1) * r1 + b2Abs(omega2) * r2; + var relativeVelocity = (dX*(v1X-v2X) + dY*(v1Y - v2Y)) + Math.abs(omega1) * r1 + Math.abs(omega2) * r2; + if (Math.abs(relativeVelocity) < Number.MIN_VALUE) + { + hit = false; + break; + } + + invRelativeVelocity = 1.0 / relativeVelocity; + } + + // Get the conservative movement. + //float32 ds = distance * invRelativeVelocity; + var ds = distance * invRelativeVelocity; + //float32 s2 = s1 + ds; + var s2 = s1 + ds; + + if (s2 < 0.0 || 1.0 < s2) + { + hit = false; + break; + } + + if (s2 < (1.0 + 100.0 * Number.MIN_VALUE) * s1) + { + hit = true; + break; + } + + s1 = s2; + + // Move forward conservatively. + //p1 = p1Start + s1 * v1; + p1X = p1StartX + s1 * v1.x; + p1Y = p1StartY + s1 * v1.y; + //a1 = a1Start + s1 * omega1; + a1 = a1Start + s1 * omega1; + //p2 = p2Start + s1 * v2; + p2X = p2StartX + s1 * v2.x; + p2Y = p2StartY + s1 * v2.y; + //a2 = a2Start + s1 * omega2; + a2 = a2Start + s1 * omega2; + + b2Conservative.R1.Set(a1); + b2Conservative.R2.Set(a2); + shape1.QuickSync(p1, b2Conservative.R1); + shape2.QuickSync(p2, b2Conservative.R2); + } + + if (hit) + { + // Hit, move bodies to safe position and re-sync shapes. + //b2Vec2 d = b2Conservative.x2 - b2Conservative.x1; + dX = b2Conservative.x2.x - b2Conservative.x1.x; + dY = b2Conservative.x2.y - b2Conservative.x1.y; + //float32 length = d.Length(); + var length = Math.sqrt(dX*dX + dY*dY); + if (length > FLT_EPSILON) + { + d *= b2_linearSlop / length; + } + + if (body1.IsStatic()) + { + //body1.m_position = p1; + body1.m_position.x = p1X; + body1.m_position.y = p1Y; + } + else + { + //body1.m_position = p1 - d; + body1.m_position.x = p1X - dX; + body1.m_position.y = p1Y - dY; + } + body1.m_rotation = a1; + body1.m_R.Set(a1); + body1.QuickSyncShapes(); + + if (body2.IsStatic()) + { + //body2->m_position = p2; + body2.m_position.x = p2X; + body2.m_position.y = p2Y; + } + else + { + //body2->m_position = p2 + d; + body2.m_position.x = p2X + dX; + body2.m_position.y = p2Y + dY; + } + //body2.m_position = p2 + d; + body2.m_position.x = p2X + dX; + body2.m_position.y = p2Y + dY; + body2.m_rotation = a2; + body2.m_R.Set(a2); + body2.QuickSyncShapes(); + + return true; + } + + // No hit, restore shapes. + shape1.QuickSync(body1.m_position, body1.m_R); + shape2.QuickSync(body2.m_position, body2.m_R); + return false; + }; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2Contact.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2Contact.js new file mode 100644 index 0000000..1626a7e --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2Contact.js @@ -0,0 +1,201 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +//typedef b2Contact* b2ContactCreateFcn(b2Shape* shape1, b2Shape* shape2, b2BlockAllocator* allocator); +//typedef void b2ContactDestroyFcn(b2Contact* contact, b2BlockAllocator* allocator); + + + +var b2Contact = Class.create(); +b2Contact.prototype = +{ + GetManifolds: function(){return null}, + GetManifoldCount: function() + { + return this.m_manifoldCount; + }, + + GetNext: function(){ + return this.m_next; + }, + + GetShape1: function(){ + return this.m_shape1; + }, + + GetShape2: function(){ + return this.m_shape2; + }, + + //--------------- Internals Below ------------------- + + // this.m_flags + // enum + + + initialize: function(s1, s2) + { + // initialize instance variables for references + this.m_node1 = new b2ContactNode(); + this.m_node2 = new b2ContactNode(); + // + + this.m_flags = 0; + + if (!s1 || !s2){ + this.m_shape1 = null; + this.m_shape2 = null; + return; + } + + this.m_shape1 = s1; + this.m_shape2 = s2; + + this.m_manifoldCount = 0; + + this.m_friction = Math.sqrt(this.m_shape1.m_friction * this.m_shape2.m_friction); + this.m_restitution = b2Math.b2Max(this.m_shape1.m_restitution, this.m_shape2.m_restitution); + + this.m_prev = null; + this.m_next = null; + + this.m_node1.contact = null; + this.m_node1.prev = null; + this.m_node1.next = null; + this.m_node1.other = null; + + this.m_node2.contact = null; + this.m_node2.prev = null; + this.m_node2.next = null; + this.m_node2.other = null; + }, + + //virtual ~b2Contact() {} + + Evaluate: function(){}, + + m_flags: 0, + + // World pool and list pointers. + m_prev: null, + m_next: null, + + // Nodes for connecting bodies. + m_node1: new b2ContactNode(), + m_node2: new b2ContactNode(), + + m_shape1: null, + m_shape2: null, + + m_manifoldCount: 0, + + // Combined friction + m_friction: null, + m_restitution: null}; +b2Contact.e_islandFlag = 0x0001; +b2Contact.e_destroyFlag = 0x0002; +b2Contact.AddType = function(createFcn, destroyFcn, type1, type2) + { + //b2Settings.b2Assert(b2Shape.e_unknownShape < type1 && type1 < b2Shape.e_shapeTypeCount); + //b2Settings.b2Assert(b2Shape.e_unknownShape < type2 && type2 < b2Shape.e_shapeTypeCount); + + b2Contact.s_registers[type1][type2].createFcn = createFcn; + b2Contact.s_registers[type1][type2].destroyFcn = destroyFcn; + b2Contact.s_registers[type1][type2].primary = true; + + if (type1 != type2) + { + b2Contact.s_registers[type2][type1].createFcn = createFcn; + b2Contact.s_registers[type2][type1].destroyFcn = destroyFcn; + b2Contact.s_registers[type2][type1].primary = false; + } + }; +b2Contact.InitializeRegisters = function(){ + b2Contact.s_registers = new Array(b2Shape.e_shapeTypeCount); + for (var i = 0; i < b2Shape.e_shapeTypeCount; i++){ + b2Contact.s_registers[i] = new Array(b2Shape.e_shapeTypeCount); + for (var j = 0; j < b2Shape.e_shapeTypeCount; j++){ + b2Contact.s_registers[i][j] = new b2ContactRegister(); + } + } + + b2Contact.AddType(b2CircleContact.Create, b2CircleContact.Destroy, b2Shape.e_circleShape, b2Shape.e_circleShape); + b2Contact.AddType(b2PolyAndCircleContact.Create, b2PolyAndCircleContact.Destroy, b2Shape.e_polyShape, b2Shape.e_circleShape); + b2Contact.AddType(b2PolyContact.Create, b2PolyContact.Destroy, b2Shape.e_polyShape, b2Shape.e_polyShape); + + }; +b2Contact.Create = function(shape1, shape2, allocator){ + if (b2Contact.s_initialized == false) + { + b2Contact.InitializeRegisters(); + b2Contact.s_initialized = true; + } + + var type1 = shape1.m_type; + var type2 = shape2.m_type; + + //b2Settings.b2Assert(b2Shape.e_unknownShape < type1 && type1 < b2Shape.e_shapeTypeCount); + //b2Settings.b2Assert(b2Shape.e_unknownShape < type2 && type2 < b2Shape.e_shapeTypeCount); + + var createFcn = b2Contact.s_registers[type1][type2].createFcn; + if (createFcn) + { + if (b2Contact.s_registers[type1][type2].primary) + { + return createFcn(shape1, shape2, allocator); + } + else + { + var c = createFcn(shape2, shape1, allocator); + for (var i = 0; i < c.GetManifoldCount(); ++i) + { + var m = c.GetManifolds()[ i ]; + m.normal = m.normal.Negative(); + } + return c; + } + } + else + { + return null; + } + }; +b2Contact.Destroy = function(contact, allocator){ + //b2Settings.b2Assert(b2Contact.s_initialized == true); + + if (contact.GetManifoldCount() > 0) + { + contact.m_shape1.m_body.WakeUp(); + contact.m_shape2.m_body.WakeUp(); + } + + var type1 = contact.m_shape1.m_type; + var type2 = contact.m_shape2.m_type; + + //b2Settings.b2Assert(b2Shape.e_unknownShape < type1 && type1 < b2Shape.e_shapeTypeCount); + //b2Settings.b2Assert(b2Shape.e_unknownShape < type2 && type2 < b2Shape.e_shapeTypeCount); + + var destroyFcn = b2Contact.s_registers[type1][type2].destroyFcn; + destroyFcn(contact, allocator); + }; +b2Contact.s_registers = null; +b2Contact.s_initialized = false; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactConstraint.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactConstraint.js new file mode 100644 index 0000000..9e83864 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactConstraint.js @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2ContactConstraint = Class.create(); +b2ContactConstraint.prototype = +{ + initialize: function(){ + // initialize instance variables for references + this.normal = new b2Vec2(); + // + + this.points = new Array(b2Settings.b2_maxManifoldPoints); + for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++){ + this.points[i] = new b2ContactConstraintPoint(); + } + + + }, + points: null, + normal: new b2Vec2(), + manifold: null, + body1: null, + body2: null, + friction: null, + restitution: null, + pointCount: 0}; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactConstraintPoint.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactConstraintPoint.js new file mode 100644 index 0000000..7346ba9 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactConstraintPoint.js @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2ContactConstraintPoint = Class.create(); +b2ContactConstraintPoint.prototype = +{ + localAnchor1: new b2Vec2(), + localAnchor2: new b2Vec2(), + normalImpulse: null, + tangentImpulse: null, + positionImpulse: null, + normalMass: null, + tangentMass: null, + separation: null, + velocityBias: null, + initialize: function() { + // initialize instance variables for references + this.localAnchor1 = new b2Vec2(); + this.localAnchor2 = new b2Vec2(); + // +}}; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactNode.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactNode.js new file mode 100644 index 0000000..f410e4b --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactNode.js @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2ContactNode = Class.create(); +b2ContactNode.prototype = +{ + other: null, + contact: null, + prev: null, + next: null, + initialize: function() {}}; + + + diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactRegister.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactRegister.js new file mode 100644 index 0000000..2da69e1 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactRegister.js @@ -0,0 +1,30 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +var b2ContactRegister = Class.create(); +b2ContactRegister.prototype = +{ + createFcn: null, + destroyFcn: null, + primary: null, + initialize: function() {}}; + + + diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactSolver.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactSolver.js new file mode 100644 index 0000000..1a5704c --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2ContactSolver.js @@ -0,0 +1,537 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2ContactSolver = Class.create(); +b2ContactSolver.prototype = +{ + initialize: function(contacts, contactCount, allocator){ + // initialize instance variables for references + this.m_constraints = new Array(); + // + + this.m_allocator = allocator; + + var i = 0; + var tVec; + var tMat; + + this.m_constraintCount = 0; + for (i = 0; i < contactCount; ++i) + { + this.m_constraintCount += contacts[i].GetManifoldCount(); + } + + // fill array + for (i = 0; i < this.m_constraintCount; i++){ + this.m_constraints[i] = new b2ContactConstraint(); + } + + var count = 0; + for (i = 0; i < contactCount; ++i) + { + var contact = contacts[i]; + var b1 = contact.m_shape1.m_body; + var b2 = contact.m_shape2.m_body; + var manifoldCount = contact.GetManifoldCount(); + var manifolds = contact.GetManifolds(); + var friction = contact.m_friction; + var restitution = contact.m_restitution; + + //var v1 = b1.m_linearVelocity.Copy(); + var v1X = b1.m_linearVelocity.x; + var v1Y = b1.m_linearVelocity.y; + //var v2 = b2.m_linearVelocity.Copy(); + var v2X = b2.m_linearVelocity.x; + var v2Y = b2.m_linearVelocity.y; + var w1 = b1.m_angularVelocity; + var w2 = b2.m_angularVelocity; + + for (var j = 0; j < manifoldCount; ++j) + { + var manifold = manifolds[ j ]; + + //b2Settings.b2Assert(manifold.pointCount > 0); + + //var normal = manifold.normal.Copy(); + var normalX = manifold.normal.x; + var normalY = manifold.normal.y; + + //b2Settings.b2Assert(count < this.m_constraintCount); + var c = this.m_constraints[ count ]; + c.body1 = b1; + c.body2 = b2; + c.manifold = manifold; + //c.normal = normal; + c.normal.x = normalX; + c.normal.y = normalY; + c.pointCount = manifold.pointCount; + c.friction = friction; + c.restitution = restitution; + + for (var k = 0; k < c.pointCount; ++k) + { + var cp = manifold.points[ k ]; + var ccp = c.points[ k ]; + + ccp.normalImpulse = cp.normalImpulse; + ccp.tangentImpulse = cp.tangentImpulse; + ccp.separation = cp.separation; + + //var r1 = b2Math.SubtractVV( cp.position, b1.m_position ); + var r1X = cp.position.x - b1.m_position.x; + var r1Y = cp.position.y - b1.m_position.y; + //var r2 = b2Math.SubtractVV( cp.position, b2.m_position ); + var r2X = cp.position.x - b2.m_position.x; + var r2Y = cp.position.y - b2.m_position.y; + + //ccp.localAnchor1 = b2Math.b2MulTMV(b1.m_R, r1); + tVec = ccp.localAnchor1; + tMat = b1.m_R; + tVec.x = r1X * tMat.col1.x + r1Y * tMat.col1.y; + tVec.y = r1X * tMat.col2.x + r1Y * tMat.col2.y; + + //ccp.localAnchor2 = b2Math.b2MulTMV(b2.m_R, r2); + tVec = ccp.localAnchor2; + tMat = b2.m_R; + tVec.x = r2X * tMat.col1.x + r2Y * tMat.col1.y; + tVec.y = r2X * tMat.col2.x + r2Y * tMat.col2.y; + + var r1Sqr = r1X * r1X + r1Y * r1Y; + var r2Sqr = r2X * r2X + r2Y * r2Y; + + //var rn1 = b2Math.b2Dot(r1, normal); + var rn1 = r1X*normalX + r1Y*normalY; + //var rn2 = b2Math.b2Dot(r2, normal); + var rn2 = r2X*normalX + r2Y*normalY; + var kNormal = b1.m_invMass + b2.m_invMass; + kNormal += b1.m_invI * (r1Sqr - rn1 * rn1) + b2.m_invI * (r2Sqr - rn2 * rn2); + //b2Settings.b2Assert(kNormal > Number.MIN_VALUE); + ccp.normalMass = 1.0 / kNormal; + + //var tangent = b2Math.b2CrossVF(normal, 1.0); + var tangentX = normalY + var tangentY = -normalX; + + //var rt1 = b2Math.b2Dot(r1, tangent); + var rt1 = r1X*tangentX + r1Y*tangentY; + //var rt2 = b2Math.b2Dot(r2, tangent); + var rt2 = r2X*tangentX + r2Y*tangentY; + var kTangent = b1.m_invMass + b2.m_invMass; + kTangent += b1.m_invI * (r1Sqr - rt1 * rt1) + b2.m_invI * (r2Sqr - rt2 * rt2); + //b2Settings.b2Assert(kTangent > Number.MIN_VALUE); + ccp.tangentMass = 1.0 / kTangent; + + // Setup a velocity bias for restitution. + ccp.velocityBias = 0.0; + if (ccp.separation > 0.0) + { + ccp.velocityBias = -60.0 * ccp.separation; + } + //var vRel = b2Math.b2Dot(c.normal, b2Math.SubtractVV( b2Math.SubtractVV( b2Math.AddVV( v2, b2Math.b2CrossFV(w2, r2)), v1 ), b2Math.b2CrossFV(w1, r1))); + var tX = v2X + (-w2*r2Y) - v1X - (-w1*r1Y); + var tY = v2Y + (w2*r2X) - v1Y - (w1*r1X); + //var vRel = b2Dot(c.normal, tX/Y); + var vRel = c.normal.x*tX + c.normal.y*tY; + if (vRel < -b2Settings.b2_velocityThreshold) + { + ccp.velocityBias += -c.restitution * vRel; + } + } + + ++count; + } + } + + //b2Settings.b2Assert(count == this.m_constraintCount); + }, + //~b2ContactSolver(); + + PreSolve: function(){ + var tVec; + var tVec2; + var tMat; + + // Warm start. + for (var i = 0; i < this.m_constraintCount; ++i) + { + var c = this.m_constraints[ i ]; + + var b1 = c.body1; + var b2 = c.body2; + var invMass1 = b1.m_invMass; + var invI1 = b1.m_invI; + var invMass2 = b2.m_invMass; + var invI2 = b2.m_invI; + //var normal = new b2Vec2(c.normal.x, c.normal.y); + var normalX = c.normal.x; + var normalY = c.normal.y; + //var tangent = b2Math.b2CrossVF(normal, 1.0); + var tangentX = normalY; + var tangentY = -normalX; + + var j = 0; + var tCount = 0; + if (b2World.s_enableWarmStarting) + { + tCount = c.pointCount; + for (j = 0; j < tCount; ++j) + { + var ccp = c.points[ j ]; + //var P = b2Math.AddVV( b2Math.MulFV(ccp.normalImpulse, normal), b2Math.MulFV(ccp.tangentImpulse, tangent)); + var PX = ccp.normalImpulse*normalX + ccp.tangentImpulse*tangentX; + var PY = ccp.normalImpulse*normalY + ccp.tangentImpulse*tangentY; + + //var r1 = b2Math.b2MulMV(b1.m_R, ccp.localAnchor1); + tMat = b1.m_R; + tVec = ccp.localAnchor1; + var r1X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + var r1Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + //var r2 = b2Math.b2MulMV(b2.m_R, ccp.localAnchor2); + tMat = b2.m_R; + tVec = ccp.localAnchor2; + var r2X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + var r2Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + //b1.m_angularVelocity -= invI1 * b2Math.b2CrossVV(r1, P); + b1.m_angularVelocity -= invI1 * (r1X * PY - r1Y * PX); + //b1.m_linearVelocity.Subtract( b2Math.MulFV(invMass1, P) ); + b1.m_linearVelocity.x -= invMass1 * PX; + b1.m_linearVelocity.y -= invMass1 * PY; + //b2.m_angularVelocity += invI2 * b2Math.b2CrossVV(r2, P); + b2.m_angularVelocity += invI2 * (r2X * PY - r2Y * PX); + //b2.m_linearVelocity.Add( b2Math.MulFV(invMass2, P) ); + b2.m_linearVelocity.x += invMass2 * PX; + b2.m_linearVelocity.y += invMass2 * PY; + + ccp.positionImpulse = 0.0; + } + } + else{ + tCount = c.pointCount; + for (j = 0; j < tCount; ++j) + { + var ccp2 = c.points[ j ]; + ccp2.normalImpulse = 0.0; + ccp2.tangentImpulse = 0.0; + + ccp2.positionImpulse = 0.0; + } + } + } + }, + SolveVelocityConstraints: function(){ + var j = 0; + var ccp; + var r1X; + var r1Y; + var r2X; + var r2Y; + var dvX; + var dvY; + var lambda; + var newImpulse; + var PX; + var PY; + + var tMat; + var tVec; + + for (var i = 0; i < this.m_constraintCount; ++i) + { + var c = this.m_constraints[ i ]; + var b1 = c.body1; + var b2 = c.body2; + var b1_angularVelocity = b1.m_angularVelocity; + var b1_linearVelocity = b1.m_linearVelocity; + var b2_angularVelocity = b2.m_angularVelocity; + var b2_linearVelocity = b2.m_linearVelocity; + + var invMass1 = b1.m_invMass; + var invI1 = b1.m_invI; + var invMass2 = b2.m_invMass; + var invI2 = b2.m_invI; + //var normal = new b2Vec2(c.normal.x, c.normal.y); + var normalX = c.normal.x; + var normalY = c.normal.y; + //var tangent = b2Math.b2CrossVF(normal, 1.0); + var tangentX = normalY; + var tangentY = -normalX; + + // Solver normal constraints + var tCount = c.pointCount; + for (j = 0; j < tCount; ++j) + { + ccp = c.points[ j ]; + + //r1 = b2Math.b2MulMV(b1.m_R, ccp.localAnchor1); + tMat = b1.m_R; + tVec = ccp.localAnchor1; + r1X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y + r1Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y + //r2 = b2Math.b2MulMV(b2.m_R, ccp.localAnchor2); + tMat = b2.m_R; + tVec = ccp.localAnchor2; + r2X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y + r2Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y + + // Relative velocity at contact + //var dv = b2Math.SubtractVV( b2Math.AddVV( b2.m_linearVelocity, b2Math.b2CrossFV(b2.m_angularVelocity, r2)), b2Math.SubtractVV(b1.m_linearVelocity, b2Math.b2CrossFV(b1.m_angularVelocity, r1))); + //dv = b2Math.SubtractVV(b2Math.SubtractVV( b2Math.AddVV( b2.m_linearVelocity, b2Math.b2CrossFV(b2.m_angularVelocity, r2)), b1.m_linearVelocity), b2Math.b2CrossFV(b1.m_angularVelocity, r1)); + dvX = b2_linearVelocity.x + (-b2_angularVelocity * r2Y) - b1_linearVelocity.x - (-b1_angularVelocity * r1Y); + dvY = b2_linearVelocity.y + (b2_angularVelocity * r2X) - b1_linearVelocity.y - (b1_angularVelocity * r1X); + + // Compute normal impulse + //var vn = b2Math.b2Dot(dv, normal); + var vn = dvX * normalX + dvY * normalY; + lambda = -ccp.normalMass * (vn - ccp.velocityBias); + + // b2Clamp the accumulated impulse + newImpulse = b2Math.b2Max(ccp.normalImpulse + lambda, 0.0); + lambda = newImpulse - ccp.normalImpulse; + + // Apply contact impulse + //P = b2Math.MulFV(lambda, normal); + PX = lambda * normalX; + PY = lambda * normalY; + + //b1.m_linearVelocity.Subtract( b2Math.MulFV( invMass1, P ) ); + b1_linearVelocity.x -= invMass1 * PX; + b1_linearVelocity.y -= invMass1 * PY; + b1_angularVelocity -= invI1 * (r1X * PY - r1Y * PX); + + //b2.m_linearVelocity.Add( b2Math.MulFV( invMass2, P ) ); + b2_linearVelocity.x += invMass2 * PX; + b2_linearVelocity.y += invMass2 * PY; + b2_angularVelocity += invI2 * (r2X * PY - r2Y * PX); + + ccp.normalImpulse = newImpulse; + + + + // MOVED FROM BELOW + // Relative velocity at contact + //var dv = b2.m_linearVelocity + b2Cross(b2.m_angularVelocity, r2) - b1.m_linearVelocity - b2Cross(b1.m_angularVelocity, r1); + //dv = b2Math.SubtractVV(b2Math.SubtractVV(b2Math.AddVV(b2.m_linearVelocity, b2Math.b2CrossFV(b2.m_angularVelocity, r2)), b1.m_linearVelocity), b2Math.b2CrossFV(b1.m_angularVelocity, r1)); + dvX = b2_linearVelocity.x + (-b2_angularVelocity * r2Y) - b1_linearVelocity.x - (-b1_angularVelocity * r1Y); + dvY = b2_linearVelocity.y + (b2_angularVelocity * r2X) - b1_linearVelocity.y - (b1_angularVelocity * r1X); + + // Compute tangent impulse + var vt = dvX*tangentX + dvY*tangentY; + lambda = ccp.tangentMass * (-vt); + + // b2Clamp the accumulated impulse + var maxFriction = c.friction * ccp.normalImpulse; + newImpulse = b2Math.b2Clamp(ccp.tangentImpulse + lambda, -maxFriction, maxFriction); + lambda = newImpulse - ccp.tangentImpulse; + + // Apply contact impulse + //P = b2Math.MulFV(lambda, tangent); + PX = lambda * tangentX; + PY = lambda * tangentY; + + //b1.m_linearVelocity.Subtract( b2Math.MulFV( invMass1, P ) ); + b1_linearVelocity.x -= invMass1 * PX; + b1_linearVelocity.y -= invMass1 * PY; + b1_angularVelocity -= invI1 * (r1X * PY - r1Y * PX); + + //b2.m_linearVelocity.Add( b2Math.MulFV( invMass2, P ) ); + b2_linearVelocity.x += invMass2 * PX; + b2_linearVelocity.y += invMass2 * PY; + b2_angularVelocity += invI2 * (r2X * PY - r2Y * PX); + + ccp.tangentImpulse = newImpulse; + } + + + + // Solver tangent constraints + // MOVED ABOVE FOR EFFICIENCY + /*for (j = 0; j < tCount; ++j) + { + ccp = c.points[ j ]; + + //r1 = b2Math.b2MulMV(b1.m_R, ccp.localAnchor1); + tMat = b1.m_R; + tVec = ccp.localAnchor1; + r1X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y + r1Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y + //r2 = b2Math.b2MulMV(b2.m_R, ccp.localAnchor2); + tMat = b2.m_R; + tVec = ccp.localAnchor2; + r2X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y + r2Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y + + // Relative velocity at contact + //var dv = b2.m_linearVelocity + b2Cross(b2.m_angularVelocity, r2) - b1.m_linearVelocity - b2Cross(b1.m_angularVelocity, r1); + //dv = b2Math.SubtractVV(b2Math.SubtractVV(b2Math.AddVV(b2.m_linearVelocity, b2Math.b2CrossFV(b2.m_angularVelocity, r2)), b1.m_linearVelocity), b2Math.b2CrossFV(b1.m_angularVelocity, r1)); + dvX = b2_linearVelocity.x + (-b2_angularVelocity * r2Y) - b1_linearVelocity.x - (-b1_angularVelocity * r1Y); + dvY = b2_linearVelocity.y + (b2_angularVelocity * r2X) - b1_linearVelocity.y - (b1_angularVelocity * r1X); + + // Compute tangent impulse + var vt = dvX*tangentX + dvY*tangentY; + lambda = ccp.tangentMass * (-vt); + + // b2Clamp the accumulated impulse + var maxFriction = c.friction * ccp.normalImpulse; + newImpulse = b2Math.b2Clamp(ccp.tangentImpulse + lambda, -maxFriction, maxFriction); + lambda = newImpulse - ccp.tangentImpulse; + + // Apply contact impulse + //P = b2Math.MulFV(lambda, tangent); + PX = lambda * tangentX; + PY = lambda * tangentY; + + //b1.m_linearVelocity.Subtract( b2Math.MulFV( invMass1, P ) ); + b1_linearVelocity.x -= invMass1 * PX; + b1_linearVelocity.y -= invMass1 * PY; + b1_angularVelocity -= invI1 * (r1X * PY - r1Y * PX); + + //b2.m_linearVelocity.Add( b2Math.MulFV( invMass2, P ) ); + b2_linearVelocity.x += invMass2 * PX; + b2_linearVelocity.y += invMass2 * PY; + b2_angularVelocity += invI2 * (r2X * PY - r2Y * PX); + + ccp.tangentImpulse = newImpulse; + }*/ + + // Update angular velocity + b1.m_angularVelocity = b1_angularVelocity; + b2.m_angularVelocity = b2_angularVelocity; + } + }, + SolvePositionConstraints: function(beta){ + var minSeparation = 0.0; + + var tMat; + var tVec; + + for (var i = 0; i < this.m_constraintCount; ++i) + { + var c = this.m_constraints[ i ]; + var b1 = c.body1; + var b2 = c.body2; + var b1_position = b1.m_position; + var b1_rotation = b1.m_rotation; + var b2_position = b2.m_position; + var b2_rotation = b2.m_rotation; + + var invMass1 = b1.m_invMass; + var invI1 = b1.m_invI; + var invMass2 = b2.m_invMass; + var invI2 = b2.m_invI; + //var normal = new b2Vec2(c.normal.x, c.normal.y); + var normalX = c.normal.x; + var normalY = c.normal.y; + //var tangent = b2Math.b2CrossVF(normal, 1.0); + var tangentX = normalY; + var tangentY = -normalX; + + // Solver normal constraints + var tCount = c.pointCount; + for (var j = 0; j < tCount; ++j) + { + var ccp = c.points[ j ]; + + //r1 = b2Math.b2MulMV(b1.m_R, ccp.localAnchor1); + tMat = b1.m_R; + tVec = ccp.localAnchor1; + var r1X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y + var r1Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y + //r2 = b2Math.b2MulMV(b2.m_R, ccp.localAnchor2); + tMat = b2.m_R; + tVec = ccp.localAnchor2; + var r2X = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y + var r2Y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y + + //var p1 = b2Math.AddVV(b1.m_position, r1); + var p1X = b1_position.x + r1X; + var p1Y = b1_position.y + r1Y; + + //var p2 = b2Math.AddVV(b2.m_position, r2); + var p2X = b2_position.x + r2X; + var p2Y = b2_position.y + r2Y; + + //var dp = b2Math.SubtractVV(p2, p1); + var dpX = p2X - p1X; + var dpY = p2Y - p1Y; + + // Approximate the current separation. + //var separation = b2Math.b2Dot(dp, normal) + ccp.separation; + var separation = (dpX*normalX + dpY*normalY) + ccp.separation; + + // Track max constraint error. + minSeparation = b2Math.b2Min(minSeparation, separation); + + // Prevent large corrections and allow slop. + var C = beta * b2Math.b2Clamp(separation + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + + // Compute normal impulse + var dImpulse = -ccp.normalMass * C; + + // b2Clamp the accumulated impulse + var impulse0 = ccp.positionImpulse; + ccp.positionImpulse = b2Math.b2Max(impulse0 + dImpulse, 0.0); + dImpulse = ccp.positionImpulse - impulse0; + + //var impulse = b2Math.MulFV( dImpulse, normal ); + var impulseX = dImpulse * normalX; + var impulseY = dImpulse * normalY; + + //b1.m_position.Subtract( b2Math.MulFV( invMass1, impulse ) ); + b1_position.x -= invMass1 * impulseX; + b1_position.y -= invMass1 * impulseY; + b1_rotation -= invI1 * (r1X * impulseY - r1Y * impulseX); + b1.m_R.Set(b1_rotation); + + //b2.m_position.Add( b2Math.MulFV( invMass2, impulse ) ); + b2_position.x += invMass2 * impulseX; + b2_position.y += invMass2 * impulseY; + b2_rotation += invI2 * (r2X * impulseY - r2Y * impulseX); + b2.m_R.Set(b2_rotation); + } + // Update body rotations + b1.m_rotation = b1_rotation; + b2.m_rotation = b2_rotation; + } + + return minSeparation >= -b2Settings.b2_linearSlop; + }, + PostSolve: function(){ + for (var i = 0; i < this.m_constraintCount; ++i) + { + var c = this.m_constraints[ i ]; + var m = c.manifold; + + for (var j = 0; j < c.pointCount; ++j) + { + var mPoint = m.points[j]; + var cPoint = c.points[j]; + mPoint.normalImpulse = cPoint.normalImpulse; + mPoint.tangentImpulse = cPoint.tangentImpulse; + } + } + }, + + m_allocator: null, + m_constraints: new Array(), + m_constraintCount: 0}; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2NullContact.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2NullContact.js new file mode 100644 index 0000000..2d018be --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2NullContact.js @@ -0,0 +1,65 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2NullContact = Class.create(); +Object.extend(b2NullContact.prototype, b2Contact.prototype); +Object.extend(b2NullContact.prototype, +{ + initialize: function(s1, s2) { + // The constructor for b2Contact + // initialize instance variables for references + this.m_node1 = new b2ContactNode(); + this.m_node2 = new b2ContactNode(); + // + this.m_flags = 0; + + if (!s1 || !s2){ + this.m_shape1 = null; + this.m_shape2 = null; + return; + } + + this.m_shape1 = s1; + this.m_shape2 = s2; + + this.m_manifoldCount = 0; + + this.m_friction = Math.sqrt(this.m_shape1.m_friction * this.m_shape2.m_friction); + this.m_restitution = b2Math.b2Max(this.m_shape1.m_restitution, this.m_shape2.m_restitution); + + this.m_prev = null; + this.m_next = null; + + this.m_node1.contact = null; + this.m_node1.prev = null; + this.m_node1.next = null; + this.m_node1.other = null; + + this.m_node2.contact = null; + this.m_node2.prev = null; + this.m_node2.next = null; + this.m_node2.other = null; + // +}, + Evaluate: function() {}, + GetManifolds: function(){ return null; }}); + diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2PolyAndCircleContact.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2PolyAndCircleContact.js new file mode 100644 index 0000000..31ba2f2 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2PolyAndCircleContact.js @@ -0,0 +1,103 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2PolyAndCircleContact = Class.create(); +Object.extend(b2PolyAndCircleContact.prototype, b2Contact.prototype); +Object.extend(b2PolyAndCircleContact.prototype, { + + + initialize: function(s1, s2) { + // The constructor for b2Contact + // initialize instance variables for references + this.m_node1 = new b2ContactNode(); + this.m_node2 = new b2ContactNode(); + // + this.m_flags = 0; + + if (!s1 || !s2){ + this.m_shape1 = null; + this.m_shape2 = null; + return; + } + + this.m_shape1 = s1; + this.m_shape2 = s2; + + this.m_manifoldCount = 0; + + this.m_friction = Math.sqrt(this.m_shape1.m_friction * this.m_shape2.m_friction); + this.m_restitution = b2Math.b2Max(this.m_shape1.m_restitution, this.m_shape2.m_restitution); + + this.m_prev = null; + this.m_next = null; + + this.m_node1.contact = null; + this.m_node1.prev = null; + this.m_node1.next = null; + this.m_node1.other = null; + + this.m_node2.contact = null; + this.m_node2.prev = null; + this.m_node2.next = null; + this.m_node2.other = null; + // + + // initialize instance variables for references + this.m_manifold = [new b2Manifold()]; + // + + //super(shape1, shape2); + + b2Settings.b2Assert(this.m_shape1.m_type == b2Shape.e_polyShape); + b2Settings.b2Assert(this.m_shape2.m_type == b2Shape.e_circleShape); + this.m_manifold[0].pointCount = 0; + this.m_manifold[0].points[0].normalImpulse = 0.0; + this.m_manifold[0].points[0].tangentImpulse = 0.0; + }, + //~b2PolyAndCircleContact() {} + + Evaluate: function(){ + b2Collision.b2CollidePolyAndCircle(this.m_manifold[0], this.m_shape1, this.m_shape2, false); + + if (this.m_manifold[0].pointCount > 0) + { + this.m_manifoldCount = 1; + } + else + { + this.m_manifoldCount = 0; + } + }, + + GetManifolds: function() + { + return this.m_manifold; + }, + + m_manifold: [new b2Manifold()]}) + +b2PolyAndCircleContact.Create = function(shape1, shape2, allocator){ + return new b2PolyAndCircleContact(shape1, shape2); + }; +b2PolyAndCircleContact.Destroy = function(contact, allocator){ + // + }; diff --git a/lib/box2d-js/js/box2d/dynamics/contacts/b2PolyContact.js b/lib/box2d-js/js/box2d/dynamics/contacts/b2PolyContact.js new file mode 100644 index 0000000..9ba3120 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/contacts/b2PolyContact.js @@ -0,0 +1,163 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2PolyContact = Class.create(); +Object.extend(b2PolyContact.prototype, b2Contact.prototype); +Object.extend(b2PolyContact.prototype, +{ + + initialize: function(s1, s2) { + // The constructor for b2Contact + // initialize instance variables for references + this.m_node1 = new b2ContactNode(); + this.m_node2 = new b2ContactNode(); + // + this.m_flags = 0; + + if (!s1 || !s2){ + this.m_shape1 = null; + this.m_shape2 = null; + return; + } + + this.m_shape1 = s1; + this.m_shape2 = s2; + + this.m_manifoldCount = 0; + + this.m_friction = Math.sqrt(this.m_shape1.m_friction * this.m_shape2.m_friction); + this.m_restitution = b2Math.b2Max(this.m_shape1.m_restitution, this.m_shape2.m_restitution); + + this.m_prev = null; + this.m_next = null; + + this.m_node1.contact = null; + this.m_node1.prev = null; + this.m_node1.next = null; + this.m_node1.other = null; + + this.m_node2.contact = null; + this.m_node2.prev = null; + this.m_node2.next = null; + this.m_node2.other = null; + // + + // initialize instance variables for references + this.m0 = new b2Manifold(); + this.m_manifold = [new b2Manifold()]; + // + + //super(shape1, shape2); + //b2Settings.b2Assert(this.m_shape1.m_type == b2Shape.e_polyShape); + //b2Settings.b2Assert(this.m_shape2.m_type == b2Shape.e_polyShape); + this.m_manifold[0].pointCount = 0; + }, + //~b2PolyContact() {} + + // store temp manifold to reduce calls to new + m0: new b2Manifold(), + + Evaluate: function(){ + var tMani = this.m_manifold[0]; + // replace memcpy + // memcpy(&this.m0, &this.m_manifold, sizeof(b2Manifold)); + //this.m0.points = new Array(tMani.pointCount); + var tPoints = this.m0.points; + + for (var k = 0; k < tMani.pointCount; k++){ + var tPoint = tPoints[k]; + var tPoint0 = tMani.points[k]; + //tPoint.separation = tPoint0.separation; + tPoint.normalImpulse = tPoint0.normalImpulse; + tPoint.tangentImpulse = tPoint0.tangentImpulse; + //tPoint.position.SetV( tPoint0.position ); + + tPoint.id = tPoint0.id.Copy(); + + /*this.m0.points[k].id.features = new Features(); + this.m0.points[k].id.features.referenceFace = this.m_manifold[0].points[k].id.features.referenceFace; + this.m0.points[k].id.features.incidentEdge = this.m_manifold[0].points[k].id.features.incidentEdge; + this.m0.points[k].id.features.incidentVertex = this.m_manifold[0].points[k].id.features.incidentVertex; + this.m0.points[k].id.features.flip = this.m_manifold[0].points[k].id.features.flip;*/ + } + //this.m0.normal.SetV( tMani.normal ); + this.m0.pointCount = tMani.pointCount; + + b2Collision.b2CollidePoly(tMani, this.m_shape1, this.m_shape2, false); + + // Match contact ids to facilitate warm starting. + if (tMani.pointCount > 0) + { + var match = [false, false]; + + // Match old contact ids to new contact ids and copy the + // stored impulses to warm start the solver. + for (var i = 0; i < tMani.pointCount; ++i) + { + var cp = tMani.points[ i ]; + + cp.normalImpulse = 0.0; + cp.tangentImpulse = 0.0; + var idKey = cp.id.key; + + for (var j = 0; j < this.m0.pointCount; ++j) + { + + if (match[j] == true) + continue; + + var cp0 = this.m0.points[j]; + var id0 = cp0.id; + + if (id0.key == idKey) + { + match[j] = true; + cp.normalImpulse = cp0.normalImpulse; + cp.tangentImpulse = cp0.tangentImpulse; + break; + } + } + } + + this.m_manifoldCount = 1; + } + else + { + this.m_manifoldCount = 0; + } + }, + + GetManifolds: function() + { + return this.m_manifold; + }, + + m_manifold: [new b2Manifold()]}); + +b2PolyContact.Create = function(shape1, shape2, allocator){ + //void* mem = allocator->Allocate(sizeof(b2PolyContact)); + return new b2PolyContact(shape1, shape2); + }; +b2PolyContact.Destroy = function(contact, allocator){ + //((b2PolyContact*)contact)->~b2PolyContact(); + //allocator->Free(contact, sizeof(b2PolyContact)); + }; diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2DistanceJoint.js b/lib/box2d-js/js/box2d/dynamics/joints/b2DistanceJoint.js new file mode 100644 index 0000000..d0a6f61 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2DistanceJoint.js @@ -0,0 +1,264 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + +// C = norm(p2 - p1) - L +// u = (p2 - p1) / norm(p2 - p1) +// Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1)) +// J = [-u -cross(r1, u) u cross(r2, u)] +// K = J * invM * JT +// = invMass1 + invI1 * cross(r1, u)^2 + invMass2 + invI2 * cross(r2, u)^2 + +var b2DistanceJoint = Class.create(); +Object.extend(b2DistanceJoint.prototype, b2Joint.prototype); +Object.extend(b2DistanceJoint.prototype, +{ + //--------------- Internals Below ------------------- + + initialize: function(def){ + // The constructor for b2Joint + // initialize instance variables for references + this.m_node1 = new b2JointNode(); + this.m_node2 = new b2JointNode(); + // + this.m_type = def.type; + this.m_prev = null; + this.m_next = null; + this.m_body1 = def.body1; + this.m_body2 = def.body2; + this.m_collideConnected = def.collideConnected; + this.m_islandFlag = false; + this.m_userData = def.userData; + // + + // initialize instance variables for references + this.m_localAnchor1 = new b2Vec2(); + this.m_localAnchor2 = new b2Vec2(); + this.m_u = new b2Vec2(); + // + + //super(def); + + var tMat; + var tX; + var tY; + //this.m_localAnchor1 = b2MulT(this.m_body1->m_R, def->anchorPoint1 - this.m_body1->m_position); + tMat = this.m_body1.m_R; + tX = def.anchorPoint1.x - this.m_body1.m_position.x; + tY = def.anchorPoint1.y - this.m_body1.m_position.y; + this.m_localAnchor1.x = tX*tMat.col1.x + tY*tMat.col1.y; + this.m_localAnchor1.y = tX*tMat.col2.x + tY*tMat.col2.y; + //this.m_localAnchor2 = b2MulT(this.m_body2->m_R, def->anchorPoint2 - this.m_body2->m_position); + tMat = this.m_body2.m_R; + tX = def.anchorPoint2.x - this.m_body2.m_position.x; + tY = def.anchorPoint2.y - this.m_body2.m_position.y; + this.m_localAnchor2.x = tX*tMat.col1.x + tY*tMat.col1.y; + this.m_localAnchor2.y = tX*tMat.col2.x + tY*tMat.col2.y; + + //b2Vec2 d = def->anchorPoint2 - def->anchorPoint1; + tX = def.anchorPoint2.x - def.anchorPoint1.x; + tY = def.anchorPoint2.y - def.anchorPoint1.y; + //this.m_length = d.Length(); + this.m_length = Math.sqrt(tX*tX + tY*tY); + this.m_impulse = 0.0; + }, + + PrepareVelocitySolver: function(){ + + var tMat; + + // Compute the effective mass matrix. + //b2Vec2 r1 = b2Mul(this.m_body1->m_R, this.m_localAnchor1); + tMat = this.m_body1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(this.m_body2->m_R, this.m_localAnchor2); + tMat = this.m_body2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //this.m_u = this.m_body2->m_position + r2 - this.m_body1->m_position - r1; + this.m_u.x = this.m_body2.m_position.x + r2X - this.m_body1.m_position.x - r1X; + this.m_u.y = this.m_body2.m_position.y + r2Y - this.m_body1.m_position.y - r1Y; + + // Handle singularity. + //float32 length = this.m_u.Length(); + var length = Math.sqrt(this.m_u.x*this.m_u.x + this.m_u.y*this.m_u.y); + if (length > b2Settings.b2_linearSlop) + { + //this.m_u *= 1.0 / length; + this.m_u.Multiply( 1.0 / length ); + } + else + { + this.m_u.SetZero(); + } + + //float32 cr1u = b2Cross(r1, this.m_u); + var cr1u = (r1X * this.m_u.y - r1Y * this.m_u.x); + //float32 cr2u = b2Cross(r2, this.m_u); + var cr2u = (r2X * this.m_u.y - r2Y * this.m_u.x); + //this.m_mass = this.m_body1->m_invMass + this.m_body1->m_invI * cr1u * cr1u + this.m_body2->m_invMass + this.m_body2->m_invI * cr2u * cr2u; + this.m_mass = this.m_body1.m_invMass + this.m_body1.m_invI * cr1u * cr1u + this.m_body2.m_invMass + this.m_body2.m_invI * cr2u * cr2u; + //b2Settings.b2Assert(this.m_mass > Number.MIN_VALUE); + this.m_mass = 1.0 / this.m_mass; + + if (b2World.s_enableWarmStarting) + { + //b2Vec2 P = this.m_impulse * this.m_u; + var PX = this.m_impulse * this.m_u.x; + var PY = this.m_impulse * this.m_u.y; + //this.m_body1.m_linearVelocity -= this.m_body1.m_invMass * P; + this.m_body1.m_linearVelocity.x -= this.m_body1.m_invMass * PX; + this.m_body1.m_linearVelocity.y -= this.m_body1.m_invMass * PY; + //this.m_body1.m_angularVelocity -= this.m_body1.m_invI * b2Cross(r1, P); + this.m_body1.m_angularVelocity -= this.m_body1.m_invI * (r1X * PY - r1Y * PX); + //this.m_body2.m_linearVelocity += this.m_body2.m_invMass * P; + this.m_body2.m_linearVelocity.x += this.m_body2.m_invMass * PX; + this.m_body2.m_linearVelocity.y += this.m_body2.m_invMass * PY; + //this.m_body2.m_angularVelocity += this.m_body2.m_invI * b2Cross(r2, P); + this.m_body2.m_angularVelocity += this.m_body2.m_invI * (r2X * PY - r2Y * PX); + } + else + { + this.m_impulse = 0.0; + } + + }, + + + + SolveVelocityConstraints: function(step){ + + var tMat; + + //b2Vec2 r1 = b2Mul(this.m_body1->m_R, this.m_localAnchor1); + tMat = this.m_body1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(this.m_body2->m_R, this.m_localAnchor2); + tMat = this.m_body2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + // Cdot = dot(u, v + cross(w, r)) + //b2Vec2 v1 = this.m_body1->m_linearVelocity + b2Cross(this.m_body1->m_angularVelocity, r1); + var v1X = this.m_body1.m_linearVelocity.x + (-this.m_body1.m_angularVelocity * r1Y); + var v1Y = this.m_body1.m_linearVelocity.y + (this.m_body1.m_angularVelocity * r1X); + //b2Vec2 v2 = this.m_body2->m_linearVelocity + b2Cross(this.m_body2->m_angularVelocity, r2); + var v2X = this.m_body2.m_linearVelocity.x + (-this.m_body2.m_angularVelocity * r2Y); + var v2Y = this.m_body2.m_linearVelocity.y + (this.m_body2.m_angularVelocity * r2X); + //float32 Cdot = b2Dot(this.m_u, v2 - v1); + var Cdot = (this.m_u.x * (v2X - v1X) + this.m_u.y * (v2Y - v1Y)); + //float32 impulse = -this.m_mass * Cdot; + var impulse = -this.m_mass * Cdot; + this.m_impulse += impulse; + + //b2Vec2 P = impulse * this.m_u; + var PX = impulse * this.m_u.x; + var PY = impulse * this.m_u.y; + //this.m_body1->m_linearVelocity -= this.m_body1->m_invMass * P; + this.m_body1.m_linearVelocity.x -= this.m_body1.m_invMass * PX; + this.m_body1.m_linearVelocity.y -= this.m_body1.m_invMass * PY; + //this.m_body1->m_angularVelocity -= this.m_body1->m_invI * b2Cross(r1, P); + this.m_body1.m_angularVelocity -= this.m_body1.m_invI * (r1X * PY - r1Y * PX); + //this.m_body2->m_linearVelocity += this.m_body2->m_invMass * P; + this.m_body2.m_linearVelocity.x += this.m_body2.m_invMass * PX; + this.m_body2.m_linearVelocity.y += this.m_body2.m_invMass * PY; + //this.m_body2->m_angularVelocity += this.m_body2->m_invI * b2Cross(r2, P); + this.m_body2.m_angularVelocity += this.m_body2.m_invI * (r2X * PY - r2Y * PX); + }, + + SolvePositionConstraints: function(){ + + var tMat; + + //b2Vec2 r1 = b2Mul(this.m_body1->m_R, this.m_localAnchor1); + tMat = this.m_body1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(this.m_body2->m_R, this.m_localAnchor2); + tMat = this.m_body2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //b2Vec2 d = this.m_body2->m_position + r2 - this.m_body1->m_position - r1; + var dX = this.m_body2.m_position.x + r2X - this.m_body1.m_position.x - r1X; + var dY = this.m_body2.m_position.y + r2Y - this.m_body1.m_position.y - r1Y; + + //float32 length = d.Normalize(); + var length = Math.sqrt(dX*dX + dY*dY); + dX /= length; + dY /= length; + //float32 C = length - this.m_length; + var C = length - this.m_length; + C = b2Math.b2Clamp(C, -b2Settings.b2_maxLinearCorrection, b2Settings.b2_maxLinearCorrection); + + var impulse = -this.m_mass * C; + //this.m_u = d; + this.m_u.Set(dX, dY); + //b2Vec2 P = impulse * this.m_u; + var PX = impulse * this.m_u.x; + var PY = impulse * this.m_u.y; + + //this.m_body1->m_position -= this.m_body1->m_invMass * P; + this.m_body1.m_position.x -= this.m_body1.m_invMass * PX; + this.m_body1.m_position.y -= this.m_body1.m_invMass * PY; + //this.m_body1->m_rotation -= this.m_body1->m_invI * b2Cross(r1, P); + this.m_body1.m_rotation -= this.m_body1.m_invI * (r1X * PY - r1Y * PX); + //this.m_body2->m_position += this.m_body2->m_invMass * P; + this.m_body2.m_position.x += this.m_body2.m_invMass * PX; + this.m_body2.m_position.y += this.m_body2.m_invMass * PY; + //this.m_body2->m_rotation -= this.m_body2->m_invI * b2Cross(r2, P); + this.m_body2.m_rotation += this.m_body2.m_invI * (r2X * PY - r2Y * PX); + + this.m_body1.m_R.Set(this.m_body1.m_rotation); + this.m_body2.m_R.Set(this.m_body2.m_rotation); + + return b2Math.b2Abs(C) < b2Settings.b2_linearSlop; + + }, + + GetAnchor1: function(){ + return b2Math.AddVV(this.m_body1.m_position , b2Math.b2MulMV(this.m_body1.m_R, this.m_localAnchor1)); + }, + GetAnchor2: function(){ + return b2Math.AddVV(this.m_body2.m_position , b2Math.b2MulMV(this.m_body2.m_R, this.m_localAnchor2)); + }, + + GetReactionForce: function(invTimeStep) + { + //var F = (this.m_impulse * invTimeStep) * this.m_u; + var F = new b2Vec2(); + F.SetV(this.m_u); + F.Multiply(this.m_impulse * invTimeStep); + return F; + }, + + GetReactionTorque: function(invTimeStep) + { + //NOT_USED(invTimeStep); + return 0.0; + }, + + m_localAnchor1: new b2Vec2(), + m_localAnchor2: new b2Vec2(), + m_u: new b2Vec2(), + m_impulse: null, + m_mass: null, + m_length: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2DistanceJointDef.js b/lib/box2d-js/js/box2d/dynamics/joints/b2DistanceJointDef.js new file mode 100644 index 0000000..616f77e --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2DistanceJointDef.js @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2DistanceJointDef = Class.create(); +Object.extend(b2DistanceJointDef.prototype, b2JointDef.prototype); +Object.extend(b2DistanceJointDef.prototype, +{ + initialize: function() + { + // The constructor for b2JointDef + this.type = b2Joint.e_unknownJoint; + this.userData = null; + this.body1 = null; + this.body2 = null; + this.collideConnected = false; + // + + // initialize instance variables for references + this.anchorPoint1 = new b2Vec2(); + this.anchorPoint2 = new b2Vec2(); + // + + this.type = b2Joint.e_distanceJoint; + //this.anchorPoint1.Set(0.0, 0.0); + //this.anchorPoint2.Set(0.0, 0.0); + }, + + anchorPoint1: new b2Vec2(), + anchorPoint2: new b2Vec2()}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2GearJoint.js b/lib/box2d-js/js/box2d/dynamics/joints/b2GearJoint.js new file mode 100644 index 0000000..8470730 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2GearJoint.js @@ -0,0 +1,307 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + +var b2GearJoint = Class.create(); +Object.extend(b2GearJoint.prototype, b2Joint.prototype); +Object.extend(b2GearJoint.prototype, +{ + GetAnchor1: function(){ + //return this.m_body1.m_position + b2MulMV(this.m_body1.m_R, this.m_localAnchor1); + var tMat = this.m_body1.m_R; + return new b2Vec2( this.m_body1.m_position.x + (tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y), + this.m_body1.m_position.y + (tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y)); + }, + GetAnchor2: function(){ + //return this.m_body2->m_position + b2Mul(this.m_body2->m_R, this.m_localAnchor2); + var tMat = this.m_body2.m_R; + return new b2Vec2( this.m_body2.m_position.x + (tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y), + this.m_body2.m_position.y + (tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y)); + }, + + GetReactionForce: function(invTimeStep){ + //b2Vec2 F(0.0f, 0.0f); + return new b2Vec2(); + }, + GetReactionTorque: function(invTimeStep){ + return 0.0; + }, + + GetRatio: function(){ + return this.m_ratio; + }, + + //--------------- Internals Below ------------------- + + initialize: function(def){ + // The constructor for b2Joint + // initialize instance variables for references + this.m_node1 = new b2JointNode(); + this.m_node2 = new b2JointNode(); + // + this.m_type = def.type; + this.m_prev = null; + this.m_next = null; + this.m_body1 = def.body1; + this.m_body2 = def.body2; + this.m_collideConnected = def.collideConnected; + this.m_islandFlag = false; + this.m_userData = def.userData; + // + + // initialize instance variables for references + this.m_groundAnchor1 = new b2Vec2(); + this.m_groundAnchor2 = new b2Vec2(); + this.m_localAnchor1 = new b2Vec2(); + this.m_localAnchor2 = new b2Vec2(); + this.m_J = new b2Jacobian(); + // + + // parent constructor + //super(def); + + //b2Settings.b2Assert(def.joint1.m_type == b2Joint.e_revoluteJoint || def.joint1.m_type == b2Joint.e_prismaticJoint); + //b2Settings.b2Assert(def.joint2.m_type == b2Joint.e_revoluteJoint || def.joint2.m_type == b2Joint.e_prismaticJoint); + //b2Settings.b2Assert(def.joint1.m_body1.IsStatic()); + //b2Settings.b2Assert(def.joint2.m_body1.IsStatic()); + + this.m_revolute1 = null; + this.m_prismatic1 = null; + this.m_revolute2 = null; + this.m_prismatic2 = null; + + var coordinate1; + var coordinate2; + + this.m_ground1 = def.joint1.m_body1; + this.m_body1 = def.joint1.m_body2; + if (def.joint1.m_type == b2Joint.e_revoluteJoint) + { + this.m_revolute1 = def.joint1; + this.m_groundAnchor1.SetV( this.m_revolute1.m_localAnchor1 ); + this.m_localAnchor1.SetV( this.m_revolute1.m_localAnchor2 ); + coordinate1 = this.m_revolute1.GetJointAngle(); + } + else + { + this.m_prismatic1 = def.joint1; + this.m_groundAnchor1.SetV( this.m_prismatic1.m_localAnchor1 ); + this.m_localAnchor1.SetV( this.m_prismatic1.m_localAnchor2 ); + coordinate1 = this.m_prismatic1.GetJointTranslation(); + } + + this.m_ground2 = def.joint2.m_body1; + this.m_body2 = def.joint2.m_body2; + if (def.joint2.m_type == b2Joint.e_revoluteJoint) + { + this.m_revolute2 = def.joint2; + this.m_groundAnchor2.SetV( this.m_revolute2.m_localAnchor1 ); + this.m_localAnchor2.SetV( this.m_revolute2.m_localAnchor2 ); + coordinate2 = this.m_revolute2.GetJointAngle(); + } + else + { + this.m_prismatic2 = def.joint2; + this.m_groundAnchor2.SetV( this.m_prismatic2.m_localAnchor1 ); + this.m_localAnchor2.SetV( this.m_prismatic2.m_localAnchor2 ); + coordinate2 = this.m_prismatic2.GetJointTranslation(); + } + + this.m_ratio = def.ratio; + + this.m_constant = coordinate1 + this.m_ratio * coordinate2; + + this.m_impulse = 0.0; + + }, + + PrepareVelocitySolver: function(){ + var g1 = this.m_ground1; + var g2 = this.m_ground2; + var b1 = this.m_body1; + var b2 = this.m_body2; + + // temp vars + var ugX; + var ugY; + var rX; + var rY; + var tMat; + var tVec; + var crug; + + var K = 0.0; + this.m_J.SetZero(); + + if (this.m_revolute1) + { + this.m_J.angular1 = -1.0; + K += b1.m_invI; + } + else + { + //b2Vec2 ug = b2MulMV(g1->m_R, this.m_prismatic1->m_localXAxis1); + tMat = g1.m_R; + tVec = this.m_prismatic1.m_localXAxis1; + ugX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + ugY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //b2Vec2 r = b2MulMV(b1->m_R, this.m_localAnchor1); + tMat = b1.m_R; + rX = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + rY = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + + //var crug = b2Cross(r, ug); + crug = rX * ugY - rY * ugX; + //this.m_J.linear1 = -ug; + this.m_J.linear1.Set(-ugX, -ugY); + this.m_J.angular1 = -crug; + K += b1.m_invMass + b1.m_invI * crug * crug; + } + + if (this.m_revolute2) + { + this.m_J.angular2 = -this.m_ratio; + K += this.m_ratio * this.m_ratio * b2.m_invI; + } + else + { + //b2Vec2 ug = b2Mul(g2->m_R, this.m_prismatic2->m_localXAxis1); + tMat = g2.m_R; + tVec = this.m_prismatic2.m_localXAxis1; + ugX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + ugY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //b2Vec2 r = b2Mul(b2->m_R, this.m_localAnchor2); + tMat = b2.m_R; + rX = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + rY = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //float32 crug = b2Cross(r, ug); + crug = rX * ugY - rY * ugX; + //this.m_J.linear2 = -this.m_ratio * ug; + this.m_J.linear2.Set(-this.m_ratio*ugX, -this.m_ratio*ugY); + this.m_J.angular2 = -this.m_ratio * crug; + K += this.m_ratio * this.m_ratio * (b2.m_invMass + b2.m_invI * crug * crug); + } + + // Compute effective mass. + //b2Settings.b2Assert(K > 0.0); + this.m_mass = 1.0 / K; + + // Warm starting. + //b1.m_linearVelocity += b1.m_invMass * this.m_impulse * this.m_J.linear1; + b1.m_linearVelocity.x += b1.m_invMass * this.m_impulse * this.m_J.linear1.x; + b1.m_linearVelocity.y += b1.m_invMass * this.m_impulse * this.m_J.linear1.y; + b1.m_angularVelocity += b1.m_invI * this.m_impulse * this.m_J.angular1; + //b2.m_linearVelocity += b2.m_invMass * this.m_impulse * this.m_J.linear2; + b2.m_linearVelocity.x += b2.m_invMass * this.m_impulse * this.m_J.linear2.x; + b2.m_linearVelocity.y += b2.m_invMass * this.m_impulse * this.m_J.linear2.y; + b2.m_angularVelocity += b2.m_invI * this.m_impulse * this.m_J.angular2; + }, + + + SolveVelocityConstraints: function(step){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var Cdot = this.m_J.Compute( b1.m_linearVelocity, b1.m_angularVelocity, + b2.m_linearVelocity, b2.m_angularVelocity); + + var impulse = -this.m_mass * Cdot; + this.m_impulse += impulse; + + b1.m_linearVelocity.x += b1.m_invMass * impulse * this.m_J.linear1.x; + b1.m_linearVelocity.y += b1.m_invMass * impulse * this.m_J.linear1.y; + b1.m_angularVelocity += b1.m_invI * impulse * this.m_J.angular1; + b2.m_linearVelocity.x += b2.m_invMass * impulse * this.m_J.linear2.x; + b2.m_linearVelocity.y += b2.m_invMass * impulse * this.m_J.linear2.y; + b2.m_angularVelocity += b2.m_invI * impulse * this.m_J.angular2; + }, + + SolvePositionConstraints: function(){ + var linearError = 0.0; + + var b1 = this.m_body1; + var b2 = this.m_body2; + + var coordinate1; + var coordinate2; + if (this.m_revolute1) + { + coordinate1 = this.m_revolute1.GetJointAngle(); + } + else + { + coordinate1 = this.m_prismatic1.GetJointTranslation(); + } + + if (this.m_revolute2) + { + coordinate2 = this.m_revolute2.GetJointAngle(); + } + else + { + coordinate2 = this.m_prismatic2.GetJointTranslation(); + } + + var C = this.m_constant - (coordinate1 + this.m_ratio * coordinate2); + + var impulse = -this.m_mass * C; + + b1.m_position.x += b1.m_invMass * impulse * this.m_J.linear1.x; + b1.m_position.y += b1.m_invMass * impulse * this.m_J.linear1.y; + b1.m_rotation += b1.m_invI * impulse * this.m_J.angular1; + b2.m_position.x += b2.m_invMass * impulse * this.m_J.linear2.x; + b2.m_position.y += b2.m_invMass * impulse * this.m_J.linear2.y; + b2.m_rotation += b2.m_invI * impulse * this.m_J.angular2; + b1.m_R.Set(b1.m_rotation); + b2.m_R.Set(b2.m_rotation); + + return linearError < b2Settings.b2_linearSlop; + }, + + m_ground1: null, + m_ground2: null, + + // One of these is NULL. + m_revolute1: null, + m_prismatic1: null, + + // One of these is NULL. + m_revolute2: null, + m_prismatic2: null, + + m_groundAnchor1: new b2Vec2(), + m_groundAnchor2: new b2Vec2(), + + m_localAnchor1: new b2Vec2(), + m_localAnchor2: new b2Vec2(), + + m_J: new b2Jacobian(), + + m_constant: null, + m_ratio: null, + + // Effective mass + m_mass: null, + + // Impulse for accumulation/warm starting. + m_impulse: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2GearJointDef.js b/lib/box2d-js/js/box2d/dynamics/joints/b2GearJointDef.js new file mode 100644 index 0000000..7dbb2dd --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2GearJointDef.js @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + +// A gear joint is used to connect two joints together. Either joint +// can be a revolute or prismatic joint. You specify a gear ratio +// to bind the motions together: +// coordinate1 + ratio * coordinate2 = constant +// The ratio can be negative or positive. If one joint is a revolute joint +// and the other joint is a prismatic joint, then the ratio will have units +// of length or units of 1/length. +// +// RESTRICITON: The revolute and prismatic joints must be attached to +// a fixed body (which must be body1 on those joints). + +var b2GearJointDef = Class.create(); +Object.extend(b2GearJointDef.prototype, b2JointDef.prototype); +Object.extend(b2GearJointDef.prototype, +{ + initialize: function() + { + this.type = b2Joint.e_gearJoint; + this.joint1 = null; + this.joint2 = null; + this.ratio = 1.0; + }, + + joint1: null, + joint2: null, + ratio: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2Jacobian.js b/lib/box2d-js/js/box2d/dynamics/joints/b2Jacobian.js new file mode 100644 index 0000000..ba5255b --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2Jacobian.js @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2Jacobian = Class.create(); +b2Jacobian.prototype = +{ + linear1: new b2Vec2(), + angular1: null, + linear2: new b2Vec2(), + angular2: null, + + SetZero: function(){ + this.linear1.SetZero(); this.angular1 = 0.0; + this.linear2.SetZero(); this.angular2 = 0.0; + }, + Set: function(x1, a1, x2, a2){ + this.linear1.SetV(x1); this.angular1 = a1; + this.linear2.SetV(x2); this.angular2 = a2; + }, + Compute: function(x1, a1, x2, a2){ + + //return b2Math.b2Dot(this.linear1, x1) + this.angular1 * a1 + b2Math.b2Dot(this.linear2, x2) + this.angular2 * a2; + return (this.linear1.x*x1.x + this.linear1.y*x1.y) + this.angular1 * a1 + (this.linear2.x*x2.x + this.linear2.y*x2.y) + this.angular2 * a2; + }, + initialize: function() { + // initialize instance variables for references + this.linear1 = new b2Vec2(); + this.linear2 = new b2Vec2(); + // +}}; diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2Joint.js b/lib/box2d-js/js/box2d/dynamics/joints/b2Joint.js new file mode 100644 index 0000000..53d0675 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2Joint.js @@ -0,0 +1,200 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2Joint = Class.create(); +b2Joint.prototype = +{ + GetType: function(){ + return this.m_type; + }, + + GetAnchor1: function(){return null}, + GetAnchor2: function(){return null}, + + GetReactionForce: function(invTimeStep){return null}, + GetReactionTorque: function(invTimeStep){return 0.0}, + + GetBody1: function() + { + return this.m_body1; + }, + + GetBody2: function() + { + return this.m_body2; + }, + + GetNext: function(){ + return this.m_next; + }, + + GetUserData: function(){ + return this.m_userData; + }, + + //--------------- Internals Below ------------------- + + + + initialize: function(def){ + // initialize instance variables for references + this.m_node1 = new b2JointNode(); + this.m_node2 = new b2JointNode(); + // + + this.m_type = def.type; + this.m_prev = null; + this.m_next = null; + this.m_body1 = def.body1; + this.m_body2 = def.body2; + this.m_collideConnected = def.collideConnected; + this.m_islandFlag = false; + this.m_userData = def.userData; + }, + //virtual ~b2Joint() {} + + PrepareVelocitySolver: function(){}, + SolveVelocityConstraints: function(step){}, + + // This returns true if the position errors are within tolerance. + PreparePositionSolver: function(){}, + SolvePositionConstraints: function(){return false}, + + m_type: 0, + m_prev: null, + m_next: null, + m_node1: new b2JointNode(), + m_node2: new b2JointNode(), + m_body1: null, + m_body2: null, + + m_islandFlag: null, + m_collideConnected: null, + + m_userData: null + + + // ENUMS + + // enum b2JointType + + // enum b2LimitState + +}; +b2Joint.Create = function(def, allocator){ + var joint = null; + + switch (def.type) + { + case b2Joint.e_distanceJoint: + { + //void* mem = allocator->Allocate(sizeof(b2DistanceJoint)); + joint = new b2DistanceJoint(def); + } + break; + + case b2Joint.e_mouseJoint: + { + //void* mem = allocator->Allocate(sizeof(b2MouseJoint)); + joint = new b2MouseJoint(def); + } + break; + + case b2Joint.e_prismaticJoint: + { + //void* mem = allocator->Allocate(sizeof(b2PrismaticJoint)); + joint = new b2PrismaticJoint(def); + } + break; + + case b2Joint.e_revoluteJoint: + { + //void* mem = allocator->Allocate(sizeof(b2RevoluteJoint)); + joint = new b2RevoluteJoint(def); + } + break; + + case b2Joint.e_pulleyJoint: + { + //void* mem = allocator->Allocate(sizeof(b2PulleyJoint)); + joint = new b2PulleyJoint(def); + } + break; + + case b2Joint.e_gearJoint: + { + //void* mem = allocator->Allocate(sizeof(b2GearJoint)); + joint = new b2GearJoint(def); + } + break; + + default: + //b2Settings.b2Assert(false); + break; + } + + return joint; + }; +b2Joint.Destroy = function(joint, allocator){ + /*joint->~b2Joint(); + switch (joint.m_type) + { + case b2Joint.e_distanceJoint: + allocator->Free(joint, sizeof(b2DistanceJoint)); + break; + + case b2Joint.e_mouseJoint: + allocator->Free(joint, sizeof(b2MouseJoint)); + break; + + case b2Joint.e_prismaticJoint: + allocator->Free(joint, sizeof(b2PrismaticJoint)); + break; + + case b2Joint.e_revoluteJoint: + allocator->Free(joint, sizeof(b2RevoluteJoint)); + break; + + case b2Joint.e_pulleyJoint: + allocator->Free(joint, sizeof(b2PulleyJoint)); + break; + + case b2Joint.e_gearJoint: + allocator->Free(joint, sizeof(b2GearJoint)); + break; + + default: + b2Assert(false); + break; + }*/ + }; +b2Joint.e_unknownJoint = 0; +b2Joint.e_revoluteJoint = 1; +b2Joint.e_prismaticJoint = 2; +b2Joint.e_distanceJoint = 3; +b2Joint.e_pulleyJoint = 4; +b2Joint.e_mouseJoint = 5; +b2Joint.e_gearJoint = 6; +b2Joint.e_inactiveLimit = 0; +b2Joint.e_atLowerLimit = 1; +b2Joint.e_atUpperLimit = 2; +b2Joint.e_equalLimits = 3; diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2JointDef.js b/lib/box2d-js/js/box2d/dynamics/joints/b2JointDef.js new file mode 100644 index 0000000..e4ab966 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2JointDef.js @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2JointDef = Class.create(); +b2JointDef.prototype = +{ + + initialize: function() + { + this.type = b2Joint.e_unknownJoint; + this.userData = null; + this.body1 = null; + this.body2 = null; + this.collideConnected = false; + }, + + type: 0, + userData: null, + body1: null, + body2: null, + collideConnected: null} diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2JointNode.js b/lib/box2d-js/js/box2d/dynamics/joints/b2JointNode.js new file mode 100644 index 0000000..783383a --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2JointNode.js @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2JointNode = Class.create(); +b2JointNode.prototype = +{ + + other: null, + joint: null, + prev: null, + next: null, + + + initialize: function() {}} diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2MouseJoint.js b/lib/box2d-js/js/box2d/dynamics/joints/b2MouseJoint.js new file mode 100644 index 0000000..f5d379a --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2MouseJoint.js @@ -0,0 +1,234 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +// p = attached point, m = mouse point +// C = p - m +// Cdot = v +// = v + cross(w, r) +// J = [I r_skew] +// Identity used: +// w k % (rx i + ry j) = w * (-ry i + rx j) + +var b2MouseJoint = Class.create(); +Object.extend(b2MouseJoint.prototype, b2Joint.prototype); +Object.extend(b2MouseJoint.prototype, +{ + GetAnchor1: function(){ + return this.m_target; + }, + GetAnchor2: function(){ + var tVec = b2Math.b2MulMV(this.m_body2.m_R, this.m_localAnchor); + tVec.Add(this.m_body2.m_position); + return tVec; + }, + + GetReactionForce: function(invTimeStep) + { + //b2Vec2 F = invTimeStep * this.m_impulse; + var F = new b2Vec2(); + F.SetV(this.m_impulse); + F.Multiply(invTimeStep); + return F; + }, + + GetReactionTorque: function(invTimeStep) + { + //NOT_USED(invTimeStep); + return 0.0; + }, + + SetTarget: function(target){ + this.m_body2.WakeUp(); + this.m_target = target; + }, + + //--------------- Internals Below ------------------- + + initialize: function(def){ + // The constructor for b2Joint + // initialize instance variables for references + this.m_node1 = new b2JointNode(); + this.m_node2 = new b2JointNode(); + // + this.m_type = def.type; + this.m_prev = null; + this.m_next = null; + this.m_body1 = def.body1; + this.m_body2 = def.body2; + this.m_collideConnected = def.collideConnected; + this.m_islandFlag = false; + this.m_userData = def.userData; + // + + // initialize instance variables for references + this.K = new b2Mat22(); + this.K1 = new b2Mat22(); + this.K2 = new b2Mat22(); + this.m_localAnchor = new b2Vec2(); + this.m_target = new b2Vec2(); + this.m_impulse = new b2Vec2(); + this.m_ptpMass = new b2Mat22(); + this.m_C = new b2Vec2(); + // + + //super(def); + + this.m_target.SetV(def.target); + //this.m_localAnchor = b2Math.b2MulTMV(this.m_body2.m_R, b2Math.SubtractVV( this.m_target, this.m_body2.m_position ) ); + var tX = this.m_target.x - this.m_body2.m_position.x; + var tY = this.m_target.y - this.m_body2.m_position.y; + this.m_localAnchor.x = (tX * this.m_body2.m_R.col1.x + tY * this.m_body2.m_R.col1.y); + this.m_localAnchor.y = (tX * this.m_body2.m_R.col2.x + tY * this.m_body2.m_R.col2.y); + + this.m_maxForce = def.maxForce; + this.m_impulse.SetZero(); + + var mass = this.m_body2.m_mass; + + // Frequency + var omega = 2.0 * b2Settings.b2_pi * def.frequencyHz; + + // Damping coefficient + var d = 2.0 * mass * def.dampingRatio * omega; + + // Spring stiffness + var k = mass * omega * omega; + + // magic formulas + this.m_gamma = 1.0 / (d + def.timeStep * k); + this.m_beta = def.timeStep * k / (d + def.timeStep * k); + }, + + // Presolve vars + K: new b2Mat22(), + K1: new b2Mat22(), + K2: new b2Mat22(), + PrepareVelocitySolver: function(){ + var b = this.m_body2; + + var tMat; + + // Compute the effective mass matrix. + //b2Vec2 r = b2Mul(b.m_R, this.m_localAnchor); + tMat = b.m_R; + var rX = tMat.col1.x * this.m_localAnchor.x + tMat.col2.x * this.m_localAnchor.y; + var rY = tMat.col1.y * this.m_localAnchor.x + tMat.col2.y * this.m_localAnchor.y; + + // this.K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * [r1.y*r1.y -r1.x*r1.y] + // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x] + var invMass = b.m_invMass; + var invI = b.m_invI; + + //b2Mat22 this.K1; + this.K1.col1.x = invMass; this.K1.col2.x = 0.0; + this.K1.col1.y = 0.0; this.K1.col2.y = invMass; + + //b2Mat22 this.K2; + this.K2.col1.x = invI * rY * rY; this.K2.col2.x = -invI * rX * rY; + this.K2.col1.y = -invI * rX * rY; this.K2.col2.y = invI * rX * rX; + + //b2Mat22 this.K = this.K1 + this.K2; + this.K.SetM(this.K1); + this.K.AddM(this.K2); + this.K.col1.x += this.m_gamma; + this.K.col2.y += this.m_gamma; + + //this.m_ptpMass = this.K.Invert(); + this.K.Invert(this.m_ptpMass); + + //this.m_C = b.m_position + r - this.m_target; + this.m_C.x = b.m_position.x + rX - this.m_target.x; + this.m_C.y = b.m_position.y + rY - this.m_target.y; + + // Cheat with some damping + b.m_angularVelocity *= 0.98; + + // Warm starting. + //b2Vec2 P = this.m_impulse; + var PX = this.m_impulse.x; + var PY = this.m_impulse.y; + //b.m_linearVelocity += invMass * P; + b.m_linearVelocity.x += invMass * PX; + b.m_linearVelocity.y += invMass * PY; + //b.m_angularVelocity += invI * b2Cross(r, P); + b.m_angularVelocity += invI * (rX * PY - rY * PX); + }, + + + SolveVelocityConstraints: function(step){ + var body = this.m_body2; + + var tMat; + + // Compute the effective mass matrix. + //b2Vec2 r = b2Mul(body.m_R, this.m_localAnchor); + tMat = body.m_R; + var rX = tMat.col1.x * this.m_localAnchor.x + tMat.col2.x * this.m_localAnchor.y; + var rY = tMat.col1.y * this.m_localAnchor.x + tMat.col2.y * this.m_localAnchor.y; + + // Cdot = v + cross(w, r) + //b2Vec2 Cdot = body->m_linearVelocity + b2Cross(body->m_angularVelocity, r); + var CdotX = body.m_linearVelocity.x + (-body.m_angularVelocity * rY); + var CdotY = body.m_linearVelocity.y + (body.m_angularVelocity * rX); + //b2Vec2 impulse = -b2Mul(this.m_ptpMass, Cdot + (this.m_beta * step->inv_dt) * this.m_C + this.m_gamma * this.m_impulse); + tMat = this.m_ptpMass; + var tX = CdotX + (this.m_beta * step.inv_dt) * this.m_C.x + this.m_gamma * this.m_impulse.x; + var tY = CdotY + (this.m_beta * step.inv_dt) * this.m_C.y + this.m_gamma * this.m_impulse.y; + var impulseX = -(tMat.col1.x * tX + tMat.col2.x * tY); + var impulseY = -(tMat.col1.y * tX + tMat.col2.y * tY); + + var oldImpulseX = this.m_impulse.x; + var oldImpulseY = this.m_impulse.y; + //this.m_impulse += impulse; + this.m_impulse.x += impulseX; + this.m_impulse.y += impulseY; + var length = this.m_impulse.Length(); + if (length > step.dt * this.m_maxForce) + { + //this.m_impulse *= step.dt * this.m_maxForce / length; + this.m_impulse.Multiply(step.dt * this.m_maxForce / length); + } + //impulse = this.m_impulse - oldImpulse; + impulseX = this.m_impulse.x - oldImpulseX; + impulseY = this.m_impulse.y - oldImpulseY; + + //body.m_linearVelocity += body->m_invMass * impulse; + body.m_linearVelocity.x += body.m_invMass * impulseX; + body.m_linearVelocity.y += body.m_invMass * impulseY; + //body.m_angularVelocity += body->m_invI * b2Cross(r, impulse); + body.m_angularVelocity += body.m_invI * (rX * impulseY - rY * impulseX); + }, + SolvePositionConstraints: function(){ + return true; + }, + + m_localAnchor: new b2Vec2(), + m_target: new b2Vec2(), + m_impulse: new b2Vec2(), + + m_ptpMass: new b2Mat22(), + m_C: new b2Vec2(), + m_maxForce: null, + m_beta: null, + m_gamma: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2MouseJointDef.js b/lib/box2d-js/js/box2d/dynamics/joints/b2MouseJointDef.js new file mode 100644 index 0000000..9a32456 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2MouseJointDef.js @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2MouseJointDef = Class.create(); +Object.extend(b2MouseJointDef.prototype, b2JointDef.prototype); +Object.extend(b2MouseJointDef.prototype, +{ + initialize: function() + { + // The constructor for b2JointDef + this.type = b2Joint.e_unknownJoint; + this.userData = null; + this.body1 = null; + this.body2 = null; + this.collideConnected = false; + // + + // initialize instance variables for references + this.target = new b2Vec2(); + // + + this.type = b2Joint.e_mouseJoint; + this.maxForce = 0.0; + this.frequencyHz = 5.0; + this.dampingRatio = 0.7; + this.timeStep = 1.0 / 60.0; + }, + + target: new b2Vec2(), + maxForce: null, + frequencyHz: null, + dampingRatio: null, + timeStep: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2PrismaticJoint.js b/lib/box2d-js/js/box2d/dynamics/joints/b2PrismaticJoint.js new file mode 100644 index 0000000..df4056c --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2PrismaticJoint.js @@ -0,0 +1,676 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +// Linear constraint (point-to-line) +// d = p2 - p1 = x2 + r2 - x1 - r1 +// C = dot(ay1, d) +// Cdot = dot(d, cross(w1, ay1)) + dot(ay1, v2 + cross(w2, r2) - v1 - cross(w1, r1)) +// = -dot(ay1, v1) - dot(cross(d + r1, ay1), w1) + dot(ay1, v2) + dot(cross(r2, ay1), v2) +// J = [-ay1 -cross(d+r1,ay1) ay1 cross(r2,ay1)] +// +// Angular constraint +// C = a2 - a1 + a_initial +// Cdot = w2 - w1 +// J = [0 0 -1 0 0 1] + +// Motor/Limit linear constraint +// C = dot(ax1, d) +// Cdot = = -dot(ax1, v1) - dot(cross(d + r1, ax1), w1) + dot(ax1, v2) + dot(cross(r2, ax1), v2) +// J = [-ax1 -cross(d+r1,ax1) ax1 cross(r2,ax1)] + + +var b2PrismaticJoint = Class.create(); +Object.extend(b2PrismaticJoint.prototype, b2Joint.prototype); +Object.extend(b2PrismaticJoint.prototype, +{ + GetAnchor1: function(){ + var b1 = this.m_body1; + //return b2Math.AddVV(b1.m_position, b2Math.b2MulMV(b1.m_R, this.m_localAnchor1)); + var tVec = new b2Vec2(); + tVec.SetV(this.m_localAnchor1); + tVec.MulM(b1.m_R); + tVec.Add(b1.m_position); + return tVec; + }, + GetAnchor2: function(){ + var b2 = this.m_body2; + //return b2Math.AddVV(b2.m_position, b2Math.b2MulMV(b2.m_R, this.m_localAnchor2)); + var tVec = new b2Vec2(); + tVec.SetV(this.m_localAnchor2); + tVec.MulM(b2.m_R); + tVec.Add(b2.m_position); + return tVec; + }, + GetJointTranslation: function(){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + //var r1 = b2Math.b2MulMV(b1.m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //var r2 = b2Math.b2MulMV(b2.m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //var p1 = b2Math.AddVV(b1.m_position , r1); + var p1X = b1.m_position.x + r1X; + var p1Y = b1.m_position.y + r1Y; + //var p2 = b2Math.AddVV(b2.m_position , r2); + var p2X = b2.m_position.x + r2X; + var p2Y = b2.m_position.y + r2Y; + //var d = b2Math.SubtractVV(p2, p1); + var dX = p2X - p1X; + var dY = p2Y - p1Y; + //var ax1 = b2Math.b2MulMV(b1.m_R, this.m_localXAxis1); + tMat = b1.m_R; + var ax1X = tMat.col1.x * this.m_localXAxis1.x + tMat.col2.x * this.m_localXAxis1.y; + var ax1Y = tMat.col1.y * this.m_localXAxis1.x + tMat.col2.y * this.m_localXAxis1.y; + + //var translation = b2Math.b2Dot(ax1, d); + var translation = ax1X*dX + ax1Y*dY; + return translation; + }, + GetJointSpeed: function(){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + //var r1 = b2Math.b2MulMV(b1.m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //var r2 = b2Math.b2MulMV(b2.m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //var p1 = b2Math.AddVV(b1.m_position , r1); + var p1X = b1.m_position.x + r1X; + var p1Y = b1.m_position.y + r1Y; + //var p2 = b2Math.AddVV(b2.m_position , r2); + var p2X = b2.m_position.x + r2X; + var p2Y = b2.m_position.y + r2Y; + //var d = b2Math.SubtractVV(p2, p1); + var dX = p2X - p1X; + var dY = p2Y - p1Y; + //var ax1 = b2Math.b2MulMV(b1.m_R, this.m_localXAxis1); + tMat = b1.m_R; + var ax1X = tMat.col1.x * this.m_localXAxis1.x + tMat.col2.x * this.m_localXAxis1.y; + var ax1Y = tMat.col1.y * this.m_localXAxis1.x + tMat.col2.y * this.m_localXAxis1.y; + + var v1 = b1.m_linearVelocity; + var v2 = b2.m_linearVelocity; + var w1 = b1.m_angularVelocity; + var w2 = b2.m_angularVelocity; + + //var speed = b2Math.b2Dot(d, b2Math.b2CrossFV(w1, ax1)) + b2Math.b2Dot(ax1, b2Math.SubtractVV( b2Math.SubtractVV( b2Math.AddVV( v2 , b2Math.b2CrossFV(w2, r2)) , v1) , b2Math.b2CrossFV(w1, r1))); + //var b2D = (dX*(-w1 * ax1Y) + dY*(w1 * ax1X)); + //var b2D2 = (ax1X * ((( v2.x + (-w2 * r2Y)) - v1.x) - (-w1 * r1Y)) + ax1Y * ((( v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X))); + var speed = (dX*(-w1 * ax1Y) + dY*(w1 * ax1X)) + (ax1X * ((( v2.x + (-w2 * r2Y)) - v1.x) - (-w1 * r1Y)) + ax1Y * ((( v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X))); + + return speed; + }, + GetMotorForce: function(invTimeStep){ + return invTimeStep * this.m_motorImpulse; + }, + + SetMotorSpeed: function(speed) + { + this.m_motorSpeed = speed; + }, + + SetMotorForce: function(force) + { + this.m_maxMotorForce = force; + }, + + GetReactionForce: function(invTimeStep) + { + var tImp = invTimeStep * this.m_limitImpulse; + var tMat; + + //var ax1 = b2Math.b2MulMV(this.m_body1.m_R, this.m_localXAxis1); + tMat = this.m_body1.m_R; + var ax1X = tImp * (tMat.col1.x * this.m_localXAxis1.x + tMat.col2.x * this.m_localXAxis1.y); + var ax1Y = tImp * (tMat.col1.y * this.m_localXAxis1.x + tMat.col2.y * this.m_localXAxis1.y); + //var ay1 = b2Math.b2MulMV(this.m_body1.m_R, this.m_localYAxis1); + var ay1X = tImp * (tMat.col1.x * this.m_localYAxis1.x + tMat.col2.x * this.m_localYAxis1.y); + var ay1Y = tImp * (tMat.col1.y * this.m_localYAxis1.x + tMat.col2.y * this.m_localYAxis1.y); + + //return (invTimeStep * this.m_limitImpulse) * ax1 + (invTimeStep * this.m_linearImpulse) * ay1; + + return new b2Vec2(ax1X+ay1X, ax1Y+ay1Y); + }, + + GetReactionTorque: function(invTimeStep) + { + return invTimeStep * this.m_angularImpulse; + }, + + + //--------------- Internals Below ------------------- + + initialize: function(def){ + // The constructor for b2Joint + // initialize instance variables for references + this.m_node1 = new b2JointNode(); + this.m_node2 = new b2JointNode(); + // + this.m_type = def.type; + this.m_prev = null; + this.m_next = null; + this.m_body1 = def.body1; + this.m_body2 = def.body2; + this.m_collideConnected = def.collideConnected; + this.m_islandFlag = false; + this.m_userData = def.userData; + // + + // initialize instance variables for references + this.m_localAnchor1 = new b2Vec2(); + this.m_localAnchor2 = new b2Vec2(); + this.m_localXAxis1 = new b2Vec2(); + this.m_localYAxis1 = new b2Vec2(); + this.m_linearJacobian = new b2Jacobian(); + this.m_motorJacobian = new b2Jacobian(); + // + + //super(def); + + var tMat; + var tX; + var tY; + + //this.m_localAnchor1 = b2Math.b2MulTMV(this.m_body1.m_R, b2Math.SubtractVV(def.anchorPoint , this.m_body1.m_position)); + tMat = this.m_body1.m_R; + tX = (def.anchorPoint.x - this.m_body1.m_position.x); + tY = (def.anchorPoint.y - this.m_body1.m_position.y); + this.m_localAnchor1.Set((tX*tMat.col1.x + tY*tMat.col1.y), (tX*tMat.col2.x + tY*tMat.col2.y)); + + //this.m_localAnchor2 = b2Math.b2MulTMV(this.m_body2.m_R, b2Math.SubtractVV(def.anchorPoint , this.m_body2.m_position)); + tMat = this.m_body2.m_R; + tX = (def.anchorPoint.x - this.m_body2.m_position.x); + tY = (def.anchorPoint.y - this.m_body2.m_position.y); + this.m_localAnchor2.Set((tX*tMat.col1.x + tY*tMat.col1.y), (tX*tMat.col2.x + tY*tMat.col2.y)); + + //this.m_localXAxis1 = b2Math.b2MulTMV(this.m_body1.m_R, def.axis); + tMat = this.m_body1.m_R; + tX = def.axis.x; + tY = def.axis.y; + this.m_localXAxis1.Set((tX*tMat.col1.x + tY*tMat.col1.y), (tX*tMat.col2.x + tY*tMat.col2.y)); + + //this.m_localYAxis1 = b2Math.b2CrossFV(1.0, this.m_localXAxis1); + this.m_localYAxis1.x = -this.m_localXAxis1.y; + this.m_localYAxis1.y = this.m_localXAxis1.x; + + this.m_initialAngle = this.m_body2.m_rotation - this.m_body1.m_rotation; + + this.m_linearJacobian.SetZero(); + this.m_linearMass = 0.0; + this.m_linearImpulse = 0.0; + + this.m_angularMass = 0.0; + this.m_angularImpulse = 0.0; + + this.m_motorJacobian.SetZero(); + this.m_motorMass = 0.0; + this.m_motorImpulse = 0.0; + this.m_limitImpulse = 0.0; + this.m_limitPositionImpulse = 0.0; + + this.m_lowerTranslation = def.lowerTranslation; + this.m_upperTranslation = def.upperTranslation; + this.m_maxMotorForce = def.motorForce; + this.m_motorSpeed = def.motorSpeed; + this.m_enableLimit = def.enableLimit; + this.m_enableMotor = def.enableMotor; + }, + + PrepareVelocitySolver: function(){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + // Compute the effective masses. + //b2Vec2 r1 = b2Mul(b1->m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(b2->m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + //float32 invMass1 = b1->m_invMass, invMass2 = b2->m_invMass; + var invMass1 = b1.m_invMass; + var invMass2 = b2.m_invMass; + //float32 invI1 = b1->m_invI, invI2 = b2->m_invI; + var invI1 = b1.m_invI; + var invI2 = b2.m_invI; + + // Compute point to line constraint effective mass. + // J = [-ay1 -cross(d+r1,ay1) ay1 cross(r2,ay1)] + //b2Vec2 ay1 = b2Mul(b1->m_R, this.m_localYAxis1); + tMat = b1.m_R; + var ay1X = tMat.col1.x * this.m_localYAxis1.x + tMat.col2.x * this.m_localYAxis1.y; + var ay1Y = tMat.col1.y * this.m_localYAxis1.x + tMat.col2.y * this.m_localYAxis1.y; + //b2Vec2 e = b2->m_position + r2 - b1->m_position; + var eX = b2.m_position.x + r2X - b1.m_position.x; + var eY = b2.m_position.y + r2Y - b1.m_position.y; + + //this.m_linearJacobian.Set(-ay1, -b2Math.b2Cross(e, ay1), ay1, b2Math.b2Cross(r2, ay1)); + this.m_linearJacobian.linear1.x = -ay1X; + this.m_linearJacobian.linear1.y = -ay1Y; + this.m_linearJacobian.linear2.x = ay1X; + this.m_linearJacobian.linear2.y = ay1Y; + this.m_linearJacobian.angular1 = -(eX * ay1Y - eY * ay1X); + this.m_linearJacobian.angular2 = r2X * ay1Y - r2Y * ay1X; + + this.m_linearMass = invMass1 + invI1 * this.m_linearJacobian.angular1 * this.m_linearJacobian.angular1 + + invMass2 + invI2 * this.m_linearJacobian.angular2 * this.m_linearJacobian.angular2; + //b2Settings.b2Assert(this.m_linearMass > Number.MIN_VALUE); + this.m_linearMass = 1.0 / this.m_linearMass; + + // Compute angular constraint effective mass. + this.m_angularMass = 1.0 / (invI1 + invI2); + + // Compute motor and limit terms. + if (this.m_enableLimit || this.m_enableMotor) + { + // The motor and limit share a Jacobian and effective mass. + //b2Vec2 ax1 = b2Mul(b1->m_R, this.m_localXAxis1); + tMat = b1.m_R; + var ax1X = tMat.col1.x * this.m_localXAxis1.x + tMat.col2.x * this.m_localXAxis1.y; + var ax1Y = tMat.col1.y * this.m_localXAxis1.x + tMat.col2.y * this.m_localXAxis1.y; + //this.m_motorJacobian.Set(-ax1, -b2Cross(e, ax1), ax1, b2Cross(r2, ax1)); + this.m_motorJacobian.linear1.x = -ax1X; this.m_motorJacobian.linear1.y = -ax1Y; + this.m_motorJacobian.linear2.x = ax1X; this.m_motorJacobian.linear2.y = ax1Y; + this.m_motorJacobian.angular1 = -(eX * ax1Y - eY * ax1X); + this.m_motorJacobian.angular2 = r2X * ax1Y - r2Y * ax1X; + + this.m_motorMass = invMass1 + invI1 * this.m_motorJacobian.angular1 * this.m_motorJacobian.angular1 + + invMass2 + invI2 * this.m_motorJacobian.angular2 * this.m_motorJacobian.angular2; + //b2Settings.b2Assert(this.m_motorMass > Number.MIN_VALUE); + this.m_motorMass = 1.0 / this.m_motorMass; + + if (this.m_enableLimit) + { + //b2Vec2 d = e - r1; + var dX = eX - r1X; + var dY = eY - r1Y; + //float32 jointTranslation = b2Dot(ax1, d); + var jointTranslation = ax1X * dX + ax1Y * dY; + if (b2Math.b2Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) + { + this.m_limitState = b2Joint.e_equalLimits; + } + else if (jointTranslation <= this.m_lowerTranslation) + { + if (this.m_limitState != b2Joint.e_atLowerLimit) + { + this.m_limitImpulse = 0.0; + } + this.m_limitState = b2Joint.e_atLowerLimit; + } + else if (jointTranslation >= this.m_upperTranslation) + { + if (this.m_limitState != b2Joint.e_atUpperLimit) + { + this.m_limitImpulse = 0.0; + } + this.m_limitState = b2Joint.e_atUpperLimit; + } + else + { + this.m_limitState = b2Joint.e_inactiveLimit; + this.m_limitImpulse = 0.0; + } + } + } + + if (this.m_enableMotor == false) + { + this.m_motorImpulse = 0.0; + } + + if (this.m_enableLimit == false) + { + this.m_limitImpulse = 0.0; + } + + if (b2World.s_enableWarmStarting) + { + //b2Vec2 P1 = this.m_linearImpulse * this.m_linearJacobian.linear1 + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.linear1; + var P1X = this.m_linearImpulse * this.m_linearJacobian.linear1.x + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.linear1.x; + var P1Y = this.m_linearImpulse * this.m_linearJacobian.linear1.y + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.linear1.y; + //b2Vec2 P2 = this.m_linearImpulse * this.m_linearJacobian.linear2 + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.linear2; + var P2X = this.m_linearImpulse * this.m_linearJacobian.linear2.x + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.linear2.x; + var P2Y = this.m_linearImpulse * this.m_linearJacobian.linear2.y + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.linear2.y; + //float32 L1 = this.m_linearImpulse * this.m_linearJacobian.angular1 - this.m_angularImpulse + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.angular1; + var L1 = this.m_linearImpulse * this.m_linearJacobian.angular1 - this.m_angularImpulse + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.angular1; + //float32 L2 = this.m_linearImpulse * this.m_linearJacobian.angular2 + this.m_angularImpulse + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.angular2; + var L2 = this.m_linearImpulse * this.m_linearJacobian.angular2 + this.m_angularImpulse + (this.m_motorImpulse + this.m_limitImpulse) * this.m_motorJacobian.angular2; + + //b1->m_linearVelocity += invMass1 * P1; + b1.m_linearVelocity.x += invMass1 * P1X; + b1.m_linearVelocity.y += invMass1 * P1Y; + //b1->m_angularVelocity += invI1 * L1; + b1.m_angularVelocity += invI1 * L1; + + //b2->m_linearVelocity += invMass2 * P2; + b2.m_linearVelocity.x += invMass2 * P2X; + b2.m_linearVelocity.y += invMass2 * P2Y; + //b2->m_angularVelocity += invI2 * L2; + b2.m_angularVelocity += invI2 * L2; + } + else + { + this.m_linearImpulse = 0.0; + this.m_angularImpulse = 0.0; + this.m_limitImpulse = 0.0; + this.m_motorImpulse = 0.0; + } + + this.m_limitPositionImpulse = 0.0; + + }, + + SolveVelocityConstraints: function(step){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var invMass1 = b1.m_invMass; + var invMass2 = b2.m_invMass; + var invI1 = b1.m_invI; + var invI2 = b2.m_invI; + + var oldLimitImpulse; + + // Solve linear constraint. + var linearCdot = this.m_linearJacobian.Compute(b1.m_linearVelocity, b1.m_angularVelocity, b2.m_linearVelocity, b2.m_angularVelocity); + var linearImpulse = -this.m_linearMass * linearCdot; + this.m_linearImpulse += linearImpulse; + + //b1->m_linearVelocity += (invMass1 * linearImpulse) * this.m_linearJacobian.linear1; + b1.m_linearVelocity.x += (invMass1 * linearImpulse) * this.m_linearJacobian.linear1.x; + b1.m_linearVelocity.y += (invMass1 * linearImpulse) * this.m_linearJacobian.linear1.y; + //b1->m_angularVelocity += invI1 * linearImpulse * this.m_linearJacobian.angular1; + b1.m_angularVelocity += invI1 * linearImpulse * this.m_linearJacobian.angular1; + + //b2->m_linearVelocity += (invMass2 * linearImpulse) * this.m_linearJacobian.linear2; + b2.m_linearVelocity.x += (invMass2 * linearImpulse) * this.m_linearJacobian.linear2.x; + b2.m_linearVelocity.y += (invMass2 * linearImpulse) * this.m_linearJacobian.linear2.y; + //b2.m_angularVelocity += invI2 * linearImpulse * this.m_linearJacobian.angular2; + b2.m_angularVelocity += invI2 * linearImpulse * this.m_linearJacobian.angular2; + + // Solve angular constraint. + var angularCdot = b2.m_angularVelocity - b1.m_angularVelocity; + var angularImpulse = -this.m_angularMass * angularCdot; + this.m_angularImpulse += angularImpulse; + + b1.m_angularVelocity -= invI1 * angularImpulse; + b2.m_angularVelocity += invI2 * angularImpulse; + + // Solve linear motor constraint. + if (this.m_enableMotor && this.m_limitState != b2Joint.e_equalLimits) + { + var motorCdot = this.m_motorJacobian.Compute(b1.m_linearVelocity, b1.m_angularVelocity, b2.m_linearVelocity, b2.m_angularVelocity) - this.m_motorSpeed; + var motorImpulse = -this.m_motorMass * motorCdot; + var oldMotorImpulse = this.m_motorImpulse; + this.m_motorImpulse = b2Math.b2Clamp(this.m_motorImpulse + motorImpulse, -step.dt * this.m_maxMotorForce, step.dt * this.m_maxMotorForce); + motorImpulse = this.m_motorImpulse - oldMotorImpulse; + + //b1.m_linearVelocity += (invMass1 * motorImpulse) * this.m_motorJacobian.linear1; + b1.m_linearVelocity.x += (invMass1 * motorImpulse) * this.m_motorJacobian.linear1.x; + b1.m_linearVelocity.y += (invMass1 * motorImpulse) * this.m_motorJacobian.linear1.y; + //b1.m_angularVelocity += invI1 * motorImpulse * this.m_motorJacobian.angular1; + b1.m_angularVelocity += invI1 * motorImpulse * this.m_motorJacobian.angular1; + + //b2->m_linearVelocity += (invMass2 * motorImpulse) * this.m_motorJacobian.linear2; + b2.m_linearVelocity.x += (invMass2 * motorImpulse) * this.m_motorJacobian.linear2.x; + b2.m_linearVelocity.y += (invMass2 * motorImpulse) * this.m_motorJacobian.linear2.y; + //b2->m_angularVelocity += invI2 * motorImpulse * this.m_motorJacobian.angular2; + b2.m_angularVelocity += invI2 * motorImpulse * this.m_motorJacobian.angular2; + } + + // Solve linear limit constraint. + if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) + { + var limitCdot = this.m_motorJacobian.Compute(b1.m_linearVelocity, b1.m_angularVelocity, b2.m_linearVelocity, b2.m_angularVelocity); + var limitImpulse = -this.m_motorMass * limitCdot; + + if (this.m_limitState == b2Joint.e_equalLimits) + { + this.m_limitImpulse += limitImpulse; + } + else if (this.m_limitState == b2Joint.e_atLowerLimit) + { + oldLimitImpulse = this.m_limitImpulse; + this.m_limitImpulse = b2Math.b2Max(this.m_limitImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitImpulse - oldLimitImpulse; + } + else if (this.m_limitState == b2Joint.e_atUpperLimit) + { + oldLimitImpulse = this.m_limitImpulse; + this.m_limitImpulse = b2Math.b2Min(this.m_limitImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitImpulse - oldLimitImpulse; + } + + //b1->m_linearVelocity += (invMass1 * limitImpulse) * this.m_motorJacobian.linear1; + b1.m_linearVelocity.x += (invMass1 * limitImpulse) * this.m_motorJacobian.linear1.x; + b1.m_linearVelocity.y += (invMass1 * limitImpulse) * this.m_motorJacobian.linear1.y; + //b1->m_angularVelocity += invI1 * limitImpulse * this.m_motorJacobian.angular1; + b1.m_angularVelocity += invI1 * limitImpulse * this.m_motorJacobian.angular1; + + //b2->m_linearVelocity += (invMass2 * limitImpulse) * this.m_motorJacobian.linear2; + b2.m_linearVelocity.x += (invMass2 * limitImpulse) * this.m_motorJacobian.linear2.x; + b2.m_linearVelocity.y += (invMass2 * limitImpulse) * this.m_motorJacobian.linear2.y; + //b2->m_angularVelocity += invI2 * limitImpulse * this.m_motorJacobian.angular2; + b2.m_angularVelocity += invI2 * limitImpulse * this.m_motorJacobian.angular2; + } + }, + + + + SolvePositionConstraints: function(){ + + var limitC; + var oldLimitImpulse; + + var b1 = this.m_body1; + var b2 = this.m_body2; + + var invMass1 = b1.m_invMass; + var invMass2 = b2.m_invMass; + var invI1 = b1.m_invI; + var invI2 = b2.m_invI; + + var tMat; + + //b2Vec2 r1 = b2Mul(b1->m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(b2->m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //b2Vec2 p1 = b1->m_position + r1; + var p1X = b1.m_position.x + r1X; + var p1Y = b1.m_position.y + r1Y; + //b2Vec2 p2 = b2->m_position + r2; + var p2X = b2.m_position.x + r2X; + var p2Y = b2.m_position.y + r2Y; + //b2Vec2 d = p2 - p1; + var dX = p2X - p1X; + var dY = p2Y - p1Y; + //b2Vec2 ay1 = b2Mul(b1->m_R, this.m_localYAxis1); + tMat = b1.m_R; + var ay1X = tMat.col1.x * this.m_localYAxis1.x + tMat.col2.x * this.m_localYAxis1.y; + var ay1Y = tMat.col1.y * this.m_localYAxis1.x + tMat.col2.y * this.m_localYAxis1.y; + + // Solve linear (point-to-line) constraint. + //float32 linearC = b2Dot(ay1, d); + var linearC = ay1X*dX + ay1Y*dY; + // Prevent overly large corrections. + linearC = b2Math.b2Clamp(linearC, -b2Settings.b2_maxLinearCorrection, b2Settings.b2_maxLinearCorrection); + var linearImpulse = -this.m_linearMass * linearC; + + //b1->m_position += (invMass1 * linearImpulse) * this.m_linearJacobian.linear1; + b1.m_position.x += (invMass1 * linearImpulse) * this.m_linearJacobian.linear1.x; + b1.m_position.y += (invMass1 * linearImpulse) * this.m_linearJacobian.linear1.y; + //b1->m_rotation += invI1 * linearImpulse * this.m_linearJacobian.angular1; + b1.m_rotation += invI1 * linearImpulse * this.m_linearJacobian.angular1; + //b1->m_R.Set(b1->m_rotation); + //b2->m_position += (invMass2 * linearImpulse) * this.m_linearJacobian.linear2; + b2.m_position.x += (invMass2 * linearImpulse) * this.m_linearJacobian.linear2.x; + b2.m_position.y += (invMass2 * linearImpulse) * this.m_linearJacobian.linear2.y; + b2.m_rotation += invI2 * linearImpulse * this.m_linearJacobian.angular2; + //b2->m_R.Set(b2->m_rotation); + + var positionError = b2Math.b2Abs(linearC); + + // Solve angular constraint. + var angularC = b2.m_rotation - b1.m_rotation - this.m_initialAngle; + // Prevent overly large corrections. + angularC = b2Math.b2Clamp(angularC, -b2Settings.b2_maxAngularCorrection, b2Settings.b2_maxAngularCorrection); + var angularImpulse = -this.m_angularMass * angularC; + + b1.m_rotation -= b1.m_invI * angularImpulse; + b1.m_R.Set(b1.m_rotation); + b2.m_rotation += b2.m_invI * angularImpulse; + b2.m_R.Set(b2.m_rotation); + + var angularError = b2Math.b2Abs(angularC); + + // Solve linear limit constraint. + if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) + { + + //b2Vec2 r1 = b2Mul(b1->m_R, this.m_localAnchor1); + tMat = b1.m_R; + r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(b2->m_R, this.m_localAnchor2); + tMat = b2.m_R; + r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //b2Vec2 p1 = b1->m_position + r1; + p1X = b1.m_position.x + r1X; + p1Y = b1.m_position.y + r1Y; + //b2Vec2 p2 = b2->m_position + r2; + p2X = b2.m_position.x + r2X; + p2Y = b2.m_position.y + r2Y; + //b2Vec2 d = p2 - p1; + dX = p2X - p1X; + dY = p2Y - p1Y; + //b2Vec2 ax1 = b2Mul(b1->m_R, this.m_localXAxis1); + tMat = b1.m_R; + var ax1X = tMat.col1.x * this.m_localXAxis1.x + tMat.col2.x * this.m_localXAxis1.y; + var ax1Y = tMat.col1.y * this.m_localXAxis1.x + tMat.col2.y * this.m_localXAxis1.y; + + //float32 translation = b2Dot(ax1, d); + var translation = (ax1X*dX + ax1Y*dY); + var limitImpulse = 0.0; + + if (this.m_limitState == b2Joint.e_equalLimits) + { + // Prevent large angular corrections + limitC = b2Math.b2Clamp(translation, -b2Settings.b2_maxLinearCorrection, b2Settings.b2_maxLinearCorrection); + limitImpulse = -this.m_motorMass * limitC; + positionError = b2Math.b2Max(positionError, b2Math.b2Abs(angularC)); + } + else if (this.m_limitState == b2Joint.e_atLowerLimit) + { + limitC = translation - this.m_lowerTranslation; + positionError = b2Math.b2Max(positionError, -limitC); + + // Prevent large linear corrections and allow some slop. + limitC = b2Math.b2Clamp(limitC + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + limitImpulse = -this.m_motorMass * limitC; + oldLimitImpulse = this.m_limitPositionImpulse; + this.m_limitPositionImpulse = b2Math.b2Max(this.m_limitPositionImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitPositionImpulse - oldLimitImpulse; + } + else if (this.m_limitState == b2Joint.e_atUpperLimit) + { + limitC = translation - this.m_upperTranslation; + positionError = b2Math.b2Max(positionError, limitC); + + // Prevent large linear corrections and allow some slop. + limitC = b2Math.b2Clamp(limitC - b2Settings.b2_linearSlop, 0.0, b2Settings.b2_maxLinearCorrection); + limitImpulse = -this.m_motorMass * limitC; + oldLimitImpulse = this.m_limitPositionImpulse; + this.m_limitPositionImpulse = b2Math.b2Min(this.m_limitPositionImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitPositionImpulse - oldLimitImpulse; + } + + //b1->m_position += (invMass1 * limitImpulse) * this.m_motorJacobian.linear1; + b1.m_position.x += (invMass1 * limitImpulse) * this.m_motorJacobian.linear1.x; + b1.m_position.y += (invMass1 * limitImpulse) * this.m_motorJacobian.linear1.y; + //b1->m_rotation += invI1 * limitImpulse * this.m_motorJacobian.angular1; + b1.m_rotation += invI1 * limitImpulse * this.m_motorJacobian.angular1; + b1.m_R.Set(b1.m_rotation); + //b2->m_position += (invMass2 * limitImpulse) * this.m_motorJacobian.linear2; + b2.m_position.x += (invMass2 * limitImpulse) * this.m_motorJacobian.linear2.x; + b2.m_position.y += (invMass2 * limitImpulse) * this.m_motorJacobian.linear2.y; + //b2->m_rotation += invI2 * limitImpulse * this.m_motorJacobian.angular2; + b2.m_rotation += invI2 * limitImpulse * this.m_motorJacobian.angular2; + b2.m_R.Set(b2.m_rotation); + } + + return positionError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop; + + }, + + m_localAnchor1: new b2Vec2(), + m_localAnchor2: new b2Vec2(), + m_localXAxis1: new b2Vec2(), + m_localYAxis1: new b2Vec2(), + m_initialAngle: null, + + m_linearJacobian: new b2Jacobian(), + m_linearMass: null, + m_linearImpulse: null, + + m_angularMass: null, + m_angularImpulse: null, + + m_motorJacobian: new b2Jacobian(), + m_motorMass: null, + m_motorImpulse: null, + m_limitImpulse: null, + m_limitPositionImpulse: null, + + m_lowerTranslation: null, + m_upperTranslation: null, + m_maxMotorForce: null, + m_motorSpeed: null, + + m_enableLimit: null, + m_enableMotor: null, + m_limitState: 0}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2PrismaticJointDef.js b/lib/box2d-js/js/box2d/dynamics/joints/b2PrismaticJointDef.js new file mode 100644 index 0000000..83b8cf0 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2PrismaticJointDef.js @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +var b2PrismaticJointDef = Class.create(); +Object.extend(b2PrismaticJointDef.prototype, b2JointDef.prototype); +Object.extend(b2PrismaticJointDef.prototype, +{ + initialize: function() + { + // The constructor for b2JointDef + this.type = b2Joint.e_unknownJoint; + this.userData = null; + this.body1 = null; + this.body2 = null; + this.collideConnected = false; + // + + this.type = b2Joint.e_prismaticJoint; + this.anchorPoint = new b2Vec2(0.0, 0.0); + this.axis = new b2Vec2(0.0, 0.0); + this.lowerTranslation = 0.0; + this.upperTranslation = 0.0; + this.motorForce = 0.0; + this.motorSpeed = 0.0; + this.enableLimit = false; + this.enableMotor = false; + }, + + anchorPoint: null, + axis: null, + lowerTranslation: null, + upperTranslation: null, + motorForce: null, + motorSpeed: null, + enableLimit: null, + enableMotor: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2PulleyJoint.js b/lib/box2d-js/js/box2d/dynamics/joints/b2PulleyJoint.js new file mode 100644 index 0000000..d9e0cb5 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2PulleyJoint.js @@ -0,0 +1,618 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + + +var b2PulleyJoint = Class.create(); +Object.extend(b2PulleyJoint.prototype, b2Joint.prototype); +Object.extend(b2PulleyJoint.prototype, +{ + GetAnchor1: function(){ + //return this.m_body1->m_position + b2Mul(this.m_body1->m_R, this.m_localAnchor1); + var tMat = this.m_body1.m_R; + return new b2Vec2( this.m_body1.m_position.x + (tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y), + this.m_body1.m_position.y + (tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y)); + }, + GetAnchor2: function(){ + //return this.m_body2->m_position + b2Mul(this.m_body2->m_R, this.m_localAnchor2); + var tMat = this.m_body2.m_R; + return new b2Vec2( this.m_body2.m_position.x + (tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y), + this.m_body2.m_position.y + (tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y)); + }, + + GetGroundPoint1: function(){ + //return this.m_ground->m_position + this.m_groundAnchor1; + return new b2Vec2(this.m_ground.m_position.x + this.m_groundAnchor1.x, this.m_ground.m_position.y + this.m_groundAnchor1.y); + }, + GetGroundPoint2: function(){ + return new b2Vec2(this.m_ground.m_position.x + this.m_groundAnchor2.x, this.m_ground.m_position.y + this.m_groundAnchor2.y); + }, + + GetReactionForce: function(invTimeStep){ + //b2Vec2 F(0.0f, 0.0f); + return new b2Vec2(); + }, + GetReactionTorque: function(invTimeStep){ + return 0.0; + }, + + GetLength1: function(){ + var tMat; + //b2Vec2 p = this.m_body1->m_position + b2Mul(this.m_body1->m_R, this.m_localAnchor1); + tMat = this.m_body1.m_R; + var pX = this.m_body1.m_position.x + (tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y); + var pY = this.m_body1.m_position.y + (tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y); + //b2Vec2 s = this.m_ground->m_position + this.m_groundAnchor1; + //b2Vec2 d = p - s; + var dX = pX - (this.m_ground.m_position.x + this.m_groundAnchor1.x); + var dY = pY - (this.m_ground.m_position.y + this.m_groundAnchor1.y); + return Math.sqrt(dX*dX + dY*dY); + }, + GetLength2: function(){ + var tMat; + //b2Vec2 p = this.m_body2->m_position + b2Mul(this.m_body2->m_R, this.m_localAnchor2); + tMat = this.m_body2.m_R; + var pX = this.m_body2.m_position.x + (tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y); + var pY = this.m_body2.m_position.y + (tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y); + //b2Vec2 s = this.m_ground->m_position + this.m_groundAnchor2; + //b2Vec2 d = p - s; + var dX = pX - (this.m_ground.m_position.x + this.m_groundAnchor2.x); + var dY = pY - (this.m_ground.m_position.y + this.m_groundAnchor2.y); + return Math.sqrt(dX*dX + dY*dY); + }, + + GetRatio: function(){ + return this.m_ratio; + }, + + //--------------- Internals Below ------------------- + + initialize: function(def){ + // The constructor for b2Joint + // initialize instance variables for references + this.m_node1 = new b2JointNode(); + this.m_node2 = new b2JointNode(); + // + this.m_type = def.type; + this.m_prev = null; + this.m_next = null; + this.m_body1 = def.body1; + this.m_body2 = def.body2; + this.m_collideConnected = def.collideConnected; + this.m_islandFlag = false; + this.m_userData = def.userData; + // + + // initialize instance variables for references + this.m_groundAnchor1 = new b2Vec2(); + this.m_groundAnchor2 = new b2Vec2(); + this.m_localAnchor1 = new b2Vec2(); + this.m_localAnchor2 = new b2Vec2(); + this.m_u1 = new b2Vec2(); + this.m_u2 = new b2Vec2(); + // + + + // parent + //super(def); + + var tMat; + var tX; + var tY; + + this.m_ground = this.m_body1.m_world.m_groundBody; + //this.m_groundAnchor1 = def.groundPoint1 - this.m_ground.m_position; + this.m_groundAnchor1.x = def.groundPoint1.x - this.m_ground.m_position.x; + this.m_groundAnchor1.y = def.groundPoint1.y - this.m_ground.m_position.y; + //this.m_groundAnchor2 = def.groundPoint2 - this.m_ground.m_position; + this.m_groundAnchor2.x = def.groundPoint2.x - this.m_ground.m_position.x; + this.m_groundAnchor2.y = def.groundPoint2.y - this.m_ground.m_position.y; + //this.m_localAnchor1 = b2MulT(this.m_body1.m_R, def.anchorPoint1 - this.m_body1.m_position); + tMat = this.m_body1.m_R; + tX = def.anchorPoint1.x - this.m_body1.m_position.x; + tY = def.anchorPoint1.y - this.m_body1.m_position.y; + this.m_localAnchor1.x = tX*tMat.col1.x + tY*tMat.col1.y; + this.m_localAnchor1.y = tX*tMat.col2.x + tY*tMat.col2.y; + //this.m_localAnchor2 = b2MulT(this.m_body2.m_R, def.anchorPoint2 - this.m_body2.m_position); + tMat = this.m_body2.m_R; + tX = def.anchorPoint2.x - this.m_body2.m_position.x; + tY = def.anchorPoint2.y - this.m_body2.m_position.y; + this.m_localAnchor2.x = tX*tMat.col1.x + tY*tMat.col1.y; + this.m_localAnchor2.y = tX*tMat.col2.x + tY*tMat.col2.y; + + this.m_ratio = def.ratio; + + //var d1 = def.groundPoint1 - def.anchorPoint1; + tX = def.groundPoint1.x - def.anchorPoint1.x; + tY = def.groundPoint1.y - def.anchorPoint1.y; + var d1Len = Math.sqrt(tX*tX + tY*tY); + //var d2 = def.groundPoint2 - def.anchorPoint2; + tX = def.groundPoint2.x - def.anchorPoint2.x; + tY = def.groundPoint2.y - def.anchorPoint2.y; + var d2Len = Math.sqrt(tX*tX + tY*tY); + + var length1 = b2Math.b2Max(0.5 * b2PulleyJoint.b2_minPulleyLength, d1Len); + var length2 = b2Math.b2Max(0.5 * b2PulleyJoint.b2_minPulleyLength, d2Len); + + this.m_constant = length1 + this.m_ratio * length2; + + this.m_maxLength1 = b2Math.b2Clamp(def.maxLength1, length1, this.m_constant - this.m_ratio * b2PulleyJoint.b2_minPulleyLength); + this.m_maxLength2 = b2Math.b2Clamp(def.maxLength2, length2, (this.m_constant - b2PulleyJoint.b2_minPulleyLength) / this.m_ratio); + + this.m_pulleyImpulse = 0.0; + this.m_limitImpulse1 = 0.0; + this.m_limitImpulse2 = 0.0; + + }, + + PrepareVelocitySolver: function(){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + //b2Vec2 r1 = b2Mul(b1->m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(b2->m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + //b2Vec2 p1 = b1->m_position + r1; + var p1X = b1.m_position.x + r1X; + var p1Y = b1.m_position.y + r1Y; + //b2Vec2 p2 = b2->m_position + r2; + var p2X = b2.m_position.x + r2X; + var p2Y = b2.m_position.y + r2Y; + + //b2Vec2 s1 = this.m_ground->m_position + this.m_groundAnchor1; + var s1X = this.m_ground.m_position.x + this.m_groundAnchor1.x; + var s1Y = this.m_ground.m_position.y + this.m_groundAnchor1.y; + //b2Vec2 s2 = this.m_ground->m_position + this.m_groundAnchor2; + var s2X = this.m_ground.m_position.x + this.m_groundAnchor2.x; + var s2Y = this.m_ground.m_position.y + this.m_groundAnchor2.y; + + // Get the pulley axes. + //this.m_u1 = p1 - s1; + this.m_u1.Set(p1X - s1X, p1Y - s1Y); + //this.m_u2 = p2 - s2; + this.m_u2.Set(p2X - s2X, p2Y - s2Y); + + var length1 = this.m_u1.Length(); + var length2 = this.m_u2.Length(); + + if (length1 > b2Settings.b2_linearSlop) + { + //this.m_u1 *= 1.0f / length1; + this.m_u1.Multiply(1.0 / length1); + } + else + { + this.m_u1.SetZero(); + } + + if (length2 > b2Settings.b2_linearSlop) + { + //this.m_u2 *= 1.0f / length2; + this.m_u2.Multiply(1.0 / length2); + } + else + { + this.m_u2.SetZero(); + } + + if (length1 < this.m_maxLength1) + { + this.m_limitState1 = b2Joint.e_inactiveLimit; + this.m_limitImpulse1 = 0.0; + } + else + { + this.m_limitState1 = b2Joint.e_atUpperLimit; + this.m_limitPositionImpulse1 = 0.0; + } + + if (length2 < this.m_maxLength2) + { + this.m_limitState2 = b2Joint.e_inactiveLimit; + this.m_limitImpulse2 = 0.0; + } + else + { + this.m_limitState2 = b2Joint.e_atUpperLimit; + this.m_limitPositionImpulse2 = 0.0; + } + + // Compute effective mass. + //var cr1u1 = b2Cross(r1, this.m_u1); + var cr1u1 = r1X * this.m_u1.y - r1Y * this.m_u1.x; + //var cr2u2 = b2Cross(r2, this.m_u2); + var cr2u2 = r2X * this.m_u2.y - r2Y * this.m_u2.x; + + this.m_limitMass1 = b1.m_invMass + b1.m_invI * cr1u1 * cr1u1; + this.m_limitMass2 = b2.m_invMass + b2.m_invI * cr2u2 * cr2u2; + this.m_pulleyMass = this.m_limitMass1 + this.m_ratio * this.m_ratio * this.m_limitMass2; + //b2Settings.b2Assert(this.m_limitMass1 > Number.MIN_VALUE); + //b2Settings.b2Assert(this.m_limitMass2 > Number.MIN_VALUE); + //b2Settings.b2Assert(this.m_pulleyMass > Number.MIN_VALUE); + this.m_limitMass1 = 1.0 / this.m_limitMass1; + this.m_limitMass2 = 1.0 / this.m_limitMass2; + this.m_pulleyMass = 1.0 / this.m_pulleyMass; + + // Warm starting. + //b2Vec2 P1 = (-this.m_pulleyImpulse - this.m_limitImpulse1) * this.m_u1; + var P1X = (-this.m_pulleyImpulse - this.m_limitImpulse1) * this.m_u1.x; + var P1Y = (-this.m_pulleyImpulse - this.m_limitImpulse1) * this.m_u1.y; + //b2Vec2 P2 = (-this.m_ratio * this.m_pulleyImpulse - this.m_limitImpulse2) * this.m_u2; + var P2X = (-this.m_ratio * this.m_pulleyImpulse - this.m_limitImpulse2) * this.m_u2.x; + var P2Y = (-this.m_ratio * this.m_pulleyImpulse - this.m_limitImpulse2) * this.m_u2.y; + //b1.m_linearVelocity += b1.m_invMass * P1; + b1.m_linearVelocity.x += b1.m_invMass * P1X; + b1.m_linearVelocity.y += b1.m_invMass * P1Y; + //b1.m_angularVelocity += b1.m_invI * b2Cross(r1, P1); + b1.m_angularVelocity += b1.m_invI * (r1X * P1Y - r1Y * P1X); + //b2.m_linearVelocity += b2.m_invMass * P2; + b2.m_linearVelocity.x += b2.m_invMass * P2X; + b2.m_linearVelocity.y += b2.m_invMass * P2Y; + //b2.m_angularVelocity += b2.m_invI * b2Cross(r2, P2); + b2.m_angularVelocity += b2.m_invI * (r2X * P2Y - r2Y * P2X); + }, + + SolveVelocityConstraints: function(step){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + //var r1 = b2Mul(b1.m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //var r2 = b2Mul(b2.m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + // temp vars + var v1X; + var v1Y; + var v2X; + var v2Y; + var P1X; + var P1Y; + var P2X; + var P2Y; + var Cdot; + var impulse; + var oldLimitImpulse; + + //{ + //b2Vec2 v1 = b1->m_linearVelocity + b2Cross(b1->m_angularVelocity, r1); + v1X = b1.m_linearVelocity.x + (-b1.m_angularVelocity * r1Y); + v1Y = b1.m_linearVelocity.y + (b1.m_angularVelocity * r1X); + //b2Vec2 v2 = b2->m_linearVelocity + b2Cross(b2->m_angularVelocity, r2); + v2X = b2.m_linearVelocity.x + (-b2.m_angularVelocity * r2Y); + v2Y = b2.m_linearVelocity.y + (b2.m_angularVelocity * r2X); + + //Cdot = -b2Dot(this.m_u1, v1) - this.m_ratio * b2Dot(this.m_u2, v2); + Cdot = -(this.m_u1.x * v1X + this.m_u1.y * v1Y) - this.m_ratio * (this.m_u2.x * v2X + this.m_u2.y * v2Y); + impulse = -this.m_pulleyMass * Cdot; + this.m_pulleyImpulse += impulse; + + //b2Vec2 P1 = -impulse * this.m_u1; + P1X = -impulse * this.m_u1.x; + P1Y = -impulse * this.m_u1.y; + //b2Vec2 P2 = -this.m_ratio * impulse * this.m_u2; + P2X = -this.m_ratio * impulse * this.m_u2.x; + P2Y = -this.m_ratio * impulse * this.m_u2.y; + //b1.m_linearVelocity += b1.m_invMass * P1; + b1.m_linearVelocity.x += b1.m_invMass * P1X; + b1.m_linearVelocity.y += b1.m_invMass * P1Y; + //b1.m_angularVelocity += b1.m_invI * b2Cross(r1, P1); + b1.m_angularVelocity += b1.m_invI * (r1X * P1Y - r1Y * P1X); + //b2.m_linearVelocity += b2.m_invMass * P2; + b2.m_linearVelocity.x += b2.m_invMass * P2X; + b2.m_linearVelocity.y += b2.m_invMass * P2Y; + //b2.m_angularVelocity += b2.m_invI * b2Cross(r2, P2); + b2.m_angularVelocity += b2.m_invI * (r2X * P2Y - r2Y * P2X); + //} + + if (this.m_limitState1 == b2Joint.e_atUpperLimit) + { + //b2Vec2 v1 = b1->m_linearVelocity + b2Cross(b1->m_angularVelocity, r1); + v1X = b1.m_linearVelocity.x + (-b1.m_angularVelocity * r1Y); + v1Y = b1.m_linearVelocity.y + (b1.m_angularVelocity * r1X); + + //float32 Cdot = -b2Dot(this.m_u1, v1); + Cdot = -(this.m_u1.x * v1X + this.m_u1.y * v1Y); + impulse = -this.m_limitMass1 * Cdot; + oldLimitImpulse = this.m_limitImpulse1; + this.m_limitImpulse1 = b2Math.b2Max(0.0, this.m_limitImpulse1 + impulse); + impulse = this.m_limitImpulse1 - oldLimitImpulse; + //b2Vec2 P1 = -impulse * this.m_u1; + P1X = -impulse * this.m_u1.x; + P1Y = -impulse * this.m_u1.y; + //b1.m_linearVelocity += b1->m_invMass * P1; + b1.m_linearVelocity.x += b1.m_invMass * P1X; + b1.m_linearVelocity.y += b1.m_invMass * P1Y; + //b1.m_angularVelocity += b1->m_invI * b2Cross(r1, P1); + b1.m_angularVelocity += b1.m_invI * (r1X * P1Y - r1Y * P1X); + } + + if (this.m_limitState2 == b2Joint.e_atUpperLimit) + { + //b2Vec2 v2 = b2->m_linearVelocity + b2Cross(b2->m_angularVelocity, r2); + v2X = b2.m_linearVelocity.x + (-b2.m_angularVelocity * r2Y); + v2Y = b2.m_linearVelocity.y + (b2.m_angularVelocity * r2X); + + //float32 Cdot = -b2Dot(this.m_u2, v2); + Cdot = -(this.m_u2.x * v2X + this.m_u2.y * v2Y); + impulse = -this.m_limitMass2 * Cdot; + oldLimitImpulse = this.m_limitImpulse2; + this.m_limitImpulse2 = b2Math.b2Max(0.0, this.m_limitImpulse2 + impulse); + impulse = this.m_limitImpulse2 - oldLimitImpulse; + //b2Vec2 P2 = -impulse * this.m_u2; + P2X = -impulse * this.m_u2.x; + P2Y = -impulse * this.m_u2.y; + //b2->m_linearVelocity += b2->m_invMass * P2; + b2.m_linearVelocity.x += b2.m_invMass * P2X; + b2.m_linearVelocity.y += b2.m_invMass * P2Y; + //b2->m_angularVelocity += b2->m_invI * b2Cross(r2, P2); + b2.m_angularVelocity += b2.m_invI * (r2X * P2Y - r2Y * P2X); + } + }, + + + + SolvePositionConstraints: function(){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + //b2Vec2 s1 = this.m_ground->m_position + this.m_groundAnchor1; + var s1X = this.m_ground.m_position.x + this.m_groundAnchor1.x; + var s1Y = this.m_ground.m_position.y + this.m_groundAnchor1.y; + //b2Vec2 s2 = this.m_ground->m_position + this.m_groundAnchor2; + var s2X = this.m_ground.m_position.x + this.m_groundAnchor2.x; + var s2Y = this.m_ground.m_position.y + this.m_groundAnchor2.y; + + // temp vars + var r1X; + var r1Y; + var r2X; + var r2Y; + var p1X; + var p1Y; + var p2X; + var p2Y; + var length1; + var length2; + var C; + var impulse; + var oldLimitPositionImpulse; + + var linearError = 0.0; + + { + //var r1 = b2Mul(b1.m_R, this.m_localAnchor1); + tMat = b1.m_R; + r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //var r2 = b2Mul(b2.m_R, this.m_localAnchor2); + tMat = b2.m_R; + r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + //b2Vec2 p1 = b1->m_position + r1; + p1X = b1.m_position.x + r1X; + p1Y = b1.m_position.y + r1Y; + //b2Vec2 p2 = b2->m_position + r2; + p2X = b2.m_position.x + r2X; + p2Y = b2.m_position.y + r2Y; + + // Get the pulley axes. + //this.m_u1 = p1 - s1; + this.m_u1.Set(p1X - s1X, p1Y - s1Y); + //this.m_u2 = p2 - s2; + this.m_u2.Set(p2X - s2X, p2Y - s2Y); + + length1 = this.m_u1.Length(); + length2 = this.m_u2.Length(); + + if (length1 > b2Settings.b2_linearSlop) + { + //this.m_u1 *= 1.0f / length1; + this.m_u1.Multiply( 1.0 / length1 ); + } + else + { + this.m_u1.SetZero(); + } + + if (length2 > b2Settings.b2_linearSlop) + { + //this.m_u2 *= 1.0f / length2; + this.m_u2.Multiply( 1.0 / length2 ); + } + else + { + this.m_u2.SetZero(); + } + + C = this.m_constant - length1 - this.m_ratio * length2; + linearError = b2Math.b2Max(linearError, Math.abs(C)); + C = b2Math.b2Clamp(C, -b2Settings.b2_maxLinearCorrection, b2Settings.b2_maxLinearCorrection); + impulse = -this.m_pulleyMass * C; + + p1X = -impulse * this.m_u1.x; + p1Y = -impulse * this.m_u1.y; + p2X = -this.m_ratio * impulse * this.m_u2.x; + p2Y = -this.m_ratio * impulse * this.m_u2.y; + + b1.m_position.x += b1.m_invMass * p1X; + b1.m_position.y += b1.m_invMass * p1Y; + b1.m_rotation += b1.m_invI * (r1X * p1Y - r1Y * p1X); + b2.m_position.x += b2.m_invMass * p2X; + b2.m_position.y += b2.m_invMass * p2Y; + b2.m_rotation += b2.m_invI * (r2X * p2Y - r2Y * p2X); + + b1.m_R.Set(b1.m_rotation); + b2.m_R.Set(b2.m_rotation); + } + + if (this.m_limitState1 == b2Joint.e_atUpperLimit) + { + //b2Vec2 r1 = b2Mul(b1->m_R, this.m_localAnchor1); + tMat = b1.m_R; + r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 p1 = b1->m_position + r1; + p1X = b1.m_position.x + r1X; + p1Y = b1.m_position.y + r1Y; + + //this.m_u1 = p1 - s1; + this.m_u1.Set(p1X - s1X, p1Y - s1Y); + + length1 = this.m_u1.Length(); + + if (length1 > b2Settings.b2_linearSlop) + { + //this.m_u1 *= 1.0 / length1; + this.m_u1.x *= 1.0 / length1; + this.m_u1.y *= 1.0 / length1; + } + else + { + this.m_u1.SetZero(); + } + + C = this.m_maxLength1 - length1; + linearError = b2Math.b2Max(linearError, -C); + C = b2Math.b2Clamp(C + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + impulse = -this.m_limitMass1 * C; + oldLimitPositionImpulse = this.m_limitPositionImpulse1; + this.m_limitPositionImpulse1 = b2Math.b2Max(0.0, this.m_limitPositionImpulse1 + impulse); + impulse = this.m_limitPositionImpulse1 - oldLimitPositionImpulse; + + //P1 = -impulse * this.m_u1; + p1X = -impulse * this.m_u1.x; + p1Y = -impulse * this.m_u1.y; + + b1.m_position.x += b1.m_invMass * p1X; + b1.m_position.y += b1.m_invMass * p1Y; + //b1.m_rotation += b1.m_invI * b2Cross(r1, P1); + b1.m_rotation += b1.m_invI * (r1X * p1Y - r1Y * p1X); + b1.m_R.Set(b1.m_rotation); + } + + if (this.m_limitState2 == b2Joint.e_atUpperLimit) + { + //b2Vec2 r2 = b2Mul(b2->m_R, this.m_localAnchor2); + tMat = b2.m_R; + r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + //b2Vec2 p2 = b2->m_position + r2; + p2X = b2.m_position.x + r2X; + p2Y = b2.m_position.y + r2Y; + + //this.m_u2 = p2 - s2; + this.m_u2.Set(p2X - s2X, p2Y - s2Y); + + length2 = this.m_u2.Length(); + + if (length2 > b2Settings.b2_linearSlop) + { + //this.m_u2 *= 1.0 / length2; + this.m_u2.x *= 1.0 / length2; + this.m_u2.y *= 1.0 / length2; + } + else + { + this.m_u2.SetZero(); + } + + C = this.m_maxLength2 - length2; + linearError = b2Math.b2Max(linearError, -C); + C = b2Math.b2Clamp(C + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + impulse = -this.m_limitMass2 * C; + oldLimitPositionImpulse = this.m_limitPositionImpulse2; + this.m_limitPositionImpulse2 = b2Math.b2Max(0.0, this.m_limitPositionImpulse2 + impulse); + impulse = this.m_limitPositionImpulse2 - oldLimitPositionImpulse; + + //P2 = -impulse * this.m_u2; + p2X = -impulse * this.m_u2.x; + p2Y = -impulse * this.m_u2.y; + + //b2.m_position += b2.m_invMass * P2; + b2.m_position.x += b2.m_invMass * p2X; + b2.m_position.y += b2.m_invMass * p2Y; + //b2.m_rotation += b2.m_invI * b2Cross(r2, P2); + b2.m_rotation += b2.m_invI * (r2X * p2Y - r2Y * p2X); + b2.m_R.Set(b2.m_rotation); + } + + return linearError < b2Settings.b2_linearSlop; + }, + + + + m_ground: null, + m_groundAnchor1: new b2Vec2(), + m_groundAnchor2: new b2Vec2(), + m_localAnchor1: new b2Vec2(), + m_localAnchor2: new b2Vec2(), + + m_u1: new b2Vec2(), + m_u2: new b2Vec2(), + + m_constant: null, + m_ratio: null, + + m_maxLength1: null, + m_maxLength2: null, + + // Effective masses + m_pulleyMass: null, + m_limitMass1: null, + m_limitMass2: null, + + // Impulses for accumulation/warm starting. + m_pulleyImpulse: null, + m_limitImpulse1: null, + m_limitImpulse2: null, + + // Position impulses for accumulation. + m_limitPositionImpulse1: null, + m_limitPositionImpulse2: null, + + m_limitState1: 0, + m_limitState2: 0 + + // static +}); + + + +b2PulleyJoint.b2_minPulleyLength = b2Settings.b2_lengthUnitsPerMeter; diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2PulleyJointDef.js b/lib/box2d-js/js/box2d/dynamics/joints/b2PulleyJointDef.js new file mode 100644 index 0000000..60428be --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2PulleyJointDef.js @@ -0,0 +1,70 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + +// The pulley joint is connected to two bodies and two fixed ground points. +// The pulley supports a ratio such that: +// length1 + ratio * length2 = constant +// Yes, the force transmitted is scaled by the ratio. +// The pulley also enforces a maximum length limit on both sides. This is +// useful to prevent one side of the pulley hitting the top. + +var b2PulleyJointDef = Class.create(); +Object.extend(b2PulleyJointDef.prototype, b2JointDef.prototype); +Object.extend(b2PulleyJointDef.prototype, +{ + initialize: function() + { + // The constructor for b2JointDef + this.type = b2Joint.e_unknownJoint; + this.userData = null; + this.body1 = null; + this.body2 = null; + this.collideConnected = false; + // + + // initialize instance variables for references + this.groundPoint1 = new b2Vec2(); + this.groundPoint2 = new b2Vec2(); + this.anchorPoint1 = new b2Vec2(); + this.anchorPoint2 = new b2Vec2(); + // + + this.type = b2Joint.e_pulleyJoint; + this.groundPoint1.Set(-1.0, 1.0); + this.groundPoint2.Set(1.0, 1.0); + this.anchorPoint1.Set(-1.0, 0.0); + this.anchorPoint2.Set(1.0, 0.0); + this.maxLength1 = 0.5 * b2PulleyJoint.b2_minPulleyLength; + this.maxLength2 = 0.5 * b2PulleyJoint.b2_minPulleyLength; + this.ratio = 1.0; + this.collideConnected = true; + }, + + groundPoint1: new b2Vec2(), + groundPoint2: new b2Vec2(), + anchorPoint1: new b2Vec2(), + anchorPoint2: new b2Vec2(), + maxLength1: null, + maxLength2: null, + ratio: null}); + diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2RevoluteJoint.js b/lib/box2d-js/js/box2d/dynamics/joints/b2RevoluteJoint.js new file mode 100644 index 0000000..9d45b2b --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2RevoluteJoint.js @@ -0,0 +1,491 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + +// Point-to-point constraint +// C = p2 - p1 +// Cdot = v2 - v1 +// = v2 + cross(w2, r2) - v1 - cross(w1, r1) +// J = [-I -r1_skew I r2_skew ] +// Identity used: +// w k % (rx i + ry j) = w * (-ry i + rx j) + +// Motor constraint +// Cdot = w2 - w1 +// J = [0 0 -1 0 0 1] +// K = invI1 + invI2 + +var b2RevoluteJoint = Class.create(); +Object.extend(b2RevoluteJoint.prototype, b2Joint.prototype); +Object.extend(b2RevoluteJoint.prototype, +{ + GetAnchor1: function(){ + var tMat = this.m_body1.m_R; + return new b2Vec2( this.m_body1.m_position.x + (tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y), + this.m_body1.m_position.y + (tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y)); + }, + GetAnchor2: function(){ + var tMat = this.m_body2.m_R; + return new b2Vec2( this.m_body2.m_position.x + (tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y), + this.m_body2.m_position.y + (tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y)); + }, + GetJointAngle: function(){ + return this.m_body2.m_rotation - this.m_body1.m_rotation; + }, + GetJointSpeed: function(){ + return this.m_body2.m_angularVelocity - this.m_body1.m_angularVelocity; + }, + GetMotorTorque: function(invTimeStep){ + return invTimeStep * this.m_motorImpulse; + }, + + SetMotorSpeed: function(speed) + { + this.m_motorSpeed = speed; + }, + + SetMotorTorque: function(torque) + { + this.m_maxMotorTorque = torque; + }, + + GetReactionForce: function(invTimeStep) + { + var tVec = this.m_ptpImpulse.Copy(); + tVec.Multiply(invTimeStep); + //return invTimeStep * this.m_ptpImpulse; + return tVec; + }, + + GetReactionTorque: function(invTimeStep) + { + return invTimeStep * this.m_limitImpulse; + }, + + //--------------- Internals Below ------------------- + + initialize: function(def){ + // The constructor for b2Joint + // initialize instance variables for references + this.m_node1 = new b2JointNode(); + this.m_node2 = new b2JointNode(); + // + this.m_type = def.type; + this.m_prev = null; + this.m_next = null; + this.m_body1 = def.body1; + this.m_body2 = def.body2; + this.m_collideConnected = def.collideConnected; + this.m_islandFlag = false; + this.m_userData = def.userData; + // + + // initialize instance variables for references + this.K = new b2Mat22(); + this.K1 = new b2Mat22(); + this.K2 = new b2Mat22(); + this.K3 = new b2Mat22(); + this.m_localAnchor1 = new b2Vec2(); + this.m_localAnchor2 = new b2Vec2(); + this.m_ptpImpulse = new b2Vec2(); + this.m_ptpMass = new b2Mat22(); + // + + //super(def); + + var tMat; + var tX; + var tY; + + //this.m_localAnchor1 = b2Math.b2MulTMV(this.m_body1.m_R, b2Math.SubtractVV( def.anchorPoint, this.m_body1.m_position)); + tMat = this.m_body1.m_R; + tX = def.anchorPoint.x - this.m_body1.m_position.x; + tY = def.anchorPoint.y - this.m_body1.m_position.y; + this.m_localAnchor1.x = tX * tMat.col1.x + tY * tMat.col1.y; + this.m_localAnchor1.y = tX * tMat.col2.x + tY * tMat.col2.y; + //this.m_localAnchor2 = b2Math.b2MulTMV(this.m_body2.m_R, b2Math.SubtractVV( def.anchorPoint, this.m_body2.m_position)); + tMat = this.m_body2.m_R; + tX = def.anchorPoint.x - this.m_body2.m_position.x; + tY = def.anchorPoint.y - this.m_body2.m_position.y; + this.m_localAnchor2.x = tX * tMat.col1.x + tY * tMat.col1.y; + this.m_localAnchor2.y = tX * tMat.col2.x + tY * tMat.col2.y; + + this.m_intialAngle = this.m_body2.m_rotation - this.m_body1.m_rotation; + + this.m_ptpImpulse.Set(0.0, 0.0); + this.m_motorImpulse = 0.0; + this.m_limitImpulse = 0.0; + this.m_limitPositionImpulse = 0.0; + + this.m_lowerAngle = def.lowerAngle; + this.m_upperAngle = def.upperAngle; + this.m_maxMotorTorque = def.motorTorque; + this.m_motorSpeed = def.motorSpeed; + this.m_enableLimit = def.enableLimit; + this.m_enableMotor = def.enableMotor; + }, + + // internal vars + K: new b2Mat22(), + K1: new b2Mat22(), + K2: new b2Mat22(), + K3: new b2Mat22(), + PrepareVelocitySolver: function(){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + // Compute the effective mass matrix. + //b2Vec2 r1 = b2Mul(b1->m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //b2Vec2 r2 = b2Mul(b2->m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + // this.K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * [r1.y*r1.y -r1.x*r1.y] + // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x] + var invMass1 = b1.m_invMass; + var invMass2 = b2.m_invMass; + var invI1 = b1.m_invI; + var invI2 = b2.m_invI; + + //var this.K1 = new b2Mat22(); + this.K1.col1.x = invMass1 + invMass2; this.K1.col2.x = 0.0; + this.K1.col1.y = 0.0; this.K1.col2.y = invMass1 + invMass2; + + //var this.K2 = new b2Mat22(); + this.K2.col1.x = invI1 * r1Y * r1Y; this.K2.col2.x = -invI1 * r1X * r1Y; + this.K2.col1.y = -invI1 * r1X * r1Y; this.K2.col2.y = invI1 * r1X * r1X; + + //var this.K3 = new b2Mat22(); + this.K3.col1.x = invI2 * r2Y * r2Y; this.K3.col2.x = -invI2 * r2X * r2Y; + this.K3.col1.y = -invI2 * r2X * r2Y; this.K3.col2.y = invI2 * r2X * r2X; + + //var this.K = b2Math.AddMM(b2Math.AddMM(this.K1, this.K2), this.K3); + this.K.SetM(this.K1); + this.K.AddM(this.K2); + this.K.AddM(this.K3); + + //this.m_ptpMass = this.K.Invert(); + this.K.Invert(this.m_ptpMass); + + this.m_motorMass = 1.0 / (invI1 + invI2); + + if (this.m_enableMotor == false) + { + this.m_motorImpulse = 0.0; + } + + if (this.m_enableLimit) + { + var jointAngle = b2.m_rotation - b1.m_rotation - this.m_intialAngle; + if (b2Math.b2Abs(this.m_upperAngle - this.m_lowerAngle) < 2.0 * b2Settings.b2_angularSlop) + { + this.m_limitState = b2Joint.e_equalLimits; + } + else if (jointAngle <= this.m_lowerAngle) + { + if (this.m_limitState != b2Joint.e_atLowerLimit) + { + this.m_limitImpulse = 0.0; + } + this.m_limitState = b2Joint.e_atLowerLimit; + } + else if (jointAngle >= this.m_upperAngle) + { + if (this.m_limitState != b2Joint.e_atUpperLimit) + { + this.m_limitImpulse = 0.0; + } + this.m_limitState = b2Joint.e_atUpperLimit; + } + else + { + this.m_limitState = b2Joint.e_inactiveLimit; + this.m_limitImpulse = 0.0; + } + } + else + { + this.m_limitImpulse = 0.0; + } + + // Warm starting. + if (b2World.s_enableWarmStarting) + { + //b1.m_linearVelocity.Subtract( b2Math.MulFV( invMass1, this.m_ptpImpulse) ); + b1.m_linearVelocity.x -= invMass1 * this.m_ptpImpulse.x; + b1.m_linearVelocity.y -= invMass1 * this.m_ptpImpulse.y; + //b1.m_angularVelocity -= invI1 * (b2Math.b2CrossVV(r1, this.m_ptpImpulse) + this.m_motorImpulse + this.m_limitImpulse); + b1.m_angularVelocity -= invI1 * ((r1X * this.m_ptpImpulse.y - r1Y * this.m_ptpImpulse.x) + this.m_motorImpulse + this.m_limitImpulse); + + //b2.m_linearVelocity.Add( b2Math.MulFV( invMass2 , this.m_ptpImpulse )); + b2.m_linearVelocity.x += invMass2 * this.m_ptpImpulse.x; + b2.m_linearVelocity.y += invMass2 * this.m_ptpImpulse.y; + //b2.m_angularVelocity += invI2 * (b2Math.b2CrossVV(r2, this.m_ptpImpulse) + this.m_motorImpulse + this.m_limitImpulse); + b2.m_angularVelocity += invI2 * ((r2X * this.m_ptpImpulse.y - r2Y * this.m_ptpImpulse.x) + this.m_motorImpulse + this.m_limitImpulse); + } + else{ + this.m_ptpImpulse.SetZero(); + this.m_motorImpulse = 0.0; + this.m_limitImpulse = 0.0; + } + + this.m_limitPositionImpulse = 0.0; + }, + + + SolveVelocityConstraints: function(step){ + var b1 = this.m_body1; + var b2 = this.m_body2; + + var tMat; + + //var r1 = b2Math.b2MulMV(b1.m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //var r2 = b2Math.b2MulMV(b2.m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + var oldLimitImpulse; + + // Solve point-to-point constraint + //b2Vec2 ptpCdot = b2.m_linearVelocity + b2Cross(b2.m_angularVelocity, r2) - b1.m_linearVelocity - b2Cross(b1.m_angularVelocity, r1); + var ptpCdotX = b2.m_linearVelocity.x + (-b2.m_angularVelocity * r2Y) - b1.m_linearVelocity.x - (-b1.m_angularVelocity * r1Y); + var ptpCdotY = b2.m_linearVelocity.y + (b2.m_angularVelocity * r2X) - b1.m_linearVelocity.y - (b1.m_angularVelocity * r1X); + + //b2Vec2 ptpImpulse = -b2Mul(this.m_ptpMass, ptpCdot); + var ptpImpulseX = -(this.m_ptpMass.col1.x * ptpCdotX + this.m_ptpMass.col2.x * ptpCdotY); + var ptpImpulseY = -(this.m_ptpMass.col1.y * ptpCdotX + this.m_ptpMass.col2.y * ptpCdotY); + this.m_ptpImpulse.x += ptpImpulseX; + this.m_ptpImpulse.y += ptpImpulseY; + + //b1->m_linearVelocity -= b1->m_invMass * ptpImpulse; + b1.m_linearVelocity.x -= b1.m_invMass * ptpImpulseX; + b1.m_linearVelocity.y -= b1.m_invMass * ptpImpulseY; + //b1->m_angularVelocity -= b1->m_invI * b2Cross(r1, ptpImpulse); + b1.m_angularVelocity -= b1.m_invI * (r1X * ptpImpulseY - r1Y * ptpImpulseX); + + //b2->m_linearVelocity += b2->m_invMass * ptpImpulse; + b2.m_linearVelocity.x += b2.m_invMass * ptpImpulseX; + b2.m_linearVelocity.y += b2.m_invMass * ptpImpulseY; + //b2->m_angularVelocity += b2->m_invI * b2Cross(r2, ptpImpulse); + b2.m_angularVelocity += b2.m_invI * (r2X * ptpImpulseY - r2Y * ptpImpulseX); + + if (this.m_enableMotor && this.m_limitState != b2Joint.e_equalLimits) + { + var motorCdot = b2.m_angularVelocity - b1.m_angularVelocity - this.m_motorSpeed; + var motorImpulse = -this.m_motorMass * motorCdot; + var oldMotorImpulse = this.m_motorImpulse; + this.m_motorImpulse = b2Math.b2Clamp(this.m_motorImpulse + motorImpulse, -step.dt * this.m_maxMotorTorque, step.dt * this.m_maxMotorTorque); + motorImpulse = this.m_motorImpulse - oldMotorImpulse; + b1.m_angularVelocity -= b1.m_invI * motorImpulse; + b2.m_angularVelocity += b2.m_invI * motorImpulse; + } + + if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) + { + var limitCdot = b2.m_angularVelocity - b1.m_angularVelocity; + var limitImpulse = -this.m_motorMass * limitCdot; + + if (this.m_limitState == b2Joint.e_equalLimits) + { + this.m_limitImpulse += limitImpulse; + } + else if (this.m_limitState == b2Joint.e_atLowerLimit) + { + oldLimitImpulse = this.m_limitImpulse; + this.m_limitImpulse = b2Math.b2Max(this.m_limitImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitImpulse - oldLimitImpulse; + } + else if (this.m_limitState == b2Joint.e_atUpperLimit) + { + oldLimitImpulse = this.m_limitImpulse; + this.m_limitImpulse = b2Math.b2Min(this.m_limitImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitImpulse - oldLimitImpulse; + } + + b1.m_angularVelocity -= b1.m_invI * limitImpulse; + b2.m_angularVelocity += b2.m_invI * limitImpulse; + } + }, + + + SolvePositionConstraints: function(){ + + var oldLimitImpulse; + var limitC; + + var b1 = this.m_body1; + var b2 = this.m_body2; + + var positionError = 0.0; + + var tMat; + + // Solve point-to-point position error. + //var r1 = b2Math.b2MulMV(b1.m_R, this.m_localAnchor1); + tMat = b1.m_R; + var r1X = tMat.col1.x * this.m_localAnchor1.x + tMat.col2.x * this.m_localAnchor1.y; + var r1Y = tMat.col1.y * this.m_localAnchor1.x + tMat.col2.y * this.m_localAnchor1.y; + //var r2 = b2Math.b2MulMV(b2.m_R, this.m_localAnchor2); + tMat = b2.m_R; + var r2X = tMat.col1.x * this.m_localAnchor2.x + tMat.col2.x * this.m_localAnchor2.y; + var r2Y = tMat.col1.y * this.m_localAnchor2.x + tMat.col2.y * this.m_localAnchor2.y; + + //b2Vec2 p1 = b1->m_position + r1; + var p1X = b1.m_position.x + r1X; + var p1Y = b1.m_position.y + r1Y; + //b2Vec2 p2 = b2->m_position + r2; + var p2X = b2.m_position.x + r2X; + var p2Y = b2.m_position.y + r2Y; + + //b2Vec2 ptpC = p2 - p1; + var ptpCX = p2X - p1X; + var ptpCY = p2Y - p1Y; + + //float32 positionError = ptpC.Length(); + positionError = Math.sqrt(ptpCX*ptpCX + ptpCY*ptpCY); + + // Prevent overly large corrections. + //b2Vec2 dpMax(b2_maxLinearCorrection, b2_maxLinearCorrection); + //ptpC = b2Clamp(ptpC, -dpMax, dpMax); + + //float32 invMass1 = b1->m_invMass, invMass2 = b2->m_invMass; + var invMass1 = b1.m_invMass; + var invMass2 = b2.m_invMass; + //float32 invI1 = b1->m_invI, invI2 = b2->m_invI; + var invI1 = b1.m_invI; + var invI2 = b2.m_invI; + + //b2Mat22 this.K1; + this.K1.col1.x = invMass1 + invMass2; this.K1.col2.x = 0.0; + this.K1.col1.y = 0.0; this.K1.col2.y = invMass1 + invMass2; + + //b2Mat22 this.K2; + this.K2.col1.x = invI1 * r1Y * r1Y; this.K2.col2.x = -invI1 * r1X * r1Y; + this.K2.col1.y = -invI1 * r1X * r1Y; this.K2.col2.y = invI1 * r1X * r1X; + + //b2Mat22 this.K3; + this.K3.col1.x = invI2 * r2Y * r2Y; this.K3.col2.x = -invI2 * r2X * r2Y; + this.K3.col1.y = -invI2 * r2X * r2Y; this.K3.col2.y = invI2 * r2X * r2X; + + //b2Mat22 this.K = this.K1 + this.K2 + this.K3; + this.K.SetM(this.K1); + this.K.AddM(this.K2); + this.K.AddM(this.K3); + //b2Vec2 impulse = this.K.Solve(-ptpC); + this.K.Solve(b2RevoluteJoint.tImpulse, -ptpCX, -ptpCY); + var impulseX = b2RevoluteJoint.tImpulse.x; + var impulseY = b2RevoluteJoint.tImpulse.y; + + //b1.m_position -= b1.m_invMass * impulse; + b1.m_position.x -= b1.m_invMass * impulseX; + b1.m_position.y -= b1.m_invMass * impulseY; + //b1.m_rotation -= b1.m_invI * b2Cross(r1, impulse); + b1.m_rotation -= b1.m_invI * (r1X * impulseY - r1Y * impulseX); + b1.m_R.Set(b1.m_rotation); + + //b2.m_position += b2.m_invMass * impulse; + b2.m_position.x += b2.m_invMass * impulseX; + b2.m_position.y += b2.m_invMass * impulseY; + //b2.m_rotation += b2.m_invI * b2Cross(r2, impulse); + b2.m_rotation += b2.m_invI * (r2X * impulseY - r2Y * impulseX); + b2.m_R.Set(b2.m_rotation); + + + // Handle limits. + var angularError = 0.0; + + if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) + { + var angle = b2.m_rotation - b1.m_rotation - this.m_intialAngle; + var limitImpulse = 0.0; + + if (this.m_limitState == b2Joint.e_equalLimits) + { + // Prevent large angular corrections + limitC = b2Math.b2Clamp(angle, -b2Settings.b2_maxAngularCorrection, b2Settings.b2_maxAngularCorrection); + limitImpulse = -this.m_motorMass * limitC; + angularError = b2Math.b2Abs(limitC); + } + else if (this.m_limitState == b2Joint.e_atLowerLimit) + { + limitC = angle - this.m_lowerAngle; + angularError = b2Math.b2Max(0.0, -limitC); + + // Prevent large angular corrections and allow some slop. + limitC = b2Math.b2Clamp(limitC + b2Settings.b2_angularSlop, -b2Settings.b2_maxAngularCorrection, 0.0); + limitImpulse = -this.m_motorMass * limitC; + oldLimitImpulse = this.m_limitPositionImpulse; + this.m_limitPositionImpulse = b2Math.b2Max(this.m_limitPositionImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitPositionImpulse - oldLimitImpulse; + } + else if (this.m_limitState == b2Joint.e_atUpperLimit) + { + limitC = angle - this.m_upperAngle; + angularError = b2Math.b2Max(0.0, limitC); + + // Prevent large angular corrections and allow some slop. + limitC = b2Math.b2Clamp(limitC - b2Settings.b2_angularSlop, 0.0, b2Settings.b2_maxAngularCorrection); + limitImpulse = -this.m_motorMass * limitC; + oldLimitImpulse = this.m_limitPositionImpulse; + this.m_limitPositionImpulse = b2Math.b2Min(this.m_limitPositionImpulse + limitImpulse, 0.0); + limitImpulse = this.m_limitPositionImpulse - oldLimitImpulse; + } + + b1.m_rotation -= b1.m_invI * limitImpulse; + b1.m_R.Set(b1.m_rotation); + b2.m_rotation += b2.m_invI * limitImpulse; + b2.m_R.Set(b2.m_rotation); + } + + return positionError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop; + }, + + m_localAnchor1: new b2Vec2(), + m_localAnchor2: new b2Vec2(), + m_ptpImpulse: new b2Vec2(), + m_motorImpulse: null, + m_limitImpulse: null, + m_limitPositionImpulse: null, + + m_ptpMass: new b2Mat22(), + m_motorMass: null, + m_intialAngle: null, + m_lowerAngle: null, + m_upperAngle: null, + m_maxMotorTorque: null, + m_motorSpeed: null, + + m_enableLimit: null, + m_enableMotor: null, + m_limitState: 0}); + +b2RevoluteJoint.tImpulse = new b2Vec2(); diff --git a/lib/box2d-js/js/box2d/dynamics/joints/b2RevoluteJointDef.js b/lib/box2d-js/js/box2d/dynamics/joints/b2RevoluteJointDef.js new file mode 100644 index 0000000..1b90643 --- /dev/null +++ b/lib/box2d-js/js/box2d/dynamics/joints/b2RevoluteJointDef.js @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http: +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked, and must not be +* misrepresented the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + + + + + + +var b2RevoluteJointDef = Class.create(); +Object.extend(b2RevoluteJointDef.prototype, b2JointDef.prototype); +Object.extend(b2RevoluteJointDef.prototype, +{ + initialize: function() + { + // The constructor for b2JointDef + this.type = b2Joint.e_unknownJoint; + this.userData = null; + this.body1 = null; + this.body2 = null; + this.collideConnected = false; + // + + this.type = b2Joint.e_revoluteJoint; + this.anchorPoint = new b2Vec2(0.0, 0.0); + this.lowerAngle = 0.0; + this.upperAngle = 0.0; + this.motorTorque = 0.0; + this.motorSpeed = 0.0; + this.enableLimit = false; + this.enableMotor = false; + }, + + anchorPoint: null, + lowerAngle: null, + upperAngle: null, + motorTorque: null, + motorSpeed: null, + enableLimit: null, + enableMotor: null}); + diff --git a/lib/box2d-js/lib/AUTHORS b/lib/box2d-js/lib/AUTHORS new file mode 100644 index 0000000..90decb3 --- /dev/null +++ b/lib/box2d-js/lib/AUTHORS @@ -0,0 +1,10 @@ +ExplorerCanvas + +Google Open Source: + + + +Developers: + Emil A Eklund + Erik Arvidsson + Glen Murphy \ No newline at end of file diff --git a/lib/box2d-js/lib/COPYING b/lib/box2d-js/lib/COPYING new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/lib/box2d-js/lib/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/box2d-js/lib/README b/lib/box2d-js/lib/README new file mode 100644 index 0000000..eb7c42f --- /dev/null +++ b/lib/box2d-js/lib/README @@ -0,0 +1,22 @@ +ExplorerCanvas +Copyright 2006 Google Inc. + +------------------------------------------------------------------------------- +DESCRIPTION + +Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based +drawing operations. ExplorerCanvas brings the same functionality to Internet +Explorer; web developers only need to include a single script tag in their +existing canvas webpages to enable this support. + + +------------------------------------------------------------------------------- +INSTALLATION + +Include the ExplorerCanvas tag in the same directory as your HTML files, and +add the following code to your page, preferably in the tag. + + + +If you run into trouble, please look at the included example code to see how +to best implement this \ No newline at end of file diff --git a/lib/box2d-js/lib/excanvas-compressed.js b/lib/box2d-js/lib/excanvas-compressed.js new file mode 100644 index 0000000..9d71658 --- /dev/null +++ b/lib/box2d-js/lib/excanvas-compressed.js @@ -0,0 +1,19 @@ +if(!window.CanvasRenderingContext2D){(function(){var I=Math,i=I.round,L=I.sin,M=I.cos,m=10,A=m/2,Q={init:function(a){var b=a||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;b.attachEvent("onreadystatechange",function(){c.r(b)})}},r:function(a){if(a.readyState=="complete"){if(!a.namespaces["s"]){a.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var b=a.createStyleSheet();b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}"; +var c=a.getElementsByTagName("canvas");for(var d=0;d"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a.attachEvent("onresize", +W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){var b=a.srcElement; +if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit= +a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":return"round"; +case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=function(){this.j.innerHTML= +"";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this.bezierCurveTo(e, +g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRect=function(a, +b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height;a.runtimeStyle.width= +f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" ','","");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("n.x){n.x=k.x}if(l.y== +null||k.yn.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o=0;oC.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset')}else if(a){b.push('')}else{b.push("')}b.push("");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={};N(this,a); +this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n=0;this.o= +0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()}; diff --git a/lib/box2d-js/lib/excanvas.js b/lib/box2d-js/lib/excanvas.js new file mode 100644 index 0000000..3e1aedf --- /dev/null +++ b/lib/box2d-js/lib/excanvas.js @@ -0,0 +1,785 @@ +// Copyright 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Known Issues: +// +// * Patterns are not implemented. +// * Radial gradient are not implemented. The VML version of these look very +// different from the canvas one. +// * Clipping paths are not implemented. +// * Coordsize. The width and height attribute have higher priority than the +// width and height style values which isn't correct. +// * Painting mode isn't implemented. +// * Canvas width/height should is using content-box by default. IE in +// Quirks mode will draw the canvas using border-box. Either change your +// doctype to HTML5 +// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) +// or use Box Sizing Behavior from WebFX +// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) +// * Optimize. There is always room for speed improvements. + +// only add this code if we do not already have a canvas implementation +if (!window.CanvasRenderingContext2D) { + +(function () { + + // alias some functions to make (compiled) code shorter + var m = Math; + var mr = m.round; + var ms = m.sin; + var mc = m.cos; + + // this is used for sub pixel precision + var Z = 10; + var Z2 = Z / 2; + + var G_vmlCanvasManager_ = { + init: function (opt_doc) { + var doc = opt_doc || document; + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + var self = this; + doc.attachEvent("onreadystatechange", function () { + self.init_(doc); + }); + } + }, + + init_: function (doc) { + if (doc.readyState == "complete") { + // create xmlns + if (!doc.namespaces["g_vml_"]) { + doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml"); + } + + // setup default css + var ss = doc.createStyleSheet(); + ss.cssText = "canvas{display:inline-block;overflow:hidden;" + + // default size is 300x150 in Gecko and Opera + "text-align:left;width:300px;height:150px}" + + "g_vml_\\:*{behavior:url(#default#VML)}"; + + // find all canvas elements + var els = doc.getElementsByTagName("canvas"); + for (var i = 0; i < els.length; i++) { + if (!els[i].getContext) { + this.initElement(els[i]); + } + } + } + }, + + fixElement_: function (el) { + // in IE before version 5.5 we would need to add HTML: to the tag name + // but we do not care about IE before version 6 + var outerHTML = el.outerHTML; + + var newEl = el.ownerDocument.createElement(outerHTML); + // if the tag is still open IE has created the children as siblings and + // it has also created a tag with the name "/FOO" + if (outerHTML.slice(-2) != "/>") { + var tagName = "/" + el.tagName; + var ns; + // remove content + while ((ns = el.nextSibling) && ns.tagName != tagName) { + ns.removeNode(); + } + // remove the incorrect closing tag + if (ns) { + ns.removeNode(); + } + } + el.parentNode.replaceChild(newEl, el); + return newEl; + }, + + /** + * Public initializes a canvas element so that it can be used as canvas + * element from now on. This is called automatically before the page is + * loaded but if you are creating elements using createElement you need to + * make sure this is called on the element. + * @param {HTMLElement} el The canvas element to initialize. + * @return {HTMLElement} the element that was created. + */ + initElement: function (el) { + el = this.fixElement_(el); + el.getContext = function () { + if (this.context_) { + return this.context_; + } + return this.context_ = new CanvasRenderingContext2D_(this); + }; + + // do not use inline function because that will leak memory + el.attachEvent('onpropertychange', onPropertyChange); + el.attachEvent('onresize', onResize); + + var attrs = el.attributes; + if (attrs.width && attrs.width.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setWidth_(attrs.width.nodeValue); + el.style.width = attrs.width.nodeValue + "px"; + } else { + el.width = el.clientWidth; + } + if (attrs.height && attrs.height.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setHeight_(attrs.height.nodeValue); + el.style.height = attrs.height.nodeValue + "px"; + } else { + el.height = el.clientHeight; + } + //el.getContext().setCoordsize_() + return el; + } + }; + + function onPropertyChange(e) { + var el = e.srcElement; + + switch (e.propertyName) { + case 'width': + el.style.width = el.attributes.width.nodeValue + "px"; + el.getContext().clearRect(); + break; + case 'height': + el.style.height = el.attributes.height.nodeValue + "px"; + el.getContext().clearRect(); + break; + } + } + + function onResize(e) { + var el = e.srcElement; + if (el.firstChild) { + el.firstChild.style.width = el.clientWidth + 'px'; + el.firstChild.style.height = el.clientHeight + 'px'; + } + } + + G_vmlCanvasManager_.init(); + + // precompute "00" to "FF" + var dec2hex = []; + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 16; j++) { + dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); + } + } + + function createMatrixIdentity() { + return [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1] + ]; + } + + function matrixMultiply(m1, m2) { + var result = createMatrixIdentity(); + + for (var x = 0; x < 3; x++) { + for (var y = 0; y < 3; y++) { + var sum = 0; + + for (var z = 0; z < 3; z++) { + sum += m1[x][z] * m2[z][y]; + } + + result[x][y] = sum; + } + } + return result; + } + + function copyState(o1, o2) { + o2.fillStyle = o1.fillStyle; + o2.lineCap = o1.lineCap; + o2.lineJoin = o1.lineJoin; + o2.lineWidth = o1.lineWidth; + o2.miterLimit = o1.miterLimit; + o2.shadowBlur = o1.shadowBlur; + o2.shadowColor = o1.shadowColor; + o2.shadowOffsetX = o1.shadowOffsetX; + o2.shadowOffsetY = o1.shadowOffsetY; + o2.strokeStyle = o1.strokeStyle; + o2.arcScaleX_ = o1.arcScaleX_; + o2.arcScaleY_ = o1.arcScaleY_; + } + + function processStyle(styleString) { + var str, alpha = 1; + + styleString = String(styleString); + if (styleString.substring(0, 3) == "rgb") { + var start = styleString.indexOf("(", 3); + var end = styleString.indexOf(")", start + 1); + var guts = styleString.substring(start + 1, end).split(","); + + str = "#"; + for (var i = 0; i < 3; i++) { + str += dec2hex[Number(guts[i])]; + } + + if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) { + alpha = guts[3]; + } + } else { + str = styleString; + } + + return [str, alpha]; + } + + function processLineCap(lineCap) { + switch (lineCap) { + case "butt": + return "flat"; + case "round": + return "round"; + case "square": + default: + return "square"; + } + } + + /** + * This class implements CanvasRenderingContext2D interface as described by + * the WHATWG. + * @param {HTMLElement} surfaceElement The element that the 2D context should + * be associated with + */ + function CanvasRenderingContext2D_(surfaceElement) { + this.m_ = createMatrixIdentity(); + + this.mStack_ = []; + this.aStack_ = []; + this.currentPath_ = []; + + // Canvas context properties + this.strokeStyle = "#000"; + this.fillStyle = "#000"; + + this.lineWidth = 1; + this.lineJoin = "miter"; + this.lineCap = "butt"; + this.miterLimit = Z * 1; + this.globalAlpha = 1; + this.canvas = surfaceElement; + + var el = surfaceElement.ownerDocument.createElement('div'); + el.style.width = surfaceElement.clientWidth + 'px'; + el.style.height = surfaceElement.clientHeight + 'px'; + el.style.overflow = 'hidden'; + el.style.position = 'absolute'; + surfaceElement.appendChild(el); + + this.element_ = el; + this.arcScaleX_ = 1; + this.arcScaleY_ = 1; + }; + + var contextPrototype = CanvasRenderingContext2D_.prototype; + contextPrototype.clearRect = function() { + this.element_.innerHTML = ""; + this.currentPath_ = []; + }; + + contextPrototype.beginPath = function() { + // TODO: Branch current matrix so that save/restore has no effect + // as per safari docs. + + this.currentPath_ = []; + }; + + contextPrototype.moveTo = function(aX, aY) { + this.currentPath_.push({type: "moveTo", x: aX, y: aY}); + this.currentX_ = aX; + this.currentY_ = aY; + }; + + contextPrototype.lineTo = function(aX, aY) { + this.currentPath_.push({type: "lineTo", x: aX, y: aY}); + this.currentX_ = aX; + this.currentY_ = aY; + }; + + contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY) { + this.currentPath_.push({type: "bezierCurveTo", + cp1x: aCP1x, + cp1y: aCP1y, + cp2x: aCP2x, + cp2y: aCP2y, + x: aX, + y: aY}); + this.currentX_ = aX; + this.currentY_ = aY; + }; + + contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { + // the following is lifted almost directly from + // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes + var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_); + var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_); + var cp2x = cp1x + (aX - this.currentX_) / 3.0; + var cp2y = cp1y + (aY - this.currentY_) / 3.0; + this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY); + }; + + contextPrototype.arc = function(aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise) { + aRadius *= Z; + var arcType = aClockwise ? "at" : "wa"; + + var xStart = aX + (mc(aStartAngle) * aRadius) - Z2; + var yStart = aY + (ms(aStartAngle) * aRadius) - Z2; + + var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2; + var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2; + + // IE won't render arches drawn counter clockwise if xStart == xEnd. + if (xStart == xEnd && !aClockwise) { + xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something + // that can be represented in binary + } + + this.currentPath_.push({type: arcType, + x: aX, + y: aY, + radius: aRadius, + xStart: xStart, + yStart: yStart, + xEnd: xEnd, + yEnd: yEnd}); + + }; + + contextPrototype.rect = function(aX, aY, aWidth, aHeight) { + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + }; + + contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { + // Will destroy any existing path (same as FF behaviour) + this.beginPath(); + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.stroke(); + }; + + contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { + // Will destroy any existing path (same as FF behaviour) + this.beginPath(); + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.fill(); + }; + + contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { + var gradient = new CanvasGradient_("gradient"); + return gradient; + }; + + contextPrototype.createRadialGradient = function(aX0, aY0, + aR0, aX1, + aY1, aR1) { + var gradient = new CanvasGradient_("gradientradial"); + gradient.radius1_ = aR0; + gradient.radius2_ = aR1; + gradient.focus_.x = aX0; + gradient.focus_.y = aY0; + return gradient; + }; + + contextPrototype.drawImage = function (image, var_args) { + var dx, dy, dw, dh, sx, sy, sw, sh; + + // to find the original width we overide the width and height + var oldRuntimeWidth = image.runtimeStyle.width; + var oldRuntimeHeight = image.runtimeStyle.height; + image.runtimeStyle.width = 'auto'; + image.runtimeStyle.height = 'auto'; + + // get the original size + var w = image.width; + var h = image.height; + + // and remove overides + image.runtimeStyle.width = oldRuntimeWidth; + image.runtimeStyle.height = oldRuntimeHeight; + + if (arguments.length == 3) { + dx = arguments[1]; + dy = arguments[2]; + sx = sy = 0; + sw = dw = w; + sh = dh = h; + } else if (arguments.length == 5) { + dx = arguments[1]; + dy = arguments[2]; + dw = arguments[3]; + dh = arguments[4]; + sx = sy = 0; + sw = w; + sh = h; + } else if (arguments.length == 9) { + sx = arguments[1]; + sy = arguments[2]; + sw = arguments[3]; + sh = arguments[4]; + dx = arguments[5]; + dy = arguments[6]; + dw = arguments[7]; + dh = arguments[8]; + } else { + throw "Invalid number of arguments"; + } + + var d = this.getCoords_(dx, dy); + + var w2 = sw / 2; + var h2 = sh / 2; + + var vmlStr = []; + + var W = 10; + var H = 10; + + // For some reason that I've now forgotten, using divs didn't work + vmlStr.push(' ' , + '', + ''); + + this.element_.insertAdjacentHTML("BeforeEnd", + vmlStr.join("")); + }; + + contextPrototype.stroke = function(aFill) { + var lineStr = []; + var lineOpen = false; + var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); + var color = a[0]; + var opacity = a[1] * this.globalAlpha; + + var W = 10; + var H = 10; + + lineStr.push(' max.x) { + max.x = c.x; + } + if (min.y == null || c.y < min.y) { + min.y = c.y; + } + if (max.y == null || c.y > max.y) { + max.y = c.y; + } + } + } + lineStr.push(' ">'); + + if (typeof this.fillStyle == "object") { + var focus = {x: "50%", y: "50%"}; + var width = (max.x - min.x); + var height = (max.y - min.y); + var dimension = (width > height) ? width : height; + + focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; + focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; + + var colors = []; + + // inside radius (%) + if (this.fillStyle.type_ == "gradientradial") { + var inside = (this.fillStyle.radius1_ / dimension * 100); + + // percentage that outside radius exceeds inside radius + var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; + } else { + var inside = 0; + var expansion = 100; + } + + var insidecolor = {offset: null, color: null}; + var outsidecolor = {offset: null, color: null}; + + // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie + // won't interpret it correctly + this.fillStyle.colors_.sort(function (cs1, cs2) { + return cs1.offset - cs2.offset; + }); + + for (var i = 0; i < this.fillStyle.colors_.length; i++) { + var fs = this.fillStyle.colors_[i]; + + colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); + + if (fs.offset > insidecolor.offset || insidecolor.offset == null) { + insidecolor.offset = fs.offset; + insidecolor.color = fs.color; + } + + if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) { + outsidecolor.offset = fs.offset; + outsidecolor.color = fs.color; + } + } + colors.pop(); + + lineStr.push(''); + } else if (aFill) { + lineStr.push(''); + } else { + lineStr.push( + '' + ); + } + + lineStr.push(""); + + this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); + + this.currentPath_ = []; + }; + + contextPrototype.fill = function() { + this.stroke(true); + } + + contextPrototype.closePath = function() { + this.currentPath_.push({type: "close"}); + }; + + /** + * @private + */ + contextPrototype.getCoords_ = function(aX, aY) { + return { + x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2, + y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2 + } + }; + + contextPrototype.save = function() { + var o = {}; + copyState(this, o); + this.aStack_.push(o); + this.mStack_.push(this.m_); + this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); + }; + + contextPrototype.restore = function() { + copyState(this.aStack_.pop(), this); + this.m_ = this.mStack_.pop(); + }; + + contextPrototype.translate = function(aX, aY) { + var m1 = [ + [1, 0, 0], + [0, 1, 0], + [aX, aY, 1] + ]; + + this.m_ = matrixMultiply(m1, this.m_); + }; + + contextPrototype.rotate = function(aRot) { + var c = mc(aRot); + var s = ms(aRot); + + var m1 = [ + [c, s, 0], + [-s, c, 0], + [0, 0, 1] + ]; + + this.m_ = matrixMultiply(m1, this.m_); + }; + + contextPrototype.scale = function(aX, aY) { + this.arcScaleX_ *= aX; + this.arcScaleY_ *= aY; + var m1 = [ + [aX, 0, 0], + [0, aY, 0], + [0, 0, 1] + ]; + + this.m_ = matrixMultiply(m1, this.m_); + }; + + /******** STUBS ********/ + contextPrototype.clip = function() { + // TODO: Implement + }; + + contextPrototype.arcTo = function() { + // TODO: Implement + }; + + contextPrototype.createPattern = function() { + return new CanvasPattern_; + }; + + // Gradient / Pattern Stubs + function CanvasGradient_(aType) { + this.type_ = aType; + this.radius1_ = 0; + this.radius2_ = 0; + this.colors_ = []; + this.focus_ = {x: 0, y: 0}; + } + + CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { + aColor = processStyle(aColor); + this.colors_.push({offset: 1-aOffset, color: aColor}); + }; + + function CanvasPattern_() {} + + // set up externs + G_vmlCanvasManager = G_vmlCanvasManager_; + CanvasRenderingContext2D = CanvasRenderingContext2D_; + CanvasGradient = CanvasGradient_; + CanvasPattern = CanvasPattern_; + +})(); + +} // if diff --git a/lib/box2d-js/lib/prototype-1.6.0.2.js b/lib/box2d-js/lib/prototype-1.6.0.2.js new file mode 100644 index 0000000..6385503 --- /dev/null +++ b/lib/box2d-js/lib/prototype-1.6.0.2.js @@ -0,0 +1,4221 @@ +/* Prototype JavaScript framework, version 1.6.0.2 + * (c) 2005-2008 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.0.2', + + Browser: { + IE: !!(window.attachEvent && !window.opera), + Opera: !!window.opera, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + document.createElement('div').__proto__ && + document.createElement('div').__proto__ !== + document.createElement('form').__proto__ + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; + } +}; + +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value, value = Object.extend((function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method), { + valueOf: function() { return method }, + toString: function() { return method.toString() } + }); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return object && object.nodeType == 1; + }, + + isArray: function(object) { + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Function.prototype.defer = Function.prototype.delay.curry(0.01); + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = new Element('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +}; + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + iterator = iterator.bind(context); + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + }, + + all: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + iterator = iterator.bind(context); + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator(value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator, context) { + iterator = iterator.bind(context); + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + iterator = iterator.bind(context); + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +}; + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + filter: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +if (Prototype.Browser.WebKit) { + $A = function(iterable) { + if (!iterable) return []; + if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && + iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; + }; +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (Object.isArray(arguments[i])) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + }; +} +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, + + _each: function(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + return Object.clone(this._object); + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return values.map(toQueryPair.curry(key)).join('&'); + } + return toQueryPair(key, values); + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); +}).call(window); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); + return element; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).select("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? element.descendants()[expression] : + element.select(expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + var originalAncestor = ancestor; + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (element.sourceIndex && !Prototype.Browser.Opera) { + var e = element.sourceIndex, a = ancestor.sourceIndex, + nextAncestor = ancestor.nextSibling; + if (!nextAncestor) { + do { ancestor = ancestor.parentNode; } + while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); + } + if (nextAncestor && nextAncestor.sourceIndex) + return (e > a && e < nextAncestor.sourceIndex); + } + + while (element = element.parentNode) + if (element == originalAncestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + // returns '0px' for hidden elements; we want it to return null + if (!Element.visible(element)) return null; + + // returns the border-box dimensions rather than the content-box + // dimensions, so we subtract padding and borders from the value + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if ('outerHTML' in document.createElement('div')) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
', 1], + TBODY: ['', '
', 2], + TR: ['', '
', 3], + TD: ['
', '
', 4], + SELECT: ['', 1] + } +}; + +(function() { + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD + }); +}).call(Element._insertionTranslations); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div').__proto__) { + window.HTMLElement = { }; + window.HTMLElement.prototype = document.createElement('div').__proto__; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + +document.viewport = { + getDimensions: function() { + var dimensions = { }; + var B = Prototype.Browser; + $w('width height').each(function(d) { + var D = d.capitalize(); + dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : + (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + }); + return dimensions; + }, + + getWidth: function() { + return this.getDimensions().width; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + return false; + + return true; + }, + + compileMatcher: function() { + if (this.shouldUseXPath()) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'checked': "[@checked]", + 'disabled': "[@disabled]", + 'enabled': "[not(@disabled)]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, + attrPresence: /^\[([\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv.startsWith(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }, + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, index) { + if (Object.isUndefined(index)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, value, single = !Object.isArray(index); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + value = this.optionValue(opt); + if (single) { + if (value == index) { + opt.selected = true; + return; + } + } + else opt.selected = index.include(value); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) var Event = { }; + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: { }, + + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, + + element: function(event) { + var node = Event.extend(event).target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + }, + + findElement: function(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + }, + + pointer: function(event) { + return { + x: event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)), + y: event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._prototypeEventID) return element._prototypeEventID[0]; + arguments.callee.id = arguments.callee.id || 1; + return element._prototypeEventID = [++arguments.callee.id]; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event); + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + document.loaded = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write(" + * 2) define style rules. See the example page for examples. + * 3) mark the
 and  tags in your source with class=prettyprint.
+ *    You can also use the (html deprecated)  tag, but the pretty printer
+ *    needs to do more substantial DOM manipulations to support that, so some
+ *    css styles may not be preserved.
+ * That's it.  I wanted to keep the API as simple as possible, so there's no
+ * need to specify which language the code is in.
+ *
+ * Change log:
+ * cbeust, 2006/08/22
+ *   Java annotations (start with "@") are now captured as literals ("lit")
+ */
+
+var PR_keywords = {};
+/** initialize the keyword list for our target languages. */
+(function () {
+  var CPP_KEYWORDS = "abstract bool break case catch char class const " +
+    "const_cast continue default delete deprecated dllexport dllimport do " +
+    "double dynamic_cast else enum explicit extern false float for friend " +
+    "goto if inline int long mutable naked namespace new noinline noreturn " +
+    "nothrow novtable operator private property protected public register " +
+    "reinterpret_cast return selectany short signed sizeof static " +
+    "static_cast struct switch template this thread throw true try typedef " +
+    "typeid typename union unsigned using declaration, directive uuid " +
+    "virtual void volatile while typeof";
+  var CSHARP_KEYWORDS = "as base by byte checked decimal delegate descending " +
+    "event finally fixed foreach from group implicit in interface internal " +
+    "into is lock null object out override orderby params readonly ref sbyte " +
+    "sealed stackalloc string select uint ulong unchecked unsafe ushort var";
+  var JAVA_KEYWORDS = "package synchronized boolean implements import throws " +
+    "instanceof transient extends final strictfp native super";
+  var JSCRIPT_KEYWORDS = "debugger export function with NaN Infinity";
+  var PERL_KEYWORDS = "require sub unless until use elsif BEGIN END";
+  var PYTHON_KEYWORDS = "and assert def del elif except exec global lambda " +
+    "not or pass print raise yield False True None";
+  var RUBY_KEYWORDS = "then end begin rescue ensure module when undef next " +
+    "redo retry alias defined";
+  var SH_KEYWORDS = "done fi";
+
+  var KEYWORDS = [CPP_KEYWORDS, CSHARP_KEYWORDS, JAVA_KEYWORDS,
+                  JSCRIPT_KEYWORDS, PERL_KEYWORDS, PYTHON_KEYWORDS,
+                  RUBY_KEYWORDS, SH_KEYWORDS];
+  for (var k = 0; k < KEYWORDS.length; k++) {
+    var kw = KEYWORDS[k].split(' ');
+    for (var i = 0; i < kw.length; i++) {
+      if (kw[i]) { PR_keywords[kw[i]] = true; }
+    }
+  }
+}).call(this);
+
+// token style names.  correspond to css classes
+/** token style for a string literal */
+var PR_STRING = 'str';
+/** token style for a keyword */
+var PR_KEYWORD = 'kwd';
+/** token style for a comment */
+var PR_COMMENT = 'com';
+/** token style for a type */
+var PR_TYPE = 'typ';
+/** token style for a literal value.  e.g. 1, null, true. */
+var PR_LITERAL = 'lit';
+/** token style for a punctuation string. */
+var PR_PUNCTUATION = 'pun';
+/** token style for a punctuation string. */
+var PR_PLAIN = 'pln';
+
+/** token style for an sgml tag. */
+var PR_TAG = 'tag';
+/** token style for a markup declaration such as a DOCTYPE. */
+var PR_DECLARATION = 'dec';
+/** token style for embedded source. */
+var PR_SOURCE = 'src';
+/** token style for an sgml attribute name. */
+var PR_ATTRIB_NAME = 'atn';
+/** token style for an sgml attribute value. */
+var PR_ATTRIB_VALUE = 'atv';
+
+/** the number of characters between tab columns */
+var PR_TAB_WIDTH = 8;
+
+function PR_isWordChar(ch) {
+  return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
+}
+
+function PR_isIE6() {
+  var isIE6 = navigator && navigator.userAgent
+      && /\bMSIE 6\./.test(navigator.userAgent);
+  PR_isIE6 = function () { return isIE6; }
+  return isIE6;
+}
+
+/** Splice one array into another.
+  * Like the python <code>
+  * container[containerPosition:containerPosition + countReplaced] = inserted
+  * </code>
+  * @param {Array} inserted
+  * @param {Array} container modified in place
+  * @param {Number} containerPosition
+  * @param {Number} countReplaced
+  */
+function PR_spliceArrayInto(
+    inserted, container, containerPosition, countReplaced) {
+  inserted.unshift(containerPosition, countReplaced || 0);
+  try {
+    container.splice.apply(container, inserted);
+  } finally {
+    inserted.splice(0, 2);
+  }
+}
+
+/** a set of tokens that can precede a regular expression literal in javascript.
+  * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
+  * list, but I've removed ones that might be problematic when seen in languages
+  * that don't support regular expression literals.
+  *
+  * <p>Specifically, I've removed any keywords that can't precede a regexp
+  * literal in a syntactically legal javascript program, and I've removed the
+  * "in" keyword since it's not a keyword in many languages, and might be used
+  * as a count of inches.
+  * @private
+  */
+var REGEXP_PRECEDER_PATTERN = (function () {
+    var preceders = [
+        "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
+        "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
+        "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
+        "<", "<<", "<<=", "<=", "=", "==", "===", ">",
+        ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
+        "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
+        "||=", "~", "break", "case", "continue", "delete",
+        "do", "else", "finally", "instanceof",
+        "return", "throw", "try", "typeof"
+        ];
+    var pattern = '(?:' +
+      '(?:(?:^|[^0-9\.])\\.{1,3})|' +  // a dot that's not part of a number
+      '(?:(?:^|[^\\+])\\+)|' +  // allow + but not ++
+      '(?:(?:^|[^\\-])-)'  // allow - but not --
+      ;
+    for (var i = 0; i < preceders.length; ++i) {
+      var preceder = preceders[i];
+      if (PR_isWordChar(preceder.charAt(0))) {
+        pattern += '|\\b' + preceder;
+      } else {
+        pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1');
+      }
+    }
+    pattern += '|^)\\s*$';  // matches at end, and matches empty string
+    return new RegExp(pattern);
+    // CAVEAT: this does not properly handle the case where a regular expression
+    // immediately follows another since a regular expression may have flags
+    // for case-sensitivity and the like.  Having regexp tokens adjacent is not
+    // valid in any language I'm aware of, so I'm punting.
+    // TODO: maybe style special characters inside a regexp as punctuation.
+  })();
+
+// Define regexps here so that the interpreter doesn't have to create an object
+// each time the function containing them is called.
+// The language spec requires a new object created even if you don't access the
+// $1 members.
+var pr_amp = /&/g;
+var pr_lt = /</g;
+var pr_gt = />/g;
+var pr_quot = /\"/g;
+/** like textToHtml but escapes double quotes to be attribute safe. */
+function PR_attribToHtml(str) {
+  return str.replace(pr_amp, '&amp;')
+    .replace(pr_lt, '&lt;')
+    .replace(pr_gt, '&gt;')
+    .replace(pr_quot, '&quot;');
+}
+
+/** escapest html special characters to html. */
+function PR_textToHtml(str) {
+  return str.replace(pr_amp, '&amp;')
+    .replace(pr_lt, '&lt;')
+    .replace(pr_gt, '&gt;');
+}
+
+
+var pr_ltEnt = /&lt;/g;
+var pr_gtEnt = /&gt;/g;
+var pr_aposEnt = /&apos;/g;
+var pr_quotEnt = /&quot;/g;
+var pr_ampEnt = /&amp;/g;
+/** unescapes html to plain text. */
+function PR_htmlToText(html) {
+  var pos = html.indexOf('&');
+  if (pos < 0) { return html; }
+  // Handle numeric entities specially.  We can't use functional substitution
+  // since that doesn't work in older versions of Safari.
+  // These should be rare since most browsers convert them to normal chars.
+  for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0;) {
+    var end = html.indexOf(';', pos);
+    if (end >= 0) {
+      var num = html.substring(pos + 3, end);
+      var radix = 10;
+      if (num && num.charAt(0) == 'x') {
+        num = num.substring(1);
+        radix = 16;
+      }
+      var codePoint = parseInt(num, radix);
+      if (!isNaN(codePoint)) {
+        html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
+                html.substring(end + 1));
+      }
+    }
+  }
+
+  return html.replace(pr_ltEnt, '<')
+    .replace(pr_gtEnt, '>')
+    .replace(pr_aposEnt, "'")
+    .replace(pr_quotEnt, '"')
+    .replace(pr_ampEnt, '&');
+}
+
+/** is the given node's innerHTML normally unescaped? */
+function PR_isRawContent(node) {
+  return 'XMP' == node.tagName;
+}
+
+var PR_innerHtmlWorks = null;
+function PR_getInnerHtml(node) {
+  // inner html is hopelessly broken in Safari 2.0.4 when the content is
+  // an html description of well formed XML and the containing tag is a PRE
+  // tag, so we detect that case and emulate innerHTML.
+  if (null === PR_innerHtmlWorks) {
+    var testNode = document.createElement('PRE');
+    testNode.appendChild(
+        document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));
+    PR_innerHtmlWorks = !/</.test(testNode.innerHTML);
+  }
+
+  if (PR_innerHtmlWorks) {
+    var content = node.innerHTML;
+    // XMP tags contain unescaped entities so require special handling.
+    if (PR_isRawContent(node)) {
+      content = PR_textToHtml(content);
+    }
+    return content;
+  }
+
+  var out = [];
+  for (var child = node.firstChild; child; child = child.nextSibling) {
+    PR_normalizedHtml(child, out);
+  }
+  return out.join('');
+}
+
+/** walks the DOM returning a properly escaped version of innerHTML.
+  */
+function PR_normalizedHtml(node, out) {
+  switch (node.nodeType) {
+    case 1:  // an element
+      var name = node.tagName.toLowerCase();
+      out.push('\074', name);
+      for (var i = 0; i < node.attributes.length; ++i) {
+        var attr = node.attributes[i];
+        if (!attr.specified) { continue; }
+        out.push(' ');
+        PR_normalizedHtml(attr, out);
+      }
+      out.push('>');
+      for (var child = node.firstChild; child; child = child.nextSibling) {
+        PR_normalizedHtml(child, out);
+      }
+      if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
+        out.push('<\/', name, '>');
+      }
+      break;
+    case 2: // an attribute
+      out.push(node.name.toLowerCase(), '="', PR_attribToHtml(node.value), '"');
+      break;
+    case 3: case 4: // text
+      out.push(PR_textToHtml(node.nodeValue));
+      break;
+  }
+}
+
+/** returns a function that expand tabs to spaces.  This function can be fed
+  * successive chunks of text, and will maintain its own internal state to
+  * keep track of how tabs are expanded.
+  * @return {function (plainText : String) : String } a function that takes
+  *   plain text and return the text with tabs expanded.
+  * @private
+  */
+function PR_tabExpander(tabWidth) {
+  var SPACES = '                ';
+  var charInLine = 0;
+
+  return function (plainText) {
+    // walk over each character looking for tabs and newlines.
+    // On tabs, expand them.  On newlines, reset charInLine.
+    // Otherwise increment charInLine
+    var out = null;
+    var pos = 0;
+    for (var i = 0, n = plainText.length; i < n; ++i) {
+      var ch = plainText.charAt(i);
+
+      switch (ch) {
+        case '\t':
+          if (!out) { out = []; }
+          out.push(plainText.substring(pos, i));
+          // calculate how much space we need in front of this part
+          // nSpaces is the amount of padding -- the number of spaces needed to
+          // move us to the next column, where columns occur at factors of
+          // tabWidth.
+          var nSpaces = tabWidth - (charInLine % tabWidth);
+          charInLine += nSpaces;
+          for (; nSpaces >= 0; nSpaces -= SPACES.length) {
+            out.push(SPACES.substring(0, nSpaces));
+          }
+          pos = i + 1;
+          break;
+        case '\n':
+          charInLine = 0;
+          break;
+        default:
+          ++charInLine;
+      }
+    }
+    if (!out) { return plainText; }
+    out.push(plainText.substring(pos));
+    return out.join('');
+  };
+}
+
+// The below pattern matches one of the following
+// (1) /[^<]+/ : A run of characters other than '<'
+// (2) /<!--.*?-->/: an HTML comment
+// (3) /<!\[CDATA\[.*?\]\]>/: a cdata section
+// (3) /<\/?[a-zA-Z][^>]*>/ : A probably tag that should not be highlighted
+// (4) /</ : A '<' that does not begin a larger chunk.  Treated as 1
+var pr_chunkPattern =
+  /(?:[^<]+|<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g;
+var pr_commentPrefix = /^<!--/;
+var pr_cdataPrefix = /^<\[CDATA\[/;
+var pr_brPrefix = /^<br\b/i;
+
+/** split markup into chunks of html tags (style null) and
+  * plain text (style {@link #PR_PLAIN}), converting tags which are significant
+  * for tokenization (<br>) into their textual equivalent.
+  *
+  * @param {String} s html where whitespace is considered significant.
+  * @return {Object} source code and extracted tags.
+  * @private
+  */
+function PR_extractTags(s) {
+
+  // since the pattern has the 'g' modifier and defines no capturing groups,
+  // this will return a list of all chunks which we then classify and wrap as
+  // PR_Tokens
+  var matches = s.match(pr_chunkPattern);
+  var sourceBuf = [];
+  var sourceBufLen = 0;
+  var extractedTags = [];
+  if (matches) {
+    for (var i = 0, n = matches.length; i < n; ++i) {
+      var match = matches[i];
+      if (match.length > 1 && match.charAt(0) === '<') {
+        if (pr_commentPrefix.test(match)) { continue; }
+        if (pr_cdataPrefix.test(match)) {
+          // strip CDATA prefix and suffix.  Don't unescape since it's CDATA
+          sourceBuf.push(match.substring(9, match.length - 3));
+          sourceBufLen += match.length - 12;
+        } else if (pr_brPrefix.test(match)) {
+          // <br> tags are lexically significant so convert them to text.
+          // This is undone later.
+          // <br> tags are lexically significant
+          sourceBuf.push('\n');
+          sourceBufLen += 1;
+        } else {
+          extractedTags.push(sourceBufLen, match);
+        }
+      } else {
+        var literalText = PR_htmlToText(match);
+        sourceBuf.push(literalText);
+        sourceBufLen += literalText.length;
+      }
+    }
+  }
+  return { source: sourceBuf.join(''), tags: extractedTags };
+}
+
+/** Given triples of [style, pattern, context] returns a lexing function,
+  * The lexing function interprets the patterns to find token boundaries and
+  * returns a decoration list of the form
+  * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
+  * where index_n is an index into the sourceCode, and style_n is a style
+  * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
+  * all characters in sourceCode[index_n-1:index_n].
+  *
+  * The stylePatterns is a list whose elements have the form
+  * [style : String, pattern : RegExp, context : RegExp, shortcut : String].
+  &
+  * Style is a style constant like PR_PLAIN.
+  *
+  * Pattern must only match prefixes, and if it matches a prefix and context is
+  * null or matches the last non-comment token parsed, then that match is
+  * considered a token with the same style.
+  *
+  * Context is applied to the last non-whitespace, non-comment token recognized.
+  *
+  * Shortcut is an optional string of characters, any of which, if the first
+  * character, gurantee that this pattern and only this pattern matches.
+  *
+  * @param {Array} shortcutStylePatterns patterns that always start with
+  *   a known character.  Must have a shortcut string.
+  * @param {Array} fallthroughStylePatterns patterns that will be tried in order
+  *   if the shortcut ones fail.  May have shortcuts.
+  *
+  * @return {function (sourceCode : String) -> Array.<Number|String>} a function
+  *   that takes source code and a list of decorations to append to.
+  */
+function PR_createSimpleLexer(shortcutStylePatterns,
+                              fallthroughStylePatterns) {
+  var shortcuts = {};
+  (function () {
+    var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
+    for (var i = allPatterns.length; --i >= 0;) {
+      var patternParts = allPatterns[i];
+      var shortcutChars = patternParts[3];
+      if (shortcutChars) {
+        for (var c = shortcutChars.length; --c >= 0;) {
+          shortcuts[shortcutChars.charAt(c)] = patternParts;
+        }
+      }
+    }
+  })();
+
+  var nPatterns = fallthroughStylePatterns.length;
+
+  return function (sourceCode, opt_basePos) {
+    opt_basePos = opt_basePos || 0;
+    var decorations = [opt_basePos, PR_PLAIN];
+    var lastToken = '';
+    var pos = 0;  // index into sourceCode
+    var tail = sourceCode;
+
+    while (tail.length) {
+      var style;
+      var token = null;
+
+      var patternParts = shortcuts[tail.charAt(0)];
+      if (patternParts) {
+        var match = tail.match(patternParts[1]);
+        token = match[0];
+        style = patternParts[0];
+      } else {
+        for (var i = 0; i < nPatterns; ++i) {
+          patternParts = fallthroughStylePatterns[i];
+          var contextPattern = patternParts[2];
+          if (contextPattern && !contextPattern.test(lastToken)) {
+            // rule can't be used
+            continue;
+          }
+          var match = tail.match(patternParts[1]);
+          if (match) {
+            token = match[0];
+            style = patternParts[0];
+            break;
+          }
+        }
+
+        if (!token) {  // make sure that we make progress
+          style = PR_PLAIN;
+          token = tail.substring(0, 1);
+        }
+      }
+
+      decorations.push(opt_basePos + pos, style);
+      pos += token.length;
+      tail = tail.substring(token.length);
+      if (style !== PR_COMMENT && /\S/.test(token)) { lastToken = token; }
+    }
+    return decorations;
+  };
+}
+
+var PR_C_STYLE_STRING_AND_COMMENT_LEXER = PR_createSimpleLexer([
+    [PR_STRING,  /^\'(?:[^\\\']|\\[\s\S])*(?:\'|$)/, null, "'"],
+    [PR_STRING,  /^\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)/, null, '"'],
+    [PR_STRING,  /^\`(?:[^\\\`]|\\[\s\S])*(?:\`|$)/, null, '`']
+    ], [
+    [PR_PLAIN,   /^(?:[^\'\"\`\/\#]+)/, null, ' \r\n'],
+    [PR_COMMENT, /^#[^\r\n]*/, null, '#'],
+    [PR_COMMENT, /^\/\/[^\r\n]*/, null],
+    [PR_STRING,  /^\/(?:[^\\\*\/]|\\[\s\S])+(?:\/|$)/, REGEXP_PRECEDER_PATTERN],
+    [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]
+    ]);
+/** splits the given string into comment, string, and "other" tokens.
+  * @param {String} sourceCode as plain text
+  * @return {Array.<Number|String>} a decoration list.
+  * @private
+  */
+function PR_splitStringAndCommentTokens(sourceCode) {
+  return PR_C_STYLE_STRING_AND_COMMENT_LEXER(sourceCode);
+}
+
+var PR_C_STYLE_LITERAL_IDENTIFIER_PUNC_RECOGNIZER = PR_createSimpleLexer([], [
+    [PR_PLAIN,       /^\s+/, null, ' \r\n'],
+    // TODO(mikesamuel): recognize non-latin letters and numerals in identifiers
+    [PR_PLAIN,       /^[a-z_$@][a-z_$@0-9]*/i, null],
+    // A hex number
+    [PR_LITERAL,     /^0x[a-f0-9]+[a-z]/i, null],
+    // An octal or decimal number, possibly in scientific notation
+    [PR_LITERAL,     /^(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?[a-z]*/i,
+     null, '123456789'],
+    [PR_PUNCTUATION, /^[^\s\w\.$@]+/, null]
+    // Fallback will handle decimal points not adjacent to a digit
+    ]);
+
+/** splits plain text tokens into more specific tokens, and then tries to
+  * recognize keywords, and types.
+  * @private
+  */
+function PR_splitNonStringNonCommentTokens(source, decorations) {
+  for (var i = 0; i < decorations.length; i += 2) {
+    var style = decorations[i + 1];
+    if (style === PR_PLAIN) {
+      var start = decorations[i];
+      var end = i + 2 < decorations.length ? decorations[i + 2] : source.length;
+      var chunk = source.substring(start, end);
+      var subDecs = PR_C_STYLE_LITERAL_IDENTIFIER_PUNC_RECOGNIZER(chunk, start);
+      for (var j = 0, m = subDecs.length; j < m; j += 2) {
+        var subStyle = subDecs[j + 1];
+        if (subStyle === PR_PLAIN) {
+          var subStart = subDecs[j];
+          var subEnd = j + 2 < m ? subDecs[j + 2] : chunk.length;
+          var token = source.substring(subStart, subEnd);
+          if (token == '.') {
+            subDecs[j + 1] = PR_PUNCTUATION;
+          } else if (token in PR_keywords) {
+            subDecs[j + 1] = PR_KEYWORD;
+          } else if (/^@?[A-Z][A-Z$]*[a-z][A-Za-z$]*$/.test(token)) {
+            // classify types and annotations using Java's style conventions
+            subDecs[j + 1] = token.charAt(0) == '@' ? PR_LITERAL : PR_TYPE;
+          }
+        }
+      }
+      PR_spliceArrayInto(subDecs, decorations, i, 2);
+      i += subDecs.length - 2;
+    }
+  }
+  return decorations;
+}
+
+var PR_MARKUP_LEXER = PR_createSimpleLexer([], [
+    [PR_PLAIN,       /^[^<]+/, null],
+    [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/, null],
+    [PR_COMMENT,     /^<!--[\s\S]*?(?:-->|$)/, null],
+    [PR_SOURCE,      /^<\?[\s\S]*?(?:\?>|$)/, null],
+    [PR_SOURCE,      /^<%[\s\S]*?(?:%>|$)/, null],
+    [PR_SOURCE,
+     // Tags whose content is not escaped, and which contain source code.
+     /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null],
+    [PR_TAG,         /^<\/?\w[^<>]*>/, null]
+    ]);
+// Splits any of the source|style|xmp entries above into a start tag,
+// source content, and end tag.
+var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/;
+/** split markup on tags, comments, application directives, and other top level
+  * constructs.  Tags are returned as a single token - attributes are not yet
+  * broken out.
+  * @private
+  */
+function PR_tokenizeMarkup(source) {
+  var decorations = PR_MARKUP_LEXER(source);
+  for (var i = 0; i < decorations.length; i += 2) {
+    if (decorations[i + 1] === PR_SOURCE) {
+      var start = decorations[i];
+      var end = i + 2 < decorations.length ? decorations[i + 2] : source.length;
+      // Split out start and end script tags as actual tags, and leave the body
+      // with style SCRIPT.
+      var sourceChunk = source.substring(start, end);
+      var match = (sourceChunk.match(PR_SOURCE_CHUNK_PARTS)
+                   //|| sourceChunk.match(/^(<[?%])([\s\S]*)([?%]>)$/)
+                   );
+      if (match) {
+        decorations.splice(
+            i, 2,
+            start, PR_TAG,  // the open chunk
+            start + match[1].length, PR_SOURCE,
+            start + match[1].length + (match[2] || '').length, PR_TAG);
+      }
+    }
+  }
+  return decorations;
+}
+
+var PR_TAG_LEXER = PR_createSimpleLexer([
+    [PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"],
+    [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'],
+    [PR_PUNCTUATION,  /^[<>\/=]+/, null, '<>/=']
+    ], [
+    [PR_TAG,          /^[\w:-]+/, /^</],
+    [PR_ATTRIB_VALUE, /^[\w-]+/, /^=/],
+    [PR_ATTRIB_NAME,  /^[\w:-]+/, null],
+    [PR_PLAIN,        /^\s+/, null, ' \t\r\n']
+    ]);
+/** split tags attributes and their values out from the tag name, and
+  * recursively lex source chunks.
+  * @private
+  */
+function PR_splitTagAttributes(source, decorations) {
+  for (var i = 0; i < decorations.length; i += 2) {
+    var style = decorations[i + 1];
+    if (style === PR_TAG) {
+      var start = decorations[i];
+      var end = i + 2 < decorations.length ? decorations[i + 2] : source.length;
+      var chunk = source.substring(start, end);
+      var subDecorations = PR_TAG_LEXER(chunk, start);
+      PR_spliceArrayInto(subDecorations, decorations, i, 2);
+      i += subDecorations.length - 2;
+    }
+  }
+  return decorations;
+}
+
+/** identify regions of markup that are really source code, and recursivley
+  * lex them.
+  * @private
+  */
+function PR_splitSourceNodes(source, decorations) {
+  for (var i = 0; i < decorations.length; i += 2) {
+    var style = decorations[i + 1];
+    if (style == PR_SOURCE) {
+      // Recurse using the non-markup lexer
+      var start = decorations[i];
+      var end = i + 2 < decorations.length ? decorations[i + 2] : source.length;
+      var subDecorations = PR_decorateSource(source.substring(start, end));
+      for (var j = 0, m = subDecorations.length; j < m; j += 2) {
+        subDecorations[j] += start;
+      }
+      PR_spliceArrayInto(subDecorations, decorations, i, 2);
+      i += subDecorations.length - 2;
+    }
+  }
+  return decorations;
+}
+
+/** identify attribute values that really contain source code and recursively
+  * lex them.
+  * @private
+  */
+function PR_splitSourceAttributes(source, decorations) {
+  var nextValueIsSource = false;
+  for (var i = 0; i < decorations.length; i += 2) {
+    var style = decorations[i + 1];
+    if (style === PR_ATTRIB_NAME) {
+      var start = decorations[i];
+      var end = i + 2 < decorations.length ? decorations[i + 2] : source.length;
+      nextValueIsSource = /^on|^style$/i.test(source.substring(start, end));
+    } else if (style == PR_ATTRIB_VALUE) {
+      if (nextValueIsSource) {
+        var start = decorations[i];
+        var end
+            = i + 2 < decorations.length ? decorations[i + 2] : source.length;
+        var attribValue = source.substring(start, end);
+        var attribLen = attribValue.length;
+        var quoted =
+            (attribLen >= 2 && /^[\"\']/.test(attribValue) &&
+             attribValue.charAt(0) === attribValue.charAt(attribLen - 1));
+
+        var attribSource;
+        var attribSourceStart;
+        var attribSourceEnd;
+        if (quoted) {
+          attribSourceStart = start + 1;
+          attribSourceEnd = end - 1;
+          attribSource = attribValue;
+        } else {
+          attribSourceStart = start + 1;
+          attribSourceEnd = end - 1;
+          attribSource = attribValue.substring(1, attribValue.length - 1);
+        }
+
+        var attribSourceDecorations = PR_decorateSource(attribSource);
+        for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) {
+          attribSourceDecorations[j] += attribSourceStart;
+        }
+
+        if (quoted) {
+          attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE);
+          PR_spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0);
+        } else {
+          PR_spliceArrayInto(attribSourceDecorations, decorations, i, 2);
+        }
+      }
+      nextValueIsSource = false;
+    }
+  }
+  return decorations;
+}
+
+/** returns a list of decorations, where even entries
+  *
+  * This code treats ", ', and ` as string delimiters, and \ as a string escape.
+  * It does not recognize perl's qq() style strings.  It has no special handling
+  * for double delimiter escapes as in basic, or tje tripled delimiters used in
+  * python, but should work on those regardless although in those cases a single
+  * string literal may be broken up into multiple adjacent string literals.
+  *
+  * It recognizes C, C++, and shell style comments.
+  *
+  * @param {String} sourceCode as plain text
+  * @return {Array.<String,Number>} a  decoration list
+  */
+function PR_decorateSource(sourceCode) {
+  // Split into strings, comments, and other.
+  // We do this because strings and comments are easily recognizable and can
+  // contain stuff that looks like other tokens, so we want to mark those early
+  // so we don't recurse into them.
+  var decorations = PR_splitStringAndCommentTokens(sourceCode);
+
+  // Split non comment|string tokens on whitespace and word boundaries
+  decorations = PR_splitNonStringNonCommentTokens(sourceCode, decorations);
+
+  return decorations;
+}
+
+/** returns a decoration list given a string of markup.
+  *
+  * This code recognizes a number of constructs.
+  * <!-- ... --> comment
+  * <!\w ... >   declaration
+  * <\w ... >    tag
+  * </\w ... >   tag
+  * <?...?>      embedded source
+  * <%...%>      embedded source
+  * &[#\w]...;   entity
+  *
+  * It does not recognizes %foo; doctype entities from  .
+  *
+  * It will recurse into any <style>, <script>, and on* attributes using
+  * PR_lexSource.
+  */
+function PR_decorateMarkup(sourceCode) {
+  // This function works as follows:
+  // 1) Start by splitting the markup into text and tag chunks
+  //    Input:  String s
+  //    Output: List<PR_Token> where style in (PR_PLAIN, null)
+  // 2) Then split the text chunks further into comments, declarations,
+  //    tags, etc.
+  //    After each split, consider whether the token is the start of an
+  //    embedded source section, i.e. is an open <script> tag.  If it is,
+  //    find the corresponding close token, and don't bother to lex in between.
+  //    Input:  List<String>
+  //    Output: List<PR_Token> with style in (PR_TAG, PR_PLAIN, PR_SOURCE, null)
+  // 3) Finally go over each tag token and split out attribute names and values.
+  //    Input:  List<PR_Token>
+  //    Output: List<PR_Token> where style in
+  //            (PR_TAG, PR_PLAIN, PR_SOURCE, NAME, VALUE, null)
+  var decorations = PR_tokenizeMarkup(sourceCode);
+  decorations = PR_splitTagAttributes(sourceCode, decorations);
+  decorations = PR_splitSourceNodes(sourceCode, decorations);
+  decorations = PR_splitSourceAttributes(sourceCode, decorations);
+  return decorations;
+}
+
+/**
+  * @param {String} sourceText plain text
+  * @param {Array.<Number|String>} extractedTags chunks of raw html preceded by
+  *   their position in sourceText in order.
+  * @param {Array.<Number|String> decorations style classes preceded by their
+  *   position in sourceText in order.
+  * @return {String} html
+  * @private
+  */
+function PR_recombineTagsAndDecorations(
+    sourceText, extractedTags, decorations) {
+  var html = [];
+  var outputIdx = 0;  // index past the last char in sourceText written to html
+
+  var openDecoration = null;
+  var currentDecoration = null;
+  var tagPos = 0;  // index into extractedTags
+  var decPos = 0;  // index into decorations
+  var tabExpander = PR_tabExpander(PR_TAB_WIDTH);
+
+  // A helper function that is responsible for opening sections of decoration
+  // and outputing properly escaped chunks of source
+  function emitTextUpTo(sourceIdx) {
+    if (sourceIdx > outputIdx) {
+      if (openDecoration && openDecoration !== currentDecoration) {
+        // Close the current decoration
+        html.push('</span>');
+        openDecoration = null;
+      }
+      if (!openDecoration && currentDecoration) {
+        openDecoration = currentDecoration;
+        html.push('<span class="', openDecoration, '">');
+      }
+      // This interacts badly with some wikis which introduces paragraph tags
+      // into pre blocks for some strange reason.
+      // It's necessary for IE though which seems to lose the preformattednes
+
+      // of <pre> tags when their innerHTML is assigned.
+      // http://stud3.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
+      // and it serves to undo the conversion of <br>s to newlines done in
+      // chunkify.
+      var htmlChunk = PR_textToHtml(
+          tabExpander(sourceText.substring(outputIdx, sourceIdx)))
+          .replace(/(\r\n?|\n| ) /g, '$1&nbsp;')
+          .replace(/\r\n?|\n/g, '<br />');
+      html.push(htmlChunk);
+      outputIdx = sourceIdx;
+    }
+  }
+
+  while (true) {
+    // Determine if we're going to consume a tag this time around.  Otherwise we
+    // consume a decoration or exit.
+    var outputTag;
+    if (tagPos < extractedTags.length) {
+      if (decPos < decorations.length) {
+        // Pick one giving preference to extractedTags since we shouldn't open
+        // a new style that we're going to have to immediately close in order
+        // to output a tag.
+        outputTag = extractedTags[tagPos] <= decorations[decPos];
+      } else {
+        outputTag = true;
+      }
+    } else {
+      outputTag = false;
+    }
+    // Consume either a decoration or a tag or exit.
+    if (outputTag) {
+      emitTextUpTo(extractedTags[tagPos]);
+      if (openDecoration) {
+        // Close the current decoration
+        html.push('</span>');
+        openDecoration = null;
+      }
+      html.push(extractedTags[tagPos + 1]);
+      tagPos += 2;
+    } else if (decPos < decorations.length) {
+      emitTextUpTo(decorations[decPos]);
+      currentDecoration = decorations[decPos + 1];
+      decPos += 2;
+    } else {
+      break;
+    }
+  }
+  emitTextUpTo(sourceText.length);
+  if (openDecoration) {
+    html.push('</span>');
+  }
+
+  return html.join('');
+}
+
+/** pretty print a chunk of code.
+  *
+  * @param {String} sourceCodeHtml code as html
+  * @return {String} code as html, but prettier
+  */
+function prettyPrintOne(sourceCodeHtml) {
+  try {
+    // Extract tags, and convert the source code to plain text.
+    var sourceAndExtractedTags = PR_extractTags(sourceCodeHtml);
+    /** Plain text. @type {String} */
+    var source = sourceAndExtractedTags.source;
+
+    /** Even entries are positions in source in ascending order.  Odd entries
+      * are tags that were extracted at that position.
+      * @type {Array.<Number|String>}
+      */
+    var extractedTags = sourceAndExtractedTags.tags;
+
+    // Pick a lexer and apply it.
+    /** Treat it as markup if the first non whitespace character is a < and the
+      * last non-whitespace character is a >.
+      * @type {Boolean}
+      */
+    var isMarkup = /^\s*</.test(source) && />\s*$/.test(source);
+
+    /** Even entires are positions in source in ascending order.  Odd enties are
+      * style markers (e.g., PR_COMMENT) that run from that position until the
+      * end.
+      * @type {Array.<Number|String>}
+      */
+    var decorations = isMarkup
+        ? PR_decorateMarkup(source)
+        : PR_decorateSource(source);
+
+    // Integrate the decorations and tags back into the source code to produce
+    // a decorated html string.
+    return PR_recombineTagsAndDecorations(source, extractedTags, decorations);
+  } catch (e) {
+    if ('console' in window) {
+      console.log(e);
+      console.trace();
+    }
+    return sourceCodeHtml;
+  }
+}
+
+var PR_SHOULD_USE_CONTINUATION = true;
+/** find all the < pre > and < code > tags in the DOM with class=prettyprint and
+  * prettify them.
+  * @param {Function} opt_whenDone if specified, called when the last entry
+  *     has been finished.
+  */
+function prettyPrint(opt_whenDone) {
+  var isIE6 = PR_isIE6();
+  
+  // fetch a list of nodes to rewrite
+  var codeSegments = [
+      document.getElementsByTagName('pre'),
+      document.getElementsByTagName('code'),
+      document.getElementsByTagName('xmp') ];
+  var elements = [];
+  for (var i = 0; i < codeSegments.length; ++i) {
+    for (var j = 0; j < codeSegments[i].length; ++j) {
+      elements.push(codeSegments[i][j]);
+    }
+  }
+  codeSegments = null;
+
+  // the loop is broken into a series of continuations to make sure that we
+  // don't make the browser unresponsive when rewriting a large page.
+  var k = 0;
+
+  function doWork() {
+    var endTime = (PR_SHOULD_USE_CONTINUATION
+                   ? new Date().getTime() + 250
+                   : Infinity);
+    for (; k < elements.length && new Date().getTime() < endTime; k++) {
+      var cs = elements[k];
+      if (cs.className && cs.className.indexOf('prettyprint') >= 0) {
+
+        // make sure this is not nested in an already prettified element
+        var nested = false;
+        for (var p = cs.parentNode; p != null; p = p.parentNode) {
+          if ((p.tagName == 'pre' || p.tagName == 'code' ||
+               p.tagName == 'xmp') &&
+              p.className && p.className.indexOf('prettyprint') >= 0) {
+            nested = true;
+            break;
+          }
+        }
+        if (!nested) {
+          // fetch the content as a snippet of properly escaped HTML.
+          // Firefox adds newlines at the end.
+          var content = PR_getInnerHtml(cs);
+          content = content.replace(/(?:\r\n?|\n)$/, '');
+
+          // do the pretty printing
+          var newContent = prettyPrintOne(content);
+
+          // push the prettified html back into the tag.
+          if (!PR_isRawContent(cs)) {
+            // just replace the old html with the new
+            cs.innerHTML = newContent;
+          } else {
+            // we need to change the tag to a <pre> since <xmp>s do not allow
+            // embedded tags such as the span tags used to attach styles to
+            // sections of source code.
+            var pre = document.createElement('PRE');
+            for (var i = 0; i < cs.attributes.length; ++i) {
+              var a = cs.attributes[i];
+              if (a.specified) {
+                pre.setAttribute(a.name, a.value);
+              }
+            }
+            pre.innerHTML = newContent;
+
+            // remove the old
+            cs.parentNode.replaceChild(pre, cs);
+            pre = cs;
+          }
+
+          // Replace <br>s with line-feeds so that copying and pasting works on
+          // IE 6.
+          // Doing this on other browsers breaks lots of stuff since \r\n is
+          // treated as two newlines on Firefox, and doing this also slows down
+          // rendering.
+          if (isIE6 && cs.tagName === 'PRE') {
+            var lineBreaks = cs.getElementsByTagName('br');
+            for (var i = lineBreaks.length; --i >= 0;) {
+              var lineBreak = lineBreaks[i];
+              lineBreak.parentNode.replaceChild(
+                  document.createTextNode('\r\n'), lineBreak);
+            }
+          }
+        }
+      }
+    }
+    if (k < elements.length) {
+      // finish up in a continuation
+      setTimeout(doWork, 250);
+    } else if (opt_whenDone) {
+      opt_whenDone();
+    }
+  }
+
+  doWork();
+}
diff --git a/lib/utils.js b/lib/utils.js
index 417f47b..c08f5c1 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,3 +1,12 @@
+function uuidv4() {
+    return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
+        (
+            c ^
+            (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
+        ).toString(16)
+    );
+}
+
 function loadJavaScript(path) {
     const script = document.createElement("script");
     script.src = path;
@@ -74,5 +83,21 @@ function cartesianToPolara(x, y) {
 
 // polar to cartesian
 function polarToCartesian(r, θ) {
-    return { x: r * cos(θ), y: r * sin(θ) };
+    return { x: r * cos(θ), y: -r * sin(θ) };
+}
+
+function drawRect(x, y, w, h, c = "bottom") {
+    push();
+    switch (c) {
+        case "top":
+            break;
+        case "left":
+            break;
+        case "right":
+            break;
+        default:
+            rect(x - w / 2, y - h, w, h);
+            break;
+    }
+    pop();
 }