/**
*
* @module Kiwi
* @submodule Components
*
*/
module Kiwi.Components {
/**
* Arcade Physics is an Optional Component that can be used when you are wanting to do basic physics collisions.
* These have been ported from Flixel, so most function operate identically to the original flixel functions, though some
* have been split into multiple functions. Generally where functions originally accepted
* either groups or gameobjects within the same argument, the ported functions one or the other.
* http://www.flixel.org/
* http://www.adamatomic.com/
*
* @class ArcadePhysics
* @constructor
* @namespace Kiwi.Components
* @param entity {Kiwi.Entity} The Entity that this ArcadePhysics should be used on.
* @param box {Kiwi.Components.Box} The box component that holds the hitbox that should be used when resolving and calculating collisions.
* @return {Kiwi.Components.ArcadePhysics}
* @extends Kiwi.Component
*
* @author Adam 'Atomic' Saltsman, Flixel
*
*/
export class ArcadePhysics extends Kiwi.Component {
constructor(entity:Kiwi.Entity, box?: Kiwi.Components.Box) {
super(entity,'ArcadePhysics');
this.parent = entity;
this.box = box;
this.transform = this.parent.transform;
this.last = new Kiwi.Geom.Point(this.transform.worldX, this.transform.worldY);
this.mass = 1.0;
this.elasticity = 0.0;
this.immovable = false;
this.moves = true;
this.touching = ArcadePhysics.NONE;
this.wasTouching = ArcadePhysics.NONE;
this.allowCollisions = ArcadePhysics.ANY;
this.velocity = new Kiwi.Geom.Point();
this.acceleration = new Kiwi.Geom.Point();
this.drag = new Kiwi.Geom.Point();
this.maxVelocity = new Kiwi.Geom.Point(10000, 10000);
this.angularVelocity = 0;
this.angularAcceleration = 0;
this.angularDrag = 0;
this.maxAngular = 10000;
}
/**
* The transform component of the entity that the ArcadePhysics is a part of.
* @property transform
* @type Kiwi.Geom.Transform
* @public
*/
public transform: Kiwi.Geom.Transform;
/**
* The bounding box component that the collisions are going to be based off.
* You can modify the 'hitbox' of that component to modify the collision area.
*
* @property box
* @type Kiwi.Components.Box
* @public
*/
public box: Kiwi.Components.Box;
/**
* Whether an object will move/alter position after a collision.
* @property immovable
* @type boolean
* @public
*/
public immovable: boolean;
/**
* The basic speed of this object.
* You can modify the values contained inside this Object to change the speed.
*
* @property velocity
* @type Kiwi.Geom.Point
* @public
*/
public velocity: Kiwi.Geom.Point;
/**
* The virtual mass of the object. Default value is 1.
* Currently only used with <code>elasticity</code> during collision resolution.
* Change at your own risk; effects seem crazy unpredictable so far!
* @property mass
* @type number
* @public
*/
public mass: number;
/**
* The bounciness of this object. Only affects collisions. Default value is 0, or "not bouncy at all."
* @property elasticity
* @type number
* @public
*/
public elasticity: number;
/**
* How fast the speed of this object is changing.
* Useful for smooth movement and gravity.
*
* @property acceleration
* @type Kiwi.Geom.Point
* @public
*/
public acceleration: Kiwi.Geom.Point;
/**
* This isn't drag exactly, more like deceleration that is only applied
* when acceleration is not affecting the sprite.
* @property drag
* @type Kiwi.Geom.Point
* @public
*/
public drag: Kiwi.Geom.Point;
/**
* If you are using <code>acceleration</code>, you can use <code>maxVelocity</code> with it
* to cap the speed automatically (very useful!).
* @property maxVelocity
* @type Kiwi.Geom.Point
* @public
*/
public maxVelocity: Kiwi.Geom.Point;
/**
* This is how fast you want this sprite to spin.
* @property angularVelocity
* @type number
* @public
*/
public angularVelocity: number;
/**
* How fast the spin speed should change.
* @property angularAcceleration
* @type number
* @public
*/
public angularAcceleration: number;
/**
* Like <code>drag</code> but for spinning.
* @property angularDrag
* @type number
* @public
*/
public angularDrag: number;
/**
* Use in conjunction with <code>angularAcceleration</code> for fluid spin speed control.
* @property maxAngular
* @type number
* @public
*/
public maxAngular: number;
/**
* If the Entity that this component is a part of 'moves' or not, and thus if the physics should update the motion should update each frame.
* @property moves
* @type boolean
* @default true
* @public
*/
public moves: boolean;
/**
* Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating surface contacts.
* Use bitwise operators to check the values stored here, or use touching(), justStartedTouching(), etc.
* You can even use them broadly as boolean values if you're feeling saucy!
* @property touching
* @type number
* @public
*/
public touching: number;
/**
* Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating surface contacts from the previous game loop step.
* Use bitwise operators to check the values stored here, or use isTouching().
* You can even use them broadly as boolean values if you're feeling saucy!
* @property wasTouching
* @type number
* @public
*/
public wasTouching: number;
/**
* Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating collision directions.
* Use bitwise operators to check the values stored here.
* Useful for things like one-way platforms (e.g. allowCollisions = UP)
* The accessor "solid" just flips this variable between NONE and ANY.
* @property allowCollisions
* @type number
* @public
*/
public allowCollisions: number;
/**
* Important variable for collision processing.
* Tracks the last location of the Entity. This is set during the time this method 'updates'.
* @property last
* @type Kiwi.Geom.Point
* @public
*/
public last: Kiwi.Geom.Point;
/**
* A boolean to indicate if this object is solid or not.
* @property _solid
* @type boolean
* @private
*/
private _solid: boolean;
/**
* A function that is to execute when this object overlaps with another.
* @property _callbackFunction
* @type Function
* @default null
* @private
*/
private _callbackFunction: any = null;
/**
* The context that the callback method should have when it executes.
* @property _callbackContext
* @type Any
* @private
*/
private _callbackContext: any = null;
/**
* Returns a boolean indicating whether the or not the object is currently colliding on a particular side that is passed.
* Use the collision constants (like LEFT, FLOOR, e.t.c) when passing sides.
* @method isTouching
* @param value [number] The collision constant of the side you want to check against.
* @return {boolean} If the Object is currently colliding on that side or not.
* @public
*/
public isTouching(value: number): boolean {
return (this.touching & value) == value;
}
/**
* Whether the object should collide with other objects or not.
* For more control over what directions the object will collide from, use collision constants (like LEFT, FLOOR, etc)
* and set the value of allowCollisions directly.
* @method solid
* @param [value] {boolean} If left empty, this will then just toggle between ANY and NONE.
* @return {boolean} If Object is currently solid or not.
* @public
*/
public solid(value?: boolean): boolean {
if (value !== undefined) {
if (value)
this.allowCollisions = ArcadePhysics.ANY;
else
this.allowCollisions = ArcadePhysics.NONE;
}
return (this.allowCollisions & ArcadePhysics.ANY) > ArcadePhysics.NONE;
}
/**
* Sets up a callback function that will run when this object overlaps with another.
* When the method is dispatched it will have TWO arguments.
* One - The parent / entity of this ArcadePhysics.
* Two - The GameObject that the collision occured with.
*
* @method setCallback
* @param callbackFunction {Function} The method that is to be executed whe a overlap occurs.
* @param callbackContext {Any} The context that the method is to be called in.
* @public
*/
public setCallback(callbackFunction, callbackContext) {
this._callbackFunction = callbackFunction;
this._callbackContext = callbackContext;
}
/**
* Returns the parent of this entity. Mainly used for executing callbacks.
* @property parent
* @type Kiwi.Entity
* @public
*/
public parent: Kiwi.Entity;
/**
* Sets the parent's rotation to be equal to the trajectory of the
* velocity of the physics component. Note that rotation 0 corresponds
* to pointing directly to the right.
* @method rotateToVelocity
* @return {number} New rotation value
* @public
* @since 1.3.0
*/
public rotateToVelocity (): number {
var result: number = Math.atan2( this.velocity.y, this.velocity.x );
this.transform.rotation = result;
return result;
}
/*
*---------------
* Seperation Code
*---------------
*/
/**
* A static method for seperating two normal GameObjects on both the X and Y Axis's.
* Both objects need to have both an ArcadePhysics Component and a Box component in order for the separate process to succeed.
* This method is not recommended to be directly used but instead use a 'collide/overlaps' method instead.
*
* @method seperate
* @static
* @param object1 {Kiwi.Entity} The first GameObject you would like to seperate.
* @param object2 {Kiwi.Entity} The second GameObject you would like to seperate from the first.
* @return {boolean}
* @public
*/
public static separate(object1: Kiwi.Entity, object2: Kiwi.Entity): boolean {
var separatedX: boolean = this.separateX(object1, object2);
var separatedY: boolean = this.separateY(object1, object2);
return separatedX || separatedY;
}
/**
* Separates two passed GameObjects on the x-axis.
* Both objects need to have both an ArcadePhysics Component and a Box component in order for the separate process to succeed.
* This method is not recommended to be directly used but instead use a 'collide/overlaps' method instead.
*
* @method seperateX
* @param object1 {Kiwi.Entity} The first GameObject.
* @param object2 {Kiwi.Entity} The second GameObject.
* @return {boolean} Whether the objects in fact touched and were separated along the X axis.
* @static
* @public
*/
public static separateX(object1: Kiwi.Entity, object2: Kiwi.Entity): boolean {
//Get the Physics Components.
var phys1: ArcadePhysics = <ArcadePhysics>object1.components.getComponent("ArcadePhysics");
var phys2: ArcadePhysics = <ArcadePhysics>object2.components.getComponent("ArcadePhysics");
//Can they even be sseparatated? two immovable objects
if (phys1.immovable && phys2.immovable)
return false;
//First, get the two object deltas
var overlap: number = 0;
var obj1delta: number = phys1.transform.worldX - phys1.last.x;
var obj2delta: number = phys2.transform.worldX - phys2.last.x;
//Are they the same?
if (obj1delta == obj2delta) return false;
//Check if the X hulls actually overlap
var obj1deltaAbs: number = (obj1delta > 0) ? obj1delta : -obj1delta;
var obj2deltaAbs: number = (obj2delta > 0) ? obj2delta : -obj2delta;
//Get the world hitboxes.
var box1 = phys1.box.worldHitbox;
var box2 = phys2.box.worldHitbox;
//Where they are now using previous y axis's.
var obj1rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(box1.x - ((obj1delta > 0) ? obj1delta : 0), phys1.last.y + phys1.box.hitboxOffset.y, box1.width + ((obj1delta > 0) ? obj1delta : -obj1delta), box1.height);
var obj2rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(box2.x - ((obj2delta > 0) ? obj2delta : 0), phys2.last.y + phys2.box.hitboxOffset.y, box2.width + ((obj2delta > 0) ? obj2delta : -obj2delta), box2.height);
//Could also use Kiwi.Geom.Intersect.rectangleToRectangle here...
if ((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height)) {
var maxOverlap: number = obj1deltaAbs + obj2deltaAbs + ArcadePhysics.OVERLAP_BIAS;
if (obj1delta > obj2delta) {
overlap = box1.x + box1.width - box2.x;
if ((overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.RIGHT) || !(phys2.allowCollisions & ArcadePhysics.LEFT)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.RIGHT;
phys2.touching |= ArcadePhysics.LEFT;
}
} else {
overlap = box1.x - box2.width - box2.x;
if ((-overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.LEFT) || !(phys2.allowCollisions & ArcadePhysics.RIGHT)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.LEFT;
phys2.touching |= ArcadePhysics.RIGHT;
}
}
}
if (overlap != 0) {
//Get the average velocity
var obj1v: number = phys1.velocity.x;
var obj2v: number = phys2.velocity.x;
if (!phys1.immovable && !phys2.immovable) {
overlap *= 0.5;
phys1.transform.x = phys1.transform.x - overlap;
phys2.transform.x = phys2.transform.x + overlap;
var obj1velocity: number = Math.sqrt((obj2v * obj2v * phys2.mass) / phys1.mass) * ((obj2v > 0) ? 1 : -1);
var obj2velocity: number = Math.sqrt((obj1v * obj1v * phys1.mass) / phys2.mass) * ((obj1v > 0) ? 1 : -1);
var average: number = (obj1velocity + obj2velocity) * 0.5;
obj1velocity -= average;
obj2velocity -= average;
phys1.velocity.x = average + obj1velocity * phys1.elasticity;
phys2.velocity.x = average + obj2velocity * phys2.elasticity;
} else if (!phys1.immovable) {
phys1.transform.x = phys1.transform.x - overlap;
phys1.velocity.x = obj2v - obj1v * phys1.elasticity;
} else if (!phys2.immovable) {
phys2.transform.x = phys2.transform.x + overlap;
phys2.velocity.x = obj1v - obj2v * phys2.elasticity;
}
return true;
}
return false;
}
/**
* Separates two GameObject on the y-axis. This method is executed from the 'separate' method.
* Both objects need to have both an ArcadePhysics Component and a Box component in order for the separate process to succeed.
* This method is not recommended to be directly used but instead use a 'collide/overlaps' method instead.
*
* @method seperateY
* @param object1 {Kiwi.Entity} The first GameObject.
* @param object2 {Kiwi.Entity} The second GameObject.
* @return {boolean} Whether the objects in fact touched and were separated along the Y axis.
* @static
* @public
*/
public static separateY(object1: Kiwi.Entity, object2: Kiwi.Entity): boolean {
//Get the Arcade Physics Components
var phys1: ArcadePhysics = <ArcadePhysics>object1.components.getComponent("ArcadePhysics");
var phys2: ArcadePhysics = <ArcadePhysics>object2.components.getComponent("ArcadePhysics");
//Can't separate two immovable objects
if (phys1.immovable && phys2.immovable) return false;
var overlap: number = 0;
var obj1delta: number = phys1.transform.worldY - phys1.last.y;
var obj2delta: number = phys2.transform.worldY - phys2.last.y;
//Do the deltas match?
if (obj1delta == obj2delta) return false;
//Absolute Deltas
var obj1deltaAbs: number = (obj1delta > 0) ? obj1delta : -obj1delta;
var obj2deltaAbs: number = (obj2delta > 0) ? obj2delta : -obj2delta;
//Hitboxes
var box1 = phys1.box.worldHitbox;
var box2 = phys2.box.worldHitbox;
//Rectangles
var obj1rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(box1.x, box1.y - ((obj1delta > 0) ? obj1delta : 0), box1.width, box1.height + obj1deltaAbs);
var obj2rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(box2.x, box2.y - ((obj2delta > 0) ? obj2delta : 0), box2.width, box2.height + obj2deltaAbs);
//Check for overlap
if ((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height)) {
var maxOverlap: number = obj1deltaAbs + obj2deltaAbs + ArcadePhysics.OVERLAP_BIAS;
if (obj1delta > obj2delta) {
overlap = box1.y + box1.height - box2.y;
if ((overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.DOWN) || !(phys2.allowCollisions & ArcadePhysics.UP)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.DOWN;
phys2.touching |= ArcadePhysics.UP;
}
} else {
overlap = box1.y - box2.height - box2.y;
if ((-overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.UP) || !(phys2.allowCollisions & ArcadePhysics.DOWN)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.UP;
phys2.touching |= ArcadePhysics.DOWN;
}
}
//Then adjust their positions and velocities accordingly (if there was any overlap)
if (overlap != 0) {
var obj1v: number = phys1.velocity.y;
var obj2v: number = phys2.velocity.y;
if (!phys1.immovable && !phys2.immovable) {
overlap *= 0.5;
phys1.transform.y = phys1.transform.y - overlap;
phys2.transform.y = phys2.transform.y + overlap;
var obj1velocity: number = Math.sqrt((obj2v * obj2v * phys2.mass) / phys1.mass) * ((obj2v > 0) ? 1 : -1);
var obj2velocity: number = Math.sqrt((obj1v * obj1v * phys1.mass) / phys2.mass) * ((obj1v > 0) ? 1 : -1);
var average: number = (obj1velocity + obj2velocity) * 0.5;
obj1velocity -= average;
obj2velocity -= average;
phys1.velocity.y = average + obj1velocity * phys1.elasticity;
phys2.velocity.y = average + obj2velocity * phys2.elasticity;
}
else if (!phys1.immovable) {
phys1.transform.y = phys1.transform.y - overlap;
phys1.velocity.y = obj2v - obj1v * phys1.elasticity;
//This is special case code that handles cases like horizontal moving platforms you can ride
if (object2.active && phys2.moves && (obj1delta > obj2delta))
phys1.transform.x = phys1.transform.worldX + object2.transform.worldX - phys2.last.x;
} else if (!phys2.immovable) {
phys2.transform.y = phys2.transform.y + overlap;
phys2.velocity.y = obj1v - obj2v * phys2.elasticity;
//This is special case code that handles cases like horizontal moving platforms you can ride
if (object1.active && phys1.moves && (obj1delta < obj2delta))
phys2.transform.x = phys2.transform.worldX + object1.transform.worldX - phys1.last.x;
}
return true;
}
}
return false;
}
/*
*---------------
* Seperation of Tiles Methods
*---------------
*/
/**
* Separates a GameObject from a series of passed Tiles that lie on a TileMapLayer.
* The gameobject needs to have a Box Component and an ArcadePhysics Component.
* This method is not recommended to be directly used but instead use the 'overlapsTiles' method instead.
*
* @method separateTiles
* @param object {Kiwi.Entity} The GameObject you are wanting to separate from a tile.
* @param layer {Kiwi.GameObjects.Tilemap.TileMapLayer} The TileMapLayer that the tiles belong on.
* @param tiles {Array}
* @return {Boolean} If any separation occured.
* @public
* @static
*/
public static separateTiles(object: Entity, layer: Kiwi.GameObjects.Tilemap.TileMapLayer, tiles:any):boolean {
//Physics Component Found?
if (object.components.hasComponent("ArcadePhysics") == false) return false;
//Immovable?
if (object.components.getComponent("ArcadePhysics").immovable) return false;
var sepX = false;
var sepY = false;
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
if(!sepX) sepX = this.separateTilesX(object, layer, tile);
if(!sepY) sepY = this.separateTilesY(object, layer, tile);
}
return sepX || sepY;
}
/**
* Separates a GameObjects from an Array of Tiles on the x-axis.
* @method separateTilesX
* @param object {Kiwi.Entity} The GameObject you are wanting to separate from a tile.
* @param layer {Kiwi.GameObjects.Tilemap.TileMapLayer} The TileMapLayer that the tiles belong on.
* @param tile {Object} An Object containing the information (x/y/tiletypr) about the tile that is being overlapped.
* @return {Boolean} If any separation occured.
* @public
* @static
*/
public static separateTilesX(object: Entity, layer: Kiwi.GameObjects.Tilemap.TileMapLayer, tile):boolean {
//Get Physics
var phys1: ArcadePhysics = <ArcadePhysics>object.components.getComponent("ArcadePhysics");
var phys2: ArcadePhysics = <ArcadePhysics>layer.components.getComponent("ArcadePhysics");
//First, get the two object deltas
var obj1delta: number = phys1.transform.worldX - phys1.last.x;
var obj2delta: number = phys2.transform.worldX - phys2.last.x;
//If they moved the same amount.
if (obj1delta == obj2delta) return false;
//Absolute Delta and Overlap
var obj1deltaAbs: number = (obj1delta > 0) ? obj1delta : -obj1delta;
var obj2deltaAbs: number = (obj2delta > 0) ? obj2delta : -obj2delta;
var overlap = 0;
var maxOverlap: number = obj1deltaAbs + obj2deltaAbs + ArcadePhysics.OVERLAP_BIAS;
//Quick References
var box1 = phys1.box.worldHitbox;
var tileTypes = layer.tilemap.tileTypes;
var tData = layer.tileData;
//Box of the GameObject
var x = phys2.transform.worldX + tile.x;
var obj1rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(box1.x - ((obj1delta > 0) ? obj1delta : 0), phys1.last.y + phys1.box.hitboxOffset.y, box1.width + ((obj1delta > 0) ? obj1delta : -obj1delta), box1.height);
var obj2rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(x - ((obj2delta > 0) ? obj2delta : 0), phys2.last.y + tile.y, layer.tileWidth + ((obj2delta > 0) ? obj2delta : -obj2delta), layer.tileHeight);
//Check to see if they overlap
if ((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y - 1 + obj1rect.height > obj2rect.y) && (obj1rect.y - 1 < obj2rect.y + obj2rect.height)) {
//Which way the delta is going
if (obj1delta > obj2delta) {
overlap = box1.x + box1.width - x;
if ((overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.RIGHT) || !(tileTypes[tData[tile.index]].allowCollisions & ArcadePhysics.LEFT)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.RIGHT;
}
} else {
overlap = box1.x - layer.tileWidth - x;
if ((-overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.LEFT) || !(tileTypes[tData[tile.index]].allowCollisions & ArcadePhysics.RIGHT)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.LEFT;
}
}
//Resolve the Collision
if (overlap != 0) {
var obj1v: number = phys1.velocity.x;
var obj2v: number = phys2.velocity.x;
phys1.transform.x = phys1.transform.x - overlap;
phys1.velocity.x = obj2v - obj1v * phys1.elasticity;
return true;
}
}
return false;
}
/**
* Separates a GameObject from a tiles on the y-axis.
* @method separateTilesY
* @param object {Kiwi.Entity} The GameObject you are wanting to separate from a tile.
* @param layer {Kiwi.GameObjects.Tilemap.TileMapLayer} The TileMapLayer that the tiles belong on.
* @param tiles {Object} An Object representing the Tile which we are checking to see any overlaps occurs.
* @return {Boolean} If any separation occured.
* @public
* @static
*/
public static separateTilesY(object:Entity, layer, tile):boolean {
//Get the physics.
var phys1: ArcadePhysics = <ArcadePhysics>object.components.getComponent("ArcadePhysics");
var phys2: ArcadePhysics = <ArcadePhysics>layer.components.getComponent("ArcadePhysics");
//First, get the two object deltas
var obj1delta: number = phys1.transform.worldY - phys1.last.y;
var obj2delta: number = phys2.transform.worldY - phys2.last.y;
//Have they moved the same amount?
if (obj1delta == obj2delta) return false;
//Absolute Delta and Max Overlap
var obj1deltaAbs: number = (obj1delta > 0) ? obj1delta : -obj1delta;
var obj2deltaAbs: number = (obj2delta > 0) ? obj2delta : -obj2delta;
var overlap: number = 0;
var maxOverlap: number = obj1deltaAbs + obj2deltaAbs + ArcadePhysics.OVERLAP_BIAS;
var box1 = phys1.box.worldHitbox;
var tileTypes = layer.tilemap.tileTypes;
var tData = layer.tileData;
var y = layer.transform.worldY + tile.y;
//Rectangles
var obj1rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(box1.x, box1.y - ((obj1delta > 0) ? obj1delta : 0), box1.width, box1.height + obj1deltaAbs);
var obj2rect: Kiwi.Geom.Rectangle = new Kiwi.Geom.Rectangle(phys2.transform.worldX + tile.x, y - ((obj2delta > 0) ? obj2delta : 0), layer.tileWidth, layer.tileHeight + obj2deltaAbs);
//Check if they overlap
if ((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height)) {
if (obj1delta > obj2delta) {
overlap = box1.y + box1.height - y;
if ((overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.DOWN) || !(tileTypes[tData[tile.index]].allowCollisions & ArcadePhysics.UP)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.DOWN;
}
} else {
overlap = box1.y - layer.tileHeight - y;
if ((-overlap > maxOverlap) || !(phys1.allowCollisions & ArcadePhysics.UP) || !(tileTypes[tData[tile.index]].allowCollisions & ArcadePhysics.DOWN)) {
overlap = 0;
} else {
phys1.touching |= ArcadePhysics.UP;
}
}
//Resolve the Collision
if (overlap != 0) {
var obj1v: number = phys1.velocity.y;
var obj2v: number = phys2.velocity.y;
phys1.transform.y = phys1.transform.y - overlap;
phys1.velocity.y = obj2v - obj1v * phys1.elasticity;
return true;
}
}
return false;
}
/*
*---------------
* Instance Functions
*---------------
*/
/**
* A method to check to see if any Tiles with in this parent TileMapLayer overlaps with a GameObject passed.
* If seperateObjects is true it will seperate the two entities based on their bounding box.
* ONLY works if the parent of the ArcadePhysics component which is calling this method is a TileMapLayer.
* Note: The GameObject passed must contain a box component and only if you want to separate the two objects must is ALSO contain an ArcadePhysics component.
*
* @method overlapsTiles
* @param gameObject {Kiwi.Entity} The GameObject you would like to separate with this one.
* @param [separateObjects=false] {Boolean} If you want the GameObject to be separated from any tile it collides with.
* @param [collisionType=ANY] {Number} If you want the GameObject to only check for collisions from a particular side of tiles. ANY by default.
* @return {Boolean} If any gameobject overlapped.
* @public
*/
public overlapsTiles(gameObject:Entity, separateObjects:boolean = false, collisionType:number = Kiwi.Components.ArcadePhysics.ANY):boolean {
//Are we a tilemaplayer?
if (this.parent.childType() !== Kiwi.TILE_LAYER) return false;
var tiles = (<Kiwi.GameObjects.Tilemap.TileMapLayer>this.parent).getOverlappingTiles(gameObject, collisionType);
if (tiles.length > 0) {
if (separateObjects) ArcadePhysics.separateTiles(gameObject, <Kiwi.GameObjects.Tilemap.TileMapLayer>this.parent, tiles);
return true;
} else {
return false;
}
}
/**
* A method to check to see if the parent of this physics component overlaps with another Kiwi.Entity.
* If seperateObjects is true it will seperate the two entities based on their bounding box.
* Note: The GameObject passed must contain a box component and only if you want to separate the two objects must is ALSO contain an ArcadePhysics component.
* Also: Not to be used for separation from tiles.
*
* @method overlaps
* @param gameObject {Kiwi.Entity}
* @param [seperateObjects=false] {boolean}
* @return {boolean}
* @public
*/
public overlaps(gameObject: Entity, separateObjects: boolean = false): boolean {
if (gameObject.components.hasComponent('Box') == false) return;
var box: Kiwi.Components.Box = gameObject.components.getComponent('Box');
var result: boolean = (box.worldHitbox.x + box.worldHitbox.width > this.box.worldHitbox.x) && (box.worldHitbox.x < this.box.worldHitbox.x + this.box.worldHitbox.width) &&
(box.worldHitbox.y + box.worldHitbox.height > this.box.worldHitbox.y) && (box.worldHitbox.y < this.box.worldHitbox.y + this.box.worldHitbox.height);
if (result) {
if (separateObjects)
ArcadePhysics.separate(<Kiwi.Entity>this.owner, gameObject);
if (this._callbackFunction !== null && this._callbackContext !== null) {
this._callbackFunction.call(this._callbackContext, this.owner, gameObject);
}
}
return result;
}
/**
* A method to check to see if the parent of this physics component overlaps with another individual in a Kiwi Group.
*
* @method overlapsGroup
* @param group {Kiwi.Group}
* @param [seperateObjects=false] {boolean}
* @return { boolean } If any object in the group overlapped with the GameObject or not.
* @public
*/
public overlapsGroup(group: Kiwi.Group, separateObjects: boolean = false): boolean {
var results: boolean = false;
for (var i = 0; i < group.members.length; i++) {
if (group.members[i].childType() === Kiwi.GROUP) {
//recursively check overlap
this.overlapsGroup(<Kiwi.Group>group.members[i], separateObjects);
} else {
//otherwise its an entity
if (this.overlaps(<Kiwi.Entity>group.members[i], separateObjects)) {
if (this._callbackContext !== null && this._callbackFunction !== null)
this._callbackFunction.call(this._callbackContext, this.owner, group.members[i]);
results = true;
}
}
}
return results;
}
/**
* A method to check to see if the parent of this physics component overlaps with any Entities that are held in an Array which is passed.
*
* @method overlapsArray
* @param array {Array} The array of GameObjects you want to check.
* @param [separateObjects=false] {boolean} If when the objects collide you want them to seperate outwards.
* @return {boolean} If any overlapping occured or not.
* @public
*/
public overlapsArray(array: Array<any>, separateObjects: boolean = false): boolean {
var results: boolean = false;
for (var i = 0; i < array.length; i++) {
if (typeof array[i].childType !== "undefined") {
if (array[i].childType() == Kiwi.GROUP) {
this.overlapsGroup(<Kiwi.Group>array[i], separateObjects);
} else {
if (this.overlaps(<Kiwi.Entity>array[i], separateObjects)) {
if (this._callbackFunction !== null && this._callbackContext !== null) {
this._callbackFunction.call(this._callbackContext, this.owner, array[i]);
}
results = true;
}
}
}
}
return results;
}
/*
*-------------
* Motion Methods
*-------------
*/
/**
* Computes the velocity based on the parameters passed.
* @method computeVelocity
* @static
* @param velocity {number} The currently velocity.
* @param [acceleration=0] {number} The acceleration of the item.
* @param [drag=0] {number} The amount of drag effecting the item.
* @param [max=10000] {number} The maximum velocity.
* @return {Number} The new velocity
* @public
*/
public static computeVelocity(velocity: number, acceleration: number = 0, drag: number = 0, max: number = 10000): number {
if (acceleration != 0)
velocity += acceleration * ArcadePhysics.updateInterval;
else if (drag != 0) {
drag = drag * ArcadePhysics.updateInterval;
if (velocity - drag > 0)
velocity = velocity - drag;
else if (velocity + drag < 0)
velocity += drag;
else
velocity = 0;
}
if ((velocity != 0) && (max != 10000)) {
if (velocity > max)
velocity = max;
else if (velocity < -max)
velocity = -max;
}
return velocity;
}
/**
* Updates the position of this object. Automatically called if the 'moves' parameter is true.
* This called each frame during the update method.
*
* @method updateMotion
* @private
*/
public updateMotion() {
var delta: number;
var velocityDelta: number;
//Update the motion calculated from rotation.
velocityDelta = (ArcadePhysics.computeVelocity(this.angularVelocity, this.angularAcceleration, this.angularDrag, this.maxAngular) - this.angularVelocity) / 2;
this.angularVelocity += velocityDelta;
this.transform.rotation += this.angularVelocity * ArcadePhysics.updateInterval;
this.angularVelocity += velocityDelta;
//Update the motion on the x-axis.
velocityDelta = (ArcadePhysics.computeVelocity(this.velocity.x, this.acceleration.x, this.drag.x, this.maxVelocity.x) - this.velocity.x) / 2;
this.velocity.x += velocityDelta;
delta = this.velocity.x * ArcadePhysics.updateInterval;
this.velocity.x += velocityDelta;
this.transform.x = this.transform.x + delta;
//Update the motion on the y-axis.
velocityDelta = (ArcadePhysics.computeVelocity(this.velocity.y, this.acceleration.y, this.drag.y, this.maxVelocity.y) - this.velocity.y) / 2;
this.velocity.y += velocityDelta;
delta = this.velocity.y * ArcadePhysics.updateInterval;
this.velocity.y += velocityDelta;
this.transform.y = this.transform.y + delta;
}
/**
* The Update loop of the physics component
* @method update
* @public
*/
public update() {
//Flixel preupdate
this.last.x = this.transform.worldX;
this.last.y = this.transform.worldY;
//Flixel postupdate
if (this.moves)
this.updateMotion();
this.wasTouching = this.touching;
this.touching = ArcadePhysics.NONE;
}
/**
* Removes all properties that refer to other objects or outside of this class in order to flag this object for garbage collection.
* @method destroy
* @public
*/
public destroy() {
super.destroy();
delete this.transform;
delete this.owner;
delete this._callbackContext;
delete this._callbackFunction;
}
/**
* The type of object that this is.
* @method objType
* @return {string} "ArcadePhysics"
* @public
*/
public objType() {
return "ArcadePhysics";
}
/*
*----------------
* Static Functions
*----------------
*/
/*
*----------------
* Collide Functions - Maps to Overlaps
*----------------
*/
/**
* A Static method to check to see if two objects collide or not. Returns a boolean indicating whether they overlaped or not.
*
* @method collide
* @static
* @public
* @param gameObject1 {Kiwi.Entity} The first game object.
* @param gameObject2 {Kiwi.Entity} The second game object.
* @param [seperate=true] {boolean} If the two gameobjects should seperated when they collide.
* @return {boolean}
*/
public static collide(gameObject1: Entity, gameObject2: Entity, seperate: boolean = true): boolean {
return ArcadePhysics.overlaps(gameObject1, gameObject2, seperate);
}
/**
* A Static method to check to see if a single entity collides with a group of entities. Returns a boolean indicating whether they overlaped or not.
*
* @method collideGroup
* @static
* @public
* @param gameObject {Kiwi.Entity} The entity you would like to check against.
* @param group {Kiwi.Group} The Kiwi Group that you want to check the entity against.
* @param [seperate=true] {boolean}
* @return {boolean}
* @public
*/
public static collideGroup(gameObject: Entity, group: Kiwi.Group, seperate: boolean = true): boolean {
return ArcadePhysics.overlapsObjectGroup(gameObject, group, seperate);
}
/**
* A Static method to check to see if a group of entities overlap with another group of entities. Returns a boolean indicating whether they overlaped or not.
*
* @method collideGroupGroup
* @static
* @public
* @param group1 {Kiwi.Group} The first Kiwi Group that you want to check the entity against.
* @param group2 {Kiwi.Group} The second Kiwi Group that you want to check the entity against.
* @param [seperate=true] {boolean}
* @return {boolean}
*/
public static collideGroupGroup(group1: Kiwi.Group, group2: Kiwi.Group, seperate: boolean = true): boolean {
return ArcadePhysics.overlapsGroupGroup(group1, group2, seperate);
}
/*
*-------------
* Overlap Static Method - Use's the Arcade Physics of one of the gameobjects passed.
*-------------
*/
/**
* A Static method to that checks to see if two objects overlap. Returns a boolean indicating whether they did or not.
*
* @method overlaps
* @static
* @public
* @param gameObject1 {Kiwi.Entity} The first game object.
* @param gameObject2 {Kiwi.Entity} The second gameobject you are testing the first against.
* @param [separateObjects=true] {boolean}
* @return {boolean}
*/
public static overlaps(gameObject1: Entity, gameObject2: Entity, separateObjects: boolean = true): boolean {
var obj1Physics: ArcadePhysics = gameObject1.components.getComponent("ArcadePhysics");
return obj1Physics.overlaps(gameObject2, separateObjects);
}
/**
* A Static method to that checks to see if a single object overlaps with a group of entities. Returns a boolean indicating whether they did or not.
*
* @method overlapsObjectGroup
* @static
* @param gameObject {Kiwi.Entity}
* @param group {Kiwi.Group}
* @param [seperateObjects=true] {boolean} If they overlap should the seperate or not
* @return {boolean}
* @public
*/
public static overlapsObjectGroup(gameObject: Entity, group: Kiwi.Group, separateObjects: boolean = true): boolean {
var objPhysics: ArcadePhysics = gameObject.components.getComponent("ArcadePhysics");
return objPhysics.overlapsGroup(group, separateObjects);
}
/**
* A Static method that checks to see if any objects in one group overlap with objects in another group.
*
* @method overlaps
* @static
* @param group1 {Kiwi.Group} The first group you would like to check against.
* @param group2 {Kiwi.Group} The second group you would like to check against.
* @param [seperate=true] {boolean} If they overlap should the seperate or not
* @return {boolean}
* @public
*/
public static overlapsGroupGroup(group1: Kiwi.Group, group2: Kiwi.Group, separateObjects: boolean = true): boolean {
var result: boolean = false;
var members: IChild[] = group1.members;
var i: number = 0;
while (i < group1.members.length) {
if (members[i].childType() == Kiwi.GROUP) {
if (ArcadePhysics.overlapsGroupGroup(<Kiwi.Group>members[i++], group2, separateObjects)) result = true;
} else {
if (ArcadePhysics.overlapsObjectGroup(<Kiwi.Entity>members[i++], group2, separateObjects)) result = true;
}
}
return result;
}
/**
* A Static method that checks to see if any objects from an Array collide with a Kiwi Group members.
*
* @method overlapsArrayGroup
* @param array {Array} An array you want to check collide.
* @param group {Kiwi.Group} A group of objects you want to check overlaps.
* @param [seperateObjects=true] {Boolean} If when a collision is found the objects should seperate out.
* @return {Boolean}
* @static
*/
public static overlapsArrayGroup(array: Array<any>, group: Kiwi.Group, separateObjects: boolean = true) {
var result: boolean = false;
//loop through the array
for (var i = 0; i < array.length; i++) {
if (typeof array[i].childType !== "undefined") {
if (array[i].childType() === Kiwi.GROUP) {
if (ArcadePhysics.overlapsGroupGroup(<Kiwi.Group>array[i], group, separateObjects))
result = true;
} else {
if (ArcadePhysics.overlapsObjectGroup(<Kiwi.Entity>array[i], group, separateObjects))
result = true;
}
}
}
return result;
}
/*
*---------------
* Static Constants
*---------------
*/
/**
* How often the motion should be updated.
* @property updateInterval
* @static
* @default 1 / 10
* @type number
* @public
*/
public static updateInterval: number = 1 / 10;
/**
* Generic value for "left" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
* @property LEFT
* @type number
* @default 0x0001
* @public
* @static
*/
public static LEFT: number = 0x0001;
/**
* Generic value for "right" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
* @property RIGHT
* @type number
* @default 0x0010
* @public
* @static
*/
public static RIGHT: number = 0x0010;
/**
* Generic value for "up" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
* @property UP
* @type number
* @default 0x0100
* @public
* @static
*/
public static UP: number = 0x0100;
/**
* Generic value for "down" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
* @property DOWN
* @type number
* @default 0x1000
* @public
* @static
*/
public static DOWN: number = 0x1000;
/**
* Special-case constant meaning no collisions, used mainly by <code>allowCollisions</code> and <code>touching</code>.
* @property NONE
* @type number
* @default 0
* @public
* @static
*/
public static NONE: number = 0;
/**
* Special-case constant meaning up, used mainly by <code>allowCollisions</code> and <code>touching</code>.
* @property CEILING
* @type number
* @default 0x0100
* @public
* @static
*/
public static CEILING: number = ArcadePhysics.UP;
/**
* Special-case constant meaning down, used mainly by <code>allowCollisions</code> and <code>touching</code>.
* @property FLOOR
* @type number
* @default 0x1000
* @public
* @static
*/
public static FLOOR: number = ArcadePhysics.DOWN;
/**
* Special-case constant meaning only the left and right sides, used mainly by <code>allowCollisions</code> and <code>touching</code>.
* @property WALL
* @type number
* @default 0x0011
* @public
* @static
*/
public static WALL: number = ArcadePhysics.LEFT | ArcadePhysics.RIGHT;
/**
* Special-case constant meaning any direction, used mainly by <code>allowCollisions</code> and <code>touching</code>.
* @property ANY
* @type number
* @default 0x1111
* @public
* @static
*/
public static ANY: number = ArcadePhysics.LEFT | ArcadePhysics.RIGHT | ArcadePhysics.UP | ArcadePhysics.DOWN;
/**
* Handy constant used during collision resolution (see <code>separateX()</code> and <code>separateY()</code>).
* @property OVERLAP_BIAS
* @type number
* @default 4
* @public
* @static
*/
public static OVERLAP_BIAS: number = 4;
}
}