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.

This commit is contained in:
photonstorm 2013-10-11 04:42:11 +01:00
parent a7230aa769
commit b868c2cb1b
72 changed files with 6704 additions and 6454 deletions

View file

@ -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/)

View file

@ -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();
},

View file

@ -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)');

View file

@ -114,9 +114,10 @@
<script src="../src/particles/arcade/ArcadeParticles.js"></script>
<script src="../src/particles/arcade/Emitter.js"></script>
<script src="../src/tilemap/Tile.js"></script>
<script src="../src/tilemap/Tilemap.js"></script>
<script src="../src/tilemap/TilemapLayer.js"></script>
<script src="../src/tilemap/Tile.js"></script>
<script src="../src/tilemap/TilemapRenderer.js"></script>
<script src="../src/tilemap/TilemapParser.js"></script>
<script src="../src/tilemap/Tileset.js"></script>
<script src="../src/PixiPatch.js"></script>

View file

@ -0,0 +1,74 @@
<?php
$title = "Tilemap Layer WIP #1";
require('../head.php');
?>
<script type="text/javascript">
// var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create });
var game = new Phaser.Game(800, 600, Phaser.CANVAS, '', { preload: preload, create: create, update: update, render: render });
function preload() {
// game.load.image('snes', 'assets/maps/smb_tiles.png');
// game.load.tilemap('nes', 'assets/maps/mario1.png', 'assets/maps/mario1.json', null, Phaser.Tilemap.JSON);
// game.load.tilemap('snes', 'assets/maps/smb_tiles.png', 'assets/maps/smb_level1.json', null, Phaser.Tilemap.JSON);
// Just loads the level data and specifies the format
// game.load.tilemap('marioLevel1', 'assets/maps/smb_level1.json', Phaser.Tilemap.JSON);
// What about passing in a JSON object though? Need that too. But a CSV would look like a 'string', not an object - how to tell apart from URL?
// game.load.tilemap('marioLevel1', SMB_LEVEL_JSON, Phaser.Tilemap.JSON);
// Exactly the same as loading a sprite sheet :)
game.load.tileset('marioLevel1', 'assets/maps/smb_tiles.png', 32, 32);
}
var layer;
function create() {
game.stage.backgroundColor = '#3d3d3d';
layer = new Phaser.TilemapLayer(game, 0, 0, 500, 500, [], 'snes');
// layer = new Phaser.TilemapLayer(game, 0, 0, 500, 500);
// layer.load(mapData, tileset);
// layer.create(mapWidth, mapHeight, [tileset]);
// layer.updateTileset(key); // can change on the fly
layer.context.fillStyle = 'rgb(255,0,0)';
layer.context.fillRect(0, 0, 200, 300);
game.world._container.addChild(layer.sprite);
layer.create(10, 10);
layer.putTile(2, 2, 1);
layer.putTile(3, 2, 1);
layer.putTile(4, 2, 1);
layer.putTile(5, 2, 1);
layer.putTile(4, 6, 1);
layer.dump();
}
function update() {
}
function render() {
}
</script>
<?php
require('../foot.php');
?>

View file

@ -5,15 +5,14 @@
<script type="text/javascript">
var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create });
var p;
var p, tween, button, flag = false;
function preload() {
game.load.image('diamond', 'assets/sprites/diamond.png');
game.load.spritesheet('button', 'assets/buttons/button_sprite_sheet.png', 193, 71);
}
@ -23,13 +22,30 @@
p = game.add.sprite(100, 100, 'diamond');
game.add.tween(p).to({ x: 600 }, 2000, Phaser.Easing.Linear.None, true)
tween = game.add.tween(p).to({ x: 600 }, 2000, Phaser.Easing.Linear.None)
.to({ y: 300 }, 1000, Phaser.Easing.Linear.None)
.to({ x: 100 }, 2000, Phaser.Easing.Linear.None)
.to({ y: 100 }, 1000, Phaser.Easing.Linear.None)
.loop();
.loop()
.start();
button = game.add.button(game.world.centerX, 400, 'button', actionOnClick, this, 2, 1, 0);
}
function actionOnClick() {
if (flag) {
console.log('started');
tween.start();
}
else {
console.log('stopped');
tween.stop();
}
flag = !flag;
}
</script>

View file

@ -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.
*/

View file

@ -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.
*

View file

@ -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)

View file

@ -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;
}

View file

@ -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);
};
})();

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}
}
}
}

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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<Nbins; i++)
bins.push([]);
var xmult = nx / (xmax-xmin);
var ymult = ny / (ymax-ymin);
// Put all bodies into bins
for(var i=0; i!==Ncolliding; i++){
var bi = collidingBodies[i];
var si = bi.shape;
if (si === undefined) {
continue;
} else if(si instanceof Circle){
// Put in bin
// check if overlap with other bins
var x = bi.position[0];
var y = bi.position[1];
var r = si.radius;
var xi1 = Math.floor(xmult * (x-r - xmin));
var yi1 = Math.floor(ymult * (y-r - ymin));
var xi2 = Math.floor(xmult * (x+r - xmin));
var yi2 = Math.floor(ymult * (y+r - ymin));
for(var j=xi1; j<=xi2; j++){
for(var k=yi1; k<=yi2; k++){
var xi = j;
var yi = k;
if(xi*(ny-1) + yi >= 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)<y; j++){
for(var k=0; k<nx; k++){
var xi = k;
var yi = Math.floor(ymult * (binsizeY*j - ymin));
bins[ xi*(ny-1) + yi ].push(bi);
}
}
} else if(bi.angle == Math.PI*0.5){
var x = bi.position[0];
for(var j=0; j!==Nbins && xmin+binsizeX*(j-1)<x; j++){
for(var k=0; k<ny; k++){
var yi = k;
var xi = Math.floor(xmult * (binsizeX*j - xmin));
bins[ xi*(ny-1) + yi ].push(bi);
}
}
} else {
for(var j=0; j!==Nbins; j++)
bins[j].push(bi);
}
} else {
throw new Error("Shape not supported in GridBroadphase!");
}
}
// Check each bin
for(var i=0; i!==Nbins; i++){
var bin = bins[i];
for(var j=0, NbodiesInBin=bin.length; j!==NbodiesInBin; j++){
var bi = bin[j];
var si = bi.shape;
for(var k=0; k!==j; k++){
var bj = bin[k];
var sj = bj.shape;
if(si instanceof Circle){
if(sj instanceof Circle) c=Broadphase.circleCircle (bi,bj);
else if(sj instanceof Particle) c=Broadphase.circleParticle(bi,bj);
else if(sj instanceof Plane) c=Broadphase.circlePlane (bi,bj);
} else if(si instanceof Particle){
if(sj instanceof Circle) c=Broadphase.circleParticle(bj,bi);
} else if(si instanceof Plane){
if(sj instanceof Circle) c=Broadphase.circlePlane (bj,bi);
}
}
}
}
return result;
};

View file

@ -0,0 +1,47 @@
var Circle = require('../shapes/Circle')
, Plane = require('../shapes/Plane')
, Shape = require('../shapes/Shape')
, Particle = require('../shapes/Particle')
, Broadphase = require('../collision/Broadphase')
, vec2 = require('../math/vec2')
module.exports = NaiveBroadphase;
/**
* Naive broadphase implementation. Does N^2 tests.
*
* @class NaiveBroadphase
* @constructor
* @extends Broadphase
*/
function NaiveBroadphase(){
Broadphase.apply(this);
};
NaiveBroadphase.prototype = new Broadphase();
/**
* Get the colliding pairs
* @method getCollisionPairs
* @param {World} world
* @return {Array}
*/
NaiveBroadphase.prototype.getCollisionPairs = function(world){
var bodies = world.bodies,
result = this.result,
i, j, bi, bj;
result.length = 0;
for(i=0, Ncolliding=bodies.length; i!==Ncolliding; i++){
bi = bodies[i];
for(j=0; j<i; j++){
bj = bodies[j];
if(Broadphase.boundingRadiusCheck(bi,bj))
result.push(bi,bj);
}
}
return result;
};

File diff suppressed because it is too large Load diff

View file

@ -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<numAdded; k+=2){
var r1 = result[k],
r2 = result[k+1];
if( (r1==item && r2==b) || (r2==item && r1==b) ){
found = true;
break;
}
}
if(!found && Broadphase.boundingRadiusCheck(b,item)){
result.push(b,item);
}
}
}
//console.log("results",result.length);
// Clear until next
this.clear();
return result;
};
function Node(bounds, depth, maxDepth, maxChildren){
this.bounds = bounds;
this.children = [];
this.nodes = [];
if(maxChildren){
this.maxChildren = maxChildren;
}
if(maxDepth){
this.maxDepth = maxDepth;
}
if(depth){
this.depth = depth;
}
}
//subnodes
Node.prototype.classConstructor = Node;
//children contained directly in the node
Node.prototype.children = null;
//read only
Node.prototype.depth = 0;
Node.prototype.maxChildren = 4;
Node.prototype.maxDepth = 4;
Node.TOP_LEFT = 0;
Node.TOP_RIGHT = 1;
Node.BOTTOM_LEFT = 2;
Node.BOTTOM_RIGHT = 3;
Node.prototype.insert = function(item){
if(this.nodes.length){
var index = this.findIndex(item);
this.nodes[index].insert(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 < 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<len; i++){
this.insert(this.children[i]);
}
this.children.length = 0;
}
}
BoundsNode.prototype.getChildren = function(){
return this.children.concat(this.stuckChildren);
}
BoundsNode.prototype.retrieve = function(item){
var out = this.out;
out.length = 0;
if(this.nodes.length){
var index = this.findIndex(item);
out.push.apply(out, this.nodes[index].retrieve(item));
}
out.push.apply(out, this.stuckChildren);
out.push.apply(out, this.children);
return out;
}
BoundsNode.prototype.clear = function(){
this.stuckChildren.length = 0;
//array
this.children.length = 0;
var len = this.nodes.length;
if(!len){
return;
}
for(var i = 0; i < len; i++){
this.nodes[i].clear();
}
//array
this.nodes.length = 0;
//we could call the super clear function but for now, im just going to inline it
//call the hidden super.clear, and make sure its called with this = this instance
//Object.getPrototypeOf(BoundsNode.prototype).clear.call(this);
}

View file

@ -0,0 +1,120 @@
var Circle = require('../shapes/Circle')
, Plane = require('../shapes/Plane')
, Shape = require('../shapes/Shape')
, Particle = require('../shapes/Particle')
, Broadphase = require('../collision/Broadphase')
, vec2 = require('../math/vec2')
module.exports = SAP1DBroadphase;
/**
* Sweep and prune broadphase along one axis.
*
* @class SAP1DBroadphase
* @constructor
* @extends Broadphase
* @param {World} world
*/
function SAP1DBroadphase(world){
Broadphase.apply(this);
/**
* List of bodies currently in the broadphase.
* @property axisList
* @type {Array}
*/
this.axisList = world.bodies.slice(0);
/**
* The world to search in.
* @property world
* @type {World}
*/
this.world = world;
/**
* Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on.
* @property axisIndex
* @type {Number}
*/
this.axisIndex = 0;
// Add listeners to update the list of bodies.
var axisList = this.axisList;
world.on("addBody",function(e){
axisList.push(e.body);
}).on("removeBody",function(e){
var idx = axisList.indexOf(e.body);
if(idx !== -1)
axisList.splice(idx,1);
});
};
SAP1DBroadphase.prototype = new Broadphase();
/**
* Function for sorting bodies along the X axis. To be passed to array.sort()
* @method sortAxisListX
* @param {Body} bodyA
* @param {Body} bodyB
* @return {Number}
*/
SAP1DBroadphase.sortAxisListX = function(bodyA,bodyB){
return (bodyA.position[0]-bodyA.boundingRadius) - (bodyB.position[0]-bodyB.boundingRadius);
};
/**
* Function for sorting bodies along the Y axis. To be passed to array.sort()
* @method sortAxisListY
* @param {Body} bodyA
* @param {Body} bodyB
* @return {Number}
*/
SAP1DBroadphase.sortAxisListY = function(bodyA,bodyB){
return (bodyA.position[1]-bodyA.boundingRadius) - (bodyB.position[1]-bodyB.boundingRadius);
};
/**
* Get the colliding pairs
* @method getCollisionPairs
* @param {World} world
* @return {Array}
*/
SAP1DBroadphase.prototype.getCollisionPairs = function(world){
var bodies = this.axisList,
result = this.result,
axisIndex = this.axisIndex,
i,j;
result.length = 0;
// Sort the list
bodies.sort(axisIndex === 0 ? SAP1DBroadphase.sortAxisListX : SAP1DBroadphase.sortAxisListY );
// Look through the list
for(i=0, N=bodies.length; i!==N; i++){
var bi = bodies[i],
biPos = bi.position[axisIndex],
ri = bi.boundingRadius;
for(j=i+1; j<N; j++){
var bj = bodies[j],
bjPos = bj.position[axisIndex],
rj = bj.boundingRadius,
boundA1 = biPos-ri,
boundA2 = biPos+ri,
boundB1 = bjPos-rj,
boundB2 = bjPos+rj;
// Abort if we got gap til the next body
if( boundB1 > boundA2 ){
break;
}
// If we got overlap, add pair
if(Broadphase.boundingRadiusCheck(bi,bj))
result.push(bi,bj);
}
}
return result;
};

View file

@ -0,0 +1,42 @@
module.exports = Constraint;
/**
* Base constraint class.
*
* @class Constraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
*/
function Constraint(bodyA,bodyB){
/**
* Equations to be solved in this constraint
* @property equations
* @type {Array}
*/
this.equations = [];
/**
* First body participating in the constraint.
* @property bodyA
* @type {Body}
*/
this.bodyA = bodyA;
/**
* Second body participating in the constraint.
* @property bodyB
* @type {Body}
*/
this.bodyB = bodyB;
};
/**
* To be implemented by subclasses. Should update the internal constraint parameters.
* @method update
*/
/*Constraint.prototype.update = function(){
throw new Error("method update() not implmemented in this Constraint subclass!");
};*/

View file

@ -0,0 +1,136 @@
var Equation = require("./Equation"),
vec2 = require('../math/vec2'),
mat2 = require('../math/mat2');
module.exports = ContactEquation;
/**
* Non-penetration constraint equation.
*
* @class ContactEquation
* @constructor
* @extends Equation
* @param {Body} bi
* @param {Body} bj
*/
function ContactEquation(bi,bj){
Equation.call(this,bi,bj,0,1e6);
this.ri = vec2.create();
this.penetrationVec = vec2.create();
this.rj = vec2.create();
this.ni = vec2.create();
this.rixn = 0;
this.rjxn = 0;
};
ContactEquation.prototype = new Equation();
ContactEquation.prototype.constructor = ContactEquation;
ContactEquation.prototype.computeB = function(a,b,h){
var bi = this.bi,
bj = this.bj,
ri = this.ri,
rj = this.rj,
xi = bi.position,
xj = bj.position;
var vi = bi.velocity,
wi = bi.angularVelocity,
fi = bi.force,
taui = bi.angularForce;
var vj = bj.velocity,
wj = bj.angularVelocity,
fj = bj.force,
tauj = bj.angularForce;
var penetrationVec = this.penetrationVec,
invMassi = bi.invMass,
invMassj = bj.invMass,
invIi = bi.invInertia,
invIj = bj.invInertia,
n = this.ni;
// Caluclate cross products
this.rixn = vec2.crossLength(ri,n);
this.rjxn = vec2.crossLength(rj,n);
// Calculate q = xj+rj -(xi+ri) i.e. the penetration vector
vec2.add(penetrationVec,xj,rj);
vec2.sub(penetrationVec,penetrationVec,xi);
vec2.sub(penetrationVec,penetrationVec,ri);
var Gq = vec2.dot(n,penetrationVec);
// Compute iteration
var GW = vec2.dot(vj,n) - vec2.dot(vi,n) + wj * this.rjxn - wi * this.rixn;
var GiMf = vec2.dot(fj,n)*invMassj - vec2.dot(fi,n)*invMassi + invIj*tauj*this.rjxn - invIi*taui*this.rixn;
var B = - Gq * a - GW * b - h*GiMf;
return B;
};
// Compute C = GMG+eps in the SPOOK equation
var computeC_tmp1 = vec2.create(),
tmpMat1 = mat2.create(),
tmpMat2 = mat2.create();
ContactEquation.prototype.computeC = function(eps){
var bi = this.bi,
bj = this.bj,
n = this.ni,
rixn = this.rixn,
rjxn = this.rjxn,
tmp = computeC_tmp1,
imMat1 = tmpMat1,
imMat2 = tmpMat2;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
var C = vec2.dot(n,vec2.transformMat2(tmp,n,imMat1)) + vec2.dot(n,vec2.transformMat2(tmp,n,imMat2)) + eps;
//var C = bi.invMass + bj.invMass + eps;
C += bi.invInertia * this.rixn * this.rixn;
C += bj.invInertia * this.rjxn * this.rjxn;
return C;
};
ContactEquation.prototype.computeGWlambda = function(){
var bi = this.bi,
bj = this.bj,
n = this.ni,
dot = vec2.dot;
return dot(n, bj.vlambda) + bj.wlambda * this.rjxn - dot(n, bi.vlambda) - bi.wlambda * this.rixn;
};
var addToWlambda_temp = vec2.create();
ContactEquation.prototype.addToWlambda = function(deltalambda){
var bi = this.bi,
bj = this.bj,
n = this.ni,
temp = addToWlambda_temp,
imMat1 = tmpMat1,
imMat2 = tmpMat2;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
// Add to linear velocity
//vec2.scale(temp,n,-bi.invMass*deltalambda);
vec2.scale(temp,vec2.transformMat2(temp,n,imMat1),-deltalambda);
vec2.add( bi.vlambda,bi.vlambda, temp );
//vec2.scale(temp,n,bj.invMass*deltalambda);
vec2.scale(temp,vec2.transformMat2(temp,n,imMat2),deltalambda);
vec2.add( bj.vlambda,bj.vlambda, temp);
// Add to angular velocity
bi.wlambda -= bi.invInertia * this.rixn * deltalambda;
bj.wlambda += bj.invInertia * this.rjxn * deltalambda;
};

View file

@ -0,0 +1,62 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, vec2 = require('../math/vec2')
module.exports = DistanceConstraint;
/**
* Constraint that tries to keep the distance between two bodies constant.
*
* @class DistanceConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
* @param {number} dist The distance to keep between the bodies.
* @param {number} maxForce
* @extends {Constraint}
*/
function DistanceConstraint(bodyA,bodyB,distance,maxForce){
Constraint.call(this,bodyA,bodyB);
this.distance = distance;
if(typeof(maxForce)==="undefined" ) {
maxForce = 1e6;
}
var normal = new ContactEquation(bodyA,bodyB); // Just in the normal direction
this.equations = [ normal ];
// Make the contact constraint bilateral
this.setMaxForce(maxForce);
}
DistanceConstraint.prototype = new Constraint();
/**
* Update the constraint equations. Should be done if any of the bodies changed position, before solving.
* @method update
*/
DistanceConstraint.prototype.update = function(){
var normal = this.equations[0],
bodyA = this.bodyA,
bodyB = this.bodyB,
distance = this.distance;
vec2.sub(normal.ni, bodyB.position, bodyA.position);
vec2.normalize(normal.ni,normal.ni);
vec2.scale(normal.ri, normal.ni, distance*0.5);
vec2.scale(normal.rj, normal.ni, -distance*0.5);
};
DistanceConstraint.prototype.setMaxForce = function(f){
var normal = this.equations[0];
normal.minForce = -f;
normal.maxForce = f;
};
DistanceConstraint.prototype.getMaxForce = function(f){
var normal = this.equations[0];
return normal.maxForce;
};

View file

@ -0,0 +1,77 @@
module.exports = Equation;
/**
* Base class for constraint equations.
* @class Equation
* @constructor
* @param {Body} bi First body participating in the equation
* @param {Body} bj Second body participating in the equation
* @param {number} minForce Minimum force to apply. Default: -1e6
* @param {number} maxForce Maximum force to apply. Default: 1e6
*/
function Equation(bi,bj,minForce,maxForce){
/**
* Minimum force to apply when solving
* @property minForce
* @type {Number}
*/
this.minForce = typeof(minForce)=="undefined" ? -1e6 : minForce;
/**
* Max force to apply when solving
* @property maxForce
* @type {Number}
*/
this.maxForce = typeof(maxForce)=="undefined" ? 1e6 : maxForce;
/**
* First body participating in the constraint
* @property bi
* @type {Body}
*/
this.bi = bi;
/**
* Second body participating in the constraint
* @property bj
* @type {Body}
*/
this.bj = bj;
/**
* The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation.
* @property stiffness
* @type {Number}
*/
this.stiffness = 1e6;
/**
* The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps.
* @property relaxation
* @type {Number}
*/
this.relaxation = 4;
this.a = 0;
this.b = 0;
this.eps = 0;
this.h = 0;
this.updateSpookParams(1/60);
};
Equation.prototype.constructor = Equation;
/**
* Update SPOOK parameters .a, .b and .eps according to the given time step. See equations 9, 10 and 11 in the <a href="http://www8.cs.umu.se/kurser/5DV058/VT09/lectures/spooknotes.pdf">SPOOK notes</a>.
* @method updateSpookParams
* @param {number} timeStep
*/
Equation.prototype.updateSpookParams = function(timeStep){
var k = this.stiffness,
d = this.relaxation,
h = timeStep;
this.a = 4.0 / (h * (1 + 4 * d));
this.b = (4.0 * d) / (1 + 4 * d);
this.eps = 4.0 / (h * h * k * (1 + 4 * d));
this.h = timeStep;
};

View file

@ -0,0 +1,180 @@
var mat2 = require('../math/mat2')
, vec2 = require('../math/vec2')
, Equation = require('./Equation')
module.exports = FrictionEquation;
// 3D cross product from glmatrix, until we get this to work...
function cross(out, a, b) {
var ax = a[0], ay = a[1], az = a[2],
bx = b[0], by = b[1], bz = b[2];
out[0] = ay * bz - az * by;
out[1] = az * bx - ax * bz;
out[2] = ax * by - ay * bx;
return out;
};
var dot = vec2.dot;
/**
* Constrains the slipping in a contact along a tangent
*
* @class FrictionEquation
* @constructor
* @param {Body} bi
* @param {Body} bj
* @param {Number} slipForce
* @extends {Equation}
*/
function FrictionEquation(bi,bj,slipForce){
Equation.call(this,bi,bj,-slipForce,slipForce);
/**
* Relative vector from center of body i to the contact point, in world coords.
* @property ri
* @type {Float32Array}
*/
this.ri = vec2.create();
/**
* Relative vector from center of body j to the contact point, in world coords.
* @property rj
* @type {Float32Array}
*/
this.rj = vec2.create();
/**
* Tangent vector that the friction force will act along, in world coords.
* @property t
* @type {Float32Array}
*/
this.t = vec2.create();
this.rixt = 0;
this.rjxt = 0;
};
FrictionEquation.prototype = new Equation();
FrictionEquation.prototype.constructor = FrictionEquation;
/**
* Set the slipping condition for the constraint. The friction force cannot be
* larger than this value.
* @method setSlipForce
* @param {Number} slipForce
*/
FrictionEquation.prototype.setSlipForce = function(slipForce){
this.maxForce = slipForce;
this.minForce = -slipForce;
};
var rixtVec = [0,0,0];
var rjxtVec = [0,0,0];
var ri3 = [0,0,0];
var rj3 = [0,0,0];
var t3 = [0,0,0];
FrictionEquation.prototype.computeB = function(a,b,h){
var a = this.a,
b = this.b,
bi = this.bi,
bj = this.bj,
ri = this.ri,
rj = this.rj,
t = this.t;
// Caluclate cross products
ri3[0] = ri[0];
ri3[1] = ri[1];
rj3[0] = rj[0];
rj3[1] = rj[1];
t3[0] = t[0];
t3[1] = t[1];
cross(rixtVec, ri3, t3);//ri.cross(t,rixt);
cross(rjxtVec, rj3, t3);//rj.cross(t,rjxt);
this.rixt = rixtVec[2];
this.rjxt = rjxtVec[2];
var GW = -dot(bi.velocity,t) + dot(bj.velocity,t) - this.rixt*bi.angularVelocity + this.rjxt*bj.angularVelocity; // eq. 40
var GiMf = -dot(bi.force,t)*bi.invMass +dot(bj.force,t)*bj.invMass -this.rixt*bi.invInertia*bi.angularForce + this.rjxt*bj.invInertia*bj.angularForce;
var B = /* - Gq * a */ - GW * b - h*GiMf;
return B;
};
// Compute C = G * iM * G' + eps
//
// G*iM*G' =
//
// [ iM1 ] [-t ]
// [-t (-ri x t) t (rj x t)] * [ iI1 ] [-ri x t]
// [ iM2 ] [t ]
// [ iI2 ] [rj x t ]
//
// = (-t)*iM1*(-t) + (-ri x t)*iI1*(-ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t)
//
// = t*iM1*t + (ri x t)*iI1*(ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t)
//
var computeC_tmp1 = vec2.create(),
tmpMat1 = mat2.create(),
tmpMat2 = mat2.create();
FrictionEquation.prototype.computeC = function(eps){
var bi = this.bi,
bj = this.bj,
t = this.t,
C = 0.0,
tmp = computeC_tmp1,
imMat1 = tmpMat1,
imMat2 = tmpMat2,
dot = vec2.dot;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
C = dot(t,vec2.transformMat2(tmp,t,imMat1)) + dot(t,vec2.transformMat2(tmp,t,imMat2)) + eps;
//C = bi.invMass + bj.invMass + eps;
C += bi.invInertia * this.rixt * this.rixt;
C += bj.invInertia * this.rjxt * this.rjxt;
return C;
};
FrictionEquation.prototype.computeGWlambda = function(){
var bi = this.bi,
bj = this.bj,
t = this.t,
dot = vec2.dot;
return dot(t, bj.vlambda) + bj.wlambda * this.rjxt - bi.wlambda * this.rixt - dot(t, bi.vlambda);
};
var FrictionEquation_addToWlambda_tmp = vec2.create();
FrictionEquation.prototype.addToWlambda = function(deltalambda){
var bi = this.bi,
bj = this.bj,
t = this.t,
tmp = FrictionEquation_addToWlambda_tmp,
imMat1 = tmpMat1,
imMat2 = tmpMat2;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat1),-deltalambda);
//vec2.scale(tmp, t, -bi.invMass * deltalambda); //t.mult(invMassi * deltalambda, tmp);
vec2.add(bi.vlambda, bi.vlambda, tmp); //bi.vlambda.vsub(tmp,bi.vlambda);
vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat2),deltalambda);
//vec2.scale(tmp, t, bj.invMass * deltalambda); //t.mult(invMassj * deltalambda, tmp);
vec2.add(bj.vlambda, bj.vlambda, tmp); //bj.vlambda.vadd(tmp,bj.vlambda);
bi.wlambda -= bi.invInertia * this.rixt * deltalambda;
bj.wlambda += bj.invInertia * this.rjxt * deltalambda;
};

View file

