@awayfl/awayfl-player
Version:
Flash Player emulator for executing SWF files (published for FP versions 6 and up) in javascript
1,459 lines (1,202 loc) • 41.7 kB
text/typescript
import { ASMethodClosure, ASClass } from '@awayfl/avm2';
import { b2Vec2, b2Math, b2Transform, b2Sweep } from '../Common/Math';
import { b2Island } from './b2Island';
import { b2Body } from './b2Body';
import { b2Contact, b2ContactEdge, b2ContactSolver } from './Contacts';
import { b2PolygonShape } from '../Collision/Shapes/b2PolygonShape';
import { b2CircleShape } from '../Collision/Shapes/b2CircleShape';
import { b2Shape } from '../Collision/Shapes/b2Shape';
import { b2Settings } from '../Common/b2Settings';
import { b2Fixture } from './b2Fixture';
import { b2JointEdge, b2PulleyJoint, b2Joint, b2JointDef } from './Joints';
import { b2TimeStep } from './b2TimeStep';
import { b2Controller } from './Controllers/b2Controller';
import { b2Color } from '../Common/b2Color';
import { b2EdgeShape } from '../Collision/Shapes/b2EdgeShape';
import { b2DebugDraw } from './b2DebugDraw';
import { b2DestructionListener } from './b2DestructionListener';
import { b2BodyDef } from './b2BodyDef';
import { b2ContactListener } from './b2ContactListener';
import { b2ContactManager } from './b2ContactManager';
import { b2RayCastInput } from '../Collision/b2RayCastInput';
import { b2RayCastOutput } from '../Collision/b2RayCastOutput';
import { IBroadPhase } from '../Collision/IBroadPhase';
import { b2AABB } from '../Collision/b2AABB';
import { b2ContactFilter } from './b2ContactFilter';
import { b2ControllerEdge } from './Controllers/b2ControllerEdge';
// unbox methods from ASClass to real class prototupe
function unBoxMethods (object: ASClass | any, prototype: any) {
if (!object || typeof object['traits'] === 'undefined') {
return object;
}
const names = Object.getOwnPropertyNames(prototype);
const mangle = '$Bg';
for (const name of names) {
if (!object[name] && object[mangle + name]) {
object[name] = object[mangle + name];
}
}
return object;
}
/**
* The world class manages all physics entities, dynamic simulation,
* and asynchronous queries.
*/
export class b2World {
__fast__: boolean = true;
// Construct a world object.
/**
* @param gravity the world gravity vector.
* @param doSleep improve performance by not simulating inactive bodies.
*/
constructor(gravity: b2Vec2, doSleep: boolean) {
this.m_destructionListener = null;
this.m_debugDraw = null;
this.m_bodyList = null;
this.m_contactList = null;
this.m_jointList = null;
this.m_controllerList = null;
this.m_bodyCount = 0;
this.m_contactCount = 0;
this.m_jointCount = 0;
this.m_controllerCount = 0;
b2World.m_warmStarting = true;
b2World.m_continuousPhysics = true;
this.m_allowSleep = doSleep;
this.m_gravity = gravity;
this.m_inv_dt0 = 0.0;
this.m_contactManager.m_world = this;
const bd: b2BodyDef = new b2BodyDef();
this.m_groundBody = this.CreateBody(bd);
}
/**
* Destruct the world. All physics entities are destroyed and all heap memory is released.
*/
//~b2World();
/**
* Register a destruction listener.
*/
public SetDestructionListener(listener: b2DestructionListener): void {
this.m_destructionListener = unBoxMethods(listener, b2DestructionListener.prototype);
}
/**
* Register a contact filter to provide specific control over collision.
* Otherwise the default filter is used (b2_defaultFilter).
*/
public SetContactFilter(filter: b2ContactFilter | ASClass | null): void {
this.m_contactManager.m_contactFilter = unBoxMethods(filter, b2ContactFilter.prototype);
}
/**
* Register a contact event listener
*/
public SetContactListener(listener: b2ContactListener | ASClass | null): void {
this.m_contactManager.m_contactListener = unBoxMethods(listener, b2ContactListener.prototype);
}
/**
* Register a routine for debug drawing. The debug draw functions are called
* inside the b2World::Step method, so make sure your renderer is ready to
* consume draw commands when you call Step().
*/
public SetDebugDraw(debugDraw: b2DebugDraw): void {
this.m_debugDraw = debugDraw;
}
/**
* Use the given object as a broadphase.
* The old broadphase will not be cleanly emptied.
* @warning It is not recommended you call this except immediately after constructing the world.
* @warning This function is locked during callbacks.
*/
public SetBroadPhase(broadPhase: IBroadPhase): void {
const oldBroadPhase: IBroadPhase = this.m_contactManager.m_broadPhase;
this.m_contactManager.m_broadPhase = broadPhase;
for (let b: b2Body = this.m_bodyList; b; b = b.m_next) {
for (let f: b2Fixture = b.m_fixtureList; f; f = f.m_next) {
f.m_proxy = broadPhase.CreateProxy(oldBroadPhase.GetFatAABB(f.m_proxy), f);
}
}
}
/**
* Perform validation of internal data structures.
*/
public Validate(): void {
this.m_contactManager.m_broadPhase.Validate();
}
/**
* Get the number of broad-phase proxies.
*/
public GetProxyCount(): number /** int */
{
return this.m_contactManager.m_broadPhase.GetProxyCount();
}
/**
* Create a rigid body given a definition. No reference to the definition
* is retained.
* @warning This function is locked during callbacks.
*/
public CreateBody(def: b2BodyDef): b2Body {
//b2Settings.b2Assert(this.m_lock == false);
if (this.IsLocked() == true) {
return null;
}
//void* mem = this.m_blockAllocator.Allocate(sizeof(b2Body));
const b: b2Body = new b2Body(def, this);
// Add to world doubly linked list.
b.m_prev = null;
b.m_next = this.m_bodyList;
if (this.m_bodyList) {
this.m_bodyList.m_prev = b;
}
this.m_bodyList = b;
++this.m_bodyCount;
return b;
}
/**
* Destroy a rigid body given a definition. No reference to the definition
* is retained. This function is locked during callbacks.
* @warning This automatically deletes all associated shapes and joints.
* @warning This function is locked during callbacks.
*/
public DestroyBody(b: b2Body): void {
//b2Settings.b2Assert(this.m_bodyCount > 0);
//b2Settings.b2Assert(this.m_lock == false);
if (this.IsLocked() == true) {
return;
}
// Delete the attached joints.
let jn: b2JointEdge = b.m_jointList;
while (jn) {
const jn0: b2JointEdge = jn;
jn = jn.next;
if (this.m_destructionListener) {
this.m_destructionListener.SayGoodbyeJoint(jn0.joint);
}
this.DestroyJoint(jn0.joint);
}
// Detach controllers attached to this body
let coe: b2ControllerEdge = b.m_controllerList;
while (coe) {
const coe0: b2ControllerEdge = coe;
coe = coe.nextController;
coe0.controller.RemoveBody(b);
}
// Delete the attached contacts.
let ce: b2ContactEdge = b.m_contactList;
while (ce) {
const ce0: b2ContactEdge = ce;
ce = ce.next;
this.m_contactManager.Destroy(ce0.contact);
}
b.m_contactList = null;
// Delete the attached fixtures. This destroys broad-phase
// proxies.
let f: b2Fixture = b.m_fixtureList;
while (f) {
const f0: b2Fixture = f;
f = f.m_next;
if (this.m_destructionListener) {
this.m_destructionListener.SayGoodbyeFixture(f0);
}
f0.DestroyProxy(this.m_contactManager.m_broadPhase);
f0.Destroy();
//f0->~b2Fixture();
//this.m_blockAllocator.Free(f0, sizeof(b2Fixture));
}
b.m_fixtureList = null;
b.m_fixtureCount = 0;
// Remove world body list.
if (b.m_prev) {
b.m_prev.m_next = b.m_next;
}
if (b.m_next) {
b.m_next.m_prev = b.m_prev;
}
if (b == this.m_bodyList) {
this.m_bodyList = b.m_next;
}
--this.m_bodyCount;
//b->~b2Body();
//this.m_blockAllocator.Free(b, sizeof(b2Body));
}
/**
* Create a joint to constrain bodies together. No reference to the definition
* is retained. This may cause the connected bodies to cease colliding.
* @warning This function is locked during callbacks.
*/
public CreateJoint(def: b2JointDef): b2Joint {
//b2Settings.b2Assert(this.m_lock == false);
const j: b2Joint = b2Joint.Create(def, null);
// Connect to the world list.
j.m_prev = null;
j.m_next = this.m_jointList;
if (this.m_jointList) {
this.m_jointList.m_prev = j;
}
this.m_jointList = j;
++this.m_jointCount;
// Connect to the bodies' doubly linked lists.
j.m_edgeA.joint = j;
j.m_edgeA.other = j.m_bodyB;
j.m_edgeA.prev = null;
j.m_edgeA.next = j.m_bodyA.m_jointList;
if (j.m_bodyA.m_jointList) j.m_bodyA.m_jointList.prev = j.m_edgeA;
j.m_bodyA.m_jointList = j.m_edgeA;
j.m_edgeB.joint = j;
j.m_edgeB.other = j.m_bodyA;
j.m_edgeB.prev = null;
j.m_edgeB.next = j.m_bodyB.m_jointList;
if (j.m_bodyB.m_jointList) j.m_bodyB.m_jointList.prev = j.m_edgeB;
j.m_bodyB.m_jointList = j.m_edgeB;
const bodyA: b2Body = def.bodyA;
const bodyB: b2Body = def.bodyB;
// If the joint prevents collisions, then flag any contacts for filtering.
if (def.collideConnected == false) {
let edge: b2ContactEdge = bodyB.GetContactList();
while (edge) {
if (edge.other == bodyA) {
// Flag the contact for filtering at the next time step (where either
// body is awake).
edge.contact.FlagForFiltering();
}
edge = edge.next;
}
}
// Note: creating a joint doesn't wake the bodies.
return j;
}
/**
* Destroy a joint. This may cause the connected bodies to begin colliding.
* @warning This function is locked during callbacks.
*/
public DestroyJoint(j: b2Joint): void {
//b2Settings.b2Assert(this.m_lock == false);
const collideConnected: boolean = j.m_collideConnected;
// Remove from the doubly linked list.
if (j.m_prev) {
j.m_prev.m_next = j.m_next;
}
if (j.m_next) {
j.m_next.m_prev = j.m_prev;
}
if (j == this.m_jointList) {
this.m_jointList = j.m_next;
}
// Disconnect from island graph.
const bodyA: b2Body = j.m_bodyA;
const bodyB: b2Body = j.m_bodyB;
// Wake up connected bodies.
bodyA.SetAwake(true);
bodyB.SetAwake(true);
// Remove from body 1.
if (j.m_edgeA.prev) {
j.m_edgeA.prev.next = j.m_edgeA.next;
}
if (j.m_edgeA.next) {
j.m_edgeA.next.prev = j.m_edgeA.prev;
}
if (j.m_edgeA == bodyA.m_jointList) {
bodyA.m_jointList = j.m_edgeA.next;
}
j.m_edgeA.prev = null;
j.m_edgeA.next = null;
// Remove from body 2
if (j.m_edgeB.prev) {
j.m_edgeB.prev.next = j.m_edgeB.next;
}
if (j.m_edgeB.next) {
j.m_edgeB.next.prev = j.m_edgeB.prev;
}
if (j.m_edgeB == bodyB.m_jointList) {
bodyB.m_jointList = j.m_edgeB.next;
}
j.m_edgeB.prev = null;
j.m_edgeB.next = null;
b2Joint.Destroy(j, null);
//b2Settings.b2Assert(this.m_jointCount > 0);
--this.m_jointCount;
// If the joint prevents collisions, then flag any contacts for filtering.
if (collideConnected == false) {
let edge: b2ContactEdge = bodyB.GetContactList();
while (edge) {
if (edge.other == bodyA) {
// Flag the contact for filtering at the next time step (where either
// body is awake).
edge.contact.FlagForFiltering();
}
edge = edge.next;
}
}
}
/**
* Add a controller to the world list
*/
public AddController(c: b2Controller): b2Controller {
c.m_next = this.m_controllerList;
c.m_prev = null;
this.m_controllerList = c;
c.m_world = this;
this.m_controllerCount++;
return c;
}
public RemoveController(c: b2Controller): void {
//TODO: Remove bodies from controller
if (c.m_prev)
c.m_prev.m_next = c.m_next;
if (c.m_next)
c.m_next.m_prev = c.m_prev;
if (this.m_controllerList == c)
this.m_controllerList = c.m_next;
this.m_controllerCount--;
}
public CreateController(controller: b2Controller): b2Controller {
if (controller.m_world != this)
throw new Error('Controller can only be a member of one world');
controller.m_next = this.m_controllerList;
controller.m_prev = null;
if (this.m_controllerList)
this.m_controllerList.m_prev = controller;
this.m_controllerList = controller;
++this.m_controllerCount;
controller.m_world = this;
return controller;
}
public DestroyController(controller: b2Controller): void {
//b2Settings.b2Assert(this.m_controllerCount > 0);
controller.Clear();
if (controller.m_next)
controller.m_next.m_prev = controller.m_prev;
if (controller.m_prev)
controller.m_prev.m_next = controller.m_next;
if (controller == this.m_controllerList)
this.m_controllerList = controller.m_next;
--this.m_controllerCount;
}
/**
* Enable/disable warm starting. For testing.
*/
public SetWarmStarting(flag: boolean): void { b2World.m_warmStarting = flag; }
/**
* Enable/disable continuous physics. For testing.
*/
public SetContinuousPhysics(flag: boolean): void { b2World.m_continuousPhysics = flag; }
/**
* Get the number of bodies.
*/
public GetBodyCount(): number /** int */
{
return this.m_bodyCount;
}
/**
* Get the number of joints.
*/
public GetJointCount(): number /** int */
{
return this.m_jointCount;
}
/**
* Get the number of contacts (each may have 0 or more contact points).
*/
public GetContactCount(): number /** int */
{
return this.m_contactCount;
}
/**
* Change the global gravity vector.
*/
public SetGravity(gravity: b2Vec2): void {
this.m_gravity = gravity;
}
/**
* Get the global gravity vector.
*/
public GetGravity(): b2Vec2 {
return this.m_gravity;
}
/**
* The world provides a single static ground body with no collision shapes.
* You can use this to simplify the creation of joints and static shapes.
*/
public GetGroundBody(): b2Body {
return this.m_groundBody;
}
private static s_timestep2: b2TimeStep = new b2TimeStep();
/**
* Take a time step. This performs collision detection, integration,
* and constraint solution.
* @param timeStep the amount of time to simulate, this should not vary.
* @param velocityIterations for the velocity constraint solver.
* @param positionIterations for the position constraint solver.
*/
public Step(dt: number, velocityIterations: number /** int */, positionIterations: number /** int */): void {
if (this.m_flags & b2World.e_newFixture) {
this.m_contactManager.FindNewContacts();
this.m_flags &= ~b2World.e_newFixture;
}
this.m_flags |= b2World.e_locked;
const step: b2TimeStep = b2World.s_timestep2;
step.dt = dt;
step.velocityIterations = velocityIterations;
step.positionIterations = positionIterations;
if (dt > 0.0) {
step.inv_dt = 1.0 / dt;
} else {
step.inv_dt = 0.0;
}
step.dtRatio = this.m_inv_dt0 * dt;
step.warmStarting = b2World.m_warmStarting;
// Update contacts.
this.m_contactManager.Collide();
// Integrate velocities, solve velocity constraints, and integrate positions.
if (step.dt > 0.0) {
this.Solve(step);
}
// Handle TOI events.
if (b2World.m_continuousPhysics && step.dt > 0.0) {
this.SolveTOI(step);
}
if (step.dt > 0.0) {
this.m_inv_dt0 = step.inv_dt;
}
this.m_flags &= ~b2World.e_locked;
}
/**
* Call this after you are done with time steps to clear the forces. You normally
* call this after each call to Step, unless you are performing sub-steps.
*/
public ClearForces(): void {
for (let body: b2Body = this.m_bodyList; body; body = body.m_next) {
body.m_force.SetZero();
body.m_torque = 0.0;
}
}
private static s_xf: b2Transform = new b2Transform();
/**
* Call this to draw shapes and other debug draw data.
*/
public DrawDebugData(): void {
if (this.m_debugDraw == null) {
return;
}
this.m_debugDraw.m_sprite.graphics.clear();
const flags: number /** uint */ = this.m_debugDraw.GetFlags();
let i: number /** int */;
let b: b2Body;
let f: b2Fixture;
let s: b2Shape;
let j: b2Joint;
let bp: IBroadPhase;
const invQ: b2Vec2 = new b2Vec2;
const x1: b2Vec2 = new b2Vec2;
const x2: b2Vec2 = new b2Vec2;
let xf: b2Transform;
const b1: b2AABB = new b2AABB();
const b2: b2AABB = new b2AABB();
let vs: Array<b2Vec2> = [new b2Vec2(), new b2Vec2(), new b2Vec2(), new b2Vec2()];
// Store color here and reuse, to reduce allocations
const color: b2Color = new b2Color(0, 0, 0);
if (flags & b2DebugDraw.e_shapeBit) {
for (b = this.m_bodyList; b; b = b.m_next) {
xf = b.m_xf;
for (f = b.GetFixtureList(); f; f = f.m_next) {
s = f.GetShape();
if (b.IsActive() == false) {
color.Set(0.5, 0.5, 0.3);
this.DrawShape(s, xf, color);
} else if (b.GetType() == b2Body.b2_staticBody) {
color.Set(0.5, 0.9, 0.5);
this.DrawShape(s, xf, color);
} else if (b.GetType() == b2Body.b2_kinematicBody) {
color.Set(0.5, 0.5, 0.9);
this.DrawShape(s, xf, color);
} else if (b.IsAwake() == false) {
color.Set(0.6, 0.6, 0.6);
this.DrawShape(s, xf, color);
} else {
color.Set(0.9, 0.7, 0.7);
this.DrawShape(s, xf, color);
}
}
}
}
if (flags & b2DebugDraw.e_jointBit) {
for (j = this.m_jointList; j; j = j.m_next) {
this.DrawJoint(j);
}
}
if (flags & b2DebugDraw.e_controllerBit) {
for (let c: b2Controller = this.m_controllerList; c; c = c.m_next) {
c.Draw(this.m_debugDraw);
}
}
if (flags & b2DebugDraw.e_pairBit) {
color.Set(0.3, 0.9, 0.9);
for (let contact: b2Contact = this.m_contactManager.m_contactList; contact; contact = contact.GetNext()) {
const fixtureA: b2Fixture = contact.GetFixtureA();
const fixtureB: b2Fixture = contact.GetFixtureB();
const cA: b2Vec2 = fixtureA.GetAABB().GetCenter();
const cB: b2Vec2 = fixtureB.GetAABB().GetCenter();
this.m_debugDraw.DrawSegment(cA, cB, color);
}
}
if (flags & b2DebugDraw.e_aabbBit) {
bp = this.m_contactManager.m_broadPhase;
vs = [new b2Vec2(),new b2Vec2(),new b2Vec2(),new b2Vec2()];
for (b = this.m_bodyList; b; b = b.GetNext()) {
if (b.IsActive() == false) {
continue;
}
for (f = b.GetFixtureList(); f; f = f.GetNext()) {
const aabb: b2AABB = bp.GetFatAABB(f.m_proxy);
vs[0].Set(aabb.lowerBound.x, aabb.lowerBound.y);
vs[1].Set(aabb.upperBound.x, aabb.lowerBound.y);
vs[2].Set(aabb.upperBound.x, aabb.upperBound.y);
vs[3].Set(aabb.lowerBound.x, aabb.upperBound.y);
this.m_debugDraw.DrawPolygon(vs, 4, color);
}
}
}
if (flags & b2DebugDraw.e_centerOfMassBit) {
for (b = this.m_bodyList; b; b = b.m_next) {
xf = b2World.s_xf;
xf.R = b.m_xf.R;
xf.position = b.GetWorldCenter();
this.m_debugDraw.DrawTransform(xf);
}
}
}
/**
* Query the world for all fixtures that potentially overlap the
* provided AABB.
* @param callback a user implemented callback class. It should match signature
* <code>function Callback(fixture:b2Fixture):boolean</code>
* Return true to continue to the next fixture.
* @param aabb the query box.
*/
public QueryAABB(callback: Function | ASMethodClosure, aabb: b2AABB): void {
const broadPhase: IBroadPhase = this.m_contactManager.m_broadPhase;
function WorldQueryWrapper(proxy: any): boolean {
if (typeof callback === 'function') {
return callback(broadPhase.GetUserData(proxy));
} else {
return callback.axApply(null, [broadPhase.GetUserData(proxy)]);
}
}
broadPhase.Query(WorldQueryWrapper, aabb);
}
/**
* Query the world for all fixtures that precisely overlap the
* provided transformed shape.
* @param callback a user implemented callback class. It should match signature
* <code>function Callback(fixture:b2Fixture):boolean</code>
* Return true to continue to the next fixture.
* @asonly
*/
public QueryShape(callback: Function, shape: b2Shape, transform: b2Transform = null): void {
if (transform == null) {
transform = new b2Transform();
transform.SetIdentity();
}
const broadPhase: IBroadPhase = this.m_contactManager.m_broadPhase;
function WorldQueryWrapper(proxy: any): boolean {
const fixture: b2Fixture = broadPhase.GetUserData(proxy) as b2Fixture;
if (b2Shape.TestOverlap(shape, transform, fixture.GetShape(), fixture.GetBody().GetTransform()))
return callback(fixture);
return true;
}
const aabb: b2AABB = new b2AABB();
shape.ComputeAABB(aabb, transform);
broadPhase.Query(WorldQueryWrapper, aabb);
}
/**
* Query the world for all fixtures that contain a point.
* @param callback a user implemented callback class. It should match signature
* <code>function Callback(fixture:b2Fixture):boolean</code>
* Return true to continue to the next fixture.
* @asonly
*/
public QueryPoint(callback: Function, p: b2Vec2): void {
const broadPhase: IBroadPhase = this.m_contactManager.m_broadPhase;
function WorldQueryWrapper(proxy: any): boolean {
const fixture: b2Fixture = broadPhase.GetUserData(proxy) as b2Fixture;
if (fixture.TestPoint(p))
return callback(fixture);
return true;
}
// Make a small box.
const aabb: b2AABB = new b2AABB();
aabb.lowerBound.Set(p.x - b2Settings.b2_linearSlop, p.y - b2Settings.b2_linearSlop);
aabb.upperBound.Set(p.x + b2Settings.b2_linearSlop, p.y + b2Settings.b2_linearSlop);
broadPhase.Query(WorldQueryWrapper, aabb);
}
/**
* Ray-cast the world for all fixtures in the path of the ray. Your callback
* Controls whether you get the closest point, any point, or n-points
* The ray-cast ignores shapes that contain the starting point
* @param callback A callback function which must be of signature:
* <code>function Callback(fixture:b2Fixture, // The fixture hit by the ray
* point:b2Vec2, // The point of initial intersection
* normal:b2Vec2, // The normal vector at the point of intersection
* fraction:number // The fractional length along the ray of the intersection
* ):number
* </code>
* Callback should return the new length of the ray as a fraction of the original length.
* By returning 0, you immediately terminate.
* By returning 1, you continue wiht the original ray.
* By returning the current fraction, you proceed to find the closest point.
* @param point1 the ray starting point
* @param point2 the ray ending point
*/
public RayCast(callback: Function, point1: b2Vec2, point2: b2Vec2): void {
const broadPhase: IBroadPhase = this.m_contactManager.m_broadPhase;
const output: b2RayCastOutput = new b2RayCastOutput;
function RayCastWrapper(input: b2RayCastInput, proxy: any): number {
const userData: any = broadPhase.GetUserData(proxy);
const fixture: b2Fixture = userData as b2Fixture;
const hit: boolean = fixture.RayCast(output, input);
if (hit && !fixture.IsSensor()) {
const fraction: number = output.fraction;
const point: b2Vec2 = new b2Vec2(
(1.0 - fraction) * point1.x + fraction * point2.x,
(1.0 - fraction) * point1.y + fraction * point2.y);
return callback(fixture, point, output.normal, fraction);
}
return input.maxFraction;
}
const input: b2RayCastInput = new b2RayCastInput(point1, point2);
broadPhase.RayCast(RayCastWrapper, input);
}
public RayCastOne(point1: b2Vec2, point2: b2Vec2): b2Fixture {
let result: b2Fixture;
let best: number = Number.MAX_VALUE;
function RayCastOneWrapper(fixture: b2Fixture, point: b2Vec2, normal: b2Vec2, fraction: number): number {
if (fraction <= best) {
best = fraction;
result = fixture;
}
return best;
}
this.RayCast(RayCastOneWrapper, point1, point2);
return result;
}
public RayCastAll(point1: b2Vec2, point2: b2Vec2): Array<b2Fixture> {
const result: Array<b2Fixture> = new Array<b2Fixture>();
function RayCastAllWrapper(fixture: b2Fixture, point: b2Vec2, normal: b2Vec2, fraction: number): number {
result[result.length] = fixture;
return 1;
}
this.RayCast(RayCastAllWrapper, point1, point2);
return result;
}
/**
* Get the world body list. With the returned body, use b2Body::GetNext to get
* the next body in the world list. A NULL body indicates the end of the list.
* @return the head of the world body list.
*/
public GetBodyList(): b2Body {
return this.m_bodyList;
}
/**
* Get the world joint list. With the returned joint, use b2Joint::GetNext to get
* the next joint in the world list. A NULL joint indicates the end of the list.
* @return the head of the world joint list.
*/
public GetJointList(): b2Joint {
return this.m_jointList;
}
/**
* Get the world contact list. With the returned contact, use b2Contact::GetNext to get
* the next contact in the world list. A NULL contact indicates the end of the list.
* @return the head of the world contact list.
* @warning contacts are
*/
public GetContactList(): b2Contact {
return this.m_contactList;
}
/**
* Is the world locked (in the middle of a time step).
*/
public IsLocked(): boolean {
return (this.m_flags & b2World.e_locked) > 0;
}
//--------------- Internals Below -------------------
// Internal yet public to make life easier.
// Find islands, integrate and solve constraints, solve position constraints
private s_stack: Array<b2Body> = new Array<b2Body>();
public Solve(step: b2TimeStep): void {
let b: b2Body;
// Step all controllers
for (let controller: b2Controller = this.m_controllerList;controller;controller = controller.m_next) {
controller.Step(step);
}
// Size the island for the worst case.
const island: b2Island = this.m_island;
island.Initialize(this.m_bodyCount, this.m_contactCount, this.m_jointCount, null, this.m_contactManager.m_contactListener, this.m_contactSolver);
// Clear all the island flags.
for (b = this.m_bodyList; b; b = b.m_next) {
b.m_flags &= ~b2Body.e_islandFlag;
}
for (let c: b2Contact = this.m_contactList; c; c = c.m_next) {
c.m_flags &= ~b2Contact.e_islandFlag;
}
for (let j: b2Joint = this.m_jointList; j; j = j.m_next) {
j.m_islandFlag = false;
}
// Build and simulate all awake islands.
const stackSize: number /** int */ = this.m_bodyCount;
//b2Body** stack = (b2Body**)this.m_stackAllocator.Allocate(stackSize * sizeof(b2Body*));
const stack: Array<b2Body> = this.s_stack;
for (let seed: b2Body = this.m_bodyList; seed; seed = seed.m_next) {
if (seed.m_flags & b2Body.e_islandFlag) {
continue;
}
if (seed.IsAwake() == false || seed.IsActive() == false) {
continue;
}
// The seed can be dynamic or kinematic.
if (seed.GetType() == b2Body.b2_staticBody) {
continue;
}
// Reset island and stack.
island.Clear();
let stackCount: number /** int */ = 0;
stack[stackCount++] = seed;
seed.m_flags |= b2Body.e_islandFlag;
// Perform a depth first search (DFS) on the constraint graph.
while (stackCount > 0) {
// Grab the next body off the stack and add it to the island.
b = stack[--stackCount];
//b2Assert(b.IsActive() == true);
island.AddBody(b);
// Make sure the body is awake.
if (b.IsAwake() == false) {
b.SetAwake(true);
}
// To keep islands as small as possible, we don't
// propagate islands across static bodies.
if (b.GetType() == b2Body.b2_staticBody) {
continue;
}
var other: b2Body;
// Search all contacts connected to this body.
for (let ce: b2ContactEdge = b.m_contactList; ce; ce = ce.next) {
// Has this contact already been added to an island?
if (ce.contact.m_flags & b2Contact.e_islandFlag) {
continue;
}
// Is this contact solid and touching?
if (ce.contact.IsSensor() == true ||
ce.contact.IsEnabled() == false ||
ce.contact.IsTouching() == false) {
continue;
}
island.AddContact(ce.contact);
ce.contact.m_flags |= b2Contact.e_islandFlag;
//var other:b2Body = ce.other;
other = ce.other;
// Was the other body already added to this island?
if (other.m_flags & b2Body.e_islandFlag) {
continue;
}
//b2Settings.b2Assert(stackCount < stackSize);
stack[stackCount++] = other;
other.m_flags |= b2Body.e_islandFlag;
}
// Search all joints connect to this body.
for (let jn: b2JointEdge = b.m_jointList; jn; jn = jn.next) {
if (jn.joint.m_islandFlag == true) {
continue;
}
other = jn.other;
// Don't simulate joints connected to inactive bodies.
if (other.IsActive() == false) {
continue;
}
island.AddJoint(jn.joint);
jn.joint.m_islandFlag = true;
if (other.m_flags & b2Body.e_islandFlag) {
continue;
}
//b2Settings.b2Assert(stackCount < stackSize);
stack[stackCount++] = other;
other.m_flags |= b2Body.e_islandFlag;
}
}
island.Solve(step, this.m_gravity, this.m_allowSleep);
// Post solve cleanup.
for (var i: number /** int */ = 0; i < island.m_bodyCount; ++i) {
// Allow static bodies to participate in other islands.
b = island.m_bodies[i];
if (b.GetType() == b2Body.b2_staticBody) {
b.m_flags &= ~b2Body.e_islandFlag;
}
}
}
//this.m_stackAllocator.Free(stack);
for (i = 0; i < stack.length;++i) {
if (!stack[i]) break;
stack[i] = null;
}
// Synchronize fixutres, check for out of range bodies.
for (b = this.m_bodyList; b; b = b.m_next) {
if (b.IsAwake() == false || b.IsActive() == false) {
continue;
}
if (b.GetType() == b2Body.b2_staticBody) {
continue;
}
// Update fixtures (for broad-phase).
b.SynchronizeFixtures();
}
// Look for new contacts.
this.m_contactManager.FindNewContacts();
}
private static s_backupA: b2Sweep = new b2Sweep();
private static s_backupB: b2Sweep = new b2Sweep();
private static s_timestep: b2TimeStep = new b2TimeStep();
private static s_queue: Array<b2Body> = new Array<b2Body>();
// Find TOI contacts and solve them.
public SolveTOI(step: b2TimeStep): void {
let b: b2Body;
let fA: b2Fixture;
let fB: b2Fixture;
let bA: b2Body;
let bB: b2Body;
let cEdge: b2ContactEdge;
let j: b2Joint;
// Reserve an island and a queue for TOI island solution.
const island: b2Island = this.m_island;
island.Initialize(this.m_bodyCount, b2Settings.b2_maxTOIContactsPerIsland, b2Settings.b2_maxTOIJointsPerIsland, null, this.m_contactManager.m_contactListener, this.m_contactSolver);
//Simple one pass queue
//Relies on the fact that we're only making one pass
//through and each body can only be pushed/popped one.
//To push:
// queue[queueStart+queueSize++] = newElement;
//To pop:
// poppedElement = queue[queueStart++];
// --queueSize;
const queue: Array<b2Body> = b2World.s_queue;
for (b = this.m_bodyList; b; b = b.m_next) {
b.m_flags &= ~b2Body.e_islandFlag;
b.m_sweep.t0 = 0.0;
}
let c: b2Contact;
for (c = this.m_contactList; c; c = c.m_next) {
// Invalidate TOI
c.m_flags &= ~(b2Contact.e_toiFlag | b2Contact.e_islandFlag);
}
for (j = this.m_jointList; j; j = j.m_next) {
j.m_islandFlag = false;
}
// Find TOI events and solve them.
for (;;) {
// Find the first TOI.
let minContact: b2Contact = null;
let minTOI: number = 1.0;
for (c = this.m_contactList; c; c = c.m_next) {
// Can this contact generate a solid TOI contact?
if (c.IsSensor() == true ||
c.IsEnabled() == false ||
c.IsContinuous() == false) {
continue;
}
// TODO_ERIN keep a counter on the contact, only respond to M TOIs per contact.
let toi: number = 1.0;
if (c.m_flags & b2Contact.e_toiFlag) {
// This contact has a valid cached TOI.
toi = c.m_toi;
} else {
// Compute the TOI for this contact.
fA = c.m_fixtureA;
fB = c.m_fixtureB;
bA = fA.m_body;
bB = fB.m_body;
if ((bA.GetType() != b2Body.b2_dynamicBody || bA.IsAwake() == false) &&
(bB.GetType() != b2Body.b2_dynamicBody || bB.IsAwake() == false)) {
continue;
}
// Put the sweeps onto the same time interval.
let t0: number = bA.m_sweep.t0;
if (bA.m_sweep.t0 < bB.m_sweep.t0) {
t0 = bB.m_sweep.t0;
bA.m_sweep.Advance(t0);
} else if (bB.m_sweep.t0 < bA.m_sweep.t0) {
t0 = bA.m_sweep.t0;
bB.m_sweep.Advance(t0);
}
//b2Settings.b2Assert(t0 < 1.0f);
// Compute the time of impact.
toi = c.ComputeTOI(bA.m_sweep, bB.m_sweep);
b2Settings.b2Assert(0.0 <= toi && toi <= 1.0);
// If the TOI is in range ...
if (toi > 0.0 && toi < 1.0) {
// Interpolate on the actual range.
//toi = Math.min((1.0 - toi) * t0 + toi, 1.0);
toi = (1.0 - toi) * t0 + toi;
if (toi > 1) toi = 1;
}
c.m_toi = toi;
c.m_flags |= b2Contact.e_toiFlag;
}
if (Number.MIN_VALUE < toi && toi < minTOI) {
// This is the minimum TOI found so far.
minContact = c;
minTOI = toi;
}
}
if (minContact == null || 1.0 - 100.0 * Number.MIN_VALUE < minTOI) {
// No more TOI events. Done!
break;
}
// Advance the bodies to the TOI.
fA = minContact.m_fixtureA;
fB = minContact.m_fixtureB;
bA = fA.m_body;
bB = fB.m_body;
b2World.s_backupA.Set(bA.m_sweep);
b2World.s_backupB.Set(bB.m_sweep);
bA.Advance(minTOI);
bB.Advance(minTOI);
// The TOI contact likely has some new contact points.
minContact.Update(this.m_contactManager.m_contactListener);
minContact.m_flags &= ~b2Contact.e_toiFlag;
// Is the contact solid?
if (minContact.IsSensor() == true ||
minContact.IsEnabled() == false) {
// Restore the sweeps
bA.m_sweep.Set(b2World.s_backupA);
bB.m_sweep.Set(b2World.s_backupB);
bA.SynchronizeTransform();
bB.SynchronizeTransform();
continue;
}
// Did numerical issues prevent;,ontact pointjrom being generated
if (minContact.IsTouching() == false) {
// Give up on this TOI
continue;
}
// Build the TOI island. We need a dynamic seed.
let seed: b2Body = bA;
if (seed.GetType() != b2Body.b2_dynamicBody) {
seed = bB;
}
// Reset island and queue.
island.Clear();
let queueStart: number /** int */ = 0; //start index for queue
let queueSize: number /** int */ = 0; //elements in queue
queue[queueStart + queueSize++] = seed;
seed.m_flags |= b2Body.e_islandFlag;
// Perform a breadth first search (BFS) on the contact graph.
while (queueSize > 0) {
// Grab the next body off the stack and add it to the island.
b = queue[queueStart++];
--queueSize;
island.AddBody(b);
// Make sure the body is awake.
if (b.IsAwake() == false) {
b.SetAwake(true);
}
// To keep islands as small as possible, we don't
// propagate islands across static or kinematic bodies.
if (b.GetType() != b2Body.b2_dynamicBody) {
continue;
}
// Search all contacts connected to this body.
for (cEdge = b.m_contactList; cEdge; cEdge = cEdge.next) {
// Does the TOI island still have space for contacts?
if (island.m_contactCount == island.m_contactCapacity) {
break;
}
// Has this contact already been added to an island?
if (cEdge.contact.m_flags & b2Contact.e_islandFlag) {
continue;
}
// Skip sperate, sensor, or disabled contacts.
if (cEdge.contact.IsSensor() == true ||
cEdge.contact.IsEnabled() == false ||
cEdge.contact.IsTouching() == false) {
continue;
}
island.AddContact(cEdge.contact);
cEdge.contact.m_flags |= b2Contact.e_islandFlag;
// Update other body.
var other: b2Body = cEdge.other;
// Was the other body already added to this island?
if (other.m_flags & b2Body.e_islandFlag) {
continue;
}
// Synchronize the connected body.
if (other.GetType() != b2Body.b2_staticBody) {
other.Advance(minTOI);
other.SetAwake(true);
}
//b2Settings.b2Assert(queueStart + queueSize < queueCapacity);
queue[queueStart + queueSize] = other;
++queueSize;
other.m_flags |= b2Body.e_islandFlag;
}
for (let jEdge: b2JointEdge = b.m_jointList; jEdge; jEdge = jEdge.next) {
if (island.m_jointCount == island.m_jointCapacity)
continue;
if (jEdge.joint.m_islandFlag == true)
continue;
other = jEdge.other;
if (other.IsActive() == false) {
continue;
}
island.AddJoint(jEdge.joint);
jEdge.joint.m_islandFlag = true;
if (other.m_flags & b2Body.e_islandFlag)
continue;
// Synchronize the connected body.
if (other.GetType() != b2Body.b2_staticBody) {
other.Advance(minTOI);
other.SetAwake(true);
}
//b2Settings.b2Assert(queueStart + queueSize < queueCapacity);
queue[queueStart + queueSize] = other;
++queueSize;
other.m_flags |= b2Body.e_islandFlag;
}
}
const subStep: b2TimeStep = b2World.s_timestep;
subStep.warmStarting = false;
subStep.dt = (1.0 - minTOI) * step.dt;
subStep.inv_dt = 1.0 / subStep.dt;
subStep.dtRatio = 0.0;
subStep.velocityIterations = step.velocityIterations;
subStep.positionIterations = step.positionIterations;
island.SolveTOI(subStep);
var i: number /** int */;
// Post solve cleanup.
for (i = 0; i < island.m_bodyCount; ++i) {
// Allow bodies to participate in future TOI islands.
b = island.m_bodies[i];
b.m_flags &= ~b2Body.e_islandFlag;
if (b.IsAwake() == false) {
continue;
}
if (b.GetType() != b2Body.b2_dynamicBody) {
continue;
}
// Update fixtures (for broad-phase).
b.SynchronizeFixtures();
// Invalidate all contact TOIs associated with this body. Some of these
// may not be in the island because they were not touching.
for (cEdge = b.m_contactList; cEdge; cEdge = cEdge.next) {
cEdge.contact.m_flags &= ~b2Contact.e_toiFlag;
}
}
for (i = 0; i < island.m_contactCount; ++i) {
// Allow contacts to participate in future TOI islands.
c = island.m_contacts[i];
c.m_flags &= ~(b2Contact.e_toiFlag | b2Contact.e_islandFlag);
}
for (i = 0; i < island.m_jointCount;++i) {
// Allow joints to participate in future TOI islands
j = island.m_joints[i];
j.m_islandFlag = false;
}
// Commit fixture proxy movements to the broad-phase so that new contacts are created.
// Also, some contacts can be destroyed.
this.m_contactManager.FindNewContacts();
}
//this.m_stackAllocator.Free(queue);
}
private static s_jointColor: b2Color = new b2Color(0.5, 0.8, 0.8);
//
public DrawJoint(joint: b2Joint): void {
const b1: b2Body = joint.GetBodyA();
const b2: b2Body = joint.GetBodyB();
const xf1: b2Transform = b1.m_xf;
const xf2: b2Transform = b2.m_xf;
const x1: b2Vec2 = xf1.position;
const x2: b2Vec2 = xf2.position;
const p1: b2Vec2 = joint.GetAnchorA();
const p2: b2Vec2 = joint.GetAnchorB();
//b2Color color(0.5f, 0.8f, 0.8f);
const color: b2Color = b2World.s_jointColor;
switch (joint.m_type) {
case b2Joint.e_distanceJoint:
this.m_debugDraw.DrawSegment(p1, p2, color);
break;
case b2Joint.e_pulleyJoint:
{
const pulley: b2PulleyJoint = (joint as b2PulleyJoint);
const s1: b2Vec2 = pulley.GetGroundAnchorA();
const s2: b2Vec2 = pulley.GetGroundAnchorB();
this.m_debugDraw.DrawSegment(s1, p1, color);
this.m_debugDraw.DrawSegment(s2, p2, color);
this.m_debugDraw.DrawSegment(s1, s2, color);
}
break;
case b2Joint.e_mouseJoint:
this.m_debugDraw.DrawSegment(p1, p2, color);
break;
default:
if (b1 != this.m_groundBody)
this.m_debugDraw.DrawSegment(x1, p1, color);
this.m_debugDraw.DrawSegment(p1, p2, color);
if (b2 != this.m_groundBody)
this.m_debugDraw.DrawSegment(x2, p2, color);
}
}
public DrawShape(shape: b2Shape, xf: b2Transform, color: b2Color): void {
switch (shape.m_type) {
case b2Shape.e_circleShape:
{
const circle: b2CircleShape = <b2CircleShape> shape;
const center: b2Vec2 = b2Math.MulX(xf, circle.m_p);
const radius: number = circle.m_radius;
const axis: b2Vec2 = xf.R.col1;
this.m_debugDraw.DrawSolidCircle(center, radius, axis, color);
}
break;
case b2Shape.e_polygonShape:
{
let i: number /** int */;
const poly: b2PolygonShape = <b2PolygonShape> shape;
const vertexCount: number /** int */ = poly.GetVertexCount();
const localVertices: Array<b2Vec2> = poly.GetVertices();
const vertices: Array<b2Vec2> = new Array<b2Vec2>(vertexCount);
for (i = 0; i < vertexCount; ++i) {
vertices[i] = b2Math.MulX(xf, localVertices[i]);
}
this.m_debugDraw.DrawSolidPolygon(vertices, vertexCount, color);
}
break;
case b2Shape.e_edgeShape:
{
const edge: b2EdgeShape = shape as b2EdgeShape;
this.m_debugDraw.DrawSegment(b2Math.MulX(xf, edge.GetVertex1()), b2Math.MulX(xf, edge.GetVertex2()), color);
}
break;
}
}
public m_flags: number /** int */;
public m_contactManager: b2ContactManager = new b2ContactManager();
// These two are stored purely for efficiency purposes, they don't maintain
// any data outside of a call to Step
public m_contactSolver: b2ContactSolver = new b2ContactSolver();
public m_island: b2Island = new b2Island();
public m_bodyList: b2Body;
private m_jointList: b2Joint;
public m_contactList: b2Contact;
public m_bodyCount: number /** int */;
public m_contactCount: number /** int */;
private m_jointCount: number /** int */;
private m_controllerList: b2Controller;
private m_controllerCount: number /** int */;
private m_gravity: b2Vec2;
private m_allowSleep: boolean;
public m_groundBody: b2Body;
public m_destructionListener: b2DestructionListener;
private m_debugDraw: b2DebugDraw;
// This is used to compute the time step ratio to support a variable time step.
private m_inv_dt0: number;
// This is for debugging the solver.
private static m_warmStarting: boolean;
// This is for debugging the solver.
private static m_continuousPhysics: boolean;
// this.m_flags
public static readonly e_newFixture: number /** int */ = 0x0001;
public static readonly e_locked: number /** int */ = 0x0002;
}