planck
Version:
2D JavaScript/TypeScript physics engine for cross-platform HTML5 game development
1,193 lines (1,030 loc) • 33.9 kB
text/typescript
/*
* Planck.js
*
* Copyright (c) Erin Catto, Ali Shakiba
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as matrix from "../common/Matrix";
import { options } from "../util/options";
import { Vec2, Vec2Value } from "../common/Vec2";
import { Rot } from "../common/Rot";
import { Sweep } from "../common/Sweep";
import { Transform, TransformValue } from "../common/Transform";
import { Velocity } from "./Velocity";
import { Position } from "./Position";
import { Fixture, FixtureDef, FixtureOpt } from "./Fixture";
import { Shape } from "../collision/Shape";
import { JointEdge } from "./Joint";
import { World } from "./World";
import { ContactEdge } from "./Contact";
import { Style } from "../util/Testbed";
/** @internal */ const _ASSERT = typeof ASSERT === "undefined" ? false : ASSERT;
/**
* A static body does not move under simulation and behaves as if it has infinite mass.
* Internally, zero is stored for the mass and the inverse mass.
* Static bodies can be moved manually by the user.
* A static body has zero velocity.
* Static bodies do not collide with other static or kinematic bodies.
*
* A kinematic body moves under simulation according to its velocity.
* Kinematic bodies do not respond to forces.
* They can be moved manually by the user, but normally a kinematic body is moved by setting its velocity.
* A kinematic body behaves as if it has infinite mass, however, zero is stored for the mass and the inverse mass.
* Kinematic bodies do not collide with other kinematic or static bodies.
*
* A dynamic body is fully simulated.
* They can be moved manually by the user, but normally they move according to forces.
* A dynamic body can collide with all body types.
* A dynamic body always has finite, non-zero mass.
* If you try to set the mass of a dynamic body to zero, it will automatically acquire a mass of one kilogram and it won't rotate.
*/
export type BodyType = "static" | "kinematic" | "dynamic";
/** @internal */ const STATIC = "static";
/** @internal */ const KINEMATIC = "kinematic";
/** @internal */ const DYNAMIC = "dynamic";
/** @internal */ const oldCenter = matrix.vec2(0, 0);
/** @internal */ const localCenter = matrix.vec2(0, 0);
/** @internal */ const shift = matrix.vec2(0, 0);
/** @internal */ const temp = matrix.vec2(0, 0);
/** @internal */ const xf = matrix.transform(0, 0, 0);
export interface BodyDef {
/**
* Body types are static, kinematic, or dynamic. Note: if a dynamic
* body would have zero mass, the mass is set to one.
*/
type?: BodyType;
/**
* The world position of the body. Avoid creating bodies at the
* origin since this can lead to many overlapping shapes.
*/
position?: Vec2Value;
/**
* The world angle of the body in radians.
*/
angle?: number;
/**
* The linear velocity of the body's origin in world co-ordinates.
*/
linearVelocity?: Vec2Value;
angularVelocity?: number;
/**
* Linear damping is use to reduce the linear velocity. The
* damping parameter can be larger than 1.0 but the damping effect becomes
* sensitive to the time step when the damping parameter is large.
* Units are 1/time
*/
linearDamping?: number;
/**
* Angular damping is use to reduce the angular velocity.
* The damping parameter can be larger than 1.0 but the damping effect
* becomes sensitive to the time step when the damping parameter is large.
* Units are 1/time
*/
angularDamping?: number;
/**
* Should this body be prevented from rotating? Useful for characters.
*/
fixedRotation?: boolean;
/**
* Is this a fast moving body that should be prevented from
* tunneling through other moving bodies? Note that all bodies are
* prevented from tunneling through kinematic and static bodies. This
* setting is only considered on dynamic bodies. Warning: You should use
* this flag sparingly since it increases processing time.
*/
bullet?: boolean;
gravityScale?: number;
/**
* Set this flag to false if this body should never fall asleep. Note that this increases CPU usage.
*/
allowSleep?: boolean;
/**
* Is this body initially awake or sleeping?
*/
awake?: boolean;
/**
* Does this body start out active?
*/
active?: boolean;
userData?: any;
/** Styling for dev-tools. */
style?: Style;
}
/** @internal */ const BodyDefDefault: BodyDef = {
type: STATIC,
position: Vec2.zero(),
angle: 0.0,
linearVelocity: Vec2.zero(),
angularVelocity: 0.0,
linearDamping: 0.0,
angularDamping: 0.0,
fixedRotation: false,
bullet: false,
gravityScale: 1.0,
allowSleep: true,
awake: true,
active: true,
userData: null,
};
/**
* MassData This holds the mass data computed for a shape.
*/
export interface MassData {
/** The mass of the shape, usually in kilograms. */
mass: number;
/** The position of the shape's centroid relative to the shape's origin. */
center: Vec2Value;
/** The rotational inertia of the shape about the local origin. */
I: number;
}
/**
* A rigid body composed of one or more fixtures.
*
* To create a new Body use {@link World.createBody}.
*/
export class Body {
/** @hidden */
static readonly STATIC: BodyType = "static";
/** @hidden */
static readonly KINEMATIC: BodyType = "kinematic";
/** @hidden */
static readonly DYNAMIC: BodyType = "dynamic";
/** @internal */ m_world: World;
/** @internal */ m_awakeFlag: boolean;
/** @internal */ m_autoSleepFlag: boolean;
/** @internal */ m_bulletFlag: boolean;
/** @internal */ m_fixedRotationFlag: boolean;
/** @internal */ m_activeFlag: boolean;
/** @internal */ m_islandFlag: boolean;
/** @internal */ m_toiFlag: boolean;
/** @internal */ m_userData: unknown;
/** @internal */ m_type: BodyType;
/** @internal */ m_mass: number;
/** @internal */ m_invMass: number;
/** @internal Rotational inertia about the center of mass. */
m_I: number;
/** @internal */ m_invI: number;
/** @internal the body origin transform */
m_xf: Transform;
/** @internal the swept motion for CCD */
m_sweep: Sweep;
// position and velocity correction
/** @internal */ c_velocity: Velocity;
/** @internal */ c_position: Position;
/** @internal */ m_force: Vec2;
/** @internal */ m_torque: number;
/** @internal */ m_linearVelocity: Vec2;
/** @internal */ m_angularVelocity: number;
/** @internal */ m_linearDamping: number;
/** @internal */ m_angularDamping: number;
/** @internal */ m_gravityScale: number;
/** @internal */ m_sleepTime: number;
/** @internal */ m_jointList: JointEdge | null;
/** @internal */ m_contactList: ContactEdge | null;
/** @internal */ m_fixtureList: Fixture | null;
/** @internal */ m_prev: Body | null;
/** @internal */ m_next: Body | null;
/** @internal */ m_destroyed: boolean;
/** Styling for dev-tools. */
style: Style = {};
/** @hidden @experimental Similar to userData, but used by dev-tools or runtime environment. */
appData: Record<string, any> = {};
/** @internal */
constructor(world: World, def: BodyDef) {
def = options(def, BodyDefDefault);
if (_ASSERT) console.assert(Vec2.isValid(def.position));
if (_ASSERT) console.assert(Vec2.isValid(def.linearVelocity));
if (_ASSERT) console.assert(Number.isFinite(def.angle));
if (_ASSERT) console.assert(Number.isFinite(def.angularVelocity));
if (_ASSERT) console.assert(Number.isFinite(def.angularDamping) && def.angularDamping >= 0.0);
if (_ASSERT) console.assert(Number.isFinite(def.linearDamping) && def.linearDamping >= 0.0);
this.m_world = world;
this.m_awakeFlag = def.awake;
this.m_autoSleepFlag = def.allowSleep;
this.m_bulletFlag = def.bullet;
this.m_fixedRotationFlag = def.fixedRotation;
this.m_activeFlag = def.active;
this.m_islandFlag = false;
this.m_toiFlag = false;
this.m_userData = def.userData;
this.m_type = def.type;
if (this.m_type == DYNAMIC) {
this.m_mass = 1.0;
this.m_invMass = 1.0;
} else {
this.m_mass = 0.0;
this.m_invMass = 0.0;
}
// Rotational inertia about the center of mass.
this.m_I = 0.0;
this.m_invI = 0.0;
// the body origin transform
this.m_xf = Transform.identity();
this.m_xf.p.setVec2(def.position);
this.m_xf.q.setAngle(def.angle);
// the swept motion for CCD
this.m_sweep = new Sweep();
this.m_sweep.setTransform(this.m_xf);
// position and velocity correction
this.c_velocity = new Velocity();
this.c_position = new Position();
this.m_force = Vec2.zero();
this.m_torque = 0.0;
this.m_linearVelocity = Vec2.clone(def.linearVelocity);
this.m_angularVelocity = def.angularVelocity;
this.m_linearDamping = def.linearDamping;
this.m_angularDamping = def.angularDamping;
this.m_gravityScale = def.gravityScale;
this.m_sleepTime = 0.0;
this.m_jointList = null;
this.m_contactList = null;
this.m_fixtureList = null;
this.m_prev = null;
this.m_next = null;
this.m_destroyed = false;
if (typeof def.style === "object" && def.style !== null) {
this.style = def.style;
}
}
/** @hidden */
_serialize(): object {
const fixtures = [];
for (let f = this.m_fixtureList; f; f = f.m_next) {
fixtures.push(f);
}
return {
type: this.m_type,
bullet: this.m_bulletFlag,
fixedRotation: this.m_fixedRotationFlag,
position: this.m_xf.p,
angle: this.m_xf.q.getAngle(),
linearVelocity: this.m_linearVelocity,
angularVelocity: this.m_angularVelocity,
fixtures,
};
}
/** @hidden */
static _deserialize(data: any, world: any, restore: any): Body {
const body = new Body(world, data);
if (data.fixtures) {
for (let i = data.fixtures.length - 1; i >= 0; i--) {
const fixture = restore(Fixture, data.fixtures[i], body);
body._addFixture(fixture);
}
}
return body;
}
isWorldLocked(): boolean {
return this.m_world && this.m_world.isLocked() ? true : false;
}
getWorld(): World {
return this.m_world;
}
getNext(): Body | null {
return this.m_next;
}
setUserData(data: any): void {
this.m_userData = data;
}
getUserData(): unknown {
return this.m_userData;
}
getFixtureList(): Fixture | null {
return this.m_fixtureList;
}
getJointList(): JointEdge | null {
return this.m_jointList;
}
/**
* Warning: this list changes during the time step and you may miss some
* collisions if you don't use ContactListener.
*/
getContactList(): ContactEdge | null {
return this.m_contactList;
}
isStatic(): boolean {
return this.m_type == STATIC;
}
isDynamic(): boolean {
return this.m_type == DYNAMIC;
}
isKinematic(): boolean {
return this.m_type == KINEMATIC;
}
/**
* This will alter the mass and velocity.
*/
setStatic(): Body {
this.setType(STATIC);
return this;
}
setDynamic(): Body {
this.setType(DYNAMIC);
return this;
}
setKinematic(): Body {
this.setType(KINEMATIC);
return this;
}
/**
* Get the type of the body.
*/
getType(): BodyType {
return this.m_type;
}
/**
* Set the type of the body to "static", "kinematic" or "dynamic".
* @param type The type of the body.
*
* Warning: This function is locked when a world simulation step is in progress. Use queueUpdate to schedule a function to be called after the step.
*/
setType(type: BodyType): void {
if (_ASSERT) console.assert(type === STATIC || type === KINEMATIC || type === DYNAMIC);
if (_ASSERT) console.assert(this.isWorldLocked() == false);
if (this.isWorldLocked() == true) {
return;
}
if (this.m_type == type) {
return;
}
this.m_type = type;
this.resetMassData();
if (this.m_type == STATIC) {
this.m_linearVelocity.setZero();
this.m_angularVelocity = 0.0;
this.m_sweep.forward();
this.synchronizeFixtures();
}
this.setAwake(true);
this.m_force.setZero();
this.m_torque = 0.0;
// Delete the attached contacts.
let ce = this.m_contactList;
while (ce) {
const ce0 = ce;
ce = ce.next;
this.m_world.destroyContact(ce0.contact);
}
this.m_contactList = null;
// Touch the proxies so that new contacts will be created (when appropriate)
const broadPhase = this.m_world.m_broadPhase;
for (let f = this.m_fixtureList; f; f = f.m_next) {
for (let i = 0; i < f.m_proxyCount; ++i) {
broadPhase.touchProxy(f.m_proxies[i].proxyId);
}
}
}
isBullet(): boolean {
return this.m_bulletFlag;
}
/**
* Should this body be treated like a bullet for continuous collision detection?
*/
setBullet(flag: boolean): void {
this.m_bulletFlag = !!flag;
}
isSleepingAllowed(): boolean {
return this.m_autoSleepFlag;
}
setSleepingAllowed(flag: boolean): void {
this.m_autoSleepFlag = !!flag;
if (this.m_autoSleepFlag == false) {
this.setAwake(true);
}
}
isAwake(): boolean {
return this.m_awakeFlag;
}
/**
* Set the sleep state of the body. A sleeping body has very low CPU cost.
*
* @param flag Set to true to wake the body, false to put it to sleep.
*/
setAwake(flag: boolean): void {
if (flag) {
this.m_awakeFlag = true;
this.m_sleepTime = 0.0;
} else {
this.m_awakeFlag = false;
this.m_sleepTime = 0.0;
this.m_linearVelocity.setZero();
this.m_angularVelocity = 0.0;
this.m_force.setZero();
this.m_torque = 0.0;
}
}
isActive(): boolean {
return this.m_activeFlag;
}
/**
* Set the active state of the body. An inactive body is not simulated and
* cannot be collided with or woken up. If you pass a flag of true, all fixtures
* will be added to the broad-phase. If you pass a flag of false, all fixtures
* will be removed from the broad-phase and all contacts will be destroyed.
* Fixtures and joints are otherwise unaffected.
*
* You may continue to create/destroy fixtures and joints on inactive bodies.
* Fixtures on an inactive body are implicitly inactive and will not participate
* in collisions, ray-casts, or queries. Joints connected to an inactive body
* are implicitly inactive. An inactive body is still owned by a World object
* and remains
*
* Warning: This function is locked when a world simulation step is in progress. Use queueUpdate to schedule a function to be called after the step.
*/
setActive(flag: boolean): void {
if (_ASSERT) console.assert(this.isWorldLocked() == false);
if (flag == this.m_activeFlag) {
return;
}
this.m_activeFlag = !!flag;
if (this.m_activeFlag) {
// Create all proxies.
const broadPhase = this.m_world.m_broadPhase;
for (let f = this.m_fixtureList; f; f = f.m_next) {
f.createProxies(broadPhase, this.m_xf);
}
// Contacts are created at the beginning of the next
this.m_world.m_newFixture = true;
} else {
// Destroy all proxies.
const broadPhase = this.m_world.m_broadPhase;
for (let f = this.m_fixtureList; f; f = f.m_next) {
f.destroyProxies(broadPhase);
}
// Destroy the attached contacts.
let ce = this.m_contactList;
while (ce) {
const ce0 = ce;
ce = ce.next;
this.m_world.destroyContact(ce0.contact);
}
this.m_contactList = null;
}
}
isFixedRotation(): boolean {
return this.m_fixedRotationFlag;
}
/**
* Set this body to have fixed rotation. This causes the mass to be reset.
*/
setFixedRotation(flag: boolean): void {
if (this.m_fixedRotationFlag == flag) {
return;
}
this.m_fixedRotationFlag = !!flag;
this.m_angularVelocity = 0.0;
this.resetMassData();
}
/**
* Get the world transform for the body's origin.
*/
getTransform(): Transform {
return this.m_xf;
}
/**
* Set the position of the body's origin and rotation. Manipulating a body's
* transform may cause non-physical behavior. Note: contacts are updated on the
* next call to World.step.
*
* Warning: This function is locked when a world simulation step is in progress. Use queueUpdate to schedule a function to be called after the step.
*
* @param position The world position of the body's local origin.
* @param angle The world rotation in radians.
*/
setTransform(position: Vec2Value, angle: number): void;
/**
* Set the position of the body's origin and rotation. Manipulating a body's
* transform may cause non-physical behavior. Note: contacts are updated on the
* next call to World.step.
*
* Warning: This function is locked when a world simulation step is in progress. Use queueUpdate to schedule a function to be called after the step.
*/
setTransform(xf: Transform): void;
setTransform(a: Vec2Value | Transform, b?: number): void {
if (_ASSERT) console.assert(this.isWorldLocked() == false);
if (this.isWorldLocked() == true) {
return;
}
if (typeof b === "number") {
this.m_xf.setNum(a as Vec2Value, b);
} else {
this.m_xf.setTransform(a as TransformValue);
}
this.m_sweep.setTransform(this.m_xf);
const broadPhase = this.m_world.m_broadPhase;
for (let f = this.m_fixtureList; f; f = f.m_next) {
f.synchronize(broadPhase, this.m_xf, this.m_xf);
}
this.setAwake(true);
}
synchronizeTransform(): void {
this.m_sweep.getTransform(this.m_xf, 1);
}
/**
* Update fixtures in broad-phase.
*/
synchronizeFixtures(): void {
this.m_sweep.getTransform(xf, 0);
const broadPhase = this.m_world.m_broadPhase;
for (let f = this.m_fixtureList; f; f = f.m_next) {
f.synchronize(broadPhase, xf, this.m_xf);
}
}
/**
* Used in TOI.
*/
advance(alpha: number): void {
// Advance to the new safe time. This doesn't sync the broad-phase.
this.m_sweep.advance(alpha);
matrix.copyVec2(this.m_sweep.c, this.m_sweep.c0);
this.m_sweep.a = this.m_sweep.a0;
this.m_sweep.getTransform(this.m_xf, 1);
}
/**
* Get the world position for the body's origin.
*/
getPosition(): Vec2 {
return this.m_xf.p;
}
setPosition(p: Vec2Value): void {
this.setTransform(p, this.m_sweep.a);
}
/**
* Get the current world rotation angle in radians.
*/
getAngle(): number {
return this.m_sweep.a;
}
setAngle(angle: number): void {
this.setTransform(this.m_xf.p, angle);
}
/**
* Get the world position of the center of mass.
*/
getWorldCenter(): Vec2 {
return this.m_sweep.c;
}
/**
* Get the local position of the center of mass.
*/
getLocalCenter(): Vec2 {
return this.m_sweep.localCenter;
}
/**
* Get the linear velocity of the center of mass.
*
* @return the linear velocity of the center of mass.
*/
getLinearVelocity(): Vec2 {
return this.m_linearVelocity;
}
/**
* Get the world linear velocity of a world point attached to this body.
*
* @param worldPoint A point in world coordinates.
*/
getLinearVelocityFromWorldPoint(worldPoint: Vec2Value): Vec2 {
const localCenter = Vec2.sub(worldPoint, this.m_sweep.c);
return Vec2.add(this.m_linearVelocity, Vec2.crossNumVec2(this.m_angularVelocity, localCenter));
}
/**
* Get the world velocity of a local point.
*
* @param localPoint A point in local coordinates.
*/
getLinearVelocityFromLocalPoint(localPoint: Vec2Value): Vec2 {
return this.getLinearVelocityFromWorldPoint(this.getWorldPoint(localPoint));
}
/**
* Set the linear velocity of the center of mass.
*
* @param v The new linear velocity of the center of mass.
*/
setLinearVelocity(v: Vec2Value): void {
if (this.m_type == STATIC) {
return;
}
if (Vec2.dot(v, v) > 0.0) {
this.setAwake(true);
}
this.m_linearVelocity.setVec2(v);
}
/**
* Get the angular velocity.
*
* @returns the angular velocity in radians/second.
*/
getAngularVelocity(): number {
return this.m_angularVelocity;
}
/**
* Set the angular velocity.
*
* @param w The new angular velocity in radians/second.
*/
setAngularVelocity(w: number): void {
if (this.m_type == STATIC) {
return;
}
if (w * w > 0.0) {
this.setAwake(true);
}
this.m_angularVelocity = w;
}
getLinearDamping(): number {
return this.m_linearDamping;
}
setLinearDamping(linearDamping: number): void {
this.m_linearDamping = linearDamping;
}
getAngularDamping(): number {
return this.m_angularDamping;
}
setAngularDamping(angularDamping: number): void {
this.m_angularDamping = angularDamping;
}
getGravityScale(): number {
return this.m_gravityScale;
}
/**
* Scale the gravity applied to this body.
*/
setGravityScale(scale: number): void {
this.m_gravityScale = scale;
}
/**
* Get the total mass of the body.
*
* @returns The mass, usually in kilograms (kg).
*/
getMass(): number {
return this.m_mass;
}
/**
* Get the rotational inertia of the body about the local origin.
*
* @return the rotational inertia, usually in kg-m^2.
*/
getInertia(): number {
return this.m_I + this.m_mass * Vec2.dot(this.m_sweep.localCenter, this.m_sweep.localCenter);
}
/**
* Copy the mass data of the body to data.
*/
getMassData(data: MassData): void {
data.mass = this.m_mass;
data.I = this.getInertia();
matrix.copyVec2(data.center, this.m_sweep.localCenter);
}
/**
* This resets the mass properties to the sum of the mass properties of the
* fixtures. This normally does not need to be called unless you called
* SetMassData to override the mass and you later want to reset the mass.
*/
resetMassData(): void {
// Compute mass data from shapes. Each shape has its own density.
this.m_mass = 0.0;
this.m_invMass = 0.0;
this.m_I = 0.0;
this.m_invI = 0.0;
matrix.zeroVec2(this.m_sweep.localCenter);
// Static and kinematic bodies have zero mass.
if (this.isStatic() || this.isKinematic()) {
matrix.copyVec2(this.m_sweep.c0, this.m_xf.p);
matrix.copyVec2(this.m_sweep.c, this.m_xf.p);
this.m_sweep.a0 = this.m_sweep.a;
return;
}
if (_ASSERT) console.assert(this.isDynamic());
// Accumulate mass over all fixtures.
matrix.zeroVec2(localCenter);
for (let f = this.m_fixtureList; f; f = f.m_next) {
if (f.m_density == 0.0) {
continue;
}
const massData: MassData = {
mass: 0,
center: matrix.vec2(0, 0),
I: 0,
};
f.getMassData(massData);
this.m_mass += massData.mass;
matrix.plusScaleVec2(localCenter, massData.mass, massData.center);
this.m_I += massData.I;
}
// Compute center of mass.
if (this.m_mass > 0.0) {
this.m_invMass = 1.0 / this.m_mass;
matrix.scaleVec2(localCenter, this.m_invMass, localCenter);
} else {
// Force all dynamic bodies to have a positive mass.
this.m_mass = 1.0;
this.m_invMass = 1.0;
}
if (this.m_I > 0.0 && this.m_fixedRotationFlag == false) {
// Center the inertia about the center of mass.
this.m_I -= this.m_mass * matrix.dotVec2(localCenter, localCenter);
if (_ASSERT) console.assert(this.m_I > 0.0);
this.m_invI = 1.0 / this.m_I;
} else {
this.m_I = 0.0;
this.m_invI = 0.0;
}
// Move center of mass.
matrix.copyVec2(oldCenter, this.m_sweep.c);
this.m_sweep.setLocalCenter(localCenter, this.m_xf);
// Update center of mass velocity.
matrix.subVec2(shift, this.m_sweep.c, oldCenter);
matrix.crossNumVec2(temp, this.m_angularVelocity, shift);
matrix.plusVec2(this.m_linearVelocity, temp);
}
/**
* Set the mass properties to override the mass properties of the fixtures. Note
* that this changes the center of mass position. Note that creating or
* destroying fixtures can also alter the mass. This function has no effect if
* the body isn't dynamic.
*
* Warning: This function is locked when a world simulation step is in progress. Use queueUpdate to schedule a function to be called after the step.
*
* @param massData The mass properties.
*/
setMassData(massData: MassData): void {
if (_ASSERT) console.assert(this.isWorldLocked() == false);
if (this.isWorldLocked() == true) {
return;
}
if (this.m_type != DYNAMIC) {
return;
}
this.m_invMass = 0.0;
this.m_I = 0.0;
this.m_invI = 0.0;
this.m_mass = massData.mass;
if (this.m_mass <= 0.0) {
this.m_mass = 1.0;
}
this.m_invMass = 1.0 / this.m_mass;
if (massData.I > 0.0 && this.m_fixedRotationFlag == false) {
this.m_I = massData.I - this.m_mass * matrix.dotVec2(massData.center, massData.center);
if (_ASSERT) console.assert(this.m_I > 0.0);
this.m_invI = 1.0 / this.m_I;
}
// Move center of mass.
matrix.copyVec2(oldCenter, this.m_sweep.c);
this.m_sweep.setLocalCenter(massData.center, this.m_xf);
// Update center of mass velocity.
matrix.subVec2(shift, this.m_sweep.c, oldCenter);
matrix.crossNumVec2(temp, this.m_angularVelocity, shift);
matrix.plusVec2(this.m_linearVelocity, temp);
}
/**
* Apply a force at a world point. If the force is not applied at the center of
* mass, it will generate a torque and affect the angular velocity. This wakes
* up the body.
*
* @param force The world force vector, usually in Newtons (N).
* @param point The world position of the point of application.
* @param wake Also wake up the body
*/
applyForce(force: Vec2Value, point: Vec2Value, wake: boolean = true): void {
if (this.m_type != DYNAMIC) {
return;
}
if (wake && this.m_awakeFlag == false) {
this.setAwake(true);
}
// Don't accumulate a force if the body is sleeping.
if (this.m_awakeFlag) {
this.m_force.add(force);
this.m_torque += Vec2.crossVec2Vec2(Vec2.sub(point, this.m_sweep.c), force);
}
}
/**
* Apply a force to the center of mass. This wakes up the body.
*
* @param force The world force vector, usually in Newtons (N).
* @param wake Also wake up the body
*/
applyForceToCenter(force: Vec2Value, wake: boolean = true): void {
if (this.m_type != DYNAMIC) {
return;
}
if (wake && this.m_awakeFlag == false) {
this.setAwake(true);
}
// Don't accumulate a force if the body is sleeping
if (this.m_awakeFlag) {
this.m_force.add(force);
}
}
/**
* Apply a torque. This affects the angular velocity without affecting the
* linear velocity of the center of mass. This wakes up the body.
*
* @param torque About the z-axis (out of the screen), usually in N-m.
* @param wake Also wake up the body
*/
applyTorque(torque: number, wake: boolean = true): void {
if (this.m_type != DYNAMIC) {
return;
}
if (wake && this.m_awakeFlag == false) {
this.setAwake(true);
}
// Don't accumulate a force if the body is sleeping
if (this.m_awakeFlag) {
this.m_torque += torque;
}
}
/**
* Apply an impulse at a point. This immediately modifies the velocity. It also
* modifies the angular velocity if the point of application is not at the
* center of mass. This wakes up the body.
*
* @param impulse The world impulse vector, usually in N-seconds or kg-m/s.
* @param point The world position of the point of application.
* @param wake Also wake up the body
*/
applyLinearImpulse(impulse: Vec2Value, point: Vec2Value, wake: boolean = true): void {
if (this.m_type != DYNAMIC) {
return;
}
if (wake && this.m_awakeFlag == false) {
this.setAwake(true);
}
// Don't accumulate velocity if the body is sleeping
if (this.m_awakeFlag) {
this.m_linearVelocity.addMul(this.m_invMass, impulse);
this.m_angularVelocity += this.m_invI * Vec2.crossVec2Vec2(Vec2.sub(point, this.m_sweep.c), impulse);
}
}
/**
* Apply an angular impulse.
*
* @param impulse The angular impulse in units of kg*m*m/s
* @param wake Also wake up the body
*/
applyAngularImpulse(impulse: number, wake: boolean = true): void {
if (this.m_type != DYNAMIC) {
return;
}
if (wake && this.m_awakeFlag == false) {
this.setAwake(true);
}
// Don't accumulate velocity if the body is sleeping
if (this.m_awakeFlag) {
this.m_angularVelocity += this.m_invI * impulse;
}
}
/**
* This is used to test if two bodies should collide.
*
* Bodies do not collide when:
* - Neither of them is dynamic
* - They are connected by a joint with collideConnected == false
*/
shouldCollide(that: Body): boolean {
// At least one body should be dynamic.
if (this.m_type != DYNAMIC && that.m_type != DYNAMIC) {
return false;
}
// Does a joint prevent collision?
for (let jn = this.m_jointList; jn; jn = jn.next) {
if (jn.other == that) {
if (jn.joint.m_collideConnected == false) {
return false;
}
}
}
return true;
}
/** @internal Used for deserialize. */
_addFixture(fixture: Fixture): Fixture {
if (_ASSERT) console.assert(this.isWorldLocked() == false);
if (this.isWorldLocked() == true) {
return null;
}
if (this.m_activeFlag) {
const broadPhase = this.m_world.m_broadPhase;
fixture.createProxies(broadPhase, this.m_xf);
}
fixture.m_next = this.m_fixtureList;
this.m_fixtureList = fixture;
// Adjust mass properties if needed.
if (fixture.m_density > 0.0) {
this.resetMassData();
}
// Let the world know we have a new fixture. This will cause new contacts
// to be created at the beginning of the next time step.
this.m_world.m_newFixture = true;
return fixture;
}
/**
* Creates a fixture and attach it to this body.
*
* If the density is non-zero, this function automatically updates the mass of
* the body.
*
* Contacts are not created until the next time step.
*
* Warning: This function is locked when a world simulation step is in progress. Use queueUpdate to schedule a function to be called after the step.
*/
createFixture(def: FixtureDef): Fixture;
createFixture(shape: Shape, opt?: FixtureOpt): Fixture;
createFixture(shape: Shape, density?: number): Fixture;
// tslint:disable-next-line:typedef
createFixture(shape, fixdef?) {
if (_ASSERT) console.assert(this.isWorldLocked() == false);
if (this.isWorldLocked() == true) {
return null;
}
const fixture = new Fixture(this, shape, fixdef);
this._addFixture(fixture);
this.m_world.publish("add-fixture", fixture);
return fixture;
}
/**
* Destroy a fixture. This removes the fixture from the broad-phase and destroys
* all contacts associated with this fixture. This will automatically adjust the
* mass of the body if the body is dynamic and the fixture has positive density.
* All fixtures attached to a body are implicitly destroyed when the body is
* destroyed.
*
* Warning: This function is locked when a world simulation step is in progress. Use queueUpdate to schedule a function to be called after the step.
*
* @param fixture The fixture to be removed.
*/
destroyFixture(fixture: Fixture): void {
if (_ASSERT) console.assert(this.isWorldLocked() == false);
if (this.isWorldLocked() == true) {
return;
}
if (_ASSERT) console.assert(fixture.m_body == this);
// Remove the fixture from this body's singly linked list.
let found = false;
if (this.m_fixtureList === fixture) {
this.m_fixtureList = fixture.m_next;
found = true;
} else {
let node = this.m_fixtureList;
while (node != null) {
if (node.m_next === fixture) {
node.m_next = fixture.m_next;
found = true;
break;
}
node = node.m_next;
}
}
// You tried to remove a shape that is not attached to this body.
if (_ASSERT) console.assert(found);
// Destroy any contacts associated with the fixture.
let edge = this.m_contactList;
while (edge) {
const c = edge.contact;
edge = edge.next;
const fixtureA = c.getFixtureA();
const fixtureB = c.getFixtureB();
if (fixture == fixtureA || fixture == fixtureB) {
// This destroys the contact and removes it from
// this body's contact list.
this.m_world.destroyContact(c);
}
}
if (this.m_activeFlag) {
const broadPhase = this.m_world.m_broadPhase;
fixture.destroyProxies(broadPhase);
}
fixture.m_body = null;
fixture.m_next = null;
this.m_world.publish("remove-fixture", fixture);
// Reset the mass data.
this.resetMassData();
}
/**
* Get the corresponding world point of a local point.
*/
getWorldPoint(localPoint: Vec2Value): Vec2 {
return Transform.mulVec2(this.m_xf, localPoint);
}
/**
* Get the corresponding world vector of a local vector.
*/
getWorldVector(localVector: Vec2Value): Vec2 {
return Rot.mulVec2(this.m_xf.q, localVector);
}
/**
* Gets the corresponding local point of a world point.
*/
getLocalPoint(worldPoint: Vec2Value): Vec2 {
return Transform.mulTVec2(this.m_xf, worldPoint);
}
/**
* Gets the corresponding local vector of a world vector.
*/
getLocalVector(worldVector: Vec2Value): Vec2 {
return Rot.mulTVec2(this.m_xf.q, worldVector);
}
}