@ -0,0 +1,94 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, RotationalVelocityEquation = require('./RotationalVelocityEquation')
, vec2 = require('../math/vec2')
module.exports = PointToPointConstraint;
/**
* Connects two bodies at given offset points
* @class PointToPointConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Float32Array} pivotA The point relative to the center of mass of bodyA which bodyA is constrained to.
* @param {Body} bodyB Body that will be constrained in a similar way to the same point as bodyA. We will therefore get sort of a link between bodyA and bodyB. If not specified, bodyA will be constrained to a static point.
* @param {Float32Array} pivotB See pivotA.
* @param {Number} maxForce The maximum force that should be applied to constrain the bodies.
* @extends {Constraint}
* @todo Ability to specify world points
*/
function PointToPointConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){
Constraint.call(this,bodyA,bodyB);
maxForce = typeof(maxForce)!="undefined" ? maxForce : 1e7;
this.pivotA = pivotA;
this.pivotB = pivotB;
// Equations to be fed to the solver
var eqs = this.equations = [
new ContactEquation(bodyA,bodyB), // Normal
new ContactEquation(bodyA,bodyB), // Tangent
];
var normal = eqs[0];
var tangent = eqs[1];
tangent.minForce = normal.minForce = -maxForce;
tangent.maxForce = normal.maxForce = maxForce;
this.motorEquation = null;
}
PointToPointConstraint.prototype = new Constraint();
PointToPointConstraint.prototype.update = function(){
var bodyA = this.bodyA,
bodyB = this.bodyB,
pivotA = this.pivotA,
pivotB = this.pivotB,
eqs = this.equations,
normal = eqs[0],
tangent= eqs[1];
vec2.subtract(normal.ni, bodyB.position, bodyA.position);
vec2.normalize(normal.ni,normal.ni);
vec2.rotate(normal.ri, pivotA, bodyA.angle);
vec2.rotate(normal.rj, pivotB, bodyB.angle);
vec2.rotate(tangent.ni, normal.ni, Math.PI / 2);
vec2.copy(tangent.ri, normal.ri);
vec2.copy(tangent.rj, normal.rj);
};
/**
* Enable the rotational motor
* @method enableMotor
*/
PointToPointConstraint.prototype.enableMotor = function(){
if(this.motorEquation) return;
this.motorEquation = new RotationalVelocityEquation(this.bodyA,this.bodyB);
this.equations.push(this.motorEquation);
};
/**
* Disable the rotational motor
* @method disableMotor
*/
PointToPointConstraint.prototype.disableMotor = function(){
if(!this.motorEquation) return;
var i = this.equations.indexOf(this.motorEquation);
this.motorEquation = null;
this.equations.splice(i,1);
};
/**
* Set the speed of the rotational constraint motor
* @method setMotorSpeed
* @param {Number} speed
*/
PointToPointConstraint.prototype.setMotorSpeed = function(speed){
if(!this.motorEquation) return;
var i = this.equations.indexOf(this.motorEquation);
this.equations[i].relativeVelocity = speed;
};

View file

@ -0,0 +1,83 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, vec2 = require('../math/vec2')
module.exports = PrismaticConstraint;
/**
* Constraint that only allows translation along a line between the bodies, no rotation
*
* @class PrismaticConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
* @param {Object} options
* @param {Number} options.maxForce
* @param {Array} options.worldAxis
* @param {Array} options.localAxisA
* @param {Array} options.localAxisB
* @extends {Constraint}
*/
function PrismaticConstraint(bodyA,bodyB,options){
options = options || {};
Constraint.call(this,bodyA,bodyB);
var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : 1e6;
// Equations to be fed to the solver
var eqs = this.equations = [
new ContactEquation(bodyA,bodyB), // Tangent for bodyA
new ContactEquation(bodyB,bodyA), // Tangent for bodyB
];
var tangentA = eqs[0],
tangentB = eqs[1];
tangentA.minForce = tangentB.minForce = -maxForce;
tangentA.maxForce = tangentB.maxForce = maxForce;
var worldAxis = vec2.create();
if(options.worldAxis){
vec2.copy(worldAxis, options.worldAxis);
} else {
vec2.sub(worldAxis, bodyB.position, bodyA.position);
}
vec2.normalize(worldAxis,worldAxis);
// Axis that is local in each body
this.localAxisA = vec2.create();
this.localAxisB = vec2.create();
if(options.localAxisA) vec2.copy(this.localAxisA, options.localAxisA);
else vec2.rotate(this.localAxisA, worldAxis, -bodyA.angle);
if(options.localAxisB) vec2.copy(this.localAxisB, options.localAxisB);
else vec2.rotate(this.localAxisB, worldAxis, -bodyB.angle);
}
PrismaticConstraint.prototype = new Constraint();
/**
* Update the constraint equations. Should be done if any of the bodies changed position, before solving.
* @method update
*/
PrismaticConstraint.prototype.update = function(){
var tangentA = this.equations[0],
tangentB = this.equations[1],
bodyA = this.bodyA,
bodyB = this.bodyB;
// Get tangent directions
vec2.rotate(tangentA.ni, this.localAxisA, bodyA.angle - Math.PI/2);
vec2.rotate(tangentB.ni, this.localAxisB, bodyB.angle + Math.PI/2);
// Get distance vector
var dist = vec2.create();
vec2.sub(dist, bodyB.position, bodyA.position);
vec2.scale(tangentA.ri, tangentA.ni, -vec2.dot(tangentA.ni, dist));
vec2.scale(tangentB.ri, tangentB.ni, vec2.dot(tangentB.ni, dist));
vec2.add(tangentA.rj, tangentA.ri, dist);
vec2.sub(tangentB.rj, tangentB.ri, dist);
vec2.set(tangentA.ri, 0, 0);
vec2.set(tangentB.ri, 0, 0);
};

View file

@ -0,0 +1,70 @@
var Equation = require("./Equation"),
vec2 = require('../math/vec2');
module.exports = RotationalVelocityEquation;
/**
* Syncs rotational velocity of two bodies, or sets a relative velocity (motor).
*
* @class RotationalVelocityEquation
* @constructor
* @extends Equation
* @param {Body} bi
* @param {Body} bj
*/
function RotationalVelocityEquation(bi,bj){
Equation.call(this,bi,bj,-1e6,1e6);
this.relativeVelocity = 1;
this.ratio = 1;
};
RotationalVelocityEquation.prototype = new Equation();
RotationalVelocityEquation.prototype.constructor = RotationalVelocityEquation;
RotationalVelocityEquation.prototype.computeB = function(a,b,h){
var bi = this.bi,
bj = this.bj,
vi = bi.velocity,
wi = bi.angularVelocity,
taui = bi.angularForce,
vj = bj.velocity,
wj = bj.angularVelocity,
tauj = bj.angularForce,
invIi = bi.invInertia,
invIj = bj.invInertia,
Gq = 0,
GW = this.ratio * wj - wi + this.relativeVelocity,
GiMf = invIj*tauj - invIi*taui;
var B = - Gq * a - GW * b - h*GiMf;
return B;
};
// Compute C = GMG+eps in the SPOOK equation
RotationalVelocityEquation.prototype.computeC = function(eps){
var bi = this.bi,
bj = this.bj;
var C = bi.invInertia + bj.invInertia + eps;
return C;
};
var computeGWlambda_ulambda = vec2.create();
RotationalVelocityEquation.prototype.computeGWlambda = function(){
var bi = this.bi,
bj = this.bj;
var GWlambda = bj.wlambda - bi.wlambda;
return GWlambda;
};
var addToWlambda_temp = vec2.create();
RotationalVelocityEquation.prototype.addToWlambda = function(deltalambda){
var bi = this.bi,
bj = this.bj;
// Add to angular velocity
bi.wlambda -= bi.invInertia * deltalambda;
bj.wlambda += bj.invInertia * deltalambda;
};

View file

@ -0,0 +1,84 @@
/**
* Base class for objects that dispatches events.
* @class EventEmitter
* @constructor
*/
var EventEmitter = function () {}
module.exports = EventEmitter;
EventEmitter.prototype = {
constructor: EventEmitter,
/**
* Add an event listener
* @method on
* @param {String} type
* @param {Function} listener
* @return {EventEmitter} The self object, for chainability.
*/
on: function ( type, listener ) {
if ( this._listeners === undefined ) this._listeners = {};
var listeners = this._listeners;
if ( listeners[ type ] === undefined ) {
listeners[ type ] = [];
}
if ( listeners[ type ].indexOf( listener ) === - 1 ) {
listeners[ type ].push( listener );
}
return this;
},
/**
* Check if an event listener is added
* @method has
* @param {String} type
* @param {Function} listener
* @return {Boolean}
*/
has: function ( type, listener ) {
if ( this._listeners === undefined ) return false;
var listeners = this._listeners;
if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
return true;
}
return false;
},
/**
* Remove an event listener
* @method off
* @param {String} type
* @param {Function} listener
* @return {EventEmitter} The self object, for chainability.
*/
off: function ( type, listener ) {
if ( this._listeners === undefined ) return;
var listeners = this._listeners;
var index = listeners[ type ].indexOf( listener );
if ( index !== - 1 ) {
listeners[ type ].splice( index, 1 );
}
return this;
},
/**
* Emit an event.
* @method emit
* @param {Object} event
* @param {String} event.type
* @return {EventEmitter} The self object, for chainability.
*/
emit: function ( event ) {
if ( this._listeners === undefined ) return;
var listeners = this._listeners;
var listenerArray = listeners[ event.type ];
if ( listenerArray !== undefined ) {
event.target = this;
for ( var i = 0, l = listenerArray.length; i < l; i ++ ) {
listenerArray[ i ].call( this, event );
}
}
return this;
}
};

View file

@ -1,130 +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.
*/
//-------------------------------------------------------------------------------------------------
// Angle Joint
//
// C = a2 - a1 - refAngle
// Cdot = w2 - w1
// J = [0, -1, 0, 1]
//
// impulse = JT * lambda = [ 0, -lambda, 0, lambda ]
//-------------------------------------------------------------------------------------------------
AngleJoint = function(body1, body2) {
Joint.call(this, Joint.TYPE_ANGLE, body1, body2, true);
this.anchor1 = new vec2(0, 0);
this.anchor2 = new vec2(0, 0);
// Initial angle difference
this.refAngle = body2.a - body1.a;
// Accumulated lambda for angular velocity constraint
this.lambda_acc = 0;
}
AngleJoint.prototype = new Joint;
AngleJoint.prototype.constructor = AngleJoint;
AngleJoint.prototype.setWorldAnchor1 = function(anchor1) {
this.anchor1 = new vec2(0, 0);
}
AngleJoint.prototype.setWorldAnchor2 = function(anchor2) {
this.anchor2 = new vec2(0, 0);
}
AngleJoint.prototype.serialize = function() {
return {
"type": "AngleJoint",
"body1": this.body1.id,
"body2": this.body2.id,
"collideConnected": this.collideConnected
};
}
AngleJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
// invEM = J * invM * JT
var em_inv = body1.i_inv + body2.i_inv;
this.em = em_inv == 0 ? 0 : 1 / em_inv;
if (warmStarting) {
// Apply cached constraint impulses
// V += JT * lambda * invM
body1.w -= this.lambda_acc * body1.i_inv;
body2.w += this.lambda_acc * body2.i_inv;
}
else {
this.lambda_acc = 0;
}
}
AngleJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -J * V
var cdot = body2.w - body1.w;
var lambda = -this.em * cdot;
// Accumulate lambda
this.lambda_acc += lambda;
// Apply constraint impulses
// V += JT * lambda * invM
body1.w -= lambda * body1.i_inv;
body2.w += lambda * body2.i_inv;
}
AngleJoint.prototype.solvePositionConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Position (angle) constraint
var c = body2.a - body1.a - this.refAngle;
var correction = Math.clamp(c, -Joint.MAX_ANGULAR_CORRECTION, Joint.MAX_ANGULAR_CORRECTION);
// Compute lambda for position (angle) constraint
// Solve J * invM * JT * lambda = -C / dt
var lambda_dt = this.em * (-correction);
// Apply constraint impulses
// impulse = JT * lambda
// X += impulse * invM * dt
body1.a -= lambda_dt * body1.i_inv;
body2.a += lambda_dt * body2.i_inv;
return Math.abs(c) < Joint.ANGULAR_SLOP;
}
AngleJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.zero;
}
AngleJoint.prototype.getReactionTorque = function(dt_inv) {
return this.lambda_acc * dt_inv;
}

View file

@ -1,522 +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.
*/
//-------------------------------------------------------------------------------------------------
// Distance Joint
//
// d = p2 - p1
// u = d / norm(d)
// C = norm(d) - l
// C = sqrt(dot(d, d)) - l
// Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1))
// = -dot(u, v1) - dot(w1, cross(r1, u)) + dot(u, v2) + dot(w2, cross(r2, u))
// J = [ -u, -cross(r1, u), u, cross(r2, u) ]
//
// impulse = JT * lambda = [ -u * lambda, -cross(r1, u) * lambda, u * lambda, cross(r1, u) * lambda ]
//-------------------------------------------------------------------------------------------------
DistanceJoint = function(body1, body2, anchor1, anchor2) {
Joint.call(this, Joint.TYPE_DISTANCE, body1, body2, true);
// Local anchor points
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.anchor2 = this.body2.getLocalPoint(anchor2);
// Rest length
this.restLength = vec2.dist(anchor1, anchor2);
// Soft constraint coefficients
this.gamma = 0;
this.beta_c = 0;
// Spring coefficients
this.frequencyHz = 0;
this.dampingRatio = 0;
// Accumulated impulse
this.lambda_acc = 0;
}
DistanceJoint.prototype = new Joint;
DistanceJoint.prototype.constructor = DistanceJoint;
DistanceJoint.prototype.setWorldAnchor1 = function(anchor1) {
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.restLength = vec2.dist(anchor1, this.getWorldAnchor2());
}
DistanceJoint.prototype.setWorldAnchor2 = function(anchor2) {
this.anchor2 = this.body2.getLocalPoint(anchor2);
this.restLength = vec2.dist(anchor2, this.getWorldAnchor1());
}
DistanceJoint.prototype.serialize = function() {
return {
"type": "DistanceJoint",
"body1": this.body1.id,
"body2": this.body2.id,
"anchor1": this.body1.getWorldPoint(this.anchor1),
"anchor2": this.body2.getWorldPoint(this.anchor2),
"collideConnected": this.collideConnected,
"maxForce": this.maxForce,
"breakable": this.breakable,
"frequencyHz": this.frequencyHz,
"dampingRatio": this.dampingRatio
};
}
DistanceJoint.prototype.setSpringFrequencyHz = function(frequencyHz) {
// NOTE: frequencyHz should be limited to under 4 times time steps
this.frequencyHz = frequencyHz;
}
DistanceJoint.prototype.setSpringDampingRatio = function(dampingRatio) {
this.dampingRatio = dampingRatio;
}
DistanceJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
// Transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// Delta vector between two world anchors
var d = vec2.sub(vec2.add(body2.p, this.r2), vec2.add(body1.p, this.r1));
// Distance between two anchors
var dist = d.length();
// Unit delta vector
if (dist > Joint.LINEAR_SLOP) {
this.u = vec2.scale(d, 1 / dist);
}
else {
this.u = vec2.zero;
}
// s1, s2
this.s1 = vec2.cross(this.r1, this.u);
this.s2 = vec2.cross(this.r2, this.u);
// invEM = J * invM * JT
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.s1 * this.s1 + body2.i_inv * this.s2 * this.s2;
this.em = em_inv == 0 ? 0 : 1 / em_inv;
// Compute soft constraint parameters
if (this.frequencyHz > 0) {
// Frequency
var omega = 2 * Math.PI * this.frequencyHz;
// Spring stiffness
var k = this.em * omega * omega;
// Damping coefficient
var c = this.em * 2 * this.dampingRatio * omega;
// Soft constraint formulas
// gamma and beta are divided by dt to reduce computation
this.gamma = (c + k * dt) * dt;
this.gamma = this.gamma == 0 ? 0 : 1 / this.gamma;
var beta = dt * k * this.gamma;
// Position constraint
var pc = dist - this.restLength;
this.beta_c = beta * pc;
// invEM = invEM + gamma * I (to reduce calculation)
em_inv = em_inv + this.gamma;
this.em = em_inv == 0 ? 0 : 1 / em_inv;
}
else {
this.gamma = 0;
this.beta_c = 0;
}
if (warmStarting) {
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.u, this.lambda_acc);
// Apply cached constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * this.lambda_acc * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * this.lambda_acc * body2.i_inv;
}
else {
this.lambda_acc = 0;
}
}
DistanceJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -(J * V + beta * C + gamma * (lambda_acc + lambda))
var cdot = this.u.dot(vec2.sub(body2.v, body1.v)) + this.s2 * body2.w - this.s1 * body1.w;
var soft = this.beta_c + this.gamma * this.lambda_acc;
var lambda = -this.em * (cdot + soft);
// Accumulate lambda
this.lambda_acc += lambda;
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.u, lambda);
// Apply constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * lambda * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * lambda * body2.i_inv;
}
DistanceJoint.prototype.solvePositionConstraints = function() {
// There is no position correction for soft constraints
if (this.frequencyHz > 0) {
return true;
}
var body1 = this.body1;
var body2 = this.body2;
// Transformed r1, r2
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
// Delta vector between two anchors
var d = vec2.sub(vec2.add(body2.p, r2), vec2.add(body1.p, r1));
// Distance between two anchors
var dist = d.length();
// Unit delta vector
var u = vec2.scale(d, 1 / dist);
// Position constraint
var c = dist - this.restLength;
var correction = Math.clamp(c, -Joint.MAX_LINEAR_CORRECTION, Joint.MAX_LINEAR_CORRECTION);
// Compute lambda for correction
// Solve J * invM * JT * lambda = -C / dt
var s1 = vec2.cross(r1, u);
var s2 = vec2.cross(r2, u);
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * s1 * s1 + body2.i_inv * s2 * s2;
var lambda_dt = em_inv == 0 ? 0 : -correction / em_inv;
// Apply constraint impulses
// impulse = JT * lambda
// X += impulse * invM * dt
var impulse_dt = vec2.scale(u, lambda_dt);
body1.p.mad(impulse_dt, -body1.m_inv);
body1.a -= s1 * lambda_dt * body1.i_inv;
body2.p.mad(impulse_dt, body2.m_inv);
body2.a += s2 * lambda_dt * body2.i_inv;
return Math.abs(c) < Joint.LINEAR_SLOP;
}
DistanceJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.u, this.lambda_acc * dt_inv);
}
DistanceJoint.prototype.getReactionTorque = function(dt_inv) {
return 0;
}
/*
//------------------------------------------
// MaxDistance Joint
//------------------------------------------
MaxDistanceJoint = function(body1, body2, anchor1, anchor2, minDist, maxDist) {
Joint.call(this, body1, body2, true);
// Local anchor points
this.anchor1 = body1.getLocalPoint(anchor1);
this.anchor2 = body2.getLocalPoint(anchor2);
this.minDist = minDist || 0;
this.maxDist = maxDist;
if (maxDist == undefined) {
var p1 = vec2.add(vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a), body1.p);
var p2 = vec2.add(vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a), body2.p);
this.maxDist = vec2.dist(p1, p2);
}
// accumulated impulse
this.lambda_acc = 0;
}
MaxDistanceJoint.prototype = new Joint;
MaxDistanceJoint.prototype.constructor = MaxDistanceJoint;
MaxDistanceJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// max impulse
this.maxImpulse = this.maxForce * dt;
// transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// delta vector between two anchors
var d = vec2.sub(vec2.add(body2.p, this.r2), vec2.add(body1.p, this.r1));
// distance between two anchors
var dist = d.length();
// unit delta vector
this.u = vec2.scale(d, 1 / dist);
// s1, s2
this.s1 = vec2.cross(this.r1, this.u);
this.s2 = vec2.cross(this.r2, this.u);
// invEM = J * invM * JT
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.s1 * this.s1 + body2.i_inv * this.s2 * this.s2;
this.em = em_inv == 0 ? 0 : 1 / em_inv;
// initial error
this.initial_err = 0;
if (dist < this.minDist) {
this.initial_err = dist - this.minDist;
}
else if (dist > this.maxDist) {
this.initial_err = dist - this.maxDist;
}
if (this.initial_err == 0) {
this.lambda_acc = 0;
}
if (warmStarting) {
// apply cached impulses
// V += JT * lambda
var impulse = vec2.scale(this.u, this.lambda_acc);
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * this.lambda_acc * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * this.lambda_acc * body2.i_inv;
}
else {
this.lambda_acc = 0;
}
}
MaxDistanceJoint.prototype.solveVelocityConstraints = function() {
if (this.initial_err == 0)
return;
var body1 = this.body1;
var body2 = this.body2;
// compute lambda for velocity constraint
// solve J * invM * JT * lambda = -J * V
var cdot = this.u.dot(vec2.sub(body2.v, body1.v)) + this.s2 * body2.w - this.s1 * body1.w;
var lambda = -this.em * cdot;
// accumulate lambda for velocity constraint
this.lambda_acc += lambda;
// apply impulses
// V += JT * lambda
var impulse = vec2.scale(this.u, lambda);
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * lambda * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * lambda * body2.i_inv;
}
MaxDistanceJoint.prototype.solvePositionConstraints = function() {
if (this.initial_err == 0)
return;
var body1 = this.body1;
var body2 = this.body2;
// transformed r1, r2
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
// World Center points
var pc1 = vec2.add(body1.p, body1.centroid);
var pc2 = vec2.add(body2.p, body2.centroid);
// delta vector between two anchors
var d = vec2.sub(vec2.add(pc2, r2), vec2.add(pc1, r1));
// distance between two anchors
var dist = d.length();
// unit delta vector
u = vec2.scale(d, 1 / dist);
// position constraint
var c = 0;
if (dist < this.minDist) {
c = dist - this.minDist;
}
else if (dist > this.maxDist) {
c = dist - this.maxDist;
}
var correction = Math.clamp(c, -Joint.MAX_LINEAR_CORRECTION, Joint.MAX_LINEAR_CORRECTION);
// compute lambda for position constraint
// solve J * invM * JT * lambda = -C / dt
var s1 = vec2.cross(r1, u);
var s2 = vec2.cross(r2, u);
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * s1 * s1 + body2.i_inv * s2 * s2;
var lambda_dt = em_inv == 0 ? 0 : -correction / em_inv;
// apply impulses
// X += JT * lambda * dt
var impulse_dt = vec2.scale(u, lambda_dt);
body1.p.mad(impulse_dt, -body1.m_inv);
body1.a -= s1 * lambda_dt * body1.i_inv;
body2.p.mad(impulse_dt, body2.m_inv);
body2.a += s2 * lambda_dt * body2.i_inv;
return Math.abs(c) < Joint.LINEAR_SLOP;
}
MaxDistanceJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.u, this.lambda_acc * dt_inv);
}
MaxDistanceJoint.prototype.getReactionTorque = function(dt_inv) {
return 0;
}
//------------------------------------------
// Damped Spring (Deprecated)
//------------------------------------------
SpringJoint = function(body1, body2, anchor1, anchor2, restLength, stiffness, damping) {
Joint.call(this, body1, body2, true);
// local anchor points
this.anchor1 = anchor1;
this.anchor2 = anchor2;
this.restLength = restLength;
this.stiffness = stiffness;
this.damping = damping;
}
SpringJoint.prototype = new Joint;
SpringJoint.prototype.constructor = SpringJoint;
SpringJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body2.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
var d = vec2.sub(vec2.add(body2.p, this.r2), vec2.add(body1.p, this.r1));
var dist = d.length();
this.u = vec2.scale(d, 1 / dist);
// s1, s2
this.s1 = vec2.cross(this.r1, this.u);
this.s2 = vec2.cross(this.r2, this.u);
// invEM = J * invM * JT
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.s1 * this.s1 + body2.i_inv * this.s2 * this.s2;
this.em = em_inv == 0 ? 0 : 1 / em_inv;
//
this.target_rnv = 0;
this.v_coeff = 1.0 - Math.exp(-this.damping * dt * em_inv);
// apply spring force
var spring_f = (this.restLength - dist) * this.stiffness;
this.spring_impulse = spring_f * dt;
// apply impulses
// V += JT * lambda
var impulse = vec2.scale(this.u, this.spring_impulse);
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * this.spring_impulse * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * this.spring_impulse * body2.i_inv;
}
SpringJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// compute lambda for velocity constraint
// solve J * invM * JT * lambda = -J * V
var cdot = this.u.dot(vec2.sub(body2.v, body1.v)) + this.s2 * body2.w - this.s1 * body1.w;
var rnv = cdot + this.target_rnv;
// compute velocity loss from drag
var v_damp = rnv * this.v_coeff;
this.target_rnv = -rnv + v_damp;
var lambda = -this.em * v_damp;
// apply impulses
// V += JT * lambda
var impulse = vec2.scale(this.u, lambda);
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * lambda * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * lambda * body2.i_inv;
}
SpringJoint.prototype.solvePositionConstraints = function() {
return true;
}
SpringJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.u, this.spring_impulse * dt_inv);
}
SpringJoint.prototype.getReactionTorque = function(dt_inv) {
return 0;
}*/

View file

@ -1,150 +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.
*/
//-------------------------------------------------------------------------------------------------
// Mouse Joint
//
// p = attached point, m = mouse point (constant)
// C = p - m
// Cdot = v + cross(w, r)
// J = [ I, -skew(r) ]
//
// impulse = JT * lambda = [ lambda, cross(r2, lambda) ]
//-------------------------------------------------------------------------------------------------
MouseJoint = function(mouseBody, body, anchor) {
if (arguments.length == 0)
return;
Joint.call(this, Joint.TYPE_MOUSE, mouseBody, body, true);
// Local anchor points
this.anchor1 = this.body1.getLocalPoint(anchor);
this.anchor2 = this.body2.getLocalPoint(anchor);
// Soft constraint coefficients
this.gamma = 0;
this.beta_c = 0;
// Spring coefficients
this.frequencyHz = 5;
this.dampingRatio = 0.9;
// Accumulated impulse
this.lambda_acc = new vec2(0, 0);
}
MouseJoint.prototype = new Joint;
MouseJoint.prototype.constructor = MouseJoint;
MouseJoint.prototype.setSpringFrequencyHz = function(frequencyHz) {
this.frequencyHz = frequencyHz;
}
MouseJoint.prototype.setSpringDampingRatio = function(dampingRatio) {
this.dampingRatio = dampingRatio;
}
MouseJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
// Frequency
var omega = 2 * Math.PI * this.frequencyHz;
// Spring stiffness
var k = body2.m * (omega * omega);
// Damping coefficient
var d = body2.m * 2 * this.dampingRatio * omega;
// Soft constraint formulas
// gamma and beta are divided by dt to reduce computation
this.gamma = (d + k * dt) * dt;
this.gamma = this.gamma == 0 ? 0 : 1 / this.gamma;
var beta = dt * k * this.gamma;
// Transformed r
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// invEM = J * invM * JT
var r2 = this.r2;
var r2y_i = r2.y * body2.i_inv;
var k11 = body2.m_inv + r2.y * r2y_i + this.gamma;
var k12 = -r2.x * r2y_i;
var k22 = body2.m_inv + r2.x * r2.x * body2.i_inv + this.gamma;
this.em_inv = new mat2(k11, k12, k12, k22);
// Position constraint
var c = vec2.sub(vec2.add(body2.p, this.r2), body1.p);
this.beta_c = vec2.scale(c, beta);
body2.w *= 0.98;
if (warmStarting) {
// Apply cached constraint impulse
// V += JT * lambda * invM
body2.v.mad(this.lambda_acc, body2.m_inv);
body2.w += vec2.cross(this.r2, this.lambda_acc) * body2.i_inv;
}
else {
this.lambda_acc.set(0, 0);
}
}
MouseJoint.prototype.solveVelocityConstraints = function() {
var body2 = this.body2;
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -(J * V + beta * C + gamma * (lambda_acc + lambda))
// in 2D: cross(w, r) = perp(r) * w
var cdot = vec2.mad(body2.v, vec2.perp(this.r2), body2.w);
var soft = vec2.mad(this.beta_c, this.lambda_acc, this.gamma);
var lambda = this.em_inv.solve(vec2.add(cdot, soft).neg());
// Accumulate lambda
var lambda_old = this.lambda_acc.duplicate();
this.lambda_acc.addself(lambda);
var lsq = this.lambda_acc.lengthsq();
if (lsq > this.maxImpulse * this.maxImpulse) {
this.lambda_acc.scale(this.maxImpulse / Math.sqrt(lsq));
}
lambda = vec2.sub(this.lambda_acc, lambda_old);
// Apply constraint impulse
// V += JT * lambda * invM
body2.v.mad(lambda, body2.m_inv);
body2.w += vec2.cross(this.r2, lambda) * body2.i_inv;
}
MouseJoint.prototype.solvePositionConstraints = function() {
return true;
}
MouseJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.lambda_acc, dt_inv);
}
MouseJoint.prototype.getReactionTorque = function(dt_inv) {
return 0;
}

