mirror of
https://github.com/photonstorm/phaser
synced 2025-02-16 22:18:29 +00:00
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:
parent
a7230aa769
commit
b868c2cb1b
72 changed files with 6704 additions and 6454 deletions
|
@ -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/)
|
||||
|
|
122
build/phaser.js
122
build/phaser.js
|
@ -9,7 +9,7 @@
|
|||
*
|
||||
* Phaser - http://www.phaser.io
|
||||
*
|
||||
* v1.0.7 - Built at: Wed, 09 Oct 2013 17:16:16 +0100
|
||||
* v1.0.7 - Built at: Thu, 10 Oct 2013 16:51:46 +0100
|
||||
*
|
||||
* By Richard Davey http://www.photonstorm.com @photonstorm
|
||||
*
|
||||
|
@ -7340,10 +7340,21 @@ Phaser.Camera.prototype = {
|
|||
break;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Move the camera focus on a display object instantly.
|
||||
* @method Phaser.Camera#focusOn
|
||||
* @param {any} displayObject - The display object to focus the camera on. Must have visible x/y properties.
|
||||
*/
|
||||
focusOn: function (displayObject) {
|
||||
|
||||
this.setPosition(Math.round(displayObject.x - this.view.halfWidth), Math.round(displayObject.y - this.view.halfHeight));
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Move the camera focus to a location instantly.
|
||||
* Move the camera focus on a location instantly.
|
||||
* @method Phaser.Camera#focusOnXY
|
||||
* @param {number} x - X position.
|
||||
* @param {number} y - Y position.
|
||||
|
@ -16007,6 +16018,7 @@ Phaser.Sprite.prototype.preUpdate = function() {
|
|||
this.prevY = this.y;
|
||||
|
||||
this.updateCache();
|
||||
this.updateAnimation();
|
||||
|
||||
// Re-run the camera visibility check
|
||||
if (this._cache.dirty)
|
||||
|
@ -16065,7 +16077,10 @@ Phaser.Sprite.prototype.updateCache = function() {
|
|||
this._cache.dirty = true;
|
||||
}
|
||||
|
||||
// Frame updated?
|
||||
}
|
||||
|
||||
Phaser.Sprite.prototype.updateAnimation = function() {
|
||||
|
||||
if (this.currentFrame && this.currentFrame.uuid != this._cache.frameID)
|
||||
{
|
||||
this._cache.frameWidth = this.texture.frame.width;
|
||||
|
@ -16119,6 +16134,47 @@ Phaser.Sprite.prototype.postUpdate = function() {
|
|||
|
||||
}
|
||||
|
||||
Phaser.Sprite.prototype.loadTexture = function (key, frame) {
|
||||
|
||||
this.key = key;
|
||||
|
||||
if (key instanceof Phaser.RenderTexture)
|
||||
{
|
||||
this.currentFrame = this.game.cache.getTextureFrame(key.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (key == null || this.game.cache.checkImageKey(key) == false)
|
||||
{
|
||||
key = '__default';
|
||||
}
|
||||
|
||||
if (this.game.cache.isSpriteSheet(key))
|
||||
{
|
||||
this.animations.loadFrameData(this.game.cache.getFrameData(key));
|
||||
|
||||
if (frame !== null)
|
||||
{
|
||||
if (typeof frame === 'string')
|
||||
{
|
||||
this.frameName = frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.frame = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentFrame = this.game.cache.getFrame(key);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateAnimation();
|
||||
|
||||
}
|
||||
|
||||
Phaser.Sprite.prototype.deltaAbsX = function () {
|
||||
return (this.deltaX() > 0 ? this.deltaX() : -this.deltaX());
|
||||
}
|
||||
|
@ -16430,6 +16486,18 @@ Object.defineProperty(Phaser.Sprite.prototype, "inCamera", {
|
|||
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Object.defineProperty(Phaser.Sprite.prototype, "worldX", {
|
||||
|
||||
get: function () {
|
||||
return 1;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the input enabled state of this Sprite.
|
||||
* @returns {Description}
|
||||
|
@ -22959,6 +23027,7 @@ Phaser.Tween.prototype = {
|
|||
*/
|
||||
pause: function () {
|
||||
this._paused = true;
|
||||
this._pausedTime = this.game.time.now;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -22968,7 +23037,7 @@ Phaser.Tween.prototype = {
|
|||
*/
|
||||
resume: function () {
|
||||
this._paused = false;
|
||||
this._startTime += this.game.time.pauseDuration;
|
||||
this._startTime += (this.game.time.now - this._pausedTime);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -29081,6 +29150,7 @@ Phaser.Utils.Debug.prototype = {
|
|||
this.line('angle: ' + sprite.angle.toFixed(1) + ' rotation: ' + sprite.rotation.toFixed(1));
|
||||
this.line('visible: ' + sprite.visible + ' in camera: ' + sprite.inCamera);
|
||||
this.line('body x: ' + sprite.body.x.toFixed(1) + ' y: ' + sprite.body.y.toFixed(1));
|
||||
this.stop();
|
||||
|
||||
// 0 = scaleX
|
||||
// 1 = skewY
|
||||
|
@ -29131,6 +29201,7 @@ Phaser.Utils.Debug.prototype = {
|
|||
this.line('scaleY: ' + sprite.worldTransform[4]);
|
||||
this.line('transX: ' + sprite.worldTransform[2]);
|
||||
this.line('transY: ' + sprite.worldTransform[5]);
|
||||
this.stop();
|
||||
|
||||
},
|
||||
|
||||
|
@ -29160,6 +29231,49 @@ Phaser.Utils.Debug.prototype = {
|
|||
this.line('scaleY: ' + sprite.localTransform[4]);
|
||||
this.line('transX: ' + sprite.localTransform[2]);
|
||||
this.line('transY: ' + sprite.localTransform[5]);
|
||||
this.stop();
|
||||
|
||||
},
|
||||
|
||||
renderSpriteCoords: function (sprite, x, y, color) {
|
||||
|
||||
if (this.context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
color = color || 'rgb(255, 255, 255)';
|
||||
|
||||
this.start(x, y, color);
|
||||
|
||||
this.line(sprite.name);
|
||||
this.line('x: ' + sprite.x);
|
||||
this.line('y: ' + sprite.y);
|
||||
this.line('local x: ' + sprite.localTransform[2]);
|
||||
this.line('local y: ' + sprite.localTransform[5]);
|
||||
this.line('world x: ' + sprite.worldTransform[2]);
|
||||
this.line('world y: ' + sprite.worldTransform[5]);
|
||||
|
||||
this.stop();
|
||||
|
||||
},
|
||||
|
||||
renderGroupInfo: function (group, x, y, color) {
|
||||
|
||||
if (this.context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
color = color || 'rgb(255, 255, 255)';
|
||||
|
||||
this.start(x, y, color);
|
||||
|
||||
this.line('Group (size: ' + group.length + ')');
|
||||
this.line('x: ' + group.x);
|
||||
this.line('y: ' + group.y);
|
||||
|
||||
this.stop();
|
||||
|
||||
},
|
||||
|
||||
|
|
|
@ -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)');
|
||||
|
|
|
@ -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>
|
||||
|
|
74
examples/tilemaps/wip1.php
Normal file
74
examples/tilemaps/wip1.php
Normal 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');
|
||||
?>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
})();
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
43
src/physics/advanced/collision/Broadphase.js
Normal file
43
src/physics/advanced/collision/Broadphase.js
Normal 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;
|
||||
};
|
159
src/physics/advanced/collision/GridBroadphase.js
Normal file
159
src/physics/advanced/collision/GridBroadphase.js
Normal 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;
|
||||
};
|
47
src/physics/advanced/collision/NaiveBroadphase.js
Normal file
47
src/physics/advanced/collision/NaiveBroadphase.js
Normal 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;
|
||||
};
|
1249
src/physics/advanced/collision/Nearphase.js
Normal file
1249
src/physics/advanced/collision/Nearphase.js
Normal file
File diff suppressed because it is too large
Load diff
376
src/physics/advanced/collision/QuadTree.js
Normal file
376
src/physics/advanced/collision/QuadTree.js
Normal 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);
|
||||
}
|
||||
|
120
src/physics/advanced/collision/SAP1DBroadphase.js
Normal file
120
src/physics/advanced/collision/SAP1DBroadphase.js
Normal 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;
|
||||
};
|
42
src/physics/advanced/constraints/Constraint.js
Normal file
42
src/physics/advanced/constraints/Constraint.js
Normal 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!");
|
||||
};*/
|
136
src/physics/advanced/constraints/ContactEquation.js
Normal file
136
src/physics/advanced/constraints/ContactEquation.js
Normal 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;
|
||||
};
|
||||
|
62
src/physics/advanced/constraints/DistanceConstraint.js
Normal file
62
src/physics/advanced/constraints/DistanceConstraint.js
Normal 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;
|
||||
};
|
77
src/physics/advanced/constraints/Equation.js
Normal file
77
src/physics/advanced/constraints/Equation.js
Normal 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;
|
||||
};
|
180
src/physics/advanced/constraints/FrictionEquation.js
Normal file
180
src/physics/advanced/constraints/FrictionEquation.js
Normal 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;
|
||||
};
|
94
src/physics/advanced/constraints/PointToPointConstraint.js
Normal file
94
src/physics/advanced/constraints/PointToPointConstraint.js
Normal 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;
|
||||
};
|
83
src/physics/advanced/constraints/PrismaticConstraint.js
Normal file
83
src/physics/advanced/constraints/PrismaticConstraint.js
Normal 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);
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
|
84
src/physics/advanced/events/EventEmitter.js
Normal file
84
src/physics/advanced/events/EventEmitter.js
Normal 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;
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}*/
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
81
src/physics/advanced/material/ContactMaterial.js
Normal file
81
src/physics/advanced/material/ContactMaterial.js
Normal 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;
|
||||
};
|
19
src/physics/advanced/material/Material.js
Normal file
19
src/physics/advanced/material/Material.js
Normal 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++;
|
||||
};
|
10
src/physics/advanced/math/mat2.js
Normal file
10
src/physics/advanced/math/mat2.js
Normal 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;
|
477
src/physics/advanced/math/polyk.js
Normal file
477
src/physics/advanced/math/polyk.js
Normal 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;
|
122
src/physics/advanced/math/vec2.js
Normal file
122
src/physics/advanced/math/vec2.js
Normal 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;
|
330
src/physics/advanced/objects/Body.js
Normal file
330
src/physics/advanced/objects/Body.js
Normal 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;
|
181
src/physics/advanced/objects/Spring.js
Normal file
181
src/physics/advanced/objects/Spring.js
Normal 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;
|
||||
};
|
37
src/physics/advanced/p2.js
Normal file
37
src/physics/advanced/p2.js
Normal 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,
|
||||
};
|
|
@ -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);
|
||||
}
|
39
src/physics/advanced/shapes/Capsule.js
Normal file
39
src/physics/advanced/shapes/Capsule.js
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
|
|
213
src/physics/advanced/shapes/Convex.js
Normal file
213
src/physics/advanced/shapes/Convex.js
Normal 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);
|
||||
};
|
||||
|
30
src/physics/advanced/shapes/Line.js
Normal file
30
src/physics/advanced/shapes/Line.js
Normal 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;
|
||||
};
|
||||
|
22
src/physics/advanced/shapes/Particle.js
Normal file
22
src/physics/advanced/shapes/Particle.js
Normal 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;
|
||||
};
|
||||
|
22
src/physics/advanced/shapes/Plane.js
Normal file
22
src/physics/advanced/shapes/Plane.js
Normal 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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
43
src/physics/advanced/shapes/Rectangle.js
Normal file
43
src/physics/advanced/shapes/Rectangle.js
Normal 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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
90
src/physics/advanced/shapes/Shape.js
Normal file
90
src/physics/advanced/shapes/Shape.js
Normal 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...");
|
||||
};
|
|
@ -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);
|
||||
}
|
190
src/physics/advanced/solver/GSSolver.js
Normal file
190
src/physics/advanced/solver/GSSolver.js
Normal 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;
|
||||
};
|
||||
|
81
src/physics/advanced/solver/Island.js
Normal file
81
src/physics/advanced/solver/Island.js
Normal 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});
|
||||
};
|
159
src/physics/advanced/solver/IslandSolver.js
Normal file
159
src/physics/advanced/solver/IslandSolver.js
Normal 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);
|
||||
}
|
||||
};
|
65
src/physics/advanced/solver/Solver.js
Normal file
65
src/physics/advanced/solver/Solver.js
Normal 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;
|
||||
};
|
||||
|
25
src/physics/advanced/utils/Utils.js
Normal file
25
src/physics/advanced/utils/Utils.js
Normal 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]);
|
||||
}
|
||||
}
|
||||
};
|
870
src/physics/advanced/world/World.js
Normal file
870
src/physics/advanced/world/World.js
Normal 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
190
src/tilemap/Tile.js
Normal 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;
|
||||
}
|
||||
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
120
src/tilemap/TilemapParser.js
Normal file
120
src/tilemap/TilemapParser.js
Normal 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
50
src/tilemap/Tileset.js
Normal 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;
|
||||
}
|
||||
|
||||
});
|
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue