From b868c2cb1b190d3c239df262121fd28c8df68307 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Fri, 11 Oct 2013 04:42:11 +0100 Subject: [PATCH] Started revamp of the Tilemap system. Also removed old 'Advanced Physics' and dropped in p2.js which is what I hope we'll eventually use. --- README.md | 1 + build/phaser.js | 122 +- examples/collision/transform.php | 8 + examples/js.php | 5 +- examples/tilemaps/wip1.php | 74 + examples/tweens/chained tweens.php | 26 +- src/gameobjects/RenderTexture.js | 3 +- src/loader/Cache.js | 97 +- src/loader/Loader.js | 29 +- src/physics/advanced/Body.js | 402 ------ src/physics/advanced/Collision.js | 382 ----- src/physics/advanced/Contact.js | 37 - src/physics/advanced/ContactSolver.js | 267 ---- src/physics/advanced/Joint.js | 76 - src/physics/advanced/Math.js | 817 ----------- src/physics/advanced/Shape.js | 47 - src/physics/advanced/Space.js | 797 ----------- src/physics/advanced/Util.js | 147 -- src/physics/advanced/collision/Broadphase.js | 43 + .../advanced/collision/GridBroadphase.js | 159 +++ .../advanced/collision/NaiveBroadphase.js | 47 + src/physics/advanced/collision/Nearphase.js | 1249 +++++++++++++++++ src/physics/advanced/collision/QuadTree.js | 376 +++++ .../advanced/collision/SAP1DBroadphase.js | 120 ++ .../advanced/constraints/Constraint.js | 42 + .../advanced/constraints/ContactEquation.js | 136 ++ .../constraints/DistanceConstraint.js | 62 + src/physics/advanced/constraints/Equation.js | 77 + .../advanced/constraints/FrictionEquation.js | 180 +++ .../constraints/PointToPointConstraint.js | 94 ++ .../constraints/PrismaticConstraint.js | 83 ++ .../constraints/RotationalVelocityEquation.js | 70 + src/physics/advanced/events/EventEmitter.js | 84 ++ src/physics/advanced/joints/Angle.js | 130 -- src/physics/advanced/joints/Distance.js | 522 ------- src/physics/advanced/joints/Mouse.js | 150 -- src/physics/advanced/joints/Prismatic.js | 241 ---- src/physics/advanced/joints/Revolute.js | 378 ----- src/physics/advanced/joints/Rope.js | 211 --- src/physics/advanced/joints/Weld.js | 306 ---- src/physics/advanced/joints/Wheel.js | 376 ----- .../advanced/material/ContactMaterial.js | 81 ++ src/physics/advanced/material/Material.js | 19 + src/physics/advanced/math/mat2.js | 10 + src/physics/advanced/math/polyk.js | 477 +++++++ src/physics/advanced/math/vec2.js | 122 ++ src/physics/advanced/objects/Body.js | 330 +++++ src/physics/advanced/objects/Spring.js | 181 +++ src/physics/advanced/p2.js | 37 + src/physics/advanced/shapes/Box.js | 19 - src/physics/advanced/shapes/Capsule.js | 39 + src/physics/advanced/shapes/Circle.js | 123 +- src/physics/advanced/shapes/Convex.js | 213 +++ src/physics/advanced/shapes/Line.js | 30 + src/physics/advanced/shapes/Particle.js | 22 + src/physics/advanced/shapes/Plane.js | 22 + src/physics/advanced/shapes/Poly.js | 254 ---- src/physics/advanced/shapes/Rectangle.js | 43 + src/physics/advanced/shapes/Segment.js | 170 --- src/physics/advanced/shapes/Shape.js | 90 ++ src/physics/advanced/shapes/Triangle.js | 12 - src/physics/advanced/solver/GSSolver.js | 190 +++ src/physics/advanced/solver/Island.js | 81 ++ src/physics/advanced/solver/IslandSolver.js | 159 +++ src/physics/advanced/solver/Solver.js | 65 + src/physics/advanced/utils/Utils.js | 25 + src/physics/advanced/world/World.js | 870 ++++++++++++ src/tilemap/Tile.js | 190 +++ src/tilemap/TilemapLayer.js | 623 +------- src/tilemap/TilemapParser.js | 120 ++ src/tilemap/Tileset.js | 50 + src/tween/Tween.js | 18 +- 72 files changed, 6704 insertions(+), 6454 deletions(-) create mode 100644 examples/tilemaps/wip1.php delete mode 100644 src/physics/advanced/Body.js delete mode 100644 src/physics/advanced/Collision.js delete mode 100644 src/physics/advanced/Contact.js delete mode 100644 src/physics/advanced/ContactSolver.js delete mode 100644 src/physics/advanced/Joint.js delete mode 100644 src/physics/advanced/Math.js delete mode 100644 src/physics/advanced/Shape.js delete mode 100644 src/physics/advanced/Space.js delete mode 100644 src/physics/advanced/Util.js create mode 100644 src/physics/advanced/collision/Broadphase.js create mode 100644 src/physics/advanced/collision/GridBroadphase.js create mode 100644 src/physics/advanced/collision/NaiveBroadphase.js create mode 100644 src/physics/advanced/collision/Nearphase.js create mode 100644 src/physics/advanced/collision/QuadTree.js create mode 100644 src/physics/advanced/collision/SAP1DBroadphase.js create mode 100644 src/physics/advanced/constraints/Constraint.js create mode 100644 src/physics/advanced/constraints/ContactEquation.js create mode 100644 src/physics/advanced/constraints/DistanceConstraint.js create mode 100644 src/physics/advanced/constraints/Equation.js create mode 100644 src/physics/advanced/constraints/FrictionEquation.js create mode 100644 src/physics/advanced/constraints/PointToPointConstraint.js create mode 100644 src/physics/advanced/constraints/PrismaticConstraint.js create mode 100644 src/physics/advanced/constraints/RotationalVelocityEquation.js create mode 100644 src/physics/advanced/events/EventEmitter.js delete mode 100644 src/physics/advanced/joints/Angle.js delete mode 100644 src/physics/advanced/joints/Distance.js delete mode 100644 src/physics/advanced/joints/Mouse.js delete mode 100644 src/physics/advanced/joints/Prismatic.js delete mode 100644 src/physics/advanced/joints/Revolute.js delete mode 100644 src/physics/advanced/joints/Rope.js delete mode 100644 src/physics/advanced/joints/Weld.js delete mode 100644 src/physics/advanced/joints/Wheel.js create mode 100644 src/physics/advanced/material/ContactMaterial.js create mode 100644 src/physics/advanced/material/Material.js create mode 100644 src/physics/advanced/math/mat2.js create mode 100644 src/physics/advanced/math/polyk.js create mode 100644 src/physics/advanced/math/vec2.js create mode 100644 src/physics/advanced/objects/Body.js create mode 100644 src/physics/advanced/objects/Spring.js create mode 100644 src/physics/advanced/p2.js delete mode 100644 src/physics/advanced/shapes/Box.js create mode 100644 src/physics/advanced/shapes/Capsule.js create mode 100644 src/physics/advanced/shapes/Convex.js create mode 100644 src/physics/advanced/shapes/Line.js create mode 100644 src/physics/advanced/shapes/Particle.js create mode 100644 src/physics/advanced/shapes/Plane.js delete mode 100644 src/physics/advanced/shapes/Poly.js create mode 100644 src/physics/advanced/shapes/Rectangle.js delete mode 100644 src/physics/advanced/shapes/Segment.js create mode 100644 src/physics/advanced/shapes/Shape.js delete mode 100644 src/physics/advanced/shapes/Triangle.js create mode 100644 src/physics/advanced/solver/GSSolver.js create mode 100644 src/physics/advanced/solver/Island.js create mode 100644 src/physics/advanced/solver/IslandSolver.js create mode 100644 src/physics/advanced/solver/Solver.js create mode 100644 src/physics/advanced/utils/Utils.js create mode 100644 src/physics/advanced/world/World.js create mode 100644 src/tilemap/Tile.js create mode 100644 src/tilemap/TilemapParser.js create mode 100644 src/tilemap/Tileset.js diff --git a/README.md b/README.md index 9cbd3c26..c2287ee4 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Version 1.0.7 (in progress in the dev branch) * World.randomX/Y now works with negative World.bounds values. * Added killOnComplete parameter to Animation.play. Really useful in situations where you want a Sprite to animate once then kill itself on complete, like an explosion effect. * Added Sprite.loadTexture(key, frame) which allows you to load a new texture set into an existing sprite rather than having to create a new sprite. +* Tweens .to will now always return the parent (thanks powerfear) * TODO: look at Sprite.crop (http://www.html5gamedevs.com/topic/1617-error-in-spritecrop/) diff --git a/build/phaser.js b/build/phaser.js index bdee7283..9dfe1d67 100644 --- a/build/phaser.js +++ b/build/phaser.js @@ -9,7 +9,7 @@ * * Phaser - http://www.phaser.io * -* v1.0.7 - Built at: Wed, 09 Oct 2013 17:16:16 +0100 +* v1.0.7 - Built at: Thu, 10 Oct 2013 16:51:46 +0100 * * By Richard Davey http://www.photonstorm.com @photonstorm * @@ -7340,10 +7340,21 @@ Phaser.Camera.prototype = { break; } + }, + + /** + * Move the camera focus on a display object instantly. + * @method Phaser.Camera#focusOn + * @param {any} displayObject - The display object to focus the camera on. Must have visible x/y properties. + */ + focusOn: function (displayObject) { + + this.setPosition(Math.round(displayObject.x - this.view.halfWidth), Math.round(displayObject.y - this.view.halfHeight)); + }, /** - * Move the camera focus to a location instantly. + * Move the camera focus on a location instantly. * @method Phaser.Camera#focusOnXY * @param {number} x - X position. * @param {number} y - Y position. @@ -16007,6 +16018,7 @@ Phaser.Sprite.prototype.preUpdate = function() { this.prevY = this.y; this.updateCache(); + this.updateAnimation(); // Re-run the camera visibility check if (this._cache.dirty) @@ -16065,7 +16077,10 @@ Phaser.Sprite.prototype.updateCache = function() { this._cache.dirty = true; } - // Frame updated? +} + +Phaser.Sprite.prototype.updateAnimation = function() { + if (this.currentFrame && this.currentFrame.uuid != this._cache.frameID) { this._cache.frameWidth = this.texture.frame.width; @@ -16119,6 +16134,47 @@ Phaser.Sprite.prototype.postUpdate = function() { } +Phaser.Sprite.prototype.loadTexture = function (key, frame) { + + this.key = key; + + if (key instanceof Phaser.RenderTexture) + { + this.currentFrame = this.game.cache.getTextureFrame(key.name); + } + else + { + if (key == null || this.game.cache.checkImageKey(key) == false) + { + key = '__default'; + } + + if (this.game.cache.isSpriteSheet(key)) + { + this.animations.loadFrameData(this.game.cache.getFrameData(key)); + + if (frame !== null) + { + if (typeof frame === 'string') + { + this.frameName = frame; + } + else + { + this.frame = frame; + } + } + } + else + { + this.currentFrame = this.game.cache.getFrame(key); + } + } + + this.updateAnimation(); + +} + Phaser.Sprite.prototype.deltaAbsX = function () { return (this.deltaX() > 0 ? this.deltaX() : -this.deltaX()); } @@ -16430,6 +16486,18 @@ Object.defineProperty(Phaser.Sprite.prototype, "inCamera", { }); +/** +* +* @returns {boolean} +*/ +Object.defineProperty(Phaser.Sprite.prototype, "worldX", { + + get: function () { + return 1; + } + +}); + /** * Get the input enabled state of this Sprite. * @returns {Description} @@ -22959,6 +23027,7 @@ Phaser.Tween.prototype = { */ pause: function () { this._paused = true; + this._pausedTime = this.game.time.now; }, /** @@ -22968,7 +23037,7 @@ Phaser.Tween.prototype = { */ resume: function () { this._paused = false; - this._startTime += this.game.time.pauseDuration; + this._startTime += (this.game.time.now - this._pausedTime); }, /** @@ -29081,6 +29150,7 @@ Phaser.Utils.Debug.prototype = { this.line('angle: ' + sprite.angle.toFixed(1) + ' rotation: ' + sprite.rotation.toFixed(1)); this.line('visible: ' + sprite.visible + ' in camera: ' + sprite.inCamera); this.line('body x: ' + sprite.body.x.toFixed(1) + ' y: ' + sprite.body.y.toFixed(1)); + this.stop(); // 0 = scaleX // 1 = skewY @@ -29131,6 +29201,7 @@ Phaser.Utils.Debug.prototype = { this.line('scaleY: ' + sprite.worldTransform[4]); this.line('transX: ' + sprite.worldTransform[2]); this.line('transY: ' + sprite.worldTransform[5]); + this.stop(); }, @@ -29160,6 +29231,49 @@ Phaser.Utils.Debug.prototype = { this.line('scaleY: ' + sprite.localTransform[4]); this.line('transX: ' + sprite.localTransform[2]); this.line('transY: ' + sprite.localTransform[5]); + this.stop(); + + }, + + renderSpriteCoords: function (sprite, x, y, color) { + + if (this.context == null) + { + return; + } + + color = color || 'rgb(255, 255, 255)'; + + this.start(x, y, color); + + this.line(sprite.name); + this.line('x: ' + sprite.x); + this.line('y: ' + sprite.y); + this.line('local x: ' + sprite.localTransform[2]); + this.line('local y: ' + sprite.localTransform[5]); + this.line('world x: ' + sprite.worldTransform[2]); + this.line('world y: ' + sprite.worldTransform[5]); + + this.stop(); + + }, + + renderGroupInfo: function (group, x, y, color) { + + if (this.context == null) + { + return; + } + + color = color || 'rgb(255, 255, 255)'; + + this.start(x, y, color); + + this.line('Group (size: ' + group.length + ')'); + this.line('x: ' + group.x); + this.line('y: ' + group.y); + + this.stop(); }, diff --git a/examples/collision/transform.php b/examples/collision/transform.php index c87dbf97..b33b677c 100644 --- a/examples/collision/transform.php +++ b/examples/collision/transform.php @@ -13,12 +13,14 @@ game.load.image('atari', 'assets/sprites/atari130xe.png'); game.load.image('mushroom', 'assets/sprites/mushroom2.png'); + game.load.image('flectrum', 'assets/sprites/flectrum.png'); } var testGroup; var sprite1; var sprite2; + var sprite3; function create() { @@ -61,6 +63,9 @@ sprite2 = game.add.sprite(-100, 150, 'mushroom'); sprite2.name = 'mushroom'; + sprite3 = game.add.sprite(-200, 150, 'flectrum'); + sprite3.name = 'tall'; + testGroup.x = -600; testGroup.y = 200; @@ -88,6 +93,8 @@ game.physics.collide(sprite1, sprite2, collisionHandler, null, this); + // sprite3.angle += 0.5; + } function collisionHandler (obj1, obj2) { @@ -110,6 +117,7 @@ game.debug.renderSpriteBody(sprite1); game.debug.renderSpriteBody(sprite2); + game.debug.renderSpriteBody(sprite3); game.debug.renderGroupInfo(testGroup, 500, 500); game.debug.renderPixel(testGroup.x, testGroup.y, 'rgb(255,255,0)'); diff --git a/examples/js.php b/examples/js.php index 807181eb..e41fb353 100644 --- a/examples/js.php +++ b/examples/js.php @@ -114,9 +114,10 @@ + - - + + diff --git a/examples/tilemaps/wip1.php b/examples/tilemaps/wip1.php new file mode 100644 index 00000000..2b429fb0 --- /dev/null +++ b/examples/tilemaps/wip1.php @@ -0,0 +1,74 @@ + + + + + \ No newline at end of file diff --git a/examples/tweens/chained tweens.php b/examples/tweens/chained tweens.php index 000e88ec..8b2acf68 100644 --- a/examples/tweens/chained tweens.php +++ b/examples/tweens/chained tweens.php @@ -5,15 +5,14 @@ diff --git a/src/gameobjects/RenderTexture.js b/src/gameobjects/RenderTexture.js index e9d154cd..3a213106 100644 --- a/src/gameobjects/RenderTexture.js +++ b/src/gameobjects/RenderTexture.js @@ -39,7 +39,8 @@ Phaser.RenderTexture = function (game, key, width, height) { */ this.height = height || 100; - /** I know this has a typo in it, but it's because the PIXI.RenderTexture does and we need to pair-up with it + /** + * I know this has a typo in it, but it's because the PIXI.RenderTexture does and we need to pair-up with it * once they update pixi to fix the typo, we'll fix it here too :) * @property {Description} indetityMatrix - Description. */ diff --git a/src/loader/Cache.js b/src/loader/Cache.js index 436a1607..b350718e 100644 --- a/src/loader/Cache.js +++ b/src/loader/Cache.js @@ -56,6 +56,11 @@ Phaser.Cache = function (game) { */ this._tilemaps = {}; + /** + * @property {object} _tilesets - Tileset key-value container. + * @private + */ + this._tilesets = {}; this.addDefaultImage(); @@ -118,6 +123,28 @@ Phaser.Cache.prototype = { }, + /** + * Add a new tile set in to the cache. + * + * @method Phaser.Cache#addTileset + * @param {string} key - The unique key by which you will reference this object. + * @param {string} url - URL of this tile set file. + * @param {object} data - Extra tile set data. + * @param {number} tileWidth - Width of the sprite sheet. + * @param {number} tileHeight - Height of the sprite sheet. + * @param {number} tileMax - How many tiles stored in the sprite sheet. + */ + addTileset: function (key, url, data, tileWidth, tileHeight, tileMax) { + + this._tilesets[key] = { url: url, data: data, tileWidth: tileWidth, tileHeight: tileHeight }; + + PIXI.BaseTextureCache[key] = new PIXI.BaseTexture(data); + PIXI.TextureCache[key] = new PIXI.Texture(PIXI.BaseTextureCache[key]); + + this._tilesets[key].tileData = Phaser.TilemapParser.tileset(this.game, key, tileWidth, tileHeight, tileMax); + + }, + /** * Add a new tilemap. * @@ -208,6 +235,23 @@ Phaser.Cache.prototype = { }, + /** + * Add a new text data. + * + * @method Phaser.Cache#addText + * @param {string} key - Asset key for the text data. + * @param {string} url - URL of this text data file. + * @param {object} data - Extra text data. + */ + addText: function (key, url, data) { + + this._text[key] = { + url: url, + data: data + }; + + }, + /** * Add a new image. * @@ -316,23 +360,6 @@ Phaser.Cache.prototype = { this._sounds[key].decoded = true; this._sounds[key].isDecoding = false; - }, - - /** - * Add a new text data. - * - * @method Phaser.Cache#addText - * @param {string} key - Asset key for the text data. - * @param {string} url - URL of this text data file. - * @param {object} data - Extra text data. - */ - addText: function (key, url, data) { - - this._text[key] = { - url: url, - data: data - }; - }, /** @@ -387,6 +414,42 @@ Phaser.Cache.prototype = { return null; }, + /** + * Get tile set image data by key. + * + * @method Phaser.Cache#getTileSetImage + * @param {string} key - Asset key of the image you want. + * @return {object} The image data you want. + */ + getTilesetImage: function (key) { + + if (this._tilesets[key]) + { + return this._tilesets[key].data; + } + + return null; + + }, + + /** + * Get tile set image data by key. + * + * @method Phaser.Cache#getTileset + * @param {string} key - Asset key of the image you want. + * @return {Phaser.Tileset} The tileset data. The tileset image is in the data property, the tile data in tileData. + */ + getTileset: function (key) { + + if (this._tilesets[key]) + { + return this._tilesets[key]; + } + + return null; + + }, + /** * Get tilemap data by key. * diff --git a/src/loader/Loader.js b/src/loader/Loader.js index 733d5f8b..33ec4920 100644 --- a/src/loader/Loader.js +++ b/src/loader/Loader.js @@ -280,7 +280,7 @@ Phaser.Loader.prototype = { * @param {string} url - URL of the sheet file. * @param {number} frameWidth - Width of each single frame. * @param {number} frameHeight - Height of each single frame. - * @param {number} frameMax - How many frames in this sprite sheet. + * @param {number} [frameMax=-1] - How many frames in this sprite sheet. If not specified it will divide the whole image into frames. */ spritesheet: function (key, url, frameWidth, frameHeight, frameMax) { @@ -293,6 +293,27 @@ Phaser.Loader.prototype = { }, + /** + * Add a new tile set to the loader. These are used in the rendering of tile maps. + * + * @method Phaser.Loader#tileset + * @param {string} key - Unique asset key of the tileset file. + * @param {string} url - URL of the tileset. + * @param {number} tileWidth - Width of each single tile in pixels. + * @param {number} tileHeight - Height of each single tile in pixels. + * @param {number} [tileMax=-1] - How many tiles in this tileset. If not specified it will divide the whole image into tiles. + */ + tileset: function (key, url, tileWidth, tileHeight, tileMax) { + + if (typeof tileMax === "undefined") { tileMax = -1; } + + if (this.checkKeyExists(key) === false) + { + this.addToFileList('tileset', key, url, { tileWidth: tileWidth, tileHeight: tileHeight, tileMax: tileMax }); + } + + }, + /** * Add a new audio file to the loader. * @@ -617,6 +638,7 @@ Phaser.Loader.prototype = { case 'textureatlas': case 'bitmapfont': case 'tilemap': + case 'tileset': file.data = new Image(); file.data.name = file.key; file.data.onload = function () { @@ -771,6 +793,11 @@ Phaser.Loader.prototype = { this.game.cache.addSpriteSheet(file.key, file.url, file.data, file.frameWidth, file.frameHeight, file.frameMax); break; + case 'tileset': + + this.game.cache.addTileset(file.key, file.url, file.data, file.tileWidth, file.tileHeight, file.tileMax); + break; + case 'tilemap': if (file.mapDataURL == null) diff --git a/src/physics/advanced/Body.js b/src/physics/advanced/Body.js deleted file mode 100644 index 03dbcba0..00000000 --- a/src/physics/advanced/Body.js +++ /dev/null @@ -1,402 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -Body = function(type, pos, angle) { - if (Body.id_counter == undefined) { - Body.id_counter = 0; - } - - this.id = Body.id_counter++; - - // Identifier - this.name = "body" + this.id; - - // STATIC or DYNAMIC - this.type = type; - - // Default values - pos = pos || new vec2(0, 0); - angle = angle || 0; - - // Local to world transform - this.xf = new Transform(pos, angle); - - // Local center of mass - this.centroid = new vec2(0, 0); - - // World position of centroid - this.p = new vec2(pos.x, pos.y); - - // Velocity - this.v = new vec2(0, 0); - - // Force - this.f = new vec2(0, 0); - - // Orientation (angle) - this.a = angle; - - // Angular velocity - this.w = 0; - - // Torque - this.t = 0; - - // Linear damping - this.linearDamping = 0; - - // Angular damping - this.angularDamping = 0; - - // Sleep time - this.sleepTime = 0; - - // Awaked flag - this.awaked = false; - - // Shape list for this body - this.shapeArr = []; - - // Joint hash for this body - this.jointArr = []; - this.jointHash = {}; - - // Bounds of all shapes - this.bounds = new Bounds; - - this.fixedRotation = false; - - this.categoryBits = 0x0001; - this.maskBits = 0xFFFF; - - this.stepCount = 0; -} - -Body.STATIC = 0; -Body.KINETIC = 1; -Body.DYNAMIC = 2; - -Body.prototype.duplicate = function() { - var body = new Body(this.type, this.xf.t, this.a); - for (var i = 0; i < this.shapeArr.length; i++) { - body.addShape(this.shapeArr[i].duplicate()); - } - body.resetMassData(); - - return body; -} - -Body.prototype.serialize = function() { - var shapes = []; - for (var i = 0; i < this.shapeArr.length; i++) { - var obj = this.shapeArr[i].serialize(); - shapes.push(obj); - } - - return { - "type": ["static", "kinetic", "dynamic"][this.type], - "name": this.name, - "position": this.xf.t, - "angle": this.xf.a, - "shapes": shapes - }; -} - -Body.prototype.toString = function() { - return "[{Body (name=" + this.name + " velocity=" + this.v.toString() + " angularVelocity: " + this.w + ")}]"; -} - -Body.prototype.isStatic = function() { - return this.type == Body.STATIC ? true : false; -} - -Body.prototype.isDynamic = function() { - return this.type == Body.DYNAMIC ? true : false; -} - -Body.prototype.isKinetic = function() { - return this.type == Body.KINETIC ? true : false; -} - -Body.prototype.setType = function(type) { - if (type == this.type) { - return; - } - - this.f.set(0, 0); - this.v.set(0, 0); - this.t = 0; - this.w = 0; - this.type = type; - - this.awake(true); -} - -Body.prototype.addShape = function(shape) { - shape.body = this; - this.shapeArr.push(shape); -} - -Body.prototype.removeShape = function(shape) { - var index = this.shapeArr.indexOf(shape); - if (index != -1) { - this.shapeArr.splice(index, 1); - shape.body = undefined; - } -} - -// Internal function -Body.prototype.setMass = function(mass) { - this.m = mass; - this.m_inv = mass > 0 ? 1 / mass : 0; -} - -// Internal function -Body.prototype.setInertia = function(inertia) { - this.i = inertia; - this.i_inv = inertia > 0 ? 1 / inertia : 0; -} - -Body.prototype.setTransform = function(pos, angle) { - this.xf.set(pos, angle); - this.p = this.xf.transform(this.centroid); - this.a = angle; -} - -Body.prototype.syncTransform = function() { - this.xf.setRotation(this.a); - this.xf.setPosition(vec2.sub(this.p, this.xf.rotate(this.centroid))); -} - -Body.prototype.getWorldPoint = function(p) { - return this.xf.transform(p); -} - -Body.prototype.getWorldVector = function(v) { - return this.xf.rotate(v); -} - -Body.prototype.getLocalPoint = function(p) { - return this.xf.untransform(p); -} - -Body.prototype.getLocalVector = function(v) { - return this.xf.unrotate(v); -} - -Body.prototype.setFixedRotation = function(flag) { - this.fixedRotation = flag; - this.resetMassData(); -} - -Body.prototype.resetMassData = function() { - this.centroid.set(0, 0); - this.m = 0; - this.m_inv = 0; - this.i = 0; - this.i_inv = 0; - - if (!this.isDynamic()) { - this.p = this.xf.transform(this.centroid); - return; - } - - var totalMassCentroid = new vec2(0, 0); - var totalMass = 0; - var totalInertia = 0; - - for (var i = 0; i < this.shapeArr.length; i++) { - var shape = this.shapeArr[i]; - var centroid = shape.centroid(); - var mass = shape.area() * shape.density; - var inertia = shape.inertia(mass); - - totalMassCentroid.mad(centroid, mass); - totalMass += mass; - totalInertia += inertia; - } - - this.centroid.copy(vec2.scale(totalMassCentroid, 1 / totalMass)); - this.setMass(totalMass); - - if (!this.fixedRotation) { - this.setInertia(totalInertia - totalMass * vec2.dot(this.centroid, this.centroid)); - } - - // Move center of mass - var old_p = this.p; - this.p = this.xf.transform(this.centroid); - - // Update center of mass velocity ?? - this.v.mad(vec2.perp(vec2.sub(this.p, old_p)), this.w); -} - -Body.prototype.resetJointAnchors = function() { - for (var i = 0; i < this.jointArr.length; i++) { - var joint = this.jointArr[i]; - if (!joint) { - continue; - } - - var anchor1 = joint.getWorldAnchor1(); - var anchor2 = joint.getWorldAnchor2(); - - joint.setWorldAnchor1(anchor1); - joint.setWorldAnchor2(anchor2); - } -} - -Body.prototype.cacheData = function() { - - this.bounds.clear(); - - for (var i = 0; i < this.shapeArr.length; i++) { - var shape = this.shapeArr[i]; - shape.cacheData(this.xf); - this.bounds.addBounds(shape.bounds); - } - -} - -Body.prototype.updateVelocity = function(gravity, dt, damping) { - this.v = vec2.mad(this.v, vec2.mad(gravity, this.f, this.m_inv), dt); - this.w = this.w + this.t * this.i_inv * dt; - - // Apply damping. - // ODE: dv/dt + c * v = 0 - // Solution: v(t) = v0 * exp(-c * t) - // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) - // v2 = exp(-c * dt) * v1 - // Taylor expansion: - // v2 = (1.0f - c * dt) * v1 - this.v.scale(Math.clamp(1 - dt * (damping + this.linearDamping), 0, 1)); - this.w *= Math.clamp(1 - dt * (damping + this.angularDamping), 0, 1); - - this.f.set(0, 0); - this.t = 0; -} - -Body.prototype.updatePosition = function(dt) { - this.p.addself(vec2.scale(this.v, dt)); - this.a += this.w * dt; -} - -Body.prototype.resetForce = function() { - this.f.set(0, 0); - this.t = 0; -} - -Body.prototype.applyForce = function(force, p) { - if (!this.isDynamic()) - return; - - if (!this.isAwake()) - this.awake(true); - - this.f.addself(force); - this.t += vec2.cross(vec2.sub(p, this.p), force); -} - -Body.prototype.applyForceToCenter = function(force) { - if (!this.isDynamic()) - return; - - if (!this.isAwake()) - this.awake(true); - - this.f.addself(force); -} - -Body.prototype.applyTorque = function(torque) { - if (!this.isDynamic()) - return; - - if (!this.isAwake()) - this.awake(true); - - this.t += torque; -} - -Body.prototype.applyLinearImpulse = function(impulse, p) { - if (!this.isDynamic()) - return; - - if (!this.isAwake()) - this.awake(true); - - this.v.mad(impulse, this.m_inv); - this.w += vec2.cross(vec2.sub(p, this.p), impulse) * this.i_inv; -} - -Body.prototype.applyAngularImpulse = function(impulse) { - if (!this.isDynamic()) - return; - - if (!this.isAwake()) - this.awake(true); - - this.w += impulse * this.i_inv; -} - -Body.prototype.kineticEnergy = function() { - var vsq = this.v.dot(this.v); - var wsq = this.w * this.w; - return 0.5 * (this.m * vsq + this.i * wsq); -} - -Body.prototype.isAwake = function() { - return this.awaked; -} - -Body.prototype.awake = function(flag) { - this.awaked = flag; - if (flag) { - this.sleepTime = 0; - } - else { - this.v.set(0, 0); - this.w = 0; - this.f.set(0, 0); - this.t = 0; - } -} - -Body.prototype.isCollidable = function(other) { - if (this == other) - return false; - - if (!this.isDynamic() && !other.isDynamic()) - return false; - - if (!(this.maskBits & other.categoryBits) || !(other.maskBits & this.categoryBits)) - return false; - - for (var i = 0; i < this.jointArr.length; i++) { - var joint = this.jointArr[i]; - if (!joint) { - continue; - } - - if (!joint.collideConnected && other.jointHash[joint.id] != undefined) { - return false; - } - } - - return true; -} \ No newline at end of file diff --git a/src/physics/advanced/Collision.js b/src/physics/advanced/Collision.js deleted file mode 100644 index 848a07aa..00000000 --- a/src/physics/advanced/Collision.js +++ /dev/null @@ -1,382 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -var collision = {}; - -(function() { - var colFuncs = []; - - function addCollideFunc(a, b, func) { - colFuncs[a * Shape.NUM_TYPES + b] = func; - } - - function _circle2Circle(c1, r1, c2, r2, contactArr) { - var rmax = r1 + r2; - var t = vec2.sub(c2, c1); - var distsq = t.lengthsq(); - - if (distsq > rmax * rmax) { - return 0; - } - - var dist = Math.sqrt(distsq); - - var p = vec2.mad(c1, t, 0.5 + (r1 - r2) * 0.5 / dist); - var n = (dist != 0) ? vec2.scale(t, 1 / dist) : vec2.zero; - var d = dist - rmax; - - contactArr.push(new Contact(p, n, d, 0)); - - return 1; - } - - function circle2Circle(circ1, circ2, contactArr) { - return _circle2Circle(circ1.tc, circ1.r, circ2.tc, circ2.r, contactArr); - } - - function circle2Segment(circ, seg, contactArr) { - var rsum = circ.r + seg.r; - - // Normal distance from segment - var dn = vec2.dot(circ.tc, seg.tn) - vec2.dot(seg.ta, seg.tn); - var dist = (dn < 0 ? dn * -1 : dn) - rsum; - if (dist > 0) { - return 0; - } - - // Tangential distance along segment - var dt = vec2.cross(circ.tc, seg.tn); - var dtMin = vec2.cross(seg.ta, seg.tn); - var dtMax = vec2.cross(seg.tb, seg.tn); - - if (dt < dtMin) { - if (dt < dtMin - rsum) { - return 0; - } - - return _circle2Circle(circ.tc, circ.r, seg.ta, seg.r, contactArr); - } - else if (dt > dtMax) { - if (dt > dtMax + rsum) { - return 0; - } - - return _circle2Circle(circ.tc, circ.r, seg.tb, seg.r, contactArr); - } - - var n = (dn > 0) ? seg.tn : vec2.neg(seg.tn); - - contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + dist * 0.5)), vec2.neg(n), dist, 0)); - - return 1; - } - - function circle2Poly(circ, poly, contactArr) { - var minDist = -999999; - var minIdx = -1; - - for (var i = 0; i < poly.verts.length; i++) { - var plane = poly.tplanes[i]; - var dist = vec2.dot(circ.tc, plane.n) - plane.d - circ.r; - - if (dist > 0) { - return 0; - } - else if (dist > minDist) { - minDist = dist; - minIdx = i; - } - } - - var n = poly.tplanes[minIdx].n; - var a = poly.tverts[minIdx]; - var b = poly.tverts[(minIdx + 1) % poly.verts.length]; - var dta = vec2.cross(a, n); - var dtb = vec2.cross(b, n); - var dt = vec2.cross(circ.tc, n); - - if (dt > dta) { - return _circle2Circle(circ.tc, circ.r, a, 0, contactArr); - } - else if (dt < dtb) { - return _circle2Circle(circ.tc, circ.r, b, 0, contactArr); - } - - contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + minDist * 0.5)), vec2.neg(n), minDist, 0)); - - return 1; - } - - function segmentPointDistanceSq(seg, p) { - var w = vec2.sub(p, seg.ta); - var d = vec2.sub(seg.tb, seg.ta); - - var proj = w.dot(d); - if (proj <= 0) { - return w.dot(w); - } - - var vsq = d.dot(d) - if (proj >= vsq) { - return w.dot(w) - 2 * proj + vsq; - } - - return w.dot(w) - proj * proj / vsq; - } - - // FIXME !! - function segment2Segment(seg1, seg2, contactArr) { - var d = []; - d[0] = segmentPointDistanceSq(seg1, seg2.ta); - d[1] = segmentPointDistanceSq(seg1, seg2.tb); - d[2] = segmentPointDistanceSq(seg2, seg1.ta); - d[3] = segmentPointDistanceSq(seg2, seg1.tb); - - var idx1 = d[0] < d[1] ? 0 : 1; - var idx2 = d[2] < d[3] ? 2 : 3; - var idxm = d[idx1] < d[idx2] ? idx1 : idx2; - var s, t; - - var u = vec2.sub(seg1.tb, seg1.ta); - var v = vec2.sub(seg2.tb, seg2.ta); - - switch (idxm) { - case 0: - s = vec2.dot(vec2.sub(seg2.ta, seg1.ta), u) / vec2.dot(u, u); - s = s < 0 ? 0 : (s > 1 ? 1 : s); - t = 0; - break; - case 1: - s = vec2.dot(vec2.sub(seg2.tb, seg1.ta), u) / vec2.dot(u, u); - s = s < 0 ? 0 : (s > 1 ? 1 : s); - t = 1; - break; - case 2: - s = 0; - t = vec2.dot(vec2.sub(seg1.ta, seg2.ta), v) / vec2.dot(v, v); - t = t < 0 ? 0 : (t > 1 ? 1 : t); - break; - case 3: - s = 1; - t = vec2.dot(vec2.sub(seg1.tb, seg2.ta), v) / vec2.dot(v, v); - t = t < 0 ? 0 : (t > 1 ? 1 : t); - break; - } - - var minp1 = vec2.mad(seg1.ta, u, s); - var minp2 = vec2.mad(seg2.ta, v, t); - - return _circle2Circle(minp1, seg1.r, minp2, seg2.r, contactArr); - } - - // Identify vertexes that have penetrated the segment. - function findPointsBehindSeg(contactArr, seg, poly, dist, coef) { - var dta = vec2.cross(seg.tn, seg.ta); - var dtb = vec2.cross(seg.tn, seg.tb); - var n = vec2.scale(seg.tn, coef); - - for (var i = 0; i < poly.verts.length; i++) { - var v = poly.tverts[i]; - if (vec2.dot(v, n) < vec2.dot(seg.tn, seg.ta) * coef + seg.r) { - var dt = vec2.cross(seg.tn, v); - if (dta >= dt && dt >= dtb) { - contactArr.push(new Contact(v, n, dist, (poly.id << 16) | i)); - } - } - } - } - - function segment2Poly(seg, poly, contactArr) { - var seg_td = vec2.dot(seg.tn, seg.ta); - var seg_d1 = poly.distanceOnPlane(seg.tn, seg_td) - seg.r; - if (seg_d1 > 0) { - return 0; - } - var seg_d2 = poly.distanceOnPlane(vec2.neg(seg.tn), -seg_td) - seg.r; - if (seg_d2 > 0) { - return 0; - } - - var poly_d = -999999; - var poly_i = -1; - - for (var i = 0; i < poly.verts.length; i++) { - var plane = poly.tplanes[i]; - var dist = seg.distanceOnPlane(plane.n, plane.d); - if (dist > 0) { - return 0; - } - - if (dist > poly_d) { - poly_d = dist; - poly_i = i; - } - } - - var poly_n = vec2.neg(poly.tplanes[poly_i].n); - var va = vec2.mad(seg.ta, poly_n, seg.r); - var vb = vec2.mad(seg.tb, poly_n, seg.r); - - if (poly.containPoint(va)) { - contactArr.push(new Contact(va, poly_n, poly_d, (seg.id << 16) | 0)); - } - - if (poly.containPoint(vb)) { - contactArr.push(new Contact(vb, poly_n, poly_d, (seg.id << 16) | 1)); - } - - // Floating point precision problems here. - // This will have to do for now. - poly_d -= 0.1 - if (seg_d1 >= poly_d || seg_d2 >= poly_d) { - if (seg_d1 > seg_d2) { - findPointsBehindSeg(contactArr, seg, poly, seg_d1, 1); - } - else { - findPointsBehindSeg(contactArr, seg, poly, seg_d2, -1); - } - } - - // If no other collision points are found, try colliding endpoints. - if (contactArr.length == 0) { - var poly_a = poly.tverts[poly_i]; - var poly_b = poly.tverts[(poly_i + 1) % poly.verts.length]; - - if (_circle2Circle(seg.ta, seg.r, poly_a, 0, contactArr)) - return 1; - - if (_circle2Circle(seg.tb, seg.r, poly_a, 0, contactArr)) - return 1; - - if (_circle2Circle(seg.ta, seg.r, poly_b, 0, contactArr)) - return 1; - - if (_circle2Circle(seg.tb, seg.r, poly_b, 0, contactArr)) - return 1; - } - - return contactArr.length; - } - - // Find the minimum separating axis for the given poly and plane list. - function findMSA(poly, planes, num) { - var min_dist = -999999; - var min_index = -1; - - for (var i = 0; i < num; i++) { - var dist = poly.distanceOnPlane(planes[i].n, planes[i].d); - if (dist > 0) { // no collision - return { dist: 0, index: -1 }; - } - else if (dist > min_dist) { - min_dist = dist; - min_index = i; - } - } - - return { dist: min_dist, index: min_index }; - } - - function findVertsFallback(contactArr, poly1, poly2, n, dist) { - var num = 0; - - for (var i = 0; i < poly1.verts.length; i++) { - var v = poly1.tverts[i]; - if (poly2.containPointPartial(v, n)) { - contactArr.push(new Contact(v, n, dist, (poly1.id << 16) | i)); - - num++; - } - } - - for (var i = 0; i < poly2.verts.length; i++) { - var v = poly2.tverts[i]; - if (poly1.containPointPartial(v, n)) { - contactArr.push(new Contact(v, n, dist, (poly2.id << 16) | i)); - - num++; - } - } - - return num; - } - - // Find the overlapped vertices. - function findVerts(contactArr, poly1, poly2, n, dist) { - var num = 0; - - for (var i = 0; i < poly1.verts.length; i++) { - var v = poly1.tverts[i]; - if (poly2.containPoint(v)) { - contactArr.push(new Contact(v, n, dist, (poly1.id << 16) | i)); - - num++; - } - } - - for (var i = 0; i < poly2.verts.length; i++) { - var v = poly2.tverts[i]; - if (poly1.containPoint(v)) { - contactArr.push(new Contact(v, n, dist, (poly2.id << 16) | i)); - - num++; - } - } - - return num > 0 ? num : findVertsFallback(contactArr, poly1, poly2, n, dist); - } - - function poly2Poly(poly1, poly2, contactArr) { - var msa1 = findMSA(poly2, poly1.tplanes, poly1.verts.length); - if (msa1.index == -1) { - return 0; - } - - var msa2 = findMSA(poly1, poly2.tplanes, poly2.verts.length); - if (msa2.index == -1) { - return 0; - } - - // Penetration normal direction shoud be from poly1 to poly2 - if (msa1.dist > msa2.dist) { - return findVerts(contactArr, poly1, poly2, poly1.tplanes[msa1.index].n, msa1.dist); - } - - return findVerts(contactArr, poly1, poly2, vec2.neg(poly2.tplanes[msa2.index].n), msa2.dist); - } - - collision.init = function() { - addCollideFunc(Shape.TYPE_CIRCLE, Shape.TYPE_CIRCLE, circle2Circle); - addCollideFunc(Shape.TYPE_CIRCLE, Shape.TYPE_SEGMENT, circle2Segment); - addCollideFunc(Shape.TYPE_CIRCLE, Shape.TYPE_POLY, circle2Poly); - addCollideFunc(Shape.TYPE_SEGMENT, Shape.TYPE_SEGMENT, segment2Segment); - addCollideFunc(Shape.TYPE_SEGMENT, Shape.TYPE_POLY, segment2Poly); - addCollideFunc(Shape.TYPE_POLY, Shape.TYPE_POLY, poly2Poly); - }; - - collision.collide = function(a, b, contactArr) { - if (a.type > b.type) { - var c = a; - a = b; - b = c; - } - - return colFuncs[a.type * Shape.NUM_TYPES + b.type](a, b, contactArr); - }; -})(); diff --git a/src/physics/advanced/Contact.js b/src/physics/advanced/Contact.js deleted file mode 100644 index f4ab7423..00000000 --- a/src/physics/advanced/Contact.js +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -function Contact(p, n, d, hash) { - this.hash = hash; - - // Contact point - this.p = p; - - // Contact normal (toward shape2) - this.n = n; - - // Penetration depth (d < 0) - this.d = d; - - // Accumulated normal constraint impulse - this.lambda_n_acc = 0; - - // Accumulated tangential constraint impulse - this.lambda_t_acc = 0; -} \ No newline at end of file diff --git a/src/physics/advanced/ContactSolver.js b/src/physics/advanced/ContactSolver.js deleted file mode 100644 index b5f2b31b..00000000 --- a/src/physics/advanced/ContactSolver.js +++ /dev/null @@ -1,267 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -//------------------------------------------------------------------------------------------------- -// Contact Constraint -// -// Non-penetration constraint: -// C = dot(p2 - p1, n) -// Cdot = dot(v2 - v1, n) -// J = [ -n, -cross(r1, n), n, cross(r2, n) ] -// -// impulse = JT * lambda = [ -n * lambda, -cross(r1, n) * lambda, n * lambda, cross(r1, n) * lambda ] -// -// Friction constraint: -// C = dot(p2 - p1, t) -// Cdot = dot(v2 - v1, t) -// J = [ -t, -cross(r1, t), t, cross(r2, t) ] -// -// impulse = JT * lambda = [ -t * lambda, -cross(r1, t) * lambda, t * lambda, cross(r1, t) * lambda ] -// -// NOTE: lambda is an impulse in constraint space. -//------------------------------------------------------------------------------------------------- - -function ContactSolver(shape1, shape2) { - // Contact shapes - this.shape1 = shape1; - this.shape2 = shape2; - - // Contact list - this.contactArr = []; - - // Coefficient of restitution (elasticity) - this.e = 1; - - // Frictional coefficient - this.u = 1; -} - -ContactSolver.COLLISION_SLOP = 0.0008; -ContactSolver.BAUMGARTE = 0.28; -ContactSolver.MAX_LINEAR_CORRECTION = 1;//Infinity; - -ContactSolver.prototype.update = function(newContactArr) { - for (var i = 0; i < newContactArr.length; i++) { - var newContact = newContactArr[i]; - var k = -1; - for (var j = 0; j < this.contactArr.length; j++) { - if (newContact.hash == this.contactArr[j].hash) { - k = j; - break; - } - } - - if (k > -1) { - newContact.lambda_n_acc = this.contactArr[k].lambda_n_acc; - newContact.lambda_t_acc = this.contactArr[k].lambda_t_acc; - } - } - - this.contactArr = newContactArr; -} - -ContactSolver.prototype.initSolver = function(dt_inv) { - var body1 = this.shape1.body; - var body2 = this.shape2.body; - - var sum_m_inv = body1.m_inv + body2.m_inv; - - for (var i = 0; i < this.contactArr.length; i++) { - var con = this.contactArr[i]; - - // Transformed r1, r2 - con.r1 = vec2.sub(con.p, body1.p); - con.r2 = vec2.sub(con.p, body2.p); - - // Local r1, r2 - con.r1_local = body1.xf.unrotate(con.r1); - con.r2_local = body2.xf.unrotate(con.r2); - - var n = con.n; - var t = vec2.perp(con.n); - - // invEMn = J * invM * JT - // J = [ -n, -cross(r1, n), n, cross(r2, n) ] - var sn1 = vec2.cross(con.r1, n); - var sn2 = vec2.cross(con.r2, n); - var emn_inv = sum_m_inv + body1.i_inv * sn1 * sn1 + body2.i_inv * sn2 * sn2; - con.emn = emn_inv == 0 ? 0 : 1 / emn_inv; - - // invEMt = J * invM * JT - // J = [ -t, -cross(r1, t), t, cross(r2, t) ] - var st1 = vec2.cross(con.r1, t); - var st2 = vec2.cross(con.r2, t); - var emt_inv = sum_m_inv + body1.i_inv * st1 * st1 + body2.i_inv * st2 * st2; - con.emt = emt_inv == 0 ? 0 : 1 / emt_inv; - - // Linear velocities at contact point - // in 2D: cross(w, r) = perp(r) * w - var v1 = vec2.mad(body1.v, vec2.perp(con.r1), body1.w); - var v2 = vec2.mad(body2.v, vec2.perp(con.r2), body2.w); - - // relative velocity at contact point - var rv = vec2.sub(v2, v1); - - // bounce velocity dot n - con.bounce = vec2.dot(rv, con.n) * this.e; - } -} - -ContactSolver.prototype.warmStart = function() { - - var body1 = this.shape1.body; - var body2 = this.shape2.body; - - for (var i = 0; i < this.contactArr.length; i++) { - var con = this.contactArr[i]; - var n = con.n; - var lambda_n = con.lambda_n_acc; - var lambda_t = con.lambda_t_acc; - - // Apply accumulated impulses - //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); - var impulse = new vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); - - body1.v.mad(impulse, -body1.m_inv); - body1.w -= vec2.cross(con.r1, impulse) * body1.i_inv; - - body2.v.mad(impulse, body2.m_inv); - body2.w += vec2.cross(con.r2, impulse) * body2.i_inv; - } -} - -ContactSolver.prototype.solveVelocityConstraints = function() { - - var body1 = this.shape1.body; - var body2 = this.shape2.body; - - var m1_inv = body1.m_inv; - var i1_inv = body1.i_inv; - var m2_inv = body2.m_inv; - var i2_inv = body2.i_inv; - - for (var i = 0; i < this.contactArr.length; i++) { - - var con = this.contactArr[i]; - var n = con.n; - var t = vec2.perp(n); - var r1 = con.r1; - var r2 = con.r2; - - // Linear velocities at contact point - // in 2D: cross(w, r) = perp(r) * w - var v1 = vec2.mad(body1.v, vec2.perp(r1), body1.w); - var v2 = vec2.mad(body2.v, vec2.perp(r2), body2.w); - - // Relative velocity at contact point - var rv = vec2.sub(v2, v1); - - // Compute normal constraint impulse + adding bounce as a velocity bias - // lambda_n = -EMn * J * V - var lambda_n = -con.emn * (vec2.dot(n, rv) + con.bounce); - - // Accumulate and clamp - var lambda_n_old = con.lambda_n_acc; - con.lambda_n_acc = Math.max(lambda_n_old + lambda_n, 0); - lambda_n = con.lambda_n_acc - lambda_n_old; - - // Compute frictional constraint impulse - // lambda_t = -EMt * J * V - var lambda_t = -con.emt * vec2.dot(t, rv); - - // Max friction constraint impulse (Coulomb's Law) - var lambda_t_max = con.lambda_n_acc * this.u; - - // Accumulate and clamp - var lambda_t_old = con.lambda_t_acc; - con.lambda_t_acc = Math.clamp(lambda_t_old + lambda_t, -lambda_t_max, lambda_t_max); - lambda_t = con.lambda_t_acc - lambda_t_old; - - // Apply the final impulses - //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); - var impulse = new vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); - - body1.v.mad(impulse, -m1_inv); - body1.w -= vec2.cross(r1, impulse) * i1_inv; - - body2.v.mad(impulse, m2_inv); - body2.w += vec2.cross(r2, impulse) * i2_inv; - - } -} - -ContactSolver.prototype.solvePositionConstraints = function() { - - var body1 = this.shape1.body; - var body2 = this.shape2.body; - - var m1_inv = body1.m_inv; - var i1_inv = body1.i_inv; - var m2_inv = body2.m_inv; - var i2_inv = body2.i_inv; - var sum_m_inv = m1_inv + m2_inv; - - var max_penetration = 0; - - for (var i = 0; i < this.contactArr.length; i++) { - - var con = this.contactArr[i]; - var n = con.n; - - // Transformed r1, r2 - var r1 = vec2.rotate(con.r1_local, body1.a); - var r2 = vec2.rotate(con.r2_local, body2.a); - - // Contact points (corrected) - var p1 = vec2.add(body1.p, r1); - var p2 = vec2.add(body2.p, r2); - - // Corrected delta vector - var dp = vec2.sub(p2, p1); - - // Position constraint - var c = vec2.dot(dp, n) + con.d; - var correction = Math.clamp(ContactSolver.BAUMGARTE * (c + ContactSolver.COLLISION_SLOP), -ContactSolver.MAX_LINEAR_CORRECTION, 0); - - if (correction == 0) { - continue; - } - - // We don't need max_penetration less than or equal slop - max_penetration = Math.max(max_penetration, -c); - - // Compute lambda for position constraint - // Solve (J * invM * JT) * lambda = -C / dt - var sn1 = vec2.cross(r1, n); - var sn2 = vec2.cross(r2, n); - var em_inv = sum_m_inv + body1.i_inv * sn1 * sn1 + body2.i_inv * sn2 * sn2; - var lambda_dt = em_inv == 0 ? 0 : -correction / em_inv; - - // Apply correction impulses - var impulse_dt = vec2.scale(n, lambda_dt); - - body1.p.mad(impulse_dt, -m1_inv); - body1.a -= sn1 * lambda_dt * i1_inv; - - body2.p.mad(impulse_dt, m2_inv); - body2.a += sn2 * lambda_dt * i2_inv; - } - - return max_penetration <= ContactSolver.COLLISION_SLOP * 3; -} diff --git a/src/physics/advanced/Joint.js b/src/physics/advanced/Joint.js deleted file mode 100644 index 1dc01968..00000000 --- a/src/physics/advanced/Joint.js +++ /dev/null @@ -1,76 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -Joint = function(type, body1, body2, collideConnected) { - if (arguments.length == 0) - return; - - if (Joint.id_counter == undefined) - Joint.id_counter = 0; - - this.id = Joint.id_counter++; - this.type = type; - - this.body1 = body1; - this.body2 = body2; - - // Allow collision between to cennected body - this.collideConnected = collideConnected; - - // Constraint force limit - this.maxForce = 9999999999; - - // Is breakable ? - this.breakable = false; -} - -Joint.TYPE_ANGLE = 0; -Joint.TYPE_REVOLUTE = 1; -Joint.TYPE_WELD = 2; -Joint.TYPE_WHEEL = 3; -Joint.TYPE_PRISMATIC = 4; -Joint.TYPE_DISTANCE = 5; -Joint.TYPE_ROPE = 6; -Joint.TYPE_MOUSE = 7; - -Joint.LINEAR_SLOP = 0.0008; -Joint.ANGULAR_SLOP = deg2rad(2); -Joint.MAX_LINEAR_CORRECTION = 0.5; -Joint.MAX_ANGULAR_CORRECTION = deg2rad(8); - -Joint.LIMIT_STATE_INACTIVE = 0; -Joint.LIMIT_STATE_AT_LOWER = 1; -Joint.LIMIT_STATE_AT_UPPER = 2; -Joint.LIMIT_STATE_EQUAL_LIMITS = 3; - -Joint.prototype.getWorldAnchor1 = function() { - return this.body1.getWorldPoint(this.anchor1); -} - -Joint.prototype.getWorldAnchor2 = function() { - return this.body2.getWorldPoint(this.anchor2); -} - -Joint.prototype.setWorldAnchor1 = function(anchor1) { - this.anchor1 = this.body1.getLocalPoint(anchor1); -} - -Joint.prototype.setWorldAnchor2 = function(anchor2) { - this.anchor2 = this.body2.getLocalPoint(anchor2); -} \ No newline at end of file diff --git a/src/physics/advanced/Math.js b/src/physics/advanced/Math.js deleted file mode 100644 index b144f8b0..00000000 --- a/src/physics/advanced/Math.js +++ /dev/null @@ -1,817 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -Math.clamp = function(v, min, max) { return v < min ? min : (v > max ? max : v); } -Math.log2 = function(a) { return Math.log(a) / Math.log(2); } - -function deg2rad(deg) { return (deg / 180) * Math.PI; } -function rad2deg(rad) { return (rad / Math.PI) * 180; } - -function pixel2meter(px) { return px * 0.02; } -function meter2pixel(mt) { return mt * 50; } - -//----------------------------------- -// 2D Vector -//----------------------------------- - -function vec2(x, y) { - this.x = x || 0; - this.y = y || 0; -} - -vec2.zero = new vec2(0, 0); - -vec2.prototype.toString = function() { - //return ["x:", this.x, "y:", this.y].join(" "); - return "x=" + this.x + " y=" + this.y; -} - -vec2.prototype.set = function(x, y) { - this.x = x; - this.y = y; - - return this; -} - -vec2.prototype.copy = function(v) { - this.x = v.x; - this.y = v.y; - - return this; -} - -vec2.prototype.duplicate = function() { - return new vec2(this.x, this.y); -} - -vec2.prototype.equal = function(v) { - return (this.x != v.x || this.y != v.y) ? false : true; -} - -vec2.prototype.add = function(v1, v2) { - this.x = v1.x + v2.x; - this.y = v1.y + v2.y; - - return this; -} - -vec2.prototype.addself = function(v) { - this.x += v.x; - this.y += v.y; - - return this; -} - -vec2.prototype.sub = function(v1, v2) { - this.x = v1.x - v2.x; - this.y = v1.y - v2.y; - - return this; -} - -vec2.prototype.subself = function(v) { - this.x -= v.x; - this.y -= v.y; - - return this; -} - -vec2.prototype.scale = function(s) { - this.x *= s; - this.y *= s; - - return this; -} - -vec2.prototype.scale2 = function(s) { - this.x *= s.x; - this.y *= s.y; - - return this; -} - -vec2.prototype.mad = function(v, s) { - this.x += v.x * s; - this.y += v.y * s; -} - -vec2.prototype.neg = function() { - this.x *= -1; - this.y *= -1; - - return this; -} - -vec2.prototype.rcp = function() { - this.x = 1 / this.x; - this.y = 1 / this.y; - - return this; -} - -vec2.prototype.lengthsq = function() { - return this.x * this.x + this.y * this.y; -} - -vec2.prototype.length = function() { - return Math.sqrt(this.x * this.x + this.y * this.y); -} - -vec2.prototype.normalize = function() { - var inv = (this.x != 0 || this.y != 0) ? 1 / Math.sqrt(this.x * this.x + this.y * this.y) : 0; - this.x *= inv; - this.y *= inv; - - return this; -} - -vec2.prototype.dot = function(v) { - return this.x * v.x + this.y * v.y; -} - -// Z-component of 3d cross product (ax, ay, 0) x (bx, by, 0) -vec2.prototype.cross = function(v) { - return this.x * v.y - this.y * v.x; -} - -vec2.prototype.toAngle = function() { - return Math.atan2(this.y, this.x); -} - -vec2.prototype.rotation = function(angle) { - this.x = Math.cos(angle); - this.y = Math.sin(angle); - return this; -} - -vec2.prototype.rotate = function(angle) { - var c = Math.cos(angle); - var s = Math.sin(angle); - return this.set(this.x * c - this.y * s, this.x * s + this.y * c); -} - -vec2.prototype.lerp = function(v1, v2, t) { - return this.add(vec2.scale(v1, 1 - t), vec2.scale(v2, t)); -} - -vec2.add = function(v1, v2) { - return new vec2(v1.x + v2.x, v1.y + v2.y); -} - -vec2.sub = function(v1, v2) { - return new vec2(v1.x - v2.x, v1.y - v2.y); -} - -vec2.scale = function(v, s) { - return new vec2(v.x * s, v.y * s); -} - -vec2.scale2 = function(v, s) { - return new vec2(v.x * s.x, v.y * s.y); -} - -vec2.mad = function(v1, v2, s) { - return new vec2(v1.x + v2.x * s, v1.y + v2.y * s); -} - -vec2.neg = function(v) { - return new vec2(-v.x, -v.y); -} - -vec2.rcp = function(v) { - return new vec2(1 / v.x, 1 / v.y); -} - -vec2.normalize = function(v) { - var inv = (v.x != 0 || v.y != 0) ? 1 / Math.sqrt(v.x * v.x + v.y * v.y) : 0; - return new vec2(v.x * inv, v.y * inv); -} - -vec2.dot = function(v1, v2) { - return v1.x * v2.x + v1.y * v2.y; -} - -vec2.cross = function(v1, v2) { - return v1.x * v2.y - v1.y * v2.x; -} - -vec2.toAngle = function(v) { - return Math.atan2(v.y, v.x); -} - -vec2.rotation = function(angle) { - return new vec2(Math.cos(angle), Math.sin(angle)); -} - -vec2.rotate = function(v, angle) { - var c = Math.cos(angle); - var s = Math.sin(angle); - return new vec2(v.x * c - v.y * s, v.x * s + v.y * c); -} - -// Return perpendicular vector (90 degree rotation) -vec2.perp = function(v) { - return new vec2(-v.y, v.x); -} - -// Return perpendicular vector (-90 degree rotation) -vec2.rperp = function(v) { - return new vec2(v.y, -v.x); -} - -vec2.dist = function(v1, v2) { - var dx = v2.x - v1.x; - var dy = v2.y - v1.y; - return Math.sqrt(dx * dx + dy * dy); -} - -vec2.distsq = function(v1, v2) { - var dx = v2.x - v1.x; - var dy = v2.y - v1.y; - return dx * dx + dy * dy; -} - -vec2.lerp = function(v1, v2, t) { - return vec2.add(vec2.scale(v1, 1 - t), vec2.scale(v2, t)); -} - -vec2.truncate = function(v, length) { - var ret = v.duplicate(); - var length_sq = v.x * v.x + v.y * v.y; - if (length_sq > length * length) { - ret.scale(length / Math.sqrt(length_sq)); - } - - return ret; -} - -//----------------------------------- -// 3D Vector -//----------------------------------- - -function vec3(x, y, z) { - this.x = x || 0; - this.y = y || 0; - this.z = z || 0; -} - -vec3.zero = new vec3(0, 0, 0); - -vec3.prototype.toString = function() { - return ["x:", this.x, "y:", this.y, "z:", this.z].join(" "); -} - -vec3.prototype.set = function(x, y, z) { - this.x = x; - this.y = y; - this.z = z; - - return this; -} - -vec3.prototype.copy = function(v) { - this.x = v.x; - this.y = v.y; - this.z = v.z; - - return this; -} - -vec3.prototype.duplicate = function() { - return new vec3(this.x, this.y, this.z); -} - -vec3.prototype.equal = function(v) { - return this.x != v.x || this.y != v.y || this.z != v.z ? false : true; -} - -vec3.prototype.add = function(v1, v2) { - this.x = v1.x + v2.x; - this.y = v1.y + v2.y; - this.z = v1.z + v2.z; - - return this; -} - -vec3.prototype.addself = function(v) { - this.x += v.x; - this.y += v.y; - this.z += v.z; - - return this; -} - -vec3.prototype.sub = function(v1, v2) { - this.x = v1.x - v2.x; - this.y = v1.y - v2.y; - this.z = v1.z - v2.z; - - return this; -} - -vec3.prototype.subself = function(v) { - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - - return this; -} - -vec3.prototype.scale = function(s) { - this.x *= s; - this.y *= s; - this.z *= s; - - return this; -} - -vec3.prototype.mad = function(v, s) { - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; -} - -vec3.prototype.neg = function() { - this.x *= -1; - this.y *= -1; - this.z *= -1; - - return this; -} - -vec3.prototype.rcp = function() { - this.x = 1 / this.x; - this.y = 1 / this.y; - this.z = 1 / this.z; - - return this; -} - -vec3.prototype.lengthsq = function() { - return this.x * this.x + this.y * this.y + this.z * this.z; -} - -vec3.prototype.length = function() { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); -} - -vec3.prototype.normalize = function() { - var inv = (this.x != 0 || this.y != 0 || this.z != 0) ? 1 / Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z) : 0; - this.x *= inv; - this.y *= inv; - this.z *= inv; - - return this; -} - -vec3.prototype.dot = function(v) { - return this.x * v.x + this.y * v.y + this.z * v.z; -} - -vec3.prototype.toVec2 = function() { - return new vec2(this.x, this.y); -} - -vec3.fromVec2 = function(v, z) { - return new vec3(v.x, v.y, z); -} - -vec3.truncate = function(v, length) { - var ret = v.duplicate(); - var length_sq = v.x * v.x + v.y * v.y + v.z * v.z; - if (length_sq > length * length) { - ret.scale(length / Math.sqrt(length_sq)); - } - - return ret; -} - -//----------------------------------- -// 2x2 Matrix (row major) -//----------------------------------- - -function mat2(_11, _12, _21, _22) { - this._11 = _11 || 0; - this._12 = _12 || 0; - this._21 = _21 || 0; - this._22 = _22 || 0; -} - -mat2.zero = new mat2(0, 0, 0, 0); - -mat2.prototype.toString = function() { - return ["[", this._11, this._12, this_21, this._22, "]"].join(" "); -} - -mat2.prototype.set = function(_11, _12, _21, _22) { - this._11 = _11; - this._12 = _12; - this._21 = _21; - this._22 = _22; - - return this; -} - -mat2.prototype.copy = function(m) { - this._11 = m._11; - this._12 = m._12; - this._21 = m._21; - this._22 = m._22; - - return this; -} - -mat2.prototype.duplicate = function() { - return new mat2(this._11, this._12, this._21, this._22); -} - -mat2.prototype.scale = function(s) { - this._11 *= s; - this._12 *= s; - this._21 *= s; - this._22 *= s; - - return this; -} - -mat2.prototype.mul = function(m) { - return this.set( - this._11 * m2._11 + this._12 * m2._21, - this._11 * m2._12 + this._12 * m2._22, - this._21 * m2._11 + this._22 * m2._21, - this._21 * m2._12 + this._22 * m2._22); -} - -mat2.prototype.mulvec = function(v) { - return new vec2( - this._11 * v.x + this._12 * v.y, - this._21 * v.x + this._22 * v.y); -} - -mat2.prototype.invert = function() { - var det = this._11 * this._22 - this._12 * this._21; - if (det != 0) - det = 1 / det; - - return this.set( - this._22 * det, -this._12 * det, - -this._21 * det, this._11 * det); -} - -// Solve A * x = b -mat2.prototype.solve = function(b) { - var det = this._11 * this._22 - this._12 * this._21; - if (det != 0) - det = 1 / det; - - return new vec2( - det * (this._22 * b.x - this._12 * b.y), - det * (this._11 * b.y - this._21 * b.x)); -} - -mat2.mul = function(m1, m2) { - return new mat2( - m1._11 * m2._11 + m1._12 * m2._21, - m1._11 * m2._12 + m1._12 * m2._22, - m1._21 * m2._11 + m1._22 * m2._21, - m1._21 * m2._12 + m1._22 * m2._22); -} - -//----------------------------------- -// 3x3 Matrix (row major) -//----------------------------------- - -function mat3(_11, _12, _13, _21, _22, _23, _31, _32, _33) { - this._11 = _11 || 0; - this._12 = _12 || 0; - this._13 = _13 || 0; - this._21 = _21 || 0; - this._22 = _22 || 0; - this._23 = _23 || 0; - this._31 = _31 || 0; - this._32 = _32 || 0; - this._33 = _33 || 0; -} - -mat3.zero = new mat3(0, 0, 0, 0, 0, 0, 0, 0, 0); - -mat3.prototype.toString = function() { - return ["[", this._11, this._12, this._13, this_21, this._22, this._23, this._31, this._32, this._33, "]"].join(" "); -} - -mat3.prototype.set = function(_11, _12, _13, _21, _22, _23, _31, _32, _33) { - this._11 = _11; - this._12 = _12; - this._13 = _13; - this._21 = _21; - this._22 = _22; - this._23 = _23; - this._31 = _31; - this._32 = _32; - this._33 = _33; - - return this; -} - -mat3.prototype.copy = function(m) { - this._11 = m._11; - this._12 = m._12; - this._13 = m._13; - this._21 = m._21; - this._22 = m._22; - this._23 = m._23; - this._31 = m._31; - this._32 = m._32; - this._33 = m._33; - - return this; -} - -mat3.prototype.duplicate = function() { - return new mat3(this._11, this._12, this._13, this._21, this._22, this._23, this._31, this._32, this._33); -} - -mat3.prototype.scale = function(s) { - this._11 *= s; - this._12 *= s; - this._13 *= s; - this._21 *= s; - this._22 *= s; - this._23 *= s; - this._31 *= s; - this._32 *= s; - this._33 *= s; - - return this; -} - -mat3.prototype.mul = function(m) { - return this.set( - this._11 * m2._11 + this._12 * m2._21 + this._13 * m2._31, - this._11 * m2._12 + this._12 * m2._22 + this._13 * m2._32, - this._11 * m2._13 + this._12 * m2._23 + this._13 * m2._33, - this._21 * m2._11 + this._22 * m2._21 + this._23 * m2._31, - this._21 * m2._12 + this._22 * m2._22 + this._23 * m2._32, - this._21 * m2._13 + this._22 * m2._23 + this._23 * m2._33, - this._31 * m2._11 + this._32 * m2._21 + this._33 * m2._31, - this._31 * m2._12 + this._32 * m2._22 + this._33 * m2._32, - this._31 * m2._13 + this._32 * m2._23 + this._33 * m2._33); -} - -mat3.prototype.mulvec = function(v) { - return new vec2( - this._11 * v.x + this._12 * v.y + this._13 * v.z, - this._21 * v.x + this._22 * v.y + this._23 * v.z, - this._31 * v.x + this._32 * v.y + this._33 * v.z); -} - -mat3.prototype.invert = function() { - var det2_11 = this._22 * this._33 - this._23 * this._32; - var det2_12 = this._23 * this._31 - this._21 * this._33; - var det2_13 = this._21 * this._32 - this._22 * this._31; - - var det = this._11 * det2_11 + this._12 * det2_12 + this._13 * det2_13; - if (det != 0) - det = 1 / det; - - var det2_21 = this._13 * this._32 - this._12 * this._33; - var det2_22 = this._11 * this._33 - this._13 * this._31; - var det2_23 = this._12 * this._31 - this._11 * this._32; - var det2_31 = this._12 * this._23 - this._13 * this._22; - var det2_32 = this._13 * this._21 - this._11 * this._23; - var det2_33 = this._11 * this._22 - this._12 * this._21; - - return this.set( - det2_11 * det, det2_12 * det, det2_13 * det, - det2_21 * det, det2_22 * det, det2_23 * det, - det2_31 * det, det2_32 * det, det2_33 * det); -} - -// Solve A(2x2) * x = b -mat3.prototype.solve2x2 = function(b) { - var det = this._11 * this._22 - this._12 * this._21; - if (det != 0) - det = 1 / det; - - return new vec2( - det * (this._22 * b.x - this._12 * b.y), - det * (this._11 * b.y - this._21 * b.x)); -} - -// Solve A(3x3) * x = b -mat3.prototype.solve = function(b) { - var det2_11 = this._22 * this._33 - this._23 * this._32; - var det2_12 = this._23 * this._31 - this._21 * this._33; - var det2_13 = this._21 * this._32 - this._22 * this._31; - - var det = this._11 * det2_11 + this._12 * det2_12 + this._13 * det2_13; - if (det != 0) - det = 1 / det; - - var det2_21 = this._13 * this._32 - this._12 * this._33; - var det2_22 = this._11 * this._33 - this._13 * this._31; - var det2_23 = this._12 * this._31 - this._11 * this._32; - var det2_31 = this._12 * this._23 - this._13 * this._22; - var det2_32 = this._13 * this._21 - this._11 * this._23; - var det2_33 = this._11 * this._22 - this._12 * this._21; - - return new vec3( - det * (det2_11 * b.x + det2_12 * b.y + det2_13 * b.z), - det * (det2_21 * b.x + det2_22 * b.y + det2_23 * b.z), - det * (det2_31 * b.x + det2_32 * b.y + det2_33 * b.z)); -} - -mat3.mul = function(m1, m2) { - return new mat3( - m1._11 * m2._11 + m1._12 * m2._21 + m1._13 * m2._31, - m1._11 * m2._12 + m1._12 * m2._22 + m1._13 * m2._32, - m1._11 * m2._13 + m1._12 * m2._23 + m1._13 * m2._33, - m1._21 * m2._11 + m1._22 * m2._21 + m1._23 * m2._31, - m1._21 * m2._12 + m1._22 * m2._22 + m1._23 * m2._32, - m1._21 * m2._13 + m1._22 * m2._23 + m1._23 * m2._33, - m1._31 * m2._11 + m1._32 * m2._21 + m1._33 * m2._31, - m1._31 * m2._12 + m1._32 * m2._22 + m1._33 * m2._32, - m1._31 * m2._13 + m1._32 * m2._23 + m1._33 * m2._33); -} - -//----------------------------------- -// 2D Transform -//----------------------------------- - -Transform = function(pos, angle) { - this.t = pos.duplicate(); - this.c = Math.cos(angle); - this.s = Math.sin(angle); - this.a = angle; -} - -Transform.prototype.toString = function() { - return 't=' + this.t.toString() + ' c=' + this.c + ' s=' + this.s + ' a=' + this.a; -} - -Transform.prototype.set = function(pos, angle) { - this.t.copy(pos); - this.c = Math.cos(angle); - this.s = Math.sin(angle); - this.a = angle; - return this; -} - -Transform.prototype.setRotation = function(angle) { - this.c = Math.cos(angle); - this.s = Math.sin(angle); - this.a = angle; - return this; -} - -Transform.prototype.setPosition = function(p) { - this.t.copy(p); - return this; -} - -Transform.prototype.identity = function() { - this.t.set(0, 0); - this.c = 1; - this.s = 0; - this.a = 0; - return this; -} - -Transform.prototype.rotate = function(v) { - return new vec2(v.x * this.c - v.y * this.s, v.x * this.s + v.y * this.c); -} - -Transform.prototype.unrotate = function(v) { - return new vec2(v.x * this.c + v.y * this.s, -v.x * this.s + v.y * this.c); -} - -Transform.prototype.transform = function(v) { - return new vec2(v.x * this.c - v.y * this.s + this.t.x, v.x * this.s + v.y * this.c + this.t.y); -} - -Transform.prototype.untransform = function(v) { - var px = v.x - this.t.x; - var py = v.y - this.t.y; - return new vec2(px * this.c + py * this.s, -px * this.s + py * this.c); -} - -//----------------------------------- -// 2D AABB -//----------------------------------- - -Bounds = function(mins, maxs) { - this.mins = mins ? new vec2(mins.x, mins.y) : new vec2(999999, 999999); - this.maxs = maxs ? new vec2(maxs.x, maxs.y) : new vec2(-999999, -999999); -} - -Bounds.prototype.toString = function() { - return ["mins:", this.mins.toString(), "maxs:", this.maxs.toString()].join(" "); -} - -Bounds.prototype.set = function(mins, maxs) { - this.mins.set(mins.x, mins.y); - this.maxs.set(maxs.x, maxs.y); -} - -Bounds.prototype.copy = function(b) { - this.mins.copy(b.mins); - this.maxs.copy(b.maxs); - return this; -} - -Bounds.prototype.clear = function() { - this.mins.set(999999, 999999); - this.maxs.set(-999999, -999999); - return this; -} - -Bounds.prototype.isEmpty = function() { - if (this.mins.x > this.maxs.x || this.mins.y > this.maxs.y) - return true; -} - -Bounds.prototype.getCenter = function() { - return vec2.scale(vec2.add(this.mins, this.maxs), 0.5); -} - -Bounds.prototype.getExtent = function() { - return vec2.scale(vec2.sub(this.maxs, this.mins), 0.5); -} - -Bounds.prototype.getPerimeter = function() { - return (maxs.x - mins.x + maxs.y - mins.y) * 2; -} - -Bounds.prototype.addPoint = function(p) { - if (this.mins.x > p.x) this.mins.x = p.x; - if (this.maxs.x < p.x) this.maxs.x = p.x; - if (this.mins.y > p.y) this.mins.y = p.y; - if (this.maxs.y < p.y) this.maxs.y = p.y; - return this; -} - -Bounds.prototype.addBounds = function(b) { - if (this.mins.x > b.mins.x) this.mins.x = b.mins.x; - if (this.maxs.x < b.maxs.x) this.maxs.x = b.maxs.x; - if (this.mins.y > b.mins.y) this.mins.y = b.mins.y; - if (this.maxs.y < b.maxs.y) this.maxs.y = b.maxs.y; - return this; -} - -Bounds.prototype.addBounds2 = function(mins, maxs) { - if (this.mins.x > mins.x) this.mins.x = mins.x; - if (this.maxs.x < maxs.x) this.maxs.x = maxs.x; - if (this.mins.y > mins.y) this.mins.y = mins.y; - if (this.maxs.y < maxs.y) this.maxs.y = maxs.y; - return this; -} - -Bounds.prototype.addExtents = function(center, extent_x, extent_y) { - if (this.mins.x > center.x - extent_x) this.mins.x = center.x - extent_x; - if (this.maxs.x < center.x + extent_x) this.maxs.x = center.x + extent_x; - if (this.mins.y > center.y - extent_y) this.mins.y = center.y - extent_y; - if (this.maxs.y < center.y + extent_y) this.maxs.y = center.y + extent_y; - return this; -} - -Bounds.prototype.expand = function(ax, ay) { - this.mins.x -= ax; - this.mins.y -= ay; - this.maxs.x += ax; - this.maxs.y += ay; - return this; -} - -Bounds.prototype.containPoint = function(p) { - if (p.x < this.mins.x || p.x > this.maxs.x || p.y < this.mins.y || p.y > this.maxs.y) - return false; - return true; -} - -Bounds.prototype.intersectsBounds = function(b) { - if (this.mins.x > b.maxs.x || this.maxs.x < b.mins.x || this.mins.y > b.maxs.y || this.maxs.y < b.mins.y) - return false; - return true; -} - -Bounds.expand = function(b, ax, ay) { - var b = new Bounds(b.mins, b.maxs); - b.expand(ax, ay); - return b; -} \ No newline at end of file diff --git a/src/physics/advanced/Shape.js b/src/physics/advanced/Shape.js deleted file mode 100644 index 0e31f21f..00000000 --- a/src/physics/advanced/Shape.js +++ /dev/null @@ -1,47 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -Shape = function(type) { - if (arguments.length == 0) - return; - - if (Shape.id_counter == undefined) - Shape.id_counter = 0; - - this.id = Shape.id_counter++; - this.type = type; - - // Coefficient of restitution (elasticity) - this.e = 0.0; - - // Frictional coefficient - this.u = 1.0; - - // Mass density - this.density = 1; - - // Axis-aligned bounding box - this.bounds = new Bounds; -} - -Shape.TYPE_CIRCLE = 0; -Shape.TYPE_SEGMENT = 1; -Shape.TYPE_POLY = 2; -Shape.NUM_TYPES = 3; - diff --git a/src/physics/advanced/Space.js b/src/physics/advanced/Space.js deleted file mode 100644 index e869af2e..00000000 --- a/src/physics/advanced/Space.js +++ /dev/null @@ -1,797 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -function Space() { - this.bodyArr = []; - this.bodyHash = {}; - - this.jointArr = []; - this.jointHash = {}; - - this.numContacts = 0; - this.contactSolverArr = []; - - this.postSolve = function(arb) {}; - - this.gravity = new vec2(0, 0); - this.damping = 0; - - this.log = []; -} - -Space.TIME_TO_SLEEP = 0.5; -Space.SLEEP_LINEAR_TOLERANCE = 0.5; -Space.SLEEP_ANGULAR_TOLERANCE = deg2rad(2); - -Space.prototype.clear = function() { - Shape.id_counter = 0; - Body.id_counter = 0; - Joint.id_counter = 0; - - for (var i = 0; i < this.bodyArr.length; i++) { - if (this.bodyArr[i]) { - this.removeBody(this.bodyArr[i]); - } - } - - this.bodyArr = []; - this.bodyHash = {}; - - this.jointArr = []; - this.jointHash = {}; - - this.contactSolverArr = []; - - this.stepCount = 0; -} - -Space.prototype.toJSON = function(key) { - var o_bodies = []; - for (var i = 0; i < this.bodyArr.length; i++) { - if (this.bodyArr[i]) { - o_bodies.push(this.bodyArr[i].serialize()); - } - } - - var o_joints = []; - for (var i = 0; i < this.jointArr.length; i++) { - if (this.jointArr[i]) { - o_joints.push(this.jointHash[i].serialize()); - } - } - - return { - bodies: o_bodies, - joints: o_joints - }; -} - -Space.prototype.create = function(text) { - var config = JSON.parse(text); - - this.clear(); - - for (var i = 0; i < config.bodies.length; i++) { - var config_body = config.bodies[i]; - var type = {"static": Body.Static, "kinetic": Body.KINETIC, "dynamic": Body.DYNAMIC}[config_body.type]; - var body = new Body(type, config_body.position.x, config_body.position.y, config_body.angle); - - for (var j = 0; j < config_body.shapes.length; j++) { - var config_shape = config_body.shapes[j]; - var shape; - - switch (config_shape.type) { - case "ShapeCircle": - shape = new ShapeCircle(config_shape.center.x, config_shape.center.y, config_shape.radius); - break; - case "ShapeSegment": - shape = new ShapeSegment(config_shape.a, config_shape.b, config_shape.radius); - break; - case "ShapePoly": - shape = new ShapePoly(config_shape.verts); - break; - } - - shape.e = config_shape.e; - shape.u = config_shape.u; - shape.density = config_shape.density; - - body.addShape(shape); - } - - body.resetMassData(); - this.addBody(body); - } - - for (var i = 0; i < config.joints.length; i++) { - var config_joint = config.joints[i]; - var body1 = this.bodyArr[this.bodyHash[config_joint.body1]]; - var body2 = this.bodyArr[this.bodyHash[config_joint.body2]]; - var joint; - - switch (config_joint.type) { - case "AngleJoint": - joint = new AngleJoint(body1, body2); - break; - case "RevoluteJoint": - joint = new RevoluteJoint(body1, body2, config_joint.anchor); - joint.enableLimit(config_joint.limitEnabled); - joint.setLimits(config_joint.limitLowerAngle, config_joint.limitUpperAngle); - joint.enableMotor(config_joint.motorEnabled); - joint.setMotorSpeed(config_joint.motorSpeed); - joint.setMaxMotorTorque(config_joint.maxMotorTorque); - break; - case "WeldJoint": - joint = new WeldJoint(body1, body2, config_joint.anchor); - joint.setSpringFrequencyHz(config_joint.frequencyHz); - joint.setSpringDampingRatio(config_joint.dampingRatio); - break; - case "WheelJoint": - joint = new WheelJoint(body1, body2, config_joint.anchor1, config_joint.anchor2); - joint.enableMotor(config_joint.motorEnabled); - joint.setMotorSpeed(config_joint.motorSpeed); - joint.setMaxMotorTorque(config_joint.maxMotorTorque); - break; - case "PrismaticJoint": - joint = new PrismaticJoint(body1, body2, config_joint.anchor1, config_joint.anchor2); - break; - case "DistanceJoint": - joint = new DistanceJoint(body1, body2, config_joint.anchor1, config_joint.anchor2); - joint.setSpringFrequencyHz(config_joint.frequencyHz); - joint.setSpringDampingRatio(config_joint.dampingRatio); - break; - case "RopeJoint": - joint = new RopeJoint(body1, body2, config_joint.anchor1, config_joint.anchor2); - break; - } - - joint.collideConnected = config_joint.collideConnected; - joint.maxForce = config_joint.maxForce; - joint.breakable = config_joint.breakable; - - this.addJoint(joint); - } -} - -Space.prototype.addBody = function(body) { - if (this.bodyHash[body.id] != undefined) { - return; - } - - var index = this.bodyArr.push(body) - 1; - this.bodyHash[body.id] = index; - - body.awake(true); - body.space = this; - body.cacheData(); -} - -Space.prototype.removeBody = function(body) { - if (this.bodyHash[body.id] == undefined) { - return; - } - - // Remove linked joint - for (var i = 0; i < body.jointArr.length; i++) { - if (body.jointArr[i]) { - this.removeJoint(body.jointArr[i]); - } - } - - body.space = null; - - var index = this.bodyHash[body.id]; - delete this.bodyHash[body.id]; - delete this.bodyArr[index]; -} - -Space.prototype.addJoint = function(joint) { - if (this.jointHash[joint.id] != undefined) { - return; - } - - joint.body1.awake(true); - joint.body2.awake(true); - - var index = this.jointArr.push(joint) - 1; - this.jointHash[joint.id] = index; - - var index = joint.body1.jointArr.push(joint) - 1; - joint.body1.jointHash[joint.id] = index; - - var index = joint.body2.jointArr.push(joint) - 1; - joint.body2.jointHash[joint.id] = index; -} - -Space.prototype.removeJoint = function(joint) { - if (this.jointHash[joint.id] == undefined) { - return; - } - - joint.body1.awake(true); - joint.body2.awake(true); - - var index = joint.body1.jointHash[joint.id]; - delete joint.body1.jointHash[joint.id]; - delete joint.body1.jointArr[index]; - - var index = joint.body2.jointHash[joint.id]; - delete joint.body2.jointHash[joint.id]; - delete joint.body2.jointArr[index]; - - var index = this.jointHash[joint.id]; - delete this.jointHash[joint.id]; - delete this.jointArr[index]; -} - -Space.prototype.findShapeByPoint = function(p, refShape) { - var firstShape; - - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - for (var j = 0; j < body.shapeArr.length; j++) { - var shape = body.shapeArr[j]; - - if (shape.pointQuery(p)) { - if (!refShape) { - return shape; - } - - if (!firstShape) { - firstShape = shape; - } - - if (shape == refShape) { - refShape = null; - } - } - } - } - - return firstShape; -} - -Space.prototype.findBodyByPoint = function(p, refBody) { - var firstBody; - - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - for (var j = 0; j < body.shapeArr.length; j++) { - var shape = body.shapeArr[j]; - - if (shape.pointQuery(p)) { - if (!refBody) { - return shape.body; - } - - if (!firstBody) { - firstBody = shape.body; - } - - if (shape.body == refBody) { - refBody = null; - } - - break; - } - } - } - - return firstBody; -} - -// TODO: Replace this function to shape hashing -Space.prototype.shapeById = function(id) { - var shape; - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - for (var j = 0; j < body.shapeArr.length; j++) { - if (body.shapeArr[j].id == id) { - return body.shapeArr[j]; - } - } - } - - return null; -} - -Space.prototype.jointById = function(id) { - var index = this.jointHash[id]; - if (index != undefined) { - return this.jointArr[index]; - } - - return null; -} - -Space.prototype.findVertexByPoint = function(p, minDist, refVertexId) { - var firstVertexId = -1; - - refVertexId = refVertexId || -1; - - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - for (var j = 0; j < body.shapeArr.length; j++) { - var shape = body.shapeArr[j]; - var index = shape.findVertexByPoint(p, minDist); - if (index != -1) { - var vertex = (shape.id << 16) | index; - if (refVertexId == -1) { - return vertex; - } - - if (firstVertexId == -1) { - firstVertexId = vertex; - } - - if (vertex == refVertexId) { - refVertexId = -1; - } - } - } - } - - return firstVertexId; -} - -Space.prototype.findEdgeByPoint = function(p, minDist, refEdgeId) { - var firstEdgeId = -1; - - refEdgeId = refEdgeId || -1; - - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - for (var j = 0; j < body.shapeArr.length; j++) { - var shape = body.shapeArr[j]; - if (shape.type != Shape.TYPE_POLY) { - continue; - } - - var index = shape.findEdgeByPoint(p, minDist); - if (index != -1) { - var edge = (shape.id << 16) | index; - if (refEdgeId == -1) { - return edge; - } - - if (firstEdgeId == -1) { - firstEdgeId = edge; - } - - if (edge == refEdgeId) { - refEdgeId = -1; - } - } - } - } - - return firstEdgeId; -} - -Space.prototype.findJointByPoint = function(p, minDist, refJointId) { - var firstJointId = -1; - - var dsq = minDist * minDist; - - refJointId = refJointId || -1; - - for (var i = 0; i < this.jointArr.length; i++) { - var joint = this.jointArr[i]; - if (!joint) { - continue; - } - - var jointId = -1; - - if (vec2.distsq(p, joint.getWorldAnchor1()) < dsq) { - jointId = (joint.id << 16 | 0); - } - else if (vec2.distsq(p, joint.getWorldAnchor2()) < dsq) { - jointId = (joint.id << 16 | 1); - } - - if (jointId != -1) { - if (refJointId == -1) { - return jointId; - } - - if (firstJointId == -1) { - firstJointId = jointId; - } - - if (jointId == refJointId) { - refJointId = -1; - } - } - } - - return firstJointId; -} - -Space.prototype.findContactSolver = function(shape1, shape2) { - - for (var i = 0; i < this.contactSolverArr.length; i++) { - var contactSolver = this.contactSolverArr[i]; - if (shape1 == contactSolver.shape1 && shape2 == contactSolver.shape2) { - return contactSolver; - } - } - - return null; -} - -Space.dump = function (phase, body) { - - var s = "\n\nPhase: " + phase + "\n"; - s += "Position: " + body.p.toString() + "\n"; - s += "Velocity: " + body.v.toString() + "\n"; - s += "Angle: " + body.a + "\n"; - s += "Force: " + body.f.toString() + "\n"; - s += "Torque: " + body.t + "\n"; - s += "Bounds: " + body.bounds.toString() + "\n"; - s += "Shape ***\n"; - s += "Vert 0: " + body.shapeArr[0].verts[0].toString() + "\n"; - s += "Vert 1: " + body.shapeArr[0].verts[1].toString() + "\n"; - s += "Vert 2: " + body.shapeArr[0].verts[2].toString() + "\n"; - s += "Vert 3: " + body.shapeArr[0].verts[3].toString() + "\n"; - s += "TVert 0: " + body.shapeArr[0].tverts[0].toString() + "\n"; - s += "TVert 1: " + body.shapeArr[0].tverts[1].toString() + "\n"; - s += "TVert 2: " + body.shapeArr[0].tverts[2].toString() + "\n"; - s += "TVert 3: " + body.shapeArr[0].tverts[3].toString() + "\n"; - s += "Plane 0: " + body.shapeArr[0].planes[0].n.toString() + "\n"; - s += "Plane 1: " + body.shapeArr[0].planes[1].n.toString() + "\n"; - s += "Plane 2: " + body.shapeArr[0].planes[2].n.toString() + "\n"; - s += "Plane 3: " + body.shapeArr[0].planes[3].n.toString() + "\n"; - s += "TPlane 0: " + body.shapeArr[0].tplanes[0].n.toString() + "\n"; - s += "TPlane 1: " + body.shapeArr[0].tplanes[1].n.toString() + "\n"; - s += "TPlane 2: " + body.shapeArr[0].tplanes[2].n.toString() + "\n"; - s += "TPlane 3: " + body.shapeArr[0].tplanes[3].n.toString() + "\n"; - - this.log.push(s); - -} - -Space.prototype.genTemporalContactSolvers = function() { - - var t0 = Date.now(); - var newContactSolverArr = []; - - this.numContacts = 0; - - for (var body1_index = 0; body1_index < this.bodyArr.length; body1_index++) { - var body1 = this.bodyArr[body1_index]; - if (!body1) { - continue; - } - - body1.stepCount = this.stepCount; - - for (var body2_index = 0; body2_index < this.bodyArr.length; body2_index++) { - var body2 = this.bodyArr[body2_index]; - if (!body2) { - continue; - } - - if (body1.stepCount == body2.stepCount) { - continue; - } - - var active1 = body1.isAwake() && !body1.isStatic(); - var active2 = body2.isAwake() && !body2.isStatic(); - - if (!active1 && !active2) { - continue; - } - - if (!body1.isCollidable(body2)) { - continue; - } - - if (!body1.bounds.intersectsBounds(body2.bounds)) { - continue; - } - - for (var i = 0; i < body1.shapeArr.length; i++) { - for (var j = 0; j < body2.shapeArr.length; j++) { - var shape1 = body1.shapeArr[i]; - var shape2 = body2.shapeArr[j]; - - var contactArr = []; - if (!collision.collide(shape1, shape2, contactArr)) { - continue; - } - - if (shape1.type > shape2.type) { - var temp = shape1; - shape1 = shape2; - shape2 = temp; - } - - this.numContacts += contactArr.length; - - var contactSolver = this.findContactSolver(shape1, shape2); - - if (contactSolver) { - contactSolver.update(contactArr); - newContactSolverArr.push(contactSolver); - } - else { - body1.awake(true); - body2.awake(true); - - var newContactSolver = new ContactSolver(shape1, shape2); - newContactSolver.contactArr = contactArr; - newContactSolver.e = Math.max(shape1.e, shape2.e); - newContactSolver.u = Math.sqrt(shape1.u * shape2.u); - newContactSolverArr.push(newContactSolver); - } - } - } - } - } - - stats.timeCollision = Date.now() - t0; - - return newContactSolverArr; -} - -Space.prototype.initSolver = function(dt, dt_inv, warmStarting) { - - var t0 = Date.now(); - - // Initialize contact solvers - for (var i = 0; i < this.contactSolverArr.length; i++) { - this.contactSolverArr[i].initSolver(dt_inv); - } - - // Initialize joint solver - for (var i = 0; i < this.jointArr.length; i++) { - if (this.jointArr[i]) { - this.jointArr[i].initSolver(dt, warmStarting); - } - } - - // Warm starting (apply cached impulse) - if (warmStarting) { - for (var i = 0; i < this.contactSolverArr.length; i++) { - this.contactSolverArr[i].warmStart(); - } - } - - stats.timeInitSolver = Date.now() - t0; -} - -Space.prototype.velocitySolver = function(iteration) { - - var t0 = Date.now(); - - for (var i = 0; i < iteration; i++) { - for (var j = 0; j < this.jointArr.length; j++) { - if (this.jointArr[j]) { - this.jointArr[j].solveVelocityConstraints(); - } - } - - for (var j = 0; j < this.contactSolverArr.length; j++) { - this.contactSolverArr[j].solveVelocityConstraints(); - } - } - - stats.timeVelocitySolver = Date.now() - t0; -} - -Space.prototype.positionSolver = function(iteration) { - var t0 = Date.now(); - - var positionSolved = false; - - stats.positionIterations = 0; - - for (var i = 0; i < iteration; i++) { - var contactsOk = true; - var jointsOk = true; - - for (var j = 0; j < this.contactSolverArr.length; j++) { - var contactOk = this.contactSolverArr[j].solvePositionConstraints(); - contactsOk = contactOk && contactsOk; - } - - for (var j = 0; j < this.jointArr.length; j++) { - if (this.jointArr[j]) { - var jointOk = this.jointArr[j].solvePositionConstraints(); - jointsOk = jointOk && jointsOk; - } - } - - if (contactsOk && jointsOk) { - // exit early if the position errors are small - positionSolved = true; - break; - } - - stats.positionIterations++; - } - - stats.timePositionSolver = Date.now() - t0; - - return positionSolved; -} - -Space.prototype.step = function(dt, vel_iteration, pos_iteration, warmStarting, allowSleep) { - - var dt_inv = 1 / dt; - - this.stepCount++; - - // Generate contact & contactSolver - this.contactSolverArr = this.genTemporalContactSolvers(); - - // Initialize contacts & joints solver - this.initSolver(dt, dt_inv, warmStarting); - - // Intergrate velocity - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - if (body.isDynamic() && body.isAwake()) { - body.updateVelocity(this.gravity, dt, this.damping); - } - } - - for (var i = 0; i < this.jointArr.length; i++) { - var joint = this.jointArr[i]; - if (!joint) { - continue; - } - - var body1 = joint.body1; - var body2 = joint.body2; - - var awake1 = body1.isAwake() && !body1.isStatic(); - var awake2 = body2.isAwake() && !body2.isStatic(); - - if (awake1 ^ awake2) { - if (!awake1) - body1.awake(true); - if (!awake2) - body2.awake(true); - } - } - - // Iterative velocity constraints solver - this.velocitySolver(vel_iteration); - - // Intergrate position - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue - } - - if (body.isDynamic() && body.isAwake()) { - body.updatePosition(dt); - } - } - - // Process breakable joint - for (var i = 0; i < this.jointArr.length; i++) { - var joint = this.jointArr[i]; - if (!joint) { - continue; - } - - if (joint.breakable) { - if (joint.getReactionForce(dt_inv).lengthsq() >= joint.maxForce * joint.maxForce) - this.removeJoint(joint); - } - } - - // Iterative position constraints solver - var positionSolved = this.positionSolver(pos_iteration); - - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - body.syncTransform(); - } - - // Post solve collision callback - for (var i = 0; i < this.contactSolverArr.length; i++) { - var arb = this.contactSolverArr[i]; - //this.postSolve(arb); - } - - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - if (body.isDynamic() && body.isAwake()) { - body.cacheData(); - } - } - - // Process sleeping - if (allowSleep) { - var minSleepTime = 999999; - - var linTolSqr = Space.SLEEP_LINEAR_TOLERANCE * Space.SLEEP_LINEAR_TOLERANCE; - var angTolSqr = Space.SLEEP_ANGULAR_TOLERANCE * Space.SLEEP_ANGULAR_TOLERANCE; - - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - if (!body.isDynamic()) { - continue; - } - - if (body.w * body.w > angTolSqr || body.v.dot(body.v) > linTolSqr) { - body.sleepTime = 0; - minSleepTime = 0; - } - else { - body.sleepTime += dt; - minSleepTime = Math.min(minSleepTime, body.sleepTime); - } - } - - if (positionSolved && minSleepTime >= Space.TIME_TO_SLEEP) { - for (var i = 0; i < this.bodyArr.length; i++) { - var body = this.bodyArr[i]; - if (!body) { - continue; - } - - body.awake(false); - } - } - } - -} - diff --git a/src/physics/advanced/Util.js b/src/physics/advanced/Util.js deleted file mode 100644 index 93b0e793..00000000 --- a/src/physics/advanced/Util.js +++ /dev/null @@ -1,147 +0,0 @@ -/* -* Copyright (c) 2012 Ju Hyung Lee -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of this software -* and associated documentation files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -function areaForCircle(radius_outer, radius_inner) { - return Math.PI * (radius_outer * radius_outer - radius_inner * radius_inner); -} - -function inertiaForCircle(mass, center, radius_outer, radius_inner) { - return mass * ((radius_outer * radius_outer + radius_inner * radius_inner) * 0.5 + center.lengthsq()); -} - -function areaForSegment(a, b, radius) { - return radius * (Math.PI * radius + 2 * vec2.dist(a, b)); -} - -function centroidForSegment(a, b) { - return vec2.scale(vec2.add(a, b), 0.5); -} - -function inertiaForSegment(mass, a, b) { - var distsq = vec2.distsq(b, a); - var offset = vec2.scale(vec2.add(a, b), 0.5); - - return mass * (distsq / 12 + offset.lengthsq()); -} - -function areaForPoly(verts) { - var area = 0; - for (var i = 0; i < verts.length; i++) { - area += vec2.cross(verts[i], verts[(i + 1) % verts.length]); - } - - return area / 2; -} - -function centroidForPoly(verts) { - var area = 0; - var vsum = new vec2(0, 0); - - for (var i = 0; i < verts.length; i++) { - var v1 = verts[i]; - var v2 = verts[(i + 1) % verts.length]; - var cross = vec2.cross(v1, v2); - - area += cross; - vsum.addself(vec2.scale(vec2.add(v1, v2), cross)); - } - - return vec2.scale(vsum, 1 / (3 * area)); -} - -function inertiaForPoly(mass, verts, offset) { - var sum1 = 0; - var sum2 = 0; - - for (var i = 0; i < verts.length; i++) { - var v1 = vec2.add(verts[i], offset); - var v2 = vec2.add(verts[(i+1) % verts.length], offset); - - var a = vec2.cross(v2, v1); - var b = vec2.dot(v1, v1) + vec2.dot(v1, v2) + vec2.dot(v2, v2); - - sum1 += a * b; - sum2 += a; - } - - return (mass * sum1) / (6 * sum2); -} - -function inertiaForBox(mass, w, h) { - return mass * (w * w + h * h) / 12; -} - -// Create the convex hull using the Gift wrapping algorithm -// http://en.wikipedia.org/wiki/Gift_wrapping_algorithm -function createConvexHull(points) { - // Find the right most point on the hull - var i0 = 0; - var x0 = points[0].x; - for (var i = 1; i < points.length; i++) { - var x = points[i].x; - if (x > x0 || (x == x0 && points[i].y < points[i0].y)) { - i0 = i; - x0 = x; - } - } - - var n = points.length; - var hull = []; - var m = 0; - var ih = i0; - - while (1) { - hull[m] = ih; - - var ie = 0; - for (var j = 1; j < n; j++) { - if (ie == ih) { - ie = j; - continue; - } - - var r = vec2.sub(points[ie], points[hull[m]]); - var v = vec2.sub(points[j], points[hull[m]]); - var c = vec2.cross(r, v); - if (c < 0) { - ie = j; - } - - // Collinearity check - if (c == 0 && v.lengthsq() > r.lengthsq()) { - ie = j; - } - } - - m++; - ih = ie; - - if (ie == i0) { - break; - } - } - - // Copy vertices - var newPoints = []; - for (var i = 0; i < m; ++i) { - newPoints.push(points[hull[i]]); - } - - return newPoints; -} \ No newline at end of file diff --git a/src/physics/advanced/collision/Broadphase.js b/src/physics/advanced/collision/Broadphase.js new file mode 100644 index 00000000..b0d6129c --- /dev/null +++ b/src/physics/advanced/collision/Broadphase.js @@ -0,0 +1,43 @@ +var vec2 = require('../math/vec2') +, Nearphase = require('./Nearphase') +, Shape = require('./../shapes/Shape') + +module.exports = Broadphase; + +/** + * Base class for broadphase implementations. + * @class Broadphase + * @constructor + */ +function Broadphase(){ + this.result = []; +}; + +/** + * Get all potential intersecting body pairs. + * @method getCollisionPairs + * @param {World} world The world to search in. + * @return {Array} An array of the bodies, ordered in pairs. Example: A result of [a,b,c,d] means that the potential pairs are: (a,b), (c,d). + */ +Broadphase.prototype.getCollisionPairs = function(world){ + throw new Error("getCollisionPairs must be implemented in a subclass!"); +}; + +// Temp things +var dist = vec2.create(), + worldNormal = vec2.create(), + yAxis = vec2.fromValues(0,1); + +/** + * Check whether the bounding radius of two bodies overlap. + * @method boundingRadiusCheck + * @param {Body} bodyA + * @param {Body} bodyB + * @return {Boolean} + */ +Broadphase.boundingRadiusCheck = function(bodyA, bodyB){ + vec2.sub(dist, bodyA.position, bodyB.position); + var d2 = vec2.squaredLength(dist), + r = bodyA.boundingRadius + bodyB.boundingRadius; + return d2 <= r*r; +}; diff --git a/src/physics/advanced/collision/GridBroadphase.js b/src/physics/advanced/collision/GridBroadphase.js new file mode 100644 index 00000000..d0a7172b --- /dev/null +++ b/src/physics/advanced/collision/GridBroadphase.js @@ -0,0 +1,159 @@ +var Circle = require('../shapes/Circle') +, Plane = require('../shapes/Plane') +, Particle = require('../shapes/Particle') +, Broadphase = require('../collision/Broadphase') +, vec2 = require('../math/vec2') + +module.exports = GridBroadphase; + +/** + * Broadphase that uses axis-aligned bins. + * @class GridBroadphase + * @constructor + * @extends Broadphase + * @param {number} xmin Lower x bound of the grid + * @param {number} xmax Upper x bound + * @param {number} ymin Lower y bound + * @param {number} ymax Upper y bound + * @param {number} nx Number of bins along x axis + * @param {number} ny Number of bins along y axis + * @todo test + */ +function GridBroadphase(xmin,xmax,ymin,ymax,nx,ny){ + Broadphase.apply(this); + + nx = nx || 10; + ny = ny || 10; + + this.binsizeX = (xmax-xmin) / nx; + this.binsizeY = (ymax-ymin) / ny; + this.nx = nx; + this.ny = ny; + this.xmin = xmin; + this.ymin = ymin; + this.xmax = xmax; + this.ymax = ymax; +}; +GridBroadphase.prototype = new Broadphase(); + +/** + * Get a bin index given a world coordinate + * @method getBinIndex + * @param {Number} x + * @param {Number} y + * @return {Number} Integer index + */ +GridBroadphase.prototype.getBinIndex = function(x,y){ + var nx = this.nx, + ny = this.ny, + xmin = this.xmin, + ymin = this.ymin, + xmax = this.xmax, + ymax = this.ymax; + + var xi = Math.floor(nx * (x - xmin) / (xmax-xmin)); + var yi = Math.floor(ny * (y - ymin) / (ymax-ymin)); + return xi*ny + yi; +} + +/** + * Get collision pairs. + * @method getCollisionPairs + * @param {World} world + * @return {Array} + */ +GridBroadphase.prototype.getCollisionPairs = function(world){ + var result = [], + collidingBodies = world.bodies, + Ncolliding = Ncolliding=collidingBodies.length, + binsizeX = this.binsizeX, + binsizeY = this.binsizeY; + + var bins=[], Nbins=nx*ny; + for(var i=0; i= 0 && xi*(ny-1) + yi < Nbins) + bins[ xi*(ny-1) + yi ].push(bi); + } + } + } else if(si instanceof Plane){ + // Put in all bins for now + if(bi.angle == 0){ + var y = bi.position[1]; + for(var j=0; j!==Nbins && ymin+binsizeY*(j-1) pos0 && pos < pos1){ + // We got contact! + + if(justTest) return true; + + var c = this.createContactEquation(circleBody,lineBody); + + vec2.scale(c.ni, orthoDist, -1); + vec2.normalize(c.ni, c.ni); + + vec2.scale( c.ri, c.ni, circleRadius); + add(c.ri, c.ri, circleOffset); + sub(c.ri, c.ri, circleBody.position); + + sub(c.rj, projectedPoint, lineOffset); + add(c.rj, c.rj, lineOffset); + sub(c.rj, c.rj, lineBody.position); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + + return true; + } + } + + // Add corner + var verts = [worldVertex0, worldVertex1]; + + for(var i=0; i 0){ + + // Now project the circle onto the edge + vec2.scale(orthoDist, worldTangent, d); + sub(projectedPoint, circleOffset, orthoDist); + + // Check if the point is within the edge span + var pos = dot(worldEdgeUnit, projectedPoint); + var pos0 = dot(worldEdgeUnit, worldVertex0); + var pos1 = dot(worldEdgeUnit, worldVertex1); + + if(pos > pos0 && pos < pos1){ + // We got contact! + + var c = this.createContactEquation(circleBody,convexBody); + + vec2.scale(c.ni, orthoDist, -1); + vec2.normalize(c.ni, c.ni); + + vec2.scale( c.ri, c.ni, circleShape.radius); + add(c.ri, c.ri, circleOffset); + sub(c.ri, c.ri, circleBody.position); + + sub( c.rj, projectedPoint, convexOffset); + add(c.rj, c.rj, convexOffset); + sub(c.rj, c.rj, convexBody.position); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push( this.createFrictionFromContact(c) ); + } + + return true; + } + } + } + + // Check all vertices + for(var i=0; i= 0){ + + // Now project the particle onto the edge + vec2.scale(orthoDist, worldTangent, d); + sub(projectedPoint, particleOffset, orthoDist); + + // Check if the point is within the edge span + var pos = dot(worldEdgeUnit, projectedPoint); + var pos0 = dot(worldEdgeUnit, worldVertex0); + var pos1 = dot(worldEdgeUnit, worldVertex1); + + if(pos > pos0 && pos < pos1){ + // We got contact! + if(justTest) return true; + + if(closestEdgeDistance === null || d*d r*r){ + return false; + } + + if(justTest) return true; + + var c = this.createContactEquation(bodyA,bodyB); + sub(c.ni, offsetB, offsetA); + vec2.normalize(c.ni,c.ni); + + vec2.scale( c.ri, c.ni, shapeA.radius); + vec2.scale( c.rj, c.ni, -shapeB.radius); + + add(c.ri, c.ri, offsetA); + sub(c.ri, c.ri, bodyA.position); + + add(c.rj, c.rj, offsetB); + sub(c.rj, c.rj, bodyB.position); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + return true; +}; + +/** + * Convex/Plane nearphase + * @method convexPlane + * @param {Body} bi + * @param {Convex} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Plane} sj + * @param {Array} xj + * @param {Number} aj + */ +Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var convexBody = bi, + convexOffset = xi, + convexShape = si, + convexAngle = ai, + planeBody = bj, + planeShape = sj, + planeOffset = xj, + planeAngle = aj; + + var worldVertex = tmp1, + worldNormal = tmp2, + dist = tmp3; + + var numReported = 0; + vec2.rotate(worldNormal, yAxis, planeAngle); + + for(var i=0; i= 2) + break; + } + } + return numReported > 0; +}; + +/** + * Nearphase for particle vs plane + * @method particlePlane + * @param {Body} bi The particle body + * @param {Particle} si Particle shape + * @param {Array} xi World position for the particle + * @param {Number} ai World angle for the particle + * @param {Body} bj Plane body + * @param {Plane} sj Plane shape + * @param {Array} xj World position for the plane + * @param {Number} aj World angle for the plane + */ +Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ + var particleBody = bi, + particleShape = si, + particleOffset = xi, + planeBody = bj, + planeShape = sj, + planeOffset = xj, + planeAngle = aj; + + var dist = tmp1, + worldNormal = tmp2; + + planeAngle = planeAngle || 0; + + sub(dist, particleOffset, planeOffset); + vec2.rotate(worldNormal, yAxis, planeAngle); + + var d = dot(dist, worldNormal); + + if(d > 0) return false; + if(justTest) return true; + + var c = this.createContactEquation(planeBody,particleBody); + + vec2.copy(c.ni, worldNormal); + vec2.scale( dist, c.ni, d ); + // dist is now the distance vector in the normal direction + + // ri is the particle position projected down onto the plane, from the plane center + sub( c.ri, particleOffset, dist); + sub( c.ri, c.ri, planeBody.position); + + // rj is from the body center to the particle center + sub( c.rj, particleOffset, particleBody.position ); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + return true; +}; + +/** + * Circle/Particle nearphase + * @method circleParticle + * @param {Body} bi + * @param {Circle} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Particle} sj + * @param {Array} xj + * @param {Number} aj + */ +Nearphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ + var circleBody = bi, + circleShape = si, + circleOffset = xi, + particleBody = bj, + particleShape = sj, + particleOffset = xj, + dist = tmp1; + + sub(dist, particleOffset, circleOffset); + if(vec2.squaredLength(dist) > circleShape.radius*circleShape.radius) return false; + if(justTest) return true; + + var c = this.createContactEquation(circleBody,particleBody); + vec2.copy(c.ni, dist); + vec2.normalize(c.ni,c.ni); + + // Vector from circle to contact point is the normal times the circle radius + vec2.scale(c.ri, c.ni, circleShape.radius); + add(c.ri, c.ri, circleOffset); + sub(c.ri, c.ri, circleBody.position); + + // Vector from particle center to contact point is zero + sub(c.rj, particleOffset, particleBody.position); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + + return true; +}; + +var capsulePlane_tmpCircle = new Circle(1), + capsulePlane_tmp1 = vec2.create(), + capsulePlane_tmp2 = vec2.create(), + capsulePlane_tmp3 = vec2.create(); +Nearphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var end1 = capsulePlane_tmp1, + end2 = capsulePlane_tmp2, + circle = capsulePlane_tmpCircle, + dst = capsulePlane_tmp3; + + // Compute world end positions + vec2.set(end1, -si.length/2, 0); + vec2.rotate(end1,end1,ai); + add(end1,end1,xi); + + vec2.set(end2, si.length/2, 0); + vec2.rotate(end2,end2,ai); + add(end2,end2,xi); + + circle.radius = si.radius; + + // Do nearphase as two circles + this.circlePlane(bi,circle,end1,0, bj,sj,xj,aj); + this.circlePlane(bi,circle,end2,0, bj,sj,xj,aj); +}; + +/** + * Creates ContactEquations and FrictionEquations for a collision. + * @method circlePlane + * @param {Body} bi The first body that should be connected to the equations. + * @param {Circle} si The circle shape participating in the collision. + * @param {Array} xi Extra offset to take into account for the Shape, in addition to the one in circleBody.position. Will *not* be rotated by circleBody.angle (maybe it should, for sake of homogenity?). Set to null if none. + * @param {Body} bj The second body that should be connected to the equations. + * @param {Plane} sj The Plane shape that is participating + * @param {Array} xj Extra offset for the plane shape. + * @param {Number} aj Extra angle to apply to the plane + */ +Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var circleBody = bi, + circleShape = si, + circleOffset = xi, // Offset from body center, rotated! + planeBody = bj, + shapeB = sj, + planeOffset = xj, + planeAngle = aj; + + planeAngle = planeAngle || 0; + + // Vector from plane to circle + var planeToCircle = tmp1, + worldNormal = tmp2, + temp = tmp3; + + sub(planeToCircle, circleOffset, planeOffset); + + // World plane normal + vec2.rotate(worldNormal, yAxis, planeAngle); + + // Normal direction distance + var d = dot(worldNormal, planeToCircle); + + if(d > circleShape.radius) return false; // No overlap. Abort. + + // Create contact + var contact = this.createContactEquation(planeBody,circleBody); + + // ni is the plane world normal + vec2.copy(contact.ni, worldNormal); + + // rj is the vector from circle center to the contact point + vec2.scale(contact.rj, contact.ni, -circleShape.radius); + add(contact.rj, contact.rj, circleOffset); + sub(contact.rj, contact.rj, circleBody.position); + + // ri is the distance from plane center to contact. + vec2.scale(temp, contact.ni, d); + sub(contact.ri, planeToCircle, temp ); // Subtract normal distance vector from the distance vector + add(contact.ri, contact.ri, planeOffset); + sub(contact.ri, contact.ri, planeBody.position); + + this.contactEquations.push(contact); + + if(this.enableFriction){ + this.frictionEquations.push( this.createFrictionFromContact(contact) ); + } + + return true; +}; + + +/** + * Convex/convex nearphase.See this article for more info. + * @method convexConvex + * @param {Body} bi + * @param {Convex} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Convex} sj + * @param {Array} xj + * @param {Number} aj + */ +Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var sepAxis = tmp1, + worldPoint = tmp2, + worldPoint0 = tmp3, + worldPoint1 = tmp4, + worldEdge = tmp5, + projected = tmp6, + penetrationVec = tmp7, + dist = tmp8, + worldNormal = tmp9; + + var found = Nearphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); + if(!found) return false; + + // Make sure the separating axis is directed from shape i to shape j + sub(dist,xj,xi); + if(dot(sepAxis,dist) > 0){ + vec2.scale(sepAxis,sepAxis,-1); + } + + // Find edges with normals closest to the separating axis + var closestEdge1 = Nearphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis + closestEdge2 = Nearphase.getClosestEdge(sj,aj,sepAxis); + + if(closestEdge1==-1 || closestEdge2==-1) return false; + + // Loop over the shapes + for(var k=0; k<2; k++){ + + var closestEdgeA = closestEdge1, + closestEdgeB = closestEdge2, + shapeA = si, shapeB = sj, + offsetA = xi, offsetB = xj, + angleA = ai, angleB = aj, + bodyA = bi, bodyB = bj; + + if(k==0){ + // Swap! + var tmp; + tmp = closestEdgeA; closestEdgeA = closestEdgeB; closestEdgeB = tmp; + tmp = shapeA; shapeA = shapeB; shapeB = tmp; + tmp = offsetA; offsetA = offsetB; offsetB = tmp; + tmp = angleA; angleA = angleB; angleB = tmp; + tmp = bodyA; bodyA = bodyB; bodyB = tmp; + } + + // Loop over 2 points in convex B + for(var j=closestEdgeB; j max) max = value; + if(min === null || value < min) min = value; + } + + if(min > max){ + var t = min; + min = max; + max = t; + } + + // Project the position of the body onto the axis - need to add this to the result + var offset = dot(convexOffset, worldAxis); + + vec2.set( result, min + offset, max + offset); +}; + +// .findSeparatingAxis is called by other functions, need local tmp vectors +var fsa_tmp1 = vec2.fromValues(0,0) +, fsa_tmp2 = vec2.fromValues(0,0) +, fsa_tmp3 = vec2.fromValues(0,0) +, fsa_tmp4 = vec2.fromValues(0,0) +, fsa_tmp5 = vec2.fromValues(0,0) +, fsa_tmp6 = vec2.fromValues(0,0) + +/** + * Find a separating axis between the shapes, that maximizes the separating distance between them. + * @method findSeparatingAxis + * @static + * @param {Convex} c1 + * @param {Array} offset1 + * @param {Number} angle1 + * @param {Convex} c2 + * @param {Array} offset2 + * @param {Number} angle2 + * @param {Array} sepAxis The resulting axis + * @return {Boolean} Whether the axis could be found. + */ +Nearphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){ + var maxDist = null, + overlap = false, + found = false, + edge = fsa_tmp1, + worldPoint0 = fsa_tmp2, + worldPoint1 = fsa_tmp3, + normal = fsa_tmp4, + span1 = fsa_tmp5, + span2 = fsa_tmp6; + + for(var j=0; j!==2; j++){ + var c = c1, + angle = angle1; + if(j===1){ + c = c2; + angle = angle2; + } + + for(var i=0; i!==c.vertices.length; i++){ + // Get the world edge + vec2.rotate(worldPoint0, c.vertices[i], angle); + vec2.rotate(worldPoint1, c.vertices[(i+1)%c.vertices.length], angle); + + sub(edge, worldPoint1, worldPoint0); + + // Get normal - just rotate 90 degrees since vertices are given in CCW + vec2.rotate(normal, edge, -Math.PI / 2); + vec2.normalize(normal,normal); + + // Project hulls onto that normal + Nearphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1); + Nearphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2); + + // Order by span position + var a=span1, + b=span2, + swapped = false; + if(span1[0] > span2[0]){ + b=span1; + a=span2; + swapped = true; + } + + // Get separating distance + var dist = b[0] - a[1]; + overlap = dist < 0; + + if(maxDist===null || dist > maxDist){ + vec2.copy(sepAxis, normal); + maxDist = dist; + found = overlap; + } + } + } + + return found; +}; + +// .getClosestEdge is called by other functions, need local tmp vectors +var gce_tmp1 = vec2.fromValues(0,0) +, gce_tmp2 = vec2.fromValues(0,0) +, gce_tmp3 = vec2.fromValues(0,0) + +/** + * Get the edge that has a normal closest to an axis. + * @method getClosestEdge + * @static + * @param {Convex} c + * @param {Number} angle + * @param {Array} axis + * @param {Boolean} flip + * @return {Number} Index of the edge that is closest. This index and the next spans the resulting edge. Returns -1 if failed. + */ +Nearphase.getClosestEdge = function(c,angle,axis,flip){ + var localAxis = gce_tmp1, + edge = gce_tmp2, + normal = gce_tmp3; + + // Convert the axis to local coords of the body + vec2.rotate(localAxis, axis, -angle); + if(flip){ + vec2.scale(localAxis,localAxis,-1); + } + + var closestEdge = -1, + N = c.vertices.length, + halfPi = Math.PI / 2; + for(var i=0; i!==N; i++){ + // Get the edge + sub(edge, c.vertices[(i+1)%N], c.vertices[i%N]); + + // Get normal - just rotate 90 degrees since vertices are given in CCW + vec2.rotate(normal, edge, -halfPi); + vec2.normalize(normal,normal); + + var d = dot(normal,localAxis); + if(closestEdge == -1 || d > maxDot){ + closestEdge = i % N; + maxDot = d; + } + } + + return closestEdge; +}; + diff --git a/src/physics/advanced/collision/QuadTree.js b/src/physics/advanced/collision/QuadTree.js new file mode 100644 index 00000000..b0a48a73 --- /dev/null +++ b/src/physics/advanced/collision/QuadTree.js @@ -0,0 +1,376 @@ +var Plane = require("../shapes/Plane"); +var Broadphase = require("../collision/Broadphase"); + +module.exports = { + QuadTree : QuadTree, + Node : Node, + BoundsNode : BoundsNode, +}; + +/** + * QuadTree data structure. See https://github.com/mikechambers/ExamplesByMesh/tree/master/JavaScript/QuadTree + * @class QuadTree + * @constructor + * @param {Object} An object representing the bounds of the top level of the QuadTree. The object + * should contain the following properties : x, y, width, height + * @param {Boolean} pointQuad Whether the QuadTree will contain points (true), or items with bounds + * (width / height)(false). Default value is false. + * @param {Number} maxDepth The maximum number of levels that the quadtree will create. Default is 4. + * @param {Number} maxChildren The maximum number of children that a node can contain before it is split into sub-nodes. + */ +function QuadTree(bounds, pointQuad, maxDepth, maxChildren){ + var node; + if(pointQuad){ + node = new Node(bounds, 0, maxDepth, maxChildren); + } else { + node = new BoundsNode(bounds, 0, maxDepth, maxChildren); + } + + /** + * The root node of the QuadTree which covers the entire area being segmented. + * @property root + * @type Node + */ + this.root = node; +} + +/** + * Inserts an item into the QuadTree. + * @method insert + * @param {Object|Array} item The item or Array of items to be inserted into the QuadTree. The item should expose x, y + * properties that represents its position in 2D space. + */ +QuadTree.prototype.insert = function(item){ + if(item instanceof Array){ + var len = item.length; + for(var i = 0; i < len; i++){ + this.root.insert(item[i]); + } + } else { + this.root.insert(item); + } +} + +/** + * Clears all nodes and children from the QuadTree + * @method clear + */ +QuadTree.prototype.clear = function(){ + this.root.clear(); +} + +/** + * Retrieves all items / points in the same node as the specified item / point. If the specified item + * overlaps the bounds of a node, then all children in both nodes will be returned. + * @method retrieve + * @param {Object} item An object representing a 2D coordinate point (with x, y properties), or a shape + * with dimensions (x, y, width, height) properties. + */ +QuadTree.prototype.retrieve = function(item){ + //get a copy of the array of items + var out = this.root.retrieve(item).slice(0); + return out; +} + +QuadTree.prototype.getCollisionPairs = function(world){ + + var result = []; + + // Add all bodies + this.insert(world.bodies); + + /* + console.log("bodies",world.bodies.length); + console.log("maxDepth",this.root.maxDepth,"maxChildren",this.root.maxChildren); + */ + + for(var i=0; i!==world.bodies.length; i++){ + var b = world.bodies[i], + items = this.retrieve(b); + + //console.log("items",items.length); + + // Check results + for(var j=0, len=items.length; j!==len; j++){ + var item = items[j]; + + if(b === item) continue; // Do not add self + + // Check if they were already added + var found = false; + for(var k=0, numAdded=result.length; k= this.maxDepth) && len > this.maxChildren) { + this.subdivide(); + + for(var i = 0; i < len; i++){ + this.insert(this.children[i]); + } + + this.children.length = 0; + } +} + +Node.prototype.retrieve = function(item){ + if(this.nodes.length){ + var index = this.findIndex(item); + return this.nodes[index].retrieve(item); + } + + return this.children; +} + +Node.prototype.findIndex = function(item){ + var b = this.bounds; + var left = (item.position[0]-item.boundingRadius > b.x + b.width / 2) ? false : true; + var top = (item.position[1]-item.boundingRadius > b.y + b.height / 2) ? false : true; + + if(item instanceof Plane){ + left = top = false; // Will overlap the left/top boundary since it is infinite + } + + //top left + var index = Node.TOP_LEFT; + if(left){ + if(!top){ + index = Node.BOTTOM_LEFT; + } + } else { + if(top){ + index = Node.TOP_RIGHT; + } else { + index = Node.BOTTOM_RIGHT; + } + } + + return index; +} + + +Node.prototype.subdivide = function(){ + var depth = this.depth + 1; + + var bx = this.bounds.x; + var by = this.bounds.y; + + //floor the values + var b_w_h = (this.bounds.width / 2); + var b_h_h = (this.bounds.height / 2); + var bx_b_w_h = bx + b_w_h; + var by_b_h_h = by + b_h_h; + + //top left + this.nodes[Node.TOP_LEFT] = new this.classConstructor({ + x:bx, + y:by, + width:b_w_h, + height:b_h_h + }, + depth); + + //top right + this.nodes[Node.TOP_RIGHT] = new this.classConstructor({ + x:bx_b_w_h, + y:by, + width:b_w_h, + height:b_h_h + }, + depth); + + //bottom left + this.nodes[Node.BOTTOM_LEFT] = new this.classConstructor({ + x:bx, + y:by_b_h_h, + width:b_w_h, + height:b_h_h + }, + depth); + + + //bottom right + this.nodes[Node.BOTTOM_RIGHT] = new this.classConstructor({ + x:bx_b_w_h, + y:by_b_h_h, + width:b_w_h, + height:b_h_h + }, + depth); +} + +Node.prototype.clear = function(){ + this.children.length = 0; + + var len = this.nodes.length; + for(var i = 0; i < len; i++){ + this.nodes[i].clear(); + } + + this.nodes.length = 0; +} + + +// BoundsQuadTree + +function BoundsNode(bounds, depth, maxChildren, maxDepth){ + Node.call(this, bounds, depth, maxChildren, maxDepth); + this.stuckChildren = []; +} + +BoundsNode.prototype = new Node(); +BoundsNode.prototype.classConstructor = BoundsNode; +BoundsNode.prototype.stuckChildren = null; + +//we use this to collect and conctenate items being retrieved. This way +//we dont have to continuously create new Array instances. +//Note, when returned from QuadTree.retrieve, we then copy the array +BoundsNode.prototype.out = []; + +BoundsNode.prototype.insert = function(item){ + if(this.nodes.length){ + var index = this.findIndex(item); + var node = this.nodes[index]; + + /* + console.log("radius:",item.boundingRadius); + console.log("item x:",item.position[0] - item.boundingRadius,"x range:",node.bounds.x,node.bounds.x+node.bounds.width); + console.log("item y:",item.position[1] - item.boundingRadius,"y range:",node.bounds.y,node.bounds.y+node.bounds.height); + */ + + //todo: make _bounds bounds + if( !(item instanceof Plane) && // Plane is infinite.. Make it a "stuck" child + item.position[0] - item.boundingRadius >= node.bounds.x && + item.position[0] + item.boundingRadius <= node.bounds.x + node.bounds.width && + item.position[1] - item.boundingRadius >= node.bounds.y && + item.position[1] + item.boundingRadius <= node.bounds.y + node.bounds.height){ + this.nodes[index].insert(item); + } else { + this.stuckChildren.push(item); + } + + return; + } + + this.children.push(item); + + var len = this.children.length; + + if(this.depth < this.maxDepth && len > this.maxChildren){ + this.subdivide(); + + for(var i=0; i