View file

@ -1,241 +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.
*/
//-------------------------------------------------------------------------------------------------
// Prismatic Joint
//
// Linear Constraint:
// d = p2 - p1
// n = normalize(perp(d))
// C1 = dot(n, d)
// C1dot = dot(d, dn/dt) + dot(n dd/dt)
// = dot(d, cross(w1, n)) + dot(n, v2 + cross(w2, r2) - v1 - cross(w1, r1))
// = dot(d, cross(w1, n)) + dot(n, v2) + dot(n, cross(w2, r2)) - dot(n, v1) - dot(n, cross(w1, r1))
// = -dot(n, v1) - dot(cross(d + r1, n), w1) + dot(n, v2) + dot(cross(r2, n), w2)
// J1 = [ -n, -s1, n, s2 ]
// s1 = cross(r1 + d, n)
// s2 = cross(r2, n)
//
// Angular Constraint:
// C2 = a2 - a1 - initial_da
// C2dot = w2 - w1
// J2 = [ 0, -1, 0, 1 ]
//
// Block Jacobian Matrix:
// J = [ -n, -s1, n, s2 ]
// [ 0, -1, 0, 1 ]
//
// impulse = JT * lambda = [ -n * lambda_x, -(s1 * lambda_x + lambda_y), n * lambda_x, s2 * lambda_x + lambda_y ]
//-------------------------------------------------------------------------------------------------
PrismaticJoint = function(body1, body2, anchor1, anchor2) {
Joint.call(this, Joint.TYPE_PRISMATIC, body1, body2, true);
// Local anchor points
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.anchor2 = this.body2.getLocalPoint(anchor2);
var d = vec2.sub(anchor2, anchor1);
// Body1's local line normal
this.n_local = this.body1.getLocalVector(vec2.normalize(vec2.perp(d)));
this.da = body2.a - body1.a;
// Accumulated lambda
this.lambda_acc = new vec2(0, 0);
}
PrismaticJoint.prototype = new Joint;
PrismaticJoint.prototype.constructor = PrismaticJoint;
PrismaticJoint.prototype.setWorldAnchor1 = function(anchor1) {
// Local anchor points
this.anchor1 = this.body1.getLocalPoint(anchor1);
var d = vec2.sub(this.getWorldAnchor2(), anchor1);
// Body1's local line normal
this.n_local = this.body1.getLocalVector(vec2.normalize(vec2.perp(d)));
}
PrismaticJoint.prototype.setWorldAnchor2 = function(anchor2) {
// Local anchor points
this.anchor2 = this.body2.getLocalPoint(anchor2);
var d = vec2.sub(anchor2, this.getWorldAnchor1());
// Body1's local line normal
this.n_local = this.body1.getLocalVector(vec2.normalize(vec2.perp(d)));
}
PrismaticJoint.prototype.serialize = function() {
return {
"type": "PrismaticJoint",
"body1": this.body1.id,
"body2": this.body2.id,
"anchor1": this.body1.getWorldPoint(this.anchor1),
"anchor2": this.body2.getWorldPoint(this.anchor2),
"collideConnected": this.collideConnected,
"maxForce": this.maxForce,
"breakable": this.breakable
};
}
PrismaticJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
// Transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// World anchor points
var p1 = vec2.add(body1.p, this.r1);
var p2 = vec2.add(body2.p, this.r2);
// Delta vector between world anchor points
var d = vec2.sub(p2, p1);
// r1 + d
this.r1_d = vec2.add(this.r1, d);
// World line normal
this.n = vec2.normalize(vec2.perp(d));
// s1, s2
this.s1 = vec2.cross(this.r1_d, this.n);
this.s2 = vec2.cross(this.r2, this.n);
// invEM = J * invM * JT
var s1 = this.s1;
var s2 = this.s2;
var s1_i = s1 * body1.i_inv;
var s2_i = s2 * body2.i_inv;
var k11 = body1.m_inv + body2.m_inv + s1 * s1_i + s2 * s2_i;
var k12 = s1_i + s2_i;
var k22 = body1.i_inv + body2.i_inv;
this.em_inv = new mat2(k11, k12, k12, k22);
if (warmStarting) {
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.n, this.lambda_acc.x);
// Apply cached constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= (this.s1 * this.lambda_acc.x + this.lambda_acc.y) * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += (this.s2 * this.lambda_acc.x + this.lambda_acc.y) * body2.i_inv;
}
else {
this.lambda_acc.set(0, 0);
}
}
PrismaticJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -J * V
var cdot1 = this.n.dot(vec2.sub(body2.v, body1.v)) + this.s2 * body2.w - this.s1 * body1.w;
var cdot2 = body2.w - body1.w;
var lambda = this.em_inv.solve(new vec2(-cdot1, -cdot2));
// Accumulate lambda
this.lambda_acc.addself(lambda);
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.n, lambda.x);
// Apply constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= (this.s1 * lambda.x + lambda.y) * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += (this.s2 * lambda.x + lambda.y) * body2.i_inv;
}
PrismaticJoint.prototype.solvePositionConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Transformed r1, r2
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
// World anchor points
var p1 = vec2.add(body1.p, r1);
var p2 = vec2.add(body2.p, r2);
// Delta vector between world anchor points
var d = vec2.sub(p2, p1);
// r1 + d
var r1_d = vec2.add(r1, d);
// World line normal
var n = vec2.rotate(this.n_local, body1.a);
// Position constraint
var c1 = vec2.dot(n, d);
var c2 = body2.a - body1.a - this.da;
var correction = new vec2;
correction.x = Math.clamp(c1, -Joint.MAX_LINEAR_CORRECTION, Joint.MAX_LINEAR_CORRECTION);
correction.y = Math.clamp(c2, -Joint.MAX_ANGULAR_CORRECTION, Joint.MAX_ANGULAR_CORRECTION);
// Compute impulse for position constraint
// Solve J * invM * JT * lambda = -C / dt
var s1 = vec2.cross(r1_d, n);
var s2 = vec2.cross(r2, n);
var s1_i = s1 * body1.i_inv;
var s2_i = s2 * body2.i_inv;
var k11 = body1.m_inv + body2.m_inv + s1 * s1_i + s2 * s2_i;
var k12 = s1_i + s2_i;
var k22 = body1.i_inv + body2.i_inv;
var em_inv = new mat2(k11, k12, k12, k22);
var lambda_dt = em_inv.solve(correction.neg());
// Apply constarint impulses
// impulse = JT * lambda
// X += impulse * invM * dt
var impulse_dt = vec2.scale(n, lambda_dt.x);
body1.p.mad(impulse_dt, -body1.m_inv);
body1.a -= (vec2.cross(r1_d, impulse_dt) + lambda_dt.y) * body1.i_inv;
body2.p.mad(impulse_dt, body2.m_inv);
body2.a += (vec2.cross(r2, impulse_dt) + lambda_dt.y) * body2.i_inv;
return Math.abs(c1) <= Joint.LINEAR_SLOP && Math.abs(c2) <= Joint.ANGULAR_SLOP;
}
PrismaticJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.n, this.lambda_acc.x * dt_inv);
}
PrismaticJoint.prototype.getReactionTorque = function(dt_inv) {
return this.lambda_acc.y * dt_inv;
}

View file

@ -1,378 +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.
*/
//-------------------------------------------------------------------------------------------------
// Revolute Joint
//
// Point-to-Point Constraint:
// C1 = p2 - p1
// C1dot = v2 + cross(w2, r2) - v1 - cross(w1, r1)
// = -v1 + cross(r1, w1) + v2 - cross(r2, w1)
// J1 = [ -I, skew(r1), I, -skew(r2) ]
//
// Angular Constraint (for angle limit):
// C2 = a2 - a1 - refAngle
// C2dot = w2 - w1
// J2 = [ 0, -1, 0, 1 ]
//
// Block Jacobian Matrix:
// J = [ -I, skew(r1), I, -skew(r2) ]
// [ 0, -1, 0, 1 ]
//
// impulse = JT * lambda = [ -lambda_xy, -(cross(r1, lambda_xy) + lambda_z), lambda_xy, cross(r1, lambda_xy) + lambda_z ]
//-------------------------------------------------------------------------------------------------
RevoluteJoint = function(body1, body2, anchor) {
Joint.call(this, Joint.TYPE_REVOLUTE, body1, body2, false);
this.anchor1 = this.body1.getLocalPoint(anchor);
this.anchor2 = this.body2.getLocalPoint(anchor);
// Initial angle difference
this.refAngle = body2.a - body1.a;
// Accumulated lambda
this.lambda_acc = new vec3(0, 0, 0);
this.motorLambda_acc = 0;
// Angle limit
this.limitEnabled = false;
this.limitLowerAngle = 0;
this.limitUpperAngle = 0;
this.limitState = Joint.LIMIT_STATE_INACTIVE;
// Motor
this.motorEnabled = false;
this.motorSpeed = 0;
this.maxMotorTorque = 0;
}
RevoluteJoint.prototype = new Joint;
RevoluteJoint.prototype.constructor = RevoluteJoint;
RevoluteJoint.prototype.setWorldAnchor1 = function(anchor1) {
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.anchor2 = this.body2.getLocalPoint(anchor1);
}
RevoluteJoint.prototype.setWorldAnchor2 = function(anchor2) {
this.anchor1 = this.body1.getLocalPoint(anchor2);
this.anchor2 = this.body2.getLocalPoint(anchor2);
}
RevoluteJoint.prototype.serialize = function() {
return {
"type": "RevoluteJoint",
"body1": this.body1.id,
"body2": this.body2.id,
"anchor": this.body1.getWorldPoint(this.anchor1),
"collideConnected": this.collideConnected,
"maxForce": this.maxForce,
"breakable": this.breakable,
"limitEnabled": this.limitEnabled,
"limitLowerAngle": this.limitLowerAngle,
"limitUpperAngle": this.limitUpperAngle,
"motorEnabled": this.motorEnabled,
"motorSpeed": this.motorSpeed,
"maxMotorTorque": this.maxMotorTorque
};
}
RevoluteJoint.prototype.enableMotor = function(flag) {
this.motorEnabled = flag;
}
RevoluteJoint.prototype.setMotorSpeed = function(speed) {
this.motorSpeed = speed;
}
RevoluteJoint.prototype.setMaxMotorTorque = function(torque) {
this.maxMotorTorque = torque;
}
RevoluteJoint.prototype.enableLimit = function(flag) {
this.limitEnabled = flag;
}
RevoluteJoint.prototype.setLimits = function(lower, upper) {
this.limitLowerAngle = lower;
this.limitUpperAngle = upper;
}
RevoluteJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
if (!this.motorEnabled) {
this.motorLambda_acc = 0;
}
else {
this.maxMotorImpulse = this.maxMotorTorque * dt;
}
if (this.limitEnabled) {
var da = body2.a - body1.a - this.refAngle;
if (Math.abs(this.limitUpperAngle - this.limitLowerAngle) < Joint.ANGULAR_SLOP) {
this.limitState = Joint.LIMIT_STATE_EQUAL_LIMITS;
}
else if (da <= this.limitLowerAngle) {
if (this.limitState != Joint.LIMIT_STATE_AT_LOWER) {
this.lambda_acc.z = 0;
}
this.limitState = Joint.LIMIT_STATE_AT_LOWER;
}
else if (da >= this.limitUpperAngle) {
if (this.limitState != Joint.LIMIT_STATE_AT_UPPER) {
this.lambda_acc.z = 0;
}
this.limitState = Joint.LIMIT_STATE_AT_UPPER;
}
else {
this.limitState = Joint.LIMIT_STATE_INACTIVE;
this.lambda_acc.z = 0;
}
}
else {
this.limitState = Joint.LIMIT_STATE_INACTIVE;
}
// Transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// invEM = J * invM * JT
var sum_m_inv = body1.m_inv + body2.m_inv;
var r1 = this.r1;
var r2 = this.r2;
var r1x_i = r1.x * body1.i_inv;
var r1y_i = r1.y * body1.i_inv;
var r2x_i = r2.x * body2.i_inv;
var r2y_i = r2.y * body2.i_inv;
var k11 = sum_m_inv + r1.y * r1y_i + r2.y * r2y_i;
var k12 = -r1.x * r1y_i - r2.x * r2y_i;
var k13 = -r1y_i - r2y_i;
var k22 = sum_m_inv + r1.x * r1x_i + r2.x * r2x_i;
var k23 = r1x_i + r2x_i;
var k33 = body1.i_inv + body2.i_inv;
this.em_inv = new mat3(k11, k12, k13, k12, k22, k23, k13, k23, k33);
// K2 = J2 * invM * J2T
if (k33 != 0) {
this.em2 = 1 / k33;
}
if (warmStarting) {
// Apply cached constraint impulses
// V += JT * lambda
var lambda_xy = new vec2(this.lambda_acc.x, this.lambda_acc.y);
var lambda_z = this.lambda_acc.z + this.motorLambda_acc;
body1.v.mad(lambda_xy, -body1.m_inv);
body1.w -= (vec2.cross(this.r1, lambda_xy) + lambda_z) * body1.i_inv;
body2.v.mad(lambda_xy, body2.m_inv);
body2.w += (vec2.cross(this.r2, lambda_xy) + lambda_z) * body2.i_inv;
}
else {
this.lambda_acc.set(0, 0, 0);
this.motorLambda_acc = 0;
}
}
RevoluteJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Solve motor constraint
if (this.motorEnabled && this.limitState != Joint.LIMIT_STATE_EQUAL_LIMITS) {
// Compute motor impulse
var cdot = body2.w - body1.w - this.motorSpeed;
var lambda = -this.em2 * cdot;
var motorLambdaOld = this.motorLambda_acc;
this.motorLambda_acc = Math.clamp(this.motorLambda_acc + lambda, -this.maxMotorImpulse, this.maxMotorImpulse);
lambda = this.motorLambda_acc - motorLambdaOld;
// Apply motor constraint impulses
body1.w -= lambda * body1.i_inv;
body2.w += lambda * body2.i_inv;
}
// Solve point-to-point constraint with angular limit
if (this.limitEnabled && this.limitState != Joint.LIMIT_STATE_INACTIVE) {
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -J * V
// in 2D: cross(w, r) = perp(r) * w
var v1 = vec2.mad(body1.v, vec2.perp(this.r1), body1.w);
var v2 = vec2.mad(body2.v, vec2.perp(this.r2), body2.w);
var cdot1 = vec2.sub(v2, v1);
var cdot2 = body2.w - body1.w;
var cdot = vec3.fromVec2(cdot1, cdot2);
var lambda = this.em_inv.solve(cdot.neg());
if (this.limitState == Joint.LIMIT_STATE_EQUAL_LIMITS) {
// Accumulate lambda
this.lambda_acc.addself(lambda);
}
else if (this.limitState == Joint.LIMIT_STATE_AT_LOWER || this.limitState == Joint.LIMIT_STATE_AT_UPPER) {
// Accumulated new lambda.z
var newLambda_z = this.lambda_acc.z + lambda.z;
var lowerLimited = this.limitState == Joint.LIMIT_STATE_AT_LOWER && newLambda_z < 0;
var upperLimited = this.limitState == Joint.LIMIT_STATE_AT_UPPER && newLambda_z > 0;
if (lowerLimited || upperLimited) {
// Modify last equation to get lambda_acc.z to 0
// That is, lambda.z have to be equal -lambda_acc.z
// rhs = -J * V - (K_13, K_23, K_33) * (lambda.z + lambda_acc.z)
// Solve J * invM * JT * reduced_lambda = rhs
var rhs = vec2.add(cdot1, vec2.scale(new vec2(this.em_inv._13, this.em_inv._23), newLambda_z));
var reduced = this.em_inv.solve2x2(rhs.neg());
lambda.x = reduced.x;
lambda.y = reduced.y;
lambda.z = -this.lambda_acc.z;
// Accumulate lambda
this.lambda_acc.x += lambda.x;
this.lambda_acc.y += lambda.y;
this.lambda_acc.z = 0;
}
else {
// Accumulate lambda
this.lambda_acc.addself(lambda);
}
}
// Apply constraint impulses
// V += JT * lambda * invM
var lambda_xy = new vec2(lambda.x, lambda.y);
body1.v.mad(lambda_xy, -body1.m_inv);
body1.w -= (vec2.cross(this.r1, lambda_xy) + lambda.z) * body1.i_inv;
body2.v.mad(lambda_xy, body2.m_inv);
body2.w += (vec2.cross(this.r2, lambda_xy) + lambda.z) * body2.i_inv;
}
// Solve point-to-point constraint
else {
// Compute lambda for velocity constraint
// Solve J1 * invM * J1T * lambda = -J1 * V
// in 2D: cross(w, r) = perp(r) * w
var v1 = vec2.mad(body1.v, vec2.perp(this.r1), body1.w);
var v2 = vec2.mad(body2.v, vec2.perp(this.r2), body2.w);
var cdot = vec2.sub(v2, v1);
var lambda = this.em_inv.solve2x2(cdot.neg());
// Accumulate lambda
this.lambda_acc.addself(vec3.fromVec2(lambda, 0));
// Apply constraint impulses
// V += J1T * lambda * invM
body1.v.mad(lambda, -body1.m_inv);
body1.w -= vec2.cross(this.r1, lambda) * body1.i_inv;
body2.v.mad(lambda, body2.m_inv);
body2.w += vec2.cross(this.r2, lambda) * body2.i_inv;
}
}
RevoluteJoint.prototype.solvePositionConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
var angularError = 0;
var positionError = 0;
// Solve limit constraint
if (this.limitEnabled && this.limitState != Joint.LIMIT_STATE_INACTIVE) {
var da = body2.a - body1.a - this.refAngle;
// angular lambda = -EM * C / dt
var angularImpulseDt = 0;
if (this.limitState == Joint.LIMIT_STATE_EQUAL_LIMITS) {
var c = Math.clamp(da - this.limitLowerAngle, -Joint.MAX_ANGULAR_CORRECTION, Joint.MAX_ANGULAR_CORRECTION);
angularError = Math.abs(c);
angularImpulseDt = -this.em2 * c;
}
else if (this.limitState == Joint.LIMIT_STATE_AT_LOWER) {
var c = da - this.limitLowerAngle;
angularError = -c;
c = Math.clamp(c + Joint.ANGULAR_SLOP, -Joint.MAX_ANGULAR_CORRECTION, 0);
angularImpulseDt = -this.em2 * c;
}
else if (this.limitState == Joint.LIMIT_STATE_AT_UPPER) {
var c = da - this.limitUpperAngle;
angularError = c;
c = Math.clamp(c - Joint.ANGULAR_SLOP, 0, Joint.MAX_ANGULAR_CORRECTION);
angularImpulseDt = -this.em2 * c;
}
body1.a -= angularImpulseDt * body1.i_inv;
body2.a += angularImpulseDt * body2.i_inv;
}
// Solve point-to-point constraint
{
// Transformed r1, r2
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
// Position constraint
var c = vec2.sub(vec2.add(body2.p, r2), vec2.add(body1.p, r1));
var correction = vec2.truncate(c, Joint.MAX_LINEAR_CORRECTION);
positionError = correction.length();
// Compute lambda for position constraint
// Solve J1 * invM * J1T * lambda = -C / dt
var sum_m_inv = body1.m_inv + body2.m_inv;
var r1y_i = r1.y * body1.i_inv;
var r2y_i = r2.y * body2.i_inv;
var k11 = sum_m_inv + r1.y * r1y_i + r2.y * r2y_i;
var k12 = -r1.x * r1y_i - r2.x * r2y_i;
var k22 = sum_m_inv + r1.x * r1.x * body1.i_inv + r2.x * r2.x * body2.i_inv;
var em_inv = new mat2(k11, k12, k12, k22);
var lambda_dt = em_inv.solve(correction.neg());
// Apply constraint impulses
// impulse = J1T * lambda
// X += impulse * invM * dt
body1.p.mad(lambda_dt, -body1.m_inv);
body1.a -= vec2.cross(r1, lambda_dt) * body1.i_inv;
body2.p.mad(lambda_dt, body2.m_inv);
body2.a += vec2.cross(r2, lambda_dt) * body2.i_inv;
}
return positionError < Joint.LINEAR_SLOP && angularError < Joint.ANGULAR_SLOP;
}
RevoluteJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.lambda_acc, dt_inv);
}
RevoluteJoint.prototype.getReactionTorque = function(dt_inv) {
return 0;
}

View file

@ -1,211 +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.
*/
//-------------------------------------------------------------------------------------------------
// Rope Joint
//
// d = p2 - p1
// u = d / norm(d)
// C = norm(d) - l
// C = sqrt(dot(d, d)) - l
// Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1))
// = -dot(u, v1) - dot(w1, cross(r1, u)) + dot(u, v2) + dot(w2, cross(r2, u))
// J = [ -u, -cross(r1, u), u, cross(r2, u) ]
//
// impulse = JT * lambda = [ -u * lambda, -cross(r1, u) * lambda, u * lambda, cross(r1, u) * lambda ]
//-------------------------------------------------------------------------------------------------
RopeJoint = function(body1, body2, anchor1, anchor2) {
Joint.call(this, Joint.TYPE_ROPE, body1, body2, true);
// Local anchor points
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.anchor2 = this.body2.getLocalPoint(anchor2);
// Max distance
this.maxDistance = vec2.dist(anchor1, anchor2);
// Accumulated impulse
this.lambda_acc = 0;
}
RopeJoint.prototype = new Joint;
RopeJoint.prototype.constructor = RopeJoint;
RopeJoint.prototype.setWorldAnchor1 = function(anchor1) {
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.maxDistance = vec2.dist(anchor1, this.getWorldAnchor2());
}
RopeJoint.prototype.setWorldAnchor2 = function(anchor2) {
this.anchor2 = this.body2.getLocalPoint(anchor2);
this.maxDistance = vec2.dist(anchor2, this.getWorldAnchor1());
}
RopeJoint.prototype.serialize = function() {
return {
"type": "RopeJoint",
"body1": this.body1.id,
"body2": this.body2.id,
"anchor1": this.body1.getWorldPoint(this.anchor1),
"anchor2": this.body2.getWorldPoint(this.anchor2),
"collideConnected": this.collideConnected,
"maxForce": this.maxForce,
"breakable": this.breakable,
};
}
RopeJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
// Transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// Delta vector between two world anchors
var d = vec2.sub(vec2.add(body2.p, this.r2), vec2.add(body1.p, this.r1));
// Distance between two anchors
this.distance = d.length();
//
var c = this.distance - this.maxDistance;
if (c > 0) {
this.cdt = 0;
this.limitState = Joint.LIMIT_STATE_AT_UPPER;
}
else {
this.cdt = c / dt;
this.limitState = Joint.LIMIT_STATE_INACTIVE;
}
// Unit delta vector
if (this.distance > Joint.LINEAR_SLOP) {
this.u = vec2.scale(d, 1 / this.distance);
}
else {
this.u = vec2.zero;
}
// s1, s2
this.s1 = vec2.cross(this.r1, this.u);
this.s2 = vec2.cross(this.r2, this.u);
// invEM = J * invM * JT
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.s1 * this.s1 + body2.i_inv * this.s2 * this.s2;
this.em = em_inv == 0 ? 0 : 1 / em_inv;
if (warmStarting) {
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.u, this.lambda_acc);
// Apply cached constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * this.lambda_acc * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * this.lambda_acc * body2.i_inv;
}
else {
this.lambda_acc = 0;
}
}
RopeJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -(J * V)
var cdot = this.u.dot(vec2.sub(body2.v, body1.v)) + this.s2 * body2.w - this.s1 * body1.w;
var lambda = -this.em * (cdot + this.cdt);
// Accumulate lambda and clamp it to zero
var lambda_old = this.lambda_acc;
this.lambda_acc = Math.min(lambda_old + lambda, 0);
lambda = this.lambda_acc - lambda_old;
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.u, lambda);
// Apply constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.s1 * lambda * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.s2 * lambda * body2.i_inv;
}
RopeJoint.prototype.solvePositionConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Transformed r1, r2
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
// Delta vector between two anchors
var d = vec2.sub(vec2.add(body2.p, r2), vec2.add(body1.p, r1));
// Distance between two anchors
var dist = d.length();
// Unit delta vector
var u = vec2.scale(d, 1 / dist);
// Position constraint
var c = dist - this.maxDistance;
var correction = Math.clamp(c, 0, Joint.MAX_LINEAR_CORRECTION);
// Compute lambda for correction
// Solve J * invM * JT * lambda = -C / dt
var s1 = vec2.cross(r1, u);
var s2 = vec2.cross(r2, u);
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * s1 * s1 + body2.i_inv * s2 * s2;
var lambda_dt = em_inv == 0 ? 0 : -correction / em_inv;
// Apply constraint impulses
// impulse = JT * lambda
// X += impulse * invM * dt
var impulse_dt = vec2.scale(u, lambda_dt);
body1.p.mad(impulse_dt, -body1.m_inv);
body1.a -= s1 * lambda_dt * body1.i_inv;
body2.p.mad(impulse_dt, body2.m_inv);
body2.a += s2 * lambda_dt * body2.i_inv;
return c < Joint.LINEAR_SLOP;
}
RopeJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.u, this.lambda_acc * dt_inv);
}
RopeJoint.prototype.getReactionTorque = function(dt_inv) {
return 0;
}

View file

