UNPKG

planck

Version:

2D JavaScript/TypeScript physics engine for cross-platform HTML5 game development

638 lines (560 loc) 15.9 kB
/* * 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 { clamp, EPSILON } from "./Math"; /** @internal */ const _ASSERT = typeof ASSERT === "undefined" ? false : ASSERT; /** @internal */ const _CONSTRUCTOR_FACTORY = typeof CONSTRUCTOR_FACTORY === "undefined" ? false : CONSTRUCTOR_FACTORY; /** @internal */ const math_abs = Math.abs; /** @internal */ const math_sqrt = Math.sqrt; /** @internal */ const math_max = Math.max; /** @internal */ const math_min = Math.min; /** 2D vector */ export interface Vec2Value { x: number; y: number; } declare module "./Vec2" { /** @hidden @deprecated Use new keyword. */ // @ts-expect-error function Vec2(x: number, y: number): Vec2; /** @hidden @deprecated Use new keyword. */ // @ts-expect-error function Vec2(obj: Vec2Value): Vec2; /** @hidden @deprecated Use new keyword. */ // @ts-expect-error function Vec2(): Vec2; } /** 2D vector */ // @ts-expect-error export class Vec2 { x: number; y: number; constructor(x: number, y: number); constructor(obj: Vec2Value); constructor(); // tslint:disable-next-line:typedef constructor(x?, y?) { if (_CONSTRUCTOR_FACTORY && !(this instanceof Vec2)) { return new Vec2(x, y); } if (typeof x === "undefined") { this.x = 0; this.y = 0; } else if (typeof x === "object") { this.x = x.x; this.y = x.y; } else { this.x = x; this.y = y; } if (_ASSERT) Vec2.assert(this); } /** @hidden */ _serialize(): object { return { x: this.x, y: this.y, }; } /** @hidden */ static _deserialize(data: any): Vec2 { const obj = Object.create(Vec2.prototype); obj.x = data.x; obj.y = data.y; return obj; } static zero(): Vec2 { const obj = Object.create(Vec2.prototype); obj.x = 0; obj.y = 0; return obj; } /** @hidden */ static neo(x: number, y: number): Vec2 { const obj = Object.create(Vec2.prototype); obj.x = x; obj.y = y; return obj; } static clone(v: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); return Vec2.neo(v.x, v.y); } /** @hidden */ toString(): string { return JSON.stringify(this); } /** * Does this vector contain finite coordinates? */ static isValid(obj: any): boolean { if (obj === null || typeof obj === "undefined") { return false; } return Number.isFinite(obj.x) && Number.isFinite(obj.y); } static assert(o: any): void { if (_ASSERT) console.assert(!Vec2.isValid(o), "Invalid Vec2!", o); } clone(): Vec2 { return Vec2.clone(this); } /** * Set this vector to all zeros. * * @returns this */ setZero(): Vec2 { this.x = 0.0; this.y = 0.0; return this; } set(x: number, y: number): Vec2; set(value: Vec2Value): Vec2; /** * Set this vector to some specified coordinates. * * @returns this */ // tslint:disable-next-line:typedef set(x, y?) { if (typeof x === "object") { if (_ASSERT) Vec2.assert(x); this.x = x.x; this.y = x.y; } else { if (_ASSERT) console.assert(Number.isFinite(x)); if (_ASSERT) console.assert(Number.isFinite(y)); this.x = x; this.y = y; } return this; } /** * Set this vector to some specified coordinates. * * @returns this */ setNum(x: number, y: number) { if (_ASSERT) console.assert(Number.isFinite(x)); if (_ASSERT) console.assert(Number.isFinite(y)); this.x = x; this.y = y; return this; } /** * Set this vector to some specified coordinates. * * @returns this */ setVec2(value: Vec2Value) { if (_ASSERT) Vec2.assert(value); this.x = value.x; this.y = value.y; return this; } /** @internal @deprecated Use setCombine or setMul */ wSet(a: number, v: Vec2Value, b?: number, w?: Vec2Value): Vec2 { if (typeof b !== "undefined" || typeof w !== "undefined") { return this.setCombine(a, v, b, w); } else { return this.setMul(a, v); } } /** * Set linear combination of v and w: `a * v + b * w` */ setCombine(a: number, v: Vec2Value, b: number, w: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(v); if (_ASSERT) console.assert(Number.isFinite(b)); if (_ASSERT) Vec2.assert(w); const x = a * v.x + b * w.x; const y = a * v.y + b * w.y; // `this` may be `w` this.x = x; this.y = y; return this; } setMul(a: number, v: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(v); const x = a * v.x; const y = a * v.y; this.x = x; this.y = y; return this; } /** * Add a vector to this vector. * * @returns this */ add(w: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(w); this.x += w.x; this.y += w.y; return this; } /** @internal @deprecated Use addCombine or addMul */ wAdd(a: number, v: Vec2Value, b?: number, w?: Vec2Value): Vec2 { if (typeof b !== "undefined" || typeof w !== "undefined") { return this.addCombine(a, v, b, w); } else { return this.addMul(a, v); } } /** * Add linear combination of v and w: `a * v + b * w` */ addCombine(a: number, v: Vec2Value, b: number, w: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(v); if (_ASSERT) console.assert(Number.isFinite(b)); if (_ASSERT) Vec2.assert(w); const x = a * v.x + b * w.x; const y = a * v.y + b * w.y; // `this` may be `w` this.x += x; this.y += y; return this; } addMul(a: number, v: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(v); const x = a * v.x; const y = a * v.y; this.x += x; this.y += y; return this; } /** * @deprecated Use subCombine or subMul */ wSub(a: number, v: Vec2Value, b?: number, w?: Vec2Value): Vec2 { if (typeof b !== "undefined" || typeof w !== "undefined") { return this.subCombine(a, v, b, w); } else { return this.subMul(a, v); } } /** * Subtract linear combination of v and w: `a * v + b * w` */ subCombine(a: number, v: Vec2Value, b: number, w: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(v); if (_ASSERT) console.assert(Number.isFinite(b)); if (_ASSERT) Vec2.assert(w); const x = a * v.x + b * w.x; const y = a * v.y + b * w.y; // `this` may be `w` this.x -= x; this.y -= y; return this; } subMul(a: number, v: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(v); const x = a * v.x; const y = a * v.y; this.x -= x; this.y -= y; return this; } /** * Subtract a vector from this vector * * @returns this */ sub(w: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(w); this.x -= w.x; this.y -= w.y; return this; } /** * Multiply this vector by a scalar. * * @returns this */ mul(m: number): Vec2 { if (_ASSERT) console.assert(Number.isFinite(m)); this.x *= m; this.y *= m; return this; } /** * Get the length of this vector (the norm). * * For performance, use this instead of lengthSquared (if possible). */ length(): number { return Vec2.lengthOf(this); } /** * Get the length squared. */ lengthSquared(): number { return Vec2.lengthSquared(this); } /** * Convert this vector into a unit vector. * * @returns old length */ normalize(): number { const length = this.length(); if (length < EPSILON) { return 0.0; } const invLength = 1.0 / length; this.x *= invLength; this.y *= invLength; return length; } /** * Returns a new unit vector from the provided vector. * * @returns new unit vector */ static normalize(v: Vec2Value): Vec2 { const length = Vec2.lengthOf(v); if (length < EPSILON) { return Vec2.zero(); } const invLength = 1.0 / length; return Vec2.neo(v.x * invLength, v.y * invLength); } /** * Get the length of this vector (the norm). * * For performance, use this instead of lengthSquared (if possible). */ static lengthOf(v: Vec2Value): number { if (_ASSERT) Vec2.assert(v); return math_sqrt(v.x * v.x + v.y * v.y); } /** * Get the length squared. */ static lengthSquared(v: Vec2Value): number { if (_ASSERT) Vec2.assert(v); return v.x * v.x + v.y * v.y; } static distance(v: Vec2Value, w: Vec2Value): number { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); const dx = v.x - w.x; const dy = v.y - w.y; return math_sqrt(dx * dx + dy * dy); } static distanceSquared(v: Vec2Value, w: Vec2Value): number { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); const dx = v.x - w.x; const dy = v.y - w.y; return dx * dx + dy * dy; } static areEqual(v: Vec2Value, w: Vec2Value): boolean { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return v === w || (typeof w === "object" && w !== null && v.x === w.x && v.y === w.y); } /** * Get the skew vector such that dot(skew_vec, other) == cross(vec, other) */ static skew(v: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); return Vec2.neo(-v.y, v.x); } /** Dot product on two vectors */ static dot(v: Vec2Value, w: Vec2Value): number { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return v.x * w.x + v.y * w.y; } /** Cross product between two vectors */ static cross(v: Vec2Value, w: Vec2Value): number; /** Cross product between a vector and a scalar */ static cross(v: Vec2Value, w: number): Vec2; /** Cross product between a scalar and a vector */ static cross(v: number, w: Vec2Value): Vec2; static cross(v: any, w: any): any { if (typeof w === "number") { if (_ASSERT) Vec2.assert(v); if (_ASSERT) console.assert(Number.isFinite(w)); return Vec2.neo(w * v.y, -w * v.x); } else if (typeof v === "number") { if (_ASSERT) console.assert(Number.isFinite(v)); if (_ASSERT) Vec2.assert(w); return Vec2.neo(-v * w.y, v * w.x); } else { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return v.x * w.y - v.y * w.x; } } /** Cross product on two vectors */ static crossVec2Vec2(v: Vec2Value, w: Vec2Value): number { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return v.x * w.y - v.y * w.x; } /** Cross product on a vector and a scalar */ static crossVec2Num(v: Vec2Value, w: number): Vec2 { if (_ASSERT) Vec2.assert(v); if (_ASSERT) console.assert(Number.isFinite(w)); return Vec2.neo(w * v.y, -w * v.x); } /** Cross product on a vector and a scalar */ static crossNumVec2(v: number, w: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(v)); if (_ASSERT) Vec2.assert(w); return Vec2.neo(-v * w.y, v * w.x); } /** Returns `a + (v x w)` */ static addCross(a: Vec2Value, v: Vec2Value, w: number): Vec2; /** Returns `a + (v x w)` */ static addCross(a: Vec2Value, v: number, w: Vec2Value): Vec2; static addCross(a: Vec2Value, v: any, w: any): Vec2 { if (typeof w === "number") { if (_ASSERT) Vec2.assert(v); if (_ASSERT) console.assert(Number.isFinite(w)); return Vec2.neo(w * v.y + a.x, -w * v.x + a.y); } else if (typeof v === "number") { if (_ASSERT) console.assert(Number.isFinite(v)); if (_ASSERT) Vec2.assert(w); return Vec2.neo(-v * w.y + a.x, v * w.x + a.y); } if (_ASSERT) console.assert(false); } /** * Returns `a + (v x w)` */ static addCrossVec2Num(a: Vec2Value, v: Vec2Value, w: number): Vec2 { if (_ASSERT) Vec2.assert(v); if (_ASSERT) console.assert(Number.isFinite(w)); return Vec2.neo(w * v.y + a.x, -w * v.x + a.y); } /** * Returns `a + (v x w)` */ static addCrossNumVec2(a: Vec2Value, v: number, w: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(v)); if (_ASSERT) Vec2.assert(w); return Vec2.neo(-v * w.y + a.x, v * w.x + a.y); } static add(v: Vec2Value, w: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return Vec2.neo(v.x + w.x, v.y + w.y); } /** @hidden @deprecated */ static wAdd(a: number, v: Vec2Value, b: number, w: Vec2Value): Vec2 { if (typeof b !== "undefined" || typeof w !== "undefined") { return Vec2.combine(a, v, b, w); } else { return Vec2.mulNumVec2(a, v); } } static combine(a: number, v: Vec2Value, b: number, w: Vec2Value): Vec2 { return Vec2.zero().setCombine(a, v, b, w); } static sub(v: Vec2Value, w: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return Vec2.neo(v.x - w.x, v.y - w.y); } static mul(a: Vec2Value, b: number): Vec2; static mul(a: number, b: Vec2Value): Vec2; static mul(a: any, b: any): Vec2 { if (typeof a === "object") { if (_ASSERT) Vec2.assert(a); if (_ASSERT) console.assert(Number.isFinite(b)); return Vec2.neo(a.x * b, a.y * b); } else if (typeof b === "object") { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(b); return Vec2.neo(a * b.x, a * b.y); } } static mulVec2Num(a: Vec2Value, b: number): Vec2 { if (_ASSERT) Vec2.assert(a); if (_ASSERT) console.assert(Number.isFinite(b)); return Vec2.neo(a.x * b, a.y * b); } static mulNumVec2(a: number, b: Vec2Value): Vec2 { if (_ASSERT) console.assert(Number.isFinite(a)); if (_ASSERT) Vec2.assert(b); return Vec2.neo(a * b.x, a * b.y); } neg(): Vec2 { this.x = -this.x; this.y = -this.y; return this; } static neg(v: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); return Vec2.neo(-v.x, -v.y); } static abs(v: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); return Vec2.neo(math_abs(v.x), math_abs(v.y)); } static mid(v: Vec2Value, w: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return Vec2.neo((v.x + w.x) * 0.5, (v.y + w.y) * 0.5); } static upper(v: Vec2Value, w: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return Vec2.neo(math_max(v.x, w.x), math_max(v.y, w.y)); } static lower(v: Vec2Value, w: Vec2Value): Vec2 { if (_ASSERT) Vec2.assert(v); if (_ASSERT) Vec2.assert(w); return Vec2.neo(math_min(v.x, w.x), math_min(v.y, w.y)); } clamp(max: number): Vec2 { const lengthSqr = this.x * this.x + this.y * this.y; if (lengthSqr > max * max) { const scale = max / math_sqrt(lengthSqr); this.x *= scale; this.y *= scale; } return this; } static clamp(v: Vec2Value, max: number): Vec2 { const r = Vec2.neo(v.x, v.y); r.clamp(max); return r; } /** @hidden */ static clampVec2(v: Vec2Value, min?: Vec2Value, max?: Vec2Value): Vec2Value { return { x: clamp(v.x, min?.x, max?.x), y: clamp(v.y, min?.y, max?.y), }; } /** @hidden @deprecated */ static scaleFn(x: number, y: number) { // todo: this was used in examples, remove in the future return function (v: Vec2Value): Vec2 { return Vec2.neo(v.x * x, v.y * y); }; } /** @hidden @deprecated */ static translateFn(x: number, y: number) { // todo: this was used in examples, remove in the future return function (v: Vec2Value): Vec2 { return Vec2.neo(v.x + x, v.y + y); }; } }