mirror of
https://github.com/photonstorm/phaser
synced 2024-12-04 18:40:59 +00:00
657 lines
No EOL
17 KiB
TypeScript
657 lines
No EOL
17 KiB
TypeScript
/// <reference path="../math/Vec2.ts" />
|
|
/// <reference path="../geom/Point.ts" />
|
|
/// <reference path="../math/Vec2Utils.ts" />
|
|
/// <reference path="../math/Transform.ts" />
|
|
/// <reference path="../math/TransformUtils.ts" />
|
|
/// <reference path="../utils/BodyUtils.ts" />
|
|
/// <reference path="AdvancedPhysics.ts" />
|
|
/// <reference path="joints/Joint.ts" />
|
|
/// <reference path="Bounds.ts" />
|
|
/// <reference path="Space.ts" />
|
|
/// <reference path="shapes/IShape.ts" />
|
|
/// <reference path="shapes/Triangle.ts" />
|
|
/// <reference path="shapes/Circle.ts" />
|
|
/// <reference path="shapes/Box.ts" />
|
|
/// <reference path="shapes/Poly.ts" />
|
|
/// <reference path="shapes/Segment.ts" />
|
|
|
|
/**
|
|
* Phaser - Advanced Physics - Body
|
|
*
|
|
* Based on the work Ju Hyung Lee started in JS PhyRus.
|
|
*/
|
|
|
|
module Phaser.Physics {
|
|
|
|
export class Body {
|
|
|
|
constructor(sprite: Phaser.Sprite, type: number, x?: number = 0, y?: number = 0, shapeType?: number = 0) {
|
|
|
|
this.id = Phaser.Physics.AdvancedPhysics.bodyCounter++;
|
|
this.name = 'body' + this.id;
|
|
this.type = type;
|
|
|
|
if (sprite)
|
|
{
|
|
this.sprite = sprite;
|
|
this.game = sprite.game;
|
|
this.position = new Phaser.Vec2(Phaser.Physics.AdvancedPhysics.pixelsToMeters(sprite.x), Phaser.Physics.AdvancedPhysics.pixelsToMeters(sprite.y));
|
|
this.angle = this.game.math.degreesToRadians(sprite.rotation);
|
|
}
|
|
else
|
|
{
|
|
this.position = new Phaser.Vec2(Phaser.Physics.AdvancedPhysics.pixelsToMeters(x), Phaser.Physics.AdvancedPhysics.pixelsToMeters(y));
|
|
this.angle = 0;
|
|
}
|
|
|
|
this.transform = new Phaser.Transform(this.position, this.angle);
|
|
this.centroid = new Phaser.Vec2;
|
|
this.velocity = new Phaser.Vec2;
|
|
this.force = new Phaser.Vec2;
|
|
this.angularVelocity = 0;
|
|
this.torque = 0;
|
|
this.linearDamping = 0;
|
|
this.angularDamping = 0;
|
|
this.sleepTime = 0;
|
|
this.awaked = false;
|
|
|
|
this.shapes = [];
|
|
this.joints = [];
|
|
this.jointHash = {};
|
|
|
|
this.bounds = new Bounds;
|
|
|
|
this.allowCollisions = Phaser.Types.ANY;
|
|
|
|
this.categoryBits = 0x0001;
|
|
this.maskBits = 0xFFFF;
|
|
|
|
this.stepCount = 0;
|
|
|
|
if (sprite)
|
|
{
|
|
if (shapeType == 0)
|
|
{
|
|
Phaser.BodyUtils.addBox(this, 0, 0, this.sprite.width, this.sprite.height, 1, 1, 1);
|
|
}
|
|
else
|
|
{
|
|
Phaser.BodyUtils.addCircle(this, Math.max(this.sprite.width, this.sprite.height) / 2, 0, 0, 1, 1, 1);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private _tempVec2: Phaser.Vec2 = new Phaser.Vec2;
|
|
private _fixedRotation: bool = false;
|
|
|
|
/**
|
|
* Reference to Phaser.Game
|
|
*/
|
|
public game: Phaser.Game;
|
|
|
|
/**
|
|
* Reference to the parent Sprite
|
|
*/
|
|
public sprite: Phaser.Sprite;
|
|
|
|
/**
|
|
* The Body ID
|
|
*/
|
|
public id: number;
|
|
|
|
/**
|
|
* The Body name
|
|
*/
|
|
public name: string;
|
|
|
|
/**
|
|
* The type of Body (disabled, dynamic, static or kinematic)
|
|
* Disabled = skips all physics operations / tests (default)
|
|
* Dynamic = gives and receives impacts
|
|
* Static = gives but doesn't receive impacts, cannot be moved by physics
|
|
* Kinematic = gives impacts, but never receives, can be moved by physics
|
|
* @type {number}
|
|
*/
|
|
public type: number;
|
|
|
|
/**
|
|
* The angle of the body in radians. Used by all of the internal physics methods.
|
|
*/
|
|
public angle: number;
|
|
|
|
/**
|
|
* The rotation of the body in degrees. Phaser uses a right-handed coordinate system, where 0 points to the right.
|
|
*/
|
|
public get rotation(): number {
|
|
return this.game.math.radiansToDegrees(this.angle);
|
|
}
|
|
|
|
/**
|
|
* Set the rotation of the body in degrees. Phaser uses a right-handed coordinate system, where 0 points to the right.
|
|
* The value is automatically wrapped to be between 0 and 360.
|
|
*/
|
|
public set rotation(value: number) {
|
|
this.angle = this.game.math.degreesToRadians(this.game.math.wrap(value, 360, 0));
|
|
}
|
|
|
|
// Local to world transform
|
|
public transform: Phaser.Transform;
|
|
|
|
// Local center of mass
|
|
public centroid: Phaser.Vec2;
|
|
|
|
// World position of centroid
|
|
public position: Phaser.Vec2;
|
|
|
|
// Velocity
|
|
public velocity: Phaser.Vec2;
|
|
|
|
// Force
|
|
public force: Phaser.Vec2;
|
|
|
|
// Angular velocity
|
|
public angularVelocity: number;
|
|
|
|
// Torque
|
|
public torque: number;
|
|
|
|
// Linear damping
|
|
public linearDamping: number;
|
|
|
|
// Angular damping
|
|
public angularDamping: number;
|
|
|
|
// Sleep time
|
|
public sleepTime: number;
|
|
|
|
// Awaked
|
|
public awaked: bool;
|
|
|
|
// Allow Collisions
|
|
public allowCollisions: number;
|
|
|
|
// Shapes
|
|
public shapes: IShape[] = [];
|
|
|
|
// Length of the shapes array
|
|
public shapesLength: number;
|
|
|
|
// Joints
|
|
public joints: IJoint[] = [];
|
|
public jointHash = {};
|
|
|
|
// Bounds of all shapes
|
|
public bounds: Bounds;
|
|
|
|
public mass: number;
|
|
public massInverted: number;
|
|
public inertia: number;
|
|
public inertiaInverted: number;
|
|
|
|
public categoryBits: number = 0x0001;
|
|
public maskBits: number = 0xFFFF;
|
|
public stepCount: number = 0;
|
|
public space: Space;
|
|
|
|
|
|
public get isDisabled(): bool {
|
|
return this.type == Phaser.Types.BODY_DISABLED ? true : false;
|
|
}
|
|
|
|
public get isStatic(): bool {
|
|
return this.type == Phaser.Types.BODY_STATIC ? true : false;
|
|
}
|
|
|
|
public get isKinetic(): bool {
|
|
return this.type == Phaser.Types.BODY_KINETIC ? true : false;
|
|
}
|
|
|
|
public get isDynamic(): bool {
|
|
return this.type == Phaser.Types.BODY_DYNAMIC ? true : false;
|
|
}
|
|
|
|
public setType(type: number) {
|
|
|
|
if (type == this.type)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.force.setTo(0, 0);
|
|
this.velocity.setTo(0, 0);
|
|
this.torque = 0;
|
|
this.angularVelocity = 0;
|
|
this.type = type;
|
|
|
|
this.awake(true);
|
|
|
|
}
|
|
|
|
|
|
public addShape(shape) {
|
|
|
|
// Check not already part of this body
|
|
shape.body = this;
|
|
|
|
this.shapes.push(shape);
|
|
|
|
this.shapesLength = this.shapes.length;
|
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
public removeShape(shape) {
|
|
|
|
var index = this.shapes.indexOf(shape);
|
|
|
|
if (index != -1)
|
|
{
|
|
this.shapes.splice(index, 1);
|
|
shape.body = undefined;
|
|
}
|
|
|
|
this.shapesLength = this.shapes.length;
|
|
|
|
}
|
|
|
|
private setMass(mass:number) {
|
|
|
|
this.mass = mass;
|
|
this.massInverted = mass > 0 ? 1 / mass : 0;
|
|
|
|
}
|
|
|
|
private setInertia(inertia:number) {
|
|
|
|
this.inertia = inertia;
|
|
this.inertiaInverted = inertia > 0 ? 1 / inertia : 0;
|
|
|
|
}
|
|
|
|
private _newPosition: Phaser.Vec2 = new Phaser.Vec2;
|
|
|
|
public setPosition(x: number, y: number) {
|
|
|
|
this._newPosition.setTo(Phaser.Physics.AdvancedPhysics.pixelsToMeters(x), Phaser.Physics.AdvancedPhysics.pixelsToMeters(y));
|
|
|
|
this.setTransform(this._newPosition, this.angle);
|
|
|
|
}
|
|
|
|
public setTransform(pos:Phaser.Vec2, angle:number) {
|
|
|
|
// inject the transform into this.position
|
|
this.transform.setTo(pos, angle);
|
|
//Manager.write('setTransform: ' + this.position.toString());
|
|
//Manager.write('centroid: ' + this.centroid.toString());
|
|
Phaser.TransformUtils.transform(this.transform, this.centroid, this.position);
|
|
//Manager.write('post setTransform: ' + this.position.toString());
|
|
//this.position.copyFrom(this.transform.transform(this.centroid));
|
|
this.angle = angle;
|
|
|
|
}
|
|
|
|
public syncTransform() {
|
|
|
|
//Manager.write('syncTransform:');
|
|
//Manager.write('p: ' + this.position.toString());
|
|
//Manager.write('centroid: ' + this.centroid.toString());
|
|
//Manager.write('xf: ' + this.transform.toString());
|
|
//Manager.write('a: ' + this.angle);
|
|
this.transform.setRotation(this.angle);
|
|
// OPTIMISE: Creating new vector
|
|
Phaser.Vec2Utils.subtract(this.position, Phaser.TransformUtils.rotate(this.transform, this.centroid), this.transform.t);
|
|
//Manager.write('--------------------');
|
|
//Manager.write('xf: ' + this.transform.toString());
|
|
//Manager.write('--------------------');
|
|
|
|
}
|
|
|
|
public getWorldPoint(p:Phaser.Vec2) {
|
|
// OPTIMISE: Creating new vector
|
|
return Phaser.TransformUtils.transform(this.transform, p);
|
|
}
|
|
|
|
public getWorldVector(v:Phaser.Vec2) {
|
|
// OPTIMISE: Creating new vector
|
|
return Phaser.TransformUtils.rotate(this.transform, v);
|
|
}
|
|
|
|
public getLocalPoint(p:Phaser.Vec2) {
|
|
// OPTIMISE: Creating new vector
|
|
return Phaser.TransformUtils.untransform(this.transform, p);
|
|
}
|
|
|
|
public getLocalVector(v:Phaser.Vec2) {
|
|
// OPTIMISE: Creating new vector
|
|
return Phaser.TransformUtils.unrotate(this.transform, v);
|
|
}
|
|
|
|
public set fixedRotation(value:bool) {
|
|
|
|
this._fixedRotation = value;
|
|
|
|
this.resetMassData();
|
|
|
|
}
|
|
|
|
public get fixedRotation(): bool {
|
|
return this._fixedRotation;
|
|
}
|
|
|
|
public resetMassData() {
|
|
|
|
this.centroid.setTo(0, 0);
|
|
this.mass = 0;
|
|
this.massInverted = 0;
|
|
this.inertia = 0;
|
|
this.inertiaInverted = 0;
|
|
|
|
if (this.isDynamic == false)
|
|
{
|
|
Phaser.TransformUtils.transform(this.transform, this.centroid, this.position);
|
|
return;
|
|
}
|
|
|
|
var totalMassCentroid = new Phaser.Vec2(0, 0);
|
|
var totalMass = 0;
|
|
var totalInertia = 0;
|
|
|
|
for (var i = 0; i < this.shapes.length; i++)
|
|
{
|
|
var shape = this.shapes[i];
|
|
var centroid = shape.centroid();
|
|
var mass = shape.area() * shape.density;
|
|
var inertia = shape.inertia(mass);
|
|
|
|
totalMassCentroid.multiplyAddByScalar(centroid, mass);
|
|
totalMass += mass;
|
|
totalInertia += inertia;
|
|
}
|
|
|
|
Phaser.Vec2Utils.scale(totalMassCentroid, 1 / totalMass, this.centroid);
|
|
|
|
this.setMass(totalMass);
|
|
|
|
if (!this.fixedRotation)
|
|
{
|
|
this.setInertia(totalInertia - totalMass * Phaser.Vec2Utils.dot(this.centroid, this.centroid));
|
|
}
|
|
|
|
// Move center of mass
|
|
var oldPosition: Phaser.Vec2 = Phaser.Vec2Utils.clone(this.position);
|
|
Phaser.TransformUtils.transform(this.transform, this.centroid, this.position);
|
|
|
|
// Update center of mass velocity
|
|
oldPosition.subtract(this.position);
|
|
this.velocity.multiplyAddByScalar(Phaser.Vec2Utils.perp(oldPosition, oldPosition), this.angularVelocity);
|
|
|
|
}
|
|
|
|
public resetJointAnchors() {
|
|
|
|
for (var i = 0; i < this.joints.length; i++)
|
|
{
|
|
var joint = this.joints[i];
|
|
|
|
if (!joint)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var anchor1 = joint.getWorldAnchor1();
|
|
var anchor2 = joint.getWorldAnchor2();
|
|
|
|
joint.setWorldAnchor1(anchor1);
|
|
joint.setWorldAnchor2(anchor2);
|
|
}
|
|
}
|
|
|
|
public cacheData(source:string = '') {
|
|
|
|
//Manager.write('cacheData -- start');
|
|
//Manager.write('p: ' + this.position.toString());
|
|
//Manager.write('xf: ' + this.transform.toString());
|
|
|
|
this.bounds.clear();
|
|
|
|
for (var i = 0; i < this.shapesLength; i++)
|
|
{
|
|
var shape: IShape = this.shapes[i];
|
|
shape.cacheData(this.transform);
|
|
this.bounds.addBounds(shape.bounds);
|
|
}
|
|
|
|
//Manager.write('bounds: ' + this.bounds.toString());
|
|
|
|
//Manager.write('p: ' + this.position.toString());
|
|
//Manager.write('xf: ' + this.transform.toString());
|
|
//Manager.write('cacheData -- stop');
|
|
|
|
}
|
|
|
|
public updateVelocity(gravity, dt, damping) {
|
|
|
|
Phaser.Vec2Utils.multiplyAdd(gravity, this.force, this.massInverted, this._tempVec2);
|
|
Phaser.Vec2Utils.multiplyAdd(this.velocity, this._tempVec2, dt, this.velocity);
|
|
|
|
this.angularVelocity = this.angularVelocity + this.torque * this.inertiaInverted * 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.velocity.scale(this.clamp(1 - dt * (damping + this.linearDamping), 0, 1));
|
|
this.angularVelocity *= this.clamp(1 - dt * (damping + this.angularDamping), 0, 1);
|
|
|
|
this.force.setTo(0, 0);
|
|
this.torque = 0;
|
|
|
|
}
|
|
|
|
public inContact(body2: Body): bool {
|
|
|
|
if (!body2 || this.stepCount == body2.stepCount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!(this.isAwake && this.isStatic == false) && !(body2.isAwake && body2.isStatic == false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (this.isCollidable(body2) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!this.bounds.intersectsBounds(body2.bounds))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
public clamp(v, min, max) {
|
|
return v < min ? min : (v > max ? max : v);
|
|
}
|
|
|
|
public updatePosition(dt:number) {
|
|
|
|
this.position.add(Phaser.Vec2Utils.scale(this.velocity, dt, this._tempVec2));
|
|
|
|
this.angle += this.angularVelocity * dt;
|
|
|
|
if (this.sprite)
|
|
{
|
|
this.sprite.x = this.position.x * 50;
|
|
this.sprite.y = this.position.y * 50;
|
|
this.sprite.transform.rotation = this.game.math.radiansToDegrees(this.angle);
|
|
}
|
|
|
|
}
|
|
|
|
public resetForce() {
|
|
|
|
this.force.setTo(0, 0);
|
|
this.torque = 0;
|
|
|
|
}
|
|
|
|
public applyForce(force:Phaser.Vec2, p:Phaser.Vec2) {
|
|
|
|
if (this.isDynamic == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.isAwake == false)
|
|
{
|
|
this.awake(true);
|
|
}
|
|
|
|
this.force.add(force);
|
|
|
|
Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2);
|
|
|
|
this.torque += Phaser.Vec2Utils.cross(this._tempVec2, force);
|
|
|
|
}
|
|
|
|
public applyForceToCenter(force:Phaser.Vec2) {
|
|
|
|
if (this.isDynamic == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.isAwake == false)
|
|
{
|
|
this.awake(true);
|
|
}
|
|
|
|
this.force.add(force);
|
|
|
|
}
|
|
|
|
public applyTorque(torque:number) {
|
|
|
|
if (this.isDynamic == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.isAwake == false)
|
|
{
|
|
this.awake(true);
|
|
}
|
|
|
|
this.torque += torque;
|
|
|
|
}
|
|
|
|
public applyLinearImpulse(impulse:Phaser.Vec2, p:Phaser.Vec2) {
|
|
|
|
if (this.isDynamic == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.isAwake == false)
|
|
{
|
|
this.awake(true);
|
|
}
|
|
|
|
this.velocity.multiplyAddByScalar(impulse, this.massInverted);
|
|
|
|
Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2);
|
|
|
|
this.angularVelocity += Phaser.Vec2Utils.cross(this._tempVec2, impulse) * this.inertiaInverted;
|
|
|
|
}
|
|
|
|
public applyAngularImpulse(impulse: number) {
|
|
|
|
if (this.isDynamic == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.isAwake == false)
|
|
{
|
|
this.awake(true);
|
|
}
|
|
|
|
this.angularVelocity += impulse * this.inertiaInverted;
|
|
|
|
}
|
|
|
|
public kineticEnergy() {
|
|
|
|
return 0.5 * (this.mass * this.velocity.dot(this.velocity) + this.inertia * (this.angularVelocity * this.angularVelocity));
|
|
|
|
}
|
|
|
|
public get isAwake(): bool {
|
|
return this.awaked;
|
|
}
|
|
|
|
public awake(flag) {
|
|
|
|
this.awaked = flag;
|
|
|
|
if (flag)
|
|
{
|
|
this.sleepTime = 0;
|
|
}
|
|
else
|
|
{
|
|
this.velocity.setTo(0, 0);
|
|
this.angularVelocity = 0;
|
|
this.force.setTo(0, 0);
|
|
this.torque = 0;
|
|
}
|
|
|
|
}
|
|
|
|
public isCollidable(other:Body) {
|
|
|
|
if ((this.isDynamic == false && other.isDynamic == false) || this == other)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!(this.maskBits & other.categoryBits) || !(other.maskBits & this.categoryBits))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < this.joints.length; i++)
|
|
{
|
|
var joint = this.joints[i];
|
|
|
|
if (!this.joints[i] || (!this.joints[i].collideConnected && other.jointHash[this.joints[i].id] != undefined))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
public toString(): string {
|
|
return "[{Body (name=" + this.name + " velocity=" + this.velocity.toString() + " angularVelocity: " + this.angularVelocity + ")}]";
|
|
}
|
|
|
|
}
|
|
|
|
} |