@ -1,306 +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.
*/
//-------------------------------------------------------------------------------------------------
// Weld Joint
//
// Point-to-Point Constraint:
// C1 = p2 - p1
// Cdot1 = v2 + cross(w2, r2) - v1 - cross(w1, r1)
// = -v1 + cross(r1, w1) + v2 - cross(r2, w1)
// J1 = [ -I, skew(r1), I, -skew(r2) ]
//
// Angular Constraint:
// C2 = a2 - a1
// C2dot = w2 - w1
// J2 = [ 0, -1, 0, 1 ]
//
// Block Jacobian Matrix:
// J = [ -I, skew(r1), I, -skew(r2) ]
// [ 0, -1, 0, 1 ]
//
// impulse = JT * lambda = [ -lambda_xy, -(cross(r1, lambda_xy) + lambda_z), lambda_xy, cross(r1, lambda_xy) + lambda_z ]
//-------------------------------------------------------------------------------------------------
WeldJoint = function(body1, body2, anchor) {
Joint.call(this, Joint.TYPE_WELD, body1, body2, false);
this.anchor1 = this.body1.getLocalPoint(anchor);
this.anchor2 = this.body2.getLocalPoint(anchor);
// Soft constraint coefficients
this.gamma = 0;
this.beta_c = 0;
// Spring coefficients
this.frequencyHz = 0;
this.dampingRatio = 0;
// Accumulated lambda
this.lambda_acc = new vec3(0, 0, 0);
}
WeldJoint.prototype = new Joint;
WeldJoint.prototype.constructor = WeldJoint;
WeldJoint.prototype.setWorldAnchor1 = function(anchor1) {
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.anchor2 = this.body2.getLocalPoint(anchor1);
}
WeldJoint.prototype.setWorldAnchor2 = function(anchor2) {
this.anchor1 = this.body1.getLocalPoint(anchor2);
this.anchor2 = this.body2.getLocalPoint(anchor2);
}
WeldJoint.prototype.serialize = function() {
return {
"type": "WeldJoint",
"body1": this.body1.id,
"body2": this.body2.id,
"anchor1": this.body1.getWorldPoint(this.anchor1),
"anchor2": this.body2.getWorldPoint(this.anchor2),
"collideConnected": this.collideConnected,
"maxForce": this.maxForce,
"breakable": this.breakable,
"frequencyHz": this.frequencyHz,
"dampingRatio": this.dampingRatio
};
}
WeldJoint.prototype.setSpringFrequencyHz = function(frequencyHz) {
// NOTE: frequencyHz should be limited to under 4 times time steps
this.frequencyHz = frequencyHz;
}
WeldJoint.prototype.setSpringDampingRatio = function(dampingRatio) {
this.dampingRatio = dampingRatio;
}
WeldJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
// Transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// invEM = J * invM * JT
var sum_m_inv = body1.m_inv + body2.m_inv;
var r1 = this.r1;
var r2 = this.r2;
var r1x_i = r1.x * body1.i_inv;
var r1y_i = r1.y * body1.i_inv;
var r2x_i = r2.x * body2.i_inv;
var r2y_i = r2.y * body2.i_inv;
var k11 = sum_m_inv + r1.y * r1y_i + r2.y * r2y_i;
var k12 = -r1.x * r1y_i - r2.x * r2y_i;
var k13 = -r1y_i - r2y_i;
var k22 = sum_m_inv + r1.x * r1x_i + r2.x * r2x_i;
var k23 = r1x_i + r2x_i;
var k33 = body1.i_inv + body2.i_inv;
this.em_inv = new mat3(k11, k12, k13, k12, k22, k23, k13, k23, k33);
// Compute soft constraint parameters
if (this.frequencyHz > 0) {
var m = k33 > 0 ? 1 / k33 : 0;
// Frequency
var omega = 2 * Math.PI * this.frequencyHz;
// Spring stiffness
var k = m * omega * omega;
// Damping coefficient
var c = m * 2 * this.dampingRatio * omega;
// Soft constraint formulas
// gamma and beta are divided by dt to reduce computation
this.gamma = (c + k * dt) * dt;
this.gamma = this.gamma == 0 ? 0 : 1 / this.gamma;
var beta = dt * k * this.gamma;
// Position constraint
var pc = body2.a - body1.a;
this.beta_c = beta * pc;
// invEM = invEM + gamma * I (to reduce calculation)
this.em_inv._33 += this.gamma;
}
else {
this.gamma = 0;
this.beta_c = 0;
}
if (warmStarting) {
// Apply cached constraint impulses
// V += JT * lambda * invM
var lambda_xy = new vec2(this.lambda_acc.x, this.lambda_acc.y);
var lambda_z = this.lambda_acc.z;
body1.v.mad(lambda_xy, -body1.m_inv);
body1.w -= (vec2.cross(this.r1, lambda_xy) + lambda_z) * body1.i_inv;
body2.v.mad(lambda_xy, body2.m_inv);
body2.w += (vec2.cross(this.r2, lambda_xy) + lambda_z) * body2.i_inv;
}
else {
this.lambda_acc.set(0, 0, 0);
}
}
WeldJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
if (this.frequencyHz > 0) {
// Compute lambda for angular velocity constraint
// Solve J2 * invM * J2T * lambda = -(J2 * V + beta * C + gamma * (lambda_acc + lambda))
var cdot2 = body2.w - body1.w;
lambda_z = -(cdot2 + this.beta_c + this.gamma * this.lambda_acc.z) / this.em_inv._33;
// Apply angular constraint impulses
// V += J2T * lambda * invM
body1.w -= lambda_z * body1.i_inv;
body2.w += lambda_z * body2.i_inv;
// Compute lambda for velocity constraint
// Solve J1 * invM * J1T * lambda = -J1 * V
var v1 = vec2.mad(body1.v, vec2.perp(this.r1), body1.w);
var v2 = vec2.mad(body2.v, vec2.perp(this.r2), body2.w);
var cdot1 = vec2.sub(v2, v1);
var lambda_xy = this.em_inv.solve2x2(cdot1.neg());
// Accumulate lambda
this.lambda_acc.x += lambda_xy.x;
this.lambda_acc.y += lambda_xy.y;
this.lambda_acc.z += lambda_z;
// Apply constraint impulses
// V += J1T * lambda * invM
body1.v.mad(lambda_xy, -body1.m_inv);
body1.w -= vec2.cross(this.r1, lambda_xy) * body1.i_inv;
body2.v.mad(lambda_xy, body2.m_inv);
body2.w += vec2.cross(this.r2, lambda_xy) * body2.i_inv;
}
else {
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -J * V
// in 2D: cross(w, r) = perp(r) * w
var v1 = vec2.mad(body1.v, vec2.perp(this.r1), body1.w);
var v2 = vec2.mad(body2.v, vec2.perp(this.r2), body2.w);
var cdot1 = vec2.sub(v2, v1);
var cdot2 = body2.w - body1.w;
var cdot = vec3.fromVec2(cdot1, cdot2);
var lambda = this.em_inv.solve(cdot.neg());
// Accumulate lambda
this.lambda_acc.addself(lambda);
// Apply constraint impulses
// V += JT * lambda * invM
var lambda_xy = new vec2(lambda.x, lambda.y);
body1.v.mad(lambda_xy, -body1.m_inv);
body1.w -= (vec2.cross(this.r1, lambda_xy) + lambda.z) * body1.i_inv;
body2.v.mad(lambda_xy, body2.m_inv);
body2.w += (vec2.cross(this.r2, lambda_xy) + lambda.z) * body2.i_inv;
}
}
WeldJoint.prototype.solvePositionConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Transformed r1, r2
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
// Compute J * invM * JT
var sum_m_inv = body1.m_inv + body2.m_inv;
var r1x_i = r1.x * body1.i_inv;
var r1y_i = r1.y * body1.i_inv;
var r2x_i = r2.x * body2.i_inv;
var r2y_i = r2.y * body2.i_inv;
var k11 = sum_m_inv + r1.y * r1y_i + r2.y * r2y_i;
var k12 = -r1.x * r1y_i - r2.x * r2y_i;
var k13 = -r1y_i - r2y_i;
var k22 = sum_m_inv + r1.x * r1x_i + r2.x * r2x_i;
var k23 = r1x_i + r2x_i;
var k33 = body1.i_inv + body2.i_inv;
var em_inv = new mat3(k11, k12, k13, k12, k22, k23, k13, k23, k33);
if (this.frequencyHz > 0) {
// Position constraint
var c1 = vec2.sub(vec2.add(body2.p, r2), vec2.add(body1.p, r1));
var c2 = 0;
var correction = vec2.truncate(c1, Joint.MAX_LINEAR_CORRECTION);
// Compute lambda for position constraint
// Solve J1 * invM * J1T * lambda = -C / dt
var lambda_dt_xy = em_inv.solve2x2(correction.neg());
// Apply constraint impulses
// impulse = J1T * lambda
// X += impulse * invM * dt
body1.p.mad(lambda_dt_xy, -body1.m_inv);
body1.a -= vec2.cross(r1, lambda_dt_xy) * body1.i_inv;
body2.p.mad(lambda_dt_xy, body2.m_inv);
body2.a += vec2.cross(r2, lambda_dt_xy) * body2.i_inv;
}
else {
// Position constraint
var c1 = vec2.sub(vec2.add(body2.p, r2), vec2.add(body1.p, r1));
var c2 = body2.a - body1.a;
var correction = vec3.fromVec2(
vec2.truncate(c1, Joint.MAX_LINEAR_CORRECTION),
Math.clamp(c2, -Joint.MAX_ANGULAR_CORRECTION, Joint.MAX_ANGULAR_CORRECTION));
// Compute lambda for position constraint
// Solve J * invM * JT * lambda = -C / dt
var lambda_dt = em_inv.solve(correction.neg());
// Apply constraint impulses
// impulse = JT * lambda
// X += impulse * invM * dt
var lambda_dt_xy = new vec2(lambda_dt.x, lambda_dt.y);
body1.p.mad(lambda_dt_xy, -body1.m_inv);
body1.a -= (vec2.cross(r1, lambda_dt_xy) + lambda_dt.z) * body1.i_inv;
body2.p.mad(lambda_dt_xy, body2.m_inv);
body2.a += (vec2.cross(r2, lambda_dt_xy) + lambda_dt.z) * body2.i_inv;
}
return c1.length() < Joint.LINEAR_SLOP && Math.abs(c2) <= Joint.ANGULAR_SLOP;
}
WeldJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.lambda_acc.toVec2(), dt_inv);
}
WeldJoint.prototype.getReactionTorque = function(dt_inv) {
return this.lambda_acc.z * dt_inv;
}

View file

@ -1,376 +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.
*/
//-------------------------------------------------------------------------------------------------
// Wheel Joint
//
// Point-to-Line constraint:
// d = p2 - p1
// n = normalize(perp(d))
// C = dot(n, d)
// Cdot = dot(d, dn/dt) + dot(n dd/dt)
// = dot(d, cross(w1, n)) + dot(n, v2 + cross(w2, r2) - v1 - cross(w1, r1))
// = dot(d, cross(w1, n)) + dot(n, v2) + dot(n, cross(w2, r2)) - dot(n, v1) - dot(n, cross(w1, r1))
// = -dot(n, v1) - dot(cross(d + r1, n), w1) + dot(n, v2) + dot(cross(r2, n), w2)
// J = [ -n, -sn1, n, sn2 ]
// sn1 = cross(r1 + d, n)
// sn2 = cross(r2, n)
//
// impulse = JT * lambda = [ -n * lambda, -(sn1 * lambda), n * lambda, sn2 * lambda ]
//
// Spring constraint:
// u = normalize(d)
// C = dot(u, d)
// Cdot = -dot(u, v1) - dot(cross(d + r1, u), w1) + dot(u, v2) + dot(cross(r2, u), w2)
// J = [ -u, -su1, u, su2 ]
// su1 = cross(r1 + d, u)
// su2 = cross(r2, u)
//
// impulse = JT * lambda = [ -u * lambda, -(su1 * lambda), u * lambda, su2 * lambda ]
//
// Motor rotational constraint:
// Cdot = w2 - w1
// J = [ 0, -1, 0, 1 ]
//-------------------------------------------------------------------------------------------------
WheelJoint = function(body1, body2, anchor1, anchor2) {
Joint.call(this, Joint.TYPE_WHEEL, body1, body2, true);
// Local anchor points
this.anchor1 = this.body1.getLocalPoint(anchor1);
this.anchor2 = this.body2.getLocalPoint(anchor2);
var d = vec2.sub(anchor2, anchor1);
// Rest length
this.restLength = d.length();
// Body1's local axis
this.u_local = this.body1.getLocalVector(vec2.normalize(d));
this.n_local = vec2.perp(this.u_local);
// Accumulated impulse
this.lambda_acc = 0;
this.motorLambda_acc = 0;
this.springLambda_acc = 0;
// Motor
this.motorEnabled = false;
this.motorSpeed = 0;
this.maxMotorTorque = 0;
// Soft constraint coefficients
this.gamma = 0;
this.beta_c = 0;
// Spring coefficients
this.frequencyHz = 0;
this.dampingRatio = 0;
}
WheelJoint.prototype = new Joint;
WheelJoint.prototype.constructor = WheelJoint;
WheelJoint.prototype.setWorldAnchor1 = function(anchor1) {
this.anchor1 = this.body1.getLocalPoint(anchor1);
var d = vec2.sub(this.getWorldAnchor2(), anchor1);
this.u_local = this.body1.getLocalVector(vec2.normalize(d));
this.n_local = vec2.perp(this.u_local);
}
WheelJoint.prototype.setWorldAnchor2 = function(anchor2) {
this.anchor2 = this.body2.getLocalPoint(anchor2);
var d = vec2.sub(anchor2, this.getWorldAnchor1());
this.u_local = this.body1.getLocalVector(vec2.normalize(d));
this.n_local = vec2.perp(this.u_local);
}
WheelJoint.prototype.serialize = function() {
return {
"type": "WheelJoint",
"body1": this.body1.id,
"body2": this.body2.id,
"anchor1": this.body1.getWorldPoint(this.anchor1),
"anchor2": this.body2.getWorldPoint(this.anchor2),
"collideConnected": this.collideConnected,
"maxForce": this.maxForce,
"breakable": this.breakable,
"motorEnabled": this.motorEnabled,
"motorSpeed": this.motorSpeed,
"maxMotorTorque": this.maxMotorTorque,
"frequencyHz": this.frequencyHz,
"dampingRatio": this.dampingRatio
};
}
WheelJoint.prototype.setSpringFrequencyHz = function(frequencyHz) {
// NOTE: frequencyHz should be limited to under 4 times time steps
this.frequencyHz = frequencyHz;
}
WheelJoint.prototype.setSpringDampingRatio = function(dampingRatio) {
this.dampingRatio = dampingRatio;
}
WheelJoint.prototype.enableMotor = function(flag) {
this.motorEnabled = flag;
}
WheelJoint.prototype.setMotorSpeed = function(speed) {
this.motorSpeed = speed;
}
WheelJoint.prototype.setMaxMotorTorque = function(torque) {
this.maxMotorTorque = torque;
}
WheelJoint.prototype.initSolver = function(dt, warmStarting) {
var body1 = this.body1;
var body2 = this.body2;
// Max impulse
this.maxImpulse = this.maxForce * dt;
// Transformed r1, r2
this.r1 = body1.xf.rotate(vec2.sub(this.anchor1, body1.centroid));
this.r2 = body2.xf.rotate(vec2.sub(this.anchor2, body2.centroid));
// World anchor points
var p1 = vec2.add(body1.p, this.r1);
var p2 = vec2.add(body2.p, this.r2);
// Delta vector between world anchor points
var d = vec2.sub(p2, p1);
// r1 + d
this.r1_d = vec2.add(this.r1, d);
// World line normal
this.n = vec2.rotate(this.n_local, body1.a);
// sn1, sn2
this.sn1 = vec2.cross(this.r1_d, this.n);
this.sn2 = vec2.cross(this.r2, this.n);
// invEM = J * invM * JT
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.sn1 * this.sn1 + body2.i_inv * this.sn2 * this.sn2;
this.em = em_inv > 0 ? 1 / em_inv : em_inv;
// Compute soft constraint parameters
if (this.frequencyHz > 0) {
// World delta axis
this.u = vec2.rotate(this.u_local, body1.a);
// su1, su2
this.su1 = vec2.cross(this.r1_d, this.u);
this.su2 = vec2.cross(this.r2, this.u);
// invEM = J * invM * JT
var springEm_inv = body1.m_inv + body2.m_inv + body1.i_inv * this.su1 * this.su1 + body2.i_inv * this.su2 * this.su2;
springEm = springEm_inv == 0 ? 0 : 1 / springEm_inv;
// Frequency
var omega = 2 * Math.PI * this.frequencyHz;
// Spring stiffness
var k = springEm * omega * omega;
// Damping coefficient
var c = springEm * 2 * this.dampingRatio * omega;
// Soft constraint formulas
// gamma and beta are divided by dt to reduce computation
this.gamma = (c + k * dt) * dt;
this.gamma = this.gamma == 0 ? 0 : 1 / this.gamma;
var beta = dt * k * this.gamma;
// Position constraint
var pc = vec2.dot(d, this.u) - this.restLength;
this.beta_c = beta * pc;
// invEM = invEM + gamma * I (to reduce calculation)
springEm_inv = springEm_inv + this.gamma;
this.springEm = springEm_inv == 0 ? 0 : 1 / springEm_inv;
}
else {
this.gamma = 0;
this.beta_c = 0;
this.springLambda_acc = 0;
}
if (this.motorEnabled) {
this.maxMotorImpulse = this.maxMotorTorque * dt;
// invEM2 = J2 * invM * J2T
var motorEm_inv = body1.i_inv + body2.i_inv;
this.motorEm = motorEm_inv > 0 ? 1 / motorEm_inv : motorEm_inv;
}
else {
this.motorEm = 0;
this.motorLambda_acc = 0;
}
if (warmStarting) {
// impulse = JT * lambda
var linearImpulse = vec2.scale(this.n, this.lambda_acc);
var angularImpulse1 = this.sn1 * this.lambda_acc + this.motorLambda_acc;
var angularImpulse2 = this.sn2 * this.lambda_acc + this.motorLambda_acc;
if (this.frequencyHz > 0) {
linearImpulse.addself(vec2.scale(this.u, this.springLambda_acc));
angularImpulse1 += this.su1 * this.springLambda_acc;
angularImpulse2 += this.su2 * this.springLambda_acc;
}
// Apply cached constraint impulses
// V += JT * lambda * invM
body1.v.mad(linearImpulse, -body1.m_inv);
body1.w -= angularImpulse1 * body1.i_inv;
body2.v.mad(linearImpulse, body2.m_inv);
body2.w += angularImpulse2 * body2.i_inv;
}
else {
this.lambda_acc = 0;
this.springLambda_acc = 0;
this.motorLambda_acc = 0;
}
}
WheelJoint.prototype.solveVelocityConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Solve spring constraint
if (this.frequencyHz > 0) {
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -(J * V + beta * C + gamma * (lambda_acc + lambda))
var cdot = this.u.dot(vec2.sub(body2.v, body1.v)) + this.su2 * body2.w - this.su1 * body1.w;
var soft = this.beta_c + this.gamma * this.springLambda_acc;
var lambda = -this.springEm * (cdot + soft);
// Accumulate lambda
this.springLambda_acc += lambda;
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.u, lambda);
// Apply constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.su1 * lambda * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.su2 * lambda * body2.i_inv;
}
// Solve motor constraint
if (this.motorEnabled) {
// Compute motor impulse
var cdot = body2.w - body1.w - this.motorSpeed;
var lambda = -this.motorEm * cdot;
var motorLambdaOld = this.motorLambda_acc;
this.motorLambda_acc = Math.clamp(this.motorLambda_acc + lambda, -this.maxMotorImpulse, this.maxMotorImpulse);
lambda = this.motorLambda_acc - motorLambdaOld;
// Apply motor impulses
body1.w -= lambda * body1.i_inv;
body2.w += lambda * body2.i_inv;
}
// Compute lambda for velocity constraint
// Solve J * invM * JT * lambda = -J * V
var cdot = this.n.dot(vec2.sub(body2.v, body1.v)) + this.sn2 * body2.w - this.sn1 * body1.w;
var lambda = -this.em * cdot;
// Accumulate lambda
this.lambda_acc += lambda;
// linearImpulse = JT * lambda
var impulse = vec2.scale(this.n, lambda);
// Apply constraint impulses
// V += JT * lambda * invM
body1.v.mad(impulse, -body1.m_inv);
body1.w -= this.sn1 * lambda * body1.i_inv;
body2.v.mad(impulse, body2.m_inv);
body2.w += this.sn2 * lambda * body2.i_inv;
}
WheelJoint.prototype.solvePositionConstraints = function() {
var body1 = this.body1;
var body2 = this.body2;
// Transformed r1, r2
var r1 = vec2.rotate(vec2.sub(this.anchor1, body1.centroid), body1.a);
var r2 = vec2.rotate(vec2.sub(this.anchor2, body2.centroid), body2.a);
// World anchor points
var p1 = vec2.add(body1.p, r1);
var p2 = vec2.add(body2.p, r2);
// Delta vector between world anchor points
var d = vec2.sub(p2, p1);
// r1 + d
var r1_d = vec2.add(r1, d);
// World line normal
var n = vec2.rotate(this.n_local, body1.a);
// Position constraint
var c = vec2.dot(n, d);
var correction = Math.clamp(c, -Joint.MAX_LINEAR_CORRECTION, Joint.MAX_LINEAR_CORRECTION);
// Compute lambda for position constraint
// Solve J * invM * JT * lambda = -C / dt
var s1 = vec2.cross(r1_d, n);
var s2 = vec2.cross(r2, n);
var em_inv = body1.m_inv + body2.m_inv + body1.i_inv * s1 * s1 + body2.i_inv * s2 * s2;
var k_inv = em_inv == 0 ? 0 : 1 / em_inv;
var lambda_dt = k_inv * (-correction);
// Apply constraint impulses
// impulse = JT * lambda
// X += impulse * invM * dt
var impulse_dt = vec2.scale(n, lambda_dt);
body1.p.mad(impulse_dt, -body1.m_inv);
body1.a -= s1 * lambda_dt * body1.i_inv;
body2.p.mad(impulse_dt, body2.m_inv);
body2.a += s2 * lambda_dt * body2.i_inv;
return Math.abs(c) < Joint.LINEAR_SLOP;
}
WheelJoint.prototype.getReactionForce = function(dt_inv) {
return vec2.scale(this.n, this.lambda_acc * dt_inv);
}
WheelJoint.prototype.getReactionTorque = function(dt_inv) {
return 0;
}

View file

@ -0,0 +1,81 @@
module.exports = ContactMaterial;
var idCounter = 0;
/**
* Defines a physics material.
* @class ContactMaterial
* @constructor
* @param {Material} materialA
* @param {Material} materialB
* @param {Object} [options]
* @param {Number} options.friction
* @param {Number} options.restitution
* @author schteppe
*/
function ContactMaterial(materialA, materialB, options){
options = options || {};
/**
* The contact material identifier
* @property id
* @type {Number}
*/
this.id = idCounter++;
/**
* First material participating in the contact material
* @property materialA
* @type {Material}
*/
this.materialA = materialA;
/**
* Second material participating in the contact material
* @property materialB
* @type {Material}
*/
this.materialB = materialB;
/**
* Friction to use in the contact of these two materials
* @property friction
* @type {Number}
*/
this.friction = typeof(options.friction) !== "undefined" ? Number(options.friction) : 0.3;
/**
* Restitution to use in the contact of these two materials
* @property restitution
* @type {Number}
*/
this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.3;
/**
* Stiffness of the resulting ContactEquation that this ContactMaterial generate
* @property stiffness
* @type {Number}
*/
this.stiffness = typeof(options.stiffness) !== "undefined" ? Number(options.stiffness) : 1e7;
/**
* Relaxation of the resulting ContactEquation that this ContactMaterial generate
* @property relaxation
* @type {Number}
*/
this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : 3;
/**
* Stiffness of the resulting FrictionEquation that this ContactMaterial generate
* @property frictionStiffness
* @type {Number}
*/
this.frictionStiffness = typeof(options.frictionStiffness) !== "undefined" ? Number(options.frictionStiffness) : 1e7;
/**
* Relaxation of the resulting FrictionEquation that this ContactMaterial generate
* @property frictionRelaxation
* @type {Number}
*/
this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : 3;
};

View file

@ -0,0 +1,19 @@
module.exports = Material;
var idCounter = 0;
/**
* Defines a physics material.
* @class Material
* @constructor
* @param string name
* @author schteppe
*/
function Material(){
/**
* The material identifier
* @property id
* @type {Number}
*/
this.id = idCounter++;
};

View file

@ -0,0 +1,10 @@
/**
* The mat2 object from glMatrix, extended with the functions documented here. See http://glmatrix.net for full doc.
* @class mat2
*/
// Only import mat2 from gl-matrix and skip the rest
var mat2 = require('../../node_modules/gl-matrix/src/gl-matrix/mat2').mat2;
// Export everything
module.exports = mat2;

View file

@ -0,0 +1,477 @@
/*
PolyK library
url: http://polyk.ivank.net
Released under MIT licence.
Copyright (c) 2012 Ivan Kuckir
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 PolyK = {};
/*
Is Polygon self-intersecting?
O(n^2)
*/
/*
PolyK.IsSimple = function(p)
{
var n = p.length>>1;
if(n<4) return true;
var a1 = new PolyK._P(), a2 = new PolyK._P();
var b1 = new PolyK._P(), b2 = new PolyK._P();
var c = new PolyK._P();
for(var i=0; i<n; i++)
{
a1.x = p[2*i ];
a1.y = p[2*i+1];
if(i==n-1) { a2.x = p[0 ]; a2.y = p[1 ]; }
else { a2.x = p[2*i+2]; a2.y = p[2*i+3]; }
for(var j=0; j<n; j++)
{
if(Math.abs(i-j) < 2) continue;
if(j==n-1 && i==0) continue;
if(i==n-1 && j==0) continue;
b1.x = p[2*j ];
b1.y = p[2*j+1];
if(j==n-1) { b2.x = p[0 ]; b2.y = p[1 ]; }
else { b2.x = p[2*j+2]; b2.y = p[2*j+3]; }
if(PolyK._GetLineIntersection(a1,a2,b1,b2,c) != null) return false;
}
}
return true;
}
PolyK.IsConvex = function(p)
{
if(p.length<6) return true;
var l = p.length - 4;
for(var i=0; i<l; i+=2)
if(!PolyK._convex(p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5])) return false;
if(!PolyK._convex(p[l ], p[l+1], p[l+2], p[l+3], p[0], p[1])) return false;
if(!PolyK._convex(p[l+2], p[l+3], p[0 ], p[1 ], p[2], p[3])) return false;
return true;
}
*/
PolyK.GetArea = function(p)
{
if(p.length <6) return 0;
var l = p.length - 2;
var sum = 0;
for(var i=0; i<l; i+=2)
sum += (p[i+2]-p[i]) * (p[i+1]+p[i+3]);
sum += (p[0]-p[l]) * (p[l+1]+p[1]);
return - sum * 0.5;
}
/*
PolyK.GetAABB = function(p)
{
var minx = Infinity;
var miny = Infinity;
var maxx = -minx;
var maxy = -miny;
for(var i=0; i<p.length; i+=2)
{
minx = Math.min(minx, p[i ]);
maxx = Math.max(maxx, p[i ]);
miny = Math.min(miny, p[i+1]);
maxy = Math.max(maxy, p[i+1]);
}
return {x:minx, y:miny, width:maxx-minx, height:maxy-miny};
}
*/
PolyK.Triangulate = function(p)
{
var n = p.length>>1;
if(n<3) return [];
var tgs = [];
var avl = [];
for(var i=0; i<n; i++) avl.push(i);
var i = 0;
var al = n;
while(al > 3)
{
var i0 = avl[(i+0)%al];
var i1 = avl[(i+1)%al];
var i2 = avl[(i+2)%al];
var ax = p[2*i0], ay = p[2*i0+1];
var bx = p[2*i1], by = p[2*i1+1];
var cx = p[2*i2], cy = p[2*i2+1];
var earFound = false;
if(PolyK._convex(ax, ay, bx, by, cx, cy))
{
earFound = true;
for(var j=0; j<al; j++)
{
var vi = avl[j];
if(vi==i0 || vi==i1 || vi==i2) continue;
if(PolyK._PointInTriangle(p[2*vi], p[2*vi+1], ax, ay, bx, by, cx, cy)) {earFound = false; break;}
}
}
if(earFound)
{
tgs.push(i0, i1, i2);
avl.splice((i+1)%al, 1);
al--;
i= 0;
}
else if(i++ > 3*al) break; // no convex angles :(
}
tgs.push(avl[0], avl[1], avl[2]);
return tgs;
}
/*
PolyK.ContainsPoint = function(p, px, py)
{
var n = p.length>>1;
var ax, ay, bx = p[2*n-2]-px, by = p[2*n-1]-py;
var depth = 0;
for(var i=0; i<n; i++)
{
ax = bx; ay = by;
bx = p[2*i ] - px;
by = p[2*i+1] - py;
if(ay< 0 && by< 0) continue; // both "up" or both "donw"
if(ay>=0 && by>=0) continue; // both "up" or both "donw"
if(ax< 0 && bx< 0) continue;
var lx = ax + (bx-ax)*(-ay)/(by-ay);
if(lx>0) depth++;
}
return (depth & 1) == 1;
}
PolyK.Slice = function(p, ax, ay, bx, by)
{
if(PolyK.ContainsPoint(p, ax, ay) || PolyK.ContainsPoint(p, bx, by)) return [p.slice(0)];
var a = new PolyK._P(ax, ay);
var b = new PolyK._P(bx, by);
var iscs = []; // intersections
var ps = []; // points
for(var i=0; i<p.length; i+=2) ps.push(new PolyK._P(p[i], p[i+1]));
for(var i=0; i<ps.length; i++)
{
var isc = new PolyK._P(0,0);
isc = PolyK._GetLineIntersection(a, b, ps[i], ps[(i+1)%ps.length], isc);
if(isc)
{
isc.flag = true;
iscs.push(isc);
ps.splice(i+1,0,isc);
i++;
}
}
if(iscs.length == 0) return [p.slice(0)];
var comp = function(u,v) {return PolyK._P.dist(a,u) - PolyK._P.dist(a,v); }
iscs.sort(comp);
var pgs = [];
var dir = 0;
while(iscs.length > 0)
{
var n = ps.length;
var i0 = iscs[0];
var i1 = iscs[1];
var ind0 = ps.indexOf(i0);
var ind1 = ps.indexOf(i1);
var solved = false;
if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true;
else
{
i0 = iscs[1];
i1 = iscs[0];
ind0 = ps.indexOf(i0);
ind1 = ps.indexOf(i1);
if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true;
}
if(solved)
{
dir--;
var pgn = PolyK._getPoints(ps, ind0, ind1);
pgs.push(pgn);
ps = PolyK._getPoints(ps, ind1, ind0);
i0.flag = i1.flag = false;
iscs.splice(0,2);
if(iscs.length == 0) pgs.push(ps);
}
else { dir++; iscs.reverse(); }
if(dir>1) break;
}
var result = [];
for(var i=0; i<pgs.length; i++)
{
var pg = pgs[i];
var npg = [];
for(var j=0; j<pg.length; j++) npg.push(pg[j].x, pg[j].y);
result.push(npg);
}
return result;
}
PolyK.Raycast = function(p, x, y, dx, dy, isc)
{
var l = p.length - 2;
var tp = PolyK._tp;
var a1 = tp[0], a2 = tp[1],
b1 = tp[2], b2 = tp[3], c = tp[4];
a1.x = x; a1.y = y;
a2.x = x+dx; a2.y = y+dy;
if(isc==null) isc = {dist:0, edge:0, norm:{x:0, y:0}, refl:{x:0, y:0}};
isc.dist = Infinity;
for(var i=0; i<l; i+=2)
{
b1.x = p[i ]; b1.y = p[i+1];
b2.x = p[i+2]; b2.y = p[i+3];
var nisc = PolyK._RayLineIntersection(a1, a2, b1, b2, c);
if(nisc) PolyK._updateISC(dx, dy, a1, b1, b2, c, i/2, isc);
}
b1.x = b2.x; b1.y = b2.y;
b2.x = p[0]; b2.y = p[1];
var nisc = PolyK._RayLineIntersection(a1, a2, b1, b2, c);
if(nisc) PolyK._updateISC(dx, dy, a1, b1, b2, c, p.length/2, isc);
return (isc.dist != Infinity) ? isc : null;
}
PolyK.ClosestEdge = function(p, x, y, isc)
{
var l = p.length - 2;
var tp = PolyK._tp;
var a1 = tp[0],
b1 = tp[2], b2 = tp[3], c = tp[4];
a1.x = x; a1.y = y;
if(isc==null) isc = {dist:0, edge:0, point:{x:0, y:0}, norm:{x:0, y:0}};
isc.dist = Infinity;
for(var i=0; i<l; i+=2)
{
b1.x = p[i ]; b1.y = p[i+1];
b2.x = p[i+2]; b2.y = p[i+3];
PolyK._pointLineDist(a1, b1, b2, i>>1, isc);
}
b1.x = b2.x; b1.y = b2.y;
b2.x = p[0]; b2.y = p[1];
PolyK._pointLineDist(a1, b1, b2, l>>1, isc);
var idst = 1/isc.dist;
isc.norm.x = (x-isc.point.x)*idst;
isc.norm.y = (y-isc.point.y)*idst;
return isc;
}
PolyK._pointLineDist = function(p, a, b, edge, isc)
{
var x = p.x, y = p.y, x1 = a.x, y1 = a.y, x2 = b.x, y2 = b.y;
var A = x - x1;
var B = y - y1;
var C = x2 - x1;
var D = y2 - y1;
var dot = A * C + B * D;
var len_sq = C * C + D * D;
var param = dot / len_sq;
var xx, yy;
if (param < 0 || (x1 == x2 && y1 == y2)) {
xx = x1;
yy = y1;
}
else if (param > 1) {
xx = x2;
yy = y2;
}
else {
xx = x1 + param * C;
yy = y1 + param * D;
}
var dx = x - xx;
var dy = y - yy;
var dst = Math.sqrt(dx * dx + dy * dy);
if(dst<isc.dist)
{
isc.dist = dst;
isc.edge = edge;
isc.point.x = xx;
isc.point.y = yy;
}
}
PolyK._updateISC = function(dx, dy, a1, b1, b2, c, edge, isc)
{
var nrl = PolyK._P.dist(a1, c);
if(nrl<isc.dist)
{
var ibl = 1/PolyK._P.dist(b1, b2);
var nx = -(b2.y-b1.y)*ibl;
var ny = (b2.x-b1.x)*ibl;
var ddot = 2*(dx*nx+dy*ny);
isc.dist = nrl;
isc.norm.x = nx;
isc.norm.y = ny;
isc.refl.x = -ddot*nx+dx;
isc.refl.y = -ddot*ny+dy;
isc.edge = edge;
}
}
PolyK._getPoints = function(ps, ind0, ind1)
{
var n = ps.length;
var nps = [];
if(ind1<ind0) ind1 += n;
for(var i=ind0; i<= ind1; i++) nps.push(ps[i%n]);
return nps;
}
PolyK._firstWithFlag = function(ps, ind)
{
var n = ps.length;
while(true)
{
ind = (ind+1)%n;
if(ps[ind].flag) return ind;
}
}
*/
PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy)
{
var v0x = cx-ax;
var v0y = cy-ay;
var v1x = bx-ax;
var v1y = by-ay;
var v2x = px-ax;
var v2y = py-ay;
var dot00 = v0x*v0x+v0y*v0y;
var dot01 = v0x*v1x+v0y*v1y;
var dot02 = v0x*v2x+v0y*v2y;
var dot11 = v1x*v1x+v1y*v1y;
var dot12 = v1x*v2x+v1y*v2y;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// Check if point is in triangle
return (u >= 0) && (v >= 0) && (u + v < 1);
}
/*
PolyK._RayLineIntersection = function(a1, a2, b1, b2, c)
{
var dax = (a1.x-a2.x), dbx = (b1.x-b2.x);
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
var Den = dax*dby - day*dbx;
if (Den == 0) return null; // parallel
var A = (a1.x * a2.y - a1.y * a2.x);
var B = (b1.x * b2.y - b1.y * b2.x);
var I = c;
var iDen = 1/Den;
I.x = ( A*dbx - dax*B ) * iDen;
I.y = ( A*dby - day*B ) * iDen;
if(!PolyK._InRect(I, b1, b2)) return null;
if((day>0 && I.y>a1.y) || (day<0 && I.y<a1.y)) return null;
if((dax>0 && I.x>a1.x) || (dax<0 && I.x<a1.x)) return null;
return I;
}
PolyK._GetLineIntersection = function(a1, a2, b1, b2, c)
{
var dax = (a1.x-a2.x), dbx = (b1.x-b2.x);
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
var Den = dax*dby - day*dbx;
if (Den == 0) return null; // parallel
var A = (a1.x * a2.y - a1.y * a2.x);
var B = (b1.x * b2.y - b1.y * b2.x);
var I = c;
I.x = ( A*dbx - dax*B ) / Den;
I.y = ( A*dby - day*B ) / Den;
if(PolyK._InRect(I, a1, a2) && PolyK._InRect(I, b1, b2)) return I;
return null;
}
PolyK._InRect = function(a, b, c)
{
if (b.x == c.x) return (a.y>=Math.min(b.y, c.y) && a.y<=Math.max(b.y, c.y));
if (b.y == c.y) return (a.x>=Math.min(b.x, c.x) && a.x<=Math.max(b.x, c.x));
if(a.x >= Math.min(b.x, c.x) && a.x <= Math.max(b.x, c.x)
&& a.y >= Math.min(b.y, c.y) && a.y <= Math.max(b.y, c.y))
return true;
return false;
}
*/
PolyK._convex = function(ax, ay, bx, by, cx, cy)
{
return (ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0;
}
/*
PolyK._P = function(x,y)
{
this.x = x;
this.y = y;
this.flag = false;
}
PolyK._P.prototype.toString = function()
{
return "Point ["+this.x+", "+this.y+"]";
}
PolyK._P.dist = function(a,b)
{
var dx = b.x-a.x;
var dy = b.y-a.y;
return Math.sqrt(dx*dx + dy*dy);
}
PolyK._tp = [];
for(var i=0; i<10; i++) PolyK._tp.push(new PolyK._P(0,0));
*/
module.exports = PolyK;

View file

@ -0,0 +1,122 @@
/**
* The vec2 object from glMatrix, extended with the functions documented here. See http://glmatrix.net for full doc.
* @class vec2
*/
// Only import vec2 from gl-matrix and skip the rest
var vec2 = require('../../node_modules/gl-matrix/src/gl-matrix/vec2').vec2;
// Now add some extensions
/**
* Get the vector x component
* @method getX
* @static
* @param {Float32Array} a
* @return {Number}
*/
vec2.getX = function(a){
return a[0];
};
/**
* Get the vector y component
* @method getY
* @static
* @param {Float32Array} a
* @return {Number}
*/
vec2.getY = function(a){
return a[1];
};
/**
* Make a cross product and only return the z component
* @method crossLength
* @static
* @param {Float32Array} a
* @param {Float32Array} b
* @return {Number}
*/
vec2.crossLength = function(a,b){
return a[0] * b[1] - a[1] * b[0];
};
/**
* Cross product between a vector and the Z component of a vector
* @method crossVZ
* @static
* @param {Float32Array} out
* @param {Float32Array} vec
* @param {Number} zcomp
* @return {Number}
*/
vec2.crossVZ = function(out, vec, zcomp){
vec2.rotate(out,vec,-Math.PI/2);// Rotate according to the right hand rule
vec2.scale(out,out,zcomp); // Scale with z
return out;
};
/**
* Cross product between a vector and the Z component of a vector
* @method crossZV
* @static
* @param {Float32Array} out
* @param {Number} zcomp
* @param {Float32Array} vec
* @return {Number}
*/
vec2.crossZV = function(out, zcomp, vec){
vec2.rotate(out,vec,Math.PI/2); // Rotate according to the right hand rule
vec2.scale(out,out,zcomp); // Scale with z
return out;
};
/**
* Rotate a vector by an angle
* @method rotate
* @static
* @param {Float32Array} out
* @param {Float32Array} a
* @param {Number} angle
*/
vec2.rotate = function(out,a,angle){
var c = Math.cos(angle),
s = Math.sin(angle),
x = a[0],
y = a[1];
out[0] = c*x -s*y;
out[1] = s*x +c*y;
};
vec2.toLocalFrame = function(out, worldPoint, framePosition, frameAngle){
vec2.copy(out, worldPoint);
vec2.sub(out, out, framePosition);
vec2.rotate(out, out, -frameAngle);
};
vec2.toGlobalFrame = function(out, localPoint, framePosition, frameAngle){
vec2.copy(out, localPoint);
vec2.rotate(out, out, frameAngle);
vec2.add(out, out, framePosition);
};
/**
* Compute centroid of a triangle spanned by vectors a,b,c. See http://easycalculation.com/analytical/learn-centroid.php
* @method centroid
* @static
* @param {Float32Array} out
* @param {Float32Array} a
* @param {Float32Array} b
* @param {Float32Array} c
* @return {Float32Array} The out object
*/
vec2.centroid = function(out, a, b, c){
vec2.add(out, a, b);
vec2.add(out, out, c);
vec2.scale(out, out, 1/3);
return out;
};
// Export everything
module.exports = vec2;

View file

@ -0,0 +1,330 @@
var vec2 = require('../math/vec2');
module.exports = Body;
var zero = vec2.fromValues(0,0);
/**
* A rigid body. Has got a center of mass, position, velocity and a number of
* shapes that are used for collisions.
*
* @class Body
* @constructor
* @param {Object} [options]
* @param {Number} [options.mass=0] A number >= 0. If zero, the .motionState will be set to Body.STATIC.
* @param {Float32Array|Array} [options.position]
* @param {Float32Array|Array} [options.velocity]
* @param {Number} [options.angle=0]
* @param {Number} [options.angularVelocity=0]
* @param {Float32Array|Array} [options.force]
* @param {Number} [options.angularForce=0]
*
* @todo Should not take mass as argument to Body, but as density to each Shape
*/
function Body(options){
options = options || {};
/**
* The body identifyer
* @property id
* @type {Number}
*/
this.id = ++Body._idCounter;
/**
* The shapes of the body. The local transform of the shape in .shapes[i] is
* defined by .shapeOffsets[i] and .shapeAngles[i].
*
* @property shapes
* @type {Array}
*/
this.shapes = [];
/**
* The local shape offsets, relative to the body center of mass. This is an
* array of Float32Array.
* @property shapeOffsets
* @type {Array}
*/
this.shapeOffsets = [];
/**
* The body-local shape angle transforms. This is an array of numbers (angles).
* @property shapeAngles
* @type {Array}
*/
this.shapeAngles = [];
/**
* The mass of the body.
* @property mass
* @type {number}
*/
this.mass = options.mass || 0;
/**
* The inverse mass of the body.
* @property invMass
* @type {number}
*/
this.invMass = 0;
/**
* The inertia of the body around the Z axis.
* @property inertia
* @type {number}
*/
this.inertia = 0;
/**
* The inverse inertia of the body.
* @property invInertia
* @type {number}
*/
this.invInertia = 0;
this.updateMassProperties();
/**
* The position of the body
* @property position
* @type {Float32Array}
*/
this.position = vec2.fromValues(0,0);
if(options.position) vec2.copy(this.position, options.position);
/**
* The velocity of the body
* @property velocity
* @type {Float32Array}
*/
this.velocity = vec2.fromValues(0,0);
if(options.velocity) vec2.copy(this.velocity, options.velocity);
/**
* Constraint velocity that was added to the body during the last step.
* @property vlambda
* @type {Float32Array}
*/
this.vlambda = vec2.fromValues(0,0);
/**
* Angular constraint velocity that was added to the body during last step.
* @property wlambda
* @type {Float32Array}
*/
this.wlambda = 0;
/**
* The angle of the body
* @property angle
* @type {number}
*/
this.angle = options.angle || 0;
/**
* The angular velocity of the body
* @property angularVelocity
* @type {number}
*/
this.angularVelocity = options.angularVelocity || 0;
/**
* The force acting on the body
* @property force
* @type {Float32Array}
*/
this.force = vec2.create();
if(options.force) vec2.copy(this.force, options.force);
/**
* The angular force acting on the body
* @property angularForce
* @type {number}
*/
this.angularForce = options.angularForce || 0;
/**
* The type of motion this body has. Should be one of: Body.STATIC (the body
* does not move), Body.DYNAMIC (body can move and respond to collisions)
* and Body.KINEMATIC (only moves according to its .velocity).
*
* @property motionState
* @type {number}
*
* @example
* // This body will move and interact with other bodies
* var dynamicBody = new Body();
* dynamicBody.motionState = Body.DYNAMIC;
*
* @example
* // This body will not move at all
* var staticBody = new Body();
* staticBody.motionState = Body.STATIC;
*
* @example
* // This body will only move if you change its velocity
* var kinematicBody = new Body();
* kinematicBody.motionState = Body.KINEMATIC;
*/
this.motionState = this.mass == 0 ? Body.STATIC : Body.DYNAMIC;
/**
* Bounding circle radius
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
};
Body._idCounter = 0;
/**
* Update the bounding radius of the body. Should be done if any of the shapes
* are changed.
* @method updateBoundingRadius
*/
Body.prototype.updateBoundingRadius = function(){
var shapes = this.shapes,
shapeOffsets = this.shapeOffsets,
N = shapes.length,
radius = 0;
for(var i=0; i!==N; i++){
var shape = shapes[i],
offset = vec2.length(shapeOffsets[i] || zero),
r = shape.boundingRadius;
if(offset + r > radius)
radius = offset + r;
}
this.boundingRadius = radius;
};
/**
* Add a shape to the body. You can pass a local transform when adding a shape,
* so that the shape gets an offset and angle relative to the body center of mass.
* Will automatically update the mass properties and bounding radius.
*
* @method addShape
* @param {Shape} shape
* @param {Float32Array|Array} [offset] Local body offset of the shape.
* @param {Number} [angle] Local body angle.
*
* @example
* var body = new Body(),
* shape = new Circle();
*
* // Add the shape to the body, positioned in the center
* body.addShape(shape);
*
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local x-axis.
* body.addShape(shape,[1,0]);
*
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local y-axis, and rotated 90 degrees CCW.
* body.addShape(shape,[0,1],Math.PI/2);
*/
Body.prototype.addShape = function(shape,offset,angle){
this.shapes .push(shape);
this.shapeOffsets.push(offset);
this.shapeAngles .push(angle);
this.updateMassProperties();
this.updateBoundingRadius();
};
/**
* Updates .inertia, .invMass, .invInertia for this Body. Should be called when
* changing the structure or mass of the Body.
*
* @method updateMassProperties
*
* @example
* body.mass += 1;
* body.updateMassProperties();
*/
Body.prototype.updateMassProperties = function(){
var shapes = this.shapes,
N = shapes.length,
m = this.mass / N,
I = 0;
for(var i=0; i<N; i++){
var shape = shapes[i],
r2 = vec2.squaredLength(this.shapeOffsets[i] || zero),
Icm = shape.computeMomentOfInertia(m);
I += Icm + m*r2;
}
this.inertia = I;
// Inverse mass properties are easy
this.invMass = this.mass > 0 ? 1/this.mass : 0;
this.invInertia = I>0 ? 1/I : 0;
};
var Body_applyForce_r = vec2.create();
/**
* Apply force to a world point. This could for example be a point on the RigidBody surface. Applying force this way will add to Body.force and Body.angularForce.
* @method applyForce
* @param {Float32Array} force The force to add.
* @param {Float32Array} worldPoint A world point to apply the force on.
*/
Body.prototype.applyForce = function(force,worldPoint){
// Compute point position relative to the body center
var r = Body_applyForce_r;
vec2.sub(r,worldPoint,this.position);
// Add linear force
vec2.add(this.force,this.force,force);
// Compute produced rotational force
var rotForce = vec2.crossLength(r,force);
// Add rotational force
this.angularForce += rotForce;
};
/**
* Transform a world point to local body frame.
* @method toLocalFrame
* @param {Float32Array|Array} out The vector to store the result in
* @param {Float32Array|Array} worldPoint The input world vector
*/
Body.prototype.toLocalFrame = function(out, worldPoint){
vec2.toLocalFrame(out, worldPoint, this.position, this.angle);
};
/**
* Transform a local point to world frame.
* @method toWorldFrame
* @param {Array} out The vector to store the result in
* @param {Array} localPoint The input local vector
*/
Body.prototype.toWorldFrame = function(out, localPoint){
vec2.toGlobalFrame(out, localPoint, this.position, this.angle);
};
/**
* Dynamic body.
* @property DYNAMIC
* @type {Number}
* @static
*/
Body.DYNAMIC = 1;
/**
* Static body.
* @property STATIC
* @type {Number}
* @static
*/
Body.STATIC = 2;
/**
* Kinematic body.
* @property KINEMATIC
* @type {Number}
* @static
*/
Body.KINEMATIC = 4;

View file

@ -0,0 +1,181 @@
var vec2 = require('../math/vec2');
module.exports = Spring;
/**
* A spring, connecting two bodies.
*
* @class Spring
* @constructor
* @param {Body} bodyA
* @param {Body} bodyB
* @param {Object} [options]
* @param {number} options.restLength A number > 0. Default: 1
* @param {number} options.stiffness A number >= 0. Default: 100
* @param {number} options.damping A number >= 0. Default: 1
* @param {Array} options.worldAnchorA Where to hook the spring to body A, in world coordinates.
* @param {Array} options.worldAnchorB
* @param {Array} options.localAnchorA Where to hook the spring to body A, in local body coordinates.
* @param {Array} options.localAnchorB
*/
function Spring(bodyA,bodyB,options){
options = options || {};
/**
* Rest length of the spring.
* @property restLength
* @type {number}
*/
this.restLength = typeof(options.restLength)=="number" ? options.restLength : 1;
/**
* Stiffness of the spring.
* @property stiffness
* @type {number}
*/
this.stiffness = options.stiffness || 100;
/**
* Damping of the spring.
* @property damping
* @type {number}
*/
this.damping = options.damping || 1;
/**
* First connected body.
* @property bodyA
* @type {Body}
*/
this.bodyA = bodyA;
/**
* Second connected body.
* @property bodyB
* @type {Body}
*/
this.bodyB = bodyB;
/**
* Anchor for bodyA in local bodyA coordinates.
* @property localAnchorA
* @type {Array}
*/
this.localAnchorA = vec2.fromValues(0,0);
/**
* Anchor for bodyB in local bodyB coordinates.
* @property localAnchorB
* @type {Array}
*/
this.localAnchorB = vec2.fromValues(0,0);
if(options.localAnchorA) vec2.copy(this.localAnchorA, options.localAnchorA);
if(options.localAnchorB) vec2.copy(this.localAnchorB, options.localAnchorB);
if(options.worldAnchorA) this.setWorldAnchorA(options.worldAnchorA);
if(options.worldAnchorB) this.setWorldAnchorB(options.worldAnchorB);
};
/**
* Set the anchor point on body A, using world coordinates.
* @method setWorldAnchorA
* @param {Array} worldAnchorA
*/
Spring.prototype.setWorldAnchorA = function(worldAnchorA){
this.bodyA.toLocalFrame(this.localAnchorA, worldAnchorA);
};
/**
* Set the anchor point on body B, using world coordinates.
* @method setWorldAnchorB
* @param {Array} worldAnchorB
*/
Spring.prototype.setWorldAnchorB = function(worldAnchorB){
this.bodyB.toLocalFrame(this.localAnchorB, worldAnchorB);
};
/**
* Get the anchor point on body A, in world coordinates.
* @method getWorldAnchorA
* @param {Array} result The vector to store the result in.
*/
Spring.prototype.getWorldAnchorA = function(result){
this.bodyA.toWorldFrame(result, this.localAnchorA);
};
/**
* Get the anchor point on body B, in world coordinates.
* @method getWorldAnchorB
* @param {Array} result The vector to store the result in.
*/
Spring.prototype.getWorldAnchorB = function(result){
this.bodyB.toWorldFrame(result, this.localAnchorB);
};
var applyForce_r = vec2.create(),
applyForce_r_unit = vec2.create(),
applyForce_u = vec2.create(),
applyForce_f = vec2.create(),
applyForce_worldAnchorA = vec2.create(),
applyForce_worldAnchorB = vec2.create(),
applyForce_ri = vec2.create(),
applyForce_rj = vec2.create(),
applyForce_tmp = vec2.create();
/**
* Apply the spring force to the connected bodies.
* @method applyForce
*/
Spring.prototype.applyForce = function(){
var k = this.stiffness,
d = this.damping,
l = this.restLength,
bodyA = this.bodyA,
bodyB = this.bodyB,
r = applyForce_r,
r_unit = applyForce_r_unit,
u = applyForce_u,
f = applyForce_f,
tmp = applyForce_tmp;
var worldAnchorA = applyForce_worldAnchorA,
worldAnchorB = applyForce_worldAnchorB,
ri = applyForce_ri,
rj = applyForce_rj;
// Get world anchors
this.getWorldAnchorA(worldAnchorA);
this.getWorldAnchorB(worldAnchorB);
// Get offset points
vec2.sub(ri, worldAnchorA, bodyA.position);
vec2.sub(rj, worldAnchorB, bodyB.position);
// Compute distance vector between world anchor points
vec2.sub(r, worldAnchorB, worldAnchorA);
var rlen = vec2.len(r);
vec2.normalize(r_unit,r);
//console.log(rlen)
//console.log("A",vec2.str(worldAnchorA),"B",vec2.str(worldAnchorB))
// Compute relative velocity of the anchor points, u
vec2.sub(u, bodyB.velocity, bodyA.velocity);
vec2.crossZV(tmp, bodyB.angularVelocity, rj);
vec2.add(u, u, tmp);
vec2.crossZV(tmp, bodyA.angularVelocity, ri);
vec2.sub(u, u, tmp);
// F = - k * ( x - L ) - D * ( u )
vec2.scale(f, r_unit, -k*(rlen-l) - d*vec2.dot(u,r_unit));
// Add forces to bodies
vec2.sub( bodyA.force, bodyA.force, f);
vec2.add( bodyB.force, bodyB.force, f);
// Angular force
var ri_x_f = vec2.crossLength(ri, f);
var rj_x_f = vec2.crossLength(rj, f);
bodyA.angularForce -= ri_x_f;
bodyB.angularForce += rj_x_f;
};

View file

@ -0,0 +1,37 @@
// Export p2 classes
module.exports = {
Body : require('./objects/Body'),
Broadphase : require('./collision/Broadphase'),
Capsule : require('./shapes/Capsule'),
Circle : require('./shapes/Circle'),
Constraint : require('./constraints/Constraint'),
ContactEquation : require('./constraints/ContactEquation'),
ContactMaterial : require('./material/ContactMaterial'),
Convex : require('./shapes/Convex'),
DistanceConstraint : require('./constraints/DistanceConstraint'),
Equation : require('./constraints/Equation'),
EventEmitter : require('./events/EventEmitter'),
FrictionEquation : require('./constraints/FrictionEquation'),
GridBroadphase : require('./collision/GridBroadphase'),
GSSolver : require('./solver/GSSolver'),
Island : require('./solver/IslandSolver'),
IslandSolver : require('./solver/IslandSolver'),
Line : require('./shapes/Line'),
Material : require('./material/Material'),
NaiveBroadphase : require('./collision/NaiveBroadphase'),
Particle : require('./shapes/Particle'),
Plane : require('./shapes/Plane'),
PointToPointConstraint : require('./constraints/PointToPointConstraint'),
PrismaticConstraint : require('./constraints/PrismaticConstraint'),
Rectangle : require('./shapes/Rectangle'),
RotationalVelocityEquation : require('./constraints/RotationalVelocityEquation'),
SAP1DBroadphase : require('./collision/SAP1DBroadphase'),
Shape : require('./shapes/Shape'),
Solver : require('./solver/Solver'),
Spring : require('./objects/Spring'),
Utils : require('./utils/Utils'),
World : require('./world/World'),
QuadTree : require('./collision/QuadTree').QuadTree,
vec2 : require('./math/vec2'),
version : require('../package.json').version,
};

View file

@ -1,19 +0,0 @@
//--------------------------------
// Box
//--------------------------------
ShapeBox = function(local_x, local_y, w, h) {
local_x = local_x || 0;
local_y = local_y || 0;
var hw = w * 0.5;
var hh = h * 0.5;
var verts = [
new vec2(-hw + local_x, +hh + local_y),
new vec2(-hw + local_x, -hh + local_y),
new vec2(+hw + local_x, -hh + local_y),
new vec2(+hw + local_x, +hh + local_y)
];
return new ShapePoly(verts);
}

View file

@ -0,0 +1,39 @@
var Shape = require('./Shape')
, vec2 = require('../math/vec2')
module.exports = Capsule;
/**
* Capsule shape class.
* @class Capsule
* @constructor
* @extends {Shape}
* @param {Number} length The distance between the end points
* @param {Number} radius Radius of the capsule
*/
function Capsule(length,radius){
this.length = length || 1;
this.radius = radius || 1;
Shape.call(this,Shape.CAPSULE);
};
Capsule.prototype = new Shape();
/**
* Compute the mass moment of inertia of the Capsule.
* @method conputeMomentOfInertia
* @param {Number} mass
* @return {Number}
* @todo
*/
Capsule.prototype.computeMomentOfInertia = function(mass){
// Approximate with rectangle
var r = this.radius,
w = this.length + r, // 2*r is too much, 0 is too little
h = r*2;
return mass * (h*h + w*w) / 12;
};
Capsule.prototype.updateBoundingRadius = function(){
this.boundingRadius = this.radius + this.length/2;
};

View file

@ -1,102 +1,31 @@
/*
* 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 Shape = require('./Shape');
//------------------------------------------
// ShapeCircle
//------------------------------------------
module.exports = Circle;
ShapeCircle = function(local_x, local_y, radius) {
Shape.call(this, Shape.TYPE_CIRCLE);
this.c = new vec2(local_x || 0, local_y || 0);
this.r = radius;
/**
* Circle shape class.
* @class Circle
* @extends {Shape}
* @constructor
* @param {number} radius
*/
function Circle(radius){
this.tc = vec2.zero;
/**
* The radius of the circle.
* @property radius
* @type {number}
*/
this.radius = radius || 1;
this.finishVerts();
}
Shape.call(this,Shape.CIRCLE);
};
Circle.prototype = new Shape();
Circle.prototype.computeMomentOfInertia = function(mass){
var r = this.radius;
return mass * r * r / 2;
};
ShapeCircle.prototype = new Shape;
ShapeCircle.prototype.constructor = ShapeCircle;
ShapeCircle.prototype.finishVerts = function() {
this.r = Math.abs(this.r);
}
ShapeCircle.prototype.duplicate = function() {
return new ShapeCircle(this.c.x, this.c.y, this.r);
}
ShapeCircle.prototype.serialize = function() {
return {
"type": "ShapeCircle",
"e": this.e,
"u": this.u,
"density": this.density,
"center": this.c,
"radius": this.r
};
}
ShapeCircle.prototype.recenter = function(c) {
this.c.subself(c);
}
ShapeCircle.prototype.transform = function(xf) {
this.c = xf.transform(this.c);
}
ShapeCircle.prototype.untransform = function(xf) {
this.c = xf.untransform(this.c);
}
ShapeCircle.prototype.area = function() {
return areaForCircle(this.r, 0);
}
ShapeCircle.prototype.centroid = function() {
return this.c.duplicate();
}
ShapeCircle.prototype.inertia = function(mass) {
return inertiaForCircle(mass, this.c, this.r, 0);
}
ShapeCircle.prototype.cacheData = function(xf) {
this.tc = xf.transform(this.c);
this.bounds.mins.set(this.tc.x - this.r, this.tc.y - this.r);
this.bounds.maxs.set(this.tc.x + this.r, this.tc.y + this.r);
}
ShapeCircle.prototype.pointQuery = function(p) {
return vec2.distsq(this.tc, p) < (this.r * this.r);
}
ShapeCircle.prototype.findVertexByPoint = function(p, minDist) {
var dsq = minDist * minDist;
if (vec2.distsq(this.tc, p) < dsq) {
return 0;
}
return -1;
}
ShapeCircle.prototype.distanceOnPlane = function(n, d) {
return vec2.dot(n, this.tc) - this.r - d;
}
Circle.prototype.updateBoundingRadius = function(){
this.boundingRadius = this.radius;
};

View file

@ -0,0 +1,213 @@
var Shape = require('./Shape')
, vec2 = require('../math/vec2')
, polyk = require('../math/polyk')
module.exports = Convex;
/**
* Convex shape class.
* @class Convex
* @constructor
* @extends {Shape}
* @param {Array} vertices An array of Float32Array vertices that span this shape. Vertices are given in counter-clockwise (CCW) direction.
*/
function Convex(vertices){
/**
* Vertices defined in the local frame.
* @property vertices
* @type {Array}
*/
this.vertices = vertices || [];
/**
* The center of mass of the Convex
* @property centerOfMass
* @type {Float32Array}
*/
this.centerOfMass = vec2.fromValues(0,0);
/**
* Triangulated version of this convex. The structure is Array of 3-Arrays, and each subarray contains 3 integers, referencing the vertices.
* @property triangles
* @type {Array}
*/
this.triangles = [];
if(this.vertices.length){
this.updateTriangles();
this.updateCenterOfMass();
}
/**
* The bounding radius of the convex
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
this.updateBoundingRadius();
Shape.call(this,Shape.CONVEX);
};
Convex.prototype = new Shape();
Convex.prototype.updateTriangles = function(){
this.triangles.length = 0;
// Rewrite on polyk notation, array of numbers
var polykVerts = [];
for(var i=0; i<this.vertices.length; i++){
var v = this.vertices[i];
polykVerts.push(v[0],v[1]);
}
// Triangulate
var triangles = polyk.Triangulate(polykVerts);
// Loop over all triangles, add their inertia contributions to I
for(var i=0; i<triangles.length; i+=3){
var id1 = triangles[i],
id2 = triangles[i+1],
id3 = triangles[i+2];
// Add to triangles
this.triangles.push([id1,id2,id3]);
}
};
var updateCenterOfMass_centroid = vec2.create(),
updateCenterOfMass_centroid_times_mass = vec2.create(),
updateCenterOfMass_a = vec2.create(),
updateCenterOfMass_b = vec2.create(),
updateCenterOfMass_c = vec2.create(),
updateCenterOfMass_ac = vec2.create(),
updateCenterOfMass_ca = vec2.create(),
updateCenterOfMass_cb = vec2.create(),
updateCenterOfMass_n = vec2.create();
Convex.prototype.updateCenterOfMass = function(){
var triangles = this.triangles,
verts = this.vertices,
cm = this.centerOfMass,
centroid = updateCenterOfMass_centroid,
n = updateCenterOfMass_n,
a = updateCenterOfMass_a,
b = updateCenterOfMass_b,
c = updateCenterOfMass_c,
ac = updateCenterOfMass_ac,
ca = updateCenterOfMass_ca,
cb = updateCenterOfMass_cb,
centroid_times_mass = updateCenterOfMass_centroid_times_mass;
vec2.set(cm,0,0);
for(var i=0; i<triangles.length; i++){
var t = triangles[i],
a = verts[t[0]],
b = verts[t[1]],
c = verts[t[2]];
vec2.centroid(centroid,a,b,c);
vec2.sub(ca, c, a);
vec2.sub(cb, c, b);
// Get mass for the triangle (density=1 in this case)
// http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors
var m = 0.5 * vec2.crossLength(ca,cb);
// Add to center of mass
vec2.scale(centroid_times_mass, centroid, m);
vec2.add(cm, cm, centroid_times_mass);
}
};
/**
* Compute the mass moment of inertia of the Convex.
* @method conputeMomentOfInertia
* @param {Number} mass
* @return {Number}
* @todo should use .triangles
*/
Convex.prototype.computeMomentOfInertia = function(mass){
// In short: Triangulate the Convex, compute centroid and inertia of
// each sub-triangle. Add up to total using parallel axis theorem.
var I = 0;
// Rewrite on polyk notation, array of numbers
var polykVerts = [];
for(var i=0; i<this.vertices.length; i++){
var v = this.vertices[i];
polykVerts.push(v[0],v[1]);
}
// Triangulate
var triangles = polyk.Triangulate(polykVerts);
// Get total convex area and density
var area = polyk.GetArea(polykVerts);
var density = mass / area;
// Temp vectors
var a = vec2.create(),
b = vec2.create(),
c = vec2.create(),
centroid = vec2.create(),
n = vec2.create(),
ac = vec2.create(),
ca = vec2.create(),
cb = vec2.create(),
centroid_times_mass = vec2.create();
// Loop over all triangles, add their inertia contributions to I
for(var i=0; i<triangles.length; i+=3){
var id1 = triangles[i],
id2 = triangles[i+1],
id3 = triangles[i+2];
// a,b,c are triangle corners
vec2.set(a, polykVerts[2*id1], polykVerts[2*id1+1]);
vec2.set(b, polykVerts[2*id2], polykVerts[2*id2+1]);
vec2.set(c, polykVerts[2*id3], polykVerts[2*id3+1]);
vec2.centroid(centroid, a, b, c);
vec2.sub(ca, c, a);
vec2.sub(cb, c, b);
var area_triangle = 0.5 * vec2.crossLength(ca,cb);
var base = vec2.length(ca);
var height = 2*area_triangle / base; // a=b*h/2 => h=2*a/b
// Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m
var I_triangle = (base * (Math.pow(height,3))) / 36;
// Get mass for the triangle
var m = base*height/2 * density;
// Add to total inertia using parallel axis theorem
var r2 = vec2.squaredLength(centroid);
I += I_triangle + m*r2;
}
return I;
};
/**
* Updates the .boundingRadius property
* @method updateBoundingRadius
*/
Convex.prototype.updateBoundingRadius = function(){
var verts = this.vertices,
r2 = 0;
for(var i=0; i!==verts.length; i++){
var l2 = vec2.squaredLength(verts[i]);
if(l2 > r2) r2 = l2;
}
this.boundingRadius = Math.sqrt(r2);
};

View file

@ -0,0 +1,30 @@
var Shape = require('./Shape');
module.exports = Line;
/**
* Line shape class. The line shape is along the x direction, and stretches from [-length/2, 0] to [length/2,0].
* @class Line
* @extends {Shape}
* @constructor
*/
function Line(length){
/**
* Length of this line
* @property length
* @type {Number}
*/
this.length = length;
Shape.call(this,Shape.LINE);
};
Line.prototype = new Shape();
Line.prototype.computeMomentOfInertia = function(mass){
return mass * Math.pow(this.length,2) / 12;
};
Line.prototype.updateBoundingRadius = function(){
this.boundingRadius = this.length/2;
};

View file

@ -0,0 +1,22 @@
var Shape = require('./Shape');
module.exports = Particle;
/**
* Particle shape class.
* @class Particle
* @constructor
* @extends {Shape}
*/
function Particle(){
Shape.call(this,Shape.PARTICLE);
};
Particle.prototype = new Shape();
Particle.prototype.computeMomentOfInertia = function(mass){
return 0; // Can't rotate a particle
};
Particle.prototype.updateBoundingRadius = function(){
this.boundingRadius = 0;
};

View file

@ -0,0 +1,22 @@
var Shape = require('./Shape');
module.exports = Plane;
/**
* Plane shape class. The plane is facing in the Y direction.
* @class Plane
* @extends {Shape}
* @constructor
*/
function Plane(){
Shape.call(this,Shape.PLANE);
};
Plane.prototype = new Shape();
Plane.prototype.computeMomentOfInertia = function(mass){
return 0; // Plane is infinite. The inertia should therefore be infinty but by convention we set 0 here
};
Plane.prototype.updateBoundingRadius = function(){
this.boundingRadius = Number.MAX_VALUE;
};

View file

@ -1,254 +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.
*/
//--------------------------------
// ShapePoly (convex only)
//--------------------------------
ShapePoly = function(verts) {
Shape.call(this, Shape.TYPE_POLY);
this.verts = [];
this.planes = [];
this.tverts = [];
this.tplanes = [];
if (verts) {
for (var i = 0; i < verts.length; i++) {
this.verts[i] = verts[i].duplicate();
this.tverts[i] = this.verts[i];
this.tplanes[i] = {};
this.tplanes[i].n = vec2.zero;
this.tplanes[i].d = 0;
}
}
this.finishVerts();
}
ShapePoly.prototype = new Shape;
ShapePoly.prototype.constructor = ShapePoly;
ShapePoly.prototype.finishVerts = function() {
if (this.verts.length < 2) {
this.convexity = false;
this.planes = [];
return;
}
this.convexity = true;
this.tverts = [];
this.tplanes = [];
// Must be counter-clockwise verts
for (var i = 0; i < this.verts.length; i++) {
var a = this.verts[i];
var b = this.verts[(i + 1) % this.verts.length];
var n = vec2.normalize(vec2.perp(vec2.sub(a, b)));
this.planes[i] = {};
this.planes[i].n = n;
this.planes[i].d = vec2.dot(n, a);
this.tverts[i] = this.verts[i];
this.tplanes[i] = {};
this.tplanes[i].n = vec2.zero;
this.tplanes[i].d = 0;
}
for (var i = 0; i < this.verts.length; i++) {
var b = this.verts[(i + 2) % this.verts.length];
var n = this.planes[i].n;
var d = this.planes[i].d;
if (vec2.dot(n, b) - d > 0) {
this.convexity = false;
}
}
}
ShapePoly.prototype.duplicate = function() {
return new ShapePoly(this.verts);
}
ShapePoly.prototype.serialize = function() {
return {
"type": "ShapePoly",
"e": this.e,
"u": this.u,
"density": this.density,
"verts": this.verts
};
}
ShapePoly.prototype.recenter = function(c) {
for (var i = 0; i < this.verts.length; i++) {
this.verts[i].subself(c);
}
}
ShapePoly.prototype.transform = function(xf) {
for (var i = 0; i < this.verts.length; i++) {
this.verts[i] = xf.transform(this.verts[i]);
}
}
ShapePoly.prototype.untransform = function(xf) {
for (var i = 0; i < this.verts.length; i++) {
this.verts[i] = xf.untransform(this.verts[i]);
}
}
ShapePoly.prototype.area = function() {
return areaForPoly(this.verts);
}
ShapePoly.prototype.centroid = function() {
return centroidForPoly(this.verts);
}
ShapePoly.prototype.inertia = function(mass) {
return inertiaForPoly(mass, this.verts, vec2.zero);
}
ShapePoly.prototype.cacheData = function(xf) {
this.bounds.clear();
var numVerts = this.verts.length;
if (numVerts == 0) {
return;
}
for (var i = 0; i < numVerts; i++) {
this.tverts[i] = xf.transform(this.verts[i]);
}
if (numVerts < 2) {
this.bounds.addPoint(this.tverts[0]);
return;
}
for (var i = 0; i < numVerts; i++) {
var a = this.tverts[i];
var b = this.tverts[(i + 1) % numVerts];
var n = vec2.normalize(vec2.perp(vec2.sub(a, b)));
this.tplanes[i].n = n;
this.tplanes[i].d = vec2.dot(n, a);
this.bounds.addPoint(a);
}
}
ShapePoly.prototype.pointQuery = function(p) {
if (!this.bounds.containPoint(p)) {
return false;
}
return this.containPoint(p);
}
ShapePoly.prototype.findVertexByPoint = function(p, minDist) {
var dsq = minDist * minDist;
for (var i = 0; i < this.tverts.length; i++) {
if (vec2.distsq(this.tverts[i], p) < dsq) {
return i;
}
}
return -1;
}
ShapePoly.prototype.findEdgeByPoint = function(p, minDist) {
var dsq = minDist * minDist;
var numVerts = this.tverts.length;
for (var i = 0; i < this.tverts.length; i++) {
var v1 = this.tverts[i];
var v2 = this.tverts[(i + 1) % numVerts];
var n = this.tplanes[i].n;
var dtv1 = vec2.cross(v1, n);
var dtv2 = vec2.cross(v2, n);
var dt = vec2.cross(p, n);
if (dt > dtv1) {
if (vec2.distsq(v1, p) < dsq) {
return i;
}
}
else if (dt < dtv2) {
if (vec2.distsq(v2, p) < dsq) {
return i;
}
}
else {
var dist = vec2.dot(n, p) - vec2.dot(n, v1);
if (dist * dist < dsq) {
return i;
}
}
}
return -1;
}
ShapePoly.prototype.distanceOnPlane = function(n, d) {
var min = 999999;
for (var i = 0; i < this.verts.length; i++) {
min = Math.min(min, vec2.dot(n, this.tverts[i]));
}
return min - d;
}
ShapePoly.prototype.containPoint = function(p) {
for (var i = 0; i < this.verts.length; i++) {
var plane = this.tplanes[i];
if (vec2.dot(plane.n, p) - plane.d > 0) {
return false;
}
}
return true;
}
ShapePoly.prototype.containPointPartial = function(p, n) {
for (var i = 0; i < this.verts.length; i++) {
var plane = this.tplanes[i];
if (vec2.dot(plane.n, n) < 0.0001) {
continue;
}
if (vec2.dot(plane.n, p) - plane.d > 0) {
return false;
}
}
return true;
}

View file

@ -0,0 +1,43 @@
var vec2 = require('../math/vec2')
, Shape = require('./Shape')
, Convex = require('./Convex')
module.exports = Rectangle;
/**
* Rectangle shape class.
* @class Rectangle
* @constructor
* @extends {Convex}
*/
function Rectangle(w,h){
var verts = [ vec2.fromValues(-w/2, -h/2),
vec2.fromValues( w/2, -h/2),
vec2.fromValues( w/2, h/2),
vec2.fromValues(-w/2, h/2)];
this.width = w;
this.height = h;
Convex.call(this,verts);
};
Rectangle.prototype = new Convex();
/**
* Compute moment of inertia
* @method computeMomentOfInertia
* @param {Number} mass
* @return {Number}
*/
Rectangle.prototype.computeMomentOfInertia = function(mass){
var w = this.width,
h = this.height;
return mass * (h*h + w*w) / 12;
};
Rectangle.prototype.updateBoundingRadius = function(){
var w = this.width,
h = this.height;
this.boundingRadius = Math.sqrt(w*w + h*h) / 2;
};

View file

@ -1,170 +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.
*/
//------------------------------------------
// ShapeSegment (thick rounded line segment)
//------------------------------------------
ShapeSegment = function(a, b, radius) {
Shape.call(this, Shape.TYPE_SEGMENT);
this.a = a.duplicate();
this.b = b.duplicate();
this.r = radius;
this.n = vec2.perp(vec2.sub(b, a));
this.n.normalize();
this.ta = vec2.zero;
this.tb = vec2.zero;
this.tn = vec2.zero;
this.finishVerts();
}
ShapeSegment.prototype = new Shape;
ShapeSegment.prototype.constructor = ShapeSegment;
ShapeSegment.prototype.finishVerts = function() {
this.n = vec2.perp(vec2.sub(this.b, this.a));
this.n.normalize();
this.r = Math.abs(this.r);
}
ShapeSegment.prototype.duplicate = function() {
return new ShapeSegment(this.a, this.b, this.r);
}
ShapeSegment.prototype.serialize = function() {
return {
"type": "ShapeSegment",
"e": this.e,
"u": this.u,
"density": this.density,
"a": this.a,
"b": this.b,
"radius": this.r
};
}
ShapeSegment.prototype.recenter = function(c) {
this.a.subself(c);
this.b.subself(c);
}
ShapeSegment.prototype.transform = function(xf) {
this.a = xf.transform(this.a);
this.b = xf.transform(this.b);
}
ShapeSegment.prototype.untransform = function(xf) {
this.a = xf.untransform(this.a);
this.b = xf.untransform(this.b);
}
ShapeSegment.prototype.area = function() {
return areaForSegment(this.a, this.b, this.r);
}
ShapeSegment.prototype.centroid = function() {
return centroidForSegment(this.a, this.b);
}
ShapeSegment.prototype.inertia = function(mass) {
return inertiaForSegment(mass, this.a, this.b);
}
ShapeSegment.prototype.cacheData = function(xf) {
this.ta = xf.transform(this.a);
this.tb = xf.transform(this.b);
this.tn = vec2.perp(vec2.sub(this.tb, this.ta)).normalize();
if (this.ta.x < this.tb.x) {
l = this.ta.x;
r = this.tb.x;
}
else {
l = this.tb.x;
r = this.ta.x;
}
if (this.ta.y < this.tb.y) {
b = this.ta.y;
t = this.tb.y;
} else {
b = this.tb.y;
t = this.ta.y;
}
this.bounds.mins.set(l - this.r, b - this.r);
this.bounds.maxs.set(r + this.r, t + this.r);
}
ShapeSegment.prototype.pointQuery = function(p) {
if (!this.bounds.containPoint(p)) {
return false;
}
var dn = vec2.dot(this.tn, p) - vec2.dot(this.ta, this.tn);
var dist = Math.abs(dn);
if (dist > this.r) {
return false;
}
var dt = vec2.cross(p, this.tn);
var dta = vec2.cross(this.ta, this.tn);
var dtb = vec2.cross(this.tb, this.tn);
if (dt <= dta) {
if (dt < dta - this.r) {
return false;
}
return vec2.distsq(this.ta, p) < (this.r * this.r);
}
else if (dt > dtb) {
if (dt > dtb + this.r) {
return false;
}
return vec2.distsq(this.tb, p) < (this.r * this.r);
}
return true;
}
ShapeSegment.prototype.findVertexByPoint = function(p, minDist) {
var dsq = minDist * minDist;
if (vec2.distsq(this.ta, p) < dsq) {
return 0;
}
if (vec2.distsq(this.tb, p) < dsq) {
return 1;
}
return -1;
}
ShapeSegment.prototype.distanceOnPlane = function(n, d) {
var a = vec2.dot(n, this.ta) - this.r;
var b = vec2.dot(n, this.tb) - this.r;
return Math.min(a, b) - d;
}

View file

@ -0,0 +1,90 @@
module.exports = Shape;
/**
* Base class for shapes.
* @class Shape
* @constructor
*/
function Shape(type){
this.type = type;
/**
* Bounding circle radius of this shape
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
/**
* Collision group that this shape belongs to (bit mask). See <a href="http://www.aurelienribon.com/blog/2011/07/box2d-tutorial-collision-filtering/">this tutorial</a>.
* @property collisionGroup
* @type {Number}
* @example
* // Setup bits for each available group
* var PLAYER = Math.pow(2,0),
* ENEMY = Math.pow(2,1),
* GROUND = Math.pow(2,2)
*
* // Put shapes into their groups
* player1Shape.collisionGroup = PLAYER;
* player2Shape.collisionGroup = PLAYER;
* enemyShape .collisionGroup = ENEMY;
* groundShape .collisionGroup = GROUND;
*
* // Assign groups that each shape collide with.
* // Note that the players can collide with ground and enemies, but not with other players.
* player1Shape.collisionMask = ENEMY | GROUND;
* player2Shape.collisionMask = ENEMY | GROUND;
* enemyShape .collisionMask = PLAYER | GROUND;
* groundShape .collisionMask = PLAYER | ENEMY;
*
* @example
* // How collision check is done
* if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){
* // The shapes will collide
* }
*/
this.collisionGroup = 1;
/**
* Collision mask of this shape. See .collisionGroup.
* @property collisionMask
* @type {Number}
*/
this.collisionMask = 1;
if(type) this.updateBoundingRadius();
/**
* Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead.
* @property material
* @type {Material}
*/
this.material = null;
};
Shape.CIRCLE = 1;
Shape.PARTICLE = 2;
Shape.PLANE = 4;
Shape.CONVEX = 8;
Shape.LINE = 16;
Shape.RECTANGLE = 32;
Shape.CAPSULE = 64;
/**
* Should return the moment of inertia around the Z axis of the body given the total mass. See <a href="http://en.wikipedia.org/wiki/List_of_moments_of_inertia">Wikipedia's list of moments of inertia</a>.
* @method computeMomentOfInertia
* @param {Number} mass
* @return {Number} If the inertia is infinity or if the object simply isn't possible to rotate, return 0.
*/
Shape.prototype.computeMomentOfInertia = function(mass){
throw new Error("Shape.computeMomentOfInertia is not implemented in this Shape...");
};
/**
* Returns the bounding circle radius of this shape.
* @method updateBoundingRadius
* @return {Number}
*/
Shape.prototype.updateBoundingRadius = function(){
throw new Error("Shape.updateBoundingRadius is not implemented in this Shape...");
};

View file

@ -1,12 +0,0 @@
//--------------------------------
// Triangle
//--------------------------------
ShapeTriangle = function(p1, p2, p3) {
var verts = [
new vec2(p1.x, p1.y),
new vec2(p2.x, p2.y),
new vec2(p3.x, p3.y)
];
return new ShapePoly(verts);
}

View file

@ -0,0 +1,190 @@
var vec2 = require('../math/vec2'),
Solver = require('./Solver');
module.exports = GSSolver;
var ARRAY_TYPE = Float32Array || Array;
/**
* Iterative Gauss-Seidel constraint equation solver.
*
* @class GSSolver
* @constructor
* @extends Solver
* @param {Object} [options]
* @param {Number} options.iterations
* @param {Number} options.timeStep
* @param {Number} options.stiffness
* @param {Number} options.relaxation
* @param {Number} options.tolerance
*/
function GSSolver(options){
Solver.call(this);
options = options || {};
this.iterations = options.iterations || 10;
this.tolerance = options.tolerance || 0;
this.debug = options.debug || false;
this.arrayStep = 30;
this.lambda = new ARRAY_TYPE(this.arrayStep);
this.Bs = new ARRAY_TYPE(this.arrayStep);
this.invCs = new ARRAY_TYPE(this.arrayStep);
/**
* Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually.
* @type {Boolean}
* @property useGlobalEquationParameters
*/
this.useGlobalEquationParameters = true;
/**
* Global equation stiffness.
* @property stiffness
* @type {Number}
*/
this.stiffness = 1e6;
/**
* Global equation relaxation.
* @property relaxation
* @type {Number}
*/
this.relaxation = 4;
/**
* Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications.
* @property useZeroRHS
* @type {Boolean}
*/
this.useZeroRHS = false;
};
GSSolver.prototype = new Solver();
/**
* Set stiffness parameters
*
* @method setSpookParams
* @param {number} k
* @param {number} d
* @deprecated
*/
GSSolver.prototype.setSpookParams = function(k,d){
this.stiffness = k;
this.relaxation = d;
};
/**
* Solve the system of equations
* @method solve
* @param {Number} dt Time step
* @param {World} world World to solve
*/
GSSolver.prototype.solve = function(dt,world){
var iter = 0,
maxIter = this.iterations,
tolSquared = this.tolerance*this.tolerance,
equations = this.equations,
Neq = equations.length,
bodies = world.bodies,
Nbodies = world.bodies.length,
h = dt,
d = this.relaxation,
k = this.stiffness,
eps = 4.0 / (h * h * k * (1 + 4 * d)),
a = 4.0 / (h * (1 + 4 * d)),
b = (4.0 * d) / (1 + 4 * d),
useGlobalParams = this.useGlobalEquationParameters,
add = vec2.add,
set = vec2.set,
useZeroRHS = this.useZeroRHS;
// Things that does not change during iteration can be computed once
if(this.lambda.length < Neq){
this.lambda = new ARRAY_TYPE(Neq + this.arrayStep);
this.Bs = new ARRAY_TYPE(Neq + this.arrayStep);
this.invCs = new ARRAY_TYPE(Neq + this.arrayStep);
}
var invCs = this.invCs,
Bs = this.Bs,
lambda = this.lambda;
for(var i=0; i!==Neq; i++){
var c = equations[i];
lambda[i] = 0.0;
var _a = a,
_b = b,
_eps = eps;
if(!useGlobalParams){
if(h !== c.h) c.updateSpookParams(h);
_a = c.a;
_b = c.b;
_eps = c.eps;
}
Bs[i] = c.computeB(_a,_b,h);
invCs[i] = 1.0 / c.computeC(_eps);
}
var q, B, c, invC, deltalambda, deltalambdaTot, GWlambda, lambdaj;
if(Neq !== 0){
var i,j, minForce, maxForce, lambdaj_plus_deltalambda;
// Reset vlambda
for(i=0; i!==Nbodies; i++){
var b=bodies[i], vlambda=b.vlambda;
set(vlambda,0,0);
b.wlambda = 0;
}
// Iterate over equations
for(iter=0; iter!==maxIter; iter++){
// Accumulate the total error for each iteration.
deltalambdaTot = 0.0;
for(j=0; j!==Neq; j++){
c = equations[j];
var _eps = useGlobalParams ? eps : c.eps;
// Compute iteration
maxForce = c.maxForce;
minForce = c.minForce;
B = Bs[j];
invC = invCs[j];
lambdaj = lambda[j];
GWlambda = c.computeGWlambda(_eps);
if(useZeroRHS) B = 0;
deltalambda = invC * ( B - GWlambda - _eps * lambdaj );
// Clamp if we are not within the min/max interval
lambdaj_plus_deltalambda = lambdaj + deltalambda;
if(lambdaj_plus_deltalambda < minForce){
deltalambda = minForce - lambdaj;
} else if(lambdaj_plus_deltalambda > maxForce){
deltalambda = maxForce - lambdaj;
}
lambda[j] += deltalambda;
deltalambdaTot += Math.abs(deltalambda);
c.addToWlambda(deltalambda);
}
// If the total error is small enough - stop iterate
if(deltalambdaTot*deltalambdaTot <= tolSquared) break;
}
// Add result to velocity
for(i=0; i!==Nbodies; i++){
var b=bodies[i], v=b.velocity;
add( v, v, b.vlambda);
b.angularVelocity += b.wlambda;
}
}
errorTot = deltalambdaTot;
};

View file

@ -0,0 +1,81 @@
module.exports = Island;
/**
* An island of bodies connected with equations.
* @class Island
* @constructor
*/
function Island(){
/**
* Current equations in this island.
* @property equations
* @type {Array}
*/
this.equations = [];
/**
* Current bodies in this island.
* @property bodies
* @type {Array}
*/
this.bodies = [];
}
/**
* Clean this island from bodies and equations.
* @method reset
*/
Island.prototype.reset = function(){
this.equations.length = this.bodies.length = 0;
}
/**
* Get all unique bodies in this island.
* @method getBodies
* @return {Array} An array of Body
*/
Island.prototype.getBodies = function(){
var bodies = [],
bodyIds = [],
eqs = this.equations;
for(var i=0; i!==eqs.length; i++){
var eq = eqs[i];
if(bodyIds.indexOf(eq.bi.id)===-1){
bodies.push(eq.bi);
bodyIds.push(eq.bi.id);
}
if(bodyIds.indexOf(eq.bj.id)===-1){
bodies.push(eq.bj);
bodyIds.push(eq.bj.id);
}
}
return bodies;
};
/**
* Solves all constraints in the group of islands.
* @method solve
* @param {Number} dt
* @param {Solver} solver
*/
Island.prototype.solve = function(dt,solver){
var bodies = [];
solver.removeAllEquations();
// Add equations to solver
var numEquations = this.equations.length;
for(var j=0; j!==numEquations; j++){
solver.addEquation(this.equations[j]);
}
var islandBodies = this.getBodies();
var numBodies = islandBodies.length;
for(var j=0; j!==numBodies; j++){
bodies.push(islandBodies[j]);
}
// Solve
solver.solve(dt,{bodies:bodies});
};

View file

@ -0,0 +1,159 @@
var Solver = require('./Solver')
, vec2 = require('../math/vec2')
, Island = require('../solver/Island')
, Body = require('../objects/Body')
, STATIC = Body.STATIC
module.exports = IslandSolver;
/**
* Splits the system of bodies and equations into independent islands
*
* @class IslandSolver
* @constructor
* @param {Solver} subsolver
* @extends Solver
*/
function IslandSolver(subsolver){
Solver.call(this);
var that = this;
/**
* The solver used in the workers.
* @property subsolver
* @type {Solver}
*/
this.subsolver = subsolver;
/**
* Number of islands
* @property numIslands
* @type {number}
*/
this.numIslands = 0;
// Pooling of node objects saves some GC load
this._nodePool = [];
};
IslandSolver.prototype = new Object(Solver.prototype);
function getUnvisitedNode(nodes){
var Nnodes = nodes.length;
for(var i=0; i!==Nnodes; i++){
var node = nodes[i];
if(!node.visited && !(node.body.motionState & STATIC)){ // correct?
return node;
}
}
return false;
}
function bfs(root,visitFunc){
var queue = [];
queue.push(root);
root.visited = true;
visitFunc(root);
while(queue.length) {
var node = queue.pop();
// Loop over unvisited child nodes
var child;
while((child = getUnvisitedNode(node.children))) {
child.visited = true;
visitFunc(child);
queue.push(child);
}
}
}
/**
* Solves the full system.
* @method solve
* @param {Number} dt
* @param {World} world
*/
IslandSolver.prototype.solve = function(dt,world){
var nodes = [],
bodies=world.bodies,
equations=this.equations,
Neq=equations.length,
Nbodies=bodies.length,
subsolver=this.subsolver,
workers = this._workers,
workerData = this._workerData,
workerIslandGroups = this._workerIslandGroups;
// Create needed nodes, reuse if possible
for(var i=0; i!==Nbodies; i++){
if(this._nodePool.length)
nodes.push( this._nodePool.pop() );
else {
nodes.push({
body:bodies[i],
children:[],
eqs:[],
visited:false
});
}
}
// Reset node values
for(var i=0; i!==Nbodies; i++){
var node = nodes[i];
node.body = bodies[i];
node.children.length = 0;
node.eqs.length = 0;
node.visited = false;
}
// Add connectivity data. Each equation connects 2 bodies.
for(var k=0; k!==Neq; k++){
var eq=equations[k],
i=bodies.indexOf(eq.bi),
j=bodies.indexOf(eq.bj),
ni=nodes[i],
nj=nodes[j];
ni.children.push(nj);
ni.eqs.push(eq);
nj.children.push(ni);
nj.eqs.push(eq);
}
// The BFS search algorithm needs a traversal function. What we do is gather all bodies and equations connected.
var child, n=0, eqs=[], bds=[];
function visitFunc(node){
bds.push(node.body);
var Neqs = node.eqs.length;
for(var i=0; i!==Neqs; i++){
var eq = node.eqs[i];
if(eqs.indexOf(eq) === -1){
eqs.push(eq);
}
}
}
// Get islands
var islands = [];
while((child = getUnvisitedNode(nodes))){
var island = new Island(); // @todo Should be reused from somewhere
eqs.length = 0;
bds.length = 0;
bfs(child,visitFunc); // run search algo to gather an island of bodies
// Add equations to island
var Neqs = eqs.length;
for(var i=0; i!==Neqs; i++){
var eq = eqs[i];
island.equations.push(eq);
}
n++;
islands.push(island);
}
this.numIslands = n;
// Solve islands
for(var i=0; i<islands.length; i++){
islands[i].solve(dt,this.subsolver);
}
};

View file

@ -0,0 +1,65 @@
var Utils = require('../utils/Utils');
module.exports = Solver;
/**
* Base class for constraint solvers.
* @class Solver
* @constructor
*/
function Solver(){
/**
* Current equations in the solver.
*
* @property equations
* @type {Array}
*/
this.equations = [];
};
Solver.prototype.solve = function(dt,world){
throw new Error("Solver.solve should be implemented by subclasses!");
};
/**
* Add an equation to be solved.
*
* @method addEquation
* @param {Equation} eq
*/
Solver.prototype.addEquation = function(eq){
this.equations.push(eq);
};
/**
* Add equations. Same as .addEquation, but this time the argument is an array of Equations
*
* @method addEquations
* @param {Array} eqs
*/
Solver.prototype.addEquations = function(eqs){
Utils.appendArray(this.equations,eqs);
};
/**
* Remove an equation.
*
* @method removeEquation
* @param {Equation} eq
*/
Solver.prototype.removeEquation = function(eq){
var i = this.equations.indexOf(eq);
if(i!=-1)
this.equations.splice(i,1);
};
/**
* Remove all currently added equations.
*
* @method removeAllEquations
*/
Solver.prototype.removeAllEquations = function(){
this.equations.length=0;
};

View file

@ -0,0 +1,25 @@
module.exports = Utils;
/**
* Misc utility functions
* @class Utils
* @constructor
*/
function Utils(){};
/**
* Append the values in array b to the array a. See <a href="http://stackoverflow.com/questions/1374126/how-to-append-an-array-to-an-existing-javascript-array/1374131#1374131">this</a> for an explanation.
* @method appendArray
* @static
* @param {Array} a
* @param {Array} b
*/
Utils.appendArray = function(a,b){
if (b.length < 150000) {
a.push.apply(a, b)
} else {
for (var i = 0, len = b.length; i !== len; ++i) {
a.push(b[i]);
}
}
};

View file

@ -0,0 +1,870 @@
var GSSolver = require('../solver/GSSolver')
, NaiveBroadphase = require('../collision/NaiveBroadphase')
, vec2 = require('../math/vec2')
, Circle = require('../shapes/Circle')
, Rectangle = require('../shapes/Rectangle')
, Convex = require('../shapes/Convex')
, Line = require('../shapes/Line')
, Plane = require('../shapes/Plane')
, Capsule = require('../shapes/Capsule')
, Particle = require('../shapes/Particle')
, EventEmitter = require('../events/EventEmitter')
, Body = require('../objects/Body')
, Spring = require('../objects/Spring')
, Material = require('../material/Material')
, ContactMaterial = require('../material/ContactMaterial')
, DistanceConstraint = require('../constraints/DistanceConstraint')
, PointToPointConstraint = require('../constraints/PointToPointConstraint')
, PrismaticConstraint = require('../constraints/PrismaticConstraint')
, pkg = require('../../package.json')
, Broadphase = require('../collision/Broadphase')
, Nearphase = require('../collision/Nearphase')
module.exports = World;
function now(){
if(performance.now)
return performance.now();
else if(performance.webkitNow)
return performance.webkitNow();
else
return new Date().getTime();
}
/**
* The dynamics world, where all bodies and constraints lives.
*
* @class World
* @constructor
* @param {Object} [options]
* @param {Solver} options.solver Defaults to GSSolver.
* @param {Float32Array} options.gravity Defaults to [0,-9.78]
* @param {Broadphase} options.broadphase Defaults to NaiveBroadphase
* @extends {EventEmitter}
*/
function World(options){
EventEmitter.apply(this);
options = options || {};
/**
* All springs in the world.
*
* @property springs
* @type {Array}
*/
this.springs = [];
/**
* All bodies in the world.
*
* @property bodies
* @type {Array}
*/
this.bodies = [];
/**
* The solver used to satisfy constraints and contacts.
*
* @property solver
* @type {Solver}
*/
this.solver = options.solver || new GSSolver();
/**
* The nearphase to use to generate contacts.
*
* @property nearphase
* @type {Nearphase}
*/
this.nearphase = new Nearphase();
/**
* Gravity in the world. This is applied on all bodies in the beginning of each step().
*
* @property
* @type {Float32Array}
*/
this.gravity = options.gravity || vec2.fromValues(0, -9.78);
/**
* Whether to do timing measurements during the step() or not.
*
* @property doPofiling
* @type {Boolean}
*/
this.doProfiling = options.doProfiling || false;
/**
* How many millisecconds the last step() took. This is updated each step if .doProfiling is set to true.
*
* @property lastStepTime
* @type {Number}
*/
this.lastStepTime = 0.0;
/**
* The broadphase algorithm to use.
*
* @property broadphase
* @type {Broadphase}
*/
this.broadphase = options.broadphase || new NaiveBroadphase();
/**
* User-added constraints.
*
* @property constraints
* @type {Array}
*/
this.constraints = [];
/**
* Friction between colliding bodies. This value is used if no matching ContactMaterial is found for the body pair.
* @property defaultFriction
* @type {Number}
*/
this.defaultFriction = 0.1;
/**
* For keeping track of what time step size we used last step
* @property lastTimeStep
* @type {Number}
*/
this.lastTimeStep = 1/60;
/**
* Enable to automatically apply spring forces each step.
* @property applySpringForces
* @type {Boolean}
*/
this.applySpringForces = true;
/**
* Enable/disable constraint solving in each step.
* @property solveConstraints
* @type {Boolean}
*/
this.solveConstraints = true;
/**
* The ContactMaterials added to the World.
* @property contactMaterials
* @type {Array}
*/
this.contactMaterials = [];
// Id counters
this._constraintIdCounter = 0;
this._bodyIdCounter = 0;
// Event objects that are reused
this.postStepEvent = {
type : "postStep",
};
this.addBodyEvent = {
type : "addBody",
body : null
};
this.removeBodyEvent = {
type : "removeBody",
body : null
};
this.addSpringEvent = {
type : "addSpring",
body : null
};
};
World.prototype = new Object(EventEmitter.prototype);
/**
* Add a constraint to the simulation.
*
* @method addConstraint
* @param {Constraint} c
*/
World.prototype.addConstraint = function(c){
this.constraints.push(c);
};
/**
* Add a ContactMaterial to the simulation.
* @method addContactMaterial
* @param {ContactMaterial} contactMaterial
*/
World.prototype.addContactMaterial = function(contactMaterial){
this.contactMaterials.push(contactMaterial);
};
/**
* Get a contact material given two materials
* @method getContactMaterial
* @param {Material} materialA
* @param {Material} materialB
* @return {ContactMaterial} The matching ContactMaterial, or false on fail.
* @todo Use faster hash map to lookup from material id's
*/
World.prototype.getContactMaterial = function(materialA,materialB){
var cmats = this.contactMaterials;
for(var i=0, N=cmats.length; i!==N; i++){
var cm = cmats[i];
if( (cm.materialA === materialA) && (cm.materialB === materialB) ||
(cm.materialA === materialB) && (cm.materialB === materialA) )
return cm;
}
return false;
};
/**
* Removes a constraint
*
* @method removeConstraint
* @param {Constraint} c
*/
World.prototype.removeConstraint = function(c){
var idx = this.constraints.indexOf(c);
if(idx!==-1){
this.constraints.splice(idx,1);
}
};
var step_r = vec2.create(),
step_runit = vec2.create(),
step_u = vec2.create(),
step_f = vec2.create(),
step_fhMinv = vec2.create(),
step_velodt = vec2.create(),
xiw = vec2.fromValues(0,0),
xjw = vec2.fromValues(0,0),
zero = vec2.fromValues(0,0);
/**
* Step the physics world forward in time.
*
* @method step
* @param {Number} dt The time step size to use.
*
* @example
* var world = new World();
* world.step(0.01);
*/
World.prototype.step = function(dt){
var that = this,
doProfiling = this.doProfiling,
Nsprings = this.springs.length,
springs = this.springs,
bodies = this.bodies,
g = this.gravity,
solver = this.solver,
Nbodies = this.bodies.length,
broadphase = this.broadphase,
np = this.nearphase,
constraints = this.constraints,
t0, t1,
fhMinv = step_fhMinv,
velodt = step_velodt,
scale = vec2.scale,
add = vec2.add,
rotate = vec2.rotate;
this.lastTimeStep = dt;
if(doProfiling){
t0 = now();
}
// add gravity to bodies
for(var i=0; i!==Nbodies; i++){
var fi = bodies[i].force;
add(fi,fi,g);
}
// Add spring forces
if(this.applySpringForces){
for(var i=0; i!==Nsprings; i++){
var s = springs[i];
s.applyForce();
}
}
// Broadphase
var result = broadphase.getCollisionPairs(this);
// Nearphase
var glen = vec2.length(this.gravity);
np.reset();
for(var i=0, Nresults=result.length; i!==Nresults; i+=2){
var bi = result[i],
bj = result[i+1];
// Loop over all shapes of body i
for(var k=0; k!==bi.shapes.length; k++){
var si = bi.shapes[k],
xi = bi.shapeOffsets[k] || zero,
ai = bi.shapeAngles[k] || 0;
// All shapes of body j
for(var l=0; l!==bj.shapes.length; l++){
var sj = bj.shapes[l],
xj = bj.shapeOffsets[l] || zero,
aj = bj.shapeAngles[l] || 0;
if(!((si.collisionGroup & sj.collisionMask) !== 0 && (sj.collisionGroup & si.collisionMask) !== 0))
continue;
var reducedMass = (bi.invMass + bj.invMass);
if(reducedMass > 0)
reducedMass = 1/reducedMass;
var mu = this.defaultFriction;
if(si.material && sj.material){
var cm = this.getContactMaterial(si.material,sj.material);
if(cm){
mu = cm.friction;
}
}
var mug = mu * glen * reducedMass,
doFriction = mu > 0;
// Get world position and angle of each shape
rotate(xiw, xi, bi.angle);
rotate(xjw, xj, bj.angle);
add(xiw, xiw, bi.position);
add(xjw, xjw, bj.position);
var aiw = ai + bi.angle;
var ajw = aj + bj.angle;
// Run nearphase
np.enableFriction = mu > 0;
np.slipForce = mug;
if(si instanceof Circle){
if(sj instanceof Circle) np.circleCircle (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Particle) np.circleParticle(bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Plane) np.circlePlane (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Rectangle) np.circleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Convex) np.circleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Line) np.circleLine (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Capsule) np.circleCapsule (bi,si,xiw,aiw, bj,sj,xjw,ajw);
} else if(si instanceof Particle){
if(sj instanceof Circle) np.circleParticle (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Plane) np.particlePlane (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Rectangle) np.particleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Convex) np.particleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Capsule) np.particleCapsule (bi,si,xiw,aiw, bj,sj,xjw,ajw);
} else if(si instanceof Plane){
if(sj instanceof Circle) np.circlePlane (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Particle) np.particlePlane (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Rectangle) np.convexPlane (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Convex) np.convexPlane (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Line) np.planeLine (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Capsule) np.capsulePlane (bj,sj,xjw,ajw, bi,si,xiw,aiw);
} else if(si instanceof Rectangle){
if(sj instanceof Plane) np.convexPlane (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Circle) np.circleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Rectangle) np.convexConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Convex) np.convexConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Particle) np.particleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw);
} else if(si instanceof Convex){
if(sj instanceof Plane) np.convexPlane (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Circle) np.circleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Rectangle) np.convexConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Convex) np.convexConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw);
else if(sj instanceof Particle) np.particleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw);
} else if(si instanceof Line){
if(sj instanceof Circle) np.circleLine (bj,sj,xjw,ajw, bi,si,xiw,aiw);
else if(sj instanceof Plane) np.planeLine (bj,sj,xjw,ajw, bi,si,xiw,aiw);
} else if(si instanceof Capsule){
if(sj instanceof Plane) np.capsulePlane (bi,si,xiw,aiw, bj,sj,xjw,ajw);
if(sj instanceof Circle) np.circleCapsule (bj,sj,xjw,ajw, bi,si,xiw,aiw);
if(sj instanceof Particle) np.particleCapsule(bj,sj,xjw,ajw, bi,si,xiw,aiw);
}
}
}
}
// Add contact equations to solver
solver.addEquations(np.contactEquations);
solver.addEquations(np.frictionEquations);
// Add user-defined constraint equations
var Nconstraints = constraints.length;
for(i=0; i!==Nconstraints; i++){
var c = constraints[i];
c.update();
solver.addEquations(c.equations);
}
if(this.solveConstraints)
solver.solve(dt,this);
solver.removeAllEquations();
// Step forward
for(var i=0; i!==Nbodies; i++){
var body = bodies[i];
if(body.mass>0){
var minv = body.invMass,
f = body.force,
pos = body.position,
velo = body.velocity;
// Angular step
body.angularVelocity += body.angularForce * body.invInertia * dt;
body.angle += body.angularVelocity * dt;
// Linear step
scale(fhMinv,f,dt*minv);
add(velo,fhMinv,velo);
scale(velodt,velo,dt);
add(pos,pos,velodt);
}
}
// Reset force
for(var i=0; i!==Nbodies; i++){
var bi = bodies[i];
vec2.set(bi.force,0.0,0.0);
bi.angularForce = 0.0;
}
if(doProfiling){
t1 = now();
that.lastStepTime = t1-t0;
}
this.emit(this.postStepEvent);
};
/**
* Add a spring to the simulation
*
* @method addSpring
* @param {Spring} s
*/
World.prototype.addSpring = function(s){
this.springs.push(s);
this.addSpringEvent.spring = s;
this.emit(this.addSpringEvent);
};
/**
* Remove a spring
*
* @method removeSpring
* @param {Spring} s
*/
World.prototype.removeSpring = function(s){
var idx = this.springs.indexOf(s);
if(idx===-1)
this.springs.splice(idx,1);
};
/**
* Add a body to the simulation
*
* @method addBody
* @param {Body} body
*
* @example
* var world = new World(),
* body = new Body();
* world.addBody(body);
*
*/
World.prototype.addBody = function(body){
this.bodies.push(body);
this.addBodyEvent.body = body;
this.emit(this.addBodyEvent);
};
/**
* Remove a body from the simulation
*
* @method removeBody
* @param {Body} body
*/
World.prototype.removeBody = function(body){
var idx = this.bodies.indexOf(body);
if(idx!==-1){
this.bodies.splice(idx,1);
this.removeBodyEvent.body = body;
this.emit(this.removeBodyEvent);
}
};
/**
* Convert the world to a JSON-serializable Object.
*
* @method toJSON
* @return {Object}
*/
World.prototype.toJSON = function(){
var json = {
p2 : pkg.version.split(".").slice(0,2).join("."), // "X.Y"
bodies : [],
springs : [],
solver : {},
gravity : v2a(this.gravity),
broadphase : {},
constraints : [],
contactMaterials : [],
};
// Serialize springs
for(var i=0; i<this.springs.length; i++){
var s = this.springs[i];
json.springs.push({
bodyA : this.bodies.indexOf(s.bodyA),
bodyB : this.bodies.indexOf(s.bodyB),
stiffness : s.stiffness,
damping : s.damping,
restLength : s.restLength,
localAnchorA : v2a(s.localAnchorA),
localAnchorB : v2a(s.localAnchorB),
});
}
// Serialize constraints
for(var i=0; i<this.constraints.length; i++){
var c = this.constraints[i];
var jc = {
bodyA : this.bodies.indexOf(c.bodyA),
bodyB : this.bodies.indexOf(c.bodyB),
}
if(c instanceof DistanceConstraint){
jc.type = "DistanceConstraint";
jc.distance = c.distance;
jc.maxForce = c.getMaxForce();
} else if(c instanceof PointToPointConstraint){
jc.type = "PointToPointConstraint";
jc.pivotA = v2a(c.pivotA);
jc.pivotB = v2a(c.pivotB);
jc.maxForce = c.maxForce;
} else if(c instanceof PrismaticConstraint){
jc.type = "PrismaticConstraint";
jc.localAxisA = v2a(c.localAxisA);
jc.localAxisB = v2a(c.localAxisB);
jc.maxForce = c.maxForce;
} else {
console.error("Constraint not supported yet!");
continue;
}
json.constraints.push(jc);
}
// Serialize bodies
for(var i=0; i<this.bodies.length; i++){
var b = this.bodies[i],
ss = b.shapes,
jsonShapes = [];
for(var j=0; j<ss.length; j++){
var s = ss[j],
jsonShape;
// Check type
if(s instanceof Circle){
jsonShape = {
type : "Circle",
radius : s.radius,
};
} else if(s instanceof Plane){
jsonShape = { type : "Plane", };
} else if(s instanceof Particle){
jsonShape = { type : "Particle", };
} else if(s instanceof Line){
jsonShape = { type : "Line",
length : s.length };
} else if(s instanceof Rectangle){
jsonShape = { type : "Rectangle",
width : s.width,
height : s.height };
} else if(s instanceof Convex){
var verts = [];
for(var k=0; k<s.vertices.length; k++)
verts.push(v2a(s.vertices[k]));
jsonShape = { type : "Convex",
verts : verts };
} else if(s instanceof Capsule){
jsonShape = { type : "Capsule",
length : s.length,
radius : s.radius };
} else {
throw new Error("Shape type not supported yet!");
}
jsonShape.offset = v2a(b.shapeOffsets[j]);
jsonShape.angle = b.shapeAngles[j];
jsonShape.collisionGroup = s.collisionGroup;
jsonShape.collisionMask = s.collisionMask;
jsonShape.material = s.material && {
id : s.material.id,
};
jsonShapes.push(jsonShape);
}
json.bodies.push({
id : b.id,
mass : b.mass,
angle : b.angle,
position : v2a(b.position),
velocity : v2a(b.velocity),
angularVelocity : b.angularVelocity,
force : v2a(b.force),
shapes : jsonShapes,
});
}
// Serialize contactmaterials
for(var i=0; i<this.contactMaterials.length; i++){
var cm = this.contactMaterials[i];
json.contactMaterials.push({
id : cm.id,
materialA : cm.materialA.id, // Note: Reference by id!
materialB : cm.materialB.id,
friction : cm.friction,
restitution : cm.restitution,
stiffness : cm.stiffness,
relaxation : cm.relaxation,
frictionStiffness : cm.frictionStiffness,
frictionRelaxation : cm.frictionRelaxation,
});
}
return json;
function v2a(v){
if(!v) return v;
return [v[0],v[1]];
}
};
/**
* Load a scene from a serialized state.
*
* @method fromJSON
* @param {Object} json
* @return {Boolean} True on success, else false.
*/
World.prototype.fromJSON = function(json){
this.clear();
if(!json.p2)
return false;
switch(json.p2){
case "0.2":
// Set gravity
vec2.copy(this.gravity, json.gravity);
// Load bodies
var id2material = {};
for(var i=0; i<json.bodies.length; i++){
var jb = json.bodies[i],
jss = jb.shapes;
var b = new Body({
mass : jb.mass,
position : jb.position,
angle : jb.angle,
velocity : jb.velocity,
angularVelocity : jb.angularVelocity,
force : jb.force,
});
b.id = jb.id;
for(var j=0; j<jss.length; j++){
var shape, js=jss[j];
switch(js.type){
case "Circle": shape = new Circle(js.radius); break;
case "Plane": shape = new Plane(); break;
case "Particle": shape = new Particle(); break;
case "Line": shape = new Line(js.length); break;
case "Rectangle": shape = new Rectangle(js.width,js.height); break;
case "Convex": shape = new Convex(js.verts); break;
case "Capsule": shape = new Capsule(js.length, js.radius); break;
default:
throw new Error("Shape type not supported: "+js.type);
break;
}
shape.collisionMask = js.collisionMask;
shape.collisionGroup = js.collisionGroup;
shape.material = js.material;
if(shape.material){
shape.material = new Material();
shape.material.id = js.material.id;
id2material[shape.material.id+""] = shape.material;
}
b.addShape(shape,js.offset,js.angle);
}
this.addBody(b);
}
// Load springs
for(var i=0; i<json.springs.length; i++){
var js = json.springs[i];
var s = new Spring(this.bodies[js.bodyA], this.bodies[js.bodyB], {
stiffness : js.stiffness,
damping : js.damping,
restLength : js.restLength,
localAnchorA : js.localAnchorA,
localAnchorB : js.localAnchorB,
});
this.addSpring(s);
}
// Load contact materials
for(var i=0; i<json.contactMaterials.length; i++){
var jm = json.contactMaterials[i];
var cm = new ContactMaterial(id2material[jm.materialA+""], id2material[jm.materialB+""], {
friction : jm.friction,
restitution : jm.restitution,
stiffness : jm.stiffness,
relaxation : jm.relaxation,
frictionStiffness : jm.frictionStiffness,
frictionRelaxation : jm.frictionRelaxation,
});
cm.id = jm.id;
this.addContactMaterial(cm);
}
// Load constraints
for(var i=0; i<json.constraints.length; i++){
var jc = json.constraints[i],
c;
switch(jc.type){
case "DistanceConstraint":
c = new DistanceConstraint(this.bodies[jc.bodyA], this.bodies[jc.bodyB], jc.distance, jc.maxForce);
break;
case "PointToPointConstraint":
c = new PointToPointConstraint(this.bodies[jc.bodyA], jc.pivotA, this.bodies[jc.bodyB], jc.pivotB, jc.maxForce);
break;
case "PrismaticConstraint":
c = new PrismaticConstraint(this.bodies[jc.bodyA], this.bodies[jc.bodyB], {
maxForce : jc.maxForce,
localAxisA : jc.localAxisA,
localAxisB : jc.localAxisB,
});
break;
default:
throw new Error("Constraint type not recognized: "+jc.type);
}
this.addConstraint(c);
}
break;
default:
return false;
break;
}
return true;
};
/**
* Resets the World, removes all bodies, constraints and springs.
*
* @method clear
*/
World.prototype.clear = function(){
// Remove all constraints
var cs = this.constraints;
for(var i=cs.length-1; i>=0; i--){
this.removeConstraint(cs[i]);
}
// Remove all bodies
var bodies = this.bodies;
for(var i=bodies.length-1; i>=0; i--){
this.removeBody(bodies[i]);
}
// Remove all springs
var springs = this.springs;
for(var i=springs.length-1; i>=0; i--){
this.removeSpring(springs[i]);
}
};
/**
* Get a copy of this World instance
* @method clone
* @return {World}
*/
World.prototype.clone = function(){
var world = new World();
world.fromJSON(this.toJSON());
return world;
};
var hitTest_tmp1 = vec2.create(),
hitTest_zero = vec2.fromValues(0,0),
hitTest_tmp2 = vec2.fromValues(0,0);
/**
* Test if a world point overlaps bodies
* @method hitTest
* @param {Array} worldPoint Point to use for intersection tests
* @param {Array} bodies A list of objects to check for intersection
* @param {Number} precision Used for matching against particles and lines. Adds some margin to these infinitesimal objects.
* @return {Array} Array of bodies that overlap the point
*/
World.prototype.hitTest = function(worldPoint,bodies,precision){
precision = precision || 0;
// Create a dummy particle body with a particle shape to test against the bodies
var pb = new Body({ position:worldPoint }),
ps = new Particle(),
px = worldPoint,
pa = 0,
x = hitTest_tmp1,
zero = hitTest_zero,
tmp = hitTest_tmp2;
pb.addShape(ps);
var n = this.nearphase,
result = [];
// Check bodies
for(var i=0, N=bodies.length; i!==N; i++){
var b = bodies[i];
for(var j=0, NS=b.shapes.length; j!==NS; j++){
var s = b.shapes[j],
offset = b.shapeOffsets[j] || zero,
angle = b.shapeAngles[j] || 0.0;
// Get shape world position + angle
vec2.rotate(x, offset, b.angle);
vec2.add(x, x, b.position);
var a = angle + b.angle;
if( (s instanceof Circle && n.circleParticle (b,s,x,a, pb,ps,px,pa, true)) ||
(s instanceof Convex && n.particleConvex (pb,ps,px,pa, b,s,x,a, true)) ||
(s instanceof Plane && n.particlePlane (pb,ps,px,pa, b,s,x,a, true)) ||
(s instanceof Capsule && n.particleCapsule (pb,ps,px,pa, b,s,x,a, true)) ||
(s instanceof Particle && vec2.squaredLength(vec2.sub(tmp,x,worldPoint)) < precision*precision)
){
result.push(b);
}
}
}
return result;
};

190
src/tilemap/Tile.js Normal file
View file

@ -0,0 +1,190 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
* @module Phaser.Tile
*/
/**
* Create a new <code>Tile</code>.
*
* @class Phaser.Tile
* @classdesc A Tile is a single representation of a tile within a Tilemap.
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {Tilemap} tilemap - The tilemap this tile belongs to.
* @param {number} index - The index of this tile type in the core map data.
* @param {number} width - Width of the tile.
* @param {number} height - Height of the tile.
*/
Phaser.Tile = function (tileset, index, x, y, width, height) {
/**
* @property {string} tileset - The tileset this tile belongs to.
*/
this.tileset = tileset;
/**
* @property {number} index - The index of this tile within the tileset.
*/
this.index = index;
/**
* @property {number} width - The width of the tile in pixels.
*/
this.width = width;
/**
* @property {number} height - The height of the tile in pixels.
*/
this.height = height;
/**
* @property {number} x - The top-left corner of the tile within the tileset.
*/
this.x = x;
/**
* @property {number} y - The top-left corner of the tile within the tileset.
*/
this.y = y;
// Any extra meta data info we need here
/**
* @property {number} mass - The virtual mass of the tile.
* @default
*/
this.mass = 1.0;
/**
* @property {boolean} collideNone - Indicating this Tile doesn't collide at all.
* @default
*/
this.collideNone = true;
/**
* @property {boolean} collideLeft - Indicating collide with any object on the left.
* @default
*/
this.collideLeft = false;
/**
* @property {boolean} collideRight - Indicating collide with any object on the right.
* @default
*/
this.collideRight = false;
/**
* @property {boolean} collideUp - Indicating collide with any object on the top.
* @default
*/
this.collideUp = false;
/**
* @property {boolean} collideDown - Indicating collide with any object on the bottom.
* @default
*/
this.collideDown = false;
/**
* @property {boolean} separateX - Enable separation at x-axis.
* @default
*/
this.separateX = true;
/**
* @property {boolean} separateY - Enable separation at y-axis.
* @default
*/
this.separateY = true;
};
Phaser.Tile.prototype = {
/**
* Clean up memory.
* @method destroy
*/
destroy: function () {
this.tilemap = null;
},
/**
* Set collision configs.
* @method setCollision
* @param {boolean} left - Indicating collide with any object on the left.
* @param {boolean} right - Indicating collide with any object on the right.
* @param {boolean} up - Indicating collide with any object on the top.
* @param {boolean} down - Indicating collide with any object on the bottom.
* @param {boolean} reset - Description.
* @param {boolean} separateX - Separate at x-axis.
* @param {boolean} separateY - Separate at y-axis.
*/
setCollision: function (left, right, up, down, reset, separateX, separateY) {
if (reset)
{
this.resetCollision();
}
this.separateX = separateX;
this.separateY = separateY;
this.collideNone = true;
this.collideLeft = left;
this.collideRight = right;
this.collideUp = up;
this.collideDown = down;
if (left || right || up || down)
{
this.collideNone = false;
}
},
/**
* Reset collision status flags.
* @method resetCollision
*/
resetCollision: function () {
this.collideNone = true;
this.collideLeft = false;
this.collideRight = false;
this.collideUp = false;
this.collideDown = false;
}
};
Object.defineProperty(Phaser.Tile.prototype, "bottom", {
/**
* The sum of the y and height properties. Changing the bottom property of a Rectangle object has no effect on the x, y and width properties, but does change the height property.
* @method bottom
* @return {number}
**/
get: function () {
return this.y + this.height;
}
});
Object.defineProperty(Phaser.Tile.prototype, "right", {
/**
* The sum of the x and width properties. Changing the right property of a Rectangle object has no effect on the x, y and height properties.
* However it does affect the width property.
* @method right
* @return {number}
**/
get: function () {
return this.x + this.width;
}
});

View file

@ -1,217 +1,80 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013 Photon Storm Ltd.
* @license https://github.com/photonstorm/phaser/blob/master/license.txt MIT License
* @module Phaser.TilemapLayer
*/
/**
* Create a new <code>TilemapLayer</code>.
* @class Phaser.TilemapLayer
* @classdesc A Tilemap Layer. Tiled format maps can have multiple overlapping layers.
* @constructor
* @param parent {Tilemap} The tilemap that contains this layer.
* @param id {number} The ID of this layer within the Tilemap array.
* @param key {string} Asset key for this map.
* @param mapformat {number} Format of this map data, available: Tilemap.CSV or Tilemap.JSON.
* @param name {string} Name of this layer, so you can get this layer by its name.
* @param tileWidth {number} Width of tiles in this map.
* @param tileHeight {number} Height of tiles in this map.
*/
Phaser.TilemapLayer = function (parent, id, key, mapFormat, name, tileWidth, tileHeight) {
Phaser.TilemapLayer = function (game, x, y, renderWidth, renderHeight, mapData, tileset) {
/**
* @property {boolean} exists - Controls whether update() and draw() are automatically called.
* @default
*/
this.exists = true;
/**
* @property {boolean} visible - Controls whether draw() are automatically called.
* @default
*/
this.visible = true;
/**
* How many tiles in each row.
* Read-only variable, do NOT recommend changing after the map is loaded!
* @property {number} widthInTiles
* @default
*/
this.widthInTiles = 0;
/**
* How many tiles in each column.
* Read-only variable, do NOT recommend changing after the map is loaded!
* @property {number} heightInTiles
* @default
*/
this.heightInTiles = 0;
/**
* Read-only variable, do NOT recommend changing after the map is loaded!
* @property {number} widthInPixels
* @default
*/
this.widthInPixels = 0;
/**
* Read-only variable, do NOT recommend changing after the map is loaded!
* @property {number} heightInPixels
* @default
*/
this.heightInPixels = 0;
/**
* Distance between REAL tiles to the tileset texture bound.
* @property {number} tileMargin
* @default
*/
this.tileMargin = 0;
/**
* Distance between every 2 neighbor tile in the tileset texture.
* @property {number} tileSpacing
* @default
*/
this.tileSpacing = 0;
/**
* @property {Description} parent - Description.
*/
this.parent = parent;
/**
* @property {Phaser.Game} game - Description.
*/
this.game = parent.game;
this.game = game;
/**
* @property {Description} ID - Description.
*/
this.ID = id;
/**
* @property {Description} name - Description.
*/
this.name = name;
/**
* @property {Description} key - Description.
*/
this.key = key;
/**
* @property {Description} type - Description.
*/
this.type = Phaser.TILEMAPLAYER;
/**
* @property {tileWidth} mapFormat - Description.
*/
this.mapFormat = mapFormat;
/**
* @property {Description} tileWidth - Description.
*/
this.tileWidth = tileWidth;
/**
* @property {Description} tileHeight - Description.
*/
this.tileHeight = tileHeight;
/**
* @property {Phaser.Rectangle} boundsInTiles - Description.
*/
this.boundsInTiles = new Phaser.Rectangle();
var map = this.game.cache.getTilemap(key);
/**
* @property {Description} tileset - Description.
*/
this.tileset = map.data;
/**
* @property {Description} _alpha - Description.
* @private
* @default
*/
this._alpha = 1;
/**
* @property {Description} canvas - Description.
* @default
*/
this.canvas = null;
this.canvas = Phaser.Canvas.create(renderWidth, renderHeight);
/**
* @property {Description} context - Description.
* @default
*/
this.context = null;
this.context = this.canvas.getContext('2d');
/**
* @property {Description} baseTexture - Description.
* @default
*/
this.baseTexture = null;
this.baseTexture = new PIXI.BaseTexture(this.canvas);
/**
* @property {Description} texture - Description.
* @default
*/
this.texture = null;
this.texture = new PIXI.Texture(this.baseTexture);
/**
* @property {Description} sprite - Description.
* @default
*/
this.sprite = null;
this.sprite = new PIXI.Sprite(this.texture);
/**
* @property {array} mapData - Description.
*/
/**
* @property {array} mapData - Description.
*/
this.mapData = [];
/**
* @property {array} _tempTileBlock - Description.
* @private
*/
this._tempTileBlock = [];
/**
* @property {array} _tempBlockResults - Description.
* @private
*
*/
this._tempBlockResults = [];
/**
* @property {Description} tileset - Description.
*/
this.tileset = tileset;
this.widthInTiles = 0;
this.heightInTiles = 0;
this.renderWidth = renderWidth;
this.renderHeight = renderHeight;
};
Phaser.TilemapLayer.prototype = {
/**
* Set a specific tile with its x and y in tiles.
* @method putTileWorldXY
* @param {number} x - X position of this tile in world coordinates.
* @param {number} y - Y position of this tile in world coordinates.
* @param {number} index - The index of this tile type in the core map data.
*/
putTileWorldXY: function (x, y, index) {
create: function (width, height) {
x = this.game.math.snapToFloor(x, this.tileWidth) / this.tileWidth;
y = this.game.math.snapToFloor(y, this.tileHeight) / this.tileHeight;
this.mapData = [];
if (y >= 0 && y < this.mapData.length)
var data;
for (var y = 0; y < height; y++)
{
if (x >= 0 && x < this.mapData[y].length)
this.mapData[y] = [];
for (var x = 0; x < width; x++)
{
this.mapData[y][x] = index;
this.mapData[y][x] = 0;
}
}
this.widthInTiles = width;
this.heightInTiles = height;
},
/**
@ -233,430 +96,34 @@ Phaser.TilemapLayer.prototype = {
},
/**
* Swap tiles with 2 kinds of indexes.
* @method swapTile
* @param {number} tileA - First tile index.
* @param {number} tileB - Second tile index.
* @param {number} [x] - specify a Rectangle of tiles to operate. The x position in tiles of Rectangle's left-top corner.
* @param {number} [y] - specify a Rectangle of tiles to operate. The y position in tiles of Rectangle's left-top corner.
* @param {number} [width] - specify a Rectangle of tiles to operate. The width in tiles.
* @param {number} [height] - specify a Rectangle of tiles to operate. The height in tiles.
*/
swapTile: function (tileA, tileB, x, y, width, height) {
dump: function () {
x = x || 0;
y = y || 0;
width = width || this.widthInTiles;
height = height || this.heightInTiles;
this.getTempBlock(x, y, width, height);
var txt = '';
var args = [''];
for (var r = 0; r < this._tempTileBlock.length; r++)
for (var y = 0; y < this.heightInTiles; y++)
{
// First sweep marking tileA as needing a new index
if (this._tempTileBlock[r].tile.index == tileA)
for (var x = 0; x < this.widthInTiles; x++)
{
this._tempTileBlock[r].newIndex = true;
}
txt += "%c ";
// In the same pass we can swap tileB to tileA
if (this._tempTileBlock[r].tile.index == tileB)
{
this.mapData[this._tempTileBlock[r].y][this._tempTileBlock[r].x] = tileA;
}
}
for (var r = 0; r < this._tempTileBlock.length; r++)
{
// And now swap our newIndex tiles for tileB
if (this._tempTileBlock[r].newIndex == true)
{
this.mapData[this._tempTileBlock[r].y][this._tempTileBlock[r].x] = tileB;
}
}
},
/**
* Fill a tile block with a specific tile index.
* @method fillTile
* @param {number} index - Index of tiles you want to fill with.
* @param {number} [x] - X position (in tiles) of block's left-top corner.
* @param {number} [y] - Y position (in tiles) of block's left-top corner.
* @param {number} [width] - width of block.
* @param {number} [height] - height of block.
*/
fillTile: function (index, x, y, width, height) {
x = x || 0;
y = y || 0;
width = width || this.widthInTiles;
height = height || this.heightInTiles;
this.getTempBlock(x, y, width, height);
for (var r = 0; r < this._tempTileBlock.length; r++)
{
this.mapData[this._tempTileBlock[r].y][this._tempTileBlock[r].x] = index;
}
},
/**
* Set random tiles to a specific tile block.
* @method randomiseTiles
* @param {number[]} tiles - Tiles with indexes in this array will be randomly set to the given block.
* @param {number} [x] - X position (in tiles) of block's left-top corner.
* @param {number} [y] - Y position (in tiles) of block's left-top corner.
* @param {number} [width] - width of block.
* @param {number} [height] - height of block.
*/
randomiseTiles: function (tiles, x, y, width, height) {
x = x || 0;
y = y || 0;
width = width || this.widthInTiles;
height = height || this.heightInTiles;
this.getTempBlock(x, y, width, height);
for (var r = 0; r < this._tempTileBlock.length; r++)
{
this.mapData[this._tempTileBlock[r].y][this._tempTileBlock[r].x] = this.game.math.getRandom(tiles);
}
},
/**
* Replace one kind of tiles to another kind.
* @method replaceTile
* @param {number} tileA - First tile index.
* @param {number} tileB - Second tile index.
* @param {number} [x] - X position (in tiles) of block's left-top corner.
* @param {number} [y] - Y position (in tiles) of block's left-top corner.
* @param {number} [width] - width of block.
* @param {number} [height] - height of block.
*/
replaceTile: function (tileA, tileB, x, y, width, height) {
x = x || 0;
y = y || 0;
width = width || this.widthInTiles;
height = height || this.heightInTiles;
this.getTempBlock(x, y, width, height);
for (var r = 0; r < this._tempTileBlock.length; r++)
{
if (this._tempTileBlock[r].tile.index == tileA)
{
this.mapData[this._tempTileBlock[r].y][this._tempTileBlock[r].x] = tileB;
}
}
},
/**
* Get a tile block with specific position and size (both are in tiles).
* @method getTileBlock
* @param {number} [x] - X position (in tiles) of block's left-top corner.
* @param {number} [y] - Y position (in tiles) of block's left-top corner.
* @param {number} [width] - width of block.
* @param {number} [height] - height of block.
*/
getTileBlock: function (x, y, width, height) {
var output = [];
this.getTempBlock(x, y, width, height);
for (var r = 0; r < this._tempTileBlock.length; r++)
{
output.push({
x: this._tempTileBlock[r].x,
y: this._tempTileBlock[r].y,
tile: this._tempTileBlock[r].tile
});
}
return output;
},
/**
* Get a tile with specific position (in world coordinate). (thus you give a position of a point which is within the tile)
* @method getTileFromWorldXY
* @param {number} [x] - X position (in tiles) of block's left-top corner.
* @param {number} [y] - Y position (in tiles) of block's left-top corner.
*/
getTileFromWorldXY: function (x, y) {
x = Phaser.Math.snapToFloor(x, this.tileWidth) / this.tileWidth;
y = Phaser.Math.snapToFloor(y, this.tileHeight) / this.tileHeight;
return this.getTileIndex(x, y);
},
/**
* Get tiles overlaps the given object.
* @method getTileOverlaps
* @param {GameObject} object - Tiles you want to get that overlaps this.
* @return {array} Array with tiles informations (each contains x, y, and the tile).
*/
getTileOverlaps: function (object) {
this._tempBlockResults.length = 0;
// If the object is outside of the world coordinates then abort the check (tilemap has to exist within world bounds)
if (object.body.x < 0 || object.body.x > this.widthInPixels || object.body.y < 0 || object.body.bottom > this.heightInPixels)
{
return this._tempBlockResults;
}
// What tiles do we need to check against?
this._tempTileX = this.game.math.snapToFloor(object.body.x, this.tileWidth) / this.tileWidth;
this._tempTileY = this.game.math.snapToFloor(object.body.y, this.tileHeight) / this.tileHeight;
this._tempTileW = (this.game.math.snapToCeil(object.body.width, this.tileWidth) + this.tileWidth) / this.tileWidth;
this._tempTileH = (this.game.math.snapToCeil(object.body.height, this.tileHeight) + this.tileHeight) / this.tileHeight;
// Loop through the tiles we've got and check overlaps accordingly (the results are stored in this._tempTileBlock)
this.getTempBlock(this._tempTileX, this._tempTileY, this._tempTileW, this._tempTileH, true);
for (var r = 0; r < this._tempTileBlock.length; r++)
{
// separateTile: function (object, x, y, width, height, mass, collideLeft, collideRight, collideUp, collideDown, separateX, separateY)
if (this.game.physics.separateTile(object, this._tempTileBlock[r].x * this.tileWidth, this._tempTileBlock[r].y * this.tileHeight, this.tileWidth, this.tileHeight, this._tempTileBlock[r].tile.mass, this._tempTileBlock[r].tile.collideLeft, this._tempTileBlock[r].tile.collideRight, this._tempTileBlock[r].tile.collideUp, this._tempTileBlock[r].tile.collideDown, this._tempTileBlock[r].tile.separateX, this._tempTileBlock[r].tile.separateY))
{
this._tempBlockResults.push({ x: this._tempTileBlock[r].x, y: this._tempTileBlock[r].y, tile: this._tempTileBlock[r].tile });
}
}
return this._tempBlockResults;
},
/**
* Get a tile block with its position and size (this method does not return, it'll set result to _tempTileBlock).
* @method getTempBlock
* @param {number} [x] - X position (in tiles) of block's left-top corner.
* @param {number} [y] - Y position (in tiles) of block's left-top corner.
* @param {number} [width] - width of block.
* @param {number} [height] - height of block.
* @param {boolean} collisionOnly - Whethor or not ONLY return tiles which will collide (its allowCollisions value is not Collision.NONE).
*/
getTempBlock: function (x, y, width, height, collisionOnly) {
if (typeof collisionOnly === "undefined") { collisionOnly = false; }
if (x < 0)
{
x = 0;
}
if (y < 0)
{
y = 0;
}
if (width > this.widthInTiles)
{
width = this.widthInTiles;
}
if (height > this.heightInTiles)
{
height = this.heightInTiles;
}
this._tempTileBlock = [];
for (var ty = y; ty < y + height; ty++)
{
for (var tx = x; tx < x + width; tx++)
{
if (collisionOnly)
if (this.mapData[y][x] > 0)
{
// We only want to consider the tile for checking if you can actually collide with it
if (this.mapData[ty] && this.mapData[ty][tx] && this.parent.tiles[this.mapData[ty][tx]].collideNone == false)
{
this._tempTileBlock.push({
x: tx,
y: ty,
tile: this.parent.tiles[this.mapData[ty][tx]]
});
}
args.push("background: rgb(50, 50, 50)");
}
else
{
if (this.mapData[ty] && this.mapData[ty][tx])
{
this._tempTileBlock.push({
x: tx,
y: ty,
tile: this.parent.tiles[this.mapData[ty][tx]]
});
}
args.push("background: rgb(0, 0, 0)");
}
}
}
},
/**
* Get the tile index of specific position (in tiles).
* @method getTileIndex
* @param {number} x - X position of the tile.
* @param {number} y - Y position of the tile.
* @return {number} Index of the tile at that position. Return null if there isn't a tile there.
*/
getTileIndex: function (x, y) {
if (y >= 0 && y < this.mapData.length)
{
if (x >= 0 && x < this.mapData[y].length)
{
return this.mapData[y][x];
}
txt += "\n";
}
return null;
},
/**
* Add a column of tiles into the layer.
* @method addColumn
* @param {string[]|number[]} column - An array of tile indexes to be added.
*/
addColumn: function (column) {
var data = [];
for (var c = 0; c < column.length; c++)
{
data[c] = parseInt(column[c]);
}
if (this.widthInTiles == 0)
{
this.widthInTiles = data.length;
this.widthInPixels = this.widthInTiles * this.tileWidth;
}
this.mapData.push(data);
this.heightInTiles++;
this.heightInPixels += this.tileHeight;
},
/**
* Description.
* @method createCanvas
*/
createCanvas: function () {
var width = this.game.width;
var height = this.game.height;
if (this.widthInPixels < width)
{
width = this.widthInPixels;
}
if (this.heightInPixels < height)
{
height = this.heightInPixels;
}
this.canvas = Phaser.Canvas.create(width, height);
this.context = this.canvas.getContext('2d');
this.baseTexture = new PIXI.BaseTexture(this.canvas);
this.texture = new PIXI.Texture(this.baseTexture);
this.sprite = new PIXI.Sprite(this.texture);
this.parent.addChild(this.sprite);
},
createQuadTree: function (width, height) {
this.quadTree = new Phaser.QuadTree(this, 0, 0, width, height, 20, 4);
},
/**
* Update boundsInTiles with widthInTiles and heightInTiles.
* @method updateBounds
*/
updateBounds: function () {
this.boundsInTiles.setTo(0, 0, this.widthInTiles, this.heightInTiles);
},
/**
* Parse tile offsets from map data.
* Basically this creates a large array of objects that contain the x/y coordinates to grab each tile from
* for the entire map. Yes we could calculate this at run-time by using the tile index and some math, but we're
* trading a quite small bit of memory here to not have to process that in our main render loop.
* @method parseTileOffsets
* @return {number} Length of tileOffsets array.
*/
parseTileOffsets: function () {
this.tileOffsets = [];
var i = 0;
if (this.mapFormat == Phaser.Tilemap.JSON)
{
// For some reason Tiled counts from 1 not 0
this.tileOffsets[0] = null;
i = 1;
}
for (var ty = this.tileMargin; ty < this.tileset.height; ty += (this.tileHeight + this.tileSpacing))
{
for (var tx = this.tileMargin; tx < this.tileset.width; tx += (this.tileWidth + this.tileSpacing))
{
this.tileOffsets[i] = {
x: tx,
y: ty
};
i++;
}
}
return this.tileOffsets.length;
args[0] = txt;
console.log.apply(console, args);
}
};
/**
* Get
* @return {Description}
*//**
* Set
* @param {Description} value - Description.
*/
Object.defineProperty(Phaser.TilemapLayer.prototype, 'alpha', {
get: function() {
return this._alpha;
},
set: function(value) {
if (this.sprite)
{
this.sprite.alpha = value;
}
this._alpha = value;
}
});

View file

@ -0,0 +1,120 @@
Phaser.TilemapParser = {
/**
* Parse a Sprite Sheet and extract the animation frame data from it.
*
* @method Phaser.AnimationParser.spriteSheet
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {string} key - The Game.Cache asset key of the Sprite Sheet image.
* @param {number} frameWidth - The fixed width of each frame of the animation.
* @param {number} frameHeight - The fixed height of each frame of the animation.
* @param {number} [frameMax=-1] - The total number of animation frames to extact from the Sprite Sheet. The default value of -1 means "extract all frames".
* @return {Phaser.FrameData} A FrameData object containing the parsed frames.
*/
tileset: function (game, key, tileWidth, tileHeight, tileMax) {
// How big is our image?
var img = game.cache.getTilesetImage(key);
if (img == null)
{
return null;
}
var width = img.width;
var height = img.height;
if (tileWidth <= 0)
{
tileWidth = Math.floor(-width / Math.min(-1, tileWidth));
}
if (tileHeight <= 0)
{
tileHeight = Math.floor(-height / Math.min(-1, tileHeight));
}
var row = Math.round(width / tileWidth);
var column = Math.round(height / tileHeight);
var total = row * column;
if (tileMax !== -1)
{
total = tileMax;
}
// Zero or smaller than tile sizes?
if (width == 0 || height == 0 || width < tileWidth || height < tileHeight || total === 0)
{
console.warn("Phaser.TilemapParser.tileSet: width/height zero or width/height < given tileWidth/tileHeight");
return null;
}
// Let's create some tiles
var x = 0;
var y = 0;
var tileset = new Phaser.Tileset(key, tileWidth, tileHeight);
for (var i = 0; i < total; i++)
{
tileset.addTile(new Phaser.Tile(tileset, i, x, y, tileWidth, tileHeight));
x += tileWidth;
if (x === width)
{
x = 0;
y += tileHeight;
}
}
return tileset;
},
/**
* Parse csv map data and generate tiles.
*
* @method Phaser.Tilemap.prototype.parseCSV
* @param {string} data - CSV map data.
* @param {string} key - Asset key for tileset image.
* @param {number} tileWidth - Width of its tile.
* @param {number} tileHeight - Height of its tile.
*/
parseCSV: function (data, key, tileWidth, tileHeight) {
// var layer = new Phaser.TilemapLayer(this, 0, key, Phaser.Tilemap.CSV, 'TileLayerCSV' + this.layers.length.toString(), tileWidth, tileHeight);
// Trim any rogue whitespace from the data
data = data.trim();
var rows = data.split("\n");
for (var i = 0; i < rows.length; i++)
{
var column = rows[i].split(",");
if (column.length > 0)
{
// layer.addColumn(column);
}
}
// layer.updateBounds();
// layer.createCanvas();
// var tileQuantity = layer.parseTileOffsets();
// this.currentLayer = layer;
// this.collisionLayer = layer;
// this.layers.push(layer);
// this.width = this.currentLayer.widthInPixels;
// this.height = this.currentLayer.heightInPixels;
// this.generateTiles(tileQuantity);
}
}

50
src/tilemap/Tileset.js Normal file
View file

@ -0,0 +1,50 @@
Phaser.Tileset = function (key, tileWidth, tileHeight) {
/**
* @property {string} key - The cache ID.
*/
this.key = key;
this.tilewidth = tileWidth;
this.tileHeight = tileHeight;
this._tiles = [];
}
Phaser.Tileset.prototype = {
addTile: function (tile) {
this._tiles.push(tile);
return tile;
},
getTile: function (index) {
if (this._tiles[index])
{
return this._tiles[index];
}
return null;
}
}
/**
* @name Phaser.Tileset#total
* @property {number} total - The total number of tiles in this Tileset.
* @readonly
*/
Object.defineProperty(Phaser.Tileset.prototype, "total", {
get: function () {
return this._ties.length;
}
});

View file

@ -203,13 +203,14 @@ Phaser.Tween.prototype = {
if (this._parent)
{
self = this._manager.create(this._object);
self._parent = this._parent;
this.chain(self);
this._lastChild.chain(self);
this._lastChild = self;
}
else
{
self = this;
self._parent = self;
this._parent = this;
this._lastChild = this;
}
self._repeat = repeat;
@ -229,9 +230,9 @@ Phaser.Tween.prototype = {
self._yoyo = yoyo;
if (autoStart) {
return self.start();
return this.start();
} else {
return self;
return this;
}
},
@ -399,11 +400,11 @@ Phaser.Tween.prototype = {
* .to({ y: 0 }, 1000, Phaser.Easing.Linear.None)
* .loop();
* @method Phaser.Tween#loop
* @return {Tween} Itself.
* @return {Phaser.Tween} Itself.
*/
loop: function() {
if (this._parent) this.chain(this._parent);
this._lastChild.chain(this);
return this;
},
@ -457,6 +458,7 @@ Phaser.Tween.prototype = {
*/
pause: function () {
this._paused = true;
this._pausedTime = this.game.time.now;
},
/**
@ -466,7 +468,7 @@ Phaser.Tween.prototype = {
*/
resume: function () {
this._paused = false;
this._startTime += this.game.time.pauseDuration;
this._startTime += (this.game.time.now - this._pausedTime);
},
/**