var box2d = window.box2d = (function (exports) { 'use strict'; /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2BlockAllocator { } /* * Copyright (c) 2011 Erin Catto http://box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Color for debug drawing. Each value has the range [0,1]. class b2Color { constructor(r = 0.5, g = 0.5, b = 0.5, a = 1.0) { this.r = r; this.g = g; this.b = b; this.a = a; } Clone() { return new b2Color().Copy(this); } Copy(other) { this.r = other.r; this.g = other.g; this.b = other.b; this.a = other.a; return this; } IsEqual(color) { return (this.r === color.r) && (this.g === color.g) && (this.b === color.b) && (this.a === color.a); } IsZero() { return (this.r === 0) && (this.g === 0) && (this.b === 0) && (this.a === 0); } Set(r, g, b, a = this.a) { this.SetRGBA(r, g, b, a); } SetByteRGB(r, g, b) { this.r = r / 0xff; this.g = g / 0xff; this.b = b / 0xff; return this; } SetByteRGBA(r, g, b, a) { this.r = r / 0xff; this.g = g / 0xff; this.b = b / 0xff; this.a = a / 0xff; return this; } SetRGB(rr, gg, bb) { this.r = rr; this.g = gg; this.b = bb; return this; } SetRGBA(rr, gg, bb, aa) { this.r = rr; this.g = gg; this.b = bb; this.a = aa; return this; } SelfAdd(color) { this.r += color.r; this.g += color.g; this.b += color.b; this.a += color.a; return this; } Add(color, out) { out.r = this.r + color.r; out.g = this.g + color.g; out.b = this.b + color.b; out.a = this.a + color.a; return out; } SelfSub(color) { this.r -= color.r; this.g -= color.g; this.b -= color.b; this.a -= color.a; return this; } Sub(color, out) { out.r = this.r - color.r; out.g = this.g - color.g; out.b = this.b - color.b; out.a = this.a - color.a; return out; } SelfMul(s) { this.r *= s; this.g *= s; this.b *= s; this.a *= s; return this; } Mul(s, out) { out.r = this.r * s; out.g = this.g * s; out.b = this.b * s; out.a = this.a * s; return out; } Mix(mixColor, strength) { b2Color.MixColors(this, mixColor, strength); } static MixColors(colorA, colorB, strength) { const dr = (strength * (colorB.r - colorA.r)); const dg = (strength * (colorB.g - colorA.g)); const db = (strength * (colorB.b - colorA.b)); const da = (strength * (colorB.a - colorA.a)); colorA.r += dr; colorA.g += dg; colorA.b += db; colorA.a += da; colorB.r -= dr; colorB.g -= dg; colorB.b -= db; colorB.a -= da; } MakeStyleString(alpha = this.a) { return b2Color.MakeStyleString(this.r, this.g, this.b, alpha); } static MakeStyleString(r, g, b, a = 1.0) { // function clamp(x: number, lo: number, hi: number) { return x < lo ? lo : hi < x ? hi : x; } r *= 255; // r = clamp(r, 0, 255); g *= 255; // g = clamp(g, 0, 255); b *= 255; // b = clamp(b, 0, 255); // a = clamp(a, 0, 1); if (a < 1) { return `rgba(${r},${g},${b},${a})`; } else { return `rgb(${r},${g},${b})`; } } } b2Color.ZERO = new b2Color(0, 0, 0, 0); b2Color.RED = new b2Color(1, 0, 0); b2Color.GREEN = new b2Color(0, 1, 0); b2Color.BLUE = new b2Color(0, 0, 1); exports.b2DrawFlags = void 0; (function (b2DrawFlags) { b2DrawFlags[b2DrawFlags["e_none"] = 0] = "e_none"; b2DrawFlags[b2DrawFlags["e_shapeBit"] = 1] = "e_shapeBit"; b2DrawFlags[b2DrawFlags["e_jointBit"] = 2] = "e_jointBit"; b2DrawFlags[b2DrawFlags["e_aabbBit"] = 4] = "e_aabbBit"; b2DrawFlags[b2DrawFlags["e_pairBit"] = 8] = "e_pairBit"; b2DrawFlags[b2DrawFlags["e_centerOfMassBit"] = 16] = "e_centerOfMassBit"; b2DrawFlags[b2DrawFlags["e_all"] = 63] = "e_all"; })(exports.b2DrawFlags || (exports.b2DrawFlags = {})); /// Implement and register this class with a b2World to provide debug drawing of physics /// entities in your game. class b2Draw { constructor() { this.m_drawFlags = 0; } SetFlags(flags) { this.m_drawFlags = flags; } GetFlags() { return this.m_drawFlags; } AppendFlags(flags) { this.m_drawFlags |= flags; } ClearFlags(flags) { this.m_drawFlags &= ~flags; } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ // import { b2_lengthUnitsPerMeter } from "./b2_settings.js"; function b2Assert(condition, ...args) { if (!condition) { // debugger; throw new Error(...args); } } function b2Maybe(value, def) { return value !== undefined ? value : def; } const b2_maxFloat = 1E+37; // FLT_MAX instead of Number.MAX_VALUE; const b2_epsilon = 1E-5; // FLT_EPSILON instead of Number.EPSILON; const b2_epsilon_sq = (b2_epsilon * b2_epsilon); const b2_pi = 3.14159265359; // Math.PI; /// @file /// Global tuning constants based on meters-kilograms-seconds (MKS) units. /// // Tunable Constants /// You can use this to change the length scale used by your game. /// For example for inches you could use 39.4. const b2_lengthUnitsPerMeter = 1.0; /// The maximum number of vertices on a convex polygon. You cannot increase /// this too much because b2BlockAllocator has a maximum object size. const b2_maxPolygonVertices = 8; // Collision /// The maximum number of contact points between two convex shapes. Do /// not change this value. const b2_maxManifoldPoints = 2; /// This is used to fatten AABBs in the dynamic tree. This allows proxies /// to move by a small amount without triggering a tree adjustment. /// This is in meters. const b2_aabbExtension = 0.1 * b2_lengthUnitsPerMeter; /// This is used to fatten AABBs in the dynamic tree. This is used to predict /// the future position based on the current displacement. /// This is a dimensionless multiplier. const b2_aabbMultiplier = 4; /// A small length used as a collision and constraint tolerance. Usually it is /// chosen to be numerically significant, but visually insignificant. const b2_linearSlop = 0.005 * b2_lengthUnitsPerMeter; /// A small angle used as a collision and constraint tolerance. Usually it is /// chosen to be numerically significant, but visually insignificant. const b2_angularSlop = 2 / 180 * b2_pi; /// The radius of the polygon/edge shape skin. This should not be modified. Making /// this smaller means polygons will have an insufficient buffer for continuous collision. /// Making it larger may create artifacts for vertex collision. const b2_polygonRadius = 2 * b2_linearSlop; /// Maximum number of sub-steps per contact in continuous physics simulation. const b2_maxSubSteps = 8; // Dynamics /// Maximum number of contacts to be handled to solve a TOI impact. const b2_maxTOIContacts = 32; /// The maximum linear position correction used when solving constraints. This helps to /// prevent overshoot. const b2_maxLinearCorrection = 0.2 * b2_lengthUnitsPerMeter; /// The maximum angular position correction used when solving constraints. This helps to /// prevent overshoot. const b2_maxAngularCorrection = 8 / 180 * b2_pi; /// The maximum linear velocity of a body. This limit is very large and is used /// to prevent numerical problems. You shouldn't need to adjust this. const b2_maxTranslation = 2 * b2_lengthUnitsPerMeter; const b2_maxTranslationSquared = b2_maxTranslation * b2_maxTranslation; /// The maximum angular velocity of a body. This limit is very large and is used /// to prevent numerical problems. You shouldn't need to adjust this. const b2_maxRotation = 0.5 * b2_pi; const b2_maxRotationSquared = b2_maxRotation * b2_maxRotation; /// This scale factor controls how fast overlap is resolved. Ideally this would be 1 so /// that overlap is removed in one time step. However using values close to 1 often lead /// to overshoot. const b2_baumgarte = 0.2; const b2_toiBaumgarte = 0.75; // Sleep /// The time that a body must be still before it will go to sleep. const b2_timeToSleep = 0.5; /// A body cannot sleep if its linear velocity is above this tolerance. const b2_linearSleepTolerance = 0.01 * b2_lengthUnitsPerMeter; /// A body cannot sleep if its angular velocity is above this tolerance. const b2_angularSleepTolerance = 2 / 180 * b2_pi; // FILE* b2_dumpFile = nullptr; // void b2OpenDump(const char* fileName) // { // b2Assert(b2_dumpFile == nullptr); // b2_dumpFile = fopen(fileName, "w"); // } // void b2Dump(const char* string, ...) // { // if (b2_dumpFile == nullptr) // { // return; // } // va_list args; // va_start(args, string); // vfprintf(b2_dumpFile, string, args); // va_end(args); // } // void b2CloseDump() // { // fclose(b2_dumpFile); // b2_dumpFile = nullptr; // } /// Version numbering scheme. /// See http://en.wikipedia.org/wiki/Software_versioning class b2Version { constructor(major = 0, minor = 0, revision = 0) { this.major = 0; ///< significant changes this.minor = 0; ///< incremental changes this.revision = 0; ///< bug fixes this.major = major; this.minor = minor; this.revision = revision; } toString() { return this.major + "." + this.minor + "." + this.revision; } } /// Current version. const b2_version = new b2Version(2, 4, 1); const b2_branch = "master"; const b2_commit = "9ebbbcd960ad424e03e5de6e66a40764c16f51bc"; function b2ParseInt(v) { return parseInt(v, 10); } function b2ParseUInt(v) { return Math.abs(parseInt(v, 10)); } function b2MakeArray(length, init) { const a = new Array(length); for (let i = 0; i < length; ++i) { a[i] = init(i); } return a; } function b2MakeNullArray(length) { const a = new Array(length); for (let i = 0; i < length; ++i) { a[i] = null; } return a; } function b2MakeNumberArray(length, init = 0) { const a = new Array(length); for (let i = 0; i < length; ++i) { a[i] = init; } return a; } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// @file /// Settings that can be overriden for your application /// // Tunable Constants /// You can use this to change the length scale used by your game. /// For example for inches you could use 39.4. // export const b2_lengthUnitsPerMeter: number = 1.0; /// The maximum number of vertices on a convex polygon. You cannot increase /// this too much because b2BlockAllocator has a maximum object size. // export const b2_maxPolygonVertices: number = 8; // Memory Allocation /// Implement this function to use your own memory allocator. function b2Alloc(size) { return null; } /// If you implement b2Alloc, you should also implement this function. function b2Free(mem) { } /// Logging function. function b2Log(message, ...args) { // console.log(message, ...args); } /* * Copyright (c) 2010 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// This is a growable LIFO stack with an initial capacity of N. /// If the stack size exceeds the initial capacity, the heap is used /// to increase the size of the stack. class b2GrowableStack { constructor(N) { this.m_stack = []; this.m_count = 0; this.m_stack = b2MakeArray(N, (index) => null); this.m_count = 0; } Reset() { this.m_count = 0; return this; } Push(element) { this.m_stack[this.m_count] = element; this.m_count++; } Pop() { // DEBUG: b2Assert(this.m_count > 0); this.m_count--; const element = this.m_stack[this.m_count]; this.m_stack[this.m_count] = null; return element; } GetCount() { return this.m_count; } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ const b2_pi_over_180 = b2_pi / 180; const b2_180_over_pi = 180 / b2_pi; const b2_two_pi = 2 * b2_pi; const b2Abs = Math.abs; function b2Min(a, b) { return a < b ? a : b; } function b2Max(a, b) { return a > b ? a : b; } function b2Clamp(a, lo, hi) { return (a < lo) ? (lo) : ((a > hi) ? (hi) : (a)); } function b2Swap(a, b) { // DEBUG: b2Assert(false); const tmp = a[0]; a[0] = b[0]; b[0] = tmp; } /// This function is used to ensure that a floating point number is /// not a NaN or infinity. const b2IsValid = isFinite; function b2Sq(n) { return n * n; } /// This is a approximate yet fast inverse square-root. function b2InvSqrt(n) { return 1 / Math.sqrt(n); } const b2Sqrt = Math.sqrt; const b2Pow = Math.pow; function b2DegToRad(degrees) { return degrees * b2_pi_over_180; } function b2RadToDeg(radians) { return radians * b2_180_over_pi; } const b2Cos = Math.cos; const b2Sin = Math.sin; const b2Acos = Math.acos; const b2Asin = Math.asin; const b2Atan2 = Math.atan2; function b2NextPowerOfTwo(x) { x |= (x >> 1) & 0x7FFFFFFF; x |= (x >> 2) & 0x3FFFFFFF; x |= (x >> 4) & 0x0FFFFFFF; x |= (x >> 8) & 0x00FFFFFF; x |= (x >> 16) & 0x0000FFFF; return x + 1; } function b2IsPowerOfTwo(x) { return x > 0 && (x & (x - 1)) === 0; } function b2Random() { return Math.random() * 2 - 1; } function b2RandomRange(lo, hi) { return (hi - lo) * Math.random() + lo; } /// A 2D column vector. class b2Vec2 { constructor(x = 0, y = 0) { this.x = x; this.y = y; } Clone() { return new b2Vec2(this.x, this.y); } SetZero() { this.x = 0; this.y = 0; return this; } Set(x, y) { this.x = x; this.y = y; return this; } Copy(other) { this.x = other.x; this.y = other.y; return this; } SelfAdd(v) { this.x += v.x; this.y += v.y; return this; } SelfAddXY(x, y) { this.x += x; this.y += y; return this; } SelfSub(v) { this.x -= v.x; this.y -= v.y; return this; } SelfSubXY(x, y) { this.x -= x; this.y -= y; return this; } SelfMul(s) { this.x *= s; this.y *= s; return this; } SelfMulAdd(s, v) { this.x += s * v.x; this.y += s * v.y; return this; } SelfMulSub(s, v) { this.x -= s * v.x; this.y -= s * v.y; return this; } Dot(v) { return this.x * v.x + this.y * v.y; } Cross(v) { return this.x * v.y - this.y * v.x; } Length() { const x = this.x, y = this.y; return Math.sqrt(x * x + y * y); } LengthSquared() { const x = this.x, y = this.y; return (x * x + y * y); } Normalize() { const length = this.Length(); if (length >= b2_epsilon) { const inv_length = 1 / length; this.x *= inv_length; this.y *= inv_length; } return length; } SelfNormalize() { const length = this.Length(); if (length >= b2_epsilon) { const inv_length = 1 / length; this.x *= inv_length; this.y *= inv_length; } return this; } SelfRotate(radians) { const c = Math.cos(radians); const s = Math.sin(radians); const x = this.x; this.x = c * x - s * this.y; this.y = s * x + c * this.y; return this; } SelfRotateCosSin(c, s) { const x = this.x; this.x = c * x - s * this.y; this.y = s * x + c * this.y; return this; } IsValid() { return isFinite(this.x) && isFinite(this.y); } SelfCrossVS(s) { const x = this.x; this.x = s * this.y; this.y = -s * x; return this; } SelfCrossSV(s) { const x = this.x; this.x = -s * this.y; this.y = s * x; return this; } SelfMinV(v) { this.x = b2Min(this.x, v.x); this.y = b2Min(this.y, v.y); return this; } SelfMaxV(v) { this.x = b2Max(this.x, v.x); this.y = b2Max(this.y, v.y); return this; } SelfAbs() { this.x = b2Abs(this.x); this.y = b2Abs(this.y); return this; } SelfNeg() { this.x = (-this.x); this.y = (-this.y); return this; } SelfSkew() { const x = this.x; this.x = -this.y; this.y = x; return this; } static MakeArray(length) { return b2MakeArray(length, (i) => new b2Vec2()); } static AbsV(v, out) { out.x = b2Abs(v.x); out.y = b2Abs(v.y); return out; } static MinV(a, b, out) { out.x = b2Min(a.x, b.x); out.y = b2Min(a.y, b.y); return out; } static MaxV(a, b, out) { out.x = b2Max(a.x, b.x); out.y = b2Max(a.y, b.y); return out; } static ClampV(v, lo, hi, out) { out.x = b2Clamp(v.x, lo.x, hi.x); out.y = b2Clamp(v.y, lo.y, hi.y); return out; } static RotateV(v, radians, out) { const v_x = v.x, v_y = v.y; const c = Math.cos(radians); const s = Math.sin(radians); out.x = c * v_x - s * v_y; out.y = s * v_x + c * v_y; return out; } static DotVV(a, b) { return a.x * b.x + a.y * b.y; } static CrossVV(a, b) { return a.x * b.y - a.y * b.x; } static CrossVS(v, s, out) { const v_x = v.x; out.x = s * v.y; out.y = -s * v_x; return out; } static CrossVOne(v, out) { const v_x = v.x; out.x = v.y; out.y = -v_x; return out; } static CrossSV(s, v, out) { const v_x = v.x; out.x = -s * v.y; out.y = s * v_x; return out; } static CrossOneV(v, out) { const v_x = v.x; out.x = -v.y; out.y = v_x; return out; } static AddVV(a, b, out) { out.x = a.x + b.x; out.y = a.y + b.y; return out; } static SubVV(a, b, out) { out.x = a.x - b.x; out.y = a.y - b.y; return out; } static MulSV(s, v, out) { out.x = v.x * s; out.y = v.y * s; return out; } static MulVS(v, s, out) { out.x = v.x * s; out.y = v.y * s; return out; } static AddVMulSV(a, s, b, out) { out.x = a.x + (s * b.x); out.y = a.y + (s * b.y); return out; } static SubVMulSV(a, s, b, out) { out.x = a.x - (s * b.x); out.y = a.y - (s * b.y); return out; } static AddVCrossSV(a, s, v, out) { const v_x = v.x; out.x = a.x - (s * v.y); out.y = a.y + (s * v_x); return out; } static MidVV(a, b, out) { out.x = (a.x + b.x) * 0.5; out.y = (a.y + b.y) * 0.5; return out; } static ExtVV(a, b, out) { out.x = (b.x - a.x) * 0.5; out.y = (b.y - a.y) * 0.5; return out; } static IsEqualToV(a, b) { return a.x === b.x && a.y === b.y; } static DistanceVV(a, b) { const c_x = a.x - b.x; const c_y = a.y - b.y; return Math.sqrt(c_x * c_x + c_y * c_y); } static DistanceSquaredVV(a, b) { const c_x = a.x - b.x; const c_y = a.y - b.y; return (c_x * c_x + c_y * c_y); } static NegV(v, out) { out.x = -v.x; out.y = -v.y; return out; } } b2Vec2.ZERO = new b2Vec2(0, 0); b2Vec2.UNITX = new b2Vec2(1, 0); b2Vec2.UNITY = new b2Vec2(0, 1); b2Vec2.s_t0 = new b2Vec2(); b2Vec2.s_t1 = new b2Vec2(); b2Vec2.s_t2 = new b2Vec2(); b2Vec2.s_t3 = new b2Vec2(); const b2Vec2_zero = new b2Vec2(0, 0); /// A 2D column vector with 3 elements. class b2Vec3 { constructor(...args) { if (args[0] instanceof Float32Array) { if (args[0].length !== 3) { throw new Error(); } this.data = args[0]; } else { const x = typeof args[0] === "number" ? args[0] : 0; const y = typeof args[1] === "number" ? args[1] : 0; const z = typeof args[2] === "number" ? args[2] : 0; this.data = new Float32Array([x, y, z]); } } get x() { return this.data[0]; } set x(value) { this.data[0] = value; } get y() { return this.data[1]; } set y(value) { this.data[1] = value; } get z() { return this.data[2]; } set z(value) { this.data[2] = value; } Clone() { return new b2Vec3(this.x, this.y, this.z); } SetZero() { this.x = 0; this.y = 0; this.z = 0; return this; } SetXYZ(x, y, z) { this.x = x; this.y = y; this.z = z; return this; } Copy(other) { this.x = other.x; this.y = other.y; this.z = other.z; return this; } SelfNeg() { this.x = (-this.x); this.y = (-this.y); this.z = (-this.z); return this; } SelfAdd(v) { this.x += v.x; this.y += v.y; this.z += v.z; return this; } SelfAddXYZ(x, y, z) { this.x += x; this.y += y; this.z += z; return this; } SelfSub(v) { this.x -= v.x; this.y -= v.y; this.z -= v.z; return this; } SelfSubXYZ(x, y, z) { this.x -= x; this.y -= y; this.z -= z; return this; } SelfMul(s) { this.x *= s; this.y *= s; this.z *= s; return this; } static DotV3V3(a, b) { return a.x * b.x + a.y * b.y + a.z * b.z; } static CrossV3V3(a, b, out) { const a_x = a.x, a_y = a.y, a_z = a.z; const b_x = b.x, b_y = b.y, b_z = b.z; out.x = a_y * b_z - a_z * b_y; out.y = a_z * b_x - a_x * b_z; out.z = a_x * b_y - a_y * b_x; return out; } } b2Vec3.ZERO = new b2Vec3(0, 0, 0); b2Vec3.s_t0 = new b2Vec3(); /// A 2-by-2 matrix. Stored in column-major order. class b2Mat22 { constructor() { // public readonly data: Float32Array = new Float32Array([ 1, 0, 0, 1 ]); // public readonly ex: b2Vec2 = new b2Vec2(this.data.subarray(0, 2)); // public readonly ey: b2Vec2 = new b2Vec2(this.data.subarray(2, 4)); this.ex = new b2Vec2(1, 0); this.ey = new b2Vec2(0, 1); } Clone() { return new b2Mat22().Copy(this); } static FromVV(c1, c2) { return new b2Mat22().SetVV(c1, c2); } static FromSSSS(r1c1, r1c2, r2c1, r2c2) { return new b2Mat22().SetSSSS(r1c1, r1c2, r2c1, r2c2); } static FromAngle(radians) { return new b2Mat22().SetAngle(radians); } SetSSSS(r1c1, r1c2, r2c1, r2c2) { this.ex.Set(r1c1, r2c1); this.ey.Set(r1c2, r2c2); return this; } SetVV(c1, c2) { this.ex.Copy(c1); this.ey.Copy(c2); return this; } SetAngle(radians) { const c = Math.cos(radians); const s = Math.sin(radians); this.ex.Set(c, s); this.ey.Set(-s, c); return this; } Copy(other) { this.ex.Copy(other.ex); this.ey.Copy(other.ey); return this; } SetIdentity() { this.ex.Set(1, 0); this.ey.Set(0, 1); return this; } SetZero() { this.ex.SetZero(); this.ey.SetZero(); return this; } GetAngle() { return Math.atan2(this.ex.y, this.ex.x); } GetInverse(out) { const a = this.ex.x; const b = this.ey.x; const c = this.ex.y; const d = this.ey.y; let det = a * d - b * c; if (det !== 0) { det = 1 / det; } out.ex.x = det * d; out.ey.x = (-det * b); out.ex.y = (-det * c); out.ey.y = det * a; return out; } Solve(b_x, b_y, out) { const a11 = this.ex.x, a12 = this.ey.x; const a21 = this.ex.y, a22 = this.ey.y; let det = a11 * a22 - a12 * a21; if (det !== 0) { det = 1 / det; } out.x = det * (a22 * b_x - a12 * b_y); out.y = det * (a11 * b_y - a21 * b_x); return out; } SelfAbs() { this.ex.SelfAbs(); this.ey.SelfAbs(); return this; } SelfInv() { this.GetInverse(this); return this; } SelfAddM(M) { this.ex.SelfAdd(M.ex); this.ey.SelfAdd(M.ey); return this; } SelfSubM(M) { this.ex.SelfSub(M.ex); this.ey.SelfSub(M.ey); return this; } static AbsM(M, out) { const M_ex = M.ex, M_ey = M.ey; out.ex.x = b2Abs(M_ex.x); out.ex.y = b2Abs(M_ex.y); out.ey.x = b2Abs(M_ey.x); out.ey.y = b2Abs(M_ey.y); return out; } static MulMV(M, v, out) { const M_ex = M.ex, M_ey = M.ey; const v_x = v.x, v_y = v.y; out.x = M_ex.x * v_x + M_ey.x * v_y; out.y = M_ex.y * v_x + M_ey.y * v_y; return out; } static MulTMV(M, v, out) { const M_ex = M.ex, M_ey = M.ey; const v_x = v.x, v_y = v.y; out.x = M_ex.x * v_x + M_ex.y * v_y; out.y = M_ey.x * v_x + M_ey.y * v_y; return out; } static AddMM(A, B, out) { const A_ex = A.ex, A_ey = A.ey; const B_ex = B.ex, B_ey = B.ey; out.ex.x = A_ex.x + B_ex.x; out.ex.y = A_ex.y + B_ex.y; out.ey.x = A_ey.x + B_ey.x; out.ey.y = A_ey.y + B_ey.y; return out; } static MulMM(A, B, out) { const A_ex_x = A.ex.x, A_ex_y = A.ex.y; const A_ey_x = A.ey.x, A_ey_y = A.ey.y; const B_ex_x = B.ex.x, B_ex_y = B.ex.y; const B_ey_x = B.ey.x, B_ey_y = B.ey.y; out.ex.x = A_ex_x * B_ex_x + A_ey_x * B_ex_y; out.ex.y = A_ex_y * B_ex_x + A_ey_y * B_ex_y; out.ey.x = A_ex_x * B_ey_x + A_ey_x * B_ey_y; out.ey.y = A_ex_y * B_ey_x + A_ey_y * B_ey_y; return out; } static MulTMM(A, B, out) { const A_ex_x = A.ex.x, A_ex_y = A.ex.y; const A_ey_x = A.ey.x, A_ey_y = A.ey.y; const B_ex_x = B.ex.x, B_ex_y = B.ex.y; const B_ey_x = B.ey.x, B_ey_y = B.ey.y; out.ex.x = A_ex_x * B_ex_x + A_ex_y * B_ex_y; out.ex.y = A_ey_x * B_ex_x + A_ey_y * B_ex_y; out.ey.x = A_ex_x * B_ey_x + A_ex_y * B_ey_y; out.ey.y = A_ey_x * B_ey_x + A_ey_y * B_ey_y; return out; } } b2Mat22.IDENTITY = new b2Mat22(); /// A 3-by-3 matrix. Stored in column-major order. class b2Mat33 { constructor() { this.data = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]); this.ex = new b2Vec3(this.data.subarray(0, 3)); this.ey = new b2Vec3(this.data.subarray(3, 6)); this.ez = new b2Vec3(this.data.subarray(6, 9)); } Clone() { return new b2Mat33().Copy(this); } SetVVV(c1, c2, c3) { this.ex.Copy(c1); this.ey.Copy(c2); this.ez.Copy(c3); return this; } Copy(other) { this.ex.Copy(other.ex); this.ey.Copy(other.ey); this.ez.Copy(other.ez); return this; } SetIdentity() { this.ex.SetXYZ(1, 0, 0); this.ey.SetXYZ(0, 1, 0); this.ez.SetXYZ(0, 0, 1); return this; } SetZero() { this.ex.SetZero(); this.ey.SetZero(); this.ez.SetZero(); return this; } SelfAddM(M) { this.ex.SelfAdd(M.ex); this.ey.SelfAdd(M.ey); this.ez.SelfAdd(M.ez); return this; } Solve33(b_x, b_y, b_z, out) { const a11 = this.ex.x, a21 = this.ex.y, a31 = this.ex.z; const a12 = this.ey.x, a22 = this.ey.y, a32 = this.ey.z; const a13 = this.ez.x, a23 = this.ez.y, a33 = this.ez.z; let det = a11 * (a22 * a33 - a32 * a23) + a21 * (a32 * a13 - a12 * a33) + a31 * (a12 * a23 - a22 * a13); if (det !== 0) { det = 1 / det; } out.x = det * (b_x * (a22 * a33 - a32 * a23) + b_y * (a32 * a13 - a12 * a33) + b_z * (a12 * a23 - a22 * a13)); out.y = det * (a11 * (b_y * a33 - b_z * a23) + a21 * (b_z * a13 - b_x * a33) + a31 * (b_x * a23 - b_y * a13)); out.z = det * (a11 * (a22 * b_z - a32 * b_y) + a21 * (a32 * b_x - a12 * b_z) + a31 * (a12 * b_y - a22 * b_x)); return out; } Solve22(b_x, b_y, out) { const a11 = this.ex.x, a12 = this.ey.x; const a21 = this.ex.y, a22 = this.ey.y; let det = a11 * a22 - a12 * a21; if (det !== 0) { det = 1 / det; } out.x = det * (a22 * b_x - a12 * b_y); out.y = det * (a11 * b_y - a21 * b_x); return out; } GetInverse22(M) { const a = this.ex.x, b = this.ey.x, c = this.ex.y, d = this.ey.y; let det = a * d - b * c; if (det !== 0) { det = 1 / det; } M.ex.x = det * d; M.ey.x = -det * b; M.ex.z = 0; M.ex.y = -det * c; M.ey.y = det * a; M.ey.z = 0; M.ez.x = 0; M.ez.y = 0; M.ez.z = 0; } GetSymInverse33(M) { let det = b2Vec3.DotV3V3(this.ex, b2Vec3.CrossV3V3(this.ey, this.ez, b2Vec3.s_t0)); if (det !== 0) { det = 1 / det; } const a11 = this.ex.x, a12 = this.ey.x, a13 = this.ez.x; const a22 = this.ey.y, a23 = this.ez.y; const a33 = this.ez.z; M.ex.x = det * (a22 * a33 - a23 * a23); M.ex.y = det * (a13 * a23 - a12 * a33); M.ex.z = det * (a12 * a23 - a13 * a22); M.ey.x = M.ex.y; M.ey.y = det * (a11 * a33 - a13 * a13); M.ey.z = det * (a13 * a12 - a11 * a23); M.ez.x = M.ex.z; M.ez.y = M.ey.z; M.ez.z = det * (a11 * a22 - a12 * a12); } static MulM33V3(A, v, out) { const v_x = v.x, v_y = v.y, v_z = v.z; out.x = A.ex.x * v_x + A.ey.x * v_y + A.ez.x * v_z; out.y = A.ex.y * v_x + A.ey.y * v_y + A.ez.y * v_z; out.z = A.ex.z * v_x + A.ey.z * v_y + A.ez.z * v_z; return out; } static MulM33XYZ(A, x, y, z, out) { out.x = A.ex.x * x + A.ey.x * y + A.ez.x * z; out.y = A.ex.y * x + A.ey.y * y + A.ez.y * z; out.z = A.ex.z * x + A.ey.z * y + A.ez.z * z; return out; } static MulM33V2(A, v, out) { const v_x = v.x, v_y = v.y; out.x = A.ex.x * v_x + A.ey.x * v_y; out.y = A.ex.y * v_x + A.ey.y * v_y; return out; } static MulM33XY(A, x, y, out) { out.x = A.ex.x * x + A.ey.x * y; out.y = A.ex.y * x + A.ey.y * y; return out; } } b2Mat33.IDENTITY = new b2Mat33(); /// Rotation class b2Rot { constructor(angle = 0) { this.s = 0; this.c = 1; if (angle) { this.s = Math.sin(angle); this.c = Math.cos(angle); } } Clone() { return new b2Rot().Copy(this); } Copy(other) { this.s = other.s; this.c = other.c; return this; } SetAngle(angle) { this.s = Math.sin(angle); this.c = Math.cos(angle); return this; } SetIdentity() { this.s = 0; this.c = 1; return this; } GetAngle() { return Math.atan2(this.s, this.c); } GetXAxis(out) { out.x = this.c; out.y = this.s; return out; } GetYAxis(out) { out.x = -this.s; out.y = this.c; return out; } static MulRR(q, r, out) { // [qc -qs] * [rc -rs] = [qc*rc-qs*rs -qc*rs-qs*rc] // [qs qc] [rs rc] [qs*rc+qc*rs -qs*rs+qc*rc] // s = qs * rc + qc * rs // c = qc * rc - qs * rs const q_c = q.c, q_s = q.s; const r_c = r.c, r_s = r.s; out.s = q_s * r_c + q_c * r_s; out.c = q_c * r_c - q_s * r_s; return out; } static MulTRR(q, r, out) { // [ qc qs] * [rc -rs] = [qc*rc+qs*rs -qc*rs+qs*rc] // [-qs qc] [rs rc] [-qs*rc+qc*rs qs*rs+qc*rc] // s = qc * rs - qs * rc // c = qc * rc + qs * rs const q_c = q.c, q_s = q.s; const r_c = r.c, r_s = r.s; out.s = q_c * r_s - q_s * r_c; out.c = q_c * r_c + q_s * r_s; return out; } static MulRV(q, v, out) { const q_c = q.c, q_s = q.s; const v_x = v.x, v_y = v.y; out.x = q_c * v_x - q_s * v_y; out.y = q_s * v_x + q_c * v_y; return out; } static MulTRV(q, v, out) { const q_c = q.c, q_s = q.s; const v_x = v.x, v_y = v.y; out.x = q_c * v_x + q_s * v_y; out.y = -q_s * v_x + q_c * v_y; return out; } } b2Rot.IDENTITY = new b2Rot(); /// A transform contains translation and rotation. It is used to represent /// the position and orientation of rigid frames. class b2Transform { constructor() { this.p = new b2Vec2(); this.q = new b2Rot(); } Clone() { return new b2Transform().Copy(this); } Copy(other) { this.p.Copy(other.p); this.q.Copy(other.q); return this; } SetIdentity() { this.p.SetZero(); this.q.SetIdentity(); return this; } SetPositionRotation(position, q) { this.p.Copy(position); this.q.Copy(q); return this; } SetPositionAngle(pos, a) { this.p.Copy(pos); this.q.SetAngle(a); return this; } SetPosition(position) { this.p.Copy(position); return this; } SetPositionXY(x, y) { this.p.Set(x, y); return this; } SetRotation(rotation) { this.q.Copy(rotation); return this; } SetRotationAngle(radians) { this.q.SetAngle(radians); return this; } GetPosition() { return this.p; } GetRotation() { return this.q; } GetRotationAngle() { return this.q.GetAngle(); } GetAngle() { return this.q.GetAngle(); } static MulXV(T, v, out) { // float32 x = (T.q.c * v.x - T.q.s * v.y) + T.p.x; // float32 y = (T.q.s * v.x + T.q.c * v.y) + T.p.y; // return b2Vec2(x, y); const T_q_c = T.q.c, T_q_s = T.q.s; const v_x = v.x, v_y = v.y; out.x = (T_q_c * v_x - T_q_s * v_y) + T.p.x; out.y = (T_q_s * v_x + T_q_c * v_y) + T.p.y; return out; } static MulTXV(T, v, out) { // float32 px = v.x - T.p.x; // float32 py = v.y - T.p.y; // float32 x = (T.q.c * px + T.q.s * py); // float32 y = (-T.q.s * px + T.q.c * py); // return b2Vec2(x, y); const T_q_c = T.q.c, T_q_s = T.q.s; const p_x = v.x - T.p.x; const p_y = v.y - T.p.y; out.x = (T_q_c * p_x + T_q_s * p_y); out.y = (-T_q_s * p_x + T_q_c * p_y); return out; } static MulXX(A, B, out) { b2Rot.MulRR(A.q, B.q, out.q); b2Vec2.AddVV(b2Rot.MulRV(A.q, B.p, out.p), A.p, out.p); return out; } static MulTXX(A, B, out) { b2Rot.MulTRR(A.q, B.q, out.q); b2Rot.MulTRV(A.q, b2Vec2.SubVV(B.p, A.p, out.p), out.p); return out; } } b2Transform.IDENTITY = new b2Transform(); /// This describes the motion of a body/shape for TOI computation. /// Shapes are defined with respect to the body origin, which may /// no coincide with the center of mass. However, to support dynamics /// we must interpolate the center of mass position. class b2Sweep { constructor() { this.localCenter = new b2Vec2(); this.c0 = new b2Vec2(); this.c = new b2Vec2(); this.a0 = 0; this.a = 0; this.alpha0 = 0; } Clone() { return new b2Sweep().Copy(this); } Copy(other) { this.localCenter.Copy(other.localCenter); this.c0.Copy(other.c0); this.c.Copy(other.c); this.a0 = other.a0; this.a = other.a; this.alpha0 = other.alpha0; return this; } // https://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/ GetTransform(xf, beta) { xf.p.x = (1.0 - beta) * this.c0.x + beta * this.c.x; xf.p.y = (1.0 - beta) * this.c0.y + beta * this.c.y; const angle = (1.0 - beta) * this.a0 + beta * this.a; xf.q.SetAngle(angle); xf.p.SelfSub(b2Rot.MulRV(xf.q, this.localCenter, b2Vec2.s_t0)); return xf; } Advance(alpha) { // DEBUG: b2Assert(this.alpha0 < 1); const beta = (alpha - this.alpha0) / (1 - this.alpha0); this.c0.SelfMulAdd(beta, this.c.Clone().SelfSub(this.c0)); this.a0 += beta * (this.a - this.a0); this.alpha0 = alpha; } Normalize() { const d = b2_two_pi * Math.floor(this.a0 / b2_two_pi); this.a0 -= d; this.a -= d; } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2StackAllocator { } /* * Copyright (c) 2011 Erin Catto http://box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Timer for profiling. This has platform specific code and may /// not work on every platform. class b2Timer { constructor() { this.m_start = Date.now(); } /// Reset the timer. Reset() { this.m_start = Date.now(); return this; } /// Get the time since construction or the last reset. GetMilliseconds() { return Date.now() - this.m_start; } } class b2Counter { constructor() { this.m_count = 0; this.m_min_count = 0; this.m_max_count = 0; } GetCount() { return this.m_count; } GetMinCount() { return this.m_min_count; } GetMaxCount() { return this.m_max_count; } ResetCount() { const count = this.m_count; this.m_count = 0; return count; } ResetMinCount() { this.m_min_count = 0; } ResetMaxCount() { this.m_max_count = 0; } Increment() { this.m_count++; if (this.m_max_count < this.m_count) { this.m_max_count = this.m_count; } } Decrement() { this.m_count--; if (this.m_min_count > this.m_count) { this.m_min_count = this.m_count; } } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// This holds the mass data computed for a shape. class b2MassData { constructor() { /// The mass of the shape, usually in kilograms. this.mass = 0; /// The position of the shape's centroid relative to the shape's origin. this.center = new b2Vec2(0, 0); /// The rotational inertia of the shape about the local origin. this.I = 0; } } exports.b2ShapeType = void 0; (function (b2ShapeType) { b2ShapeType[b2ShapeType["e_unknown"] = -1] = "e_unknown"; b2ShapeType[b2ShapeType["e_circleShape"] = 0] = "e_circleShape"; b2ShapeType[b2ShapeType["e_edgeShape"] = 1] = "e_edgeShape"; b2ShapeType[b2ShapeType["e_polygonShape"] = 2] = "e_polygonShape"; b2ShapeType[b2ShapeType["e_chainShape"] = 3] = "e_chainShape"; b2ShapeType[b2ShapeType["e_shapeTypeCount"] = 4] = "e_shapeTypeCount"; })(exports.b2ShapeType || (exports.b2ShapeType = {})); /// A shape is used for collision detection. You can create a shape however you like. /// Shapes used for simulation in b2World are created automatically when a b2Fixture /// is created. Shapes may encapsulate a one or more child shapes. class b2Shape { constructor(type, radius) { this.m_type = exports.b2ShapeType.e_unknown; /// Radius of a shape. For polygonal shapes this must be b2_polygonRadius. There is no support for /// making rounded polygons. this.m_radius = 0; this.m_type = type; this.m_radius = radius; } Copy(other) { // DEBUG: b2Assert(this.m_type === other.m_type); this.m_radius = other.m_radius; return this; } /// Get the type of this shape. You can use this to down cast to the concrete shape. /// @return the shape type. GetType() { return this.m_type; } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// A distance proxy is used by the GJK algorithm. /// It encapsulates any shape. class b2DistanceProxy { constructor() { this.m_buffer = b2Vec2.MakeArray(2); this.m_vertices = this.m_buffer; this.m_count = 0; this.m_radius = 0; } Copy(other) { if (other.m_vertices === other.m_buffer) { this.m_vertices = this.m_buffer; this.m_buffer[0].Copy(other.m_buffer[0]); this.m_buffer[1].Copy(other.m_buffer[1]); } else { this.m_vertices = other.m_vertices; } this.m_count = other.m_count; this.m_radius = other.m_radius; return this; } Reset() { this.m_vertices = this.m_buffer; this.m_count = 0; this.m_radius = 0; return this; } SetShape(shape, index) { shape.SetupDistanceProxy(this, index); } SetVerticesRadius(vertices, count, radius) { this.m_vertices = vertices; this.m_count = count; this.m_radius = radius; } Set(...args) { if (args[0] instanceof b2Shape) { this.SetShape(args[0], args[1]); } else { this.SetVerticesRadius(args[0], args[1], args[2]); } } GetSupport(d) { let bestIndex = 0; let bestValue = b2Vec2.DotVV(this.m_vertices[0], d); for (let i = 1; i < this.m_count; ++i) { const value = b2Vec2.DotVV(this.m_vertices[i], d); if (value > bestValue) { bestIndex = i; bestValue = value; } } return bestIndex; } GetSupportVertex(d) { let bestIndex = 0; let bestValue = b2Vec2.DotVV(this.m_vertices[0], d); for (let i = 1; i < this.m_count; ++i) { const value = b2Vec2.DotVV(this.m_vertices[i], d); if (value > bestValue) { bestIndex = i; bestValue = value; } } return this.m_vertices[bestIndex]; } GetVertexCount() { return this.m_count; } GetVertex(index) { // DEBUG: b2Assert(0 <= index && index < this.m_count); return this.m_vertices[index]; } } class b2SimplexCache { constructor() { this.metric = 0; this.count = 0; this.indexA = [0, 0, 0]; this.indexB = [0, 0, 0]; } Reset() { this.metric = 0; this.count = 0; return this; } } class b2DistanceInput { constructor() { this.proxyA = new b2DistanceProxy(); this.proxyB = new b2DistanceProxy(); this.transformA = new b2Transform(); this.transformB = new b2Transform(); this.useRadii = false; } Reset() { this.proxyA.Reset(); this.proxyB.Reset(); this.transformA.SetIdentity(); this.transformB.SetIdentity(); this.useRadii = false; return this; } } class b2DistanceOutput { constructor() { this.pointA = new b2Vec2(); this.pointB = new b2Vec2(); this.distance = 0; this.iterations = 0; ///< number of GJK iterations used } Reset() { this.pointA.SetZero(); this.pointB.SetZero(); this.distance = 0; this.iterations = 0; return this; } } /// Input parameters for b2ShapeCast class b2ShapeCastInput { constructor() { this.proxyA = new b2DistanceProxy(); this.proxyB = new b2DistanceProxy(); this.transformA = new b2Transform(); this.transformB = new b2Transform(); this.translationB = new b2Vec2(); } } /// Output results for b2ShapeCast class b2ShapeCastOutput { constructor() { this.point = new b2Vec2(); this.normal = new b2Vec2(); this.lambda = 0.0; this.iterations = 0; } } exports.b2_gjkCalls = 0; exports.b2_gjkIters = 0; exports.b2_gjkMaxIters = 0; function b2_gjk_reset() { exports.b2_gjkCalls = 0; exports.b2_gjkIters = 0; exports.b2_gjkMaxIters = 0; } class b2SimplexVertex { constructor() { this.wA = new b2Vec2(); // support point in proxyA this.wB = new b2Vec2(); // support point in proxyB this.w = new b2Vec2(); // wB - wA this.a = 0; // barycentric coordinate for closest point this.indexA = 0; // wA index this.indexB = 0; // wB index } Copy(other) { this.wA.Copy(other.wA); // support point in proxyA this.wB.Copy(other.wB); // support point in proxyB this.w.Copy(other.w); // wB - wA this.a = other.a; // barycentric coordinate for closest point this.indexA = other.indexA; // wA index this.indexB = other.indexB; // wB index return this; } } class b2Simplex { constructor() { this.m_v1 = new b2SimplexVertex(); this.m_v2 = new b2SimplexVertex(); this.m_v3 = new b2SimplexVertex(); this.m_vertices = [ /*3*/]; this.m_count = 0; this.m_vertices[0] = this.m_v1; this.m_vertices[1] = this.m_v2; this.m_vertices[2] = this.m_v3; } ReadCache(cache, proxyA, transformA, proxyB, transformB) { // DEBUG: b2Assert(0 <= cache.count && cache.count <= 3); // Copy data from cache. this.m_count = cache.count; const vertices = this.m_vertices; for (let i = 0; i < this.m_count; ++i) { const v = vertices[i]; v.indexA = cache.indexA[i]; v.indexB = cache.indexB[i]; const wALocal = proxyA.GetVertex(v.indexA); const wBLocal = proxyB.GetVertex(v.indexB); b2Transform.MulXV(transformA, wALocal, v.wA); b2Transform.MulXV(transformB, wBLocal, v.wB); b2Vec2.SubVV(v.wB, v.wA, v.w); v.a = 0; } // Compute the new simplex metric, if it is substantially different than // old metric then flush the simplex. if (this.m_count > 1) { const metric1 = cache.metric; const metric2 = this.GetMetric(); if (metric2 < 0.5 * metric1 || 2 * metric1 < metric2 || metric2 < b2_epsilon) { // Reset the simplex. this.m_count = 0; } } // If the cache is empty or invalid ... if (this.m_count === 0) { const v = vertices[0]; v.indexA = 0; v.indexB = 0; const wALocal = proxyA.GetVertex(0); const wBLocal = proxyB.GetVertex(0); b2Transform.MulXV(transformA, wALocal, v.wA); b2Transform.MulXV(transformB, wBLocal, v.wB); b2Vec2.SubVV(v.wB, v.wA, v.w); v.a = 1; this.m_count = 1; } } WriteCache(cache) { cache.metric = this.GetMetric(); cache.count = this.m_count; const vertices = this.m_vertices; for (let i = 0; i < this.m_count; ++i) { cache.indexA[i] = vertices[i].indexA; cache.indexB[i] = vertices[i].indexB; } } GetSearchDirection(out) { switch (this.m_count) { case 1: return b2Vec2.NegV(this.m_v1.w, out); case 2: { const e12 = b2Vec2.SubVV(this.m_v2.w, this.m_v1.w, out); const sgn = b2Vec2.CrossVV(e12, b2Vec2.NegV(this.m_v1.w, b2Vec2.s_t0)); if (sgn > 0) { // Origin is left of e12. return b2Vec2.CrossOneV(e12, out); } else { // Origin is right of e12. return b2Vec2.CrossVOne(e12, out); } } default: // DEBUG: b2Assert(false); return out.SetZero(); } } GetClosestPoint(out) { switch (this.m_count) { case 0: // DEBUG: b2Assert(false); return out.SetZero(); case 1: return out.Copy(this.m_v1.w); case 2: return out.Set(this.m_v1.a * this.m_v1.w.x + this.m_v2.a * this.m_v2.w.x, this.m_v1.a * this.m_v1.w.y + this.m_v2.a * this.m_v2.w.y); case 3: return out.SetZero(); default: // DEBUG: b2Assert(false); return out.SetZero(); } } GetWitnessPoints(pA, pB) { switch (this.m_count) { case 0: // DEBUG: b2Assert(false); break; case 1: pA.Copy(this.m_v1.wA); pB.Copy(this.m_v1.wB); break; case 2: pA.x = this.m_v1.a * this.m_v1.wA.x + this.m_v2.a * this.m_v2.wA.x; pA.y = this.m_v1.a * this.m_v1.wA.y + this.m_v2.a * this.m_v2.wA.y; pB.x = this.m_v1.a * this.m_v1.wB.x + this.m_v2.a * this.m_v2.wB.x; pB.y = this.m_v1.a * this.m_v1.wB.y + this.m_v2.a * this.m_v2.wB.y; break; case 3: pB.x = pA.x = this.m_v1.a * this.m_v1.wA.x + this.m_v2.a * this.m_v2.wA.x + this.m_v3.a * this.m_v3.wA.x; pB.y = pA.y = this.m_v1.a * this.m_v1.wA.y + this.m_v2.a * this.m_v2.wA.y + this.m_v3.a * this.m_v3.wA.y; break; } } GetMetric() { switch (this.m_count) { case 0: // DEBUG: b2Assert(false); return 0; case 1: return 0; case 2: return b2Vec2.DistanceVV(this.m_v1.w, this.m_v2.w); case 3: return b2Vec2.CrossVV(b2Vec2.SubVV(this.m_v2.w, this.m_v1.w, b2Vec2.s_t0), b2Vec2.SubVV(this.m_v3.w, this.m_v1.w, b2Vec2.s_t1)); default: // DEBUG: b2Assert(false); return 0; } } Solve2() { const w1 = this.m_v1.w; const w2 = this.m_v2.w; const e12 = b2Vec2.SubVV(w2, w1, b2Simplex.s_e12); // w1 region const d12_2 = (-b2Vec2.DotVV(w1, e12)); if (d12_2 <= 0) { // a2 <= 0, so we clamp it to 0 this.m_v1.a = 1; this.m_count = 1; return; } // w2 region const d12_1 = b2Vec2.DotVV(w2, e12); if (d12_1 <= 0) { // a1 <= 0, so we clamp it to 0 this.m_v2.a = 1; this.m_count = 1; this.m_v1.Copy(this.m_v2); return; } // Must be in e12 region. const inv_d12 = 1 / (d12_1 + d12_2); this.m_v1.a = d12_1 * inv_d12; this.m_v2.a = d12_2 * inv_d12; this.m_count = 2; } Solve3() { const w1 = this.m_v1.w; const w2 = this.m_v2.w; const w3 = this.m_v3.w; // Edge12 // [1 1 ][a1] = [1] // [w1.e12 w2.e12][a2] = [0] // a3 = 0 const e12 = b2Vec2.SubVV(w2, w1, b2Simplex.s_e12); const w1e12 = b2Vec2.DotVV(w1, e12); const w2e12 = b2Vec2.DotVV(w2, e12); const d12_1 = w2e12; const d12_2 = (-w1e12); // Edge13 // [1 1 ][a1] = [1] // [w1.e13 w3.e13][a3] = [0] // a2 = 0 const e13 = b2Vec2.SubVV(w3, w1, b2Simplex.s_e13); const w1e13 = b2Vec2.DotVV(w1, e13); const w3e13 = b2Vec2.DotVV(w3, e13); const d13_1 = w3e13; const d13_2 = (-w1e13); // Edge23 // [1 1 ][a2] = [1] // [w2.e23 w3.e23][a3] = [0] // a1 = 0 const e23 = b2Vec2.SubVV(w3, w2, b2Simplex.s_e23); const w2e23 = b2Vec2.DotVV(w2, e23); const w3e23 = b2Vec2.DotVV(w3, e23); const d23_1 = w3e23; const d23_2 = (-w2e23); // Triangle123 const n123 = b2Vec2.CrossVV(e12, e13); const d123_1 = n123 * b2Vec2.CrossVV(w2, w3); const d123_2 = n123 * b2Vec2.CrossVV(w3, w1); const d123_3 = n123 * b2Vec2.CrossVV(w1, w2); // w1 region if (d12_2 <= 0 && d13_2 <= 0) { this.m_v1.a = 1; this.m_count = 1; return; } // e12 if (d12_1 > 0 && d12_2 > 0 && d123_3 <= 0) { const inv_d12 = 1 / (d12_1 + d12_2); this.m_v1.a = d12_1 * inv_d12; this.m_v2.a = d12_2 * inv_d12; this.m_count = 2; return; } // e13 if (d13_1 > 0 && d13_2 > 0 && d123_2 <= 0) { const inv_d13 = 1 / (d13_1 + d13_2); this.m_v1.a = d13_1 * inv_d13; this.m_v3.a = d13_2 * inv_d13; this.m_count = 2; this.m_v2.Copy(this.m_v3); return; } // w2 region if (d12_1 <= 0 && d23_2 <= 0) { this.m_v2.a = 1; this.m_count = 1; this.m_v1.Copy(this.m_v2); return; } // w3 region if (d13_1 <= 0 && d23_1 <= 0) { this.m_v3.a = 1; this.m_count = 1; this.m_v1.Copy(this.m_v3); return; } // e23 if (d23_1 > 0 && d23_2 > 0 && d123_1 <= 0) { const inv_d23 = 1 / (d23_1 + d23_2); this.m_v2.a = d23_1 * inv_d23; this.m_v3.a = d23_2 * inv_d23; this.m_count = 2; this.m_v1.Copy(this.m_v3); return; } // Must be in triangle123 const inv_d123 = 1 / (d123_1 + d123_2 + d123_3); this.m_v1.a = d123_1 * inv_d123; this.m_v2.a = d123_2 * inv_d123; this.m_v3.a = d123_3 * inv_d123; this.m_count = 3; } } b2Simplex.s_e12 = new b2Vec2(); b2Simplex.s_e13 = new b2Vec2(); b2Simplex.s_e23 = new b2Vec2(); const b2Distance_s_simplex = new b2Simplex(); const b2Distance_s_saveA = [0, 0, 0]; const b2Distance_s_saveB = [0, 0, 0]; const b2Distance_s_p = new b2Vec2(); const b2Distance_s_d = new b2Vec2(); const b2Distance_s_normal = new b2Vec2(); const b2Distance_s_supportA = new b2Vec2(); const b2Distance_s_supportB = new b2Vec2(); function b2Distance(output, cache, input) { ++exports.b2_gjkCalls; const proxyA = input.proxyA; const proxyB = input.proxyB; const transformA = input.transformA; const transformB = input.transformB; // Initialize the simplex. const simplex = b2Distance_s_simplex; simplex.ReadCache(cache, proxyA, transformA, proxyB, transformB); // Get simplex vertices as an array. const vertices = simplex.m_vertices; const k_maxIters = 20; // These store the vertices of the last simplex so that we // can check for duplicates and prevent cycling. const saveA = b2Distance_s_saveA; const saveB = b2Distance_s_saveB; let saveCount = 0; // Main iteration loop. let iter = 0; while (iter < k_maxIters) { // Copy simplex so we can identify duplicates. saveCount = simplex.m_count; for (let i = 0; i < saveCount; ++i) { saveA[i] = vertices[i].indexA; saveB[i] = vertices[i].indexB; } switch (simplex.m_count) { case 1: break; case 2: simplex.Solve2(); break; case 3: simplex.Solve3(); break; } // If we have 3 points, then the origin is in the corresponding triangle. if (simplex.m_count === 3) { break; } // Get search direction. const d = simplex.GetSearchDirection(b2Distance_s_d); // Ensure the search direction is numerically fit. if (d.LengthSquared() < b2_epsilon_sq) { // The origin is probably contained by a line segment // or triangle. Thus the shapes are overlapped. // We can't return zero here even though there may be overlap. // In case the simplex is a point, segment, or triangle it is difficult // to determine if the origin is contained in the CSO or very close to it. break; } // Compute a tentative new simplex vertex using support points. const vertex = vertices[simplex.m_count]; vertex.indexA = proxyA.GetSupport(b2Rot.MulTRV(transformA.q, b2Vec2.NegV(d, b2Vec2.s_t0), b2Distance_s_supportA)); b2Transform.MulXV(transformA, proxyA.GetVertex(vertex.indexA), vertex.wA); vertex.indexB = proxyB.GetSupport(b2Rot.MulTRV(transformB.q, d, b2Distance_s_supportB)); b2Transform.MulXV(transformB, proxyB.GetVertex(vertex.indexB), vertex.wB); b2Vec2.SubVV(vertex.wB, vertex.wA, vertex.w); // Iteration count is equated to the number of support point calls. ++iter; ++exports.b2_gjkIters; // Check for duplicate support points. This is the main termination criteria. let duplicate = false; for (let i = 0; i < saveCount; ++i) { if (vertex.indexA === saveA[i] && vertex.indexB === saveB[i]) { duplicate = true; break; } } // If we found a duplicate support point we must exit to avoid cycling. if (duplicate) { break; } // New vertex is ok and needed. ++simplex.m_count; } exports.b2_gjkMaxIters = b2Max(exports.b2_gjkMaxIters, iter); // Prepare output. simplex.GetWitnessPoints(output.pointA, output.pointB); output.distance = b2Vec2.DistanceVV(output.pointA, output.pointB); output.iterations = iter; // Cache the simplex. simplex.WriteCache(cache); // Apply radii if requested. if (input.useRadii) { const rA = proxyA.m_radius; const rB = proxyB.m_radius; if (output.distance > (rA + rB) && output.distance > b2_epsilon) { // Shapes are still no overlapped. // Move the witness points to the outer surface. output.distance -= rA + rB; const normal = b2Vec2.SubVV(output.pointB, output.pointA, b2Distance_s_normal); normal.Normalize(); output.pointA.SelfMulAdd(rA, normal); output.pointB.SelfMulSub(rB, normal); } else { // Shapes are overlapped when radii are considered. // Move the witness points to the middle. const p = b2Vec2.MidVV(output.pointA, output.pointB, b2Distance_s_p); output.pointA.Copy(p); output.pointB.Copy(p); output.distance = 0; } } } /// Perform a linear shape cast of shape B moving and shape A fixed. Determines the hit point, normal, and translation fraction. // GJK-raycast // Algorithm by Gino van den Bergen. // "Smooth Mesh Contacts with GJK" in Game Physics Pearls. 2010 // bool b2ShapeCast(b2ShapeCastOutput* output, const b2ShapeCastInput* input); const b2ShapeCast_s_n = new b2Vec2(); const b2ShapeCast_s_simplex = new b2Simplex(); const b2ShapeCast_s_wA = new b2Vec2(); const b2ShapeCast_s_wB = new b2Vec2(); const b2ShapeCast_s_v = new b2Vec2(); const b2ShapeCast_s_p = new b2Vec2(); const b2ShapeCast_s_pointA = new b2Vec2(); const b2ShapeCast_s_pointB = new b2Vec2(); function b2ShapeCast(output, input) { output.iterations = 0; output.lambda = 1.0; output.normal.SetZero(); output.point.SetZero(); // const b2DistanceProxy* proxyA = &input.proxyA; const proxyA = input.proxyA; // const b2DistanceProxy* proxyB = &input.proxyB; const proxyB = input.proxyB; // float32 radiusA = b2Max(proxyA.m_radius, b2_polygonRadius); const radiusA = b2Max(proxyA.m_radius, b2_polygonRadius); // float32 radiusB = b2Max(proxyB.m_radius, b2_polygonRadius); const radiusB = b2Max(proxyB.m_radius, b2_polygonRadius); // float32 radius = radiusA + radiusB; const radius = radiusA + radiusB; // b2Transform xfA = input.transformA; const xfA = input.transformA; // b2Transform xfB = input.transformB; const xfB = input.transformB; // b2Vec2 r = input.translationB; const r = input.translationB; // b2Vec2 n(0.0f, 0.0f); const n = b2ShapeCast_s_n.Set(0.0, 0.0); // float32 lambda = 0.0f; let lambda = 0.0; // Initial simplex const simplex = b2ShapeCast_s_simplex; simplex.m_count = 0; // Get simplex vertices as an array. // b2SimplexVertex* vertices = &simplex.m_v1; const vertices = simplex.m_vertices; // Get support point in -r direction // int32 indexA = proxyA.GetSupport(b2MulT(xfA.q, -r)); let indexA = proxyA.GetSupport(b2Rot.MulTRV(xfA.q, b2Vec2.NegV(r, b2Vec2.s_t1), b2Vec2.s_t0)); // b2Vec2 wA = b2Mul(xfA, proxyA.GetVertex(indexA)); let wA = b2Transform.MulXV(xfA, proxyA.GetVertex(indexA), b2ShapeCast_s_wA); // int32 indexB = proxyB.GetSupport(b2MulT(xfB.q, r)); let indexB = proxyB.GetSupport(b2Rot.MulTRV(xfB.q, r, b2Vec2.s_t0)); // b2Vec2 wB = b2Mul(xfB, proxyB.GetVertex(indexB)); let wB = b2Transform.MulXV(xfB, proxyB.GetVertex(indexB), b2ShapeCast_s_wB); // b2Vec2 v = wA - wB; const v = b2Vec2.SubVV(wA, wB, b2ShapeCast_s_v); // Sigma is the target distance between polygons // float32 sigma = b2Max(b2_polygonRadius, radius - b2_polygonRadius); const sigma = b2Max(b2_polygonRadius, radius - b2_polygonRadius); // const float32 tolerance = 0.5f * b2_linearSlop; const tolerance = 0.5 * b2_linearSlop; // Main iteration loop. // const int32 k_maxIters = 20; const k_maxIters = 20; // int32 iter = 0; let iter = 0; // while (iter < k_maxIters && v.Length() - sigma > tolerance) while (iter < k_maxIters && v.Length() - sigma > tolerance) { // DEBUG: b2Assert(simplex.m_count < 3); output.iterations += 1; // Support in direction -v (A - B) // indexA = proxyA.GetSupport(b2MulT(xfA.q, -v)); indexA = proxyA.GetSupport(b2Rot.MulTRV(xfA.q, b2Vec2.NegV(v, b2Vec2.s_t1), b2Vec2.s_t0)); // wA = b2Mul(xfA, proxyA.GetVertex(indexA)); wA = b2Transform.MulXV(xfA, proxyA.GetVertex(indexA), b2ShapeCast_s_wA); // indexB = proxyB.GetSupport(b2MulT(xfB.q, v)); indexB = proxyB.GetSupport(b2Rot.MulTRV(xfB.q, v, b2Vec2.s_t0)); // wB = b2Mul(xfB, proxyB.GetVertex(indexB)); wB = b2Transform.MulXV(xfB, proxyB.GetVertex(indexB), b2ShapeCast_s_wB); // b2Vec2 p = wA - wB; const p = b2Vec2.SubVV(wA, wB, b2ShapeCast_s_p); // -v is a normal at p v.Normalize(); // Intersect ray with plane const vp = b2Vec2.DotVV(v, p); const vr = b2Vec2.DotVV(v, r); if (vp - sigma > lambda * vr) { if (vr <= 0.0) { return false; } lambda = (vp - sigma) / vr; if (lambda > 1.0) { return false; } // n = -v; n.Copy(v).SelfNeg(); simplex.m_count = 0; } // Reverse simplex since it works with B - A. // Shift by lambda * r because we want the closest point to the current clip point. // Note that the support point p is not shifted because we want the plane equation // to be formed in unshifted space. // b2SimplexVertex* vertex = vertices + simplex.m_count; const vertex = vertices[simplex.m_count]; vertex.indexA = indexB; // vertex.wA = wB + lambda * r; vertex.wA.Copy(wB).SelfMulAdd(lambda, r); vertex.indexB = indexA; // vertex.wB = wA; vertex.wB.Copy(wA); // vertex.w = vertex.wB - vertex.wA; vertex.w.Copy(vertex.wB).SelfSub(vertex.wA); vertex.a = 1.0; simplex.m_count += 1; switch (simplex.m_count) { case 1: break; case 2: simplex.Solve2(); break; case 3: simplex.Solve3(); break; // DEBUG: b2Assert(false); } // If we have 3 points, then the origin is in the corresponding triangle. if (simplex.m_count === 3) { // Overlap return false; } // Get search direction. // v = simplex.GetClosestPoint(); simplex.GetClosestPoint(v); // Iteration count is equated to the number of support point calls. ++iter; } if (iter === 0) { // Initial overlap return false; } // Prepare output. const pointA = b2ShapeCast_s_pointA; const pointB = b2ShapeCast_s_pointB; simplex.GetWitnessPoints(pointA, pointB); if (v.LengthSquared() > 0.0) { // n = -v; n.Copy(v).SelfNeg(); n.Normalize(); } // output.point = pointA + radiusA * n; output.normal.Copy(n); output.lambda = lambda; output.iterations = iter; return true; } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// @file /// Structures and functions used for computing contact points, distance /// queries, and TOI queries. exports.b2ContactFeatureType = void 0; (function (b2ContactFeatureType) { b2ContactFeatureType[b2ContactFeatureType["e_vertex"] = 0] = "e_vertex"; b2ContactFeatureType[b2ContactFeatureType["e_face"] = 1] = "e_face"; })(exports.b2ContactFeatureType || (exports.b2ContactFeatureType = {})); /// The features that intersect to form the contact point /// This must be 4 bytes or less. class b2ContactFeature { constructor() { this._key = 0; this._key_invalid = false; this._indexA = 0; this._indexB = 0; this._typeA = 0; this._typeB = 0; } get key() { if (this._key_invalid) { this._key_invalid = false; this._key = this._indexA | (this._indexB << 8) | (this._typeA << 16) | (this._typeB << 24); } return this._key; } set key(value) { this._key = value; this._key_invalid = false; this._indexA = this._key & 0xff; this._indexB = (this._key >> 8) & 0xff; this._typeA = (this._key >> 16) & 0xff; this._typeB = (this._key >> 24) & 0xff; } get indexA() { return this._indexA; } set indexA(value) { this._indexA = value; this._key_invalid = true; } get indexB() { return this._indexB; } set indexB(value) { this._indexB = value; this._key_invalid = true; } get typeA() { return this._typeA; } set typeA(value) { this._typeA = value; this._key_invalid = true; } get typeB() { return this._typeB; } set typeB(value) { this._typeB = value; this._key_invalid = true; } } /// Contact ids to facilitate warm starting. class b2ContactID { constructor() { this.cf = new b2ContactFeature(); } Copy(o) { this.key = o.key; return this; } Clone() { return new b2ContactID().Copy(this); } get key() { return this.cf.key; } set key(value) { this.cf.key = value; } } /// A manifold point is a contact point belonging to a contact /// manifold. It holds details related to the geometry and dynamics /// of the contact points. /// The local point usage depends on the manifold type: /// -e_circles: the local center of circleB /// -e_faceA: the local center of cirlceB or the clip point of polygonB /// -e_faceB: the clip point of polygonA /// This structure is stored across time steps, so we keep it small. /// Note: the impulses are used for internal caching and may not /// provide reliable contact forces, especially for high speed collisions. class b2ManifoldPoint { constructor() { this.localPoint = new b2Vec2(); ///< usage depends on manifold type this.normalImpulse = 0; ///< the non-penetration impulse this.tangentImpulse = 0; ///< the friction impulse this.id = new b2ContactID(); ///< uniquely identifies a contact point between two shapes } static MakeArray(length) { return b2MakeArray(length, (i) => new b2ManifoldPoint()); } Reset() { this.localPoint.SetZero(); this.normalImpulse = 0; this.tangentImpulse = 0; this.id.key = 0; } Copy(o) { this.localPoint.Copy(o.localPoint); this.normalImpulse = o.normalImpulse; this.tangentImpulse = o.tangentImpulse; this.id.Copy(o.id); return this; } } exports.b2ManifoldType = void 0; (function (b2ManifoldType) { b2ManifoldType[b2ManifoldType["e_unknown"] = -1] = "e_unknown"; b2ManifoldType[b2ManifoldType["e_circles"] = 0] = "e_circles"; b2ManifoldType[b2ManifoldType["e_faceA"] = 1] = "e_faceA"; b2ManifoldType[b2ManifoldType["e_faceB"] = 2] = "e_faceB"; })(exports.b2ManifoldType || (exports.b2ManifoldType = {})); /// A manifold for two touching convex shapes. /// Box2D supports multiple types of contact: /// - clip point versus plane with radius /// - point versus point with radius (circles) /// The local point usage depends on the manifold type: /// -e_circles: the local center of circleA /// -e_faceA: the center of faceA /// -e_faceB: the center of faceB /// Similarly the local normal usage: /// -e_circles: not used /// -e_faceA: the normal on polygonA /// -e_faceB: the normal on polygonB /// We store contacts in this way so that position correction can /// account for movement, which is critical for continuous physics. /// All contact scenarios must be expressed in one of these types. /// This structure is stored across time steps, so we keep it small. class b2Manifold { constructor() { this.points = b2ManifoldPoint.MakeArray(b2_maxManifoldPoints); this.localNormal = new b2Vec2(); this.localPoint = new b2Vec2(); this.type = exports.b2ManifoldType.e_unknown; this.pointCount = 0; } Reset() { for (let i = 0; i < b2_maxManifoldPoints; ++i) { // DEBUG: b2Assert(this.points[i] instanceof b2ManifoldPoint); this.points[i].Reset(); } this.localNormal.SetZero(); this.localPoint.SetZero(); this.type = exports.b2ManifoldType.e_unknown; this.pointCount = 0; } Copy(o) { this.pointCount = o.pointCount; for (let i = 0; i < b2_maxManifoldPoints; ++i) { // DEBUG: b2Assert(this.points[i] instanceof b2ManifoldPoint); this.points[i].Copy(o.points[i]); } this.localNormal.Copy(o.localNormal); this.localPoint.Copy(o.localPoint); this.type = o.type; return this; } Clone() { return new b2Manifold().Copy(this); } } class b2WorldManifold { constructor() { this.normal = new b2Vec2(); this.points = b2Vec2.MakeArray(b2_maxManifoldPoints); this.separations = b2MakeNumberArray(b2_maxManifoldPoints); } Initialize(manifold, xfA, radiusA, xfB, radiusB) { if (manifold.pointCount === 0) { return; } switch (manifold.type) { case exports.b2ManifoldType.e_circles: { this.normal.Set(1, 0); const pointA = b2Transform.MulXV(xfA, manifold.localPoint, b2WorldManifold.Initialize_s_pointA); const pointB = b2Transform.MulXV(xfB, manifold.points[0].localPoint, b2WorldManifold.Initialize_s_pointB); if (b2Vec2.DistanceSquaredVV(pointA, pointB) > b2_epsilon_sq) { b2Vec2.SubVV(pointB, pointA, this.normal).SelfNormalize(); } const cA = b2Vec2.AddVMulSV(pointA, radiusA, this.normal, b2WorldManifold.Initialize_s_cA); const cB = b2Vec2.SubVMulSV(pointB, radiusB, this.normal, b2WorldManifold.Initialize_s_cB); b2Vec2.MidVV(cA, cB, this.points[0]); this.separations[0] = b2Vec2.DotVV(b2Vec2.SubVV(cB, cA, b2Vec2.s_t0), this.normal); // b2Dot(cB - cA, normal); break; } case exports.b2ManifoldType.e_faceA: { b2Rot.MulRV(xfA.q, manifold.localNormal, this.normal); const planePoint = b2Transform.MulXV(xfA, manifold.localPoint, b2WorldManifold.Initialize_s_planePoint); for (let i = 0; i < manifold.pointCount; ++i) { const clipPoint = b2Transform.MulXV(xfB, manifold.points[i].localPoint, b2WorldManifold.Initialize_s_clipPoint); const s = radiusA - b2Vec2.DotVV(b2Vec2.SubVV(clipPoint, planePoint, b2Vec2.s_t0), this.normal); const cA = b2Vec2.AddVMulSV(clipPoint, s, this.normal, b2WorldManifold.Initialize_s_cA); const cB = b2Vec2.SubVMulSV(clipPoint, radiusB, this.normal, b2WorldManifold.Initialize_s_cB); b2Vec2.MidVV(cA, cB, this.points[i]); this.separations[i] = b2Vec2.DotVV(b2Vec2.SubVV(cB, cA, b2Vec2.s_t0), this.normal); // b2Dot(cB - cA, normal); } break; } case exports.b2ManifoldType.e_faceB: { b2Rot.MulRV(xfB.q, manifold.localNormal, this.normal); const planePoint = b2Transform.MulXV(xfB, manifold.localPoint, b2WorldManifold.Initialize_s_planePoint); for (let i = 0; i < manifold.pointCount; ++i) { const clipPoint = b2Transform.MulXV(xfA, manifold.points[i].localPoint, b2WorldManifold.Initialize_s_clipPoint); const s = radiusB - b2Vec2.DotVV(b2Vec2.SubVV(clipPoint, planePoint, b2Vec2.s_t0), this.normal); const cB = b2Vec2.AddVMulSV(clipPoint, s, this.normal, b2WorldManifold.Initialize_s_cB); const cA = b2Vec2.SubVMulSV(clipPoint, radiusA, this.normal, b2WorldManifold.Initialize_s_cA); b2Vec2.MidVV(cA, cB, this.points[i]); this.separations[i] = b2Vec2.DotVV(b2Vec2.SubVV(cA, cB, b2Vec2.s_t0), this.normal); // b2Dot(cA - cB, normal); } // Ensure normal points from A to B. this.normal.SelfNeg(); break; } } } } b2WorldManifold.Initialize_s_pointA = new b2Vec2(); b2WorldManifold.Initialize_s_pointB = new b2Vec2(); b2WorldManifold.Initialize_s_cA = new b2Vec2(); b2WorldManifold.Initialize_s_cB = new b2Vec2(); b2WorldManifold.Initialize_s_planePoint = new b2Vec2(); b2WorldManifold.Initialize_s_clipPoint = new b2Vec2(); /// This is used for determining the state of contact points. exports.b2PointState = void 0; (function (b2PointState) { b2PointState[b2PointState["b2_nullState"] = 0] = "b2_nullState"; b2PointState[b2PointState["b2_addState"] = 1] = "b2_addState"; b2PointState[b2PointState["b2_persistState"] = 2] = "b2_persistState"; b2PointState[b2PointState["b2_removeState"] = 3] = "b2_removeState"; })(exports.b2PointState || (exports.b2PointState = {})); /// Compute the point states given two manifolds. The states pertain to the transition from manifold1 /// to manifold2. So state1 is either persist or remove while state2 is either add or persist. function b2GetPointStates(state1, state2, manifold1, manifold2) { // Detect persists and removes. let i; for (i = 0; i < manifold1.pointCount; ++i) { const id = manifold1.points[i].id; const key = id.key; state1[i] = exports.b2PointState.b2_removeState; for (let j = 0, jct = manifold2.pointCount; j < jct; ++j) { if (manifold2.points[j].id.key === key) { state1[i] = exports.b2PointState.b2_persistState; break; } } } for (; i < b2_maxManifoldPoints; ++i) { state1[i] = exports.b2PointState.b2_nullState; } // Detect persists and adds. for (i = 0; i < manifold2.pointCount; ++i) { const id = manifold2.points[i].id; const key = id.key; state2[i] = exports.b2PointState.b2_addState; for (let j = 0, jct = manifold1.pointCount; j < jct; ++j) { if (manifold1.points[j].id.key === key) { state2[i] = exports.b2PointState.b2_persistState; break; } } } for (; i < b2_maxManifoldPoints; ++i) { state2[i] = exports.b2PointState.b2_nullState; } } /// Used for computing contact manifolds. class b2ClipVertex { constructor() { this.v = new b2Vec2(); this.id = new b2ContactID(); } static MakeArray(length) { return b2MakeArray(length, (i) => new b2ClipVertex()); } Copy(other) { this.v.Copy(other.v); this.id.Copy(other.id); return this; } } /// Ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). class b2RayCastInput { constructor() { this.p1 = new b2Vec2(); this.p2 = new b2Vec2(); this.maxFraction = 1; } Copy(o) { this.p1.Copy(o.p1); this.p2.Copy(o.p2); this.maxFraction = o.maxFraction; return this; } } /// Ray-cast output data. The ray hits at p1 + fraction * (p2 - p1), where p1 and p2 /// come from b2RayCastInput. class b2RayCastOutput { constructor() { this.normal = new b2Vec2(); this.fraction = 0; } Copy(o) { this.normal.Copy(o.normal); this.fraction = o.fraction; return this; } } /// An axis aligned bounding box. class b2AABB { constructor() { this.lowerBound = new b2Vec2(); ///< the lower vertex this.upperBound = new b2Vec2(); ///< the upper vertex this.m_cache_center = new b2Vec2(); // access using GetCenter() this.m_cache_extent = new b2Vec2(); // access using GetExtents() } Copy(o) { this.lowerBound.Copy(o.lowerBound); this.upperBound.Copy(o.upperBound); return this; } /// Verify that the bounds are sorted. IsValid() { if (!this.lowerBound.IsValid()) { return false; } if (!this.upperBound.IsValid()) { return false; } if (this.upperBound.x < this.lowerBound.x) { return false; } if (this.upperBound.y < this.lowerBound.y) { return false; } return true; } /// Get the center of the AABB. GetCenter() { return b2Vec2.MidVV(this.lowerBound, this.upperBound, this.m_cache_center); } /// Get the extents of the AABB (half-widths). GetExtents() { return b2Vec2.ExtVV(this.lowerBound, this.upperBound, this.m_cache_extent); } /// Get the perimeter length GetPerimeter() { const wx = this.upperBound.x - this.lowerBound.x; const wy = this.upperBound.y - this.lowerBound.y; return 2 * (wx + wy); } /// Combine an AABB into this one. Combine1(aabb) { this.lowerBound.x = b2Min(this.lowerBound.x, aabb.lowerBound.x); this.lowerBound.y = b2Min(this.lowerBound.y, aabb.lowerBound.y); this.upperBound.x = b2Max(this.upperBound.x, aabb.upperBound.x); this.upperBound.y = b2Max(this.upperBound.y, aabb.upperBound.y); return this; } /// Combine two AABBs into this one. Combine2(aabb1, aabb2) { this.lowerBound.x = b2Min(aabb1.lowerBound.x, aabb2.lowerBound.x); this.lowerBound.y = b2Min(aabb1.lowerBound.y, aabb2.lowerBound.y); this.upperBound.x = b2Max(aabb1.upperBound.x, aabb2.upperBound.x); this.upperBound.y = b2Max(aabb1.upperBound.y, aabb2.upperBound.y); return this; } static Combine(aabb1, aabb2, out) { out.Combine2(aabb1, aabb2); return out; } /// Does this aabb contain the provided AABB. Contains(aabb) { let result = true; result = result && this.lowerBound.x <= aabb.lowerBound.x; result = result && this.lowerBound.y <= aabb.lowerBound.y; result = result && aabb.upperBound.x <= this.upperBound.x; result = result && aabb.upperBound.y <= this.upperBound.y; return result; } // From Real-time Collision Detection, p179. RayCast(output, input) { let tmin = (-b2_maxFloat); let tmax = b2_maxFloat; const p_x = input.p1.x; const p_y = input.p1.y; const d_x = input.p2.x - input.p1.x; const d_y = input.p2.y - input.p1.y; const absD_x = b2Abs(d_x); const absD_y = b2Abs(d_y); const normal = output.normal; if (absD_x < b2_epsilon) { // Parallel. if (p_x < this.lowerBound.x || this.upperBound.x < p_x) { return false; } } else { const inv_d = 1 / d_x; let t1 = (this.lowerBound.x - p_x) * inv_d; let t2 = (this.upperBound.x - p_x) * inv_d; // Sign of the normal vector. let s = (-1); if (t1 > t2) { const t3 = t1; t1 = t2; t2 = t3; s = 1; } // Push the min up if (t1 > tmin) { normal.x = s; normal.y = 0; tmin = t1; } // Pull the max down tmax = b2Min(tmax, t2); if (tmin > tmax) { return false; } } if (absD_y < b2_epsilon) { // Parallel. if (p_y < this.lowerBound.y || this.upperBound.y < p_y) { return false; } } else { const inv_d = 1 / d_y; let t1 = (this.lowerBound.y - p_y) * inv_d; let t2 = (this.upperBound.y - p_y) * inv_d; // Sign of the normal vector. let s = (-1); if (t1 > t2) { const t3 = t1; t1 = t2; t2 = t3; s = 1; } // Push the min up if (t1 > tmin) { normal.x = 0; normal.y = s; tmin = t1; } // Pull the max down tmax = b2Min(tmax, t2); if (tmin > tmax) { return false; } } // Does the ray start inside the box? // Does the ray intersect beyond the max fraction? if (tmin < 0 || input.maxFraction < tmin) { return false; } // Intersection. output.fraction = tmin; return true; } TestContain(point) { if (point.x < this.lowerBound.x || this.upperBound.x < point.x) { return false; } if (point.y < this.lowerBound.y || this.upperBound.y < point.y) { return false; } return true; } TestOverlap(other) { if (this.upperBound.x < other.lowerBound.x) { return false; } if (this.upperBound.y < other.lowerBound.y) { return false; } if (other.upperBound.x < this.lowerBound.x) { return false; } if (other.upperBound.y < this.lowerBound.y) { return false; } return true; } } function b2TestOverlapAABB(a, b) { if (a.upperBound.x < b.lowerBound.x) { return false; } if (a.upperBound.y < b.lowerBound.y) { return false; } if (b.upperBound.x < a.lowerBound.x) { return false; } if (b.upperBound.y < a.lowerBound.y) { return false; } return true; } /// Clipping for contact manifolds. function b2ClipSegmentToLine(vOut, vIn, normal, offset, vertexIndexA) { // Start with no output points let count = 0; const vIn0 = vIn[0]; const vIn1 = vIn[1]; // Calculate the distance of end points to the line const distance0 = b2Vec2.DotVV(normal, vIn0.v) - offset; const distance1 = b2Vec2.DotVV(normal, vIn1.v) - offset; // If the points are behind the plane if (distance0 <= 0) { vOut[count++].Copy(vIn0); } if (distance1 <= 0) { vOut[count++].Copy(vIn1); } // If the points are on different sides of the plane if (distance0 * distance1 < 0) { // Find intersection point of edge and plane const interp = distance0 / (distance0 - distance1); const v = vOut[count].v; v.x = vIn0.v.x + interp * (vIn1.v.x - vIn0.v.x); v.y = vIn0.v.y + interp * (vIn1.v.y - vIn0.v.y); // VertexA is hitting edgeB. const id = vOut[count].id; id.cf.indexA = vertexIndexA; id.cf.indexB = vIn0.id.cf.indexB; id.cf.typeA = exports.b2ContactFeatureType.e_vertex; id.cf.typeB = exports.b2ContactFeatureType.e_face; ++count; // b2Assert(count === 2); } return count; } /// Determine if two generic shapes overlap. const b2TestOverlapShape_s_input = new b2DistanceInput(); const b2TestOverlapShape_s_simplexCache = new b2SimplexCache(); const b2TestOverlapShape_s_output = new b2DistanceOutput(); function b2TestOverlapShape(shapeA, indexA, shapeB, indexB, xfA, xfB) { const input = b2TestOverlapShape_s_input.Reset(); input.proxyA.SetShape(shapeA, indexA); input.proxyB.SetShape(shapeB, indexB); input.transformA.Copy(xfA); input.transformB.Copy(xfB); input.useRadii = true; const simplexCache = b2TestOverlapShape_s_simplexCache.Reset(); simplexCache.count = 0; const output = b2TestOverlapShape_s_output.Reset(); b2Distance(output, simplexCache, input); return output.distance < 10 * b2_epsilon; } /* * Copyright (c) 2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ function verify(value) { if (value === null) { throw new Error(); } return value; } /// A node in the dynamic tree. The client does not interact with this directly. class b2TreeNode { constructor(id = 0) { this.m_id = 0; this.aabb = new b2AABB(); this._userData = null; this.parent = null; // or next this.child1 = null; this.child2 = null; this.height = 0; // leaf = 0, free node = -1 this.moved = false; this.m_id = id; } get userData() { if (this._userData === null) { throw new Error(); } return this._userData; } set userData(value) { if (this._userData !== null) { throw new Error(); } this._userData = value; } Reset() { this._userData = null; } IsLeaf() { return this.child1 === null; } } class b2DynamicTree { constructor() { this.m_root = null; // b2TreeNode* public m_nodes; // int32 public m_nodeCount; // int32 public m_nodeCapacity; this.m_freeList = null; this.m_insertionCount = 0; this.m_stack = new b2GrowableStack(256); } Query(...args) { let aabb, callback; if (args[0] instanceof b2AABB) { aabb = args[0]; callback = args[1]; } else { aabb = args[1]; callback = args[0]; } const stack = this.m_stack.Reset(); stack.Push(this.m_root); while (stack.GetCount() > 0) { const node = stack.Pop(); if (node === null) { continue; } if (node.aabb.TestOverlap(aabb)) { if (node.IsLeaf()) { const proceed = callback(node); if (!proceed) { return; } } else { stack.Push(node.child1); stack.Push(node.child2); } } } } QueryPoint(point, callback) { const stack = this.m_stack.Reset(); stack.Push(this.m_root); while (stack.GetCount() > 0) { const node = stack.Pop(); if (node === null) { continue; } if (node.aabb.TestContain(point)) { if (node.IsLeaf()) { const proceed = callback(node); if (!proceed) { return; } } else { stack.Push(node.child1); stack.Push(node.child2); } } } } RayCast(...args) { let callback, input; if (args[0] instanceof b2RayCastInput) { input = args[0]; callback = args[1]; } else { input = args[1]; callback = args[0]; } const p1 = input.p1; const p2 = input.p2; const r = b2Vec2.SubVV(p2, p1, b2DynamicTree.s_r); // DEBUG: b2Assert(r.LengthSquared() > 0); r.Normalize(); // v is perpendicular to the segment. const v = b2Vec2.CrossOneV(r, b2DynamicTree.s_v); const abs_v = b2Vec2.AbsV(v, b2DynamicTree.s_abs_v); // Separating axis for segment (Gino, p80). // |dot(v, p1 - c)| > dot(|v|, h) let maxFraction = input.maxFraction; // Build a bounding box for the segment. const segmentAABB = b2DynamicTree.s_segmentAABB; let t_x = p1.x + maxFraction * (p2.x - p1.x); let t_y = p1.y + maxFraction * (p2.y - p1.y); segmentAABB.lowerBound.x = b2Min(p1.x, t_x); segmentAABB.lowerBound.y = b2Min(p1.y, t_y); segmentAABB.upperBound.x = b2Max(p1.x, t_x); segmentAABB.upperBound.y = b2Max(p1.y, t_y); const stack = this.m_stack.Reset(); stack.Push(this.m_root); while (stack.GetCount() > 0) { const node = stack.Pop(); if (node === null) { continue; } if (!b2TestOverlapAABB(node.aabb, segmentAABB)) { continue; } // Separating axis for segment (Gino, p80). // |dot(v, p1 - c)| > dot(|v|, h) const c = node.aabb.GetCenter(); const h = node.aabb.GetExtents(); const separation = b2Abs(b2Vec2.DotVV(v, b2Vec2.SubVV(p1, c, b2Vec2.s_t0))) - b2Vec2.DotVV(abs_v, h); if (separation > 0) { continue; } if (node.IsLeaf()) { const subInput = b2DynamicTree.s_subInput; subInput.p1.Copy(input.p1); subInput.p2.Copy(input.p2); subInput.maxFraction = maxFraction; const value = callback(subInput, node); if (value === 0) { // The client has terminated the ray cast. return; } if (value > 0) { // Update segment bounding box. maxFraction = value; t_x = p1.x + maxFraction * (p2.x - p1.x); t_y = p1.y + maxFraction * (p2.y - p1.y); segmentAABB.lowerBound.x = b2Min(p1.x, t_x); segmentAABB.lowerBound.y = b2Min(p1.y, t_y); segmentAABB.upperBound.x = b2Max(p1.x, t_x); segmentAABB.upperBound.y = b2Max(p1.y, t_y); } } else { stack.Push(node.child1); stack.Push(node.child2); } } } AllocateNode() { // Expand the node pool as needed. if (this.m_freeList !== null) { const node = this.m_freeList; this.m_freeList = node.parent; // this.m_freeList = node.next; node.parent = null; node.child1 = null; node.child2 = null; node.height = 0; node.moved = false; return node; } return new b2TreeNode(b2DynamicTree.s_node_id++); } FreeNode(node) { node.parent = this.m_freeList; // node.next = this.m_freeList; node.child1 = null; node.child2 = null; node.height = -1; node.Reset(); this.m_freeList = node; } CreateProxy(aabb, userData) { const node = this.AllocateNode(); // Fatten the aabb. const r_x = b2_aabbExtension; const r_y = b2_aabbExtension; node.aabb.lowerBound.x = aabb.lowerBound.x - r_x; node.aabb.lowerBound.y = aabb.lowerBound.y - r_y; node.aabb.upperBound.x = aabb.upperBound.x + r_x; node.aabb.upperBound.y = aabb.upperBound.y + r_y; node.userData = userData; node.height = 0; node.moved = true; this.InsertLeaf(node); return node; } DestroyProxy(node) { // DEBUG: b2Assert(node.IsLeaf()); this.RemoveLeaf(node); this.FreeNode(node); } MoveProxy(node, aabb, displacement) { // DEBUG: b2Assert(node.IsLeaf()); // Extend AABB const fatAABB = b2DynamicTree.MoveProxy_s_fatAABB; const r_x = b2_aabbExtension; const r_y = b2_aabbExtension; fatAABB.lowerBound.x = aabb.lowerBound.x - r_x; fatAABB.lowerBound.y = aabb.lowerBound.y - r_y; fatAABB.upperBound.x = aabb.upperBound.x + r_x; fatAABB.upperBound.y = aabb.upperBound.y + r_y; // Predict AABB movement const d_x = b2_aabbMultiplier * displacement.x; const d_y = b2_aabbMultiplier * displacement.y; if (d_x < 0.0) { fatAABB.lowerBound.x += d_x; } else { fatAABB.upperBound.x += d_x; } if (d_y < 0.0) { fatAABB.lowerBound.y += d_y; } else { fatAABB.upperBound.y += d_y; } const treeAABB = node.aabb; // m_nodes[proxyId].aabb; if (treeAABB.Contains(aabb)) { // The tree AABB still contains the object, but it might be too large. // Perhaps the object was moving fast but has since gone to sleep. // The huge AABB is larger than the new fat AABB. const hugeAABB = b2DynamicTree.MoveProxy_s_hugeAABB; hugeAABB.lowerBound.x = fatAABB.lowerBound.x - 4.0 * r_x; hugeAABB.lowerBound.y = fatAABB.lowerBound.y - 4.0 * r_y; hugeAABB.upperBound.x = fatAABB.upperBound.x + 4.0 * r_x; hugeAABB.upperBound.y = fatAABB.upperBound.y + 4.0 * r_y; if (hugeAABB.Contains(treeAABB)) { // The tree AABB contains the object AABB and the tree AABB is // not too large. No tree update needed. return false; } // Otherwise the tree AABB is huge and needs to be shrunk } this.RemoveLeaf(node); node.aabb.Copy(fatAABB); // m_nodes[proxyId].aabb = fatAABB; this.InsertLeaf(node); node.moved = true; return true; } InsertLeaf(leaf) { ++this.m_insertionCount; if (this.m_root === null) { this.m_root = leaf; this.m_root.parent = null; return; } // Find the best sibling for this node const leafAABB = leaf.aabb; let sibling = this.m_root; while (!sibling.IsLeaf()) { const child1 = verify(sibling.child1); const child2 = verify(sibling.child2); const area = sibling.aabb.GetPerimeter(); const combinedAABB = b2DynamicTree.s_combinedAABB; combinedAABB.Combine2(sibling.aabb, leafAABB); const combinedArea = combinedAABB.GetPerimeter(); // Cost of creating a new parent for this node and the new leaf const cost = 2 * combinedArea; // Minimum cost of pushing the leaf further down the tree const inheritanceCost = 2 * (combinedArea - area); // Cost of descending into child1 let cost1; const aabb = b2DynamicTree.s_aabb; let oldArea; let newArea; if (child1.IsLeaf()) { aabb.Combine2(leafAABB, child1.aabb); cost1 = aabb.GetPerimeter() + inheritanceCost; } else { aabb.Combine2(leafAABB, child1.aabb); oldArea = child1.aabb.GetPerimeter(); newArea = aabb.GetPerimeter(); cost1 = (newArea - oldArea) + inheritanceCost; } // Cost of descending into child2 let cost2; if (child2.IsLeaf()) { aabb.Combine2(leafAABB, child2.aabb); cost2 = aabb.GetPerimeter() + inheritanceCost; } else { aabb.Combine2(leafAABB, child2.aabb); oldArea = child2.aabb.GetPerimeter(); newArea = aabb.GetPerimeter(); cost2 = newArea - oldArea + inheritanceCost; } // Descend according to the minimum cost. if (cost < cost1 && cost < cost2) { break; } // Descend if (cost1 < cost2) { sibling = child1; } else { sibling = child2; } } // Create a parent for the siblings. const oldParent = sibling.parent; const newParent = this.AllocateNode(); newParent.parent = oldParent; newParent.aabb.Combine2(leafAABB, sibling.aabb); newParent.height = sibling.height + 1; if (oldParent !== null) { // The sibling was not the root. if (oldParent.child1 === sibling) { oldParent.child1 = newParent; } else { oldParent.child2 = newParent; } newParent.child1 = sibling; newParent.child2 = leaf; sibling.parent = newParent; leaf.parent = newParent; } else { // The sibling was the root. newParent.child1 = sibling; newParent.child2 = leaf; sibling.parent = newParent; leaf.parent = newParent; this.m_root = newParent; } // Walk back up the tree fixing heights and AABBs let node = leaf.parent; while (node !== null) { node = this.Balance(node); const child1 = verify(node.child1); const child2 = verify(node.child2); node.height = 1 + b2Max(child1.height, child2.height); node.aabb.Combine2(child1.aabb, child2.aabb); node = node.parent; } // this.Validate(); } RemoveLeaf(leaf) { if (leaf === this.m_root) { this.m_root = null; return; } const parent = verify(leaf.parent); const grandParent = parent && parent.parent; const sibling = verify(parent.child1 === leaf ? parent.child2 : parent.child1); if (grandParent !== null) { // Destroy parent and connect sibling to grandParent. if (grandParent.child1 === parent) { grandParent.child1 = sibling; } else { grandParent.child2 = sibling; } sibling.parent = grandParent; this.FreeNode(parent); // Adjust ancestor bounds. let index = grandParent; while (index !== null) { index = this.Balance(index); const child1 = verify(index.child1); const child2 = verify(index.child2); index.aabb.Combine2(child1.aabb, child2.aabb); index.height = 1 + b2Max(child1.height, child2.height); index = index.parent; } } else { this.m_root = sibling; sibling.parent = null; this.FreeNode(parent); } // this.Validate(); } Balance(A) { // DEBUG: b2Assert(A !== null); if (A.IsLeaf() || A.height < 2) { return A; } const B = verify(A.child1); const C = verify(A.child2); const balance = C.height - B.height; // Rotate C up if (balance > 1) { const F = verify(C.child1); const G = verify(C.child2); // Swap A and C C.child1 = A; C.parent = A.parent; A.parent = C; // A's old parent should point to C if (C.parent !== null) { if (C.parent.child1 === A) { C.parent.child1 = C; } else { // DEBUG: b2Assert(C.parent.child2 === A); C.parent.child2 = C; } } else { this.m_root = C; } // Rotate if (F.height > G.height) { C.child2 = F; A.child2 = G; G.parent = A; A.aabb.Combine2(B.aabb, G.aabb); C.aabb.Combine2(A.aabb, F.aabb); A.height = 1 + b2Max(B.height, G.height); C.height = 1 + b2Max(A.height, F.height); } else { C.child2 = G; A.child2 = F; F.parent = A; A.aabb.Combine2(B.aabb, F.aabb); C.aabb.Combine2(A.aabb, G.aabb); A.height = 1 + b2Max(B.height, F.height); C.height = 1 + b2Max(A.height, G.height); } return C; } // Rotate B up if (balance < -1) { const D = verify(B.child1); const E = verify(B.child2); // Swap A and B B.child1 = A; B.parent = A.parent; A.parent = B; // A's old parent should point to B if (B.parent !== null) { if (B.parent.child1 === A) { B.parent.child1 = B; } else { // DEBUG: b2Assert(B.parent.child2 === A); B.parent.child2 = B; } } else { this.m_root = B; } // Rotate if (D.height > E.height) { B.child2 = D; A.child1 = E; E.parent = A; A.aabb.Combine2(C.aabb, E.aabb); B.aabb.Combine2(A.aabb, D.aabb); A.height = 1 + b2Max(C.height, E.height); B.height = 1 + b2Max(A.height, D.height); } else { B.child2 = E; A.child1 = D; D.parent = A; A.aabb.Combine2(C.aabb, D.aabb); B.aabb.Combine2(A.aabb, E.aabb); A.height = 1 + b2Max(C.height, D.height); B.height = 1 + b2Max(A.height, E.height); } return B; } return A; } GetHeight() { if (this.m_root === null) { return 0; } return this.m_root.height; } static GetAreaNode(node) { if (node === null) { return 0; } if (node.IsLeaf()) { return 0; } let area = node.aabb.GetPerimeter(); area += b2DynamicTree.GetAreaNode(node.child1); area += b2DynamicTree.GetAreaNode(node.child2); return area; } GetAreaRatio() { if (this.m_root === null) { return 0; } const root = this.m_root; const rootArea = root.aabb.GetPerimeter(); const totalArea = b2DynamicTree.GetAreaNode(this.m_root); /* float32 totalArea = 0.0; for (int32 i = 0; i < m_nodeCapacity; ++i) { const b2TreeNode* node = m_nodes + i; if (node.height < 0) { // Free node in pool continue; } totalArea += node.aabb.GetPerimeter(); } */ return totalArea / rootArea; } static ComputeHeightNode(node) { if (node === null) { return 0; } if (node.IsLeaf()) { return 0; } const height1 = b2DynamicTree.ComputeHeightNode(node.child1); const height2 = b2DynamicTree.ComputeHeightNode(node.child2); return 1 + b2Max(height1, height2); } ComputeHeight() { const height = b2DynamicTree.ComputeHeightNode(this.m_root); return height; } ValidateStructure(node) { if (node === null) { return; } if (node === this.m_root) ; if (node.IsLeaf()) { // DEBUG: b2Assert(node.child1 === null); // DEBUG: b2Assert(node.child2 === null); // DEBUG: b2Assert(node.height === 0); return; } const child1 = verify(node.child1); const child2 = verify(node.child2); // DEBUG: b2Assert(child1.parent === index); // DEBUG: b2Assert(child2.parent === index); this.ValidateStructure(child1); this.ValidateStructure(child2); } ValidateMetrics(node) { if (node === null) { return; } if (node.IsLeaf()) { // DEBUG: b2Assert(node.child1 === null); // DEBUG: b2Assert(node.child2 === null); // DEBUG: b2Assert(node.height === 0); return; } const child1 = verify(node.child1); const child2 = verify(node.child2); // DEBUG: const height1: number = child1.height; // DEBUG: const height2: number = child2.height; // DEBUG: const height: number = 1 + b2Max(height1, height2); // DEBUG: b2Assert(node.height === height); const aabb = b2DynamicTree.s_aabb; aabb.Combine2(child1.aabb, child2.aabb); // DEBUG: b2Assert(aabb.lowerBound === node.aabb.lowerBound); // DEBUG: b2Assert(aabb.upperBound === node.aabb.upperBound); this.ValidateMetrics(child1); this.ValidateMetrics(child2); } Validate() { // DEBUG: this.ValidateStructure(this.m_root); // DEBUG: this.ValidateMetrics(this.m_root); // let freeCount: number = 0; // let freeIndex: b2TreeNode | null = this.m_freeList; // while (freeIndex !== null) { // freeIndex = freeIndex.parent; // freeIndex = freeIndex.next; // ++freeCount; // } // DEBUG: b2Assert(this.GetHeight() === this.ComputeHeight()); // b2Assert(this.m_nodeCount + freeCount === this.m_nodeCapacity); } static GetMaxBalanceNode(node, maxBalance) { if (node === null) { return maxBalance; } if (node.height <= 1) { return maxBalance; } // DEBUG: b2Assert(!node.IsLeaf()); const child1 = verify(node.child1); const child2 = verify(node.child2); const balance = b2Abs(child2.height - child1.height); return b2Max(maxBalance, balance); } GetMaxBalance() { const maxBalance = b2DynamicTree.GetMaxBalanceNode(this.m_root, 0); /* int32 maxBalance = 0; for (int32 i = 0; i < m_nodeCapacity; ++i) { const b2TreeNode* node = m_nodes + i; if (node.height <= 1) { continue; } b2Assert(!node.IsLeaf()); int32 child1 = node.child1; int32 child2 = node.child2; int32 balance = b2Abs(m_nodes[child2].height - m_nodes[child1].height); maxBalance = b2Max(maxBalance, balance); } */ return maxBalance; } RebuildBottomUp() { /* int32* nodes = (int32*)b2Alloc(m_nodeCount * sizeof(int32)); int32 count = 0; // Build array of leaves. Free the rest. for (int32 i = 0; i < m_nodeCapacity; ++i) { if (m_nodes[i].height < 0) { // free node in pool continue; } if (m_nodes[i].IsLeaf()) { m_nodes[i].parent = b2_nullNode; nodes[count] = i; ++count; } else { FreeNode(i); } } while (count > 1) { float32 minCost = b2_maxFloat; int32 iMin = -1, jMin = -1; for (int32 i = 0; i < count; ++i) { b2AABB aabbi = m_nodes[nodes[i]].aabb; for (int32 j = i + 1; j < count; ++j) { b2AABB aabbj = m_nodes[nodes[j]].aabb; b2AABB b; b.Combine(aabbi, aabbj); float32 cost = b.GetPerimeter(); if (cost < minCost) { iMin = i; jMin = j; minCost = cost; } } } int32 index1 = nodes[iMin]; int32 index2 = nodes[jMin]; b2TreeNode* child1 = m_nodes + index1; b2TreeNode* child2 = m_nodes + index2; int32 parentIndex = AllocateNode(); b2TreeNode* parent = m_nodes + parentIndex; parent.child1 = index1; parent.child2 = index2; parent.height = 1 + b2Max(child1.height, child2.height); parent.aabb.Combine(child1.aabb, child2.aabb); parent.parent = b2_nullNode; child1.parent = parentIndex; child2.parent = parentIndex; nodes[jMin] = nodes[count-1]; nodes[iMin] = parentIndex; --count; } m_root = nodes[0]; b2Free(nodes); */ this.Validate(); } static ShiftOriginNode(node, newOrigin) { if (node === null) { return; } if (node.height <= 1) { return; } // DEBUG: b2Assert(!node.IsLeaf()); const child1 = node.child1; const child2 = node.child2; b2DynamicTree.ShiftOriginNode(child1, newOrigin); b2DynamicTree.ShiftOriginNode(child2, newOrigin); node.aabb.lowerBound.SelfSub(newOrigin); node.aabb.upperBound.SelfSub(newOrigin); } ShiftOrigin(newOrigin) { b2DynamicTree.ShiftOriginNode(this.m_root, newOrigin); /* // Build array of leaves. Free the rest. for (int32 i = 0; i < m_nodeCapacity; ++i) { m_nodes[i].aabb.lowerBound -= newOrigin; m_nodes[i].aabb.upperBound -= newOrigin; } */ } } b2DynamicTree.s_r = new b2Vec2(); b2DynamicTree.s_v = new b2Vec2(); b2DynamicTree.s_abs_v = new b2Vec2(); b2DynamicTree.s_segmentAABB = new b2AABB(); b2DynamicTree.s_subInput = new b2RayCastInput(); b2DynamicTree.s_combinedAABB = new b2AABB(); b2DynamicTree.s_aabb = new b2AABB(); b2DynamicTree.s_node_id = 0; b2DynamicTree.MoveProxy_s_fatAABB = new b2AABB(); b2DynamicTree.MoveProxy_s_hugeAABB = new b2AABB(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2Pair { constructor(proxyA, proxyB) { this.proxyA = proxyA; this.proxyB = proxyB; } } /// The broad-phase is used for computing pairs and performing volume queries and ray casts. /// This broad-phase does not persist pairs. Instead, this reports potentially new pairs. /// It is up to the client to consume the new pairs and to track subsequent overlap. class b2BroadPhase { constructor() { this.m_tree = new b2DynamicTree(); this.m_proxyCount = 0; // public m_moveCapacity: number = 16; this.m_moveCount = 0; this.m_moveBuffer = []; // public m_pairCapacity: number = 16; this.m_pairCount = 0; this.m_pairBuffer = []; } // public m_queryProxyId: number = 0; /// Create a proxy with an initial AABB. Pairs are not reported until /// UpdatePairs is called. CreateProxy(aabb, userData) { const proxy = this.m_tree.CreateProxy(aabb, userData); ++this.m_proxyCount; this.BufferMove(proxy); return proxy; } /// Destroy a proxy. It is up to the client to remove any pairs. DestroyProxy(proxy) { this.UnBufferMove(proxy); --this.m_proxyCount; this.m_tree.DestroyProxy(proxy); } /// Call MoveProxy as many times as you like, then when you are done /// call UpdatePairs to finalized the proxy pairs (for your time step). MoveProxy(proxy, aabb, displacement) { const buffer = this.m_tree.MoveProxy(proxy, aabb, displacement); if (buffer) { this.BufferMove(proxy); } } /// Call to trigger a re-processing of it's pairs on the next call to UpdatePairs. TouchProxy(proxy) { this.BufferMove(proxy); } /// Get the fat AABB for a proxy. // public GetFatAABB(proxy: b2TreeNode): b2AABB { // return this.m_tree.GetFatAABB(proxy); // } /// Get user data from a proxy. Returns NULL if the id is invalid. // public GetUserData(proxy: b2TreeNode): T { // return this.m_tree.GetUserData(proxy); // } /// Test overlap of fat AABBs. // public TestOverlap(proxyA: b2TreeNode, proxyB: b2TreeNode): boolean { // const aabbA: b2AABB = this.m_tree.GetFatAABB(proxyA); // const aabbB: b2AABB = this.m_tree.GetFatAABB(proxyB); // return b2TestOverlapAABB(aabbA, aabbB); // } /// Get the number of proxies. GetProxyCount() { return this.m_proxyCount; } /// Update the pairs. This results in pair callbacks. This can only add pairs. UpdatePairs(callback) { // Reset pair buffer this.m_pairCount = 0; // Perform tree queries for all moving proxies. for (let i = 0; i < this.m_moveCount; ++i) { const queryProxy = this.m_moveBuffer[i]; if (queryProxy === null) { continue; } // This is called from b2.DynamicTree::Query when we are gathering pairs. // boolean b2BroadPhase::QueryCallback(int32 proxyId); // We have to query the tree with the fat AABB so that // we don't fail to create a pair that may touch later. const fatAABB = queryProxy.aabb; // this.m_tree.GetFatAABB(queryProxy); // Query tree, create pairs and add them pair buffer. this.m_tree.Query(fatAABB, (proxy) => { // A proxy cannot form a pair with itself. if (proxy.m_id === queryProxy.m_id) { return true; } const moved = proxy.moved; // this.m_tree.WasMoved(proxy); if (moved && proxy.m_id > queryProxy.m_id) { // Both proxies are moving. Avoid duplicate pairs. return true; } // const proxyA = proxy < queryProxy ? proxy : queryProxy; // const proxyB = proxy >= queryProxy ? proxy : queryProxy; let proxyA; let proxyB; if (proxy.m_id < queryProxy.m_id) { proxyA = proxy; proxyB = queryProxy; } else { proxyA = queryProxy; proxyB = proxy; } // Grow the pair buffer as needed. if (this.m_pairCount === this.m_pairBuffer.length) { this.m_pairBuffer[this.m_pairCount] = new b2Pair(proxyA, proxyB); } else { const pair = this.m_pairBuffer[this.m_pairCount]; pair.proxyA = proxyA; pair.proxyB = proxyB; } ++this.m_pairCount; return true; }); } // Send pairs to caller for (let i = 0; i < this.m_pairCount; ++i) { const primaryPair = this.m_pairBuffer[i]; const userDataA = primaryPair.proxyA.userData; // this.m_tree.GetUserData(primaryPair.proxyA); const userDataB = primaryPair.proxyB.userData; // this.m_tree.GetUserData(primaryPair.proxyB); callback(userDataA, userDataB); } // Clear move flags for (let i = 0; i < this.m_moveCount; ++i) { const proxy = this.m_moveBuffer[i]; if (proxy === null) { continue; } proxy.moved = false; // this.m_tree.ClearMoved(proxy); } // Reset move buffer this.m_moveCount = 0; } Query(...args) { this.m_tree.Query(args[0], args[1]); } QueryPoint(point, callback) { this.m_tree.QueryPoint(point, callback); } /// Ray-cast against the proxies in the tree. This relies on the callback /// to perform a exact ray-cast in the case were the proxy contains a shape. /// The callback also performs the any collision filtering. This has performance /// roughly equal to k * log(n), where k is the number of collisions and n is the /// number of proxies in the tree. /// @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). /// @param callback a callback class that is called for each proxy that is hit by the ray. RayCast(input, callback) { this.m_tree.RayCast(input, callback); } /// Get the height of the embedded tree. GetTreeHeight() { return this.m_tree.GetHeight(); } /// Get the balance of the embedded tree. GetTreeBalance() { return this.m_tree.GetMaxBalance(); } /// Get the quality metric of the embedded tree. GetTreeQuality() { return this.m_tree.GetAreaRatio(); } /// Shift the world origin. Useful for large worlds. /// The shift formula is: position -= newOrigin /// @param newOrigin the new origin with respect to the old origin ShiftOrigin(newOrigin) { this.m_tree.ShiftOrigin(newOrigin); } BufferMove(proxy) { this.m_moveBuffer[this.m_moveCount] = proxy; ++this.m_moveCount; } UnBufferMove(proxy) { for (let i = 0; i < this.m_moveCount; i++) { if (this.m_moveBuffer[i] == proxy) { this.m_moveBuffer[i] = null; } } } } /* * Copyright (c) 2006-2010 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// A line segment (edge) shape. These can be connected in chains or loops /// to other edge shapes. Edges created independently are two-sided and do /// no provide smooth movement across junctions. class b2EdgeShape extends b2Shape { constructor() { super(exports.b2ShapeType.e_edgeShape, b2_polygonRadius); this.m_vertex1 = new b2Vec2(); this.m_vertex2 = new b2Vec2(); this.m_vertex0 = new b2Vec2(); this.m_vertex3 = new b2Vec2(); /// Uses m_vertex0 and m_vertex3 to create smooth collision. this.m_oneSided = false; } /// Set this as a part of a sequence. Vertex v0 precedes the edge and vertex v3 /// follows. These extra vertices are used to provide smooth movement /// across junctions. This also makes the collision one-sided. The edge /// normal points to the right looking from v1 to v2. // void SetOneSided(const b2Vec2& v0, const b2Vec2& v1,const b2Vec2& v2, const b2Vec2& v3); SetOneSided(v0, v1, v2, v3) { this.m_vertex0.Copy(v0); this.m_vertex1.Copy(v1); this.m_vertex2.Copy(v2); this.m_vertex3.Copy(v3); this.m_oneSided = true; return this; } /// Set this as an isolated edge. Collision is two-sided. SetTwoSided(v1, v2) { this.m_vertex1.Copy(v1); this.m_vertex2.Copy(v2); this.m_oneSided = false; return this; } /// Implement b2Shape. Clone() { return new b2EdgeShape().Copy(this); } Copy(other) { super.Copy(other); // DEBUG: b2Assert(other instanceof b2EdgeShape); this.m_vertex1.Copy(other.m_vertex1); this.m_vertex2.Copy(other.m_vertex2); this.m_vertex0.Copy(other.m_vertex0); this.m_vertex3.Copy(other.m_vertex3); this.m_oneSided = other.m_oneSided; return this; } /// @see b2Shape::GetChildCount GetChildCount() { return 1; } /// @see b2Shape::TestPoint TestPoint(xf, p) { return false; } RayCast(output, input, xf, childIndex) { // Put the ray into the edge's frame of reference. const p1 = b2Transform.MulTXV(xf, input.p1, b2EdgeShape.RayCast_s_p1); const p2 = b2Transform.MulTXV(xf, input.p2, b2EdgeShape.RayCast_s_p2); const d = b2Vec2.SubVV(p2, p1, b2EdgeShape.RayCast_s_d); const v1 = this.m_vertex1; const v2 = this.m_vertex2; const e = b2Vec2.SubVV(v2, v1, b2EdgeShape.RayCast_s_e); // Normal points to the right, looking from v1 at v2 const normal = output.normal.Set(e.y, -e.x).SelfNormalize(); // q = p1 + t * d // dot(normal, q - v1) = 0 // dot(normal, p1 - v1) + t * dot(normal, d) = 0 const numerator = b2Vec2.DotVV(normal, b2Vec2.SubVV(v1, p1, b2Vec2.s_t0)); if (this.m_oneSided && numerator > 0.0) { return false; } const denominator = b2Vec2.DotVV(normal, d); if (denominator === 0) { return false; } const t = numerator / denominator; if (t < 0 || input.maxFraction < t) { return false; } const q = b2Vec2.AddVMulSV(p1, t, d, b2EdgeShape.RayCast_s_q); // q = v1 + s * r // s = dot(q - v1, r) / dot(r, r) const r = b2Vec2.SubVV(v2, v1, b2EdgeShape.RayCast_s_r); const rr = b2Vec2.DotVV(r, r); if (rr === 0) { return false; } const s = b2Vec2.DotVV(b2Vec2.SubVV(q, v1, b2Vec2.s_t0), r) / rr; if (s < 0 || 1 < s) { return false; } output.fraction = t; b2Rot.MulRV(xf.q, output.normal, output.normal); if (numerator > 0) { output.normal.SelfNeg(); } return true; } ComputeAABB(aabb, xf, childIndex) { const v1 = b2Transform.MulXV(xf, this.m_vertex1, b2EdgeShape.ComputeAABB_s_v1); const v2 = b2Transform.MulXV(xf, this.m_vertex2, b2EdgeShape.ComputeAABB_s_v2); b2Vec2.MinV(v1, v2, aabb.lowerBound); b2Vec2.MaxV(v1, v2, aabb.upperBound); const r = this.m_radius; aabb.lowerBound.SelfSubXY(r, r); aabb.upperBound.SelfAddXY(r, r); } /// @see b2Shape::ComputeMass ComputeMass(massData, density) { massData.mass = 0; b2Vec2.MidVV(this.m_vertex1, this.m_vertex2, massData.center); massData.I = 0; } SetupDistanceProxy(proxy, index) { proxy.m_vertices = proxy.m_buffer; proxy.m_vertices[0].Copy(this.m_vertex1); proxy.m_vertices[1].Copy(this.m_vertex2); proxy.m_count = 2; proxy.m_radius = this.m_radius; } ComputeSubmergedArea(normal, offset, xf, c) { c.SetZero(); return 0; } Dump(log) { log(" const shape: b2EdgeShape = new b2EdgeShape();\n"); log(" shape.m_radius = %.15f;\n", this.m_radius); log(" shape.m_vertex0.Set(%.15f, %.15f);\n", this.m_vertex0.x, this.m_vertex0.y); log(" shape.m_vertex1.Set(%.15f, %.15f);\n", this.m_vertex1.x, this.m_vertex1.y); log(" shape.m_vertex2.Set(%.15f, %.15f);\n", this.m_vertex2.x, this.m_vertex2.y); log(" shape.m_vertex3.Set(%.15f, %.15f);\n", this.m_vertex3.x, this.m_vertex3.y); log(" shape.m_oneSided = %s;\n", this.m_oneSided); } } /// Implement b2Shape. // p = p1 + t * d // v = v1 + s * e // p1 + t * d = v1 + s * e // s * e - t * d = p1 - v1 b2EdgeShape.RayCast_s_p1 = new b2Vec2(); b2EdgeShape.RayCast_s_p2 = new b2Vec2(); b2EdgeShape.RayCast_s_d = new b2Vec2(); b2EdgeShape.RayCast_s_e = new b2Vec2(); b2EdgeShape.RayCast_s_q = new b2Vec2(); b2EdgeShape.RayCast_s_r = new b2Vec2(); /// @see b2Shape::ComputeAABB b2EdgeShape.ComputeAABB_s_v1 = new b2Vec2(); b2EdgeShape.ComputeAABB_s_v2 = new b2Vec2(); /* * Copyright (c) 2006-2010 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// A chain shape is a free form sequence of line segments. /// The chain has one-sided collision, with the surface normal pointing to the right of the edge. /// This provides a counter-clockwise winding like the polygon shape. /// Connectivity information is used to create smooth collisions. /// @warning the chain will not collide properly if there are self-intersections. class b2ChainShape extends b2Shape { constructor() { super(exports.b2ShapeType.e_chainShape, b2_polygonRadius); this.m_vertices = []; this.m_count = 0; this.m_prevVertex = new b2Vec2(); this.m_nextVertex = new b2Vec2(); } CreateLoop(...args) { if (typeof args[0][0] === "number") { const vertices = args[0]; if (vertices.length % 2 !== 0) { throw new Error(); } return this._CreateLoop((index) => ({ x: vertices[index * 2], y: vertices[index * 2 + 1] }), vertices.length / 2); } else { const vertices = args[0]; const count = args[1] || vertices.length; return this._CreateLoop((index) => vertices[index], count); } } _CreateLoop(vertices, count) { // DEBUG: b2Assert(count >= 3); if (count < 3) { return this; } // DEBUG: for (let i: number = 1; i < count; ++i) { // DEBUG: const v1 = vertices[start + i - 1]; // DEBUG: const v2 = vertices[start + i]; // DEBUG: // If the code crashes here, it means your vertices are too close together. // DEBUG: b2Assert(b2Vec2.DistanceSquaredVV(v1, v2) > b2_linearSlop * b2_linearSlop); // DEBUG: } this.m_count = count + 1; this.m_vertices = b2Vec2.MakeArray(this.m_count); for (let i = 0; i < count; ++i) { this.m_vertices[i].Copy(vertices(i)); } this.m_vertices[count].Copy(this.m_vertices[0]); this.m_prevVertex.Copy(this.m_vertices[this.m_count - 2]); this.m_nextVertex.Copy(this.m_vertices[1]); return this; } CreateChain(...args) { if (typeof args[0][0] === "number") { const vertices = args[0]; const prevVertex = args[1]; const nextVertex = args[2]; if (vertices.length % 2 !== 0) { throw new Error(); } return this._CreateChain((index) => ({ x: vertices[index * 2], y: vertices[index * 2 + 1] }), vertices.length / 2, prevVertex, nextVertex); } else { const vertices = args[0]; const count = args[1] || vertices.length; const prevVertex = args[2]; const nextVertex = args[3]; return this._CreateChain((index) => vertices[index], count, prevVertex, nextVertex); } } _CreateChain(vertices, count, prevVertex, nextVertex) { // DEBUG: b2Assert(count >= 2); // DEBUG: for (let i: number = 1; i < count; ++i) { // DEBUG: const v1 = vertices[start + i - 1]; // DEBUG: const v2 = vertices[start + i]; // DEBUG: // If the code crashes here, it means your vertices are too close together. // DEBUG: b2Assert(b2Vec2.DistanceSquaredVV(v1, v2) > b2_linearSlop * b2_linearSlop); // DEBUG: } this.m_count = count; this.m_vertices = b2Vec2.MakeArray(count); for (let i = 0; i < count; ++i) { this.m_vertices[i].Copy(vertices(i)); } this.m_prevVertex.Copy(prevVertex); this.m_nextVertex.Copy(nextVertex); return this; } /// Implement b2Shape. Vertices are cloned using b2Alloc. Clone() { return new b2ChainShape().Copy(this); } Copy(other) { super.Copy(other); // DEBUG: b2Assert(other instanceof b2ChainShape); this._CreateChain((index) => other.m_vertices[index], other.m_count, other.m_prevVertex, other.m_nextVertex); this.m_prevVertex.Copy(other.m_prevVertex); this.m_nextVertex.Copy(other.m_nextVertex); return this; } /// @see b2Shape::GetChildCount GetChildCount() { // edge count = vertex count - 1 return this.m_count - 1; } /// Get a child edge. GetChildEdge(edge, index) { // DEBUG: b2Assert(0 <= index && index < this.m_count - 1); edge.m_radius = this.m_radius; edge.m_vertex1.Copy(this.m_vertices[index]); edge.m_vertex2.Copy(this.m_vertices[index + 1]); edge.m_oneSided = true; if (index > 0) { edge.m_vertex0.Copy(this.m_vertices[index - 1]); } else { edge.m_vertex0.Copy(this.m_prevVertex); } if (index < this.m_count - 2) { edge.m_vertex3.Copy(this.m_vertices[index + 2]); } else { edge.m_vertex3.Copy(this.m_nextVertex); } } /// This always return false. /// @see b2Shape::TestPoint TestPoint(xf, p) { return false; } RayCast(output, input, xf, childIndex) { // DEBUG: b2Assert(childIndex < this.m_count); const edgeShape = b2ChainShape.RayCast_s_edgeShape; edgeShape.m_vertex1.Copy(this.m_vertices[childIndex]); edgeShape.m_vertex2.Copy(this.m_vertices[(childIndex + 1) % this.m_count]); return edgeShape.RayCast(output, input, xf, 0); } ComputeAABB(aabb, xf, childIndex) { // DEBUG: b2Assert(childIndex < this.m_count); const vertexi1 = this.m_vertices[childIndex]; const vertexi2 = this.m_vertices[(childIndex + 1) % this.m_count]; const v1 = b2Transform.MulXV(xf, vertexi1, b2ChainShape.ComputeAABB_s_v1); const v2 = b2Transform.MulXV(xf, vertexi2, b2ChainShape.ComputeAABB_s_v2); const lower = b2Vec2.MinV(v1, v2, b2ChainShape.ComputeAABB_s_lower); const upper = b2Vec2.MaxV(v1, v2, b2ChainShape.ComputeAABB_s_upper); aabb.lowerBound.x = lower.x - this.m_radius; aabb.lowerBound.y = lower.y - this.m_radius; aabb.upperBound.x = upper.x + this.m_radius; aabb.upperBound.y = upper.y + this.m_radius; } /// Chains have zero mass. /// @see b2Shape::ComputeMass ComputeMass(massData, density) { massData.mass = 0; massData.center.SetZero(); massData.I = 0; } SetupDistanceProxy(proxy, index) { // DEBUG: b2Assert(0 <= index && index < this.m_count); proxy.m_vertices = proxy.m_buffer; proxy.m_vertices[0].Copy(this.m_vertices[index]); if (index + 1 < this.m_count) { proxy.m_vertices[1].Copy(this.m_vertices[index + 1]); } else { proxy.m_vertices[1].Copy(this.m_vertices[0]); } proxy.m_count = 2; proxy.m_radius = this.m_radius; } ComputeSubmergedArea(normal, offset, xf, c) { c.SetZero(); return 0; } Dump(log) { log(" const shape: b2ChainShape = new b2ChainShape();\n"); log(" const vs: b2Vec2[] = [];\n"); for (let i = 0; i < this.m_count; ++i) { log(" vs[%d] = new bVec2(%.15f, %.15f);\n", i, this.m_vertices[i].x, this.m_vertices[i].y); } log(" shape.CreateChain(vs, %d);\n", this.m_count); log(" shape.m_prevVertex.Set(%.15f, %.15f);\n", this.m_prevVertex.x, this.m_prevVertex.y); log(" shape.m_nextVertex.Set(%.15f, %.15f);\n", this.m_nextVertex.x, this.m_nextVertex.y); } } /// Implement b2Shape. b2ChainShape.RayCast_s_edgeShape = new b2EdgeShape(); /// @see b2Shape::ComputeAABB b2ChainShape.ComputeAABB_s_v1 = new b2Vec2(); b2ChainShape.ComputeAABB_s_v2 = new b2Vec2(); b2ChainShape.ComputeAABB_s_lower = new b2Vec2(); b2ChainShape.ComputeAABB_s_upper = new b2Vec2(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// A solid circle shape class b2CircleShape extends b2Shape { constructor(radius = 0) { super(exports.b2ShapeType.e_circleShape, radius); this.m_p = new b2Vec2(); } Set(position, radius = this.m_radius) { this.m_p.Copy(position); this.m_radius = radius; return this; } /// Implement b2Shape. Clone() { return new b2CircleShape().Copy(this); } Copy(other) { super.Copy(other); // DEBUG: b2Assert(other instanceof b2CircleShape); this.m_p.Copy(other.m_p); return this; } /// @see b2Shape::GetChildCount GetChildCount() { return 1; } TestPoint(transform, p) { const center = b2Transform.MulXV(transform, this.m_p, b2CircleShape.TestPoint_s_center); const d = b2Vec2.SubVV(p, center, b2CircleShape.TestPoint_s_d); return b2Vec2.DotVV(d, d) <= b2Sq(this.m_radius); } RayCast(output, input, transform, childIndex) { const position = b2Transform.MulXV(transform, this.m_p, b2CircleShape.RayCast_s_position); const s = b2Vec2.SubVV(input.p1, position, b2CircleShape.RayCast_s_s); const b = b2Vec2.DotVV(s, s) - b2Sq(this.m_radius); // Solve quadratic equation. const r = b2Vec2.SubVV(input.p2, input.p1, b2CircleShape.RayCast_s_r); const c = b2Vec2.DotVV(s, r); const rr = b2Vec2.DotVV(r, r); const sigma = c * c - rr * b; // Check for negative discriminant and short segment. if (sigma < 0 || rr < b2_epsilon) { return false; } // Find the point of intersection of the line with the circle. let a = (-(c + b2Sqrt(sigma))); // Is the intersection point on the segment? if (0 <= a && a <= input.maxFraction * rr) { a /= rr; output.fraction = a; b2Vec2.AddVMulSV(s, a, r, output.normal).SelfNormalize(); return true; } return false; } ComputeAABB(aabb, transform, childIndex) { const p = b2Transform.MulXV(transform, this.m_p, b2CircleShape.ComputeAABB_s_p); aabb.lowerBound.Set(p.x - this.m_radius, p.y - this.m_radius); aabb.upperBound.Set(p.x + this.m_radius, p.y + this.m_radius); } /// @see b2Shape::ComputeMass ComputeMass(massData, density) { const radius_sq = b2Sq(this.m_radius); massData.mass = density * b2_pi * radius_sq; massData.center.Copy(this.m_p); // inertia about the local origin massData.I = massData.mass * (0.5 * radius_sq + b2Vec2.DotVV(this.m_p, this.m_p)); } SetupDistanceProxy(proxy, index) { proxy.m_vertices = proxy.m_buffer; proxy.m_vertices[0].Copy(this.m_p); proxy.m_count = 1; proxy.m_radius = this.m_radius; } ComputeSubmergedArea(normal, offset, xf, c) { const p = b2Transform.MulXV(xf, this.m_p, new b2Vec2()); const l = (-(b2Vec2.DotVV(normal, p) - offset)); if (l < (-this.m_radius) + b2_epsilon) { // Completely dry return 0; } if (l > this.m_radius) { // Completely wet c.Copy(p); return b2_pi * this.m_radius * this.m_radius; } // Magic const r2 = this.m_radius * this.m_radius; const l2 = l * l; const area = r2 * (b2Asin(l / this.m_radius) + b2_pi / 2) + l * b2Sqrt(r2 - l2); const com = (-2 / 3 * b2Pow(r2 - l2, 1.5) / area); c.x = p.x + normal.x * com; c.y = p.y + normal.y * com; return area; } Dump(log) { log(" const shape: b2CircleShape = new b2CircleShape();\n"); log(" shape.m_radius = %.15f;\n", this.m_radius); log(" shape.m_p.Set(%.15f, %.15f);\n", this.m_p.x, this.m_p.y); } } /// Implement b2Shape. b2CircleShape.TestPoint_s_center = new b2Vec2(); b2CircleShape.TestPoint_s_d = new b2Vec2(); /// Implement b2Shape. /// @note because the circle is solid, rays that start inside do not hit because the normal is /// not defined. // Collision Detection in Interactive 3D Environments by Gino van den Bergen // From Section 3.1.2 // x = s + a * r // norm(x) = radius b2CircleShape.RayCast_s_position = new b2Vec2(); b2CircleShape.RayCast_s_s = new b2Vec2(); b2CircleShape.RayCast_s_r = new b2Vec2(); /// @see b2Shape::ComputeAABB b2CircleShape.ComputeAABB_s_p = new b2Vec2(); const b2CollideCircles_s_pA = new b2Vec2(); const b2CollideCircles_s_pB = new b2Vec2(); function b2CollideCircles(manifold, circleA, xfA, circleB, xfB) { manifold.pointCount = 0; const pA = b2Transform.MulXV(xfA, circleA.m_p, b2CollideCircles_s_pA); const pB = b2Transform.MulXV(xfB, circleB.m_p, b2CollideCircles_s_pB); const distSqr = b2Vec2.DistanceSquaredVV(pA, pB); const radius = circleA.m_radius + circleB.m_radius; if (distSqr > radius * radius) { return; } manifold.type = exports.b2ManifoldType.e_circles; manifold.localPoint.Copy(circleA.m_p); manifold.localNormal.SetZero(); manifold.pointCount = 1; manifold.points[0].localPoint.Copy(circleB.m_p); manifold.points[0].id.key = 0; } const b2CollidePolygonAndCircle_s_c = new b2Vec2(); const b2CollidePolygonAndCircle_s_cLocal = new b2Vec2(); const b2CollidePolygonAndCircle_s_faceCenter = new b2Vec2(); function b2CollidePolygonAndCircle(manifold, polygonA, xfA, circleB, xfB) { manifold.pointCount = 0; // Compute circle position in the frame of the polygon. const c = b2Transform.MulXV(xfB, circleB.m_p, b2CollidePolygonAndCircle_s_c); const cLocal = b2Transform.MulTXV(xfA, c, b2CollidePolygonAndCircle_s_cLocal); // Find the min separating edge. let normalIndex = 0; let separation = (-b2_maxFloat); const radius = polygonA.m_radius + circleB.m_radius; const vertexCount = polygonA.m_count; const vertices = polygonA.m_vertices; const normals = polygonA.m_normals; for (let i = 0; i < vertexCount; ++i) { const s = b2Vec2.DotVV(normals[i], b2Vec2.SubVV(cLocal, vertices[i], b2Vec2.s_t0)); if (s > radius) { // Early out. return; } if (s > separation) { separation = s; normalIndex = i; } } // Vertices that subtend the incident face. const vertIndex1 = normalIndex; const vertIndex2 = (vertIndex1 + 1) % vertexCount; const v1 = vertices[vertIndex1]; const v2 = vertices[vertIndex2]; // If the center is inside the polygon ... if (separation < b2_epsilon) { manifold.pointCount = 1; manifold.type = exports.b2ManifoldType.e_faceA; manifold.localNormal.Copy(normals[normalIndex]); b2Vec2.MidVV(v1, v2, manifold.localPoint); manifold.points[0].localPoint.Copy(circleB.m_p); manifold.points[0].id.key = 0; return; } // Compute barycentric coordinates const u1 = b2Vec2.DotVV(b2Vec2.SubVV(cLocal, v1, b2Vec2.s_t0), b2Vec2.SubVV(v2, v1, b2Vec2.s_t1)); const u2 = b2Vec2.DotVV(b2Vec2.SubVV(cLocal, v2, b2Vec2.s_t0), b2Vec2.SubVV(v1, v2, b2Vec2.s_t1)); if (u1 <= 0) { if (b2Vec2.DistanceSquaredVV(cLocal, v1) > radius * radius) { return; } manifold.pointCount = 1; manifold.type = exports.b2ManifoldType.e_faceA; b2Vec2.SubVV(cLocal, v1, manifold.localNormal).SelfNormalize(); manifold.localPoint.Copy(v1); manifold.points[0].localPoint.Copy(circleB.m_p); manifold.points[0].id.key = 0; } else if (u2 <= 0) { if (b2Vec2.DistanceSquaredVV(cLocal, v2) > radius * radius) { return; } manifold.pointCount = 1; manifold.type = exports.b2ManifoldType.e_faceA; b2Vec2.SubVV(cLocal, v2, manifold.localNormal).SelfNormalize(); manifold.localPoint.Copy(v2); manifold.points[0].localPoint.Copy(circleB.m_p); manifold.points[0].id.key = 0; } else { const faceCenter = b2Vec2.MidVV(v1, v2, b2CollidePolygonAndCircle_s_faceCenter); const separation = b2Vec2.DotVV(b2Vec2.SubVV(cLocal, faceCenter, b2Vec2.s_t1), normals[vertIndex1]); if (separation > radius) { return; } manifold.pointCount = 1; manifold.type = exports.b2ManifoldType.e_faceA; manifold.localNormal.Copy(normals[vertIndex1]).SelfNormalize(); manifold.localPoint.Copy(faceCenter); manifold.points[0].localPoint.Copy(circleB.m_p); manifold.points[0].id.key = 0; } } // DEBUG: import { b2Assert } from "../common/b2_settings.js"; const b2CollideEdgeAndCircle_s_Q = new b2Vec2(); const b2CollideEdgeAndCircle_s_e = new b2Vec2(); const b2CollideEdgeAndCircle_s_d = new b2Vec2(); const b2CollideEdgeAndCircle_s_e1 = new b2Vec2(); const b2CollideEdgeAndCircle_s_e2 = new b2Vec2(); const b2CollideEdgeAndCircle_s_P = new b2Vec2(); const b2CollideEdgeAndCircle_s_n = new b2Vec2(); const b2CollideEdgeAndCircle_s_id = new b2ContactID(); function b2CollideEdgeAndCircle(manifold, edgeA, xfA, circleB, xfB) { manifold.pointCount = 0; // Compute circle in frame of edge const Q = b2Transform.MulTXV(xfA, b2Transform.MulXV(xfB, circleB.m_p, b2Vec2.s_t0), b2CollideEdgeAndCircle_s_Q); const A = edgeA.m_vertex1; const B = edgeA.m_vertex2; const e = b2Vec2.SubVV(B, A, b2CollideEdgeAndCircle_s_e); // Normal points to the right for a CCW winding // b2Vec2 n(e.y, -e.x); // const n: b2Vec2 = b2CollideEdgeAndCircle_s_n.Set(-e.y, e.x); const n = b2CollideEdgeAndCircle_s_n.Set(e.y, -e.x); // float offset = b2Dot(n, Q - A); const offset = b2Vec2.DotVV(n, b2Vec2.SubVV(Q, A, b2Vec2.s_t0)); const oneSided = edgeA.m_oneSided; if (oneSided && offset < 0.0) { return; } // Barycentric coordinates const u = b2Vec2.DotVV(e, b2Vec2.SubVV(B, Q, b2Vec2.s_t0)); const v = b2Vec2.DotVV(e, b2Vec2.SubVV(Q, A, b2Vec2.s_t0)); const radius = edgeA.m_radius + circleB.m_radius; // const cf: b2ContactFeature = new b2ContactFeature(); const id = b2CollideEdgeAndCircle_s_id; id.cf.indexB = 0; id.cf.typeB = exports.b2ContactFeatureType.e_vertex; // Region A if (v <= 0) { const P = A; const d = b2Vec2.SubVV(Q, P, b2CollideEdgeAndCircle_s_d); const dd = b2Vec2.DotVV(d, d); if (dd > radius * radius) { return; } // Is there an edge connected to A? if (edgeA.m_oneSided) { const A1 = edgeA.m_vertex0; const B1 = A; const e1 = b2Vec2.SubVV(B1, A1, b2CollideEdgeAndCircle_s_e1); const u1 = b2Vec2.DotVV(e1, b2Vec2.SubVV(B1, Q, b2Vec2.s_t0)); // Is the circle in Region AB of the previous edge? if (u1 > 0) { return; } } id.cf.indexA = 0; id.cf.typeA = exports.b2ContactFeatureType.e_vertex; manifold.pointCount = 1; manifold.type = exports.b2ManifoldType.e_circles; manifold.localNormal.SetZero(); manifold.localPoint.Copy(P); manifold.points[0].id.Copy(id); // manifold.points[0].id.key = 0; // manifold.points[0].id.cf = cf; manifold.points[0].localPoint.Copy(circleB.m_p); return; } // Region B if (u <= 0) { const P = B; const d = b2Vec2.SubVV(Q, P, b2CollideEdgeAndCircle_s_d); const dd = b2Vec2.DotVV(d, d); if (dd > radius * radius) { return; } // Is there an edge connected to B? if (edgeA.m_oneSided) { const B2 = edgeA.m_vertex3; const A2 = B; const e2 = b2Vec2.SubVV(B2, A2, b2CollideEdgeAndCircle_s_e2); const v2 = b2Vec2.DotVV(e2, b2Vec2.SubVV(Q, A2, b2Vec2.s_t0)); // Is the circle in Region AB of the next edge? if (v2 > 0) { return; } } id.cf.indexA = 1; id.cf.typeA = exports.b2ContactFeatureType.e_vertex; manifold.pointCount = 1; manifold.type = exports.b2ManifoldType.e_circles; manifold.localNormal.SetZero(); manifold.localPoint.Copy(P); manifold.points[0].id.Copy(id); // manifold.points[0].id.key = 0; // manifold.points[0].id.cf = cf; manifold.points[0].localPoint.Copy(circleB.m_p); return; } // Region AB const den = b2Vec2.DotVV(e, e); // DEBUG: b2Assert(den > 0); const P = b2CollideEdgeAndCircle_s_P; P.x = (1 / den) * (u * A.x + v * B.x); P.y = (1 / den) * (u * A.y + v * B.y); const d = b2Vec2.SubVV(Q, P, b2CollideEdgeAndCircle_s_d); const dd = b2Vec2.DotVV(d, d); if (dd > radius * radius) { return; } if (offset < 0) { n.Set(-n.x, -n.y); } n.Normalize(); id.cf.indexA = 0; id.cf.typeA = exports.b2ContactFeatureType.e_face; manifold.pointCount = 1; manifold.type = exports.b2ManifoldType.e_faceA; manifold.localNormal.Copy(n); manifold.localPoint.Copy(A); manifold.points[0].id.Copy(id); // manifold.points[0].id.key = 0; // manifold.points[0].id.cf = cf; manifold.points[0].localPoint.Copy(circleB.m_p); } var b2EPAxisType; (function (b2EPAxisType) { b2EPAxisType[b2EPAxisType["e_unknown"] = 0] = "e_unknown"; b2EPAxisType[b2EPAxisType["e_edgeA"] = 1] = "e_edgeA"; b2EPAxisType[b2EPAxisType["e_edgeB"] = 2] = "e_edgeB"; })(b2EPAxisType || (b2EPAxisType = {})); class b2EPAxis { constructor() { this.normal = new b2Vec2(); this.type = b2EPAxisType.e_unknown; this.index = 0; this.separation = 0; } } class b2TempPolygon { constructor() { this.vertices = []; this.normals = []; this.count = 0; } } class b2ReferenceFace { constructor() { this.i1 = 0; this.i2 = 0; this.v1 = new b2Vec2(); this.v2 = new b2Vec2(); this.normal = new b2Vec2(); this.sideNormal1 = new b2Vec2(); this.sideOffset1 = 0; this.sideNormal2 = new b2Vec2(); this.sideOffset2 = 0; } } // static b2EPAxis b2ComputeEdgeSeparation(const b2TempPolygon& polygonB, const b2Vec2& v1, const b2Vec2& normal1) const b2ComputeEdgeSeparation_s_axis = new b2EPAxis(); const b2ComputeEdgeSeparation_s_axes = [new b2Vec2(), new b2Vec2()]; function b2ComputeEdgeSeparation(polygonB, v1, normal1) { // b2EPAxis axis; const axis = b2ComputeEdgeSeparation_s_axis; axis.type = b2EPAxisType.e_edgeA; axis.index = -1; axis.separation = -Number.MAX_VALUE; // -FLT_MAX; axis.normal.SetZero(); // b2Vec2 axes[2] = { normal1, -normal1 }; const axes = b2ComputeEdgeSeparation_s_axes; axes[0].Copy(normal1); axes[1].Copy(normal1).SelfNeg(); // Find axis with least overlap (min-max problem) for (let j = 0; j < 2; ++j) { let sj = Number.MAX_VALUE; // FLT_MAX; // Find deepest polygon vertex along axis j for (let i = 0; i < polygonB.count; ++i) { // float si = b2Dot(axes[j], polygonB.vertices[i] - v1); const si = b2Vec2.DotVV(axes[j], b2Vec2.SubVV(polygonB.vertices[i], v1, b2Vec2.s_t0)); if (si < sj) { sj = si; } } if (sj > axis.separation) { axis.index = j; axis.separation = sj; axis.normal.Copy(axes[j]); } } return axis; } // static b2EPAxis b2ComputePolygonSeparation(const b2TempPolygon& polygonB, const b2Vec2& v1, const b2Vec2& v2) const b2ComputePolygonSeparation_s_axis = new b2EPAxis(); const b2ComputePolygonSeparation_s_n = new b2Vec2(); function b2ComputePolygonSeparation(polygonB, v1, v2) { const axis = b2ComputePolygonSeparation_s_axis; axis.type = b2EPAxisType.e_unknown; axis.index = -1; axis.separation = -Number.MAX_VALUE; // -FLT_MAX; axis.normal.SetZero(); for (let i = 0; i < polygonB.count; ++i) { // b2Vec2 n = -polygonB.normals[i]; const n = b2Vec2.NegV(polygonB.normals[i], b2ComputePolygonSeparation_s_n); // float s1 = b2Dot(n, polygonB.vertices[i] - v1); const s1 = b2Vec2.DotVV(n, b2Vec2.SubVV(polygonB.vertices[i], v1, b2Vec2.s_t0)); // float s2 = b2Dot(n, polygonB.vertices[i] - v2); const s2 = b2Vec2.DotVV(n, b2Vec2.SubVV(polygonB.vertices[i], v2, b2Vec2.s_t0)); // float s = b2Min(s1, s2); const s = b2Min(s1, s2); if (s > axis.separation) { axis.type = b2EPAxisType.e_edgeB; axis.index = i; axis.separation = s; axis.normal.Copy(n); } } return axis; } const b2CollideEdgeAndPolygon_s_xf = new b2Transform(); const b2CollideEdgeAndPolygon_s_centroidB = new b2Vec2(); const b2CollideEdgeAndPolygon_s_edge1 = new b2Vec2(); const b2CollideEdgeAndPolygon_s_normal1 = new b2Vec2(); const b2CollideEdgeAndPolygon_s_edge0 = new b2Vec2(); const b2CollideEdgeAndPolygon_s_normal0 = new b2Vec2(); const b2CollideEdgeAndPolygon_s_edge2 = new b2Vec2(); const b2CollideEdgeAndPolygon_s_normal2 = new b2Vec2(); const b2CollideEdgeAndPolygon_s_tempPolygonB = new b2TempPolygon(); const b2CollideEdgeAndPolygon_s_ref = new b2ReferenceFace(); const b2CollideEdgeAndPolygon_s_clipPoints = [new b2ClipVertex(), new b2ClipVertex()]; const b2CollideEdgeAndPolygon_s_clipPoints1 = [new b2ClipVertex(), new b2ClipVertex()]; const b2CollideEdgeAndPolygon_s_clipPoints2 = [new b2ClipVertex(), new b2ClipVertex()]; function b2CollideEdgeAndPolygon(manifold, edgeA, xfA, polygonB, xfB) { manifold.pointCount = 0; // b2Transform xf = b2MulT(xfA, xfB); const xf = b2Transform.MulTXX(xfA, xfB, b2CollideEdgeAndPolygon_s_xf); // b2Vec2 centroidB = b2Mul(xf, polygonB.m_centroid); const centroidB = b2Transform.MulXV(xf, polygonB.m_centroid, b2CollideEdgeAndPolygon_s_centroidB); // b2Vec2 v1 = edgeA.m_vertex1; const v1 = edgeA.m_vertex1; // b2Vec2 v2 = edgeA.m_vertex2; const v2 = edgeA.m_vertex2; // b2Vec2 edge1 = v2 - v1; const edge1 = b2Vec2.SubVV(v2, v1, b2CollideEdgeAndPolygon_s_edge1); edge1.Normalize(); // Normal points to the right for a CCW winding // b2Vec2 normal1(edge1.y, -edge1.x); const normal1 = b2CollideEdgeAndPolygon_s_normal1.Set(edge1.y, -edge1.x); // float offset1 = b2Dot(normal1, centroidB - v1); const offset1 = b2Vec2.DotVV(normal1, b2Vec2.SubVV(centroidB, v1, b2Vec2.s_t0)); const oneSided = edgeA.m_oneSided; if (oneSided && offset1 < 0.0) { return; } // Get polygonB in frameA // b2TempPolygon tempPolygonB; const tempPolygonB = b2CollideEdgeAndPolygon_s_tempPolygonB; tempPolygonB.count = polygonB.m_count; for (let i = 0; i < polygonB.m_count; ++i) { if (tempPolygonB.vertices.length <= i) { tempPolygonB.vertices.push(new b2Vec2()); } if (tempPolygonB.normals.length <= i) { tempPolygonB.normals.push(new b2Vec2()); } // tempPolygonB.vertices[i] = b2Mul(xf, polygonB.m_vertices[i]); b2Transform.MulXV(xf, polygonB.m_vertices[i], tempPolygonB.vertices[i]); // tempPolygonB.normals[i] = b2Mul(xf.q, polygonB.m_normals[i]); b2Rot.MulRV(xf.q, polygonB.m_normals[i], tempPolygonB.normals[i]); } const radius = polygonB.m_radius + edgeA.m_radius; // b2EPAxis edgeAxis = b2ComputeEdgeSeparation(tempPolygonB, v1, normal1); const edgeAxis = b2ComputeEdgeSeparation(tempPolygonB, v1, normal1); if (edgeAxis.separation > radius) { return; } // b2EPAxis polygonAxis = b2ComputePolygonSeparation(tedge0.y, -edge0.xempPolygonB, v1, v2); const polygonAxis = b2ComputePolygonSeparation(tempPolygonB, v1, v2); if (polygonAxis.separation > radius) { return; } // Use hysteresis for jitter reduction. const k_relativeTol = 0.98; const k_absoluteTol = 0.001; // b2EPAxis primaryAxis; let primaryAxis; if (polygonAxis.separation - radius > k_relativeTol * (edgeAxis.separation - radius) + k_absoluteTol) { primaryAxis = polygonAxis; } else { primaryAxis = edgeAxis; } if (oneSided) { // Smooth collision // See https://box2d.org/posts/2020/06/ghost-collisions/ // b2Vec2 edge0 = v1 - edgeA.m_vertex0; const edge0 = b2Vec2.SubVV(v1, edgeA.m_vertex0, b2CollideEdgeAndPolygon_s_edge0); edge0.Normalize(); // b2Vec2 normal0(edge0.y, -edge0.x); const normal0 = b2CollideEdgeAndPolygon_s_normal0.Set(edge0.y, -edge0.x); const convex1 = b2Vec2.CrossVV(edge0, edge1) >= 0.0; // b2Vec2 edge2 = edgeA.m_vertex3 - v2; const edge2 = b2Vec2.SubVV(edgeA.m_vertex3, v2, b2CollideEdgeAndPolygon_s_edge2); edge2.Normalize(); // b2Vec2 normal2(edge2.y, -edge2.x); const normal2 = b2CollideEdgeAndPolygon_s_normal2.Set(edge2.y, -edge2.x); const convex2 = b2Vec2.CrossVV(edge1, edge2) >= 0.0; const sinTol = 0.1; const side1 = b2Vec2.DotVV(primaryAxis.normal, edge1) <= 0.0; // Check Gauss Map if (side1) { if (convex1) { if (b2Vec2.CrossVV(primaryAxis.normal, normal0) > sinTol) { // Skip region return; } // Admit region } else { // Snap region primaryAxis = edgeAxis; } } else { if (convex2) { if (b2Vec2.CrossVV(normal2, primaryAxis.normal) > sinTol) { // Skip region return; } // Admit region } else { // Snap region primaryAxis = edgeAxis; } } } // b2ClipVertex clipPoints[2]; const clipPoints = b2CollideEdgeAndPolygon_s_clipPoints; // b2ReferenceFace ref; const ref = b2CollideEdgeAndPolygon_s_ref; if (primaryAxis.type === b2EPAxisType.e_edgeA) { manifold.type = exports.b2ManifoldType.e_faceA; // Search for the polygon normal that is most anti-parallel to the edge normal. let bestIndex = 0; let bestValue = b2Vec2.DotVV(primaryAxis.normal, tempPolygonB.normals[0]); for (let i = 1; i < tempPolygonB.count; ++i) { const value = b2Vec2.DotVV(primaryAxis.normal, tempPolygonB.normals[i]); if (value < bestValue) { bestValue = value; bestIndex = i; } } const i1 = bestIndex; const i2 = i1 + 1 < tempPolygonB.count ? i1 + 1 : 0; clipPoints[0].v.Copy(tempPolygonB.vertices[i1]); clipPoints[0].id.cf.indexA = 0; clipPoints[0].id.cf.indexB = i1; clipPoints[0].id.cf.typeA = exports.b2ContactFeatureType.e_face; clipPoints[0].id.cf.typeB = exports.b2ContactFeatureType.e_vertex; clipPoints[1].v.Copy(tempPolygonB.vertices[i2]); clipPoints[1].id.cf.indexA = 0; clipPoints[1].id.cf.indexB = i2; clipPoints[1].id.cf.typeA = exports.b2ContactFeatureType.e_face; clipPoints[1].id.cf.typeB = exports.b2ContactFeatureType.e_vertex; ref.i1 = 0; ref.i2 = 1; ref.v1.Copy(v1); ref.v2.Copy(v2); ref.normal.Copy(primaryAxis.normal); ref.sideNormal1.Copy(edge1).SelfNeg(); // ref.sideNormal1 = -edge1; ref.sideNormal2.Copy(edge1); } else { manifold.type = exports.b2ManifoldType.e_faceB; clipPoints[0].v.Copy(v2); clipPoints[0].id.cf.indexA = 1; clipPoints[0].id.cf.indexB = primaryAxis.index; clipPoints[0].id.cf.typeA = exports.b2ContactFeatureType.e_vertex; clipPoints[0].id.cf.typeB = exports.b2ContactFeatureType.e_face; clipPoints[1].v.Copy(v1); clipPoints[1].id.cf.indexA = 0; clipPoints[1].id.cf.indexB = primaryAxis.index; clipPoints[1].id.cf.typeA = exports.b2ContactFeatureType.e_vertex; clipPoints[1].id.cf.typeB = exports.b2ContactFeatureType.e_face; ref.i1 = primaryAxis.index; ref.i2 = ref.i1 + 1 < tempPolygonB.count ? ref.i1 + 1 : 0; ref.v1.Copy(tempPolygonB.vertices[ref.i1]); ref.v2.Copy(tempPolygonB.vertices[ref.i2]); ref.normal.Copy(tempPolygonB.normals[ref.i1]); // CCW winding ref.sideNormal1.Set(ref.normal.y, -ref.normal.x); ref.sideNormal2.Copy(ref.sideNormal1).SelfNeg(); // ref.sideNormal2 = -ref.sideNormal1; } ref.sideOffset1 = b2Vec2.DotVV(ref.sideNormal1, ref.v1); ref.sideOffset2 = b2Vec2.DotVV(ref.sideNormal2, ref.v2); // Clip incident edge against reference face side planes // b2ClipVertex clipPoints1[2]; const clipPoints1 = b2CollideEdgeAndPolygon_s_clipPoints1; // [new b2ClipVertex(), new b2ClipVertex()]; // b2ClipVertex clipPoints2[2]; const clipPoints2 = b2CollideEdgeAndPolygon_s_clipPoints2; // [new b2ClipVertex(), new b2ClipVertex()]; // int32 np; let np; // Clip to side 1 np = b2ClipSegmentToLine(clipPoints1, clipPoints, ref.sideNormal1, ref.sideOffset1, ref.i1); if (np < b2_maxManifoldPoints) { return; } // Clip to side 2 np = b2ClipSegmentToLine(clipPoints2, clipPoints1, ref.sideNormal2, ref.sideOffset2, ref.i2); if (np < b2_maxManifoldPoints) { return; } // Now clipPoints2 contains the clipped points. if (primaryAxis.type === b2EPAxisType.e_edgeA) { manifold.localNormal.Copy(ref.normal); manifold.localPoint.Copy(ref.v1); } else { manifold.localNormal.Copy(polygonB.m_normals[ref.i1]); manifold.localPoint.Copy(polygonB.m_vertices[ref.i1]); } let pointCount = 0; for (let i = 0; i < b2_maxManifoldPoints; ++i) { const separation = b2Vec2.DotVV(ref.normal, b2Vec2.SubVV(clipPoints2[i].v, ref.v1, b2Vec2.s_t0)); if (separation <= radius) { const cp = manifold.points[pointCount]; if (primaryAxis.type === b2EPAxisType.e_edgeA) { b2Transform.MulTXV(xf, clipPoints2[i].v, cp.localPoint); // cp.localPoint = b2MulT(xf, clipPoints2[i].v); cp.id.Copy(clipPoints2[i].id); } else { cp.localPoint.Copy(clipPoints2[i].v); cp.id.cf.typeA = clipPoints2[i].id.cf.typeB; cp.id.cf.typeB = clipPoints2[i].id.cf.typeA; cp.id.cf.indexA = clipPoints2[i].id.cf.indexB; cp.id.cf.indexB = clipPoints2[i].id.cf.indexA; } ++pointCount; } } manifold.pointCount = pointCount; } // MIT License // Find the max separation between poly1 and poly2 using edge normals from poly1. const b2FindMaxSeparation_s_xf = new b2Transform(); const b2FindMaxSeparation_s_n = new b2Vec2(); const b2FindMaxSeparation_s_v1 = new b2Vec2(); function b2FindMaxSeparation(edgeIndex, poly1, xf1, poly2, xf2) { const count1 = poly1.m_count; const count2 = poly2.m_count; const n1s = poly1.m_normals; const v1s = poly1.m_vertices; const v2s = poly2.m_vertices; const xf = b2Transform.MulTXX(xf2, xf1, b2FindMaxSeparation_s_xf); let bestIndex = 0; let maxSeparation = -b2_maxFloat; for (let i = 0; i < count1; ++i) { // Get poly1 normal in frame2. const n = b2Rot.MulRV(xf.q, n1s[i], b2FindMaxSeparation_s_n); const v1 = b2Transform.MulXV(xf, v1s[i], b2FindMaxSeparation_s_v1); // Find deepest point for normal i. let si = b2_maxFloat; for (let j = 0; j < count2; ++j) { const sij = b2Vec2.DotVV(n, b2Vec2.SubVV(v2s[j], v1, b2Vec2.s_t0)); if (sij < si) { si = sij; } } if (si > maxSeparation) { maxSeparation = si; bestIndex = i; } } edgeIndex[0] = bestIndex; return maxSeparation; } const b2FindIncidentEdge_s_normal1 = new b2Vec2(); function b2FindIncidentEdge(c, poly1, xf1, edge1, poly2, xf2) { const normals1 = poly1.m_normals; const count2 = poly2.m_count; const vertices2 = poly2.m_vertices; const normals2 = poly2.m_normals; // DEBUG: b2Assert(0 <= edge1 && edge1 < poly1.m_count); // Get the normal of the reference edge in poly2's frame. const normal1 = b2Rot.MulTRV(xf2.q, b2Rot.MulRV(xf1.q, normals1[edge1], b2Vec2.s_t0), b2FindIncidentEdge_s_normal1); // Find the incident edge on poly2. let index = 0; let minDot = b2_maxFloat; for (let i = 0; i < count2; ++i) { const dot = b2Vec2.DotVV(normal1, normals2[i]); if (dot < minDot) { minDot = dot; index = i; } } // Build the clip vertices for the incident edge. const i1 = index; const i2 = i1 + 1 < count2 ? i1 + 1 : 0; const c0 = c[0]; b2Transform.MulXV(xf2, vertices2[i1], c0.v); const cf0 = c0.id.cf; cf0.indexA = edge1; cf0.indexB = i1; cf0.typeA = exports.b2ContactFeatureType.e_face; cf0.typeB = exports.b2ContactFeatureType.e_vertex; const c1 = c[1]; b2Transform.MulXV(xf2, vertices2[i2], c1.v); const cf1 = c1.id.cf; cf1.indexA = edge1; cf1.indexB = i2; cf1.typeA = exports.b2ContactFeatureType.e_face; cf1.typeB = exports.b2ContactFeatureType.e_vertex; } // Find edge normal of max separation on A - return if separating axis is found // Find edge normal of max separation on B - return if separation axis is found // Choose reference edge as min(minA, minB) // Find incident edge // Clip // The normal points from 1 to 2 const b2CollidePolygons_s_incidentEdge = [new b2ClipVertex(), new b2ClipVertex()]; const b2CollidePolygons_s_clipPoints1 = [new b2ClipVertex(), new b2ClipVertex()]; const b2CollidePolygons_s_clipPoints2 = [new b2ClipVertex(), new b2ClipVertex()]; const b2CollidePolygons_s_edgeA = [0]; const b2CollidePolygons_s_edgeB = [0]; const b2CollidePolygons_s_localTangent = new b2Vec2(); const b2CollidePolygons_s_localNormal = new b2Vec2(); const b2CollidePolygons_s_planePoint = new b2Vec2(); const b2CollidePolygons_s_normal = new b2Vec2(); const b2CollidePolygons_s_tangent = new b2Vec2(); const b2CollidePolygons_s_ntangent = new b2Vec2(); const b2CollidePolygons_s_v11 = new b2Vec2(); const b2CollidePolygons_s_v12 = new b2Vec2(); function b2CollidePolygons(manifold, polyA, xfA, polyB, xfB) { manifold.pointCount = 0; const totalRadius = polyA.m_radius + polyB.m_radius; const edgeA = b2CollidePolygons_s_edgeA; edgeA[0] = 0; const separationA = b2FindMaxSeparation(edgeA, polyA, xfA, polyB, xfB); if (separationA > totalRadius) { return; } const edgeB = b2CollidePolygons_s_edgeB; edgeB[0] = 0; const separationB = b2FindMaxSeparation(edgeB, polyB, xfB, polyA, xfA); if (separationB > totalRadius) { return; } let poly1; // reference polygon let poly2; // incident polygon let xf1, xf2; let edge1 = 0; // reference edge let flip = 0; const k_tol = 0.1 * b2_linearSlop; if (separationB > separationA + k_tol) { poly1 = polyB; poly2 = polyA; xf1 = xfB; xf2 = xfA; edge1 = edgeB[0]; manifold.type = exports.b2ManifoldType.e_faceB; flip = 1; } else { poly1 = polyA; poly2 = polyB; xf1 = xfA; xf2 = xfB; edge1 = edgeA[0]; manifold.type = exports.b2ManifoldType.e_faceA; flip = 0; } const incidentEdge = b2CollidePolygons_s_incidentEdge; b2FindIncidentEdge(incidentEdge, poly1, xf1, edge1, poly2, xf2); const count1 = poly1.m_count; const vertices1 = poly1.m_vertices; const iv1 = edge1; const iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0; const local_v11 = vertices1[iv1]; const local_v12 = vertices1[iv2]; const localTangent = b2Vec2.SubVV(local_v12, local_v11, b2CollidePolygons_s_localTangent); localTangent.Normalize(); const localNormal = b2Vec2.CrossVOne(localTangent, b2CollidePolygons_s_localNormal); const planePoint = b2Vec2.MidVV(local_v11, local_v12, b2CollidePolygons_s_planePoint); const tangent = b2Rot.MulRV(xf1.q, localTangent, b2CollidePolygons_s_tangent); const normal = b2Vec2.CrossVOne(tangent, b2CollidePolygons_s_normal); const v11 = b2Transform.MulXV(xf1, local_v11, b2CollidePolygons_s_v11); const v12 = b2Transform.MulXV(xf1, local_v12, b2CollidePolygons_s_v12); // Face offset. const frontOffset = b2Vec2.DotVV(normal, v11); // Side offsets, extended by polytope skin thickness. const sideOffset1 = -b2Vec2.DotVV(tangent, v11) + totalRadius; const sideOffset2 = b2Vec2.DotVV(tangent, v12) + totalRadius; // Clip incident edge against extruded edge1 side edges. const clipPoints1 = b2CollidePolygons_s_clipPoints1; const clipPoints2 = b2CollidePolygons_s_clipPoints2; let np; // Clip to box side 1 const ntangent = b2Vec2.NegV(tangent, b2CollidePolygons_s_ntangent); np = b2ClipSegmentToLine(clipPoints1, incidentEdge, ntangent, sideOffset1, iv1); if (np < 2) { return; } // Clip to negative box side 1 np = b2ClipSegmentToLine(clipPoints2, clipPoints1, tangent, sideOffset2, iv2); if (np < 2) { return; } // Now clipPoints2 contains the clipped points. manifold.localNormal.Copy(localNormal); manifold.localPoint.Copy(planePoint); let pointCount = 0; for (let i = 0; i < b2_maxManifoldPoints; ++i) { const cv = clipPoints2[i]; const separation = b2Vec2.DotVV(normal, cv.v) - frontOffset; if (separation <= totalRadius) { const cp = manifold.points[pointCount]; b2Transform.MulTXV(xf2, cv.v, cp.localPoint); cp.id.Copy(cv.id); if (flip) { // Swap features const cf = cp.id.cf; cp.id.cf.indexA = cf.indexB; cp.id.cf.indexB = cf.indexA; cp.id.cf.typeA = cf.typeB; cp.id.cf.typeB = cf.typeA; } ++pointCount; } } manifold.pointCount = pointCount; } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// A solid convex polygon. It is assumed that the interior of the polygon is to /// the left of each edge. /// In most cases you should not need many vertices for a convex polygon. class b2PolygonShape extends b2Shape { constructor() { super(exports.b2ShapeType.e_polygonShape, b2_polygonRadius); this.m_centroid = new b2Vec2(0, 0); this.m_vertices = []; this.m_normals = []; this.m_count = 0; } /// Implement b2Shape. Clone() { return new b2PolygonShape().Copy(this); } Copy(other) { super.Copy(other); // DEBUG: b2Assert(other instanceof b2PolygonShape); this.m_centroid.Copy(other.m_centroid); this.m_count = other.m_count; this.m_vertices = b2Vec2.MakeArray(this.m_count); this.m_normals = b2Vec2.MakeArray(this.m_count); for (let i = 0; i < this.m_count; ++i) { this.m_vertices[i].Copy(other.m_vertices[i]); this.m_normals[i].Copy(other.m_normals[i]); } return this; } /// @see b2Shape::GetChildCount GetChildCount() { return 1; } Set(...args) { if (typeof args[0][0] === "number") { const vertices = args[0]; if (vertices.length % 2 !== 0) { throw new Error(); } return this._Set((index) => ({ x: vertices[index * 2], y: vertices[index * 2 + 1] }), vertices.length / 2); } else { const vertices = args[0]; const count = args[1] || vertices.length; return this._Set((index) => vertices[index], count); } } _Set(vertices, count) { // DEBUG: b2Assert(3 <= count); if (count < 3) { return this.SetAsBox(1, 1); } let n = count; // Perform welding and copy vertices into local buffer. const ps = []; for (let i = 0; i < n; ++i) { const /*b2Vec2*/ v = vertices(i); let /*bool*/ unique = true; for (let /*int32*/ j = 0; j < ps.length; ++j) { if (b2Vec2.DistanceSquaredVV(v, ps[j]) < ((0.5 * b2_linearSlop) * (0.5 * b2_linearSlop))) { unique = false; break; } } if (unique) { ps.push(v); } } n = ps.length; if (n < 3) { // Polygon is degenerate. // DEBUG: b2Assert(false); return this.SetAsBox(1.0, 1.0); } // Create the convex hull using the Gift wrapping algorithm // http://en.wikipedia.org/wiki/Gift_wrapping_algorithm // Find the right most point on the hull let i0 = 0; let x0 = ps[0].x; for (let i = 1; i < n; ++i) { const x = ps[i].x; if (x > x0 || (x === x0 && ps[i].y < ps[i0].y)) { i0 = i; x0 = x; } } const hull = []; let m = 0; let ih = i0; for (;;) { hull[m] = ih; let ie = 0; for (let j = 1; j < n; ++j) { if (ie === ih) { ie = j; continue; } const r = b2Vec2.SubVV(ps[ie], ps[hull[m]], b2PolygonShape.Set_s_r); const v = b2Vec2.SubVV(ps[j], ps[hull[m]], b2PolygonShape.Set_s_v); const c = b2Vec2.CrossVV(r, v); if (c < 0) { ie = j; } // Collinearity check if (c === 0 && v.LengthSquared() > r.LengthSquared()) { ie = j; } } ++m; ih = ie; if (ie === i0) { break; } } this.m_count = m; this.m_vertices = b2Vec2.MakeArray(this.m_count); this.m_normals = b2Vec2.MakeArray(this.m_count); // Copy vertices. for (let i = 0; i < m; ++i) { this.m_vertices[i].Copy(ps[hull[i]]); } // Compute normals. Ensure the edges have non-zero length. for (let i = 0; i < m; ++i) { const vertexi1 = this.m_vertices[i]; const vertexi2 = this.m_vertices[(i + 1) % m]; const edge = b2Vec2.SubVV(vertexi2, vertexi1, b2Vec2.s_t0); // edge uses s_t0 // DEBUG: b2Assert(edge.LengthSquared() > b2_epsilon_sq); b2Vec2.CrossVOne(edge, this.m_normals[i]).SelfNormalize(); } // Compute the polygon centroid. b2PolygonShape.ComputeCentroid(this.m_vertices, m, this.m_centroid); return this; } /// Build vertices to represent an axis-aligned box or an oriented box. /// @param hx the half-width. /// @param hy the half-height. /// @param center the center of the box in local coordinates. /// @param angle the rotation of the box in local coordinates. SetAsBox(hx, hy, center, angle = 0) { this.m_count = 4; this.m_vertices = b2Vec2.MakeArray(this.m_count); this.m_normals = b2Vec2.MakeArray(this.m_count); this.m_vertices[0].Set((-hx), (-hy)); this.m_vertices[1].Set(hx, (-hy)); this.m_vertices[2].Set(hx, hy); this.m_vertices[3].Set((-hx), hy); this.m_normals[0].Set(0, (-1)); this.m_normals[1].Set(1, 0); this.m_normals[2].Set(0, 1); this.m_normals[3].Set((-1), 0); this.m_centroid.SetZero(); if (center) { this.m_centroid.Copy(center); const xf = new b2Transform(); xf.SetPosition(center); xf.SetRotationAngle(angle); // Transform vertices and normals. for (let i = 0; i < this.m_count; ++i) { b2Transform.MulXV(xf, this.m_vertices[i], this.m_vertices[i]); b2Rot.MulRV(xf.q, this.m_normals[i], this.m_normals[i]); } } return this; } TestPoint(xf, p) { const pLocal = b2Transform.MulTXV(xf, p, b2PolygonShape.TestPoint_s_pLocal); for (let i = 0; i < this.m_count; ++i) { const dot = b2Vec2.DotVV(this.m_normals[i], b2Vec2.SubVV(pLocal, this.m_vertices[i], b2Vec2.s_t0)); if (dot > 0) { return false; } } return true; } RayCast(output, input, xf, childIndex) { // Put the ray into the polygon's frame of reference. const p1 = b2Transform.MulTXV(xf, input.p1, b2PolygonShape.RayCast_s_p1); const p2 = b2Transform.MulTXV(xf, input.p2, b2PolygonShape.RayCast_s_p2); const d = b2Vec2.SubVV(p2, p1, b2PolygonShape.RayCast_s_d); let lower = 0, upper = input.maxFraction; let index = -1; for (let i = 0; i < this.m_count; ++i) { // p = p1 + a * d // dot(normal, p - v) = 0 // dot(normal, p1 - v) + a * dot(normal, d) = 0 const numerator = b2Vec2.DotVV(this.m_normals[i], b2Vec2.SubVV(this.m_vertices[i], p1, b2Vec2.s_t0)); const denominator = b2Vec2.DotVV(this.m_normals[i], d); if (denominator === 0) { if (numerator < 0) { return false; } } else { // Note: we want this predicate without division: // lower < numerator / denominator, where denominator < 0 // Since denominator < 0, we have to flip the inequality: // lower < numerator / denominator <==> denominator * lower > numerator. if (denominator < 0 && numerator < lower * denominator) { // Increase lower. // The segment enters this half-space. lower = numerator / denominator; index = i; } else if (denominator > 0 && numerator < upper * denominator) { // Decrease upper. // The segment exits this half-space. upper = numerator / denominator; } } // The use of epsilon here causes the assert on lower to trip // in some cases. Apparently the use of epsilon was to make edge // shapes work, but now those are handled separately. // if (upper < lower - b2_epsilon) if (upper < lower) { return false; } } // DEBUG: b2Assert(0 <= lower && lower <= input.maxFraction); if (index >= 0) { output.fraction = lower; b2Rot.MulRV(xf.q, this.m_normals[index], output.normal); return true; } return false; } ComputeAABB(aabb, xf, childIndex) { const lower = b2Transform.MulXV(xf, this.m_vertices[0], aabb.lowerBound); const upper = aabb.upperBound.Copy(lower); for (let i = 0; i < this.m_count; ++i) { const v = b2Transform.MulXV(xf, this.m_vertices[i], b2PolygonShape.ComputeAABB_s_v); b2Vec2.MinV(v, lower, lower); b2Vec2.MaxV(v, upper, upper); } const r = this.m_radius; lower.SelfSubXY(r, r); upper.SelfAddXY(r, r); } ComputeMass(massData, density) { // Polygon mass, centroid, and inertia. // Let rho be the polygon density in mass per unit area. // Then: // mass = rho * int(dA) // centroid.x = (1/mass) * rho * int(x * dA) // centroid.y = (1/mass) * rho * int(y * dA) // I = rho * int((x*x + y*y) * dA) // // We can compute these integrals by summing all the integrals // for each triangle of the polygon. To evaluate the integral // for a single triangle, we make a change of variables to // the (u,v) coordinates of the triangle: // x = x0 + e1x * u + e2x * v // y = y0 + e1y * u + e2y * v // where 0 <= u && 0 <= v && u + v <= 1. // // We integrate u from [0,1-v] and then v from [0,1]. // We also need to use the Jacobian of the transformation: // D = cross(e1, e2) // // Simplification: triangle centroid = (1/3) * (p1 + p2 + p3) // // The rest of the derivation is handled by computer algebra. // DEBUG: b2Assert(this.m_count >= 3); const center = b2PolygonShape.ComputeMass_s_center.SetZero(); let area = 0; let I = 0; // Get a reference point for forming triangles. // Use the first vertex to reduce round-off errors. const s = b2PolygonShape.ComputeMass_s_s.Copy(this.m_vertices[0]); const k_inv3 = 1 / 3; for (let i = 0; i < this.m_count; ++i) { // Triangle vertices. const e1 = b2Vec2.SubVV(this.m_vertices[i], s, b2PolygonShape.ComputeMass_s_e1); const e2 = b2Vec2.SubVV(this.m_vertices[(i + 1) % this.m_count], s, b2PolygonShape.ComputeMass_s_e2); const D = b2Vec2.CrossVV(e1, e2); const triangleArea = 0.5 * D; area += triangleArea; // Area weighted centroid center.SelfAdd(b2Vec2.MulSV(triangleArea * k_inv3, b2Vec2.AddVV(e1, e2, b2Vec2.s_t0), b2Vec2.s_t1)); const ex1 = e1.x; const ey1 = e1.y; const ex2 = e2.x; const ey2 = e2.y; const intx2 = ex1 * ex1 + ex2 * ex1 + ex2 * ex2; const inty2 = ey1 * ey1 + ey2 * ey1 + ey2 * ey2; I += (0.25 * k_inv3 * D) * (intx2 + inty2); } // Total mass massData.mass = density * area; // Center of mass // DEBUG: b2Assert(area > b2_epsilon); center.SelfMul(1 / area); b2Vec2.AddVV(center, s, massData.center); // Inertia tensor relative to the local origin (point s). massData.I = density * I; // Shift to center of mass then to original body origin. massData.I += massData.mass * (b2Vec2.DotVV(massData.center, massData.center) - b2Vec2.DotVV(center, center)); } Validate() { for (let i = 0; i < this.m_count; ++i) { const i1 = i; const i2 = (i + 1) % this.m_count; const p = this.m_vertices[i1]; const e = b2Vec2.SubVV(this.m_vertices[i2], p, b2PolygonShape.Validate_s_e); for (let j = 0; j < this.m_count; ++j) { if (j === i1 || j === i2) { continue; } const v = b2Vec2.SubVV(this.m_vertices[j], p, b2PolygonShape.Validate_s_v); const c = b2Vec2.CrossVV(e, v); if (c < 0) { return false; } } } return true; } SetupDistanceProxy(proxy, index) { proxy.m_vertices = this.m_vertices; proxy.m_count = this.m_count; proxy.m_radius = this.m_radius; } ComputeSubmergedArea(normal, offset, xf, c) { // Transform plane into shape co-ordinates const normalL = b2Rot.MulTRV(xf.q, normal, b2PolygonShape.ComputeSubmergedArea_s_normalL); const offsetL = offset - b2Vec2.DotVV(normal, xf.p); const depths = []; let diveCount = 0; let intoIndex = -1; let outoIndex = -1; let lastSubmerged = false; for (let i = 0; i < this.m_count; ++i) { depths[i] = b2Vec2.DotVV(normalL, this.m_vertices[i]) - offsetL; const isSubmerged = depths[i] < (-b2_epsilon); if (i > 0) { if (isSubmerged) { if (!lastSubmerged) { intoIndex = i - 1; diveCount++; } } else { if (lastSubmerged) { outoIndex = i - 1; diveCount++; } } } lastSubmerged = isSubmerged; } switch (diveCount) { case 0: if (lastSubmerged) { // Completely submerged const md = b2PolygonShape.ComputeSubmergedArea_s_md; this.ComputeMass(md, 1); b2Transform.MulXV(xf, md.center, c); return md.mass; } else { // Completely dry return 0; } case 1: if (intoIndex === (-1)) { intoIndex = this.m_count - 1; } else { outoIndex = this.m_count - 1; } break; } const intoIndex2 = ((intoIndex + 1) % this.m_count); const outoIndex2 = ((outoIndex + 1) % this.m_count); const intoLamdda = (0 - depths[intoIndex]) / (depths[intoIndex2] - depths[intoIndex]); const outoLamdda = (0 - depths[outoIndex]) / (depths[outoIndex2] - depths[outoIndex]); const intoVec = b2PolygonShape.ComputeSubmergedArea_s_intoVec.Set(this.m_vertices[intoIndex].x * (1 - intoLamdda) + this.m_vertices[intoIndex2].x * intoLamdda, this.m_vertices[intoIndex].y * (1 - intoLamdda) + this.m_vertices[intoIndex2].y * intoLamdda); const outoVec = b2PolygonShape.ComputeSubmergedArea_s_outoVec.Set(this.m_vertices[outoIndex].x * (1 - outoLamdda) + this.m_vertices[outoIndex2].x * outoLamdda, this.m_vertices[outoIndex].y * (1 - outoLamdda) + this.m_vertices[outoIndex2].y * outoLamdda); // Initialize accumulator let area = 0; const center = b2PolygonShape.ComputeSubmergedArea_s_center.SetZero(); let p2 = this.m_vertices[intoIndex2]; let p3; // An awkward loop from intoIndex2+1 to outIndex2 let i = intoIndex2; while (i !== outoIndex2) { i = (i + 1) % this.m_count; if (i === outoIndex2) { p3 = outoVec; } else { p3 = this.m_vertices[i]; } const triangleArea = 0.5 * ((p2.x - intoVec.x) * (p3.y - intoVec.y) - (p2.y - intoVec.y) * (p3.x - intoVec.x)); area += triangleArea; // Area weighted centroid center.x += triangleArea * (intoVec.x + p2.x + p3.x) / 3; center.y += triangleArea * (intoVec.y + p2.y + p3.y) / 3; p2 = p3; } // Normalize and transform centroid center.SelfMul(1 / area); b2Transform.MulXV(xf, center, c); return area; } Dump(log) { log(" const shape: b2PolygonShape = new b2PolygonShape();\n"); log(" const vs: b2Vec2[] = [];\n"); for (let i = 0; i < this.m_count; ++i) { log(" vs[%d] = new b2Vec2(%.15f, %.15f);\n", i, this.m_vertices[i].x, this.m_vertices[i].y); } log(" shape.Set(vs, %d);\n", this.m_count); } static ComputeCentroid(vs, count, out) { // DEBUG: b2Assert(count >= 3); const c = out; c.SetZero(); let area = 0; // Get a reference point for forming triangles. // Use the first vertex to reduce round-off errors. const s = b2PolygonShape.ComputeCentroid_s_s.Copy(vs[0]); const inv3 = 1 / 3; for (let i = 0; i < count; ++i) { // Triangle vertices. const p1 = b2Vec2.SubVV(vs[0], s, b2PolygonShape.ComputeCentroid_s_p1); const p2 = b2Vec2.SubVV(vs[i], s, b2PolygonShape.ComputeCentroid_s_p2); const p3 = b2Vec2.SubVV(vs[(i + 1) % count], s, b2PolygonShape.ComputeCentroid_s_p3); const e1 = b2Vec2.SubVV(p2, p1, b2PolygonShape.ComputeCentroid_s_e1); const e2 = b2Vec2.SubVV(p3, p1, b2PolygonShape.ComputeCentroid_s_e2); const D = b2Vec2.CrossVV(e1, e2); const triangleArea = 0.5 * D; area += triangleArea; // Area weighted centroid c.x += triangleArea * inv3 * (p1.x + p2.x + p3.x); c.y += triangleArea * inv3 * (p1.y + p2.y + p3.y); } // Centroid // DEBUG: b2Assert(area > b2_epsilon); // c = (1.0f / area) * c + s; c.x = (1 / area) * c.x + s.x; c.y = (1 / area) * c.y + s.y; return c; } } /// Create a convex hull from the given array of points. /// @warning the points may be re-ordered, even if they form a convex polygon /// @warning collinear points are handled but not removed. Collinear points /// may lead to poor stacking behavior. b2PolygonShape.Set_s_r = new b2Vec2(); b2PolygonShape.Set_s_v = new b2Vec2(); /// @see b2Shape::TestPoint b2PolygonShape.TestPoint_s_pLocal = new b2Vec2(); /// Implement b2Shape. /// @note because the polygon is solid, rays that start inside do not hit because the normal is /// not defined. b2PolygonShape.RayCast_s_p1 = new b2Vec2(); b2PolygonShape.RayCast_s_p2 = new b2Vec2(); b2PolygonShape.RayCast_s_d = new b2Vec2(); /// @see b2Shape::ComputeAABB b2PolygonShape.ComputeAABB_s_v = new b2Vec2(); /// @see b2Shape::ComputeMass b2PolygonShape.ComputeMass_s_center = new b2Vec2(); b2PolygonShape.ComputeMass_s_s = new b2Vec2(); b2PolygonShape.ComputeMass_s_e1 = new b2Vec2(); b2PolygonShape.ComputeMass_s_e2 = new b2Vec2(); b2PolygonShape.Validate_s_e = new b2Vec2(); b2PolygonShape.Validate_s_v = new b2Vec2(); b2PolygonShape.ComputeSubmergedArea_s_normalL = new b2Vec2(); b2PolygonShape.ComputeSubmergedArea_s_md = new b2MassData(); b2PolygonShape.ComputeSubmergedArea_s_intoVec = new b2Vec2(); b2PolygonShape.ComputeSubmergedArea_s_outoVec = new b2Vec2(); b2PolygonShape.ComputeSubmergedArea_s_center = new b2Vec2(); b2PolygonShape.ComputeCentroid_s_s = new b2Vec2(); b2PolygonShape.ComputeCentroid_s_p1 = new b2Vec2(); b2PolygonShape.ComputeCentroid_s_p2 = new b2Vec2(); b2PolygonShape.ComputeCentroid_s_p3 = new b2Vec2(); b2PolygonShape.ComputeCentroid_s_e1 = new b2Vec2(); b2PolygonShape.ComputeCentroid_s_e2 = new b2Vec2(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ exports.b2_toiTime = 0; exports.b2_toiMaxTime = 0; exports.b2_toiCalls = 0; exports.b2_toiIters = 0; exports.b2_toiMaxIters = 0; exports.b2_toiRootIters = 0; exports.b2_toiMaxRootIters = 0; function b2_toi_reset() { exports.b2_toiTime = 0; exports.b2_toiMaxTime = 0; exports.b2_toiCalls = 0; exports.b2_toiIters = 0; exports.b2_toiMaxIters = 0; exports.b2_toiRootIters = 0; exports.b2_toiMaxRootIters = 0; } const b2TimeOfImpact_s_xfA = new b2Transform(); const b2TimeOfImpact_s_xfB = new b2Transform(); const b2TimeOfImpact_s_pointA = new b2Vec2(); const b2TimeOfImpact_s_pointB = new b2Vec2(); const b2TimeOfImpact_s_normal = new b2Vec2(); const b2TimeOfImpact_s_axisA = new b2Vec2(); const b2TimeOfImpact_s_axisB = new b2Vec2(); /// Input parameters for b2TimeOfImpact class b2TOIInput { constructor() { this.proxyA = new b2DistanceProxy(); this.proxyB = new b2DistanceProxy(); this.sweepA = new b2Sweep(); this.sweepB = new b2Sweep(); this.tMax = 0; // defines sweep interval [0, tMax] } } /// Output parameters for b2TimeOfImpact. exports.b2TOIOutputState = void 0; (function (b2TOIOutputState) { b2TOIOutputState[b2TOIOutputState["e_unknown"] = 0] = "e_unknown"; b2TOIOutputState[b2TOIOutputState["e_failed"] = 1] = "e_failed"; b2TOIOutputState[b2TOIOutputState["e_overlapped"] = 2] = "e_overlapped"; b2TOIOutputState[b2TOIOutputState["e_touching"] = 3] = "e_touching"; b2TOIOutputState[b2TOIOutputState["e_separated"] = 4] = "e_separated"; })(exports.b2TOIOutputState || (exports.b2TOIOutputState = {})); class b2TOIOutput { constructor() { this.state = exports.b2TOIOutputState.e_unknown; this.t = 0; } } exports.b2SeparationFunctionType = void 0; (function (b2SeparationFunctionType) { b2SeparationFunctionType[b2SeparationFunctionType["e_unknown"] = -1] = "e_unknown"; b2SeparationFunctionType[b2SeparationFunctionType["e_points"] = 0] = "e_points"; b2SeparationFunctionType[b2SeparationFunctionType["e_faceA"] = 1] = "e_faceA"; b2SeparationFunctionType[b2SeparationFunctionType["e_faceB"] = 2] = "e_faceB"; })(exports.b2SeparationFunctionType || (exports.b2SeparationFunctionType = {})); class b2SeparationFunction { constructor() { this.m_sweepA = new b2Sweep(); this.m_sweepB = new b2Sweep(); this.m_type = exports.b2SeparationFunctionType.e_unknown; this.m_localPoint = new b2Vec2(); this.m_axis = new b2Vec2(); } Initialize(cache, proxyA, sweepA, proxyB, sweepB, t1) { this.m_proxyA = proxyA; this.m_proxyB = proxyB; const count = cache.count; // DEBUG: b2Assert(0 < count && count < 3); this.m_sweepA.Copy(sweepA); this.m_sweepB.Copy(sweepB); const xfA = b2TimeOfImpact_s_xfA; const xfB = b2TimeOfImpact_s_xfB; this.m_sweepA.GetTransform(xfA, t1); this.m_sweepB.GetTransform(xfB, t1); if (count === 1) { this.m_type = exports.b2SeparationFunctionType.e_points; const localPointA = this.m_proxyA.GetVertex(cache.indexA[0]); const localPointB = this.m_proxyB.GetVertex(cache.indexB[0]); const pointA = b2Transform.MulXV(xfA, localPointA, b2TimeOfImpact_s_pointA); const pointB = b2Transform.MulXV(xfB, localPointB, b2TimeOfImpact_s_pointB); b2Vec2.SubVV(pointB, pointA, this.m_axis); const s = this.m_axis.Normalize(); return s; } else if (cache.indexA[0] === cache.indexA[1]) { // Two points on B and one on A. this.m_type = exports.b2SeparationFunctionType.e_faceB; const localPointB1 = this.m_proxyB.GetVertex(cache.indexB[0]); const localPointB2 = this.m_proxyB.GetVertex(cache.indexB[1]); b2Vec2.CrossVOne(b2Vec2.SubVV(localPointB2, localPointB1, b2Vec2.s_t0), this.m_axis).SelfNormalize(); const normal = b2Rot.MulRV(xfB.q, this.m_axis, b2TimeOfImpact_s_normal); b2Vec2.MidVV(localPointB1, localPointB2, this.m_localPoint); const pointB = b2Transform.MulXV(xfB, this.m_localPoint, b2TimeOfImpact_s_pointB); const localPointA = this.m_proxyA.GetVertex(cache.indexA[0]); const pointA = b2Transform.MulXV(xfA, localPointA, b2TimeOfImpact_s_pointA); let s = b2Vec2.DotVV(b2Vec2.SubVV(pointA, pointB, b2Vec2.s_t0), normal); if (s < 0) { this.m_axis.SelfNeg(); s = -s; } return s; } else { // Two points on A and one or two points on B. this.m_type = exports.b2SeparationFunctionType.e_faceA; const localPointA1 = this.m_proxyA.GetVertex(cache.indexA[0]); const localPointA2 = this.m_proxyA.GetVertex(cache.indexA[1]); b2Vec2.CrossVOne(b2Vec2.SubVV(localPointA2, localPointA1, b2Vec2.s_t0), this.m_axis).SelfNormalize(); const normal = b2Rot.MulRV(xfA.q, this.m_axis, b2TimeOfImpact_s_normal); b2Vec2.MidVV(localPointA1, localPointA2, this.m_localPoint); const pointA = b2Transform.MulXV(xfA, this.m_localPoint, b2TimeOfImpact_s_pointA); const localPointB = this.m_proxyB.GetVertex(cache.indexB[0]); const pointB = b2Transform.MulXV(xfB, localPointB, b2TimeOfImpact_s_pointB); let s = b2Vec2.DotVV(b2Vec2.SubVV(pointB, pointA, b2Vec2.s_t0), normal); if (s < 0) { this.m_axis.SelfNeg(); s = -s; } return s; } } FindMinSeparation(indexA, indexB, t) { const xfA = b2TimeOfImpact_s_xfA; const xfB = b2TimeOfImpact_s_xfB; this.m_sweepA.GetTransform(xfA, t); this.m_sweepB.GetTransform(xfB, t); switch (this.m_type) { case exports.b2SeparationFunctionType.e_points: { const axisA = b2Rot.MulTRV(xfA.q, this.m_axis, b2TimeOfImpact_s_axisA); const axisB = b2Rot.MulTRV(xfB.q, b2Vec2.NegV(this.m_axis, b2Vec2.s_t0), b2TimeOfImpact_s_axisB); indexA[0] = this.m_proxyA.GetSupport(axisA); indexB[0] = this.m_proxyB.GetSupport(axisB); const localPointA = this.m_proxyA.GetVertex(indexA[0]); const localPointB = this.m_proxyB.GetVertex(indexB[0]); const pointA = b2Transform.MulXV(xfA, localPointA, b2TimeOfImpact_s_pointA); const pointB = b2Transform.MulXV(xfB, localPointB, b2TimeOfImpact_s_pointB); const separation = b2Vec2.DotVV(b2Vec2.SubVV(pointB, pointA, b2Vec2.s_t0), this.m_axis); return separation; } case exports.b2SeparationFunctionType.e_faceA: { const normal = b2Rot.MulRV(xfA.q, this.m_axis, b2TimeOfImpact_s_normal); const pointA = b2Transform.MulXV(xfA, this.m_localPoint, b2TimeOfImpact_s_pointA); const axisB = b2Rot.MulTRV(xfB.q, b2Vec2.NegV(normal, b2Vec2.s_t0), b2TimeOfImpact_s_axisB); indexA[0] = -1; indexB[0] = this.m_proxyB.GetSupport(axisB); const localPointB = this.m_proxyB.GetVertex(indexB[0]); const pointB = b2Transform.MulXV(xfB, localPointB, b2TimeOfImpact_s_pointB); const separation = b2Vec2.DotVV(b2Vec2.SubVV(pointB, pointA, b2Vec2.s_t0), normal); return separation; } case exports.b2SeparationFunctionType.e_faceB: { const normal = b2Rot.MulRV(xfB.q, this.m_axis, b2TimeOfImpact_s_normal); const pointB = b2Transform.MulXV(xfB, this.m_localPoint, b2TimeOfImpact_s_pointB); const axisA = b2Rot.MulTRV(xfA.q, b2Vec2.NegV(normal, b2Vec2.s_t0), b2TimeOfImpact_s_axisA); indexB[0] = -1; indexA[0] = this.m_proxyA.GetSupport(axisA); const localPointA = this.m_proxyA.GetVertex(indexA[0]); const pointA = b2Transform.MulXV(xfA, localPointA, b2TimeOfImpact_s_pointA); const separation = b2Vec2.DotVV(b2Vec2.SubVV(pointA, pointB, b2Vec2.s_t0), normal); return separation; } default: // DEBUG: b2Assert(false); indexA[0] = -1; indexB[0] = -1; return 0; } } Evaluate(indexA, indexB, t) { const xfA = b2TimeOfImpact_s_xfA; const xfB = b2TimeOfImpact_s_xfB; this.m_sweepA.GetTransform(xfA, t); this.m_sweepB.GetTransform(xfB, t); switch (this.m_type) { case exports.b2SeparationFunctionType.e_points: { const localPointA = this.m_proxyA.GetVertex(indexA); const localPointB = this.m_proxyB.GetVertex(indexB); const pointA = b2Transform.MulXV(xfA, localPointA, b2TimeOfImpact_s_pointA); const pointB = b2Transform.MulXV(xfB, localPointB, b2TimeOfImpact_s_pointB); const separation = b2Vec2.DotVV(b2Vec2.SubVV(pointB, pointA, b2Vec2.s_t0), this.m_axis); return separation; } case exports.b2SeparationFunctionType.e_faceA: { const normal = b2Rot.MulRV(xfA.q, this.m_axis, b2TimeOfImpact_s_normal); const pointA = b2Transform.MulXV(xfA, this.m_localPoint, b2TimeOfImpact_s_pointA); const localPointB = this.m_proxyB.GetVertex(indexB); const pointB = b2Transform.MulXV(xfB, localPointB, b2TimeOfImpact_s_pointB); const separation = b2Vec2.DotVV(b2Vec2.SubVV(pointB, pointA, b2Vec2.s_t0), normal); return separation; } case exports.b2SeparationFunctionType.e_faceB: { const normal = b2Rot.MulRV(xfB.q, this.m_axis, b2TimeOfImpact_s_normal); const pointB = b2Transform.MulXV(xfB, this.m_localPoint, b2TimeOfImpact_s_pointB); const localPointA = this.m_proxyA.GetVertex(indexA); const pointA = b2Transform.MulXV(xfA, localPointA, b2TimeOfImpact_s_pointA); const separation = b2Vec2.DotVV(b2Vec2.SubVV(pointA, pointB, b2Vec2.s_t0), normal); return separation; } default: // DEBUG: b2Assert(false); return 0; } } } const b2TimeOfImpact_s_timer = new b2Timer(); const b2TimeOfImpact_s_cache = new b2SimplexCache(); const b2TimeOfImpact_s_distanceInput = new b2DistanceInput(); const b2TimeOfImpact_s_distanceOutput = new b2DistanceOutput(); const b2TimeOfImpact_s_fcn = new b2SeparationFunction(); const b2TimeOfImpact_s_indexA = [0]; const b2TimeOfImpact_s_indexB = [0]; const b2TimeOfImpact_s_sweepA = new b2Sweep(); const b2TimeOfImpact_s_sweepB = new b2Sweep(); function b2TimeOfImpact(output, input) { const timer = b2TimeOfImpact_s_timer.Reset(); ++exports.b2_toiCalls; output.state = exports.b2TOIOutputState.e_unknown; output.t = input.tMax; const proxyA = input.proxyA; const proxyB = input.proxyB; const maxVertices = b2Max(b2_maxPolygonVertices, b2Max(proxyA.m_count, proxyB.m_count)); const sweepA = b2TimeOfImpact_s_sweepA.Copy(input.sweepA); const sweepB = b2TimeOfImpact_s_sweepB.Copy(input.sweepB); // Large rotations can make the root finder fail, so we normalize the // sweep angles. sweepA.Normalize(); sweepB.Normalize(); const tMax = input.tMax; const totalRadius = proxyA.m_radius + proxyB.m_radius; const target = b2Max(b2_linearSlop, totalRadius - 3 * b2_linearSlop); const tolerance = 0.25 * b2_linearSlop; // DEBUG: b2Assert(target > tolerance); let t1 = 0; const k_maxIterations = 20; // TODO_ERIN b2Settings let iter = 0; // Prepare input for distance query. const cache = b2TimeOfImpact_s_cache; cache.count = 0; const distanceInput = b2TimeOfImpact_s_distanceInput; distanceInput.proxyA.Copy(input.proxyA); distanceInput.proxyB.Copy(input.proxyB); distanceInput.useRadii = false; // The outer loop progressively attempts to compute new separating axes. // This loop terminates when an axis is repeated (no progress is made). for (;;) { const xfA = b2TimeOfImpact_s_xfA; const xfB = b2TimeOfImpact_s_xfB; sweepA.GetTransform(xfA, t1); sweepB.GetTransform(xfB, t1); // Get the distance between shapes. We can also use the results // to get a separating axis. distanceInput.transformA.Copy(xfA); distanceInput.transformB.Copy(xfB); const distanceOutput = b2TimeOfImpact_s_distanceOutput; b2Distance(distanceOutput, cache, distanceInput); // If the shapes are overlapped, we give up on continuous collision. if (distanceOutput.distance <= 0) { // Failure! output.state = exports.b2TOIOutputState.e_overlapped; output.t = 0; break; } if (distanceOutput.distance < target + tolerance) { // Victory! output.state = exports.b2TOIOutputState.e_touching; output.t = t1; break; } // Initialize the separating axis. const fcn = b2TimeOfImpact_s_fcn; fcn.Initialize(cache, proxyA, sweepA, proxyB, sweepB, t1); /* #if 0 // Dump the curve seen by the root finder { const int32 N = 100; float32 dx = 1.0f / N; float32 xs[N+1]; float32 fs[N+1]; float32 x = 0.0f; for (int32 i = 0; i <= N; ++i) { sweepA.GetTransform(&xfA, x); sweepB.GetTransform(&xfB, x); float32 f = fcn.Evaluate(xfA, xfB) - target; printf("%g %g\n", x, f); xs[i] = x; fs[i] = f; x += dx; } } #endif */ // Compute the TOI on the separating axis. We do this by successively // resolving the deepest point. This loop is bounded by the number of vertices. let done = false; let t2 = tMax; let pushBackIter = 0; for (;;) { // Find the deepest point at t2. Store the witness point indices. const indexA = b2TimeOfImpact_s_indexA; const indexB = b2TimeOfImpact_s_indexB; let s2 = fcn.FindMinSeparation(indexA, indexB, t2); // Is the final configuration separated? if (s2 > (target + tolerance)) { // Victory! output.state = exports.b2TOIOutputState.e_separated; output.t = tMax; done = true; break; } // Has the separation reached tolerance? if (s2 > (target - tolerance)) { // Advance the sweeps t1 = t2; break; } // Compute the initial separation of the witness points. let s1 = fcn.Evaluate(indexA[0], indexB[0], t1); // Check for initial overlap. This might happen if the root finder // runs out of iterations. if (s1 < (target - tolerance)) { output.state = exports.b2TOIOutputState.e_failed; output.t = t1; done = true; break; } // Check for touching if (s1 <= (target + tolerance)) { // Victory! t1 should hold the TOI (could be 0.0). output.state = exports.b2TOIOutputState.e_touching; output.t = t1; done = true; break; } // Compute 1D root of: f(x) - target = 0 let rootIterCount = 0; let a1 = t1; let a2 = t2; for (;;) { // Use a mix of the secant rule and bisection. let t = 0; if (rootIterCount & 1) { // Secant rule to improve convergence. t = a1 + (target - s1) * (a2 - a1) / (s2 - s1); } else { // Bisection to guarantee progress. t = 0.5 * (a1 + a2); } ++rootIterCount; ++exports.b2_toiRootIters; const s = fcn.Evaluate(indexA[0], indexB[0], t); if (b2Abs(s - target) < tolerance) { // t2 holds a tentative value for t1 t2 = t; break; } // Ensure we continue to bracket the root. if (s > target) { a1 = t; s1 = s; } else { a2 = t; s2 = s; } if (rootIterCount === 50) { break; } } exports.b2_toiMaxRootIters = b2Max(exports.b2_toiMaxRootIters, rootIterCount); ++pushBackIter; if (pushBackIter === maxVertices) { break; } } ++iter; ++exports.b2_toiIters; if (done) { break; } if (iter === k_maxIterations) { // Root finder got stuck. Semi-victory. output.state = exports.b2TOIOutputState.e_failed; output.t = t1; break; } } exports.b2_toiMaxIters = b2Max(exports.b2_toiMaxIters, iter); const time = timer.GetMilliseconds(); exports.b2_toiMaxTime = b2Max(exports.b2_toiMaxTime, time); exports.b2_toiTime += time; } /* * Copyright (c) 2006-2007 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ exports.b2JointType = void 0; (function (b2JointType) { b2JointType[b2JointType["e_unknownJoint"] = 0] = "e_unknownJoint"; b2JointType[b2JointType["e_revoluteJoint"] = 1] = "e_revoluteJoint"; b2JointType[b2JointType["e_prismaticJoint"] = 2] = "e_prismaticJoint"; b2JointType[b2JointType["e_distanceJoint"] = 3] = "e_distanceJoint"; b2JointType[b2JointType["e_pulleyJoint"] = 4] = "e_pulleyJoint"; b2JointType[b2JointType["e_mouseJoint"] = 5] = "e_mouseJoint"; b2JointType[b2JointType["e_gearJoint"] = 6] = "e_gearJoint"; b2JointType[b2JointType["e_wheelJoint"] = 7] = "e_wheelJoint"; b2JointType[b2JointType["e_weldJoint"] = 8] = "e_weldJoint"; b2JointType[b2JointType["e_frictionJoint"] = 9] = "e_frictionJoint"; b2JointType[b2JointType["e_ropeJoint"] = 10] = "e_ropeJoint"; b2JointType[b2JointType["e_motorJoint"] = 11] = "e_motorJoint"; b2JointType[b2JointType["e_areaJoint"] = 12] = "e_areaJoint"; })(exports.b2JointType || (exports.b2JointType = {})); class b2Jacobian { constructor() { this.linear = new b2Vec2(); this.angularA = 0; this.angularB = 0; } SetZero() { this.linear.SetZero(); this.angularA = 0; this.angularB = 0; return this; } Set(x, a1, a2) { this.linear.Copy(x); this.angularA = a1; this.angularB = a2; return this; } } /// A joint edge is used to connect bodies and joints together /// in a joint graph where each body is a node and each joint /// is an edge. A joint edge belongs to a doubly linked list /// maintained in each attached body. Each joint has two joint /// nodes, one for each attached body. class b2JointEdge { constructor(joint) { this._other = null; ///< provides quick access to the other body attached. this.prev = null; ///< the previous joint edge in the body's joint list this.next = null; ///< the next joint edge in the body's joint list this.joint = joint; } get other() { if (this._other === null) { throw new Error(); } return this._other; } set other(value) { if (this._other !== null) { throw new Error(); } this._other = value; } Reset() { this._other = null; this.prev = null; this.next = null; } } /// Joint definitions are used to construct joints. class b2JointDef { constructor(type) { /// The joint type is set automatically for concrete joint types. this.type = exports.b2JointType.e_unknownJoint; /// Use this to attach application specific data to your joints. this.userData = null; /// Set this flag to true if the attached bodies should collide. this.collideConnected = false; this.type = type; } } /// Utility to compute linear stiffness values from frequency and damping ratio // void b2LinearStiffness(float& stiffness, float& damping, // float frequencyHertz, float dampingRatio, // const b2Body* bodyA, const b2Body* bodyB); // kylin: 这里是合理的,需要修改def的值;C++中为引用传递 function b2LinearStiffness(def, frequencyHertz, dampingRatio, bodyA, bodyB) { const massA = bodyA.GetMass(); const massB = bodyB.GetMass(); let mass; if (massA > 0.0 && massB > 0.0) { mass = massA * massB / (massA + massB); } else if (massA > 0.0) { mass = massA; } else { mass = massB; } const omega = 2.0 * b2_pi * frequencyHertz; def.stiffness = mass * omega * omega; def.damping = 2.0 * mass * dampingRatio * omega; } /// Utility to compute rotational stiffness values frequency and damping ratio // void b2AngularStiffness(float& stiffness, float& damping, // float frequencyHertz, float dampingRatio, // const b2Body* bodyA, const b2Body* bodyB); function b2AngularStiffness(def, frequencyHertz, dampingRatio, bodyA, bodyB) { const IA = bodyA.GetInertia(); const IB = bodyB.GetInertia(); let I; if (IA > 0.0 && IB > 0.0) { I = IA * IB / (IA + IB); } else if (IA > 0.0) { I = IA; } else { I = IB; } const omega = 2.0 * b2_pi * frequencyHertz; def.stiffness = I * omega * omega; def.damping = 2.0 * I * dampingRatio * omega; } /// The base joint class. Joints are used to constraint two bodies together in /// various fashions. Some joints also feature limits and motors. class b2Joint { constructor(def) { // DEBUG: b2Assert(def.bodyA !== def.bodyB); this.m_type = exports.b2JointType.e_unknownJoint; this.m_prev = null; this.m_next = null; this.m_edgeA = new b2JointEdge(this); this.m_edgeB = new b2JointEdge(this); this.m_index = 0; this.m_islandFlag = false; this.m_collideConnected = false; this.m_userData = null; this.m_type = def.type; this.m_edgeA.other = def.bodyB; this.m_edgeB.other = def.bodyA; this.m_bodyA = def.bodyA; this.m_bodyB = def.bodyB; this.m_collideConnected = b2Maybe(def.collideConnected, false); this.m_userData = b2Maybe(def.userData, null); } /// Get the type of the concrete joint. GetType() { return this.m_type; } /// Get the first body attached to this joint. GetBodyA() { return this.m_bodyA; } /// Get the second body attached to this joint. GetBodyB() { return this.m_bodyB; } /// Get the next joint the world joint list. GetNext() { return this.m_next; } /// Get the user data pointer. GetUserData() { return this.m_userData; } /// Set the user data pointer. SetUserData(data) { this.m_userData = data; } /// Short-cut function to determine if either body is inactive. IsEnabled() { return this.m_bodyA.IsEnabled() && this.m_bodyB.IsEnabled(); } /// Get collide connected. /// Note: modifying the collide connect flag won't work correctly because /// the flag is only checked when fixture AABBs begin to overlap. GetCollideConnected() { return this.m_collideConnected; } /// Dump this joint to the log file. Dump(log) { log("// Dump is not supported for this joint type.\n"); } /// Shift the origin for any points stored in world coordinates. ShiftOrigin(newOrigin) { } Draw(draw) { const xf1 = this.m_bodyA.GetTransform(); const xf2 = this.m_bodyB.GetTransform(); const x1 = xf1.p; const x2 = xf2.p; const p1 = this.GetAnchorA(b2Joint.Draw_s_p1); const p2 = this.GetAnchorB(b2Joint.Draw_s_p2); const color = b2Joint.Draw_s_color.SetRGB(0.5, 0.8, 0.8); switch (this.m_type) { case exports.b2JointType.e_distanceJoint: draw.DrawSegment(p1, p2, color); break; case exports.b2JointType.e_pulleyJoint: { const pulley = this; const s1 = pulley.GetGroundAnchorA(); const s2 = pulley.GetGroundAnchorB(); draw.DrawSegment(s1, p1, color); draw.DrawSegment(s2, p2, color); draw.DrawSegment(s1, s2, color); } break; case exports.b2JointType.e_mouseJoint: { const c = b2Joint.Draw_s_c; c.Set(0.0, 1.0, 0.0); draw.DrawPoint(p1, 4.0, c); draw.DrawPoint(p2, 4.0, c); c.Set(0.8, 0.8, 0.8); draw.DrawSegment(p1, p2, c); } break; default: draw.DrawSegment(x1, p1, color); draw.DrawSegment(p1, p2, color); draw.DrawSegment(x2, p2, color); } } } /// Debug draw this joint b2Joint.Draw_s_p1 = new b2Vec2(); b2Joint.Draw_s_p2 = new b2Vec2(); b2Joint.Draw_s_color = new b2Color(0.5, 0.8, 0.8); b2Joint.Draw_s_c = new b2Color(); /* * Copyright (c) 2006-2007 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Distance joint definition. This requires defining an /// anchor point on both bodies and the non-zero length of the /// distance joint. The definition uses local anchor points /// so that the initial configuration can violate the constraint /// slightly. This helps when saving and loading a game. /// @warning Do not use a zero or short length. class b2DistanceJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_distanceJoint); this.localAnchorA = new b2Vec2(); this.localAnchorB = new b2Vec2(); this.length = 1; this.minLength = 0; this.maxLength = b2_maxFloat; // FLT_MAX; this.stiffness = 0; this.damping = 0; } Initialize(b1, b2, anchor1, anchor2) { this.bodyA = b1; this.bodyB = b2; this.bodyA.GetLocalPoint(anchor1, this.localAnchorA); this.bodyB.GetLocalPoint(anchor2, this.localAnchorB); this.length = b2Max(b2Vec2.DistanceVV(anchor1, anchor2), b2_linearSlop); this.minLength = this.length; this.maxLength = this.length; } } class b2DistanceJoint extends b2Joint { constructor(def) { super(def); this.m_stiffness = 0; this.m_damping = 0; this.m_bias = 0; this.m_length = 0; this.m_minLength = 0; this.m_maxLength = 0; // Solver shared this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); this.m_gamma = 0; this.m_impulse = 0; this.m_lowerImpulse = 0; this.m_upperImpulse = 0; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_u = new b2Vec2(); this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_currentLength = 0; this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_softMass = 0; this.m_mass = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_localAnchorA.Copy(b2Maybe(def.localAnchorA, b2Vec2.ZERO)); this.m_localAnchorB.Copy(b2Maybe(def.localAnchorB, b2Vec2.ZERO)); this.m_length = b2Max(b2Maybe(def.length, this.GetCurrentLength()), b2_linearSlop); this.m_minLength = b2Max(b2Maybe(def.minLength, this.m_length), b2_linearSlop); this.m_maxLength = b2Max(b2Maybe(def.maxLength, this.m_length), this.m_minLength); this.m_stiffness = b2Maybe(def.stiffness, 0); this.m_damping = b2Maybe(def.damping, 0); } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { // b2Vec2 F = inv_dt * (m_impulse + m_lowerImpulse - m_upperImpulse) * m_u; out.x = inv_dt * (this.m_impulse + this.m_lowerImpulse - this.m_upperImpulse) * this.m_u.x; out.y = inv_dt * (this.m_impulse + this.m_lowerImpulse - this.m_upperImpulse) * this.m_u.y; return out; } GetReactionTorque(inv_dt) { return 0; } GetLocalAnchorA() { return this.m_localAnchorA; } GetLocalAnchorB() { return this.m_localAnchorB; } SetLength(length) { this.m_impulse = 0; this.m_length = b2Max(b2_linearSlop, length); return this.m_length; } GetLength() { return this.m_length; } SetMinLength(minLength) { this.m_lowerImpulse = 0; this.m_minLength = b2Clamp(minLength, b2_linearSlop, this.m_maxLength); return this.m_minLength; } SetMaxLength(maxLength) { this.m_upperImpulse = 0; this.m_maxLength = b2Max(maxLength, this.m_minLength); return this.m_maxLength; } GetCurrentLength() { const pA = this.m_bodyA.GetWorldPoint(this.m_localAnchorA, new b2Vec2()); const pB = this.m_bodyB.GetWorldPoint(this.m_localAnchorB, new b2Vec2()); return b2Vec2.DistanceVV(pA, pB); } SetStiffness(stiffness) { this.m_stiffness = stiffness; } GetStiffness() { return this.m_stiffness; } SetDamping(damping) { this.m_damping = damping; } GetDamping() { return this.m_damping; } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2DistanceJointDef = new b2DistanceJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.localAnchorA.Set(%.15f, %.15f);\n", this.m_localAnchorA.x, this.m_localAnchorA.y); log(" jd.localAnchorB.Set(%.15f, %.15f);\n", this.m_localAnchorB.x, this.m_localAnchorB.y); log(" jd.length = %.15f;\n", this.m_length); log(" jd.minLength = %.15f;\n", this.m_minLength); log(" jd.maxLength = %.15f;\n", this.m_maxLength); log(" jd.stiffness = %.15f;\n", this.m_stiffness); log(" jd.damping = %.15f;\n", this.m_damping); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; const cA = data.positions[this.m_indexA].c; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const cB = data.positions[this.m_indexB].c; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; // const qA: b2Rot = new b2Rot(aA), qB: b2Rot = new b2Rot(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // m_rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // m_rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // m_u = cB + m_rB - cA - m_rA; this.m_u.x = cB.x + this.m_rB.x - cA.x - this.m_rA.x; this.m_u.y = cB.y + this.m_rB.y - cA.y - this.m_rA.y; // Handle singularity. this.m_currentLength = this.m_u.Length(); if (this.m_currentLength > b2_linearSlop) { this.m_u.SelfMul(1 / this.m_currentLength); } else { this.m_u.SetZero(); this.m_mass = 0; this.m_impulse = 0; this.m_lowerImpulse = 0; this.m_upperImpulse = 0; } // float32 crAu = b2Cross(m_rA, m_u); const crAu = b2Vec2.CrossVV(this.m_rA, this.m_u); // float32 crBu = b2Cross(m_rB, m_u); const crBu = b2Vec2.CrossVV(this.m_rB, this.m_u); // float32 invMass = m_invMassA + m_invIA * crAu * crAu + m_invMassB + m_invIB * crBu * crBu; let invMass = this.m_invMassA + this.m_invIA * crAu * crAu + this.m_invMassB + this.m_invIB * crBu * crBu; this.m_mass = invMass !== 0 ? 1 / invMass : 0; if (this.m_stiffness > 0 && this.m_minLength < this.m_maxLength) { // soft const C = this.m_currentLength - this.m_length; const d = this.m_damping; const k = this.m_stiffness; // magic formulas const h = data.step.dt; // gamma = 1 / (h * (d + h * k)) // the extra factor of h in the denominator is since the lambda is an impulse, not a force this.m_gamma = h * (d + h * k); this.m_gamma = this.m_gamma !== 0 ? 1 / this.m_gamma : 0; this.m_bias = C * h * k * this.m_gamma; invMass += this.m_gamma; this.m_softMass = invMass !== 0 ? 1 / invMass : 0; } else { // rigid this.m_gamma = 0; this.m_bias = 0; this.m_softMass = this.m_mass; } if (data.step.warmStarting) { // Scale the impulse to support a variable time step. this.m_impulse *= data.step.dtRatio; this.m_lowerImpulse *= data.step.dtRatio; this.m_upperImpulse *= data.step.dtRatio; const P = b2Vec2.MulSV(this.m_impulse + this.m_lowerImpulse - this.m_upperImpulse, this.m_u, b2DistanceJoint.InitVelocityConstraints_s_P); vA.SelfMulSub(this.m_invMassA, P); wA -= this.m_invIA * b2Vec2.CrossVV(this.m_rA, P); vB.SelfMulAdd(this.m_invMassB, P); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, P); } else { this.m_impulse = 0; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; if (this.m_minLength < this.m_maxLength) { if (this.m_stiffness > 0) { // Cdot = dot(u, v + cross(w, r)) const vpA = b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2DistanceJoint.SolveVelocityConstraints_s_vpA); const vpB = b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2DistanceJoint.SolveVelocityConstraints_s_vpB); const Cdot = b2Vec2.DotVV(this.m_u, b2Vec2.SubVV(vpB, vpA, b2Vec2.s_t0)); const impulse = -this.m_softMass * (Cdot + this.m_bias + this.m_gamma * this.m_impulse); this.m_impulse += impulse; const P = b2Vec2.MulSV(impulse, this.m_u, b2DistanceJoint.SolveVelocityConstraints_s_P); vA.SelfMulSub(this.m_invMassA, P); wA -= this.m_invIA * b2Vec2.CrossVV(this.m_rA, P); vB.SelfMulAdd(this.m_invMassB, P); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, P); } // lower { const C = this.m_currentLength - this.m_minLength; const bias = b2Max(0, C) * data.step.inv_dt; const vpA = b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2DistanceJoint.SolveVelocityConstraints_s_vpA); const vpB = b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2DistanceJoint.SolveVelocityConstraints_s_vpB); const Cdot = b2Vec2.DotVV(this.m_u, b2Vec2.SubVV(vpB, vpA, b2Vec2.s_t0)); let impulse = -this.m_mass * (Cdot + bias); const oldImpulse = this.m_lowerImpulse; this.m_lowerImpulse = b2Max(0, this.m_lowerImpulse + impulse); impulse = this.m_lowerImpulse - oldImpulse; const P = b2Vec2.MulSV(impulse, this.m_u, b2DistanceJoint.SolveVelocityConstraints_s_P); vA.SelfMulSub(this.m_invMassA, P); wA -= this.m_invIA * b2Vec2.CrossVV(this.m_rA, P); vB.SelfMulAdd(this.m_invMassB, P); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, P); } // upper { const C = this.m_maxLength - this.m_currentLength; const bias = b2Max(0, C) * data.step.inv_dt; const vpA = b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2DistanceJoint.SolveVelocityConstraints_s_vpA); const vpB = b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2DistanceJoint.SolveVelocityConstraints_s_vpB); const Cdot = b2Vec2.DotVV(this.m_u, b2Vec2.SubVV(vpA, vpB, b2Vec2.s_t0)); let impulse = -this.m_mass * (Cdot + bias); const oldImpulse = this.m_upperImpulse; this.m_upperImpulse = b2Max(0, this.m_upperImpulse + impulse); impulse = this.m_upperImpulse - oldImpulse; const P = b2Vec2.MulSV(-impulse, this.m_u, b2DistanceJoint.SolveVelocityConstraints_s_P); vA.SelfMulSub(this.m_invMassA, P); wA -= this.m_invIA * b2Vec2.CrossVV(this.m_rA, P); vB.SelfMulAdd(this.m_invMassB, P); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, P); } } else { // Equal limits // Cdot = dot(u, v + cross(w, r)) const vpA = b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2DistanceJoint.SolveVelocityConstraints_s_vpA); const vpB = b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2DistanceJoint.SolveVelocityConstraints_s_vpB); const Cdot = b2Vec2.DotVV(this.m_u, b2Vec2.SubVV(vpB, vpA, b2Vec2.s_t0)); const impulse = -this.m_mass * Cdot; this.m_impulse += impulse; const P = b2Vec2.MulSV(impulse, this.m_u, b2DistanceJoint.SolveVelocityConstraints_s_P); vA.SelfMulSub(this.m_invMassA, P); wA -= this.m_invIA * b2Vec2.CrossVV(this.m_rA, P); vB.SelfMulAdd(this.m_invMassB, P); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, P); } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { const cA = data.positions[this.m_indexA].c; let aA = data.positions[this.m_indexA].a; const cB = data.positions[this.m_indexB].c; let aB = data.positions[this.m_indexB].a; // const qA: b2Rot = new b2Rot(aA), qB: b2Rot = new b2Rot(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // use m_rA // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // use m_rB // b2Vec2 u = cB + rB - cA - rA; const u = this.m_u; // use m_u u.x = cB.x + rB.x - cA.x - rA.x; u.y = cB.y + rB.y - cA.y - rA.y; const length = this.m_u.Normalize(); let C; if (this.m_minLength == this.m_maxLength) { C = length - this.m_minLength; } else if (length < this.m_minLength) { C = length - this.m_minLength; } else if (this.m_maxLength < length) { C = length - this.m_maxLength; } else { return true; } const impulse = -this.m_mass * C; const P = b2Vec2.MulSV(impulse, u, b2DistanceJoint.SolvePositionConstraints_s_P); cA.SelfMulSub(this.m_invMassA, P); aA -= this.m_invIA * b2Vec2.CrossVV(rA, P); cB.SelfMulAdd(this.m_invMassB, P); aB += this.m_invIB * b2Vec2.CrossVV(rB, P); // data.positions[this.m_indexA].c = cA; data.positions[this.m_indexA].a = aA; // data.positions[this.m_indexB].c = cB; data.positions[this.m_indexB].a = aB; return b2Abs(C) < b2_linearSlop; } Draw(draw) { const xfA = this.m_bodyA.GetTransform(); const xfB = this.m_bodyB.GetTransform(); const pA = b2Transform.MulXV(xfA, this.m_localAnchorA, b2DistanceJoint.Draw_s_pA); const pB = b2Transform.MulXV(xfB, this.m_localAnchorB, b2DistanceJoint.Draw_s_pB); const axis = b2Vec2.SubVV(pB, pA, b2DistanceJoint.Draw_s_axis); axis.Normalize(); const c1 = b2DistanceJoint.Draw_s_c1; // b2Color c1(0.7f, 0.7f, 0.7f); const c2 = b2DistanceJoint.Draw_s_c2; // b2Color c2(0.3f, 0.9f, 0.3f); const c3 = b2DistanceJoint.Draw_s_c3; // b2Color c3(0.9f, 0.3f, 0.3f); const c4 = b2DistanceJoint.Draw_s_c4; // b2Color c4(0.4f, 0.4f, 0.4f); draw.DrawSegment(pA, pB, c4); // b2Vec2 pRest = pA + this.m_length * axis; const pRest = b2Vec2.AddVMulSV(pA, this.m_length, axis, b2DistanceJoint.Draw_s_pRest); draw.DrawPoint(pRest, 8.0, c1); if (this.m_minLength != this.m_maxLength) { if (this.m_minLength > b2_linearSlop) { // b2Vec2 pMin = pA + this.m_minLength * axis; const pMin = b2Vec2.AddVMulSV(pA, this.m_minLength, axis, b2DistanceJoint.Draw_s_pMin); draw.DrawPoint(pMin, 4.0, c2); } if (this.m_maxLength < b2_maxFloat) { // b2Vec2 pMax = pA + this.m_maxLength * axis; const pMax = b2Vec2.AddVMulSV(pA, this.m_maxLength, axis, b2DistanceJoint.Draw_s_pMax); draw.DrawPoint(pMax, 4.0, c3); } } } } b2DistanceJoint.InitVelocityConstraints_s_P = new b2Vec2(); b2DistanceJoint.SolveVelocityConstraints_s_vpA = new b2Vec2(); b2DistanceJoint.SolveVelocityConstraints_s_vpB = new b2Vec2(); b2DistanceJoint.SolveVelocityConstraints_s_P = new b2Vec2(); b2DistanceJoint.SolvePositionConstraints_s_P = new b2Vec2(); b2DistanceJoint.Draw_s_pA = new b2Vec2(); b2DistanceJoint.Draw_s_pB = new b2Vec2(); b2DistanceJoint.Draw_s_axis = new b2Vec2(); b2DistanceJoint.Draw_s_c1 = new b2Color(0.7, 0.7, 0.7); b2DistanceJoint.Draw_s_c2 = new b2Color(0.3, 0.9, 0.3); b2DistanceJoint.Draw_s_c3 = new b2Color(0.9, 0.3, 0.3); b2DistanceJoint.Draw_s_c4 = new b2Color(0.4, 0.4, 0.4); b2DistanceJoint.Draw_s_pRest = new b2Vec2(); b2DistanceJoint.Draw_s_pMin = new b2Vec2(); b2DistanceJoint.Draw_s_pMax = new b2Vec2(); // DEBUG: import { b2Assert } from "../common/b2_settings.js"; class b2AreaJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_areaJoint); this.bodies = []; this.stiffness = 0; this.damping = 0; } AddBody(body) { this.bodies.push(body); if (this.bodies.length === 1) { this.bodyA = body; } else if (this.bodies.length === 2) { this.bodyB = body; } } } class b2AreaJoint extends b2Joint { constructor(def) { super(def); this.m_stiffness = 0; this.m_damping = 0; // Solver shared this.m_impulse = 0; this.m_targetArea = 0; this.m_delta = new b2Vec2(); // DEBUG: b2Assert(def.bodies.length >= 3, "You cannot create an area joint with less than three bodies."); this.m_bodies = def.bodies; this.m_stiffness = b2Maybe(def.stiffness, 0); this.m_damping = b2Maybe(def.damping, 0); this.m_targetLengths = b2MakeNumberArray(def.bodies.length); this.m_normals = b2Vec2.MakeArray(def.bodies.length); this.m_joints = []; // b2MakeNullArray(def.bodies.length); this.m_deltas = b2Vec2.MakeArray(def.bodies.length); const djd = new b2DistanceJointDef(); djd.stiffness = this.m_stiffness; djd.damping = this.m_damping; this.m_targetArea = 0; for (let i = 0; i < this.m_bodies.length; ++i) { const body = this.m_bodies[i]; const next = this.m_bodies[(i + 1) % this.m_bodies.length]; const body_c = body.GetWorldCenter(); const next_c = next.GetWorldCenter(); this.m_targetLengths[i] = b2Vec2.DistanceVV(body_c, next_c); this.m_targetArea += b2Vec2.CrossVV(body_c, next_c); djd.Initialize(body, next, body_c, next_c); this.m_joints[i] = body.GetWorld().CreateJoint(djd); } this.m_targetArea *= 0.5; } GetAnchorA(out) { return out; } GetAnchorB(out) { return out; } GetReactionForce(inv_dt, out) { return out; } GetReactionTorque(inv_dt) { return 0; } SetStiffness(stiffness) { this.m_stiffness = stiffness; for (let i = 0; i < this.m_joints.length; ++i) { this.m_joints[i].SetStiffness(stiffness); } } GetStiffness() { return this.m_stiffness; } SetDamping(damping) { this.m_damping = damping; for (let i = 0; i < this.m_joints.length; ++i) { this.m_joints[i].SetDamping(damping); } } GetDamping() { return this.m_damping; } Dump(log) { log("Area joint dumping is not supported.\n"); } InitVelocityConstraints(data) { for (let i = 0; i < this.m_bodies.length; ++i) { const prev = this.m_bodies[(i + this.m_bodies.length - 1) % this.m_bodies.length]; const next = this.m_bodies[(i + 1) % this.m_bodies.length]; const prev_c = data.positions[prev.m_islandIndex].c; const next_c = data.positions[next.m_islandIndex].c; const delta = this.m_deltas[i]; b2Vec2.SubVV(next_c, prev_c, delta); } if (data.step.warmStarting) { this.m_impulse *= data.step.dtRatio; for (let i = 0; i < this.m_bodies.length; ++i) { const body = this.m_bodies[i]; const body_v = data.velocities[body.m_islandIndex].v; const delta = this.m_deltas[i]; body_v.x += body.m_invMass * delta.y * 0.5 * this.m_impulse; body_v.y += body.m_invMass * -delta.x * 0.5 * this.m_impulse; } } else { this.m_impulse = 0; } } SolveVelocityConstraints(data) { let dotMassSum = 0; let crossMassSum = 0; for (let i = 0; i < this.m_bodies.length; ++i) { const body = this.m_bodies[i]; const body_v = data.velocities[body.m_islandIndex].v; const delta = this.m_deltas[i]; dotMassSum += delta.LengthSquared() / body.GetMass(); crossMassSum += b2Vec2.CrossVV(body_v, delta); } const lambda = -2 * crossMassSum / dotMassSum; // lambda = b2Clamp(lambda, -b2_maxLinearCorrection, b2_maxLinearCorrection); this.m_impulse += lambda; for (let i = 0; i < this.m_bodies.length; ++i) { const body = this.m_bodies[i]; const body_v = data.velocities[body.m_islandIndex].v; const delta = this.m_deltas[i]; body_v.x += body.m_invMass * delta.y * 0.5 * lambda; body_v.y += body.m_invMass * -delta.x * 0.5 * lambda; } } SolvePositionConstraints(data) { let perimeter = 0; let area = 0; for (let i = 0; i < this.m_bodies.length; ++i) { const body = this.m_bodies[i]; const next = this.m_bodies[(i + 1) % this.m_bodies.length]; const body_c = data.positions[body.m_islandIndex].c; const next_c = data.positions[next.m_islandIndex].c; const delta = b2Vec2.SubVV(next_c, body_c, this.m_delta); let dist = delta.Length(); if (dist < b2_epsilon) { dist = 1; } this.m_normals[i].x = delta.y / dist; this.m_normals[i].y = -delta.x / dist; perimeter += dist; area += b2Vec2.CrossVV(body_c, next_c); } area *= 0.5; const deltaArea = this.m_targetArea - area; const toExtrude = 0.5 * deltaArea / perimeter; let done = true; for (let i = 0; i < this.m_bodies.length; ++i) { const body = this.m_bodies[i]; const body_c = data.positions[body.m_islandIndex].c; const next_i = (i + 1) % this.m_bodies.length; const delta = b2Vec2.AddVV(this.m_normals[i], this.m_normals[next_i], this.m_delta); delta.SelfMul(toExtrude); const norm_sq = delta.LengthSquared(); if (norm_sq > b2Sq(b2_maxLinearCorrection)) { delta.SelfMul(b2_maxLinearCorrection / b2Sqrt(norm_sq)); } if (norm_sq > b2Sq(b2_linearSlop)) { done = false; } body_c.x += delta.x; body_c.y += delta.y; } return done; } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// This holds contact filtering data. class b2Filter { constructor() { /// The collision category bits. Normally you would just set one bit. this.categoryBits = 0x0001; /// The collision mask bits. This states the categories that this /// shape would accept for collision. this.maskBits = 0xFFFF; /// Collision groups allow a certain group of objects to never collide (negative) /// or always collide (positive). Zero means no collision group. Non-zero group /// filtering always wins against the mask bits. this.groupIndex = 0; } Clone() { return new b2Filter().Copy(this); } Copy(other) { // DEBUG: b2Assert(this !== other); this.categoryBits = other.categoryBits; this.maskBits = other.maskBits; this.groupIndex = other.groupIndex || 0; return this; } } b2Filter.DEFAULT = new b2Filter(); /// A fixture definition is used to create a fixture. This class defines an /// abstract fixture definition. You can reuse fixture definitions safely. class b2FixtureDef { constructor() { /// Use this to store application specific fixture data. this.userData = null; /// The friction coefficient, usually in the range [0,1]. this.friction = 0.2; /// The restitution (elasticity) usually in the range [0,1]. this.restitution = 0; /// Restitution velocity threshold, usually in m/s. Collisions above this /// speed have restitution applied (will bounce). this.restitutionThreshold = 1.0 * b2_lengthUnitsPerMeter; /// The density, usually in kg/m^2. this.density = 0; /// A sensor shape collects contact information but never generates a collision /// response. this.isSensor = false; /// Contact filtering data. this.filter = new b2Filter(); } } /// This proxy is used internally to connect fixtures to the broad-phase. class b2FixtureProxy { constructor(fixture, childIndex) { this.aabb = new b2AABB(); this.childIndex = 0; this.fixture = fixture; this.childIndex = childIndex; this.fixture.m_shape.ComputeAABB(this.aabb, this.fixture.m_body.GetTransform(), childIndex); this.treeNode = this.fixture.m_body.m_world.m_contactManager.m_broadPhase.CreateProxy(this.aabb, this); } Reset() { this.fixture.m_body.m_world.m_contactManager.m_broadPhase.DestroyProxy(this.treeNode); } Touch() { this.fixture.m_body.m_world.m_contactManager.m_broadPhase.TouchProxy(this.treeNode); } Synchronize(transform1, transform2) { if (transform1 === transform2) { this.fixture.m_shape.ComputeAABB(this.aabb, transform1, this.childIndex); this.fixture.m_body.m_world.m_contactManager.m_broadPhase.MoveProxy(this.treeNode, this.aabb, b2Vec2.ZERO); } else { // Compute an AABB that covers the swept shape (may miss some rotation effect). const aabb1 = b2FixtureProxy.Synchronize_s_aabb1; const aabb2 = b2FixtureProxy.Synchronize_s_aabb2; this.fixture.m_shape.ComputeAABB(aabb1, transform1, this.childIndex); this.fixture.m_shape.ComputeAABB(aabb2, transform2, this.childIndex); this.aabb.Combine2(aabb1, aabb2); const displacement = b2FixtureProxy.Synchronize_s_displacement; displacement.Copy(aabb2.GetCenter()).SelfSub(aabb1.GetCenter()); this.fixture.m_body.m_world.m_contactManager.m_broadPhase.MoveProxy(this.treeNode, this.aabb, displacement); } } } b2FixtureProxy.Synchronize_s_aabb1 = new b2AABB(); b2FixtureProxy.Synchronize_s_aabb2 = new b2AABB(); b2FixtureProxy.Synchronize_s_displacement = new b2Vec2(); /// A fixture is used to attach a shape to a body for collision detection. A fixture /// inherits its transform from its parent. Fixtures hold additional non-geometric data /// such as friction, collision filters, etc. /// Fixtures are created via b2Body::CreateFixture. /// @warning you cannot reuse fixtures. class b2Fixture { constructor(body, def) { this.m_density = 0; this.m_next = null; this.m_friction = 0; this.m_restitution = 0; this.m_restitutionThreshold = 1.0 * b2_lengthUnitsPerMeter; this.m_proxies = []; this.m_filter = new b2Filter(); this.m_isSensor = false; this.m_userData = null; this.m_body = body; this.m_shape = def.shape.Clone(); this.m_userData = b2Maybe(def.userData, null); this.m_friction = b2Maybe(def.friction, 0.2); this.m_restitution = b2Maybe(def.restitution, 0); this.m_restitutionThreshold = b2Maybe(def.restitutionThreshold, 0); this.m_filter.Copy(b2Maybe(def.filter, b2Filter.DEFAULT)); this.m_isSensor = b2Maybe(def.isSensor, false); this.m_density = b2Maybe(def.density, 0); } get m_proxyCount() { return this.m_proxies.length; } // 为了避免引擎/项目报错增加到空方法;通过 body.CreateFixture 以及 body.DestroyFixture 方法执行时不需要这两个方法的 Create(allocator, body, def) { } Destroy() { } Reset() { // The proxies must be destroyed before calling this. // DEBUG: b2Assert(this.m_proxyCount === 0); } /// Get the type of the child shape. You can use this to down cast to the concrete shape. /// @return the shape type. GetType() { return this.m_shape.GetType(); } /// Get the child shape. You can modify the child shape, however you should not change the /// number of vertices because this will crash some collision caching mechanisms. /// Manipulating the shape may lead to non-physical behavior. GetShape() { return this.m_shape; } /// Set if this fixture is a sensor. SetSensor(sensor) { if (sensor !== this.m_isSensor) { this.m_body.SetAwake(true); this.m_isSensor = sensor; } } /// Is this fixture a sensor (non-solid)? /// @return the true if the shape is a sensor. IsSensor() { return this.m_isSensor; } /// Set the contact filtering data. This will not update contacts until the next time /// step when either parent body is active and awake. /// This automatically calls Refilter. SetFilterData(filter) { this.m_filter.Copy(filter); this.Refilter(); } /// Get the contact filtering data. GetFilterData() { return this.m_filter; } /// Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide. Refilter() { // Flag associated contacts for filtering. let edge = this.m_body.GetContactList(); while (edge) { const contact = edge.contact; const fixtureA = contact.GetFixtureA(); const fixtureB = contact.GetFixtureB(); if (fixtureA === this || fixtureB === this) { contact.FlagForFiltering(); } edge = edge.next; } // Touch each proxy so that new pairs may be created this.TouchProxies(); } /// Get the parent body of this fixture. This is NULL if the fixture is not attached. /// @return the parent body. GetBody() { return this.m_body; } /// Get the next fixture in the parent body's fixture list. /// @return the next shape. GetNext() { return this.m_next; } /// Get the user data that was assigned in the fixture definition. Use this to /// store your application specific data. GetUserData() { return this.m_userData; } /// Set the user data. Use this to store your application specific data. SetUserData(data) { this.m_userData = data; } /// Test a point for containment in this fixture. /// @param p a point in world coordinates. TestPoint(p) { return this.m_shape.TestPoint(this.m_body.GetTransform(), p); } /// Cast a ray against this shape. /// @param output the ray-cast results. /// @param input the ray-cast input parameters. RayCast(output, input, childIndex) { return this.m_shape.RayCast(output, input, this.m_body.GetTransform(), childIndex); } /// Get the mass data for this fixture. The mass data is based on the density and /// the shape. The rotational inertia is about the shape's origin. This operation /// may be expensive. GetMassData(massData = new b2MassData()) { this.m_shape.ComputeMass(massData, this.m_density); return massData; } /// Set the density of this fixture. This will _not_ automatically adjust the mass /// of the body. You must call b2Body::ResetMassData to update the body's mass. SetDensity(density) { this.m_density = density; } /// Get the density of this fixture. GetDensity() { return this.m_density; } /// Get the coefficient of friction. GetFriction() { return this.m_friction; } /// Set the coefficient of friction. This will _not_ change the friction of /// existing contacts. SetFriction(friction) { this.m_friction = friction; } /// Get the coefficient of restitution. GetRestitution() { return this.m_restitution; } /// Set the coefficient of restitution. This will _not_ change the restitution of /// existing contacts. SetRestitution(restitution) { this.m_restitution = restitution; } /// Get the restitution velocity threshold. GetRestitutionThreshold() { return this.m_restitutionThreshold; } /// Set the restitution threshold. This will _not_ change the restitution threshold of /// existing contacts. SetRestitutionThreshold(threshold) { this.m_restitutionThreshold = threshold; } /// Get the fixture's AABB. This AABB may be enlarge and/or stale. /// If you need a more accurate AABB, compute it using the shape and /// the body transform. GetAABB(childIndex) { // DEBUG: b2Assert(0 <= childIndex && childIndex < this.m_proxyCount); return this.m_proxies[childIndex].aabb; } /// Dump this fixture to the log file. Dump(log, bodyIndex) { log(" const fd: b2FixtureDef = new b2FixtureDef();\n"); log(" fd.friction = %.15f;\n", this.m_friction); log(" fd.restitution = %.15f;\n", this.m_restitution); log(" fd.restitutionThreshold = %.15f;\n", this.m_restitutionThreshold); log(" fd.density = %.15f;\n", this.m_density); log(" fd.isSensor = %s;\n", (this.m_isSensor) ? ("true") : ("false")); log(" fd.filter.categoryBits = %d;\n", this.m_filter.categoryBits); log(" fd.filter.maskBits = %d;\n", this.m_filter.maskBits); log(" fd.filter.groupIndex = %d;\n", this.m_filter.groupIndex); this.m_shape.Dump(log); log("\n"); log(" fd.shape = shape;\n"); log("\n"); log(" bodies[%d].CreateFixture(fd);\n", bodyIndex); } // These support body activation/deactivation. CreateProxies() { if (this.m_proxies.length !== 0) { throw new Error(); } // Create proxies in the broad-phase. for (let i = 0; i < this.m_shape.GetChildCount(); ++i) { this.m_proxies[i] = new b2FixtureProxy(this, i); } } DestroyProxies() { // Destroy proxies in the broad-phase. for (const proxy of this.m_proxies) { proxy.Reset(); } this.m_proxies.length = 0; } TouchProxies() { for (const proxy of this.m_proxies) { proxy.Touch(); } } Synchronize(broadPhase, transform1, transform2) { this.SynchronizeProxies(transform1, transform2); } SynchronizeProxies(transform1, transform2) { for (const proxy of this.m_proxies) { proxy.Synchronize(transform1, transform2); } } } /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// The body type. /// static: zero mass, zero velocity, may be manually moved /// kinematic: zero mass, non-zero velocity set by user, moved by solver /// dynamic: positive mass, non-zero velocity determined by forces, moved by solver exports.b2BodyType = void 0; (function (b2BodyType) { b2BodyType[b2BodyType["b2_unknown"] = -1] = "b2_unknown"; b2BodyType[b2BodyType["b2_staticBody"] = 0] = "b2_staticBody"; b2BodyType[b2BodyType["b2_kinematicBody"] = 1] = "b2_kinematicBody"; b2BodyType[b2BodyType["b2_dynamicBody"] = 2] = "b2_dynamicBody"; // TODO_ERIN // b2_bulletBody = 3 })(exports.b2BodyType || (exports.b2BodyType = {})); /// A body definition holds all the data needed to construct a rigid body. /// You can safely re-use body definitions. Shapes are added to a body after construction. class b2BodyDef { constructor() { /// The body type: static, kinematic, or dynamic. /// Note: if a dynamic body would have zero mass, the mass is set to one. this.type = exports.b2BodyType.b2_staticBody; /// The world position of the body. Avoid creating bodies at the origin /// since this can lead to many overlapping shapes. /// kylin: C++版本的b2Vec2重载了大量的运算符,为了避免开发者错误操作,设置为 readonly 是合理的 this.position = new b2Vec2(0, 0); /// The world angle of the body in radians. this.angle = 0; /// The linear velocity of the body's origin in world co-ordinates. this.linearVelocity = new b2Vec2(0, 0); /// The angular velocity of the body. this.angularVelocity = 0; /// Linear damping is use to reduce the linear velocity. The damping parameter /// can be larger than 1.0f but the damping effect becomes sensitive to the /// time step when the damping parameter is large. this.linearDamping = 0; /// Angular damping is use to reduce the angular velocity. The damping parameter /// can be larger than 1.0f but the damping effect becomes sensitive to the /// time step when the damping parameter is large. this.angularDamping = 0; /// Set this flag to false if this body should never fall asleep. Note that /// this increases CPU usage. this.allowSleep = true; /// Is this body initially awake or sleeping? this.awake = true; /// Should this body be prevented from rotating? Useful for characters. this.fixedRotation = false; /// 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. this.bullet = false; /// Does this body start out enabled? this.enabled = true; /// Use this to store application specific body data. this.userData = null; /// Scale the gravity applied to this body. this.gravityScale = 1; } } /// A rigid body. These are created via b2World::CreateBody. class b2Body { constructor(bd, world) { this.m_type = exports.b2BodyType.b2_staticBody; this.m_islandFlag = false; this.m_awakeFlag = false; this.m_autoSleepFlag = false; this.m_bulletFlag = false; this.m_fixedRotationFlag = false; this.m_enabledFlag = false; this.m_toiFlag = false; this.m_islandIndex = 0; this.m_xf = new b2Transform(); // the body origin transform this.m_sweep = new b2Sweep(); // the swept motion for CCD this.m_linearVelocity = new b2Vec2(); this.m_angularVelocity = 0; this.m_force = new b2Vec2(); this.m_torque = 0; this.m_prev = null; this.m_next = null; this.m_fixtureList = null; this.m_fixtureCount = 0; this.m_jointList = null; this.m_contactList = null; this.m_mass = 1; this.m_invMass = 1; // Rotational inertia about the center of mass. this.m_I = 0; this.m_invI = 0; this.m_linearDamping = 0; this.m_angularDamping = 0; this.m_gravityScale = 1; this.m_sleepTime = 0; this.m_userData = null; this.m_bulletFlag = b2Maybe(bd.bullet, false); this.m_fixedRotationFlag = b2Maybe(bd.fixedRotation, false); this.m_autoSleepFlag = b2Maybe(bd.allowSleep, true); // this.m_awakeFlag = b2Maybe(bd.awake, true); if (b2Maybe(bd.awake, false) && b2Maybe(bd.type, exports.b2BodyType.b2_staticBody) !== exports.b2BodyType.b2_staticBody) { this.m_awakeFlag = true; } this.m_enabledFlag = b2Maybe(bd.enabled, true); this.m_world = world; this.m_xf.p.Copy(b2Maybe(bd.position, b2Vec2.ZERO)); // DEBUG: b2Assert(this.m_xf.p.IsValid()); this.m_xf.q.SetAngle(b2Maybe(bd.angle, 0)); // DEBUG: b2Assert(b2IsValid(this.m_xf.q.GetAngle())); this.m_sweep.localCenter.SetZero(); this.m_sweep.c0.Copy(this.m_xf.p); this.m_sweep.c.Copy(this.m_xf.p); this.m_sweep.a0 = this.m_sweep.a = this.m_xf.q.GetAngle(); this.m_sweep.alpha0 = 0; this.m_linearVelocity.Copy(b2Maybe(bd.linearVelocity, b2Vec2.ZERO)); // DEBUG: b2Assert(this.m_linearVelocity.IsValid()); this.m_angularVelocity = b2Maybe(bd.angularVelocity, 0); // DEBUG: b2Assert(b2IsValid(this.m_angularVelocity)); this.m_linearDamping = b2Maybe(bd.linearDamping, 0); this.m_angularDamping = b2Maybe(bd.angularDamping, 0); this.m_gravityScale = b2Maybe(bd.gravityScale, 1); // DEBUG: b2Assert(b2IsValid(this.m_gravityScale) && this.m_gravityScale >= 0); // DEBUG: b2Assert(b2IsValid(this.m_angularDamping) && this.m_angularDamping >= 0); // DEBUG: b2Assert(b2IsValid(this.m_linearDamping) && this.m_linearDamping >= 0); this.m_force.SetZero(); this.m_torque = 0; this.m_sleepTime = 0; this.m_type = b2Maybe(bd.type, exports.b2BodyType.b2_staticBody); this.m_mass = 0; this.m_invMass = 0; this.m_I = 0; this.m_invI = 0; this.m_userData = bd.userData; this.m_fixtureList = null; this.m_fixtureCount = 0; } CreateFixture(a, b = 0) { if (a instanceof b2Shape) { return this.CreateFixtureShapeDensity(a, b); } else { return this.CreateFixtureDef(a); } } /// Creates a fixture and attach it to this body. Use this function if you need /// to set some fixture parameters, like friction. Otherwise you can create the /// fixture directly from a shape. /// If the density is non-zero, this function automatically updates the mass of the body. /// Contacts are not created until the next time step. /// @param def the fixture definition. /// @warning This function is locked during callbacks. CreateFixtureDef(def) { if (this.m_world.IsLocked()) { throw new Error(); } const fixture = new b2Fixture(this, def); if (this.m_enabledFlag) { fixture.CreateProxies(); } fixture.m_next = this.m_fixtureList; this.m_fixtureList = fixture; ++this.m_fixtureCount; // fixture.m_body = this; // Adjust mass properties if needed. if (fixture.m_density > 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_newContacts = true; return fixture; } CreateFixtureShapeDensity(shape, density = 0) { const def = b2Body.CreateFixtureShapeDensity_s_def; def.shape = shape; def.density = density; return this.CreateFixtureDef(def); } /// 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. /// @param fixture the fixture to be removed. /// @warning This function is locked during callbacks. DestroyFixture(fixture) { if (this.m_world.IsLocked()) { throw new Error(); } // DEBUG: b2Assert(fixture.m_body === this); // Remove the fixture from this body's singly linked list. // DEBUG: b2Assert(this.m_fixtureCount > 0); let node = this.m_fixtureList; let ppF = null; // DEBUG: let found: boolean = false; while (node !== null) { if (node === fixture) { if (ppF) { ppF.m_next = fixture.m_next; } else { this.m_fixtureList = fixture.m_next; } // DEBUG: found = true; break; } ppF = node; node = node.m_next; } // You tried to remove a shape that is not attached to this body. // DEBUG: b2Assert(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.m_contactManager.Destroy(c); } } if (this.m_enabledFlag) { fixture.DestroyProxies(); } // fixture.m_body = null; fixture.m_next = null; fixture.Reset(); --this.m_fixtureCount; // Reset the mass data. this.ResetMassData(); } /// Set the position of the body's origin and rotation. /// This breaks any contacts and wakes the other bodies. /// Manipulating a body's transform may cause non-physical behavior. /// @param position the world position of the body's local origin. /// @param angle the world rotation in radians. SetTransformVec(position, angle) { this.SetTransformXY(position.x, position.y, angle); } SetTransformXY(x, y, angle) { if (this.m_world.IsLocked()) { throw new Error(); } this.m_xf.q.SetAngle(angle); this.m_xf.p.Set(x, y); b2Transform.MulXV(this.m_xf, this.m_sweep.localCenter, this.m_sweep.c); this.m_sweep.a = angle; this.m_sweep.c0.Copy(this.m_sweep.c); this.m_sweep.a0 = angle; for (let f = this.m_fixtureList; f; f = f.m_next) { f.SynchronizeProxies(this.m_xf, this.m_xf); } // Check for new contacts the next step this.m_world.m_newContacts = true; } SetTransform(position, angle) { this.SetTransformXY(position.x, position.y, angle); } /// Get the body transform for the body's origin. /// @return the world transform of the body's origin. GetTransform() { return this.m_xf; } /// Get the world body origin position. /// @return the world position of the body's origin. GetPosition() { return this.m_xf.p; } SetPosition(position) { this.SetTransformVec(position, this.GetAngle()); } SetPositionXY(x, y) { this.SetTransformXY(x, y, this.GetAngle()); } /// Get the angle in radians. /// @return the current world rotation angle in radians. GetAngle() { return this.m_sweep.a; } SetAngle(angle) { this.SetTransformVec(this.GetPosition(), angle); } /// Get the world position of the center of mass. GetWorldCenter() { return this.m_sweep.c; } /// Get the local position of the center of mass. GetLocalCenter() { return this.m_sweep.localCenter; } /// Set the linear velocity of the center of mass. /// @param v the new linear velocity of the center of mass. SetLinearVelocity(v) { if (this.m_type === exports.b2BodyType.b2_staticBody) { return; } if (b2Vec2.DotVV(v, v) > 0) { this.SetAwake(true); } this.m_linearVelocity.Copy(v); } /// Get the linear velocity of the center of mass. /// @return the linear velocity of the center of mass. GetLinearVelocity() { return this.m_linearVelocity; } /// Set the angular velocity. /// @param omega the new angular velocity in radians/second. SetAngularVelocity(w) { if (this.m_type === exports.b2BodyType.b2_staticBody) { return; } if (w * w > 0) { this.SetAwake(true); } this.m_angularVelocity = w; } /// Get the angular velocity. /// @return the angular velocity in radians/second. GetAngularVelocity() { return this.m_angularVelocity; } GetDefinition(bd) { bd.type = this.GetType(); bd.allowSleep = this.m_autoSleepFlag; bd.angle = this.GetAngle(); bd.angularDamping = this.m_angularDamping; bd.gravityScale = this.m_gravityScale; bd.angularVelocity = this.m_angularVelocity; bd.fixedRotation = this.m_fixedRotationFlag; bd.bullet = this.m_bulletFlag; bd.awake = this.m_awakeFlag; bd.linearDamping = this.m_linearDamping; bd.linearVelocity.Copy(this.GetLinearVelocity()); bd.position.Copy(this.GetPosition()); bd.userData = this.GetUserData(); return bd; } /// 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, point, wake = true) { if (this.m_type !== exports.b2BodyType.b2_dynamicBody) { return; } if (wake && !this.m_awakeFlag) { this.SetAwake(true); } // Don't accumulate a force if the body is sleeping. if (this.m_awakeFlag) { this.m_force.x += force.x; this.m_force.y += force.y; this.m_torque += ((point.x - this.m_sweep.c.x) * force.y - (point.y - this.m_sweep.c.y) * force.x); } } /// 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, wake = true) { if (this.m_type !== exports.b2BodyType.b2_dynamicBody) { return; } if (wake && !this.m_awakeFlag) { this.SetAwake(true); } // Don't accumulate a force if the body is sleeping. if (this.m_awakeFlag) { this.m_force.x += force.x; this.m_force.y += force.y; } } /// Apply a torque. This affects the angular velocity /// without affecting the linear velocity of the center of mass. /// @param torque about the z-axis (out of the screen), usually in N-m. /// @param wake also wake up the body ApplyTorque(torque, wake = true) { if (this.m_type !== exports.b2BodyType.b2_dynamicBody) { return; } if (wake && !this.m_awakeFlag) { 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, point, wake = true) { if (this.m_type !== exports.b2BodyType.b2_dynamicBody) { return; } if (wake && !this.m_awakeFlag) { this.SetAwake(true); } // Don't accumulate a force if the body is sleeping. if (this.m_awakeFlag) { this.m_linearVelocity.x += this.m_invMass * impulse.x; this.m_linearVelocity.y += this.m_invMass * impulse.y; this.m_angularVelocity += this.m_invI * ((point.x - this.m_sweep.c.x) * impulse.y - (point.y - this.m_sweep.c.y) * impulse.x); } } /// Apply an impulse at the center of gravity. This immediately modifies the velocity. /// @param impulse the world impulse vector, usually in N-seconds or kg-m/s. /// @param wake also wake up the body ApplyLinearImpulseToCenter(impulse, wake = true) { if (this.m_type !== exports.b2BodyType.b2_dynamicBody) { return; } if (wake && !this.m_awakeFlag) { this.SetAwake(true); } // Don't accumulate a force if the body is sleeping. if (this.m_awakeFlag) { this.m_linearVelocity.x += this.m_invMass * impulse.x; this.m_linearVelocity.y += this.m_invMass * impulse.y; } } /// 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, wake = true) { if (this.m_type !== exports.b2BodyType.b2_dynamicBody) { return; } if (wake && !this.m_awakeFlag) { this.SetAwake(true); } // Don't accumulate a force if the body is sleeping. if (this.m_awakeFlag) { this.m_angularVelocity += this.m_invI * impulse; } } /// Get the total mass of the body. /// @return the mass, usually in kilograms (kg). GetMass() { 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() { return this.m_I + this.m_mass * b2Vec2.DotVV(this.m_sweep.localCenter, this.m_sweep.localCenter); } /// Get the mass data of the body. /// @return a struct containing the mass, inertia and center of the body. GetMassData(data) { data.mass = this.m_mass; data.I = this.m_I + this.m_mass * b2Vec2.DotVV(this.m_sweep.localCenter, this.m_sweep.localCenter); data.center.Copy(this.m_sweep.localCenter); return data; } SetMassData(massData) { if (this.m_world.IsLocked()) { throw new Error(); } if (this.m_type !== exports.b2BodyType.b2_dynamicBody) { return; } this.m_invMass = 0; this.m_I = 0; this.m_invI = 0; this.m_mass = massData.mass; if (this.m_mass <= 0) { this.m_mass = 1; } this.m_invMass = 1 / this.m_mass; if (massData.I > 0 && !this.m_fixedRotationFlag) { this.m_I = massData.I - this.m_mass * b2Vec2.DotVV(massData.center, massData.center); // DEBUG: b2Assert(this.m_I > 0); this.m_invI = 1 / this.m_I; } // Move center of mass. const oldCenter = b2Body.SetMassData_s_oldCenter.Copy(this.m_sweep.c); this.m_sweep.localCenter.Copy(massData.center); b2Transform.MulXV(this.m_xf, this.m_sweep.localCenter, this.m_sweep.c); this.m_sweep.c0.Copy(this.m_sweep.c); // Update center of mass velocity. b2Vec2.AddVCrossSV(this.m_linearVelocity, this.m_angularVelocity, b2Vec2.SubVV(this.m_sweep.c, oldCenter, b2Vec2.s_t0), this.m_linearVelocity); } ResetMassData() { // Compute mass data from shapes. Each shape has its own density. this.m_mass = 0; this.m_invMass = 0; this.m_I = 0; this.m_invI = 0; this.m_sweep.localCenter.SetZero(); // Static and kinematic bodies have zero mass. if (this.m_type === exports.b2BodyType.b2_staticBody || this.m_type === exports.b2BodyType.b2_kinematicBody) { this.m_sweep.c0.Copy(this.m_xf.p); this.m_sweep.c.Copy(this.m_xf.p); this.m_sweep.a0 = this.m_sweep.a; return; } // DEBUG: b2Assert(this.m_type === b2BodyType.b2_dynamicBody); // Accumulate mass over all fixtures. const localCenter = b2Body.ResetMassData_s_localCenter.SetZero(); for (let f = this.m_fixtureList; f; f = f.m_next) { if (f.m_density === 0) { continue; } const massData = f.GetMassData(b2Body.ResetMassData_s_massData); this.m_mass += massData.mass; localCenter.x += massData.center.x * massData.mass; localCenter.y += massData.center.y * massData.mass; this.m_I += massData.I; } // Compute center of mass. if (this.m_mass > 0) { this.m_invMass = 1 / this.m_mass; localCenter.x *= this.m_invMass; localCenter.y *= this.m_invMass; } if (this.m_I > 0 && !this.m_fixedRotationFlag) { // Center the inertia about the center of mass. this.m_I -= this.m_mass * b2Vec2.DotVV(localCenter, localCenter); // DEBUG: b2Assert(this.m_I > 0); this.m_invI = 1 / this.m_I; } else { this.m_I = 0; this.m_invI = 0; } // Move center of mass. const oldCenter = b2Body.ResetMassData_s_oldCenter.Copy(this.m_sweep.c); this.m_sweep.localCenter.Copy(localCenter); b2Transform.MulXV(this.m_xf, this.m_sweep.localCenter, this.m_sweep.c); this.m_sweep.c0.Copy(this.m_sweep.c); // Update center of mass velocity. b2Vec2.AddVCrossSV(this.m_linearVelocity, this.m_angularVelocity, b2Vec2.SubVV(this.m_sweep.c, oldCenter, b2Vec2.s_t0), this.m_linearVelocity); } /// Get the world coordinates of a point given the local coordinates. /// @param localPoint a point on the body measured relative the the body's origin. /// @return the same point expressed in world coordinates. GetWorldPoint(localPoint, out) { return b2Transform.MulXV(this.m_xf, localPoint, out); } /// Get the world coordinates of a vector given the local coordinates. /// @param localVector a vector fixed in the body. /// @return the same vector expressed in world coordinates. GetWorldVector(localVector, out) { return b2Rot.MulRV(this.m_xf.q, localVector, out); } /// Gets a local point relative to the body's origin given a world point. /// @param a point in world coordinates. /// @return the corresponding local point relative to the body's origin. GetLocalPoint(worldPoint, out) { return b2Transform.MulTXV(this.m_xf, worldPoint, out); } /// Gets a local vector given a world vector. /// @param a vector in world coordinates. /// @return the corresponding local vector. GetLocalVector(worldVector, out) { return b2Rot.MulTRV(this.m_xf.q, worldVector, out); } /// Get the world linear velocity of a world point attached to this body. /// @param a point in world coordinates. /// @return the world velocity of a point. GetLinearVelocityFromWorldPoint(worldPoint, out) { return b2Vec2.AddVCrossSV(this.m_linearVelocity, this.m_angularVelocity, b2Vec2.SubVV(worldPoint, this.m_sweep.c, b2Vec2.s_t0), out); } /// Get the world velocity of a local point. /// @param a point in local coordinates. /// @return the world velocity of a point. GetLinearVelocityFromLocalPoint(localPoint, out) { return this.GetLinearVelocityFromWorldPoint(this.GetWorldPoint(localPoint, out), out); } /// Get the linear damping of the body. GetLinearDamping() { return this.m_linearDamping; } /// Set the linear damping of the body. SetLinearDamping(linearDamping) { this.m_linearDamping = linearDamping; } /// Get the angular damping of the body. GetAngularDamping() { return this.m_angularDamping; } /// Set the angular damping of the body. SetAngularDamping(angularDamping) { this.m_angularDamping = angularDamping; } /// Get the gravity scale of the body. GetGravityScale() { return this.m_gravityScale; } /// Set the gravity scale of the body. SetGravityScale(scale) { this.m_gravityScale = scale; } /// Set the type of this body. This may alter the mass and velocity. SetType(type) { if (this.m_world.IsLocked()) { throw new Error(); } if (this.m_type === type) { return; } this.m_type = type; this.ResetMassData(); if (this.m_type === exports.b2BodyType.b2_staticBody) { this.m_linearVelocity.SetZero(); this.m_angularVelocity = 0; this.m_sweep.a0 = this.m_sweep.a; this.m_sweep.c0.Copy(this.m_sweep.c); this.m_awakeFlag = false; this.SynchronizeFixtures(); } this.SetAwake(true); this.m_force.SetZero(); this.m_torque = 0; // Delete the attached contacts. let ce = this.m_contactList; while (ce) { const ce0 = ce; ce = ce.next; this.m_world.m_contactManager.Destroy(ce0.contact); } this.m_contactList = null; // Touch the proxies so that new contacts will be created (when appropriate) for (let f = this.m_fixtureList; f; f = f.m_next) { f.TouchProxies(); } } /// Get the type of this body. GetType() { return this.m_type; } /// Should this body be treated like a bullet for continuous collision detection? SetBullet(flag) { this.m_bulletFlag = flag; } /// Is this body treated like a bullet for continuous collision detection? IsBullet() { return this.m_bulletFlag; } /// You can disable sleeping on this body. If you disable sleeping, the /// body will be woken. SetSleepingAllowed(flag) { this.m_autoSleepFlag = flag; if (!flag) { this.SetAwake(true); } } /// Is this body allowed to sleep IsSleepingAllowed() { return this.m_autoSleepFlag; } /// 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) { if (this.m_type === exports.b2BodyType.b2_staticBody) { return; } if (flag) { this.m_awakeFlag = true; this.m_sleepTime = 0; } else { this.m_awakeFlag = false; this.m_sleepTime = 0; this.m_linearVelocity.SetZero(); this.m_angularVelocity = 0; this.m_force.SetZero(); this.m_torque = 0; } } /// Get the sleeping state of this body. /// @return true if the body is sleeping. IsAwake() { return this.m_awakeFlag; } /// Allow a body to be disabled. A disabled 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 disabled bodies. /// Fixtures on a disabled body are implicitly disabled and will /// not participate in collisions, ray-casts, or queries. /// Joints connected to a disabled body are implicitly disabled. /// An diabled body is still owned by a b2World object and remains /// in the body list. SetEnabled(flag) { if (this.m_world.IsLocked()) { throw new Error(); } if (flag === this.IsEnabled()) { return; } this.m_enabledFlag = flag; if (flag) { // Create all proxies. for (let f = this.m_fixtureList; f; f = f.m_next) { f.CreateProxies(); } // Contacts are created at the beginning of the next this.m_world.m_newContacts = true; } else { // Destroy all proxies. for (let f = this.m_fixtureList; f; f = f.m_next) { f.DestroyProxies(); } // Destroy the attached contacts. let ce = this.m_contactList; while (ce) { const ce0 = ce; ce = ce.next; this.m_world.m_contactManager.Destroy(ce0.contact); } this.m_contactList = null; } } /// Get the active state of the body. IsEnabled() { return this.m_enabledFlag; } /// Set this body to have fixed rotation. This causes the mass /// to be reset. SetFixedRotation(flag) { if (this.m_fixedRotationFlag === flag) { return; } this.m_fixedRotationFlag = flag; this.m_angularVelocity = 0; this.ResetMassData(); } /// Does this body have fixed rotation? IsFixedRotation() { return this.m_fixedRotationFlag; } /// Get the list of all fixtures attached to this body. GetFixtureList() { return this.m_fixtureList; } /// Get the list of all joints attached to this body. GetJointList() { return this.m_jointList; } /// Get the list of all contacts attached to this body. /// @warning this list changes during the time step and you may /// miss some collisions if you don't use b2ContactListener. GetContactList() { return this.m_contactList; } /// Get the next body in the world's body list. GetNext() { return this.m_next; } /// Get the user data pointer that was provided in the body definition. GetUserData() { return this.m_userData; } /// Set the user data. Use this to store your application specific data. SetUserData(data) { this.m_userData = data; } /// Get the parent world of this body. GetWorld() { return this.m_world; } /// Dump this body to a file Dump(log) { const bodyIndex = this.m_islandIndex; log("{\n"); log(" const bd: b2BodyDef = new b2BodyDef();\n"); let type_str = ""; switch (this.m_type) { case exports.b2BodyType.b2_staticBody: type_str = "b2BodyType.b2_staticBody"; break; case exports.b2BodyType.b2_kinematicBody: type_str = "b2BodyType.b2_kinematicBody"; break; case exports.b2BodyType.b2_dynamicBody: type_str = "b2BodyType.b2_dynamicBody"; break; } log(" bd.type = %s;\n", type_str); log(" bd.position.Set(%.15f, %.15f);\n", this.m_xf.p.x, this.m_xf.p.y); log(" bd.angle = %.15f;\n", this.m_sweep.a); log(" bd.linearVelocity.Set(%.15f, %.15f);\n", this.m_linearVelocity.x, this.m_linearVelocity.y); log(" bd.angularVelocity = %.15f;\n", this.m_angularVelocity); log(" bd.linearDamping = %.15f;\n", this.m_linearDamping); log(" bd.angularDamping = %.15f;\n", this.m_angularDamping); log(" bd.allowSleep = %s;\n", (this.m_autoSleepFlag) ? ("true") : ("false")); log(" bd.awake = %s;\n", (this.m_awakeFlag) ? ("true") : ("false")); log(" bd.fixedRotation = %s;\n", (this.m_fixedRotationFlag) ? ("true") : ("false")); log(" bd.bullet = %s;\n", (this.m_bulletFlag) ? ("true") : ("false")); log(" bd.active = %s;\n", (this.m_enabledFlag) ? ("true") : ("false")); log(" bd.gravityScale = %.15f;\n", this.m_gravityScale); log("\n"); log(" bodies[%d] = this.m_world.CreateBody(bd);\n", this.m_islandIndex); log("\n"); for (let f = this.m_fixtureList; f; f = f.m_next) { log(" {\n"); f.Dump(log, bodyIndex); log(" }\n"); } log("}\n"); } SynchronizeFixtures() { if (this.m_awakeFlag) { const xf1 = b2Body.SynchronizeFixtures_s_xf1; xf1.q.SetAngle(this.m_sweep.a0); b2Rot.MulRV(xf1.q, this.m_sweep.localCenter, xf1.p); b2Vec2.SubVV(this.m_sweep.c0, xf1.p, xf1.p); for (let f = this.m_fixtureList; f; f = f.m_next) { f.SynchronizeProxies(xf1, this.m_xf); } } else { for (let f = this.m_fixtureList; f; f = f.m_next) { f.SynchronizeProxies(this.m_xf, this.m_xf); } } } SynchronizeTransform() { this.m_xf.q.SetAngle(this.m_sweep.a); b2Rot.MulRV(this.m_xf.q, this.m_sweep.localCenter, this.m_xf.p); b2Vec2.SubVV(this.m_sweep.c, this.m_xf.p, this.m_xf.p); } // This is used to prevent connected bodies from colliding. // It may lie, depending on the collideConnected flag. ShouldCollide(other) { // At least one body should be dynamic or kinematic. if (this.m_type === exports.b2BodyType.b2_staticBody && other.m_type === exports.b2BodyType.b2_staticBody) { return false; } return this.ShouldCollideConnected(other); } ShouldCollideConnected(other) { // Does a joint prevent collision? for (let jn = this.m_jointList; jn; jn = jn.next) { if (jn.other === other) { if (!jn.joint.m_collideConnected) { return false; } } } return true; } Advance(alpha) { // Advance to the new safe time. This doesn't sync the broad-phase. this.m_sweep.Advance(alpha); this.m_sweep.c.Copy(this.m_sweep.c0); this.m_sweep.a = this.m_sweep.a0; this.m_xf.q.SetAngle(this.m_sweep.a); b2Rot.MulRV(this.m_xf.q, this.m_sweep.localCenter, this.m_xf.p); b2Vec2.SubVV(this.m_sweep.c, this.m_xf.p, this.m_xf.p); } } /// Creates a fixture from a shape and attach it to this body. /// This is a convenience function. Use b2FixtureDef if you need to set parameters /// like friction, restitution, user data, or filtering. /// If the density is non-zero, this function automatically updates the mass of the body. /// @param shape the shape to be cloned. /// @param density the shape density (set to zero for static bodies). /// @warning This function is locked during callbacks. b2Body.CreateFixtureShapeDensity_s_def = new b2FixtureDef(); /// 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. /// @param massData the mass properties. b2Body.SetMassData_s_oldCenter = new b2Vec2(); /// 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. b2Body.ResetMassData_s_localCenter = new b2Vec2(); b2Body.ResetMassData_s_oldCenter = new b2Vec2(); b2Body.ResetMassData_s_massData = new b2MassData(); b2Body.SynchronizeFixtures_s_xf1 = new b2Transform(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Friction mixing law. The idea is to allow either fixture to drive the friction to zero. /// For example, anything slides on ice. function b2MixFriction(friction1, friction2) { return b2Sqrt(friction1 * friction2); } /// Restitution mixing law. The idea is allow for anything to bounce off an inelastic surface. /// For example, a superball bounces on anything. function b2MixRestitution(restitution1, restitution2) { return restitution1 > restitution2 ? restitution1 : restitution2; } /// Restitution mixing law. This picks the lowest value. function b2MixRestitutionThreshold(threshold1, threshold2) { return threshold1 < threshold2 ? threshold1 : threshold2; } class b2ContactEdge { constructor(contact) { this._other = null; ///< provides quick access to the other body attached. this.prev = null; ///< the previous contact edge in the body's contact list this.next = null; ///< the next contact edge in the body's contact list this.contact = contact; } get other() { if (this._other === null) { throw new Error(); } return this._other; } set other(value) { if (this._other !== null) { throw new Error(); } this._other = value; } Reset() { this._other = null; this.prev = null; this.next = null; } } class b2Contact { constructor() { this.m_islandFlag = false; /// Used when crawling contact graph when forming islands. this.m_touchingFlag = false; /// Set when the shapes are touching. this.m_enabledFlag = false; /// This contact can be disabled (by user) this.m_filterFlag = false; /// This contact needs filtering because a fixture filter was changed. this.m_bulletHitFlag = false; /// This bullet contact had a TOI event this.m_toiFlag = false; /// This contact has a valid TOI in m_toi this.m_prev = null; this.m_next = null; this.m_nodeA = new b2ContactEdge(this); this.m_nodeB = new b2ContactEdge(this); this.m_indexA = 0; this.m_indexB = 0; this.m_manifold = new b2Manifold(); // TODO: readonly this.m_toiCount = 0; this.m_toi = 0; this.m_friction = 0; this.m_restitution = 0; this.m_restitutionThreshold = 0; this.m_tangentSpeed = 0; this.m_oldManifold = new b2Manifold(); // TODO: readonly } GetManifold() { return this.m_manifold; } GetWorldManifold(worldManifold) { const bodyA = this.m_fixtureA.GetBody(); const bodyB = this.m_fixtureB.GetBody(); const shapeA = this.GetShapeA(); const shapeB = this.GetShapeB(); worldManifold.Initialize(this.m_manifold, bodyA.GetTransform(), shapeA.m_radius, bodyB.GetTransform(), shapeB.m_radius); } IsTouching() { return this.m_touchingFlag; } SetEnabled(flag) { this.m_enabledFlag = flag; } IsEnabled() { return this.m_enabledFlag; } GetNext() { return this.m_next; } GetFixtureA() { return this.m_fixtureA; } GetChildIndexA() { return this.m_indexA; } GetShapeA() { return this.m_fixtureA.GetShape(); } GetFixtureB() { return this.m_fixtureB; } GetChildIndexB() { return this.m_indexB; } GetShapeB() { return this.m_fixtureB.GetShape(); } FlagForFiltering() { this.m_filterFlag = true; } SetFriction(friction) { this.m_friction = friction; } GetFriction() { return this.m_friction; } ResetFriction() { this.m_friction = b2MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction); } SetRestitution(restitution) { this.m_restitution = restitution; } GetRestitution() { return this.m_restitution; } ResetRestitution() { this.m_restitution = b2MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution); } /// Override the default restitution velocity threshold mixture. You can call this in b2ContactListener::PreSolve. /// The value persists until you set or reset. SetRestitutionThreshold(threshold) { this.m_restitutionThreshold = threshold; } /// Get the restitution threshold. GetRestitutionThreshold() { return this.m_restitutionThreshold; } /// Reset the restitution threshold to the default value. ResetRestitutionThreshold() { this.m_restitutionThreshold = b2MixRestitutionThreshold(this.m_fixtureA.m_restitutionThreshold, this.m_fixtureB.m_restitutionThreshold); } SetTangentSpeed(speed) { this.m_tangentSpeed = speed; } GetTangentSpeed() { return this.m_tangentSpeed; } Reset(fixtureA, indexA, fixtureB, indexB) { this.m_islandFlag = false; this.m_touchingFlag = false; this.m_enabledFlag = true; this.m_filterFlag = false; this.m_bulletHitFlag = false; this.m_toiFlag = false; this.m_fixtureA = fixtureA; this.m_fixtureB = fixtureB; this.m_indexA = indexA; this.m_indexB = indexB; this.m_manifold.pointCount = 0; this.m_prev = null; this.m_next = null; this.m_nodeA.Reset(); this.m_nodeB.Reset(); this.m_toiCount = 0; this.m_friction = b2MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction); this.m_restitution = b2MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution); this.m_restitutionThreshold = b2MixRestitutionThreshold(this.m_fixtureA.m_restitutionThreshold, this.m_fixtureB.m_restitutionThreshold); } Update(listener) { const tManifold = this.m_oldManifold; this.m_oldManifold = this.m_manifold; this.m_manifold = tManifold; // Re-enable this contact. this.m_enabledFlag = true; let touching = false; const wasTouching = this.m_touchingFlag; const sensorA = this.m_fixtureA.IsSensor(); const sensorB = this.m_fixtureB.IsSensor(); const sensor = sensorA || sensorB; const bodyA = this.m_fixtureA.GetBody(); const bodyB = this.m_fixtureB.GetBody(); const xfA = bodyA.GetTransform(); const xfB = bodyB.GetTransform(); // Is this contact a sensor? if (sensor) { const shapeA = this.GetShapeA(); const shapeB = this.GetShapeB(); touching = b2TestOverlapShape(shapeA, this.m_indexA, shapeB, this.m_indexB, xfA, xfB); // Sensors don't generate manifolds. this.m_manifold.pointCount = 0; } else { this.Evaluate(this.m_manifold, xfA, xfB); touching = this.m_manifold.pointCount > 0; // Match old contact ids to new contact ids and copy the // stored impulses to warm start the solver. for (let i = 0; i < this.m_manifold.pointCount; ++i) { const mp2 = this.m_manifold.points[i]; mp2.normalImpulse = 0; mp2.tangentImpulse = 0; const id2 = mp2.id; for (let j = 0; j < this.m_oldManifold.pointCount; ++j) { const mp1 = this.m_oldManifold.points[j]; if (mp1.id.key === id2.key) { mp2.normalImpulse = mp1.normalImpulse; mp2.tangentImpulse = mp1.tangentImpulse; break; } } } if (touching !== wasTouching) { bodyA.SetAwake(true); bodyB.SetAwake(true); } } this.m_touchingFlag = touching; if (!wasTouching && touching && listener) { listener.BeginContact(this); } if (wasTouching && !touching && listener) { listener.EndContact(this); } if (!sensor && touching && listener) { listener.PreSolve(this, this.m_oldManifold); } } ComputeTOI(sweepA, sweepB) { const input = b2Contact.ComputeTOI_s_input; input.proxyA.SetShape(this.GetShapeA(), this.m_indexA); input.proxyB.SetShape(this.GetShapeB(), this.m_indexB); input.sweepA.Copy(sweepA); input.sweepB.Copy(sweepB); input.tMax = b2_linearSlop; const output = b2Contact.ComputeTOI_s_output; b2TimeOfImpact(output, input); return output.t; } } b2Contact.ComputeTOI_s_input = new b2TOIInput(); b2Contact.ComputeTOI_s_output = new b2TOIOutput(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2ChainAndCircleContact extends b2Contact { static Create() { return new b2ChainAndCircleContact(); } static Destroy(contact) { } Evaluate(manifold, xfA, xfB) { const edge = b2ChainAndCircleContact.Evaluate_s_edge; this.GetShapeA().GetChildEdge(edge, this.m_indexA); b2CollideEdgeAndCircle(manifold, edge, xfA, this.GetShapeB(), xfB); } } b2ChainAndCircleContact.Evaluate_s_edge = new b2EdgeShape(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2ChainAndPolygonContact extends b2Contact { static Create() { return new b2ChainAndPolygonContact(); } static Destroy(contact) { } Evaluate(manifold, xfA, xfB) { const edge = b2ChainAndPolygonContact.Evaluate_s_edge; this.GetShapeA().GetChildEdge(edge, this.m_indexA); b2CollideEdgeAndPolygon(manifold, edge, xfA, this.GetShapeB(), xfB); } } b2ChainAndPolygonContact.Evaluate_s_edge = new b2EdgeShape(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2CircleContact extends b2Contact { static Create() { return new b2CircleContact(); } static Destroy(contact) { } Evaluate(manifold, xfA, xfB) { b2CollideCircles(manifold, this.GetShapeA(), xfA, this.GetShapeB(), xfB); } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2PolygonContact extends b2Contact { static Create() { return new b2PolygonContact(); } static Destroy(contact) { } Evaluate(manifold, xfA, xfB) { b2CollidePolygons(manifold, this.GetShapeA(), xfA, this.GetShapeB(), xfB); } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2PolygonAndCircleContact extends b2Contact { static Create() { return new b2PolygonAndCircleContact(); } static Destroy(contact) { } Evaluate(manifold, xfA, xfB) { b2CollidePolygonAndCircle(manifold, this.GetShapeA(), xfA, this.GetShapeB(), xfB); } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2EdgeAndCircleContact extends b2Contact { static Create() { return new b2EdgeAndCircleContact(); } static Destroy(contact) { } Evaluate(manifold, xfA, xfB) { b2CollideEdgeAndCircle(manifold, this.GetShapeA(), xfA, this.GetShapeB(), xfB); } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2EdgeAndPolygonContact extends b2Contact { static Create() { return new b2EdgeAndPolygonContact(); } static Destroy(contact) { } Evaluate(manifold, xfA, xfB) { b2CollideEdgeAndPolygon(manifold, this.GetShapeA(), xfA, this.GetShapeB(), xfB); } } // DEBUG: import { b2Assert } from "../common/b2_settings.js"; class b2ContactRegister { constructor() { this.pool = []; this.createFcn = null; this.destroyFcn = null; this.primary = false; } } class b2ContactFactory { constructor() { this.m_registers = []; this.InitializeRegisters(); } AddType(createFcn, destroyFcn, typeA, typeB) { const pool = []; function poolCreateFcn() { return pool.pop() || createFcn(); } function poolDestroyFcn(contact) { pool.push(contact); } this.m_registers[typeA][typeB].pool = pool; this.m_registers[typeA][typeB].createFcn = poolCreateFcn; // createFcn; this.m_registers[typeA][typeB].destroyFcn = poolDestroyFcn; // destroyFcn; this.m_registers[typeA][typeB].primary = true; if (typeA !== typeB) { this.m_registers[typeB][typeA].pool = pool; this.m_registers[typeB][typeA].createFcn = poolCreateFcn; // createFcn; this.m_registers[typeB][typeA].destroyFcn = poolDestroyFcn; // destroyFcn; this.m_registers[typeB][typeA].primary = false; } } InitializeRegisters() { for (let i = 0; i < exports.b2ShapeType.e_shapeTypeCount; i++) { this.m_registers[i] = []; for (let j = 0; j < exports.b2ShapeType.e_shapeTypeCount; j++) { this.m_registers[i][j] = new b2ContactRegister(); } } this.AddType(b2CircleContact.Create, b2CircleContact.Destroy, exports.b2ShapeType.e_circleShape, exports.b2ShapeType.e_circleShape); this.AddType(b2PolygonAndCircleContact.Create, b2PolygonAndCircleContact.Destroy, exports.b2ShapeType.e_polygonShape, exports.b2ShapeType.e_circleShape); this.AddType(b2PolygonContact.Create, b2PolygonContact.Destroy, exports.b2ShapeType.e_polygonShape, exports.b2ShapeType.e_polygonShape); this.AddType(b2EdgeAndCircleContact.Create, b2EdgeAndCircleContact.Destroy, exports.b2ShapeType.e_edgeShape, exports.b2ShapeType.e_circleShape); this.AddType(b2EdgeAndPolygonContact.Create, b2EdgeAndPolygonContact.Destroy, exports.b2ShapeType.e_edgeShape, exports.b2ShapeType.e_polygonShape); this.AddType(b2ChainAndCircleContact.Create, b2ChainAndCircleContact.Destroy, exports.b2ShapeType.e_chainShape, exports.b2ShapeType.e_circleShape); this.AddType(b2ChainAndPolygonContact.Create, b2ChainAndPolygonContact.Destroy, exports.b2ShapeType.e_chainShape, exports.b2ShapeType.e_polygonShape); } Create(fixtureA, indexA, fixtureB, indexB) { const typeA = fixtureA.GetType(); const typeB = fixtureB.GetType(); // DEBUG: b2Assert(0 <= typeA && typeA < b2ShapeType.e_shapeTypeCount); // DEBUG: b2Assert(0 <= typeB && typeB < b2ShapeType.e_shapeTypeCount); const reg = this.m_registers[typeA][typeB]; if (reg.createFcn) { const c = reg.createFcn(); if (reg.primary) { c.Reset(fixtureA, indexA, fixtureB, indexB); } else { c.Reset(fixtureB, indexB, fixtureA, indexA); } return c; } else { return null; } } Destroy(contact) { const typeA = contact.m_fixtureA.GetType(); const typeB = contact.m_fixtureB.GetType(); // DEBUG: b2Assert(0 <= typeA && typeB < b2ShapeType.e_shapeTypeCount); // DEBUG: b2Assert(0 <= typeA && typeB < b2ShapeType.e_shapeTypeCount); const reg = this.m_registers[typeA][typeB]; if (reg.destroyFcn) { reg.destroyFcn(contact); } } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Joints and fixtures are destroyed when their associated /// body is destroyed. Implement this listener so that you /// may nullify references to these joints and shapes. class b2DestructionListener { /// Called when any joint is about to be destroyed due /// to the destruction of one of its attached bodies. SayGoodbyeJoint(joint) { } /// Called when any fixture is about to be destroyed due /// to the destruction of its parent body. SayGoodbyeFixture(fixture) { } } /// Implement this class to provide collision filtering. In other words, you can implement /// this class if you want finer control over contact creation. class b2ContactFilter { /// Return true if contact calculations should be performed between these two shapes. /// @warning for performance reasons this is only called when the AABBs begin to overlap. ShouldCollide(fixtureA, fixtureB) { const bodyA = fixtureA.GetBody(); const bodyB = fixtureB.GetBody(); // At least one body should be dynamic or kinematic. if (bodyB.GetType() === exports.b2BodyType.b2_staticBody && bodyA.GetType() === exports.b2BodyType.b2_staticBody) { return false; } // Does a joint prevent collision? if (!bodyB.ShouldCollideConnected(bodyA)) { return false; } const filter1 = fixtureA.GetFilterData(); const filter2 = fixtureB.GetFilterData(); if (filter1.groupIndex === filter2.groupIndex && filter1.groupIndex !== 0) { return (filter1.groupIndex > 0); } const collide = (((filter1.maskBits & filter2.categoryBits) !== 0) && ((filter1.categoryBits & filter2.maskBits) !== 0)); return collide; } } b2ContactFilter.b2_defaultFilter = new b2ContactFilter(); /// Contact impulses for reporting. Impulses are used instead of forces because /// sub-step forces may approach infinity for rigid body collisions. These /// match up one-to-one with the contact points in b2Manifold. class b2ContactImpulse { constructor() { this.normalImpulses = b2MakeNumberArray(b2_maxManifoldPoints); this.tangentImpulses = b2MakeNumberArray(b2_maxManifoldPoints); this.count = 0; } } /// Implement this class to get contact information. You can use these results for /// things like sounds and game logic. You can also get contact results by /// traversing the contact lists after the time step. However, you might miss /// some contacts because continuous physics leads to sub-stepping. /// Additionally you may receive multiple callbacks for the same contact in a /// single time step. /// You should strive to make your callbacks efficient because there may be /// many callbacks per time step. /// @warning You cannot create/destroy Box2D entities inside these callbacks. class b2ContactListener { /// Called when two fixtures begin to touch. BeginContact(contact) { } /// Called when two fixtures cease to touch. EndContact(contact) { } /// This is called after a contact is updated. This allows you to inspect a /// contact before it goes to the solver. If you are careful, you can modify the /// contact manifold (e.g. disable contact). /// A copy of the old manifold is provided so that you can detect changes. /// Note: this is called only for awake bodies. /// Note: this is called even when the number of contact points is zero. /// Note: this is not called for sensors. /// Note: if you set the number of contact points to zero, you will not /// get an EndContact callback. However, you may get a BeginContact callback /// the next step. PreSolve(contact, oldManifold) { } /// This lets you inspect a contact after the solver is finished. This is useful /// for inspecting impulses. /// Note: the contact manifold does not include time of impact impulses, which can be /// arbitrarily large if the sub-step is small. Hence the impulse is provided explicitly /// in a separate data structure. /// Note: this is only called for contacts that are touching, solid, and awake. PostSolve(contact, impulse) { } } b2ContactListener.b2_defaultListener = new b2ContactListener(); /// Callback class for AABB queries. /// See b2World::Query class b2QueryCallback { /// Called for each fixture found in the query AABB. /// @return false to terminate the query. ReportFixture(fixture) { return true; } } /// Callback class for ray casts. /// See b2World::RayCast class b2RayCastCallback { /// Called for each fixture found in the query. You control how the ray cast /// proceeds by returning a float: /// return -1: ignore this fixture and continue /// return 0: terminate the ray cast /// return fraction: clip the ray to this point /// return 1: don't clip the ray and continue /// @param fixture the fixture hit by the ray /// @param point the point of initial intersection /// @param normal the normal vector at the point of intersection /// @return -1 to filter, 0 to terminate, fraction to clip the ray for /// closest hit, 1 to continue ReportFixture(fixture, point, normal, fraction) { return fraction; } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ // Delegate of b2World. class b2ContactManager { constructor() { this.m_broadPhase = new b2BroadPhase(); this.m_contactList = null; this.m_contactCount = 0; this.m_contactFilter = b2ContactFilter.b2_defaultFilter; this.m_contactListener = b2ContactListener.b2_defaultListener; this.m_contactFactory = new b2ContactFactory(); } // Broad-phase callback. AddPair(proxyA, proxyB) { // DEBUG: b2Assert(proxyA instanceof b2FixtureProxy); // DEBUG: b2Assert(proxyB instanceof b2FixtureProxy); let fixtureA = proxyA.fixture; let fixtureB = proxyB.fixture; let indexA = proxyA.childIndex; let indexB = proxyB.childIndex; let bodyA = fixtureA.GetBody(); let bodyB = fixtureB.GetBody(); // Are the fixtures on the same body? if (bodyA === bodyB) { return; } // TODO_ERIN use a hash table to remove a potential bottleneck when both // bodies have a lot of contacts. // Does a contact already exist? let edge = bodyB.GetContactList(); while (edge) { if (edge.other === bodyA) { const fA = edge.contact.GetFixtureA(); const fB = edge.contact.GetFixtureB(); const iA = edge.contact.GetChildIndexA(); const iB = edge.contact.GetChildIndexB(); if (fA === fixtureA && fB === fixtureB && iA === indexA && iB === indexB) { // A contact already exists. return; } if (fA === fixtureB && fB === fixtureA && iA === indexB && iB === indexA) { // A contact already exists. return; } } edge = edge.next; } // Check user filtering. if (this.m_contactFilter && !this.m_contactFilter.ShouldCollide(fixtureA, fixtureB)) { return; } // Call the factory. const c = this.m_contactFactory.Create(fixtureA, indexA, fixtureB, indexB); if (c === null) { return; } // Contact creation may swap fixtures. fixtureA = c.GetFixtureA(); fixtureB = c.GetFixtureB(); indexA = c.GetChildIndexA(); indexB = c.GetChildIndexB(); bodyA = fixtureA.m_body; bodyB = fixtureB.m_body; // Insert into the world. c.m_prev = null; c.m_next = this.m_contactList; if (this.m_contactList !== null) { this.m_contactList.m_prev = c; } this.m_contactList = c; // Connect to island graph. // Connect to body A c.m_nodeA.other = bodyB; c.m_nodeA.prev = null; c.m_nodeA.next = bodyA.m_contactList; if (bodyA.m_contactList !== null) { bodyA.m_contactList.prev = c.m_nodeA; } bodyA.m_contactList = c.m_nodeA; // Connect to body B c.m_nodeB.other = bodyA; c.m_nodeB.prev = null; c.m_nodeB.next = bodyB.m_contactList; if (bodyB.m_contactList !== null) { bodyB.m_contactList.prev = c.m_nodeB; } bodyB.m_contactList = c.m_nodeB; ++this.m_contactCount; } FindNewContacts() { this.m_broadPhase.UpdatePairs((proxyA, proxyB) => { this.AddPair(proxyA, proxyB); }); } Destroy(c) { const fixtureA = c.GetFixtureA(); const fixtureB = c.GetFixtureB(); const bodyA = fixtureA.GetBody(); const bodyB = fixtureB.GetBody(); if (this.m_contactListener && c.IsTouching()) { this.m_contactListener.EndContact(c); } // Remove from the world. 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 (c === this.m_contactList) { this.m_contactList = c.m_next; } // Remove from body 1 if (c.m_nodeA.prev) { c.m_nodeA.prev.next = c.m_nodeA.next; } if (c.m_nodeA.next) { c.m_nodeA.next.prev = c.m_nodeA.prev; } if (c.m_nodeA === bodyA.m_contactList) { bodyA.m_contactList = c.m_nodeA.next; } // Remove from body 2 if (c.m_nodeB.prev) { c.m_nodeB.prev.next = c.m_nodeB.next; } if (c.m_nodeB.next) { c.m_nodeB.next.prev = c.m_nodeB.prev; } if (c.m_nodeB === bodyB.m_contactList) { bodyB.m_contactList = c.m_nodeB.next; } // moved this from b2ContactFactory:Destroy if (c.m_manifold.pointCount > 0 && !fixtureA.IsSensor() && !fixtureB.IsSensor()) { fixtureA.GetBody().SetAwake(true); fixtureB.GetBody().SetAwake(true); } // Call the factory. this.m_contactFactory.Destroy(c); --this.m_contactCount; } // This is the top level collision call for the time step. Here // all the narrow phase collision is processed for the world // contact list. Collide() { // Update awake contacts. let c = this.m_contactList; while (c) { const fixtureA = c.GetFixtureA(); const fixtureB = c.GetFixtureB(); const indexA = c.GetChildIndexA(); const indexB = c.GetChildIndexB(); const bodyA = fixtureA.GetBody(); const bodyB = fixtureB.GetBody(); // Is this contact flagged for filtering? if (c.m_filterFlag) { // Check user filtering. if (this.m_contactFilter && !this.m_contactFilter.ShouldCollide(fixtureA, fixtureB)) { const cNuke = c; c = cNuke.m_next; this.Destroy(cNuke); continue; } // Clear the filtering flag. c.m_filterFlag = false; } const activeA = bodyA.IsAwake() && bodyA.m_type !== exports.b2BodyType.b2_staticBody; const activeB = bodyB.IsAwake() && bodyB.m_type !== exports.b2BodyType.b2_staticBody; // At least one body must be awake and it must be dynamic or kinematic. if (!activeA && !activeB) { c = c.m_next; continue; } const treeNodeA = fixtureA.m_proxies[indexA].treeNode; const treeNodeB = fixtureB.m_proxies[indexB].treeNode; const overlap = b2TestOverlapAABB(treeNodeA.aabb, treeNodeB.aabb); // Here we destroy contacts that cease to overlap in the broad-phase. if (!overlap) { const cNuke = c; c = cNuke.m_next; this.Destroy(cNuke); continue; } // The contact persists. c.Update(this.m_contactListener); c = c.m_next; } } } /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Profiling data. Times are in milliseconds. class b2Profile { constructor() { this.step = 0; this.collide = 0; this.solve = 0; this.solveInit = 0; this.solveVelocity = 0; this.solvePosition = 0; this.broadphase = 0; this.solveTOI = 0; } Reset() { this.step = 0; this.collide = 0; this.solve = 0; this.solveInit = 0; this.solveVelocity = 0; this.solvePosition = 0; this.broadphase = 0; this.solveTOI = 0; return this; } } /// This is an internal structure. class b2TimeStep { constructor() { this.dt = 0; // time step this.inv_dt = 0; // inverse time step (0 if dt == 0). this.dtRatio = 0; // dt * inv_dt0 this.velocityIterations = 0; this.positionIterations = 0; this.warmStarting = false; } Copy(step) { this.dt = step.dt; this.inv_dt = step.inv_dt; this.dtRatio = step.dtRatio; this.positionIterations = step.positionIterations; this.velocityIterations = step.velocityIterations; this.warmStarting = step.warmStarting; return this; } } class b2Position { constructor() { this.c = new b2Vec2(); this.a = 0; } static MakeArray(length) { return b2MakeArray(length, (i) => new b2Position()); } } class b2Velocity { constructor() { this.v = new b2Vec2(); this.w = 0; } static MakeArray(length) { return b2MakeArray(length, (i) => new b2Velocity()); } } class b2SolverData { constructor() { this.step = new b2TimeStep(); } } /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ // Solver debugging is normally disabled because the block solver sometimes has to deal with a poorly conditioned effective mass matrix. // #define B2_DEBUG_SOLVER 0 exports.g_blockSolve = true; function get_g_blockSolve() { return exports.g_blockSolve; } function set_g_blockSolve(value) { exports.g_blockSolve = value; } class b2VelocityConstraintPoint { constructor() { this.rA = new b2Vec2(); this.rB = new b2Vec2(); this.normalImpulse = 0; this.tangentImpulse = 0; this.normalMass = 0; this.tangentMass = 0; this.velocityBias = 0; } static MakeArray(length) { return b2MakeArray(length, (i) => new b2VelocityConstraintPoint()); } } class b2ContactVelocityConstraint { constructor() { this.points = b2VelocityConstraintPoint.MakeArray(b2_maxManifoldPoints); this.normal = new b2Vec2(); this.tangent = new b2Vec2(); this.normalMass = new b2Mat22(); this.K = new b2Mat22(); this.indexA = 0; this.indexB = 0; this.invMassA = 0; this.invMassB = 0; this.invIA = 0; this.invIB = 0; this.friction = 0; this.restitution = 0; this.threshold = 0; this.tangentSpeed = 0; this.pointCount = 0; this.contactIndex = 0; } static MakeArray(length) { return b2MakeArray(length, (i) => new b2ContactVelocityConstraint()); } } class b2ContactPositionConstraint { constructor() { this.localPoints = b2Vec2.MakeArray(b2_maxManifoldPoints); this.localNormal = new b2Vec2(); this.localPoint = new b2Vec2(); this.indexA = 0; this.indexB = 0; this.invMassA = 0; this.invMassB = 0; this.localCenterA = new b2Vec2(); this.localCenterB = new b2Vec2(); this.invIA = 0; this.invIB = 0; this.type = exports.b2ManifoldType.e_unknown; this.radiusA = 0; this.radiusB = 0; this.pointCount = 0; } static MakeArray(length) { return b2MakeArray(length, (i) => new b2ContactPositionConstraint()); } } class b2ContactSolverDef { constructor() { this.step = new b2TimeStep(); this.count = 0; } } class b2PositionSolverManifold { constructor() { this.normal = new b2Vec2(); this.point = new b2Vec2(); this.separation = 0; } Initialize(pc, xfA, xfB, index) { const pointA = b2PositionSolverManifold.Initialize_s_pointA; const pointB = b2PositionSolverManifold.Initialize_s_pointB; const planePoint = b2PositionSolverManifold.Initialize_s_planePoint; const clipPoint = b2PositionSolverManifold.Initialize_s_clipPoint; // DEBUG: b2Assert(pc.pointCount > 0); switch (pc.type) { case exports.b2ManifoldType.e_circles: { // b2Vec2 pointA = b2Mul(xfA, pc->localPoint); b2Transform.MulXV(xfA, pc.localPoint, pointA); // b2Vec2 pointB = b2Mul(xfB, pc->localPoints[0]); b2Transform.MulXV(xfB, pc.localPoints[0], pointB); // normal = pointB - pointA; // normal.Normalize(); b2Vec2.SubVV(pointB, pointA, this.normal).SelfNormalize(); // point = 0.5f * (pointA + pointB); b2Vec2.MidVV(pointA, pointB, this.point); // separation = b2Dot(pointB - pointA, normal) - pc->radius; this.separation = b2Vec2.DotVV(b2Vec2.SubVV(pointB, pointA, b2Vec2.s_t0), this.normal) - pc.radiusA - pc.radiusB; break; } case exports.b2ManifoldType.e_faceA: { // normal = b2Mul(xfA.q, pc->localNormal); b2Rot.MulRV(xfA.q, pc.localNormal, this.normal); // b2Vec2 planePoint = b2Mul(xfA, pc->localPoint); b2Transform.MulXV(xfA, pc.localPoint, planePoint); // b2Vec2 clipPoint = b2Mul(xfB, pc->localPoints[index]); b2Transform.MulXV(xfB, pc.localPoints[index], clipPoint); // separation = b2Dot(clipPoint - planePoint, normal) - pc->radius; this.separation = b2Vec2.DotVV(b2Vec2.SubVV(clipPoint, planePoint, b2Vec2.s_t0), this.normal) - pc.radiusA - pc.radiusB; // point = clipPoint; this.point.Copy(clipPoint); break; } case exports.b2ManifoldType.e_faceB: { // normal = b2Mul(xfB.q, pc->localNormal); b2Rot.MulRV(xfB.q, pc.localNormal, this.normal); // b2Vec2 planePoint = b2Mul(xfB, pc->localPoint); b2Transform.MulXV(xfB, pc.localPoint, planePoint); // b2Vec2 clipPoint = b2Mul(xfA, pc->localPoints[index]); b2Transform.MulXV(xfA, pc.localPoints[index], clipPoint); // separation = b2Dot(clipPoint - planePoint, normal) - pc->radius; this.separation = b2Vec2.DotVV(b2Vec2.SubVV(clipPoint, planePoint, b2Vec2.s_t0), this.normal) - pc.radiusA - pc.radiusB; // point = clipPoint; this.point.Copy(clipPoint); // Ensure normal points from A to B // normal = -normal; this.normal.SelfNeg(); break; } } } } b2PositionSolverManifold.Initialize_s_pointA = new b2Vec2(); b2PositionSolverManifold.Initialize_s_pointB = new b2Vec2(); b2PositionSolverManifold.Initialize_s_planePoint = new b2Vec2(); b2PositionSolverManifold.Initialize_s_clipPoint = new b2Vec2(); class b2ContactSolver { constructor() { this.m_step = new b2TimeStep(); this.m_positionConstraints = b2ContactPositionConstraint.MakeArray(1024); // TODO: b2Settings this.m_velocityConstraints = b2ContactVelocityConstraint.MakeArray(1024); // TODO: b2Settings this.m_count = 0; } Initialize(def) { this.m_step.Copy(def.step); this.m_count = def.count; // TODO: if (this.m_positionConstraints.length < this.m_count) { const new_length = b2Max(this.m_positionConstraints.length * 2, this.m_count); while (this.m_positionConstraints.length < new_length) { this.m_positionConstraints[this.m_positionConstraints.length] = new b2ContactPositionConstraint(); } } // TODO: if (this.m_velocityConstraints.length < this.m_count) { const new_length = b2Max(this.m_velocityConstraints.length * 2, this.m_count); while (this.m_velocityConstraints.length < new_length) { this.m_velocityConstraints[this.m_velocityConstraints.length] = new b2ContactVelocityConstraint(); } } this.m_positions = def.positions; this.m_velocities = def.velocities; this.m_contacts = def.contacts; // Initialize position independent portions of the constraints. for (let i = 0; i < this.m_count; ++i) { const contact = this.m_contacts[i]; const fixtureA = contact.m_fixtureA; const fixtureB = contact.m_fixtureB; const shapeA = fixtureA.GetShape(); const shapeB = fixtureB.GetShape(); const radiusA = shapeA.m_radius; const radiusB = shapeB.m_radius; const bodyA = fixtureA.GetBody(); const bodyB = fixtureB.GetBody(); const manifold = contact.GetManifold(); const pointCount = manifold.pointCount; // DEBUG: b2Assert(pointCount > 0); const vc = this.m_velocityConstraints[i]; vc.friction = contact.m_friction; vc.restitution = contact.m_restitution; vc.threshold = contact.m_restitutionThreshold; vc.tangentSpeed = contact.m_tangentSpeed; vc.indexA = bodyA.m_islandIndex; vc.indexB = bodyB.m_islandIndex; vc.invMassA = bodyA.m_invMass; vc.invMassB = bodyB.m_invMass; vc.invIA = bodyA.m_invI; vc.invIB = bodyB.m_invI; vc.contactIndex = i; vc.pointCount = pointCount; vc.K.SetZero(); vc.normalMass.SetZero(); const pc = this.m_positionConstraints[i]; pc.indexA = bodyA.m_islandIndex; pc.indexB = bodyB.m_islandIndex; pc.invMassA = bodyA.m_invMass; pc.invMassB = bodyB.m_invMass; pc.localCenterA.Copy(bodyA.m_sweep.localCenter); pc.localCenterB.Copy(bodyB.m_sweep.localCenter); pc.invIA = bodyA.m_invI; pc.invIB = bodyB.m_invI; pc.localNormal.Copy(manifold.localNormal); pc.localPoint.Copy(manifold.localPoint); pc.pointCount = pointCount; pc.radiusA = radiusA; pc.radiusB = radiusB; pc.type = manifold.type; for (let j = 0; j < pointCount; ++j) { const cp = manifold.points[j]; const vcp = vc.points[j]; if (this.m_step.warmStarting) { vcp.normalImpulse = this.m_step.dtRatio * cp.normalImpulse; vcp.tangentImpulse = this.m_step.dtRatio * cp.tangentImpulse; } else { vcp.normalImpulse = 0; vcp.tangentImpulse = 0; } vcp.rA.SetZero(); vcp.rB.SetZero(); vcp.normalMass = 0; vcp.tangentMass = 0; vcp.velocityBias = 0; pc.localPoints[j].Copy(cp.localPoint); } } return this; } InitializeVelocityConstraints() { const xfA = b2ContactSolver.InitializeVelocityConstraints_s_xfA; const xfB = b2ContactSolver.InitializeVelocityConstraints_s_xfB; const worldManifold = b2ContactSolver.InitializeVelocityConstraints_s_worldManifold; const k_maxConditionNumber = 1000; for (let i = 0; i < this.m_count; ++i) { const vc = this.m_velocityConstraints[i]; const pc = this.m_positionConstraints[i]; const radiusA = pc.radiusA; const radiusB = pc.radiusB; const manifold = this.m_contacts[vc.contactIndex].GetManifold(); const indexA = vc.indexA; const indexB = vc.indexB; const mA = vc.invMassA; const mB = vc.invMassB; const iA = vc.invIA; const iB = vc.invIB; const localCenterA = pc.localCenterA; const localCenterB = pc.localCenterB; const cA = this.m_positions[indexA].c; const aA = this.m_positions[indexA].a; const vA = this.m_velocities[indexA].v; const wA = this.m_velocities[indexA].w; const cB = this.m_positions[indexB].c; const aB = this.m_positions[indexB].a; const vB = this.m_velocities[indexB].v; const wB = this.m_velocities[indexB].w; // DEBUG: b2Assert(manifold.pointCount > 0); xfA.q.SetAngle(aA); xfB.q.SetAngle(aB); b2Vec2.SubVV(cA, b2Rot.MulRV(xfA.q, localCenterA, b2Vec2.s_t0), xfA.p); b2Vec2.SubVV(cB, b2Rot.MulRV(xfB.q, localCenterB, b2Vec2.s_t0), xfB.p); worldManifold.Initialize(manifold, xfA, radiusA, xfB, radiusB); vc.normal.Copy(worldManifold.normal); b2Vec2.CrossVOne(vc.normal, vc.tangent); // compute from normal const pointCount = vc.pointCount; for (let j = 0; j < pointCount; ++j) { const vcp = vc.points[j]; // vcp->rA = worldManifold.points[j] - cA; b2Vec2.SubVV(worldManifold.points[j], cA, vcp.rA); // vcp->rB = worldManifold.points[j] - cB; b2Vec2.SubVV(worldManifold.points[j], cB, vcp.rB); const rnA = b2Vec2.CrossVV(vcp.rA, vc.normal); const rnB = b2Vec2.CrossVV(vcp.rB, vc.normal); const kNormal = mA + mB + iA * rnA * rnA + iB * rnB * rnB; vcp.normalMass = kNormal > 0 ? 1 / kNormal : 0; // b2Vec2 tangent = b2Cross(vc->normal, 1.0f); const tangent = vc.tangent; // precomputed from normal const rtA = b2Vec2.CrossVV(vcp.rA, tangent); const rtB = b2Vec2.CrossVV(vcp.rB, tangent); const kTangent = mA + mB + iA * rtA * rtA + iB * rtB * rtB; vcp.tangentMass = kTangent > 0 ? 1 / kTangent : 0; // Setup a velocity bias for restitution. vcp.velocityBias = 0; // float32 vRel = b2Dot(vc->normal, vB + b2Cross(wB, vcp->rB) - vA - b2Cross(wA, vcp->rA)); const vRel = b2Vec2.DotVV(vc.normal, b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, vcp.rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, vcp.rA, b2Vec2.s_t1), b2Vec2.s_t0)); if (vRel < -vc.threshold) { vcp.velocityBias += (-vc.restitution * vRel); } } // If we have two points, then prepare the block solver. if (vc.pointCount === 2 && exports.g_blockSolve) { const vcp1 = vc.points[0]; const vcp2 = vc.points[1]; const rn1A = b2Vec2.CrossVV(vcp1.rA, vc.normal); const rn1B = b2Vec2.CrossVV(vcp1.rB, vc.normal); const rn2A = b2Vec2.CrossVV(vcp2.rA, vc.normal); const rn2B = b2Vec2.CrossVV(vcp2.rB, vc.normal); const k11 = mA + mB + iA * rn1A * rn1A + iB * rn1B * rn1B; const k22 = mA + mB + iA * rn2A * rn2A + iB * rn2B * rn2B; const k12 = mA + mB + iA * rn1A * rn2A + iB * rn1B * rn2B; // Ensure a reasonable condition number. // float32 k_maxConditionNumber = 1000.0f; if (k11 * k11 < k_maxConditionNumber * (k11 * k22 - k12 * k12)) { // K is safe to invert. vc.K.ex.Set(k11, k12); vc.K.ey.Set(k12, k22); vc.K.GetInverse(vc.normalMass); } else { // The constraints are redundant, just use one. // TODO_ERIN use deepest? vc.pointCount = 1; } } } } WarmStart() { const P = b2ContactSolver.WarmStart_s_P; // Warm start. for (let i = 0; i < this.m_count; ++i) { const vc = this.m_velocityConstraints[i]; const indexA = vc.indexA; const indexB = vc.indexB; const mA = vc.invMassA; const iA = vc.invIA; const mB = vc.invMassB; const iB = vc.invIB; const pointCount = vc.pointCount; const vA = this.m_velocities[indexA].v; let wA = this.m_velocities[indexA].w; const vB = this.m_velocities[indexB].v; let wB = this.m_velocities[indexB].w; const normal = vc.normal; // b2Vec2 tangent = b2Cross(normal, 1.0f); const tangent = vc.tangent; // precomputed from normal for (let j = 0; j < pointCount; ++j) { const vcp = vc.points[j]; // b2Vec2 P = vcp->normalImpulse * normal + vcp->tangentImpulse * tangent; b2Vec2.AddVV(b2Vec2.MulSV(vcp.normalImpulse, normal, b2Vec2.s_t0), b2Vec2.MulSV(vcp.tangentImpulse, tangent, b2Vec2.s_t1), P); // wA -= iA * b2Cross(vcp->rA, P); wA -= iA * b2Vec2.CrossVV(vcp.rA, P); // vA -= mA * P; vA.SelfMulSub(mA, P); // wB += iB * b2Cross(vcp->rB, P); wB += iB * b2Vec2.CrossVV(vcp.rB, P); // vB += mB * P; vB.SelfMulAdd(mB, P); } // this.m_velocities[indexA].v = vA; this.m_velocities[indexA].w = wA; // this.m_velocities[indexB].v = vB; this.m_velocities[indexB].w = wB; } } SolveVelocityConstraints() { const dv = b2ContactSolver.SolveVelocityConstraints_s_dv; const dv1 = b2ContactSolver.SolveVelocityConstraints_s_dv1; const dv2 = b2ContactSolver.SolveVelocityConstraints_s_dv2; const P = b2ContactSolver.SolveVelocityConstraints_s_P; const a = b2ContactSolver.SolveVelocityConstraints_s_a; const b = b2ContactSolver.SolveVelocityConstraints_s_b; const x = b2ContactSolver.SolveVelocityConstraints_s_x; const d = b2ContactSolver.SolveVelocityConstraints_s_d; const P1 = b2ContactSolver.SolveVelocityConstraints_s_P1; const P2 = b2ContactSolver.SolveVelocityConstraints_s_P2; const P1P2 = b2ContactSolver.SolveVelocityConstraints_s_P1P2; for (let i = 0; i < this.m_count; ++i) { const vc = this.m_velocityConstraints[i]; const indexA = vc.indexA; const indexB = vc.indexB; const mA = vc.invMassA; const iA = vc.invIA; const mB = vc.invMassB; const iB = vc.invIB; const pointCount = vc.pointCount; const vA = this.m_velocities[indexA].v; let wA = this.m_velocities[indexA].w; const vB = this.m_velocities[indexB].v; let wB = this.m_velocities[indexB].w; // b2Vec2 normal = vc->normal; const normal = vc.normal; // b2Vec2 tangent = b2Cross(normal, 1.0f); const tangent = vc.tangent; // precomputed from normal const friction = vc.friction; // DEBUG: b2Assert(pointCount === 1 || pointCount === 2); // Solve tangent constraints first because non-penetration is more important // than friction. for (let j = 0; j < pointCount; ++j) { const vcp = vc.points[j]; // Relative velocity at contact // b2Vec2 dv = vB + b2Cross(wB, vcp->rB) - vA - b2Cross(wA, vcp->rA); b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, vcp.rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, vcp.rA, b2Vec2.s_t1), dv); // Compute tangent force // float32 vt = b2Dot(dv, tangent) - vc->tangentSpeed; const vt = b2Vec2.DotVV(dv, tangent) - vc.tangentSpeed; let lambda = vcp.tangentMass * (-vt); // b2Clamp the accumulated force const maxFriction = friction * vcp.normalImpulse; const newImpulse = b2Clamp(vcp.tangentImpulse + lambda, (-maxFriction), maxFriction); lambda = newImpulse - vcp.tangentImpulse; vcp.tangentImpulse = newImpulse; // Apply contact impulse // b2Vec2 P = lambda * tangent; b2Vec2.MulSV(lambda, tangent, P); // vA -= mA * P; vA.SelfMulSub(mA, P); // wA -= iA * b2Cross(vcp->rA, P); wA -= iA * b2Vec2.CrossVV(vcp.rA, P); // vB += mB * P; vB.SelfMulAdd(mB, P); // wB += iB * b2Cross(vcp->rB, P); wB += iB * b2Vec2.CrossVV(vcp.rB, P); } // Solve normal constraints if (vc.pointCount === 1 || exports.g_blockSolve === false) { for (let j = 0; j < pointCount; ++j) { const vcp = vc.points[j]; // Relative velocity at contact // b2Vec2 dv = vB + b2Cross(wB, vcp->rB) - vA - b2Cross(wA, vcp->rA); b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, vcp.rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, vcp.rA, b2Vec2.s_t1), dv); // Compute normal impulse // float32 vn = b2Dot(dv, normal); const vn = b2Vec2.DotVV(dv, normal); let lambda = (-vcp.normalMass * (vn - vcp.velocityBias)); // b2Clamp the accumulated impulse // float32 newImpulse = b2Max(vcp->normalImpulse + lambda, 0.0f); const newImpulse = b2Max(vcp.normalImpulse + lambda, 0); lambda = newImpulse - vcp.normalImpulse; vcp.normalImpulse = newImpulse; // Apply contact impulse // b2Vec2 P = lambda * normal; b2Vec2.MulSV(lambda, normal, P); // vA -= mA * P; vA.SelfMulSub(mA, P); // wA -= iA * b2Cross(vcp->rA, P); wA -= iA * b2Vec2.CrossVV(vcp.rA, P); // vB += mB * P; vB.SelfMulAdd(mB, P); // wB += iB * b2Cross(vcp->rB, P); wB += iB * b2Vec2.CrossVV(vcp.rB, P); } } else { // Block solver developed in collaboration with Dirk Gregorius (back in 01/07 on Box2D_Lite). // Build the mini LCP for this contact patch // // vn = A * x + b, vn >= 0, x >= 0 and vn_i * x_i = 0 with i = 1..2 // // A = J * W * JT and J = ( -n, -r1 x n, n, r2 x n ) // b = vn0 - velocityBias // // The system is solved using the "Total enumeration method" (s. Murty). The complementary constraint vn_i * x_i // implies that we must have in any solution either vn_i = 0 or x_i = 0. So for the 2D contact problem the cases // vn1 = 0 and vn2 = 0, x1 = 0 and x2 = 0, x1 = 0 and vn2 = 0, x2 = 0 and vn1 = 0 need to be tested. The first valid // solution that satisfies the problem is chosen. // // In order to account of the accumulated impulse 'a' (because of the iterative nature of the solver which only requires // that the accumulated impulse is clamped and not the incremental impulse) we change the impulse variable (x_i). // // Substitute: // // x = a + d // // a := old total impulse // x := new total impulse // d := incremental impulse // // For the current iteration we extend the formula for the incremental impulse // to compute the new total impulse: // // vn = A * d + b // = A * (x - a) + b // = A * x + b - A * a // = A * x + b' // b' = b - A * a; const cp1 = vc.points[0]; const cp2 = vc.points[1]; // b2Vec2 a(cp1->normalImpulse, cp2->normalImpulse); a.Set(cp1.normalImpulse, cp2.normalImpulse); // DEBUG: b2Assert(a.x >= 0 && a.y >= 0); // Relative velocity at contact // b2Vec2 dv1 = vB + b2Cross(wB, cp1->rB) - vA - b2Cross(wA, cp1->rA); b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, cp1.rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, cp1.rA, b2Vec2.s_t1), dv1); // b2Vec2 dv2 = vB + b2Cross(wB, cp2->rB) - vA - b2Cross(wA, cp2->rA); b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, cp2.rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, cp2.rA, b2Vec2.s_t1), dv2); // Compute normal velocity // float32 vn1 = b2Dot(dv1, normal); let vn1 = b2Vec2.DotVV(dv1, normal); // float32 vn2 = b2Dot(dv2, normal); let vn2 = b2Vec2.DotVV(dv2, normal); // b2Vec2 b; b.x = vn1 - cp1.velocityBias; b.y = vn2 - cp2.velocityBias; // Compute b' // b -= b2Mul(vc->K, a); b.SelfSub(b2Mat22.MulMV(vc.K, a, b2Vec2.s_t0)); /* #if B2_DEBUG_SOLVER === 1 const k_errorTol: number = 0.001; #endif */ for (;;) { // // Case 1: vn = 0 // // 0 = A * x + b' // // Solve for x: // // x = - inv(A) * b' // // b2Vec2 x = - b2Mul(vc->normalMass, b); b2Mat22.MulMV(vc.normalMass, b, x).SelfNeg(); if (x.x >= 0 && x.y >= 0) { // Get the incremental impulse // b2Vec2 d = x - a; b2Vec2.SubVV(x, a, d); // Apply incremental impulse // b2Vec2 P1 = d.x * normal; b2Vec2.MulSV(d.x, normal, P1); // b2Vec2 P2 = d.y * normal; b2Vec2.MulSV(d.y, normal, P2); b2Vec2.AddVV(P1, P2, P1P2); // vA -= mA * (P1 + P2); vA.SelfMulSub(mA, P1P2); // wA -= iA * (b2Cross(cp1->rA, P1) + b2Cross(cp2->rA, P2)); wA -= iA * (b2Vec2.CrossVV(cp1.rA, P1) + b2Vec2.CrossVV(cp2.rA, P2)); // vB += mB * (P1 + P2); vB.SelfMulAdd(mB, P1P2); // wB += iB * (b2Cross(cp1->rB, P1) + b2Cross(cp2->rB, P2)); wB += iB * (b2Vec2.CrossVV(cp1.rB, P1) + b2Vec2.CrossVV(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; /* #if B2_DEBUG_SOLVER === 1 // Postconditions dv1 = vB + b2Cross(wB, cp1->rB) - vA - b2Cross(wA, cp1->rA); dv2 = vB + b2Cross(wB, cp2->rB) - vA - b2Cross(wA, cp2->rA); // Compute normal velocity vn1 = b2Dot(dv1, normal); vn2 = b2Dot(dv2, normal); b2Assert(b2Abs(vn1 - cp1->velocityBias) < k_errorTol); b2Assert(b2Abs(vn2 - cp2->velocityBias) < k_errorTol); #endif */ break; } // // Case 2: vn1 = 0 and x2 = 0 // // 0 = a11 * x1 + a12 * 0 + b1' // vn2 = a21 * x1 + a22 * 0 + b2' // x.x = (-cp1.normalMass * b.x); x.y = 0; vn1 = 0; vn2 = vc.K.ex.y * x.x + b.y; if (x.x >= 0 && vn2 >= 0) { // Get the incremental impulse // b2Vec2 d = x - a; b2Vec2.SubVV(x, a, d); // Apply incremental impulse // b2Vec2 P1 = d.x * normal; b2Vec2.MulSV(d.x, normal, P1); // b2Vec2 P2 = d.y * normal; b2Vec2.MulSV(d.y, normal, P2); b2Vec2.AddVV(P1, P2, P1P2); // vA -= mA * (P1 + P2); vA.SelfMulSub(mA, P1P2); // wA -= iA * (b2Cross(cp1->rA, P1) + b2Cross(cp2->rA, P2)); wA -= iA * (b2Vec2.CrossVV(cp1.rA, P1) + b2Vec2.CrossVV(cp2.rA, P2)); // vB += mB * (P1 + P2); vB.SelfMulAdd(mB, P1P2); // wB += iB * (b2Cross(cp1->rB, P1) + b2Cross(cp2->rB, P2)); wB += iB * (b2Vec2.CrossVV(cp1.rB, P1) + b2Vec2.CrossVV(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; /* #if B2_DEBUG_SOLVER === 1 // Postconditions dv1 = vB + b2Cross(wB, cp1->rB) - vA - b2Cross(wA, cp1->rA); // Compute normal velocity vn1 = b2Dot(dv1, normal); b2Assert(b2Abs(vn1 - cp1->velocityBias) < k_errorTol); #endif */ break; } // // Case 3: vn2 = 0 and x1 = 0 // // vn1 = a11 * 0 + a12 * x2 + b1' // 0 = a21 * 0 + a22 * x2 + b2' // x.x = 0; x.y = (-cp2.normalMass * b.y); vn1 = vc.K.ey.x * x.y + b.x; vn2 = 0; if (x.y >= 0 && vn1 >= 0) { // Resubstitute for the incremental impulse // b2Vec2 d = x - a; b2Vec2.SubVV(x, a, d); // Apply incremental impulse // b2Vec2 P1 = d.x * normal; b2Vec2.MulSV(d.x, normal, P1); // b2Vec2 P2 = d.y * normal; b2Vec2.MulSV(d.y, normal, P2); b2Vec2.AddVV(P1, P2, P1P2); // vA -= mA * (P1 + P2); vA.SelfMulSub(mA, P1P2); // wA -= iA * (b2Cross(cp1->rA, P1) + b2Cross(cp2->rA, P2)); wA -= iA * (b2Vec2.CrossVV(cp1.rA, P1) + b2Vec2.CrossVV(cp2.rA, P2)); // vB += mB * (P1 + P2); vB.SelfMulAdd(mB, P1P2); // wB += iB * (b2Cross(cp1->rB, P1) + b2Cross(cp2->rB, P2)); wB += iB * (b2Vec2.CrossVV(cp1.rB, P1) + b2Vec2.CrossVV(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; /* #if B2_DEBUG_SOLVER === 1 // Postconditions dv2 = vB + b2Cross(wB, cp2->rB) - vA - b2Cross(wA, cp2->rA); // Compute normal velocity vn2 = b2Dot(dv2, normal); b2Assert(b2Abs(vn2 - cp2->velocityBias) < k_errorTol); #endif */ break; } // // Case 4: x1 = 0 and x2 = 0 // // vn1 = b1 // vn2 = b2; x.x = 0; x.y = 0; vn1 = b.x; vn2 = b.y; if (vn1 >= 0 && vn2 >= 0) { // Resubstitute for the incremental impulse // b2Vec2 d = x - a; b2Vec2.SubVV(x, a, d); // Apply incremental impulse // b2Vec2 P1 = d.x * normal; b2Vec2.MulSV(d.x, normal, P1); // b2Vec2 P2 = d.y * normal; b2Vec2.MulSV(d.y, normal, P2); b2Vec2.AddVV(P1, P2, P1P2); // vA -= mA * (P1 + P2); vA.SelfMulSub(mA, P1P2); // wA -= iA * (b2Cross(cp1->rA, P1) + b2Cross(cp2->rA, P2)); wA -= iA * (b2Vec2.CrossVV(cp1.rA, P1) + b2Vec2.CrossVV(cp2.rA, P2)); // vB += mB * (P1 + P2); vB.SelfMulAdd(mB, P1P2); // wB += iB * (b2Cross(cp1->rB, P1) + b2Cross(cp2->rB, P2)); wB += iB * (b2Vec2.CrossVV(cp1.rB, P1) + b2Vec2.CrossVV(cp2.rB, P2)); // Accumulate cp1.normalImpulse = x.x; cp2.normalImpulse = x.y; break; } // No solution, give up. This is hit sometimes, but it doesn't seem to matter. break; } } // this.m_velocities[indexA].v = vA; this.m_velocities[indexA].w = wA; // this.m_velocities[indexB].v = vB; this.m_velocities[indexB].w = wB; } } StoreImpulses() { for (let i = 0; i < this.m_count; ++i) { const vc = this.m_velocityConstraints[i]; const manifold = this.m_contacts[vc.contactIndex].GetManifold(); for (let j = 0; j < vc.pointCount; ++j) { manifold.points[j].normalImpulse = vc.points[j].normalImpulse; manifold.points[j].tangentImpulse = vc.points[j].tangentImpulse; } } } SolvePositionConstraints() { const xfA = b2ContactSolver.SolvePositionConstraints_s_xfA; const xfB = b2ContactSolver.SolvePositionConstraints_s_xfB; const psm = b2ContactSolver.SolvePositionConstraints_s_psm; const rA = b2ContactSolver.SolvePositionConstraints_s_rA; const rB = b2ContactSolver.SolvePositionConstraints_s_rB; const P = b2ContactSolver.SolvePositionConstraints_s_P; let minSeparation = 0; for (let i = 0; i < this.m_count; ++i) { const pc = this.m_positionConstraints[i]; const indexA = pc.indexA; const indexB = pc.indexB; const localCenterA = pc.localCenterA; const mA = pc.invMassA; const iA = pc.invIA; const localCenterB = pc.localCenterB; const mB = pc.invMassB; const iB = pc.invIB; const pointCount = pc.pointCount; const cA = this.m_positions[indexA].c; let aA = this.m_positions[indexA].a; const cB = this.m_positions[indexB].c; let aB = this.m_positions[indexB].a; // Solve normal constraints for (let j = 0; j < pointCount; ++j) { xfA.q.SetAngle(aA); xfB.q.SetAngle(aB); b2Vec2.SubVV(cA, b2Rot.MulRV(xfA.q, localCenterA, b2Vec2.s_t0), xfA.p); b2Vec2.SubVV(cB, b2Rot.MulRV(xfB.q, localCenterB, b2Vec2.s_t0), xfB.p); psm.Initialize(pc, xfA, xfB, j); const normal = psm.normal; const point = psm.point; const separation = psm.separation; // b2Vec2 rA = point - cA; b2Vec2.SubVV(point, cA, rA); // b2Vec2 rB = point - cB; b2Vec2.SubVV(point, cB, rB); // Track max constraint error. minSeparation = b2Min(minSeparation, separation); // Prevent large corrections and allow slop. const C = b2Clamp(b2_baumgarte * (separation + b2_linearSlop), (-b2_maxLinearCorrection), 0); // Compute the effective mass. // float32 rnA = b2Cross(rA, normal); const rnA = b2Vec2.CrossVV(rA, normal); // float32 rnB = b2Cross(rB, normal); const rnB = b2Vec2.CrossVV(rB, normal); // float32 K = mA + mB + iA * rnA * rnA + iB * rnB * rnB; const K = mA + mB + iA * rnA * rnA + iB * rnB * rnB; // Compute normal impulse const impulse = K > 0 ? -C / K : 0; // b2Vec2 P = impulse * normal; b2Vec2.MulSV(impulse, normal, P); // cA -= mA * P; cA.SelfMulSub(mA, P); // aA -= iA * b2Cross(rA, P); aA -= iA * b2Vec2.CrossVV(rA, P); // cB += mB * P; cB.SelfMulAdd(mB, P); // aB += iB * b2Cross(rB, P); aB += iB * b2Vec2.CrossVV(rB, P); } // this.m_positions[indexA].c = cA; this.m_positions[indexA].a = aA; // this.m_positions[indexB].c = cB; this.m_positions[indexB].a = aB; } // We can't expect minSpeparation >= -b2_linearSlop because we don't // push the separation above -b2_linearSlop. return minSeparation > (-3 * b2_linearSlop); } SolveTOIPositionConstraints(toiIndexA, toiIndexB) { const xfA = b2ContactSolver.SolveTOIPositionConstraints_s_xfA; const xfB = b2ContactSolver.SolveTOIPositionConstraints_s_xfB; const psm = b2ContactSolver.SolveTOIPositionConstraints_s_psm; const rA = b2ContactSolver.SolveTOIPositionConstraints_s_rA; const rB = b2ContactSolver.SolveTOIPositionConstraints_s_rB; const P = b2ContactSolver.SolveTOIPositionConstraints_s_P; let minSeparation = 0; for (let i = 0; i < this.m_count; ++i) { const pc = this.m_positionConstraints[i]; const indexA = pc.indexA; const indexB = pc.indexB; const localCenterA = pc.localCenterA; const localCenterB = pc.localCenterB; const pointCount = pc.pointCount; let mA = 0; let iA = 0; if (indexA === toiIndexA || indexA === toiIndexB) { mA = pc.invMassA; iA = pc.invIA; } let mB = 0; let iB = 0; if (indexB === toiIndexA || indexB === toiIndexB) { mB = pc.invMassB; iB = pc.invIB; } const cA = this.m_positions[indexA].c; let aA = this.m_positions[indexA].a; const cB = this.m_positions[indexB].c; let aB = this.m_positions[indexB].a; // Solve normal constraints for (let j = 0; j < pointCount; ++j) { xfA.q.SetAngle(aA); xfB.q.SetAngle(aB); b2Vec2.SubVV(cA, b2Rot.MulRV(xfA.q, localCenterA, b2Vec2.s_t0), xfA.p); b2Vec2.SubVV(cB, b2Rot.MulRV(xfB.q, localCenterB, b2Vec2.s_t0), xfB.p); psm.Initialize(pc, xfA, xfB, j); const normal = psm.normal; const point = psm.point; const separation = psm.separation; // b2Vec2 rA = point - cA; b2Vec2.SubVV(point, cA, rA); // b2Vec2 rB = point - cB; b2Vec2.SubVV(point, cB, rB); // Track max constraint error. minSeparation = b2Min(minSeparation, separation); // Prevent large corrections and allow slop. const C = b2Clamp(b2_toiBaumgarte * (separation + b2_linearSlop), (-b2_maxLinearCorrection), 0); // Compute the effective mass. // float32 rnA = b2Cross(rA, normal); const rnA = b2Vec2.CrossVV(rA, normal); // float32 rnB = b2Cross(rB, normal); const rnB = b2Vec2.CrossVV(rB, normal); // float32 K = mA + mB + iA * rnA * rnA + iB * rnB * rnB; const K = mA + mB + iA * rnA * rnA + iB * rnB * rnB; // Compute normal impulse const impulse = K > 0 ? -C / K : 0; // b2Vec2 P = impulse * normal; b2Vec2.MulSV(impulse, normal, P); // cA -= mA * P; cA.SelfMulSub(mA, P); // aA -= iA * b2Cross(rA, P); aA -= iA * b2Vec2.CrossVV(rA, P); // cB += mB * P; cB.SelfMulAdd(mB, P); // aB += iB * b2Cross(rB, P); aB += iB * b2Vec2.CrossVV(rB, P); } // this.m_positions[indexA].c = cA; this.m_positions[indexA].a = aA; // this.m_positions[indexB].c = cB; this.m_positions[indexB].a = aB; } // We can't expect minSpeparation >= -b2_linearSlop because we don't // push the separation above -b2_linearSlop. return minSeparation >= -1.5 * b2_linearSlop; } } b2ContactSolver.InitializeVelocityConstraints_s_xfA = new b2Transform(); b2ContactSolver.InitializeVelocityConstraints_s_xfB = new b2Transform(); b2ContactSolver.InitializeVelocityConstraints_s_worldManifold = new b2WorldManifold(); b2ContactSolver.WarmStart_s_P = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_dv = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_dv1 = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_dv2 = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_P = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_a = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_b = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_x = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_d = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_P1 = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_P2 = new b2Vec2(); b2ContactSolver.SolveVelocityConstraints_s_P1P2 = new b2Vec2(); b2ContactSolver.SolvePositionConstraints_s_xfA = new b2Transform(); b2ContactSolver.SolvePositionConstraints_s_xfB = new b2Transform(); b2ContactSolver.SolvePositionConstraints_s_psm = new b2PositionSolverManifold(); b2ContactSolver.SolvePositionConstraints_s_rA = new b2Vec2(); b2ContactSolver.SolvePositionConstraints_s_rB = new b2Vec2(); b2ContactSolver.SolvePositionConstraints_s_P = new b2Vec2(); b2ContactSolver.SolveTOIPositionConstraints_s_xfA = new b2Transform(); b2ContactSolver.SolveTOIPositionConstraints_s_xfB = new b2Transform(); b2ContactSolver.SolveTOIPositionConstraints_s_psm = new b2PositionSolverManifold(); b2ContactSolver.SolveTOIPositionConstraints_s_rA = new b2Vec2(); b2ContactSolver.SolveTOIPositionConstraints_s_rB = new b2Vec2(); b2ContactSolver.SolveTOIPositionConstraints_s_P = new b2Vec2(); /* * Copyright (c) 2006-2007 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Friction joint definition. class b2FrictionJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_frictionJoint); this.localAnchorA = new b2Vec2(); this.localAnchorB = new b2Vec2(); this.maxForce = 0; this.maxTorque = 0; } Initialize(bA, bB, anchor) { this.bodyA = bA; this.bodyB = bB; this.bodyA.GetLocalPoint(anchor, this.localAnchorA); this.bodyB.GetLocalPoint(anchor, this.localAnchorB); } } class b2FrictionJoint extends b2Joint { constructor(def) { super(def); this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); // Solver shared this.m_linearImpulse = new b2Vec2(); this.m_angularImpulse = 0; this.m_maxForce = 0; this.m_maxTorque = 0; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_linearMass = new b2Mat22(); this.m_angularMass = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_K = new b2Mat22(); this.m_localAnchorA.Copy(b2Maybe(def.localAnchorA, b2Vec2.ZERO)); this.m_localAnchorB.Copy(b2Maybe(def.localAnchorB, b2Vec2.ZERO)); this.m_linearImpulse.SetZero(); this.m_maxForce = b2Maybe(def.maxForce, 0); this.m_maxTorque = b2Maybe(def.maxTorque, 0); this.m_linearMass.SetZero(); } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; // const cA: b2Vec2 = data.positions[this.m_indexA].c; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; // const cB: b2Vec2 = data.positions[this.m_indexB].c; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; // const qA: b2Rot = new b2Rot(aA), qB: b2Rot = new b2Rot(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // Compute the effective mass matrix. // m_rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // m_rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // J = [-I -r1_skew I r2_skew] // [ 0 -1 0 1] // r_skew = [-ry; rx] // Matlab // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB] // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB] // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB] const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const K = this.m_K; // new b2Mat22(); K.ex.x = mA + mB + iA * rA.y * rA.y + iB * rB.y * rB.y; K.ex.y = -iA * rA.x * rA.y - iB * rB.x * rB.y; K.ey.x = K.ex.y; K.ey.y = mA + mB + iA * rA.x * rA.x + iB * rB.x * rB.x; K.GetInverse(this.m_linearMass); this.m_angularMass = iA + iB; if (this.m_angularMass > 0) { this.m_angularMass = 1 / this.m_angularMass; } if (data.step.warmStarting) { // Scale impulses to support a variable time step. // m_linearImpulse *= data.step.dtRatio; this.m_linearImpulse.SelfMul(data.step.dtRatio); this.m_angularImpulse *= data.step.dtRatio; // const P: b2Vec2(m_linearImpulse.x, m_linearImpulse.y); const P = this.m_linearImpulse; // vA -= mA * P; vA.SelfMulSub(mA, P); // wA -= iA * (b2Cross(m_rA, P) + m_angularImpulse); wA -= iA * (b2Vec2.CrossVV(this.m_rA, P) + this.m_angularImpulse); // vB += mB * P; vB.SelfMulAdd(mB, P); // wB += iB * (b2Cross(m_rB, P) + m_angularImpulse); wB += iB * (b2Vec2.CrossVV(this.m_rB, P) + this.m_angularImpulse); } else { this.m_linearImpulse.SetZero(); this.m_angularImpulse = 0; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const h = data.step.dt; // Solve angular friction { const Cdot = wB - wA; let impulse = (-this.m_angularMass * Cdot); const oldImpulse = this.m_angularImpulse; const maxImpulse = h * this.m_maxTorque; this.m_angularImpulse = b2Clamp(this.m_angularImpulse + impulse, (-maxImpulse), maxImpulse); impulse = this.m_angularImpulse - oldImpulse; wA -= iA * impulse; wB += iB * impulse; } // Solve linear friction { // b2Vec2 Cdot = vB + b2Cross(wB, m_rB) - vA - b2Cross(wA, m_rA); const Cdot_v2 = b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2Vec2.s_t1), b2FrictionJoint.SolveVelocityConstraints_s_Cdot_v2); // b2Vec2 impulse = -b2Mul(m_linearMass, Cdot); const impulseV = b2Mat22.MulMV(this.m_linearMass, Cdot_v2, b2FrictionJoint.SolveVelocityConstraints_s_impulseV).SelfNeg(); // b2Vec2 oldImpulse = m_linearImpulse; const oldImpulseV = b2FrictionJoint.SolveVelocityConstraints_s_oldImpulseV.Copy(this.m_linearImpulse); // m_linearImpulse += impulse; this.m_linearImpulse.SelfAdd(impulseV); const maxImpulse = h * this.m_maxForce; if (this.m_linearImpulse.LengthSquared() > maxImpulse * maxImpulse) { this.m_linearImpulse.Normalize(); this.m_linearImpulse.SelfMul(maxImpulse); } // impulse = m_linearImpulse - oldImpulse; b2Vec2.SubVV(this.m_linearImpulse, oldImpulseV, impulseV); // vA -= mA * impulse; vA.SelfMulSub(mA, impulseV); // wA -= iA * b2Cross(m_rA, impulse); wA -= iA * b2Vec2.CrossVV(this.m_rA, impulseV); // vB += mB * impulse; vB.SelfMulAdd(mB, impulseV); // wB += iB * b2Cross(m_rB, impulse); wB += iB * b2Vec2.CrossVV(this.m_rB, impulseV); } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { return true; } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { out.x = inv_dt * this.m_linearImpulse.x; out.y = inv_dt * this.m_linearImpulse.y; return out; } GetReactionTorque(inv_dt) { return inv_dt * this.m_angularImpulse; } GetLocalAnchorA() { return this.m_localAnchorA; } GetLocalAnchorB() { return this.m_localAnchorB; } SetMaxForce(force) { this.m_maxForce = force; } GetMaxForce() { return this.m_maxForce; } SetMaxTorque(torque) { this.m_maxTorque = torque; } GetMaxTorque() { return this.m_maxTorque; } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2FrictionJointDef = new b2FrictionJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.localAnchorA.Set(%.15f, %.15f);\n", this.m_localAnchorA.x, this.m_localAnchorA.y); log(" jd.localAnchorB.Set(%.15f, %.15f);\n", this.m_localAnchorB.x, this.m_localAnchorB.y); log(" jd.maxForce = %.15f;\n", this.m_maxForce); log(" jd.maxTorque = %.15f;\n", this.m_maxTorque); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } } b2FrictionJoint.SolveVelocityConstraints_s_Cdot_v2 = new b2Vec2(); b2FrictionJoint.SolveVelocityConstraints_s_impulseV = new b2Vec2(); b2FrictionJoint.SolveVelocityConstraints_s_oldImpulseV = new b2Vec2(); /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Gear joint definition. This definition requires two existing /// revolute or prismatic joints (any combination will work). class b2GearJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_gearJoint); this.ratio = 1; } } class b2GearJoint extends b2Joint { constructor(def) { super(def); this.m_typeA = exports.b2JointType.e_unknownJoint; this.m_typeB = exports.b2JointType.e_unknownJoint; // Solver shared this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); this.m_localAnchorC = new b2Vec2(); this.m_localAnchorD = new b2Vec2(); this.m_localAxisC = new b2Vec2(); this.m_localAxisD = new b2Vec2(); this.m_referenceAngleA = 0; this.m_referenceAngleB = 0; this.m_constant = 0; this.m_ratio = 0; this.m_impulse = 0; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_indexC = 0; this.m_indexD = 0; this.m_lcA = new b2Vec2(); this.m_lcB = new b2Vec2(); this.m_lcC = new b2Vec2(); this.m_lcD = new b2Vec2(); this.m_mA = 0; this.m_mB = 0; this.m_mC = 0; this.m_mD = 0; this.m_iA = 0; this.m_iB = 0; this.m_iC = 0; this.m_iD = 0; this.m_JvAC = new b2Vec2(); this.m_JvBD = new b2Vec2(); this.m_JwA = 0; this.m_JwB = 0; this.m_JwC = 0; this.m_JwD = 0; this.m_mass = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_qC = new b2Rot(); this.m_qD = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_lalcC = new b2Vec2(); this.m_lalcD = new b2Vec2(); this.m_joint1 = def.joint1; this.m_joint2 = def.joint2; this.m_typeA = this.m_joint1.GetType(); this.m_typeB = this.m_joint2.GetType(); // DEBUG: b2Assert(this.m_typeA === b2JointType.e_revoluteJoint || this.m_typeA === b2JointType.e_prismaticJoint); // DEBUG: b2Assert(this.m_typeB === b2JointType.e_revoluteJoint || this.m_typeB === b2JointType.e_prismaticJoint); let coordinateA, coordinateB; // TODO_ERIN there might be some problem with the joint edges in b2Joint. this.m_bodyC = this.m_joint1.GetBodyA(); this.m_bodyA = this.m_joint1.GetBodyB(); // Body B on joint1 must be dynamic // b2Assert(this.m_bodyA.m_type === b2_dynamicBody); // Get geometry of joint1 const xfA = this.m_bodyA.m_xf; const aA = this.m_bodyA.m_sweep.a; const xfC = this.m_bodyC.m_xf; const aC = this.m_bodyC.m_sweep.a; if (this.m_typeA === exports.b2JointType.e_revoluteJoint) { const revolute = def.joint1; this.m_localAnchorC.Copy(revolute.m_localAnchorA); this.m_localAnchorA.Copy(revolute.m_localAnchorB); this.m_referenceAngleA = revolute.m_referenceAngle; this.m_localAxisC.SetZero(); coordinateA = aA - aC - this.m_referenceAngleA; } else { const prismatic = def.joint1; this.m_localAnchorC.Copy(prismatic.m_localAnchorA); this.m_localAnchorA.Copy(prismatic.m_localAnchorB); this.m_referenceAngleA = prismatic.m_referenceAngle; this.m_localAxisC.Copy(prismatic.m_localXAxisA); // b2Vec2 pC = m_localAnchorC; const pC = this.m_localAnchorC; // b2Vec2 pA = b2MulT(xfC.q, b2Mul(xfA.q, m_localAnchorA) + (xfA.p - xfC.p)); const pA = b2Rot.MulTRV(xfC.q, b2Vec2.AddVV(b2Rot.MulRV(xfA.q, this.m_localAnchorA, b2Vec2.s_t0), b2Vec2.SubVV(xfA.p, xfC.p, b2Vec2.s_t1), b2Vec2.s_t0), b2Vec2.s_t0); // pA uses s_t0 // coordinateA = b2Dot(pA - pC, m_localAxisC); coordinateA = b2Vec2.DotVV(b2Vec2.SubVV(pA, pC, b2Vec2.s_t0), this.m_localAxisC); } this.m_bodyD = this.m_joint2.GetBodyA(); this.m_bodyB = this.m_joint2.GetBodyB(); // Body B on joint2 must be dynamic // b2Assert(this.m_bodyB.m_type === b2_dynamicBody); // Get geometry of joint2 const xfB = this.m_bodyB.m_xf; const aB = this.m_bodyB.m_sweep.a; const xfD = this.m_bodyD.m_xf; const aD = this.m_bodyD.m_sweep.a; if (this.m_typeB === exports.b2JointType.e_revoluteJoint) { const revolute = def.joint2; this.m_localAnchorD.Copy(revolute.m_localAnchorA); this.m_localAnchorB.Copy(revolute.m_localAnchorB); this.m_referenceAngleB = revolute.m_referenceAngle; this.m_localAxisD.SetZero(); coordinateB = aB - aD - this.m_referenceAngleB; } else { const prismatic = def.joint2; this.m_localAnchorD.Copy(prismatic.m_localAnchorA); this.m_localAnchorB.Copy(prismatic.m_localAnchorB); this.m_referenceAngleB = prismatic.m_referenceAngle; this.m_localAxisD.Copy(prismatic.m_localXAxisA); // b2Vec2 pD = m_localAnchorD; const pD = this.m_localAnchorD; // b2Vec2 pB = b2MulT(xfD.q, b2Mul(xfB.q, m_localAnchorB) + (xfB.p - xfD.p)); const pB = b2Rot.MulTRV(xfD.q, b2Vec2.AddVV(b2Rot.MulRV(xfB.q, this.m_localAnchorB, b2Vec2.s_t0), b2Vec2.SubVV(xfB.p, xfD.p, b2Vec2.s_t1), b2Vec2.s_t0), b2Vec2.s_t0); // pB uses s_t0 // coordinateB = b2Dot(pB - pD, m_localAxisD); coordinateB = b2Vec2.DotVV(b2Vec2.SubVV(pB, pD, b2Vec2.s_t0), this.m_localAxisD); } this.m_ratio = b2Maybe(def.ratio, 1); this.m_constant = coordinateA + this.m_ratio * coordinateB; this.m_impulse = 0; } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_indexC = this.m_bodyC.m_islandIndex; this.m_indexD = this.m_bodyD.m_islandIndex; this.m_lcA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_lcB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_lcC.Copy(this.m_bodyC.m_sweep.localCenter); this.m_lcD.Copy(this.m_bodyD.m_sweep.localCenter); this.m_mA = this.m_bodyA.m_invMass; this.m_mB = this.m_bodyB.m_invMass; this.m_mC = this.m_bodyC.m_invMass; this.m_mD = this.m_bodyD.m_invMass; this.m_iA = this.m_bodyA.m_invI; this.m_iB = this.m_bodyB.m_invI; this.m_iC = this.m_bodyC.m_invI; this.m_iD = this.m_bodyD.m_invI; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const aC = data.positions[this.m_indexC].a; const vC = data.velocities[this.m_indexC].v; let wC = data.velocities[this.m_indexC].w; const aD = data.positions[this.m_indexD].a; const vD = data.velocities[this.m_indexD].v; let wD = data.velocities[this.m_indexD].w; // b2Rot qA(aA), qB(aB), qC(aC), qD(aD); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB), qC = this.m_qC.SetAngle(aC), qD = this.m_qD.SetAngle(aD); this.m_mass = 0; if (this.m_typeA === exports.b2JointType.e_revoluteJoint) { this.m_JvAC.SetZero(); this.m_JwA = 1; this.m_JwC = 1; this.m_mass += this.m_iA + this.m_iC; } else { // b2Vec2 u = b2Mul(qC, m_localAxisC); const u = b2Rot.MulRV(qC, this.m_localAxisC, b2GearJoint.InitVelocityConstraints_s_u); // b2Vec2 rC = b2Mul(qC, m_localAnchorC - m_lcC); b2Vec2.SubVV(this.m_localAnchorC, this.m_lcC, this.m_lalcC); const rC = b2Rot.MulRV(qC, this.m_lalcC, b2GearJoint.InitVelocityConstraints_s_rC); // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_lcA); b2Vec2.SubVV(this.m_localAnchorA, this.m_lcA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, b2GearJoint.InitVelocityConstraints_s_rA); // m_JvAC = u; this.m_JvAC.Copy(u); // m_JwC = b2Cross(rC, u); this.m_JwC = b2Vec2.CrossVV(rC, u); // m_JwA = b2Cross(rA, u); this.m_JwA = b2Vec2.CrossVV(rA, u); this.m_mass += this.m_mC + this.m_mA + this.m_iC * this.m_JwC * this.m_JwC + this.m_iA * this.m_JwA * this.m_JwA; } if (this.m_typeB === exports.b2JointType.e_revoluteJoint) { this.m_JvBD.SetZero(); this.m_JwB = this.m_ratio; this.m_JwD = this.m_ratio; this.m_mass += this.m_ratio * this.m_ratio * (this.m_iB + this.m_iD); } else { // b2Vec2 u = b2Mul(qD, m_localAxisD); const u = b2Rot.MulRV(qD, this.m_localAxisD, b2GearJoint.InitVelocityConstraints_s_u); // b2Vec2 rD = b2Mul(qD, m_localAnchorD - m_lcD); b2Vec2.SubVV(this.m_localAnchorD, this.m_lcD, this.m_lalcD); const rD = b2Rot.MulRV(qD, this.m_lalcD, b2GearJoint.InitVelocityConstraints_s_rD); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_lcB); b2Vec2.SubVV(this.m_localAnchorB, this.m_lcB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, b2GearJoint.InitVelocityConstraints_s_rB); // m_JvBD = m_ratio * u; b2Vec2.MulSV(this.m_ratio, u, this.m_JvBD); // m_JwD = m_ratio * b2Cross(rD, u); this.m_JwD = this.m_ratio * b2Vec2.CrossVV(rD, u); // m_JwB = m_ratio * b2Cross(rB, u); this.m_JwB = this.m_ratio * b2Vec2.CrossVV(rB, u); this.m_mass += this.m_ratio * this.m_ratio * (this.m_mD + this.m_mB) + this.m_iD * this.m_JwD * this.m_JwD + this.m_iB * this.m_JwB * this.m_JwB; } // Compute effective mass. this.m_mass = this.m_mass > 0 ? 1 / this.m_mass : 0; if (data.step.warmStarting) { // vA += (m_mA * m_impulse) * m_JvAC; vA.SelfMulAdd(this.m_mA * this.m_impulse, this.m_JvAC); wA += this.m_iA * this.m_impulse * this.m_JwA; // vB += (m_mB * m_impulse) * m_JvBD; vB.SelfMulAdd(this.m_mB * this.m_impulse, this.m_JvBD); wB += this.m_iB * this.m_impulse * this.m_JwB; // vC -= (m_mC * m_impulse) * m_JvAC; vC.SelfMulSub(this.m_mC * this.m_impulse, this.m_JvAC); wC -= this.m_iC * this.m_impulse * this.m_JwC; // vD -= (m_mD * m_impulse) * m_JvBD; vD.SelfMulSub(this.m_mD * this.m_impulse, this.m_JvBD); wD -= this.m_iD * this.m_impulse * this.m_JwD; } else { this.m_impulse = 0; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; // data.velocities[this.m_indexC].v = vC; data.velocities[this.m_indexC].w = wC; // data.velocities[this.m_indexD].v = vD; data.velocities[this.m_indexD].w = wD; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const vC = data.velocities[this.m_indexC].v; let wC = data.velocities[this.m_indexC].w; const vD = data.velocities[this.m_indexD].v; let wD = data.velocities[this.m_indexD].w; // float32 Cdot = b2Dot(m_JvAC, vA - vC) + b2Dot(m_JvBD, vB - vD); let Cdot = b2Vec2.DotVV(this.m_JvAC, b2Vec2.SubVV(vA, vC, b2Vec2.s_t0)) + b2Vec2.DotVV(this.m_JvBD, b2Vec2.SubVV(vB, vD, b2Vec2.s_t0)); Cdot += (this.m_JwA * wA - this.m_JwC * wC) + (this.m_JwB * wB - this.m_JwD * wD); const impulse = -this.m_mass * Cdot; this.m_impulse += impulse; // vA += (m_mA * impulse) * m_JvAC; vA.SelfMulAdd((this.m_mA * impulse), this.m_JvAC); wA += this.m_iA * impulse * this.m_JwA; // vB += (m_mB * impulse) * m_JvBD; vB.SelfMulAdd((this.m_mB * impulse), this.m_JvBD); wB += this.m_iB * impulse * this.m_JwB; // vC -= (m_mC * impulse) * m_JvAC; vC.SelfMulSub((this.m_mC * impulse), this.m_JvAC); wC -= this.m_iC * impulse * this.m_JwC; // vD -= (m_mD * impulse) * m_JvBD; vD.SelfMulSub((this.m_mD * impulse), this.m_JvBD); wD -= this.m_iD * impulse * this.m_JwD; // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; // data.velocities[this.m_indexC].v = vC; data.velocities[this.m_indexC].w = wC; // data.velocities[this.m_indexD].v = vD; data.velocities[this.m_indexD].w = wD; } SolvePositionConstraints(data) { const cA = data.positions[this.m_indexA].c; let aA = data.positions[this.m_indexA].a; const cB = data.positions[this.m_indexB].c; let aB = data.positions[this.m_indexB].a; const cC = data.positions[this.m_indexC].c; let aC = data.positions[this.m_indexC].a; const cD = data.positions[this.m_indexD].c; let aD = data.positions[this.m_indexD].a; // b2Rot qA(aA), qB(aB), qC(aC), qD(aD); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB), qC = this.m_qC.SetAngle(aC), qD = this.m_qD.SetAngle(aD); const linearError = 0; let coordinateA, coordinateB; const JvAC = this.m_JvAC, JvBD = this.m_JvBD; let JwA, JwB, JwC, JwD; let mass = 0; if (this.m_typeA === exports.b2JointType.e_revoluteJoint) { JvAC.SetZero(); JwA = 1; JwC = 1; mass += this.m_iA + this.m_iC; coordinateA = aA - aC - this.m_referenceAngleA; } else { // b2Vec2 u = b2Mul(qC, m_localAxisC); const u = b2Rot.MulRV(qC, this.m_localAxisC, b2GearJoint.SolvePositionConstraints_s_u); // b2Vec2 rC = b2Mul(qC, m_localAnchorC - m_lcC); const rC = b2Rot.MulRV(qC, this.m_lalcC, b2GearJoint.SolvePositionConstraints_s_rC); // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_lcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, b2GearJoint.SolvePositionConstraints_s_rA); // JvAC = u; JvAC.Copy(u); // JwC = b2Cross(rC, u); JwC = b2Vec2.CrossVV(rC, u); // JwA = b2Cross(rA, u); JwA = b2Vec2.CrossVV(rA, u); mass += this.m_mC + this.m_mA + this.m_iC * JwC * JwC + this.m_iA * JwA * JwA; // b2Vec2 pC = m_localAnchorC - m_lcC; const pC = this.m_lalcC; // b2Vec2 pA = b2MulT(qC, rA + (cA - cC)); const pA = b2Rot.MulTRV(qC, b2Vec2.AddVV(rA, b2Vec2.SubVV(cA, cC, b2Vec2.s_t0), b2Vec2.s_t0), b2Vec2.s_t0); // pA uses s_t0 // coordinateA = b2Dot(pA - pC, m_localAxisC); coordinateA = b2Vec2.DotVV(b2Vec2.SubVV(pA, pC, b2Vec2.s_t0), this.m_localAxisC); } if (this.m_typeB === exports.b2JointType.e_revoluteJoint) { JvBD.SetZero(); JwB = this.m_ratio; JwD = this.m_ratio; mass += this.m_ratio * this.m_ratio * (this.m_iB + this.m_iD); coordinateB = aB - aD - this.m_referenceAngleB; } else { // b2Vec2 u = b2Mul(qD, m_localAxisD); const u = b2Rot.MulRV(qD, this.m_localAxisD, b2GearJoint.SolvePositionConstraints_s_u); // b2Vec2 rD = b2Mul(qD, m_localAnchorD - m_lcD); const rD = b2Rot.MulRV(qD, this.m_lalcD, b2GearJoint.SolvePositionConstraints_s_rD); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_lcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, b2GearJoint.SolvePositionConstraints_s_rB); // JvBD = m_ratio * u; b2Vec2.MulSV(this.m_ratio, u, JvBD); // JwD = m_ratio * b2Cross(rD, u); JwD = this.m_ratio * b2Vec2.CrossVV(rD, u); // JwB = m_ratio * b2Cross(rB, u); JwB = this.m_ratio * b2Vec2.CrossVV(rB, u); mass += this.m_ratio * this.m_ratio * (this.m_mD + this.m_mB) + this.m_iD * JwD * JwD + this.m_iB * JwB * JwB; // b2Vec2 pD = m_localAnchorD - m_lcD; const pD = this.m_lalcD; // b2Vec2 pB = b2MulT(qD, rB + (cB - cD)); const pB = b2Rot.MulTRV(qD, b2Vec2.AddVV(rB, b2Vec2.SubVV(cB, cD, b2Vec2.s_t0), b2Vec2.s_t0), b2Vec2.s_t0); // pB uses s_t0 // coordinateB = b2Dot(pB - pD, m_localAxisD); coordinateB = b2Vec2.DotVV(b2Vec2.SubVV(pB, pD, b2Vec2.s_t0), this.m_localAxisD); } const C = (coordinateA + this.m_ratio * coordinateB) - this.m_constant; let impulse = 0; if (mass > 0) { impulse = -C / mass; } // cA += m_mA * impulse * JvAC; cA.SelfMulAdd(this.m_mA * impulse, JvAC); aA += this.m_iA * impulse * JwA; // cB += m_mB * impulse * JvBD; cB.SelfMulAdd(this.m_mB * impulse, JvBD); aB += this.m_iB * impulse * JwB; // cC -= m_mC * impulse * JvAC; cC.SelfMulSub(this.m_mC * impulse, JvAC); aC -= this.m_iC * impulse * JwC; // cD -= m_mD * impulse * JvBD; cD.SelfMulSub(this.m_mD * impulse, JvBD); aD -= this.m_iD * impulse * JwD; // data.positions[this.m_indexA].c = cA; data.positions[this.m_indexA].a = aA; // data.positions[this.m_indexB].c = cB; data.positions[this.m_indexB].a = aB; // data.positions[this.m_indexC].c = cC; data.positions[this.m_indexC].a = aC; // data.positions[this.m_indexD].c = cD; data.positions[this.m_indexD].a = aD; // TODO_ERIN not implemented return linearError < b2_linearSlop; } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { // b2Vec2 P = m_impulse * m_JvAC; // return inv_dt * P; return b2Vec2.MulSV(inv_dt * this.m_impulse, this.m_JvAC, out); } GetReactionTorque(inv_dt) { // float32 L = m_impulse * m_JwA; // return inv_dt * L; return inv_dt * this.m_impulse * this.m_JwA; } GetJoint1() { return this.m_joint1; } GetJoint2() { return this.m_joint2; } GetRatio() { return this.m_ratio; } SetRatio(ratio) { // DEBUG: b2Assert(b2IsValid(ratio)); this.m_ratio = ratio; } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; const index1 = this.m_joint1.m_index; const index2 = this.m_joint2.m_index; log(" const jd: b2GearJointDef = new b2GearJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.joint1 = joints[%d];\n", index1); log(" jd.joint2 = joints[%d];\n", index2); log(" jd.ratio = %.15f;\n", this.m_ratio); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } } b2GearJoint.InitVelocityConstraints_s_u = new b2Vec2(); b2GearJoint.InitVelocityConstraints_s_rA = new b2Vec2(); b2GearJoint.InitVelocityConstraints_s_rB = new b2Vec2(); b2GearJoint.InitVelocityConstraints_s_rC = new b2Vec2(); b2GearJoint.InitVelocityConstraints_s_rD = new b2Vec2(); b2GearJoint.SolvePositionConstraints_s_u = new b2Vec2(); b2GearJoint.SolvePositionConstraints_s_rA = new b2Vec2(); b2GearJoint.SolvePositionConstraints_s_rB = new b2Vec2(); b2GearJoint.SolvePositionConstraints_s_rC = new b2Vec2(); b2GearJoint.SolvePositionConstraints_s_rD = new b2Vec2(); /* * Copyright (c) 2006-2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /* Position Correction Notes ========================= I tried the several algorithms for position correction of the 2D revolute joint. I looked at these systems: - simple pendulum (1m diameter sphere on massless 5m stick) with initial angular velocity of 100 rad/s. - suspension bridge with 30 1m long planks of length 1m. - multi-link chain with 30 1m long links. Here are the algorithms: Baumgarte - A fraction of the position error is added to the velocity error. There is no separate position solver. Pseudo Velocities - After the velocity solver and position integration, the position error, Jacobian, and effective mass are recomputed. Then the velocity constraints are solved with pseudo velocities and a fraction of the position error is added to the pseudo velocity error. The pseudo velocities are initialized to zero and there is no warm-starting. After the position solver, the pseudo velocities are added to the positions. This is also called the First Order World method or the Position LCP method. Modified Nonlinear Gauss-Seidel (NGS) - Like Pseudo Velocities except the position error is re-computed for each constraint and the positions are updated after the constraint is solved. The radius vectors (aka Jacobians) are re-computed too (otherwise the algorithm has horrible instability). The pseudo velocity states are not needed because they are effectively zero at the beginning of each iteration. Since we have the current position error, we allow the iterations to terminate early if the error becomes smaller than b2_linearSlop. Full NGS or just NGS - Like Modified NGS except the effective mass are re-computed each time a constraint is solved. Here are the results: Baumgarte - this is the cheapest algorithm but it has some stability problems, especially with the bridge. The chain links separate easily close to the root and they jitter as they struggle to pull together. This is one of the most common methods in the field. The big drawback is that the position correction artificially affects the momentum, thus leading to instabilities and false bounce. I used a bias factor of 0.2. A larger bias factor makes the bridge less stable, a smaller factor makes joints and contacts more spongy. Pseudo Velocities - the is more stable than the Baumgarte method. The bridge is stable. However, joints still separate with large angular velocities. Drag the simple pendulum in a circle quickly and the joint will separate. The chain separates easily and does not recover. I used a bias factor of 0.2. A larger value lead to the bridge collapsing when a heavy cube drops on it. Modified NGS - this algorithm is better in some ways than Baumgarte and Pseudo Velocities, but in other ways it is worse. The bridge and chain are much more stable, but the simple pendulum goes unstable at high angular velocities. Full NGS - stable in all tests. The joints display good stiffness. The bridge still sags, but this is better than infinite forces. Recommendations Pseudo Velocities are not really worthwhile because the bridge and chain cannot recover from joint separation. In other cases the benefit over Baumgarte is small. Modified NGS is not a robust method for the revolute joint due to the violent instability seen in the simple pendulum. Perhaps it is viable with other constraint types, especially scalar constraints where the effective mass is a scalar. This leaves Baumgarte and Full NGS. Baumgarte has small, but manageable instabilities and is very fast. I don't think we can escape Baumgarte, especially in highly demanding cases where high constraint fidelity is not needed. Full NGS is robust and easy on the eyes. I recommend this as an option for higher fidelity simulation and certainly for suspension bridges and long chains. Full NGS might be a good choice for ragdolls, especially motorized ragdolls where joint separation can be problematic. The number of NGS iterations can be reduced for better performance without harming robustness much. Each joint in a can be handled differently in the position solver. So I recommend a system where the user can select the algorithm on a per joint basis. I would probably default to the slower Full NGS and let the user select the faster Baumgarte method in performance critical scenarios. */ /* Cache Performance The Box2D solvers are dominated by cache misses. Data structures are designed to increase the number of cache hits. Much of misses are due to random access to body data. The constraint structures are iterated over linearly, which leads to few cache misses. The bodies are not accessed during iteration. Instead read only data, such as the mass values are stored with the constraints. The mutable data are the constraint impulses and the bodies velocities/positions. The impulses are held inside the constraint structures. The body velocities/positions are held in compact, temporary arrays to increase the number of cache hits. Linear and angular velocity are stored in a single array since multiple arrays lead to multiple misses. */ /* 2D Rotation R = [cos(theta) -sin(theta)] [sin(theta) cos(theta) ] thetaDot = omega Let q1 = cos(theta), q2 = sin(theta). R = [q1 -q2] [q2 q1] q1Dot = -thetaDot * q2 q2Dot = thetaDot * q1 q1_new = q1_old - dt * w * q2 q2_new = q2_old + dt * w * q1 then normalize. This might be faster than computing sin+cos. However, we can compute sin+cos of the same angle fast. */ class b2Island { constructor() { this.m_bodies = [ /*1024*/]; // TODO: b2Settings this.m_contacts = [ /*1024*/]; // TODO: b2Settings this.m_joints = [ /*1024*/]; // TODO: b2Settings this.m_positions = b2Position.MakeArray(1024); // TODO: b2Settings this.m_velocities = b2Velocity.MakeArray(1024); // TODO: b2Settings this.m_bodyCount = 0; this.m_jointCount = 0; this.m_contactCount = 0; this.m_bodyCapacity = 0; this.m_contactCapacity = 0; this.m_jointCapacity = 0; } Initialize(bodyCapacity, contactCapacity, jointCapacity, listener) { this.m_bodyCapacity = bodyCapacity; this.m_contactCapacity = contactCapacity; this.m_jointCapacity = jointCapacity; this.m_bodyCount = 0; this.m_contactCount = 0; this.m_jointCount = 0; this.m_listener = listener; // TODO: // while (this.m_bodies.length < bodyCapacity) { // this.m_bodies[this.m_bodies.length] = null; // } // TODO: // while (this.m_contacts.length < contactCapacity) { // this.m_contacts[this.m_contacts.length] = null; // } // TODO: // while (this.m_joints.length < jointCapacity) { // this.m_joints[this.m_joints.length] = null; // } // TODO: if (this.m_positions.length < bodyCapacity) { const new_length = b2Max(this.m_positions.length * 2, bodyCapacity); while (this.m_positions.length < new_length) { this.m_positions[this.m_positions.length] = new b2Position(); } } // TODO: if (this.m_velocities.length < bodyCapacity) { const new_length = b2Max(this.m_velocities.length * 2, bodyCapacity); while (this.m_velocities.length < new_length) { this.m_velocities[this.m_velocities.length] = new b2Velocity(); } } } Clear() { this.m_bodyCount = 0; this.m_contactCount = 0; this.m_jointCount = 0; } AddBody(body) { // DEBUG: b2Assert(this.m_bodyCount < this.m_bodyCapacity); body.m_islandIndex = this.m_bodyCount; this.m_bodies[this.m_bodyCount++] = body; } AddContact(contact) { // DEBUG: b2Assert(this.m_contactCount < this.m_contactCapacity); this.m_contacts[this.m_contactCount++] = contact; } AddJoint(joint) { // DEBUG: b2Assert(this.m_jointCount < this.m_jointCapacity); this.m_joints[this.m_jointCount++] = joint; } Solve(profile, step, gravity, allowSleep) { const timer = b2Island.s_timer.Reset(); const h = step.dt; // Integrate velocities and apply damping. Initialize the body state. for (let i = 0; i < this.m_bodyCount; ++i) { const b = this.m_bodies[i]; // const c: b2Vec2 = this.m_positions[i].c.Copy(b.m_sweep.c); const a = b.m_sweep.a; const v = this.m_velocities[i].v.Copy(b.m_linearVelocity); let w = b.m_angularVelocity; // Store positions for continuous collision. b.m_sweep.c0.Copy(b.m_sweep.c); b.m_sweep.a0 = b.m_sweep.a; if (b.m_type === exports.b2BodyType.b2_dynamicBody) { // Integrate velocities. // v += h * b->m_invMass * (b->m_gravityScale * b->m_mass * gravity + b->m_force); v.x += h * b.m_invMass * (b.m_gravityScale * b.m_mass * gravity.x + b.m_force.x); v.y += h * b.m_invMass * (b.m_gravityScale * b.m_mass * gravity.y + b.m_force.y); w += h * b.m_invI * b.m_torque; // 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 // Pade approximation: // v2 = v1 * 1 / (1 + c * dt) v.SelfMul(1.0 / (1.0 + h * b.m_linearDamping)); w *= 1.0 / (1.0 + h * b.m_angularDamping); } // this.m_positions[i].c = c; this.m_positions[i].a = a; // this.m_velocities[i].v = v; this.m_velocities[i].w = w; } timer.Reset(); // Solver data const solverData = b2Island.s_solverData; solverData.step.Copy(step); solverData.positions = this.m_positions; solverData.velocities = this.m_velocities; // Initialize velocity constraints. const contactSolverDef = b2Island.s_contactSolverDef; contactSolverDef.step.Copy(step); contactSolverDef.contacts = this.m_contacts; contactSolverDef.count = this.m_contactCount; contactSolverDef.positions = this.m_positions; contactSolverDef.velocities = this.m_velocities; const contactSolver = b2Island.s_contactSolver.Initialize(contactSolverDef); contactSolver.InitializeVelocityConstraints(); if (step.warmStarting) { contactSolver.WarmStart(); } for (let i = 0; i < this.m_jointCount; ++i) { this.m_joints[i].InitVelocityConstraints(solverData); } profile.solveInit = timer.GetMilliseconds(); // Solve velocity constraints. timer.Reset(); for (let i = 0; i < step.velocityIterations; ++i) { for (let j = 0; j < this.m_jointCount; ++j) { this.m_joints[j].SolveVelocityConstraints(solverData); } contactSolver.SolveVelocityConstraints(); } // Store impulses for warm starting contactSolver.StoreImpulses(); profile.solveVelocity = timer.GetMilliseconds(); // Integrate positions. for (let i = 0; i < this.m_bodyCount; ++i) { const c = this.m_positions[i].c; let a = this.m_positions[i].a; const v = this.m_velocities[i].v; let w = this.m_velocities[i].w; // Check for large velocities const translation = b2Vec2.MulSV(h, v, b2Island.s_translation); if (b2Vec2.DotVV(translation, translation) > b2_maxTranslationSquared) { const ratio = b2_maxTranslation / translation.Length(); v.SelfMul(ratio); } const rotation = h * w; if (rotation * rotation > b2_maxRotationSquared) { const ratio = b2_maxRotation / b2Abs(rotation); w *= ratio; } // Integrate c.x += h * v.x; c.y += h * v.y; a += h * w; // this.m_positions[i].c = c; this.m_positions[i].a = a; // this.m_velocities[i].v = v; this.m_velocities[i].w = w; } // Solve position constraints timer.Reset(); let positionSolved = false; for (let i = 0; i < step.positionIterations; ++i) { const contactsOkay = contactSolver.SolvePositionConstraints(); let jointsOkay = true; for (let j = 0; j < this.m_jointCount; ++j) { const jointOkay = this.m_joints[j].SolvePositionConstraints(solverData); jointsOkay = jointsOkay && jointOkay; } if (contactsOkay && jointsOkay) { // Exit early if the position errors are small. positionSolved = true; break; } } // Copy state buffers back to the bodies for (let i = 0; i < this.m_bodyCount; ++i) { const body = this.m_bodies[i]; body.m_sweep.c.Copy(this.m_positions[i].c); body.m_sweep.a = this.m_positions[i].a; body.m_linearVelocity.Copy(this.m_velocities[i].v); body.m_angularVelocity = this.m_velocities[i].w; body.SynchronizeTransform(); } profile.solvePosition = timer.GetMilliseconds(); this.Report(contactSolver.m_velocityConstraints); if (allowSleep) { let minSleepTime = b2_maxFloat; const linTolSqr = b2_linearSleepTolerance * b2_linearSleepTolerance; const angTolSqr = b2_angularSleepTolerance * b2_angularSleepTolerance; for (let i = 0; i < this.m_bodyCount; ++i) { const b = this.m_bodies[i]; if (b.GetType() === exports.b2BodyType.b2_staticBody) { continue; } if (!b.m_autoSleepFlag || b.m_angularVelocity * b.m_angularVelocity > angTolSqr || b2Vec2.DotVV(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr) { b.m_sleepTime = 0; minSleepTime = 0; } else { b.m_sleepTime += h; minSleepTime = b2Min(minSleepTime, b.m_sleepTime); } } if (minSleepTime >= b2_timeToSleep && positionSolved) { for (let i = 0; i < this.m_bodyCount; ++i) { const b = this.m_bodies[i]; b.SetAwake(false); } } } } SolveTOI(subStep, toiIndexA, toiIndexB) { // DEBUG: b2Assert(toiIndexA < this.m_bodyCount); // DEBUG: b2Assert(toiIndexB < this.m_bodyCount); // Initialize the body state. for (let i = 0; i < this.m_bodyCount; ++i) { const b = this.m_bodies[i]; this.m_positions[i].c.Copy(b.m_sweep.c); this.m_positions[i].a = b.m_sweep.a; this.m_velocities[i].v.Copy(b.m_linearVelocity); this.m_velocities[i].w = b.m_angularVelocity; } const contactSolverDef = b2Island.s_contactSolverDef; contactSolverDef.contacts = this.m_contacts; contactSolverDef.count = this.m_contactCount; contactSolverDef.step.Copy(subStep); contactSolverDef.positions = this.m_positions; contactSolverDef.velocities = this.m_velocities; const contactSolver = b2Island.s_contactSolver.Initialize(contactSolverDef); // Solve position constraints. for (let i = 0; i < subStep.positionIterations; ++i) { const contactsOkay = contactSolver.SolveTOIPositionConstraints(toiIndexA, toiIndexB); if (contactsOkay) { break; } } /* #if 0 // Is the new position really safe? for (int32 i = 0; i < this.m_contactCount; ++i) { b2Contact* c = this.m_contacts[i]; b2Fixture* fA = c.GetFixtureA(); b2Fixture* fB = c.GetFixtureB(); b2Body* bA = fA.GetBody(); b2Body* bB = fB.GetBody(); int32 indexA = c.GetChildIndexA(); int32 indexB = c.GetChildIndexB(); b2DistanceInput input; input.proxyA.Set(fA.GetShape(), indexA); input.proxyB.Set(fB.GetShape(), indexB); input.transformA = bA.GetTransform(); input.transformB = bB.GetTransform(); input.useRadii = false; b2DistanceOutput output; b2SimplexCache cache; cache.count = 0; b2Distance(&output, &cache, &input); if (output.distance === 0 || cache.count === 3) { cache.count += 0; } } #endif */ // Leap of faith to new safe state. this.m_bodies[toiIndexA].m_sweep.c0.Copy(this.m_positions[toiIndexA].c); this.m_bodies[toiIndexA].m_sweep.a0 = this.m_positions[toiIndexA].a; this.m_bodies[toiIndexB].m_sweep.c0.Copy(this.m_positions[toiIndexB].c); this.m_bodies[toiIndexB].m_sweep.a0 = this.m_positions[toiIndexB].a; // No warm starting is needed for TOI events because warm // starting impulses were applied in the discrete solver. contactSolver.InitializeVelocityConstraints(); // Solve velocity constraints. for (let i = 0; i < subStep.velocityIterations; ++i) { contactSolver.SolveVelocityConstraints(); } // Don't store the TOI contact forces for warm starting // because they can be quite large. const h = subStep.dt; // Integrate positions for (let i = 0; i < this.m_bodyCount; ++i) { const c = this.m_positions[i].c; let a = this.m_positions[i].a; const v = this.m_velocities[i].v; let w = this.m_velocities[i].w; // Check for large velocities const translation = b2Vec2.MulSV(h, v, b2Island.s_translation); if (b2Vec2.DotVV(translation, translation) > b2_maxTranslationSquared) { const ratio = b2_maxTranslation / translation.Length(); v.SelfMul(ratio); } const rotation = h * w; if (rotation * rotation > b2_maxRotationSquared) { const ratio = b2_maxRotation / b2Abs(rotation); w *= ratio; } // Integrate c.SelfMulAdd(h, v); a += h * w; // this.m_positions[i].c = c; this.m_positions[i].a = a; // this.m_velocities[i].v = v; this.m_velocities[i].w = w; // Sync bodies const body = this.m_bodies[i]; body.m_sweep.c.Copy(c); body.m_sweep.a = a; body.m_linearVelocity.Copy(v); body.m_angularVelocity = w; body.SynchronizeTransform(); } this.Report(contactSolver.m_velocityConstraints); } Report(constraints) { if (this.m_listener === null) { return; } for (let i = 0; i < this.m_contactCount; ++i) { const c = this.m_contacts[i]; if (!c) { continue; } const vc = constraints[i]; const impulse = b2Island.s_impulse; impulse.count = vc.pointCount; for (let j = 0; j < vc.pointCount; ++j) { impulse.normalImpulses[j] = vc.points[j].normalImpulse; impulse.tangentImpulses[j] = vc.points[j].tangentImpulse; } this.m_listener.PostSolve(c, impulse); } } } b2Island.s_timer = new b2Timer(); b2Island.s_solverData = new b2SolverData(); b2Island.s_contactSolverDef = new b2ContactSolverDef(); b2Island.s_contactSolver = new b2ContactSolver(); b2Island.s_translation = new b2Vec2(); b2Island.s_impulse = new b2ContactImpulse(); /* * Copyright (c) 2006-2012 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ class b2MotorJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_motorJoint); this.linearOffset = new b2Vec2(0, 0); this.angularOffset = 0; this.maxForce = 1; this.maxTorque = 1; this.correctionFactor = 0.3; } Initialize(bA, bB) { this.bodyA = bA; this.bodyB = bB; // b2Vec2 xB = bodyB->GetPosition(); // linearOffset = bodyA->GetLocalPoint(xB); this.bodyA.GetLocalPoint(this.bodyB.GetPosition(), this.linearOffset); const angleA = this.bodyA.GetAngle(); const angleB = this.bodyB.GetAngle(); this.angularOffset = angleB - angleA; } } class b2MotorJoint extends b2Joint { constructor(def) { super(def); // Solver shared this.m_linearOffset = new b2Vec2(); this.m_angularOffset = 0; this.m_linearImpulse = new b2Vec2(); this.m_angularImpulse = 0; this.m_maxForce = 0; this.m_maxTorque = 0; this.m_correctionFactor = 0.3; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_linearError = new b2Vec2(); this.m_angularError = 0; this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_linearMass = new b2Mat22(); this.m_angularMass = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_K = new b2Mat22(); this.m_linearOffset.Copy(b2Maybe(def.linearOffset, b2Vec2.ZERO)); this.m_angularOffset = b2Maybe(def.angularOffset, 0); this.m_linearImpulse.SetZero(); this.m_maxForce = b2Maybe(def.maxForce, 0); this.m_maxTorque = b2Maybe(def.maxTorque, 0); this.m_correctionFactor = b2Maybe(def.correctionFactor, 0.3); } GetAnchorA(out) { const pos = this.m_bodyA.GetPosition(); out.x = pos.x; out.y = pos.y; return out; } GetAnchorB(out) { const pos = this.m_bodyB.GetPosition(); out.x = pos.x; out.y = pos.y; return out; } GetReactionForce(inv_dt, out) { // return inv_dt * m_linearImpulse; return b2Vec2.MulSV(inv_dt, this.m_linearImpulse, out); } GetReactionTorque(inv_dt) { return inv_dt * this.m_angularImpulse; } SetLinearOffset(linearOffset) { if (!b2Vec2.IsEqualToV(linearOffset, this.m_linearOffset)) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_linearOffset.Copy(linearOffset); } } GetLinearOffset() { return this.m_linearOffset; } SetAngularOffset(angularOffset) { if (angularOffset !== this.m_angularOffset) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_angularOffset = angularOffset; } } GetAngularOffset() { return this.m_angularOffset; } SetMaxForce(force) { // DEBUG: b2Assert(b2IsValid(force) && force >= 0); this.m_maxForce = force; } GetMaxForce() { return this.m_maxForce; } SetMaxTorque(torque) { // DEBUG: b2Assert(b2IsValid(torque) && torque >= 0); this.m_maxTorque = torque; } GetMaxTorque() { return this.m_maxTorque; } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; const cA = data.positions[this.m_indexA].c; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const cB = data.positions[this.m_indexB].c; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // Compute the effective mass matrix. // this.m_rA = b2Mul(qA, m_linearOffset - this.m_localCenterA); const rA = b2Rot.MulRV(qA, b2Vec2.SubVV(this.m_linearOffset, this.m_localCenterA, b2Vec2.s_t0), this.m_rA); // this.m_rB = b2Mul(qB, -this.m_localCenterB); const rB = b2Rot.MulRV(qB, b2Vec2.NegV(this.m_localCenterB, b2Vec2.s_t0), this.m_rB); // J = [-I -r1_skew I r2_skew] // r_skew = [-ry; rx] // Matlab // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB] // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB] // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB] const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; // Upper 2 by 2 of K for point to point const K = this.m_K; K.ex.x = mA + mB + iA * rA.y * rA.y + iB * rB.y * rB.y; K.ex.y = -iA * rA.x * rA.y - iB * rB.x * rB.y; K.ey.x = K.ex.y; K.ey.y = mA + mB + iA * rA.x * rA.x + iB * rB.x * rB.x; // this.m_linearMass = K.GetInverse(); K.GetInverse(this.m_linearMass); this.m_angularMass = iA + iB; if (this.m_angularMass > 0) { this.m_angularMass = 1 / this.m_angularMass; } // this.m_linearError = cB + rB - cA - rA; b2Vec2.SubVV(b2Vec2.AddVV(cB, rB, b2Vec2.s_t0), b2Vec2.AddVV(cA, rA, b2Vec2.s_t1), this.m_linearError); this.m_angularError = aB - aA - this.m_angularOffset; if (data.step.warmStarting) { // Scale impulses to support a variable time step. // this.m_linearImpulse *= data.step.dtRatio; this.m_linearImpulse.SelfMul(data.step.dtRatio); this.m_angularImpulse *= data.step.dtRatio; // b2Vec2 P(this.m_linearImpulse.x, this.m_linearImpulse.y); const P = this.m_linearImpulse; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * (b2Vec2.CrossVV(rA, P) + this.m_angularImpulse); // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * (b2Vec2.CrossVV(rB, P) + this.m_angularImpulse); } else { this.m_linearImpulse.SetZero(); this.m_angularImpulse = 0; } // data.velocities[this.m_indexA].v = vA; // vA is a reference data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; // vB is a reference data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const h = data.step.dt; const inv_h = data.step.inv_dt; // Solve angular friction { const Cdot = wB - wA + inv_h * this.m_correctionFactor * this.m_angularError; let impulse = -this.m_angularMass * Cdot; const oldImpulse = this.m_angularImpulse; const maxImpulse = h * this.m_maxTorque; this.m_angularImpulse = b2Clamp(this.m_angularImpulse + impulse, -maxImpulse, maxImpulse); impulse = this.m_angularImpulse - oldImpulse; wA -= iA * impulse; wB += iB * impulse; } // Solve linear friction { const rA = this.m_rA; const rB = this.m_rB; // b2Vec2 Cdot = vB + b2Vec2.CrossSV(wB, rB) - vA - b2Vec2.CrossSV(wA, rA) + inv_h * this.m_correctionFactor * this.m_linearError; const Cdot_v2 = b2Vec2.AddVV(b2Vec2.SubVV(b2Vec2.AddVV(vB, b2Vec2.CrossSV(wB, rB, b2Vec2.s_t0), b2Vec2.s_t0), b2Vec2.AddVV(vA, b2Vec2.CrossSV(wA, rA, b2Vec2.s_t1), b2Vec2.s_t1), b2Vec2.s_t2), b2Vec2.MulSV(inv_h * this.m_correctionFactor, this.m_linearError, b2Vec2.s_t3), b2MotorJoint.SolveVelocityConstraints_s_Cdot_v2); // b2Vec2 impulse = -b2Mul(this.m_linearMass, Cdot); const impulse_v2 = b2Mat22.MulMV(this.m_linearMass, Cdot_v2, b2MotorJoint.SolveVelocityConstraints_s_impulse_v2).SelfNeg(); // b2Vec2 oldImpulse = this.m_linearImpulse; const oldImpulse_v2 = b2MotorJoint.SolveVelocityConstraints_s_oldImpulse_v2.Copy(this.m_linearImpulse); // this.m_linearImpulse += impulse; this.m_linearImpulse.SelfAdd(impulse_v2); const maxImpulse = h * this.m_maxForce; if (this.m_linearImpulse.LengthSquared() > maxImpulse * maxImpulse) { this.m_linearImpulse.Normalize(); // this.m_linearImpulse *= maxImpulse; this.m_linearImpulse.SelfMul(maxImpulse); } // impulse = this.m_linearImpulse - oldImpulse; b2Vec2.SubVV(this.m_linearImpulse, oldImpulse_v2, impulse_v2); // vA -= mA * impulse; vA.SelfMulSub(mA, impulse_v2); // wA -= iA * b2Vec2.CrossVV(rA, impulse); wA -= iA * b2Vec2.CrossVV(rA, impulse_v2); // vB += mB * impulse; vB.SelfMulAdd(mB, impulse_v2); // wB += iB * b2Vec2.CrossVV(rB, impulse); wB += iB * b2Vec2.CrossVV(rB, impulse_v2); } // data.velocities[this.m_indexA].v = vA; // vA is a reference data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; // vB is a reference data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { return true; } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2MotorJointDef = new b2MotorJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.linearOffset.Set(%.15f, %.15f);\n", this.m_linearOffset.x, this.m_linearOffset.y); log(" jd.angularOffset = %.15f;\n", this.m_angularOffset); log(" jd.maxForce = %.15f;\n", this.m_maxForce); log(" jd.maxTorque = %.15f;\n", this.m_maxTorque); log(" jd.correctionFactor = %.15f;\n", this.m_correctionFactor); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } } b2MotorJoint.SolveVelocityConstraints_s_Cdot_v2 = new b2Vec2(); b2MotorJoint.SolveVelocityConstraints_s_impulse_v2 = new b2Vec2(); b2MotorJoint.SolveVelocityConstraints_s_oldImpulse_v2 = new b2Vec2(); /* * Copyright (c) 2006-2007 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Mouse joint definition. This requires a world target point, /// tuning parameters, and the time step. class b2MouseJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_mouseJoint); this.target = new b2Vec2(); this.maxForce = 0; this.stiffness = 5; this.damping = 0.7; } } class b2MouseJoint extends b2Joint { constructor(def) { super(def); this.m_localAnchorB = new b2Vec2(); this.m_targetA = new b2Vec2(); this.m_stiffness = 0; this.m_damping = 0; this.m_beta = 0; // Solver shared this.m_impulse = new b2Vec2(); this.m_maxForce = 0; this.m_gamma = 0; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_rB = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_invMassB = 0; this.m_invIB = 0; this.m_mass = new b2Mat22(); this.m_C = new b2Vec2(); this.m_qB = new b2Rot(); this.m_lalcB = new b2Vec2(); this.m_K = new b2Mat22(); this.m_targetA.Copy(b2Maybe(def.target, b2Vec2.ZERO)); // DEBUG: b2Assert(this.m_targetA.IsValid()); b2Transform.MulTXV(this.m_bodyB.GetTransform(), this.m_targetA, this.m_localAnchorB); this.m_maxForce = b2Maybe(def.maxForce, 0); // DEBUG: b2Assert(b2IsValid(this.m_maxForce) && this.m_maxForce >= 0); this.m_impulse.SetZero(); this.m_stiffness = b2Maybe(def.stiffness, 0); // DEBUG: b2Assert(b2IsValid(this.m_stiffness) && this.m_stiffness >= 0); this.m_damping = b2Maybe(def.damping, 0); // DEBUG: b2Assert(b2IsValid(this.m_damping) && this.m_damping >= 0); this.m_beta = 0; this.m_gamma = 0; } SetTarget(target) { if (!this.m_bodyB.IsAwake()) { this.m_bodyB.SetAwake(true); } this.m_targetA.Copy(target); } GetTarget() { return this.m_targetA; } SetMaxForce(maxForce) { this.m_maxForce = maxForce; } GetMaxForce() { return this.m_maxForce; } SetStiffness(stiffness) { this.m_stiffness = stiffness; } GetStiffness() { return this.m_stiffness; } SetDamping(damping) { this.m_damping = damping; } GetDamping() { return this.m_damping; } InitVelocityConstraints(data) { this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIB = this.m_bodyB.m_invI; const cB = data.positions[this.m_indexB].c; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const qB = this.m_qB.SetAngle(aB); const mass = this.m_bodyB.GetMass(); // Frequency const omega = 2 * b2_pi * this.m_stiffness; // Damping coefficient const d = 2 * mass * this.m_damping * omega; // Spring stiffness const k = mass * (omega * omega); // magic formulas // gamma has units of inverse mass. // beta has units of inverse time. const h = data.step.dt; this.m_gamma = h * (d + h * k); if (this.m_gamma !== 0) { this.m_gamma = 1 / this.m_gamma; } this.m_beta = h * k * this.m_gamma; // Compute the effective mass matrix. b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * [r1.y*r1.y -r1.x*r1.y] // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x] const K = this.m_K; K.ex.x = this.m_invMassB + this.m_invIB * this.m_rB.y * this.m_rB.y + this.m_gamma; K.ex.y = -this.m_invIB * this.m_rB.x * this.m_rB.y; K.ey.x = K.ex.y; K.ey.y = this.m_invMassB + this.m_invIB * this.m_rB.x * this.m_rB.x + this.m_gamma; K.GetInverse(this.m_mass); // m_C = cB + m_rB - m_targetA; this.m_C.x = cB.x + this.m_rB.x - this.m_targetA.x; this.m_C.y = cB.y + this.m_rB.y - this.m_targetA.y; // m_C *= m_beta; this.m_C.SelfMul(this.m_beta); // Cheat with some damping wB *= 0.98; if (data.step.warmStarting) { this.m_impulse.SelfMul(data.step.dtRatio); // vB += m_invMassB * m_impulse; vB.x += this.m_invMassB * this.m_impulse.x; vB.y += this.m_invMassB * this.m_impulse.y; wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, this.m_impulse); } else { this.m_impulse.SetZero(); } // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; // Cdot = v + cross(w, r) // b2Vec2 Cdot = vB + b2Cross(wB, m_rB); const Cdot = b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2MouseJoint.SolveVelocityConstraints_s_Cdot); // b2Vec2 impulse = b2Mul(m_mass, -(Cdot + m_C + m_gamma * m_impulse)); const impulse = b2Mat22.MulMV(this.m_mass, b2Vec2.AddVV(Cdot, b2Vec2.AddVV(this.m_C, b2Vec2.MulSV(this.m_gamma, this.m_impulse, b2Vec2.s_t0), b2Vec2.s_t0), b2Vec2.s_t0).SelfNeg(), b2MouseJoint.SolveVelocityConstraints_s_impulse); // b2Vec2 oldImpulse = m_impulse; const oldImpulse = b2MouseJoint.SolveVelocityConstraints_s_oldImpulse.Copy(this.m_impulse); // m_impulse += impulse; this.m_impulse.SelfAdd(impulse); const maxImpulse = data.step.dt * this.m_maxForce; if (this.m_impulse.LengthSquared() > maxImpulse * maxImpulse) { this.m_impulse.SelfMul(maxImpulse / this.m_impulse.Length()); } // impulse = m_impulse - oldImpulse; b2Vec2.SubVV(this.m_impulse, oldImpulse, impulse); // vB += m_invMassB * impulse; vB.SelfMulAdd(this.m_invMassB, impulse); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, impulse); // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { return true; } GetAnchorA(out) { out.x = this.m_targetA.x; out.y = this.m_targetA.y; return out; } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { return b2Vec2.MulSV(inv_dt, this.m_impulse, out); } GetReactionTorque(inv_dt) { return 0; } Dump(log) { log("Mouse joint dumping is not supported.\n"); } ShiftOrigin(newOrigin) { this.m_targetA.SelfSub(newOrigin); } } b2MouseJoint.SolveVelocityConstraints_s_Cdot = new b2Vec2(); b2MouseJoint.SolveVelocityConstraints_s_impulse = new b2Vec2(); b2MouseJoint.SolveVelocityConstraints_s_oldImpulse = new b2Vec2(); /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Prismatic joint definition. This requires defining a line of /// motion using an axis and an anchor point. The definition uses local /// anchor points and a local axis so that the initial configuration /// can violate the constraint slightly. The joint translation is zero /// when the local anchor points coincide in world space. Using local /// anchors and a local axis helps when saving and loading a game. class b2PrismaticJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_prismaticJoint); this.localAnchorA = new b2Vec2(); this.localAnchorB = new b2Vec2(); this.localAxisA = new b2Vec2(1, 0); this.referenceAngle = 0; this.enableLimit = false; this.lowerTranslation = 0; this.upperTranslation = 0; this.enableMotor = false; this.maxMotorForce = 0; this.motorSpeed = 0; } Initialize(bA, bB, anchor, axis) { this.bodyA = bA; this.bodyB = bB; this.bodyA.GetLocalPoint(anchor, this.localAnchorA); this.bodyB.GetLocalPoint(anchor, this.localAnchorB); this.bodyA.GetLocalVector(axis, this.localAxisA); this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle(); } } // Linear constraint (point-to-line) // d = p2 - p1 = x2 + r2 - x1 - r1 // C = dot(perp, d) // Cdot = dot(d, cross(w1, perp)) + dot(perp, v2 + cross(w2, r2) - v1 - cross(w1, r1)) // = -dot(perp, v1) - dot(cross(d + r1, perp), w1) + dot(perp, v2) + dot(cross(r2, perp), v2) // J = [-perp, -cross(d + r1, perp), perp, cross(r2,perp)] // // Angular constraint // C = a2 - a1 + a_initial // Cdot = w2 - w1 // J = [0 0 -1 0 0 1] // // K = J * invM * JT // // J = [-a -s1 a s2] // [0 -1 0 1] // a = perp // s1 = cross(d + r1, a) = cross(p2 - x1, a) // s2 = cross(r2, a) = cross(p2 - x2, a) // Motor/Limit linear constraint // C = dot(ax1, d) // Cdot = -dot(ax1, v1) - dot(cross(d + r1, ax1), w1) + dot(ax1, v2) + dot(cross(r2, ax1), v2) // J = [-ax1 -cross(d+r1,ax1) ax1 cross(r2,ax1)] // Predictive limit is applied even when the limit is not active. // Prevents a constraint speed that can lead to a constraint error in one time step. // Want C2 = C1 + h * Cdot >= 0 // Or: // Cdot + C1/h >= 0 // I do not apply a negative constraint error because that is handled in position correction. // So: // Cdot + max(C1, 0)/h >= 0 // Block Solver // We develop a block solver that includes the angular and linear constraints. This makes the limit stiffer. // // The Jacobian has 2 rows: // J = [-uT -s1 uT s2] // linear // [0 -1 0 1] // angular // // u = perp // s1 = cross(d + r1, u), s2 = cross(r2, u) // a1 = cross(d + r1, v), a2 = cross(r2, v) class b2PrismaticJoint extends b2Joint { constructor(def) { super(def); this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); this.m_localXAxisA = new b2Vec2(); this.m_localYAxisA = new b2Vec2(); this.m_referenceAngle = 0; this.m_impulse = new b2Vec2(0, 0); this.m_motorImpulse = 0; this.m_lowerImpulse = 0; this.m_upperImpulse = 0; this.m_lowerTranslation = 0; this.m_upperTranslation = 0; this.m_maxMotorForce = 0; this.m_motorSpeed = 0; this.m_enableLimit = false; this.m_enableMotor = false; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_axis = new b2Vec2(0, 0); this.m_perp = new b2Vec2(0, 0); this.m_s1 = 0; this.m_s2 = 0; this.m_a1 = 0; this.m_a2 = 0; this.m_K = new b2Mat22(); this.m_K3 = new b2Mat33(); this.m_K2 = new b2Mat22(); this.m_translation = 0; this.m_axialMass = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localAnchorA.Copy(b2Maybe(def.localAnchorA, b2Vec2.ZERO)); this.m_localAnchorB.Copy(b2Maybe(def.localAnchorB, b2Vec2.ZERO)); this.m_localXAxisA.Copy(b2Maybe(def.localAxisA, new b2Vec2(1, 0))).SelfNormalize(); b2Vec2.CrossOneV(this.m_localXAxisA, this.m_localYAxisA); this.m_referenceAngle = b2Maybe(def.referenceAngle, 0); this.m_lowerTranslation = b2Maybe(def.lowerTranslation, 0); this.m_upperTranslation = b2Maybe(def.upperTranslation, 0); // b2Assert(this.m_lowerTranslation <= this.m_upperTranslation); this.m_maxMotorForce = b2Maybe(def.maxMotorForce, 0); this.m_motorSpeed = b2Maybe(def.motorSpeed, 0); this.m_enableLimit = b2Maybe(def.enableLimit, false); this.m_enableMotor = b2Maybe(def.enableMotor, false); } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; const cA = data.positions[this.m_indexA].c; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const cB = data.positions[this.m_indexB].c; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // Compute the effective masses. // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // b2Vec2 d = (cB - cA) + rB - rA; const d = b2Vec2.AddVV(b2Vec2.SubVV(cB, cA, b2Vec2.s_t0), b2Vec2.SubVV(rB, rA, b2Vec2.s_t1), b2PrismaticJoint.InitVelocityConstraints_s_d); const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; // Compute motor Jacobian and effective mass. { // m_axis = b2Mul(qA, m_localXAxisA); b2Rot.MulRV(qA, this.m_localXAxisA, this.m_axis); // m_a1 = b2Cross(d + rA, m_axis); this.m_a1 = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), this.m_axis); // m_a2 = b2Cross(rB, m_axis); this.m_a2 = b2Vec2.CrossVV(rB, this.m_axis); this.m_axialMass = mA + mB + iA * this.m_a1 * this.m_a1 + iB * this.m_a2 * this.m_a2; if (this.m_axialMass > 0) { this.m_axialMass = 1 / this.m_axialMass; } } // Prismatic constraint. { // m_perp = b2Mul(qA, m_localYAxisA); b2Rot.MulRV(qA, this.m_localYAxisA, this.m_perp); // m_s1 = b2Cross(d + rA, m_perp); this.m_s1 = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), this.m_perp); // m_s2 = b2Cross(rB, m_perp); this.m_s2 = b2Vec2.CrossVV(rB, this.m_perp); // float32 k11 = mA + mB + iA * m_s1 * m_s1 + iB * m_s2 * m_s2; this.m_K.ex.x = mA + mB + iA * this.m_s1 * this.m_s1 + iB * this.m_s2 * this.m_s2; // float32 k12 = iA * m_s1 + iB * m_s2; this.m_K.ex.y = iA * this.m_s1 + iB * this.m_s2; this.m_K.ey.x = this.m_K.ex.y; // float32 k22 = iA + iB; this.m_K.ey.y = iA + iB; if (this.m_K.ey.y === 0) { // For bodies with fixed rotation. this.m_K.ey.y = 1; } // m_K.ex.Set(k11, k12); // m_K.ey.Set(k12, k22); } // Compute motor and limit terms. if (this.m_enableLimit) { this.m_translation = b2Vec2.DotVV(this.m_axis, d); } else { this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } if (!this.m_enableMotor) { this.m_motorImpulse = 0; } if (data.step.warmStarting) { // Account for variable time step. // m_impulse *= data.step.dtRatio; this.m_impulse.SelfMul(data.step.dtRatio); this.m_motorImpulse *= data.step.dtRatio; this.m_lowerImpulse *= data.step.dtRatio; this.m_upperImpulse *= data.step.dtRatio; const axialImpulse = this.m_motorImpulse + this.m_lowerImpulse - this.m_upperImpulse; // b2Vec2 P = m_impulse.x * m_perp + axialImpulse * m_axis; const P = b2Vec2.AddVV(b2Vec2.MulSV(this.m_impulse.x, this.m_perp, b2Vec2.s_t0), b2Vec2.MulSV(axialImpulse, this.m_axis, b2Vec2.s_t1), b2PrismaticJoint.InitVelocityConstraints_s_P); // float LA = m_impulse.x * m_s1 + m_impulse.y + axialImpulse * m_a1; const LA = this.m_impulse.x * this.m_s1 + this.m_impulse.y + axialImpulse * this.m_a1; // float LB = m_impulse.x * m_s2 + m_impulse.y + axialImpulse * m_a2; const LB = this.m_impulse.x * this.m_s2 + this.m_impulse.y + axialImpulse * this.m_a2; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * LA; // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * LB; } else { this.m_impulse.SetZero(); this.m_motorImpulse = 0.0; this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; // Solve linear motor constraint. if (this.m_enableMotor) { // float32 Cdot = b2Dot(m_axis, vB - vA) + m_a2 * wB - m_a1 * wA; const Cdot = b2Vec2.DotVV(this.m_axis, b2Vec2.SubVV(vB, vA, b2Vec2.s_t0)) + this.m_a2 * wB - this.m_a1 * wA; let impulse = this.m_axialMass * (this.m_motorSpeed - Cdot); const oldImpulse = this.m_motorImpulse; const maxImpulse = data.step.dt * this.m_maxMotorForce; this.m_motorImpulse = b2Clamp(this.m_motorImpulse + impulse, (-maxImpulse), maxImpulse); impulse = this.m_motorImpulse - oldImpulse; // b2Vec2 P = impulse * m_axis; const P = b2Vec2.MulSV(impulse, this.m_axis, b2PrismaticJoint.SolveVelocityConstraints_s_P); const LA = impulse * this.m_a1; const LB = impulse * this.m_a2; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * LA; // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * LB; } if (this.m_enableLimit) { // Lower limit { const C = this.m_translation - this.m_lowerTranslation; const Cdot = b2Vec2.DotVV(this.m_axis, b2Vec2.SubVV(vB, vA, b2Vec2.s_t0)) + this.m_a2 * wB - this.m_a1 * wA; let impulse = -this.m_axialMass * (Cdot + b2Max(C, 0.0) * data.step.inv_dt); const oldImpulse = this.m_lowerImpulse; this.m_lowerImpulse = b2Max(this.m_lowerImpulse + impulse, 0.0); impulse = this.m_lowerImpulse - oldImpulse; // b2Vec2 P = impulse * this.m_axis; const P = b2Vec2.MulSV(impulse, this.m_axis, b2PrismaticJoint.SolveVelocityConstraints_s_P); const LA = impulse * this.m_a1; const LB = impulse * this.m_a2; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * LA; // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * LB; } // Upper limit // Note: signs are flipped to keep C positive when the constraint is satisfied. // This also keeps the impulse positive when the limit is active. { const C = this.m_upperTranslation - this.m_translation; const Cdot = b2Vec2.DotVV(this.m_axis, b2Vec2.SubVV(vA, vB, b2Vec2.s_t0)) + this.m_a1 * wA - this.m_a2 * wB; let impulse = -this.m_axialMass * (Cdot + b2Max(C, 0.0) * data.step.inv_dt); const oldImpulse = this.m_upperImpulse; this.m_upperImpulse = b2Max(this.m_upperImpulse + impulse, 0.0); impulse = this.m_upperImpulse - oldImpulse; // b2Vec2 P = impulse * this.m_axis; const P = b2Vec2.MulSV(impulse, this.m_axis, b2PrismaticJoint.SolveVelocityConstraints_s_P); const LA = impulse * this.m_a1; const LB = impulse * this.m_a2; // vA += mA * P; vA.SelfMulAdd(mA, P); wA += iA * LA; // vB -= mB * P; vB.SelfMulSub(mB, P); wB -= iB * LB; } } // Solve the prismatic constraint in block form. { // b2Vec2 Cdot; // Cdot.x = b2Dot(m_perp, vB - vA) + m_s2 * wB - m_s1 * wA; const Cdot_x = b2Vec2.DotVV(this.m_perp, b2Vec2.SubVV(vB, vA, b2Vec2.s_t0)) + this.m_s2 * wB - this.m_s1 * wA; // Cdot.y = wB - wA; const Cdot_y = wB - wA; // b2Vec2 df = m_K.Solve(-Cdot); const df = this.m_K.Solve(-Cdot_x, -Cdot_y, b2PrismaticJoint.SolveVelocityConstraints_s_df); // m_impulse += df; this.m_impulse.SelfAdd(df); // b2Vec2 P = df.x * m_perp; const P = b2Vec2.MulSV(df.x, this.m_perp, b2PrismaticJoint.SolveVelocityConstraints_s_P); // float32 LA = df.x * m_s1 + df.y; const LA = df.x * this.m_s1 + df.y; // float32 LB = df.x * m_s2 + df.y; const LB = df.x * this.m_s2 + df.y; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * LA; // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * LB; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { const cA = data.positions[this.m_indexA].c; let aA = data.positions[this.m_indexA].a; const cB = data.positions[this.m_indexB].c; let aB = data.positions[this.m_indexB].a; const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // b2Vec2 d = cB + rB - cA - rA; const d = b2Vec2.SubVV(b2Vec2.AddVV(cB, rB, b2Vec2.s_t0), b2Vec2.AddVV(cA, rA, b2Vec2.s_t1), b2PrismaticJoint.SolvePositionConstraints_s_d); // b2Vec2 axis = b2Mul(qA, m_localXAxisA); const axis = b2Rot.MulRV(qA, this.m_localXAxisA, this.m_axis); // float32 a1 = b2Cross(d + rA, axis); const a1 = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), axis); // float32 a2 = b2Cross(rB, axis); const a2 = b2Vec2.CrossVV(rB, axis); // b2Vec2 perp = b2Mul(qA, m_localYAxisA); const perp = b2Rot.MulRV(qA, this.m_localYAxisA, this.m_perp); // float32 s1 = b2Cross(d + rA, perp); const s1 = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), perp); // float32 s2 = b2Cross(rB, perp); const s2 = b2Vec2.CrossVV(rB, perp); // b2Vec3 impulse; let impulse = b2PrismaticJoint.SolvePositionConstraints_s_impulse; // b2Vec2 C1; // C1.x = b2Dot(perp, d); const C1_x = b2Vec2.DotVV(perp, d); // C1.y = aB - aA - m_referenceAngle; const C1_y = aB - aA - this.m_referenceAngle; let linearError = b2Abs(C1_x); const angularError = b2Abs(C1_y); let active = false; let C2 = 0; if (this.m_enableLimit) { // float32 translation = b2Dot(axis, d); const translation = b2Vec2.DotVV(axis, d); if (b2Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2 * b2_linearSlop) { C2 = translation; linearError = b2Max(linearError, b2Abs(translation)); active = true; } else if (translation <= this.m_lowerTranslation) { C2 = b2Min(translation - this.m_lowerTranslation, 0.0); linearError = b2Max(linearError, this.m_lowerTranslation - translation); active = true; } else if (translation >= this.m_upperTranslation) { C2 = b2Max(translation - this.m_upperTranslation, 0.0); linearError = b2Max(linearError, translation - this.m_upperTranslation); active = true; } } if (active) { // float32 k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2; const k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2; // float32 k12 = iA * s1 + iB * s2; const k12 = iA * s1 + iB * s2; // float32 k13 = iA * s1 * a1 + iB * s2 * a2; const k13 = iA * s1 * a1 + iB * s2 * a2; // float32 k22 = iA + iB; let k22 = iA + iB; if (k22 === 0) { // For fixed rotation k22 = 1; } // float32 k23 = iA * a1 + iB * a2; const k23 = iA * a1 + iB * a2; // float32 k33 = mA + mB + iA * a1 * a1 + iB * a2 * a2; const k33 = mA + mB + iA * a1 * a1 + iB * a2 * a2; // b2Mat33 K; const K = this.m_K3; // K.ex.Set(k11, k12, k13); K.ex.SetXYZ(k11, k12, k13); // K.ey.Set(k12, k22, k23); K.ey.SetXYZ(k12, k22, k23); // K.ez.Set(k13, k23, k33); K.ez.SetXYZ(k13, k23, k33); // b2Vec3 C; // C.x = C1.x; // C.y = C1.y; // C.z = C2; // impulse = K.Solve33(-C); impulse = K.Solve33((-C1_x), (-C1_y), (-C2), impulse); } else { // float32 k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2; const k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2; // float32 k12 = iA * s1 + iB * s2; const k12 = iA * s1 + iB * s2; // float32 k22 = iA + iB; let k22 = iA + iB; if (k22 === 0) { k22 = 1; } // b2Mat22 K; const K2 = this.m_K2; // K.ex.Set(k11, k12); K2.ex.Set(k11, k12); // K.ey.Set(k12, k22); K2.ey.Set(k12, k22); // b2Vec2 impulse1 = K.Solve(-C1); const impulse1 = K2.Solve((-C1_x), (-C1_y), b2PrismaticJoint.SolvePositionConstraints_s_impulse1); impulse.x = impulse1.x; impulse.y = impulse1.y; impulse.z = 0; } // b2Vec2 P = impulse.x * perp + impulse.z * axis; const P = b2Vec2.AddVV(b2Vec2.MulSV(impulse.x, perp, b2Vec2.s_t0), b2Vec2.MulSV(impulse.z, axis, b2Vec2.s_t1), b2PrismaticJoint.SolvePositionConstraints_s_P); // float32 LA = impulse.x * s1 + impulse.y + impulse.z * a1; const LA = impulse.x * s1 + impulse.y + impulse.z * a1; // float32 LB = impulse.x * s2 + impulse.y + impulse.z * a2; const LB = impulse.x * s2 + impulse.y + impulse.z * a2; // cA -= mA * P; cA.SelfMulSub(mA, P); aA -= iA * LA; // cB += mB * P; cB.SelfMulAdd(mB, P); aB += iB * LB; // data.positions[this.m_indexA].c = cA; data.positions[this.m_indexA].a = aA; // data.positions[this.m_indexB].c = cB; data.positions[this.m_indexB].a = aB; return linearError <= b2_linearSlop && angularError <= b2_angularSlop; } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { out.x = inv_dt * (this.m_impulse.x * this.m_perp.x + (this.m_motorImpulse + this.m_lowerImpulse - this.m_upperImpulse) * this.m_axis.x); out.y = inv_dt * (this.m_impulse.y * this.m_perp.y + (this.m_motorImpulse + this.m_lowerImpulse - this.m_upperImpulse) * this.m_axis.y); return out; } GetReactionTorque(inv_dt) { return inv_dt * this.m_impulse.y; } GetLocalAnchorA() { return this.m_localAnchorA; } GetLocalAnchorB() { return this.m_localAnchorB; } GetLocalAxisA() { return this.m_localXAxisA; } GetReferenceAngle() { return this.m_referenceAngle; } GetJointTranslation() { // b2Vec2 pA = m_bodyA.GetWorldPoint(m_localAnchorA); const pA = this.m_bodyA.GetWorldPoint(this.m_localAnchorA, b2PrismaticJoint.GetJointTranslation_s_pA); // b2Vec2 pB = m_bodyB.GetWorldPoint(m_localAnchorB); const pB = this.m_bodyB.GetWorldPoint(this.m_localAnchorB, b2PrismaticJoint.GetJointTranslation_s_pB); // b2Vec2 d = pB - pA; const d = b2Vec2.SubVV(pB, pA, b2PrismaticJoint.GetJointTranslation_s_d); // b2Vec2 axis = m_bodyA.GetWorldVector(m_localXAxisA); const axis = this.m_bodyA.GetWorldVector(this.m_localXAxisA, b2PrismaticJoint.GetJointTranslation_s_axis); // float32 translation = b2Dot(d, axis); const translation = b2Vec2.DotVV(d, axis); return translation; } GetJointSpeed() { const bA = this.m_bodyA; const bB = this.m_bodyB; // b2Vec2 rA = b2Mul(bA->m_xf.q, m_localAnchorA - bA->m_sweep.localCenter); b2Vec2.SubVV(this.m_localAnchorA, bA.m_sweep.localCenter, this.m_lalcA); const rA = b2Rot.MulRV(bA.m_xf.q, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(bB->m_xf.q, m_localAnchorB - bB->m_sweep.localCenter); b2Vec2.SubVV(this.m_localAnchorB, bB.m_sweep.localCenter, this.m_lalcB); const rB = b2Rot.MulRV(bB.m_xf.q, this.m_lalcB, this.m_rB); // b2Vec2 pA = bA->m_sweep.c + rA; const pA = b2Vec2.AddVV(bA.m_sweep.c, rA, b2Vec2.s_t0); // pA uses s_t0 // b2Vec2 pB = bB->m_sweep.c + rB; const pB = b2Vec2.AddVV(bB.m_sweep.c, rB, b2Vec2.s_t1); // pB uses s_t1 // b2Vec2 d = pB - pA; const d = b2Vec2.SubVV(pB, pA, b2Vec2.s_t2); // d uses s_t2 // b2Vec2 axis = b2Mul(bA.m_xf.q, m_localXAxisA); const axis = bA.GetWorldVector(this.m_localXAxisA, this.m_axis); const vA = bA.m_linearVelocity; const vB = bB.m_linearVelocity; const wA = bA.m_angularVelocity; const wB = bB.m_angularVelocity; // float32 speed = b2Dot(d, b2Cross(wA, axis)) + b2Dot(axis, vB + b2Cross(wB, rB) - vA - b2Cross(wA, rA)); const speed = b2Vec2.DotVV(d, b2Vec2.CrossSV(wA, axis, b2Vec2.s_t0)) + b2Vec2.DotVV(axis, b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, rA, b2Vec2.s_t1), b2Vec2.s_t0)); return speed; } IsLimitEnabled() { return this.m_enableLimit; } EnableLimit(flag) { if (flag !== this.m_enableLimit) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_enableLimit = flag; this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } } GetLowerLimit() { return this.m_lowerTranslation; } GetUpperLimit() { return this.m_upperTranslation; } SetLimits(lower, upper) { if (lower !== this.m_lowerTranslation || upper !== this.m_upperTranslation) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_lowerTranslation = lower; this.m_upperTranslation = upper; this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } } IsMotorEnabled() { return this.m_enableMotor; } EnableMotor(flag) { if (flag !== this.m_enableMotor) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_enableMotor = flag; } } SetMotorSpeed(speed) { if (speed !== this.m_motorSpeed) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_motorSpeed = speed; } } GetMotorSpeed() { return this.m_motorSpeed; } SetMaxMotorForce(force) { if (force !== this.m_maxMotorForce) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_maxMotorForce = force; } } GetMaxMotorForce() { return this.m_maxMotorForce; } GetMotorForce(inv_dt) { return inv_dt * this.m_motorImpulse; } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2PrismaticJointDef = new b2PrismaticJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.localAnchorA.Set(%.15f, %.15f);\n", this.m_localAnchorA.x, this.m_localAnchorA.y); log(" jd.localAnchorB.Set(%.15f, %.15f);\n", this.m_localAnchorB.x, this.m_localAnchorB.y); log(" jd.localAxisA.Set(%.15f, %.15f);\n", this.m_localXAxisA.x, this.m_localXAxisA.y); log(" jd.referenceAngle = %.15f;\n", this.m_referenceAngle); log(" jd.enableLimit = %s;\n", (this.m_enableLimit) ? ("true") : ("false")); log(" jd.lowerTranslation = %.15f;\n", this.m_lowerTranslation); log(" jd.upperTranslation = %.15f;\n", this.m_upperTranslation); log(" jd.enableMotor = %s;\n", (this.m_enableMotor) ? ("true") : ("false")); log(" jd.motorSpeed = %.15f;\n", this.m_motorSpeed); log(" jd.maxMotorForce = %.15f;\n", this.m_maxMotorForce); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } Draw(draw) { const xfA = this.m_bodyA.GetTransform(); const xfB = this.m_bodyB.GetTransform(); const pA = b2Transform.MulXV(xfA, this.m_localAnchorA, b2PrismaticJoint.Draw_s_pA); const pB = b2Transform.MulXV(xfB, this.m_localAnchorB, b2PrismaticJoint.Draw_s_pB); // b2Vec2 axis = b2Mul(xfA.q, m_localXAxisA); const axis = b2Rot.MulRV(xfA.q, this.m_localXAxisA, b2PrismaticJoint.Draw_s_axis); const c1 = b2PrismaticJoint.Draw_s_c1; // b2Color c1(0.7f, 0.7f, 0.7f); const c2 = b2PrismaticJoint.Draw_s_c2; // b2Color c2(0.3f, 0.9f, 0.3f); const c3 = b2PrismaticJoint.Draw_s_c3; // b2Color c3(0.9f, 0.3f, 0.3f); const c4 = b2PrismaticJoint.Draw_s_c4; // b2Color c4(0.3f, 0.3f, 0.9f); const c5 = b2PrismaticJoint.Draw_s_c5; // b2Color c5(0.4f, 0.4f, 0.4f); draw.DrawSegment(pA, pB, c5); if (this.m_enableLimit) { // b2Vec2 lower = pA + m_lowerTranslation * axis; const lower = b2Vec2.AddVMulSV(pA, this.m_lowerTranslation, axis, b2PrismaticJoint.Draw_s_lower); // b2Vec2 upper = pA + m_upperTranslation * axis; const upper = b2Vec2.AddVMulSV(pA, this.m_upperTranslation, axis, b2PrismaticJoint.Draw_s_upper); // b2Vec2 perp = b2Mul(xfA.q, m_localYAxisA); const perp = b2Rot.MulRV(xfA.q, this.m_localYAxisA, b2PrismaticJoint.Draw_s_perp); draw.DrawSegment(lower, upper, c1); // draw.DrawSegment(lower - 0.5 * perp, lower + 0.5 * perp, c2); draw.DrawSegment(b2Vec2.AddVMulSV(lower, -0.5, perp, b2Vec2.s_t0), b2Vec2.AddVMulSV(lower, 0.5, perp, b2Vec2.s_t1), c2); // draw.DrawSegment(upper - 0.5 * perp, upper + 0.5 * perp, c3); draw.DrawSegment(b2Vec2.AddVMulSV(upper, -0.5, perp, b2Vec2.s_t0), b2Vec2.AddVMulSV(upper, 0.5, perp, b2Vec2.s_t1), c3); } else { // draw.DrawSegment(pA - 1.0 * axis, pA + 1.0 * axis, c1); draw.DrawSegment(b2Vec2.AddVMulSV(pA, -1.0, axis, b2Vec2.s_t0), b2Vec2.AddVMulSV(pA, 1.0, axis, b2Vec2.s_t1), c1); } draw.DrawPoint(pA, 5.0, c1); draw.DrawPoint(pB, 5.0, c4); } } b2PrismaticJoint.InitVelocityConstraints_s_d = new b2Vec2(); b2PrismaticJoint.InitVelocityConstraints_s_P = new b2Vec2(); b2PrismaticJoint.SolveVelocityConstraints_s_P = new b2Vec2(); // private static SolveVelocityConstraints_s_f2r = new b2Vec2(); // private static SolveVelocityConstraints_s_f1 = new b2Vec3(); // private static SolveVelocityConstraints_s_df3 = new b2Vec3(); b2PrismaticJoint.SolveVelocityConstraints_s_df = new b2Vec2(); // A velocity based solver computes reaction forces(impulses) using the velocity constraint solver.Under this context, // the position solver is not there to resolve forces.It is only there to cope with integration error. // // Therefore, the pseudo impulses in the position solver do not have any physical meaning.Thus it is okay if they suck. // // We could take the active state from the velocity solver.However, the joint might push past the limit when the velocity // solver indicates the limit is inactive. b2PrismaticJoint.SolvePositionConstraints_s_d = new b2Vec2(); b2PrismaticJoint.SolvePositionConstraints_s_impulse = new b2Vec3(); b2PrismaticJoint.SolvePositionConstraints_s_impulse1 = new b2Vec2(); b2PrismaticJoint.SolvePositionConstraints_s_P = new b2Vec2(); b2PrismaticJoint.GetJointTranslation_s_pA = new b2Vec2(); b2PrismaticJoint.GetJointTranslation_s_pB = new b2Vec2(); b2PrismaticJoint.GetJointTranslation_s_d = new b2Vec2(); b2PrismaticJoint.GetJointTranslation_s_axis = new b2Vec2(); b2PrismaticJoint.Draw_s_pA = new b2Vec2(); b2PrismaticJoint.Draw_s_pB = new b2Vec2(); b2PrismaticJoint.Draw_s_axis = new b2Vec2(); b2PrismaticJoint.Draw_s_c1 = new b2Color(0.7, 0.7, 0.7); b2PrismaticJoint.Draw_s_c2 = new b2Color(0.3, 0.9, 0.3); b2PrismaticJoint.Draw_s_c3 = new b2Color(0.9, 0.3, 0.3); b2PrismaticJoint.Draw_s_c4 = new b2Color(0.3, 0.3, 0.9); b2PrismaticJoint.Draw_s_c5 = new b2Color(0.4, 0.4, 0.4); b2PrismaticJoint.Draw_s_lower = new b2Vec2(); b2PrismaticJoint.Draw_s_upper = new b2Vec2(); b2PrismaticJoint.Draw_s_perp = new b2Vec2(); /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ const b2_minPulleyLength = 2; /// Pulley joint definition. This requires two ground anchors, /// two dynamic body anchor points, and a pulley ratio. class b2PulleyJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_pulleyJoint); this.groundAnchorA = new b2Vec2(-1, 1); this.groundAnchorB = new b2Vec2(1, 1); this.localAnchorA = new b2Vec2(-1, 0); this.localAnchorB = new b2Vec2(1, 0); this.lengthA = 0; this.lengthB = 0; this.ratio = 1; this.collideConnected = true; } Initialize(bA, bB, groundA, groundB, anchorA, anchorB, r) { this.bodyA = bA; this.bodyB = bB; this.groundAnchorA.Copy(groundA); this.groundAnchorB.Copy(groundB); this.bodyA.GetLocalPoint(anchorA, this.localAnchorA); this.bodyB.GetLocalPoint(anchorB, this.localAnchorB); this.lengthA = b2Vec2.DistanceVV(anchorA, groundA); this.lengthB = b2Vec2.DistanceVV(anchorB, groundB); this.ratio = r; // DEBUG: b2Assert(this.ratio > b2_epsilon); } } class b2PulleyJoint extends b2Joint { constructor(def) { super(def); this.m_groundAnchorA = new b2Vec2(); this.m_groundAnchorB = new b2Vec2(); this.m_lengthA = 0; this.m_lengthB = 0; // Solver shared this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); this.m_constant = 0; this.m_ratio = 0; this.m_impulse = 0; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_uA = new b2Vec2(); this.m_uB = new b2Vec2(); this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_mass = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_groundAnchorA.Copy(b2Maybe(def.groundAnchorA, new b2Vec2(-1, 1))); this.m_groundAnchorB.Copy(b2Maybe(def.groundAnchorB, new b2Vec2(1, 0))); this.m_localAnchorA.Copy(b2Maybe(def.localAnchorA, new b2Vec2(-1, 0))); this.m_localAnchorB.Copy(b2Maybe(def.localAnchorB, new b2Vec2(1, 0))); this.m_lengthA = b2Maybe(def.lengthA, 0); this.m_lengthB = b2Maybe(def.lengthB, 0); // DEBUG: b2Assert(b2Maybe(def.ratio, 1) !== 0); this.m_ratio = b2Maybe(def.ratio, 1); this.m_constant = b2Maybe(def.lengthA, 0) + this.m_ratio * b2Maybe(def.lengthB, 0); this.m_impulse = 0; } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; const cA = data.positions[this.m_indexA].c; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const cB = data.positions[this.m_indexB].c; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; // b2Rot qA(aA), qB(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // m_rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // m_rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // Get the pulley axes. // m_uA = cA + m_rA - m_groundAnchorA; this.m_uA.Copy(cA).SelfAdd(this.m_rA).SelfSub(this.m_groundAnchorA); // m_uB = cB + m_rB - m_groundAnchorB; this.m_uB.Copy(cB).SelfAdd(this.m_rB).SelfSub(this.m_groundAnchorB); const lengthA = this.m_uA.Length(); const lengthB = this.m_uB.Length(); if (lengthA > 10 * b2_linearSlop) { this.m_uA.SelfMul(1 / lengthA); } else { this.m_uA.SetZero(); } if (lengthB > 10 * b2_linearSlop) { this.m_uB.SelfMul(1 / lengthB); } else { this.m_uB.SetZero(); } // Compute effective mass. const ruA = b2Vec2.CrossVV(this.m_rA, this.m_uA); const ruB = b2Vec2.CrossVV(this.m_rB, this.m_uB); const mA = this.m_invMassA + this.m_invIA * ruA * ruA; const mB = this.m_invMassB + this.m_invIB * ruB * ruB; this.m_mass = mA + this.m_ratio * this.m_ratio * mB; if (this.m_mass > 0) { this.m_mass = 1 / this.m_mass; } if (data.step.warmStarting) { // Scale impulses to support variable time steps. this.m_impulse *= data.step.dtRatio; // Warm starting. // b2Vec2 PA = -(m_impulse) * m_uA; const PA = b2Vec2.MulSV(-(this.m_impulse), this.m_uA, b2PulleyJoint.InitVelocityConstraints_s_PA); // b2Vec2 PB = (-m_ratio * m_impulse) * m_uB; const PB = b2Vec2.MulSV((-this.m_ratio * this.m_impulse), this.m_uB, b2PulleyJoint.InitVelocityConstraints_s_PB); // vA += m_invMassA * PA; vA.SelfMulAdd(this.m_invMassA, PA); wA += this.m_invIA * b2Vec2.CrossVV(this.m_rA, PA); // vB += m_invMassB * PB; vB.SelfMulAdd(this.m_invMassB, PB); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, PB); } else { this.m_impulse = 0; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; // b2Vec2 vpA = vA + b2Cross(wA, m_rA); const vpA = b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2PulleyJoint.SolveVelocityConstraints_s_vpA); // b2Vec2 vpB = vB + b2Cross(wB, m_rB); const vpB = b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2PulleyJoint.SolveVelocityConstraints_s_vpB); const Cdot = -b2Vec2.DotVV(this.m_uA, vpA) - this.m_ratio * b2Vec2.DotVV(this.m_uB, vpB); const impulse = -this.m_mass * Cdot; this.m_impulse += impulse; // b2Vec2 PA = -impulse * m_uA; const PA = b2Vec2.MulSV(-impulse, this.m_uA, b2PulleyJoint.SolveVelocityConstraints_s_PA); // b2Vec2 PB = -m_ratio * impulse * m_uB; const PB = b2Vec2.MulSV(-this.m_ratio * impulse, this.m_uB, b2PulleyJoint.SolveVelocityConstraints_s_PB); // vA += m_invMassA * PA; vA.SelfMulAdd(this.m_invMassA, PA); wA += this.m_invIA * b2Vec2.CrossVV(this.m_rA, PA); // vB += m_invMassB * PB; vB.SelfMulAdd(this.m_invMassB, PB); wB += this.m_invIB * b2Vec2.CrossVV(this.m_rB, PB); // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { const cA = data.positions[this.m_indexA].c; let aA = data.positions[this.m_indexA].a; const cB = data.positions[this.m_indexB].c; let aB = data.positions[this.m_indexB].a; // b2Rot qA(aA), qB(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // Get the pulley axes. // b2Vec2 uA = cA + rA - m_groundAnchorA; const uA = this.m_uA.Copy(cA).SelfAdd(rA).SelfSub(this.m_groundAnchorA); // b2Vec2 uB = cB + rB - m_groundAnchorB; const uB = this.m_uB.Copy(cB).SelfAdd(rB).SelfSub(this.m_groundAnchorB); const lengthA = uA.Length(); const lengthB = uB.Length(); if (lengthA > 10 * b2_linearSlop) { uA.SelfMul(1 / lengthA); } else { uA.SetZero(); } if (lengthB > 10 * b2_linearSlop) { uB.SelfMul(1 / lengthB); } else { uB.SetZero(); } // Compute effective mass. const ruA = b2Vec2.CrossVV(rA, uA); const ruB = b2Vec2.CrossVV(rB, uB); const mA = this.m_invMassA + this.m_invIA * ruA * ruA; const mB = this.m_invMassB + this.m_invIB * ruB * ruB; let mass = mA + this.m_ratio * this.m_ratio * mB; if (mass > 0) { mass = 1 / mass; } const C = this.m_constant - lengthA - this.m_ratio * lengthB; const linearError = b2Abs(C); const impulse = -mass * C; // b2Vec2 PA = -impulse * uA; const PA = b2Vec2.MulSV(-impulse, uA, b2PulleyJoint.SolvePositionConstraints_s_PA); // b2Vec2 PB = -m_ratio * impulse * uB; const PB = b2Vec2.MulSV(-this.m_ratio * impulse, uB, b2PulleyJoint.SolvePositionConstraints_s_PB); // cA += m_invMassA * PA; cA.SelfMulAdd(this.m_invMassA, PA); aA += this.m_invIA * b2Vec2.CrossVV(rA, PA); // cB += m_invMassB * PB; cB.SelfMulAdd(this.m_invMassB, PB); aB += this.m_invIB * b2Vec2.CrossVV(rB, PB); // data.positions[this.m_indexA].c = cA; data.positions[this.m_indexA].a = aA; // data.positions[this.m_indexB].c = cB; data.positions[this.m_indexB].a = aB; return linearError < b2_linearSlop; } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { // b2Vec2 P = m_impulse * m_uB; // return inv_dt * P; out.x = inv_dt * this.m_impulse * this.m_uB.x; out.y = inv_dt * this.m_impulse * this.m_uB.y; return out; } GetReactionTorque(inv_dt) { return 0; } GetGroundAnchorA() { return this.m_groundAnchorA; } GetGroundAnchorB() { return this.m_groundAnchorB; } GetLengthA() { return this.m_lengthA; } GetLengthB() { return this.m_lengthB; } GetRatio() { return this.m_ratio; } GetCurrentLengthA() { // b2Vec2 p = m_bodyA->GetWorldPoint(m_localAnchorA); // b2Vec2 s = m_groundAnchorA; // b2Vec2 d = p - s; // return d.Length(); const p = this.m_bodyA.GetWorldPoint(this.m_localAnchorA, b2PulleyJoint.GetCurrentLengthA_s_p); const s = this.m_groundAnchorA; return b2Vec2.DistanceVV(p, s); } GetCurrentLengthB() { // b2Vec2 p = m_bodyB->GetWorldPoint(m_localAnchorB); // b2Vec2 s = m_groundAnchorB; // b2Vec2 d = p - s; // return d.Length(); const p = this.m_bodyB.GetWorldPoint(this.m_localAnchorB, b2PulleyJoint.GetCurrentLengthB_s_p); const s = this.m_groundAnchorB; return b2Vec2.DistanceVV(p, s); } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2PulleyJointDef = new b2PulleyJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.groundAnchorA.Set(%.15f, %.15f);\n", this.m_groundAnchorA.x, this.m_groundAnchorA.y); log(" jd.groundAnchorB.Set(%.15f, %.15f);\n", this.m_groundAnchorB.x, this.m_groundAnchorB.y); log(" jd.localAnchorA.Set(%.15f, %.15f);\n", this.m_localAnchorA.x, this.m_localAnchorA.y); log(" jd.localAnchorB.Set(%.15f, %.15f);\n", this.m_localAnchorB.x, this.m_localAnchorB.y); log(" jd.lengthA = %.15f;\n", this.m_lengthA); log(" jd.lengthB = %.15f;\n", this.m_lengthB); log(" jd.ratio = %.15f;\n", this.m_ratio); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } ShiftOrigin(newOrigin) { this.m_groundAnchorA.SelfSub(newOrigin); this.m_groundAnchorB.SelfSub(newOrigin); } } b2PulleyJoint.InitVelocityConstraints_s_PA = new b2Vec2(); b2PulleyJoint.InitVelocityConstraints_s_PB = new b2Vec2(); b2PulleyJoint.SolveVelocityConstraints_s_vpA = new b2Vec2(); b2PulleyJoint.SolveVelocityConstraints_s_vpB = new b2Vec2(); b2PulleyJoint.SolveVelocityConstraints_s_PA = new b2Vec2(); b2PulleyJoint.SolveVelocityConstraints_s_PB = new b2Vec2(); b2PulleyJoint.SolvePositionConstraints_s_PA = new b2Vec2(); b2PulleyJoint.SolvePositionConstraints_s_PB = new b2Vec2(); b2PulleyJoint.GetCurrentLengthA_s_p = new b2Vec2(); b2PulleyJoint.GetCurrentLengthB_s_p = new b2Vec2(); /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Revolute joint definition. This requires defining an anchor point where the /// bodies are joined. The definition uses local anchor points so that the /// initial configuration can violate the constraint slightly. You also need to /// specify the initial relative angle for joint limits. This helps when saving /// and loading a game. /// The local anchor points are measured from the body's origin /// rather than the center of mass because: /// 1. you might not know where the center of mass will be. /// 2. if you add/remove shapes from a body and recompute the mass, /// the joints will be broken. class b2RevoluteJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_revoluteJoint); this.localAnchorA = new b2Vec2(0, 0); this.localAnchorB = new b2Vec2(0, 0); this.referenceAngle = 0; this.enableLimit = false; this.lowerAngle = 0; this.upperAngle = 0; this.enableMotor = false; this.motorSpeed = 0; this.maxMotorTorque = 0; } Initialize(bA, bB, anchor) { this.bodyA = bA; this.bodyB = bB; this.bodyA.GetLocalPoint(anchor, this.localAnchorA); this.bodyB.GetLocalPoint(anchor, this.localAnchorB); this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle(); } } class b2RevoluteJoint extends b2Joint { constructor(def) { super(def); // Solver shared this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); this.m_impulse = new b2Vec2(); this.m_motorImpulse = 0; this.m_lowerImpulse = 0; this.m_upperImpulse = 0; this.m_enableMotor = false; this.m_maxMotorTorque = 0; this.m_motorSpeed = 0; this.m_enableLimit = false; this.m_referenceAngle = 0; this.m_lowerAngle = 0; this.m_upperAngle = 0; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_K = new b2Mat22(); this.m_angle = 0; this.m_axialMass = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_localAnchorA.Copy(b2Maybe(def.localAnchorA, b2Vec2.ZERO)); this.m_localAnchorB.Copy(b2Maybe(def.localAnchorB, b2Vec2.ZERO)); this.m_referenceAngle = b2Maybe(def.referenceAngle, 0); this.m_impulse.SetZero(); this.m_motorImpulse = 0; this.m_lowerAngle = b2Maybe(def.lowerAngle, 0); this.m_upperAngle = b2Maybe(def.upperAngle, 0); this.m_maxMotorTorque = b2Maybe(def.maxMotorTorque, 0); this.m_motorSpeed = b2Maybe(def.motorSpeed, 0); this.m_enableLimit = b2Maybe(def.enableLimit, false); this.m_enableMotor = b2Maybe(def.enableMotor, false); } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; // b2Rot qA(aA), qB(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // m_rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // m_rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // J = [-I -r1_skew I r2_skew] // r_skew = [-ry; rx] // Matlab // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x] // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB] const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; this.m_K.ex.x = mA + mB + this.m_rA.y * this.m_rA.y * iA + this.m_rB.y * this.m_rB.y * iB; this.m_K.ey.x = -this.m_rA.y * this.m_rA.x * iA - this.m_rB.y * this.m_rB.x * iB; this.m_K.ex.y = this.m_K.ey.x; this.m_K.ey.y = mA + mB + this.m_rA.x * this.m_rA.x * iA + this.m_rB.x * this.m_rB.x * iB; this.m_axialMass = iA + iB; let fixedRotation; if (this.m_axialMass > 0.0) { this.m_axialMass = 1.0 / this.m_axialMass; fixedRotation = false; } else { fixedRotation = true; } this.m_angle = aB - aA - this.m_referenceAngle; if (this.m_enableLimit === false || fixedRotation) { this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } if (this.m_enableMotor === false || fixedRotation) { this.m_motorImpulse = 0.0; } if (data.step.warmStarting) { // Scale impulses to support a variable time step. this.m_impulse.SelfMul(data.step.dtRatio); this.m_motorImpulse *= data.step.dtRatio; this.m_lowerImpulse *= data.step.dtRatio; this.m_upperImpulse *= data.step.dtRatio; const axialImpulse = this.m_motorImpulse + this.m_lowerImpulse - this.m_upperImpulse; // b2Vec2 P(m_impulse.x, m_impulse.y); const P = b2RevoluteJoint.InitVelocityConstraints_s_P.Set(this.m_impulse.x, this.m_impulse.y); // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * (b2Vec2.CrossVV(this.m_rA, P) + axialImpulse); // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * (b2Vec2.CrossVV(this.m_rB, P) + axialImpulse); } else { this.m_impulse.SetZero(); this.m_motorImpulse = 0; this.m_lowerImpulse = 0; this.m_upperImpulse = 0; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const fixedRotation = (iA + iB === 0); // Solve motor constraint. if (this.m_enableMotor && !fixedRotation) { const Cdot = wB - wA - this.m_motorSpeed; let impulse = -this.m_axialMass * Cdot; const oldImpulse = this.m_motorImpulse; const maxImpulse = data.step.dt * this.m_maxMotorTorque; this.m_motorImpulse = b2Clamp(this.m_motorImpulse + impulse, -maxImpulse, maxImpulse); impulse = this.m_motorImpulse - oldImpulse; wA -= iA * impulse; wB += iB * impulse; } // Solve limit constraint. if (this.m_enableLimit && !fixedRotation) { // Lower limit { const C = this.m_angle - this.m_lowerAngle; const Cdot = wB - wA; let impulse = -this.m_axialMass * (Cdot + b2Max(C, 0.0) * data.step.inv_dt); const oldImpulse = this.m_lowerImpulse; this.m_lowerImpulse = b2Max(this.m_lowerImpulse + impulse, 0.0); impulse = this.m_lowerImpulse - oldImpulse; wA -= iA * impulse; wB += iB * impulse; } // Upper limit // Note: signs are flipped to keep C positive when the constraint is satisfied. // This also keeps the impulse positive when the limit is active. { const C = this.m_upperAngle - this.m_angle; const Cdot = wA - wB; let impulse = -this.m_axialMass * (Cdot + b2Max(C, 0.0) * data.step.inv_dt); const oldImpulse = this.m_upperImpulse; this.m_upperImpulse = b2Max(this.m_upperImpulse + impulse, 0.0); impulse = this.m_upperImpulse - oldImpulse; wA += iA * impulse; wB -= iB * impulse; } } // Solve point-to-point constraint { // b2Vec2 Cdot = vB + b2Cross(wB, m_rB) - vA - b2Cross(wA, m_rA); const Cdot_v2 = b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2Vec2.s_t1), b2RevoluteJoint.SolveVelocityConstraints_s_Cdot_v2); // b2Vec2 impulse = m_K.Solve(-Cdot); const impulse_v2 = this.m_K.Solve(-Cdot_v2.x, -Cdot_v2.y, b2RevoluteJoint.SolveVelocityConstraints_s_impulse_v2); this.m_impulse.x += impulse_v2.x; this.m_impulse.y += impulse_v2.y; // vA -= mA * impulse; vA.SelfMulSub(mA, impulse_v2); wA -= iA * b2Vec2.CrossVV(this.m_rA, impulse_v2); // vB += mB * impulse; vB.SelfMulAdd(mB, impulse_v2); wB += iB * b2Vec2.CrossVV(this.m_rB, impulse_v2); } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { const cA = data.positions[this.m_indexA].c; let aA = data.positions[this.m_indexA].a; const cB = data.positions[this.m_indexB].c; let aB = data.positions[this.m_indexB].a; // b2Rot qA(aA), qB(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); let angularError = 0; let positionError = 0; const fixedRotation = (this.m_invIA + this.m_invIB === 0); // Solve angular limit constraint. if (this.m_enableLimit && !fixedRotation) { const angle = aB - aA - this.m_referenceAngle; let C = 0.0; if (b2Abs(this.m_upperAngle - this.m_lowerAngle) < 2.0 * b2_angularSlop) { // Prevent large angular corrections C = b2Clamp(angle - this.m_lowerAngle, -b2_maxAngularCorrection, b2_maxAngularCorrection); } else if (angle <= this.m_lowerAngle) { // Prevent large angular corrections and allow some slop. C = b2Clamp(angle - this.m_lowerAngle + b2_angularSlop, -b2_maxAngularCorrection, 0.0); } else if (angle >= this.m_upperAngle) { // Prevent large angular corrections and allow some slop. C = b2Clamp(angle - this.m_upperAngle - b2_angularSlop, 0.0, b2_maxAngularCorrection); } const limitImpulse = -this.m_axialMass * C; aA -= this.m_invIA * limitImpulse; aB += this.m_invIB * limitImpulse; angularError = b2Abs(C); } // Solve point-to-point constraint. { qA.SetAngle(aA); qB.SetAngle(aB); // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // b2Vec2 C = cB + rB - cA - rA; const C_v2 = b2Vec2.SubVV(b2Vec2.AddVV(cB, rB, b2Vec2.s_t0), b2Vec2.AddVV(cA, rA, b2Vec2.s_t1), b2RevoluteJoint.SolvePositionConstraints_s_C_v2); // positionError = C.Length(); positionError = C_v2.Length(); const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const K = this.m_K; K.ex.x = mA + mB + iA * rA.y * rA.y + iB * rB.y * rB.y; K.ex.y = -iA * rA.x * rA.y - iB * rB.x * rB.y; K.ey.x = K.ex.y; K.ey.y = mA + mB + iA * rA.x * rA.x + iB * rB.x * rB.x; // b2Vec2 impulse = -K.Solve(C); const impulse = K.Solve(C_v2.x, C_v2.y, b2RevoluteJoint.SolvePositionConstraints_s_impulse).SelfNeg(); // cA -= mA * impulse; cA.SelfMulSub(mA, impulse); aA -= iA * b2Vec2.CrossVV(rA, impulse); // cB += mB * impulse; cB.SelfMulAdd(mB, impulse); aB += iB * b2Vec2.CrossVV(rB, impulse); } // data.positions[this.m_indexA].c = cA; data.positions[this.m_indexA].a = aA; // data.positions[this.m_indexB].c = cB; data.positions[this.m_indexB].a = aB; return positionError <= b2_linearSlop && angularError <= b2_angularSlop; } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { // b2Vec2 P(this.m_impulse.x, this.m_impulse.y); // return inv_dt * P; out.x = inv_dt * this.m_impulse.x; out.y = inv_dt * this.m_impulse.y; return out; } GetReactionTorque(inv_dt) { return inv_dt * (this.m_lowerImpulse - this.m_upperImpulse); } GetLocalAnchorA() { return this.m_localAnchorA; } GetLocalAnchorB() { return this.m_localAnchorB; } GetReferenceAngle() { return this.m_referenceAngle; } GetJointAngle() { // b2Body* bA = this.m_bodyA; // b2Body* bB = this.m_bodyB; // return bB.this.m_sweep.a - bA.this.m_sweep.a - this.m_referenceAngle; return this.m_bodyB.m_sweep.a - this.m_bodyA.m_sweep.a - this.m_referenceAngle; } GetJointSpeed() { // b2Body* bA = this.m_bodyA; // b2Body* bB = this.m_bodyB; // return bB.this.m_angularVelocity - bA.this.m_angularVelocity; return this.m_bodyB.m_angularVelocity - this.m_bodyA.m_angularVelocity; } IsMotorEnabled() { return this.m_enableMotor; } EnableMotor(flag) { if (flag !== this.m_enableMotor) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_enableMotor = flag; } } GetMotorTorque(inv_dt) { return inv_dt * this.m_motorImpulse; } GetMotorSpeed() { return this.m_motorSpeed; } SetMaxMotorTorque(torque) { if (torque !== this.m_maxMotorTorque) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_maxMotorTorque = torque; } } GetMaxMotorTorque() { return this.m_maxMotorTorque; } IsLimitEnabled() { return this.m_enableLimit; } EnableLimit(flag) { if (flag !== this.m_enableLimit) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_enableLimit = flag; this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } } GetLowerLimit() { return this.m_lowerAngle; } GetUpperLimit() { return this.m_upperAngle; } SetLimits(lower, upper) { if (lower !== this.m_lowerAngle || upper !== this.m_upperAngle) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; this.m_lowerAngle = lower; this.m_upperAngle = upper; } } SetMotorSpeed(speed) { if (speed !== this.m_motorSpeed) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_motorSpeed = speed; } } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2RevoluteJointDef = new b2RevoluteJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.localAnchorA.Set(%.15f, %.15f);\n", this.m_localAnchorA.x, this.m_localAnchorA.y); log(" jd.localAnchorB.Set(%.15f, %.15f);\n", this.m_localAnchorB.x, this.m_localAnchorB.y); log(" jd.referenceAngle = %.15f;\n", this.m_referenceAngle); log(" jd.enableLimit = %s;\n", (this.m_enableLimit) ? ("true") : ("false")); log(" jd.lowerAngle = %.15f;\n", this.m_lowerAngle); log(" jd.upperAngle = %.15f;\n", this.m_upperAngle); log(" jd.enableMotor = %s;\n", (this.m_enableMotor) ? ("true") : ("false")); log(" jd.motorSpeed = %.15f;\n", this.m_motorSpeed); log(" jd.maxMotorTorque = %.15f;\n", this.m_maxMotorTorque); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } Draw(draw) { const xfA = this.m_bodyA.GetTransform(); const xfB = this.m_bodyB.GetTransform(); const pA = b2Transform.MulXV(xfA, this.m_localAnchorA, b2RevoluteJoint.Draw_s_pA); const pB = b2Transform.MulXV(xfB, this.m_localAnchorB, b2RevoluteJoint.Draw_s_pB); const c1 = b2RevoluteJoint.Draw_s_c1; // b2Color c1(0.7f, 0.7f, 0.7f); const c2 = b2RevoluteJoint.Draw_s_c2; // b2Color c2(0.3f, 0.9f, 0.3f); const c3 = b2RevoluteJoint.Draw_s_c3; // b2Color c3(0.9f, 0.3f, 0.3f); const c4 = b2RevoluteJoint.Draw_s_c4; // b2Color c4(0.3f, 0.3f, 0.9f); const c5 = b2RevoluteJoint.Draw_s_c5; // b2Color c5(0.4f, 0.4f, 0.4f); draw.DrawPoint(pA, 5.0, c4); draw.DrawPoint(pB, 5.0, c5); const aA = this.m_bodyA.GetAngle(); const aB = this.m_bodyB.GetAngle(); const angle = aB - aA - this.m_referenceAngle; const L = 0.5; // b2Vec2 r = L * b2Vec2(Math.cos(angle), Math.sin(angle)); const r = b2RevoluteJoint.Draw_s_r.Set(L * Math.cos(angle), L * Math.sin(angle)); // draw.DrawSegment(pB, pB + r, c1); draw.DrawSegment(pB, b2Vec2.AddVV(pB, r, b2Vec2.s_t0), c1); draw.DrawCircle(pB, L, c1); if (this.m_enableLimit) { // b2Vec2 rlo = L * b2Vec2(Math.cos(m_lowerAngle), Math.sin(m_lowerAngle)); const rlo = b2RevoluteJoint.Draw_s_rlo.Set(L * Math.cos(this.m_lowerAngle), L * Math.sin(this.m_lowerAngle)); // b2Vec2 rhi = L * b2Vec2(Math.cos(m_upperAngle), Math.sin(m_upperAngle)); const rhi = b2RevoluteJoint.Draw_s_rhi.Set(L * Math.cos(this.m_upperAngle), L * Math.sin(this.m_upperAngle)); // draw.DrawSegment(pB, pB + rlo, c2); draw.DrawSegment(pB, b2Vec2.AddVV(pB, rlo, b2Vec2.s_t0), c2); // draw.DrawSegment(pB, pB + rhi, c3); draw.DrawSegment(pB, b2Vec2.AddVV(pB, rhi, b2Vec2.s_t0), c3); } const color = b2RevoluteJoint.Draw_s_color_; // b2Color color(0.5f, 0.8f, 0.8f); draw.DrawSegment(xfA.p, pA, color); draw.DrawSegment(pA, pB, color); draw.DrawSegment(xfB.p, pB, color); } } b2RevoluteJoint.InitVelocityConstraints_s_P = new b2Vec2(); // private static SolveVelocityConstraints_s_P: b2Vec2 = new b2Vec2(); b2RevoluteJoint.SolveVelocityConstraints_s_Cdot_v2 = new b2Vec2(); // private static SolveVelocityConstraints_s_Cdot1: b2Vec2 = new b2Vec2(); // private static SolveVelocityConstraints_s_impulse_v3: b2Vec3 = new b2Vec3(); // private static SolveVelocityConstraints_s_reduced_v2: b2Vec2 = new b2Vec2(); b2RevoluteJoint.SolveVelocityConstraints_s_impulse_v2 = new b2Vec2(); b2RevoluteJoint.SolvePositionConstraints_s_C_v2 = new b2Vec2(); b2RevoluteJoint.SolvePositionConstraints_s_impulse = new b2Vec2(); b2RevoluteJoint.Draw_s_pA = new b2Vec2(); b2RevoluteJoint.Draw_s_pB = new b2Vec2(); b2RevoluteJoint.Draw_s_c1 = new b2Color(0.7, 0.7, 0.7); b2RevoluteJoint.Draw_s_c2 = new b2Color(0.3, 0.9, 0.3); b2RevoluteJoint.Draw_s_c3 = new b2Color(0.9, 0.3, 0.3); b2RevoluteJoint.Draw_s_c4 = new b2Color(0.3, 0.3, 0.9); b2RevoluteJoint.Draw_s_c5 = new b2Color(0.4, 0.4, 0.4); b2RevoluteJoint.Draw_s_color_ = new b2Color(0.5, 0.8, 0.8); b2RevoluteJoint.Draw_s_r = new b2Vec2(); b2RevoluteJoint.Draw_s_rlo = new b2Vec2(); b2RevoluteJoint.Draw_s_rhi = new b2Vec2(); /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Weld joint definition. You need to specify local anchor points /// where they are attached and the relative body angle. The position /// of the anchor points is important for computing the reaction torque. class b2WeldJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_weldJoint); this.localAnchorA = new b2Vec2(); this.localAnchorB = new b2Vec2(); this.referenceAngle = 0; this.stiffness = 0; this.damping = 0; } Initialize(bA, bB, anchor) { this.bodyA = bA; this.bodyB = bB; this.bodyA.GetLocalPoint(anchor, this.localAnchorA); this.bodyB.GetLocalPoint(anchor, this.localAnchorB); this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle(); } } class b2WeldJoint extends b2Joint { constructor(def) { super(def); this.m_stiffness = 0; this.m_damping = 0; this.m_bias = 0; // Solver shared this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); this.m_referenceAngle = 0; this.m_gamma = 0; this.m_impulse = new b2Vec3(0, 0, 0); // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_mass = new b2Mat33(); this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_K = new b2Mat33(); this.m_stiffness = b2Maybe(def.stiffness, 0); this.m_damping = b2Maybe(def.damping, 0); this.m_localAnchorA.Copy(b2Maybe(def.localAnchorA, b2Vec2.ZERO)); this.m_localAnchorB.Copy(b2Maybe(def.localAnchorB, b2Vec2.ZERO)); this.m_referenceAngle = b2Maybe(def.referenceAngle, 0); this.m_impulse.SetZero(); } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // m_rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // m_rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // J = [-I -r1_skew I r2_skew] // [ 0 -1 0 1] // r_skew = [-ry; rx] // Matlab // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB] // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB] // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB] const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const K = this.m_K; K.ex.x = mA + mB + this.m_rA.y * this.m_rA.y * iA + this.m_rB.y * this.m_rB.y * iB; K.ey.x = -this.m_rA.y * this.m_rA.x * iA - this.m_rB.y * this.m_rB.x * iB; K.ez.x = -this.m_rA.y * iA - this.m_rB.y * iB; K.ex.y = K.ey.x; K.ey.y = mA + mB + this.m_rA.x * this.m_rA.x * iA + this.m_rB.x * this.m_rB.x * iB; K.ez.y = this.m_rA.x * iA + this.m_rB.x * iB; K.ex.z = K.ez.x; K.ey.z = K.ez.y; K.ez.z = iA + iB; if (this.m_stiffness > 0) { K.GetInverse22(this.m_mass); let invM = iA + iB; const C = aB - aA - this.m_referenceAngle; // Damping coefficient const d = this.m_damping; // Spring stiffness const k = this.m_stiffness; // magic formulas const h = data.step.dt; this.m_gamma = h * (d + h * k); this.m_gamma = this.m_gamma !== 0 ? 1 / this.m_gamma : 0; this.m_bias = C * h * k * this.m_gamma; invM += this.m_gamma; this.m_mass.ez.z = invM !== 0 ? 1 / invM : 0; } else { K.GetSymInverse33(this.m_mass); this.m_gamma = 0; this.m_bias = 0; } if (data.step.warmStarting) { // Scale impulses to support a variable time step. this.m_impulse.SelfMul(data.step.dtRatio); // b2Vec2 P(m_impulse.x, m_impulse.y); const P = b2WeldJoint.InitVelocityConstraints_s_P.Set(this.m_impulse.x, this.m_impulse.y); // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * (b2Vec2.CrossVV(this.m_rA, P) + this.m_impulse.z); // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * (b2Vec2.CrossVV(this.m_rB, P) + this.m_impulse.z); } else { this.m_impulse.SetZero(); } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; if (this.m_stiffness > 0) { const Cdot2 = wB - wA; const impulse2 = -this.m_mass.ez.z * (Cdot2 + this.m_bias + this.m_gamma * this.m_impulse.z); this.m_impulse.z += impulse2; wA -= iA * impulse2; wB += iB * impulse2; // b2Vec2 Cdot1 = vB + b2Vec2.CrossSV(wB, this.m_rB) - vA - b2Vec2.CrossSV(wA, this.m_rA); const Cdot1 = b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2Vec2.s_t1), b2WeldJoint.SolveVelocityConstraints_s_Cdot1); // b2Vec2 impulse1 = -b2Mul22(m_mass, Cdot1); const impulse1 = b2Mat33.MulM33XY(this.m_mass, Cdot1.x, Cdot1.y, b2WeldJoint.SolveVelocityConstraints_s_impulse1).SelfNeg(); this.m_impulse.x += impulse1.x; this.m_impulse.y += impulse1.y; // b2Vec2 P = impulse1; const P = impulse1; // vA -= mA * P; vA.SelfMulSub(mA, P); // wA -= iA * b2Cross(m_rA, P); wA -= iA * b2Vec2.CrossVV(this.m_rA, P); // vB += mB * P; vB.SelfMulAdd(mB, P); // wB += iB * b2Cross(m_rB, P); wB += iB * b2Vec2.CrossVV(this.m_rB, P); } else { // b2Vec2 Cdot1 = vB + b2Cross(wB, this.m_rB) - vA - b2Cross(wA, this.m_rA); const Cdot1 = b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, this.m_rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, this.m_rA, b2Vec2.s_t1), b2WeldJoint.SolveVelocityConstraints_s_Cdot1); const Cdot2 = wB - wA; // b2Vec3 const Cdot(Cdot1.x, Cdot1.y, Cdot2); // b2Vec3 impulse = -b2Mul(m_mass, Cdot); const impulse = b2Mat33.MulM33XYZ(this.m_mass, Cdot1.x, Cdot1.y, Cdot2, b2WeldJoint.SolveVelocityConstraints_s_impulse).SelfNeg(); this.m_impulse.SelfAdd(impulse); // b2Vec2 P(impulse.x, impulse.y); const P = b2WeldJoint.SolveVelocityConstraints_s_P.Set(impulse.x, impulse.y); // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * (b2Vec2.CrossVV(this.m_rA, P) + impulse.z); // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * (b2Vec2.CrossVV(this.m_rB, P) + impulse.z); } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { const cA = data.positions[this.m_indexA].c; let aA = data.positions[this.m_indexA].a; const cB = data.positions[this.m_indexB].c; let aB = data.positions[this.m_indexB].a; const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); let positionError, angularError; const K = this.m_K; K.ex.x = mA + mB + rA.y * rA.y * iA + rB.y * rB.y * iB; K.ey.x = -rA.y * rA.x * iA - rB.y * rB.x * iB; K.ez.x = -rA.y * iA - rB.y * iB; K.ex.y = K.ey.x; K.ey.y = mA + mB + rA.x * rA.x * iA + rB.x * rB.x * iB; K.ez.y = rA.x * iA + rB.x * iB; K.ex.z = K.ez.x; K.ey.z = K.ez.y; K.ez.z = iA + iB; if (this.m_stiffness > 0) { // b2Vec2 C1 = cB + rB - cA - rA; const C1 = b2Vec2.SubVV(b2Vec2.AddVV(cB, rB, b2Vec2.s_t0), b2Vec2.AddVV(cA, rA, b2Vec2.s_t1), b2WeldJoint.SolvePositionConstraints_s_C1); positionError = C1.Length(); angularError = 0; // b2Vec2 P = -K.Solve22(C1); const P = K.Solve22(C1.x, C1.y, b2WeldJoint.SolvePositionConstraints_s_P).SelfNeg(); // cA -= mA * P; cA.SelfMulSub(mA, P); aA -= iA * b2Vec2.CrossVV(rA, P); // cB += mB * P; cB.SelfMulAdd(mB, P); aB += iB * b2Vec2.CrossVV(rB, P); } else { // b2Vec2 C1 = cB + rB - cA - rA; const C1 = b2Vec2.SubVV(b2Vec2.AddVV(cB, rB, b2Vec2.s_t0), b2Vec2.AddVV(cA, rA, b2Vec2.s_t1), b2WeldJoint.SolvePositionConstraints_s_C1); const C2 = aB - aA - this.m_referenceAngle; positionError = C1.Length(); angularError = b2Abs(C2); // b2Vec3 C(C1.x, C1.y, C2); // b2Vec3 impulse = -K.Solve33(C); const impulse = K.Solve33(C1.x, C1.y, C2, b2WeldJoint.SolvePositionConstraints_s_impulse).SelfNeg(); // b2Vec2 P(impulse.x, impulse.y); const P = b2WeldJoint.SolvePositionConstraints_s_P.Set(impulse.x, impulse.y); // cA -= mA * P; cA.SelfMulSub(mA, P); aA -= iA * (b2Vec2.CrossVV(this.m_rA, P) + impulse.z); // cB += mB * P; cB.SelfMulAdd(mB, P); aB += iB * (b2Vec2.CrossVV(this.m_rB, P) + impulse.z); } // data.positions[this.m_indexA].c = cA; data.positions[this.m_indexA].a = aA; // data.positions[this.m_indexB].c = cB; data.positions[this.m_indexB].a = aB; return positionError <= b2_linearSlop && angularError <= b2_angularSlop; } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { // b2Vec2 P(this.m_impulse.x, this.m_impulse.y); // return inv_dt * P; out.x = inv_dt * this.m_impulse.x; out.y = inv_dt * this.m_impulse.y; return out; } GetReactionTorque(inv_dt) { return inv_dt * this.m_impulse.z; } GetLocalAnchorA() { return this.m_localAnchorA; } GetLocalAnchorB() { return this.m_localAnchorB; } GetReferenceAngle() { return this.m_referenceAngle; } SetStiffness(stiffness) { this.m_stiffness = stiffness; } GetStiffness() { return this.m_stiffness; } SetDamping(damping) { this.m_damping = damping; } GetDamping() { return this.m_damping; } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2WeldJointDef = new b2WeldJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.localAnchorA.Set(%.15f, %.15f);\n", this.m_localAnchorA.x, this.m_localAnchorA.y); log(" jd.localAnchorB.Set(%.15f, %.15f);\n", this.m_localAnchorB.x, this.m_localAnchorB.y); log(" jd.referenceAngle = %.15f;\n", this.m_referenceAngle); log(" jd.stiffness = %.15f;\n", this.m_stiffness); log(" jd.damping = %.15f;\n", this.m_damping); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } } b2WeldJoint.InitVelocityConstraints_s_P = new b2Vec2(); b2WeldJoint.SolveVelocityConstraints_s_Cdot1 = new b2Vec2(); b2WeldJoint.SolveVelocityConstraints_s_impulse1 = new b2Vec2(); b2WeldJoint.SolveVelocityConstraints_s_impulse = new b2Vec3(); b2WeldJoint.SolveVelocityConstraints_s_P = new b2Vec2(); b2WeldJoint.SolvePositionConstraints_s_C1 = new b2Vec2(); b2WeldJoint.SolvePositionConstraints_s_P = new b2Vec2(); b2WeldJoint.SolvePositionConstraints_s_impulse = new b2Vec3(); /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// Wheel joint definition. This requires defining a line of /// motion using an axis and an anchor point. The definition uses local /// anchor points and a local axis so that the initial configuration /// can violate the constraint slightly. The joint translation is zero /// when the local anchor points coincide in world space. Using local /// anchors and a local axis helps when saving and loading a game. class b2WheelJointDef extends b2JointDef { constructor() { super(exports.b2JointType.e_wheelJoint); this.localAnchorA = new b2Vec2(0, 0); this.localAnchorB = new b2Vec2(0, 0); this.localAxisA = new b2Vec2(1, 0); this.enableLimit = false; this.lowerTranslation = 0; this.upperTranslation = 0; this.enableMotor = false; this.maxMotorTorque = 0; this.motorSpeed = 0; this.stiffness = 0; this.damping = 0; } Initialize(bA, bB, anchor, axis) { this.bodyA = bA; this.bodyB = bB; this.bodyA.GetLocalPoint(anchor, this.localAnchorA); this.bodyB.GetLocalPoint(anchor, this.localAnchorB); this.bodyA.GetLocalVector(axis, this.localAxisA); } } class b2WheelJoint extends b2Joint { constructor(def) { super(def); this.m_localAnchorA = new b2Vec2(); this.m_localAnchorB = new b2Vec2(); this.m_localXAxisA = new b2Vec2(); this.m_localYAxisA = new b2Vec2(); this.m_impulse = 0; this.m_motorImpulse = 0; this.m_springImpulse = 0; this.m_lowerImpulse = 0; this.m_upperImpulse = 0; this.m_translation = 0; this.m_lowerTranslation = 0; this.m_upperTranslation = 0; this.m_maxMotorTorque = 0; this.m_motorSpeed = 0; this.m_enableLimit = false; this.m_enableMotor = false; this.m_stiffness = 0; this.m_damping = 0; // Solver temp this.m_indexA = 0; this.m_indexB = 0; this.m_localCenterA = new b2Vec2(); this.m_localCenterB = new b2Vec2(); this.m_invMassA = 0; this.m_invMassB = 0; this.m_invIA = 0; this.m_invIB = 0; this.m_ax = new b2Vec2(); this.m_ay = new b2Vec2(); this.m_sAx = 0; this.m_sBx = 0; this.m_sAy = 0; this.m_sBy = 0; this.m_mass = 0; this.m_motorMass = 0; this.m_axialMass = 0; this.m_springMass = 0; this.m_bias = 0; this.m_gamma = 0; this.m_qA = new b2Rot(); this.m_qB = new b2Rot(); this.m_lalcA = new b2Vec2(); this.m_lalcB = new b2Vec2(); this.m_rA = new b2Vec2(); this.m_rB = new b2Vec2(); this.m_localAnchorA.Copy(b2Maybe(def.localAnchorA, b2Vec2.ZERO)); this.m_localAnchorB.Copy(b2Maybe(def.localAnchorB, b2Vec2.ZERO)); this.m_localXAxisA.Copy(b2Maybe(def.localAxisA, b2Vec2.UNITX)); b2Vec2.CrossOneV(this.m_localXAxisA, this.m_localYAxisA); this.m_lowerTranslation = b2Maybe(def.lowerTranslation, 0); this.m_upperTranslation = b2Maybe(def.upperTranslation, 0); this.m_enableLimit = b2Maybe(def.enableLimit, false); this.m_maxMotorTorque = b2Maybe(def.maxMotorTorque, 0); this.m_motorSpeed = b2Maybe(def.motorSpeed, 0); this.m_enableMotor = b2Maybe(def.enableMotor, false); this.m_ax.SetZero(); this.m_ay.SetZero(); this.m_stiffness = b2Maybe(def.stiffness, 0); this.m_damping = b2Maybe(def.damping, 0); } GetMotorSpeed() { return this.m_motorSpeed; } GetMaxMotorTorque() { return this.m_maxMotorTorque; } SetStiffness(hz) { this.m_stiffness = hz; } GetStiffness() { return this.m_stiffness; } SetDamping(ratio) { this.m_damping = ratio; } GetDamping() { return this.m_damping; } InitVelocityConstraints(data) { this.m_indexA = this.m_bodyA.m_islandIndex; this.m_indexB = this.m_bodyB.m_islandIndex; this.m_localCenterA.Copy(this.m_bodyA.m_sweep.localCenter); this.m_localCenterB.Copy(this.m_bodyB.m_sweep.localCenter); this.m_invMassA = this.m_bodyA.m_invMass; this.m_invMassB = this.m_bodyB.m_invMass; this.m_invIA = this.m_bodyA.m_invI; this.m_invIB = this.m_bodyB.m_invI; const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const cA = data.positions[this.m_indexA].c; const aA = data.positions[this.m_indexA].a; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const cB = data.positions[this.m_indexB].c; const aB = data.positions[this.m_indexB].a; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // Compute the effective masses. // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // b2Vec2 d = cB + rB - cA - rA; const d = b2Vec2.SubVV(b2Vec2.AddVV(cB, rB, b2Vec2.s_t0), b2Vec2.AddVV(cA, rA, b2Vec2.s_t1), b2WheelJoint.InitVelocityConstraints_s_d); // Point to line constraint { // m_ay = b2Mul(qA, m_localYAxisA); b2Rot.MulRV(qA, this.m_localYAxisA, this.m_ay); // m_sAy = b2Cross(d + rA, m_ay); this.m_sAy = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), this.m_ay); // m_sBy = b2Cross(rB, m_ay); this.m_sBy = b2Vec2.CrossVV(rB, this.m_ay); this.m_mass = mA + mB + iA * this.m_sAy * this.m_sAy + iB * this.m_sBy * this.m_sBy; if (this.m_mass > 0) { this.m_mass = 1 / this.m_mass; } } // Spring constraint b2Rot.MulRV(qA, this.m_localXAxisA, this.m_ax); // m_ax = b2Mul(qA, m_localXAxisA); this.m_sAx = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), this.m_ax); this.m_sBx = b2Vec2.CrossVV(rB, this.m_ax); const invMass = mA + mB + iA * this.m_sAx * this.m_sAx + iB * this.m_sBx * this.m_sBx; if (invMass > 0.0) { this.m_axialMass = 1.0 / invMass; } else { this.m_axialMass = 0.0; } this.m_springMass = 0; this.m_bias = 0; this.m_gamma = 0; if (this.m_stiffness > 0.0 && invMass > 0.0) { this.m_springMass = 1.0 / invMass; const C = b2Vec2.DotVV(d, this.m_ax); // magic formulas const h = data.step.dt; this.m_gamma = h * (this.m_damping + h * this.m_stiffness); if (this.m_gamma > 0.0) { this.m_gamma = 1.0 / this.m_gamma; } this.m_bias = C * h * this.m_stiffness * this.m_gamma; this.m_springMass = invMass + this.m_gamma; if (this.m_springMass > 0.0) { this.m_springMass = 1.0 / this.m_springMass; } } else { this.m_springImpulse = 0.0; } if (this.m_enableLimit) { this.m_translation = b2Vec2.DotVV(this.m_ax, d); } else { this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } if (this.m_enableMotor) { this.m_motorMass = iA + iB; if (this.m_motorMass > 0) { this.m_motorMass = 1 / this.m_motorMass; } } else { this.m_motorMass = 0; this.m_motorImpulse = 0; } if (data.step.warmStarting) { // Account for variable time step. this.m_impulse *= data.step.dtRatio; this.m_springImpulse *= data.step.dtRatio; this.m_motorImpulse *= data.step.dtRatio; const axialImpulse = this.m_springImpulse + this.m_lowerImpulse - this.m_upperImpulse; // b2Vec2 P = m_impulse * m_ay + m_springImpulse * m_ax; const P = b2Vec2.AddVV(b2Vec2.MulSV(this.m_impulse, this.m_ay, b2Vec2.s_t0), b2Vec2.MulSV(axialImpulse, this.m_ax, b2Vec2.s_t1), b2WheelJoint.InitVelocityConstraints_s_P); // float32 LA = m_impulse * m_sAy + m_springImpulse * m_sAx + m_motorImpulse; const LA = this.m_impulse * this.m_sAy + axialImpulse * this.m_sAx + this.m_motorImpulse; // float32 LB = m_impulse * m_sBy + m_springImpulse * m_sBx + m_motorImpulse; const LB = this.m_impulse * this.m_sBy + axialImpulse * this.m_sBx + this.m_motorImpulse; // vA -= m_invMassA * P; vA.SelfMulSub(this.m_invMassA, P); wA -= this.m_invIA * LA; // vB += m_invMassB * P; vB.SelfMulAdd(this.m_invMassB, P); wB += this.m_invIB * LB; } else { this.m_impulse = 0; this.m_springImpulse = 0; this.m_motorImpulse = 0; this.m_lowerImpulse = 0; this.m_upperImpulse = 0; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolveVelocityConstraints(data) { const mA = this.m_invMassA, mB = this.m_invMassB; const iA = this.m_invIA, iB = this.m_invIB; const vA = data.velocities[this.m_indexA].v; let wA = data.velocities[this.m_indexA].w; const vB = data.velocities[this.m_indexB].v; let wB = data.velocities[this.m_indexB].w; // Solve spring constraint { const Cdot = b2Vec2.DotVV(this.m_ax, b2Vec2.SubVV(vB, vA, b2Vec2.s_t0)) + this.m_sBx * wB - this.m_sAx * wA; const impulse = -this.m_springMass * (Cdot + this.m_bias + this.m_gamma * this.m_springImpulse); this.m_springImpulse += impulse; // b2Vec2 P = impulse * m_ax; const P = b2Vec2.MulSV(impulse, this.m_ax, b2WheelJoint.SolveVelocityConstraints_s_P); const LA = impulse * this.m_sAx; const LB = impulse * this.m_sBx; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * LA; // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * LB; } // Solve rotational motor constraint { const Cdot = wB - wA - this.m_motorSpeed; let impulse = -this.m_motorMass * Cdot; const oldImpulse = this.m_motorImpulse; const maxImpulse = data.step.dt * this.m_maxMotorTorque; this.m_motorImpulse = b2Clamp(this.m_motorImpulse + impulse, -maxImpulse, maxImpulse); impulse = this.m_motorImpulse - oldImpulse; wA -= iA * impulse; wB += iB * impulse; } if (this.m_enableLimit) { // Lower limit { const C = this.m_translation - this.m_lowerTranslation; const Cdot = b2Vec2.DotVV(this.m_ax, b2Vec2.SubVV(vB, vA, b2Vec2.s_t0)) + this.m_sBx * wB - this.m_sAx * wA; let impulse = -this.m_axialMass * (Cdot + b2Max(C, 0.0) * data.step.inv_dt); const oldImpulse = this.m_lowerImpulse; this.m_lowerImpulse = b2Max(this.m_lowerImpulse + impulse, 0.0); impulse = this.m_lowerImpulse - oldImpulse; // b2Vec2 P = impulse * this.m_ax; const P = b2Vec2.MulSV(impulse, this.m_ax, b2WheelJoint.SolveVelocityConstraints_s_P); const LA = impulse * this.m_sAx; const LB = impulse * this.m_sBx; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * LA; // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * LB; } // Upper limit // Note: signs are flipped to keep C positive when the constraint is satisfied. // This also keeps the impulse positive when the limit is active. { const C = this.m_upperTranslation - this.m_translation; const Cdot = b2Vec2.DotVV(this.m_ax, b2Vec2.SubVV(vA, vB, b2Vec2.s_t0)) + this.m_sAx * wA - this.m_sBx * wB; let impulse = -this.m_axialMass * (Cdot + b2Max(C, 0.0) * data.step.inv_dt); const oldImpulse = this.m_upperImpulse; this.m_upperImpulse = b2Max(this.m_upperImpulse + impulse, 0.0); impulse = this.m_upperImpulse - oldImpulse; // b2Vec2 P = impulse * this.m_ax; const P = b2Vec2.MulSV(impulse, this.m_ax, b2WheelJoint.SolveVelocityConstraints_s_P); const LA = impulse * this.m_sAx; const LB = impulse * this.m_sBx; // vA += mA * P; vA.SelfMulAdd(mA, P); wA += iA * LA; // vB -= mB * P; vB.SelfMulSub(mB, P); wB -= iB * LB; } } // Solve point to line constraint { const Cdot = b2Vec2.DotVV(this.m_ay, b2Vec2.SubVV(vB, vA, b2Vec2.s_t0)) + this.m_sBy * wB - this.m_sAy * wA; const impulse = -this.m_mass * Cdot; this.m_impulse += impulse; // b2Vec2 P = impulse * m_ay; const P = b2Vec2.MulSV(impulse, this.m_ay, b2WheelJoint.SolveVelocityConstraints_s_P); const LA = impulse * this.m_sAy; const LB = impulse * this.m_sBy; // vA -= mA * P; vA.SelfMulSub(mA, P); wA -= iA * LA; // vB += mB * P; vB.SelfMulAdd(mB, P); wB += iB * LB; } // data.velocities[this.m_indexA].v = vA; data.velocities[this.m_indexA].w = wA; // data.velocities[this.m_indexB].v = vB; data.velocities[this.m_indexB].w = wB; } SolvePositionConstraints(data) { const cA = data.positions[this.m_indexA].c; let aA = data.positions[this.m_indexA].a; const cB = data.positions[this.m_indexB].c; let aB = data.positions[this.m_indexB].a; // const qA: b2Rot = this.m_qA.SetAngle(aA), qB: b2Rot = this.m_qB.SetAngle(aB); // // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); // b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); // const rA: b2Vec2 = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); // b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); // const rB: b2Vec2 = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // // b2Vec2 d = (cB - cA) + rB - rA; // const d: b2Vec2 = b2Vec2.AddVV( // b2Vec2.SubVV(cB, cA, b2Vec2.s_t0), // b2Vec2.SubVV(rB, rA, b2Vec2.s_t1), // b2WheelJoint.SolvePositionConstraints_s_d); // // b2Vec2 ay = b2Mul(qA, m_localYAxisA); // const ay: b2Vec2 = b2Rot.MulRV(qA, this.m_localYAxisA, this.m_ay); // // float32 sAy = b2Cross(d + rA, ay); // const sAy = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), ay); // // float32 sBy = b2Cross(rB, ay); // const sBy = b2Vec2.CrossVV(rB, ay); // // float32 C = b2Dot(d, ay); // const C: number = b2Vec2.DotVV(d, this.m_ay); // const k: number = this.m_invMassA + this.m_invMassB + this.m_invIA * this.m_sAy * this.m_sAy + this.m_invIB * this.m_sBy * this.m_sBy; // let impulse: number; // if (k !== 0) { // impulse = - C / k; // } else { // impulse = 0; // } // // b2Vec2 P = impulse * ay; // const P: b2Vec2 = b2Vec2.MulSV(impulse, ay, b2WheelJoint.SolvePositionConstraints_s_P); // const LA: number = impulse * sAy; // const LB: number = impulse * sBy; // // cA -= m_invMassA * P; // cA.SelfMulSub(this.m_invMassA, P); // aA -= this.m_invIA * LA; // // cB += m_invMassB * P; // cB.SelfMulAdd(this.m_invMassB, P); // aB += this.m_invIB * LB; let linearError = 0.0; if (this.m_enableLimit) { // b2Rot qA(aA), qB(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // b2Vec2 rA = b2Mul(qA, this.m_localAnchorA - this.m_localCenterA); // b2Vec2 rB = b2Mul(qB, this.m_localAnchorB - this.m_localCenterB); // b2Vec2 d = (cB - cA) + rB - rA; // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // b2Vec2 d = (cB - cA) + rB - rA; const d = b2Vec2.AddVV(b2Vec2.SubVV(cB, cA, b2Vec2.s_t0), b2Vec2.SubVV(rB, rA, b2Vec2.s_t1), b2WheelJoint.SolvePositionConstraints_s_d); // b2Vec2 ax = b2Mul(qA, this.m_localXAxisA); const ax = b2Rot.MulRV(qA, this.m_localXAxisA, this.m_ax); // float sAx = b2Cross(d + rA, this.m_ax); const sAx = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), this.m_ax); // float sBx = b2Cross(rB, this.m_ax); const sBx = b2Vec2.CrossVV(rB, this.m_ax); let C = 0.0; const translation = b2Vec2.DotVV(ax, d); if (b2Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2.0 * b2_linearSlop) { C = translation; } else if (translation <= this.m_lowerTranslation) { C = b2Min(translation - this.m_lowerTranslation, 0.0); } else if (translation >= this.m_upperTranslation) { C = b2Max(translation - this.m_upperTranslation, 0.0); } if (C !== 0.0) { const invMass = this.m_invMassA + this.m_invMassB + this.m_invIA * sAx * sAx + this.m_invIB * sBx * sBx; let impulse = 0.0; if (invMass !== 0.0) { impulse = -C / invMass; } const P = b2Vec2.MulSV(impulse, ax, b2WheelJoint.SolvePositionConstraints_s_P); const LA = impulse * sAx; const LB = impulse * sBx; // cA -= m_invMassA * P; cA.SelfMulSub(this.m_invMassA, P); aA -= this.m_invIA * LA; // cB += m_invMassB * P; cB.SelfMulAdd(this.m_invMassB, P); // aB += m_invIB * LB; aB += this.m_invIB * LB; linearError = b2Abs(C); } } // Solve perpendicular constraint { // b2Rot qA(aA), qB(aB); const qA = this.m_qA.SetAngle(aA), qB = this.m_qB.SetAngle(aB); // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); // b2Vec2 d = (cB - cA) + rB - rA; // b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA); b2Vec2.SubVV(this.m_localAnchorA, this.m_localCenterA, this.m_lalcA); const rA = b2Rot.MulRV(qA, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB); b2Vec2.SubVV(this.m_localAnchorB, this.m_localCenterB, this.m_lalcB); const rB = b2Rot.MulRV(qB, this.m_lalcB, this.m_rB); // b2Vec2 d = (cB - cA) + rB - rA; const d = b2Vec2.AddVV(b2Vec2.SubVV(cB, cA, b2Vec2.s_t0), b2Vec2.SubVV(rB, rA, b2Vec2.s_t1), b2WheelJoint.SolvePositionConstraints_s_d); // b2Vec2 ay = b2Mul(qA, m_localYAxisA); const ay = b2Rot.MulRV(qA, this.m_localYAxisA, this.m_ay); // float sAy = b2Cross(d + rA, ay); const sAy = b2Vec2.CrossVV(b2Vec2.AddVV(d, rA, b2Vec2.s_t0), ay); // float sBy = b2Cross(rB, ay); const sBy = b2Vec2.CrossVV(rB, ay); // float C = b2Dot(d, ay); const C = b2Vec2.DotVV(d, ay); const invMass = this.m_invMassA + this.m_invMassB + this.m_invIA * this.m_sAy * this.m_sAy + this.m_invIB * this.m_sBy * this.m_sBy; let impulse = 0.0; if (invMass !== 0.0) { impulse = -C / invMass; } // b2Vec2 P = impulse * ay; // const LA: number = impulse * sAy; // const LB: number = impulse * sBy; const P = b2Vec2.MulSV(impulse, ay, b2WheelJoint.SolvePositionConstraints_s_P); const LA = impulse * sAy; const LB = impulse * sBy; // cA -= m_invMassA * P; cA.SelfMulSub(this.m_invMassA, P); aA -= this.m_invIA * LA; // cB += m_invMassB * P; cB.SelfMulAdd(this.m_invMassB, P); aB += this.m_invIB * LB; linearError = b2Max(linearError, b2Abs(C)); } // data.positions[this.m_indexA].c = cA; data.positions[this.m_indexA].a = aA; // data.positions[this.m_indexB].c = cB; data.positions[this.m_indexB].a = aB; return linearError <= b2_linearSlop; } GetDefinition(def) { // DEBUG: b2Assert(false); // TODO return def; } GetAnchorA(out) { return this.m_bodyA.GetWorldPoint(this.m_localAnchorA, out); } GetAnchorB(out) { return this.m_bodyB.GetWorldPoint(this.m_localAnchorB, out); } GetReactionForce(inv_dt, out) { out.x = inv_dt * (this.m_impulse * this.m_ay.x + (this.m_springImpulse + this.m_lowerImpulse - this.m_upperImpulse) * this.m_ax.x); out.y = inv_dt * (this.m_impulse * this.m_ay.y + (this.m_springImpulse + this.m_lowerImpulse - this.m_upperImpulse) * this.m_ax.y); return out; } GetReactionTorque(inv_dt) { return inv_dt * this.m_motorImpulse; } GetLocalAnchorA() { return this.m_localAnchorA; } GetLocalAnchorB() { return this.m_localAnchorB; } GetLocalAxisA() { return this.m_localXAxisA; } GetJointTranslation() { return this.GetPrismaticJointTranslation(); } GetJointLinearSpeed() { return this.GetPrismaticJointSpeed(); } GetJointAngle() { return this.GetRevoluteJointAngle(); } GetJointAngularSpeed() { return this.GetRevoluteJointSpeed(); } GetPrismaticJointTranslation() { const bA = this.m_bodyA; const bB = this.m_bodyB; const pA = bA.GetWorldPoint(this.m_localAnchorA, new b2Vec2()); const pB = bB.GetWorldPoint(this.m_localAnchorB, new b2Vec2()); const d = b2Vec2.SubVV(pB, pA, new b2Vec2()); const axis = bA.GetWorldVector(this.m_localXAxisA, new b2Vec2()); const translation = b2Vec2.DotVV(d, axis); return translation; } GetPrismaticJointSpeed() { const bA = this.m_bodyA; const bB = this.m_bodyB; // b2Vec2 rA = b2Mul(bA.m_xf.q, m_localAnchorA - bA.m_sweep.localCenter); b2Vec2.SubVV(this.m_localAnchorA, bA.m_sweep.localCenter, this.m_lalcA); const rA = b2Rot.MulRV(bA.m_xf.q, this.m_lalcA, this.m_rA); // b2Vec2 rB = b2Mul(bB.m_xf.q, m_localAnchorB - bB.m_sweep.localCenter); b2Vec2.SubVV(this.m_localAnchorB, bB.m_sweep.localCenter, this.m_lalcB); const rB = b2Rot.MulRV(bB.m_xf.q, this.m_lalcB, this.m_rB); // b2Vec2 pA = bA.m_sweep.c + rA; const pA = b2Vec2.AddVV(bA.m_sweep.c, rA, b2Vec2.s_t0); // pA uses s_t0 // b2Vec2 pB = bB.m_sweep.c + rB; const pB = b2Vec2.AddVV(bB.m_sweep.c, rB, b2Vec2.s_t1); // pB uses s_t1 // b2Vec2 d = pB - pA; const d = b2Vec2.SubVV(pB, pA, b2Vec2.s_t2); // d uses s_t2 // b2Vec2 axis = b2Mul(bA.m_xf.q, m_localXAxisA); const axis = bA.GetWorldVector(this.m_localXAxisA, new b2Vec2()); const vA = bA.m_linearVelocity; const vB = bB.m_linearVelocity; const wA = bA.m_angularVelocity; const wB = bB.m_angularVelocity; // float32 speed = b2Dot(d, b2Cross(wA, axis)) + b2Dot(axis, vB + b2Cross(wB, rB) - vA - b2Cross(wA, rA)); const speed = b2Vec2.DotVV(d, b2Vec2.CrossSV(wA, axis, b2Vec2.s_t0)) + b2Vec2.DotVV(axis, b2Vec2.SubVV(b2Vec2.AddVCrossSV(vB, wB, rB, b2Vec2.s_t0), b2Vec2.AddVCrossSV(vA, wA, rA, b2Vec2.s_t1), b2Vec2.s_t0)); return speed; } GetRevoluteJointAngle() { // b2Body* bA = this.m_bodyA; // b2Body* bB = this.m_bodyB; // return bB.this.m_sweep.a - bA.this.m_sweep.a; return this.m_bodyB.m_sweep.a - this.m_bodyA.m_sweep.a; } GetRevoluteJointSpeed() { const wA = this.m_bodyA.m_angularVelocity; const wB = this.m_bodyB.m_angularVelocity; return wB - wA; } IsMotorEnabled() { return this.m_enableMotor; } EnableMotor(flag) { if (flag !== this.m_enableMotor) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_enableMotor = flag; } } SetMotorSpeed(speed) { if (speed !== this.m_motorSpeed) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_motorSpeed = speed; } } SetMaxMotorTorque(force) { if (force !== this.m_maxMotorTorque) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_maxMotorTorque = force; } } GetMotorTorque(inv_dt) { return inv_dt * this.m_motorImpulse; } /// Is the joint limit enabled? IsLimitEnabled() { return this.m_enableLimit; } /// Enable/disable the joint translation limit. EnableLimit(flag) { if (flag !== this.m_enableLimit) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_enableLimit = flag; this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } } /// Get the lower joint translation limit, usually in meters. GetLowerLimit() { return this.m_lowerTranslation; } /// Get the upper joint translation limit, usually in meters. GetUpperLimit() { return this.m_upperTranslation; } /// Set the joint translation limits, usually in meters. SetLimits(lower, upper) { // b2Assert(lower <= upper); if (lower !== this.m_lowerTranslation || upper !== this.m_upperTranslation) { this.m_bodyA.SetAwake(true); this.m_bodyB.SetAwake(true); this.m_lowerTranslation = lower; this.m_upperTranslation = upper; this.m_lowerImpulse = 0.0; this.m_upperImpulse = 0.0; } } Dump(log) { const indexA = this.m_bodyA.m_islandIndex; const indexB = this.m_bodyB.m_islandIndex; log(" const jd: b2WheelJointDef = new b2WheelJointDef();\n"); log(" jd.bodyA = bodies[%d];\n", indexA); log(" jd.bodyB = bodies[%d];\n", indexB); log(" jd.collideConnected = %s;\n", (this.m_collideConnected) ? ("true") : ("false")); log(" jd.localAnchorA.Set(%.15f, %.15f);\n", this.m_localAnchorA.x, this.m_localAnchorA.y); log(" jd.localAnchorB.Set(%.15f, %.15f);\n", this.m_localAnchorB.x, this.m_localAnchorB.y); log(" jd.localAxisA.Set(%.15f, %.15f);\n", this.m_localXAxisA.x, this.m_localXAxisA.y); log(" jd.enableMotor = %s;\n", (this.m_enableMotor) ? ("true") : ("false")); log(" jd.motorSpeed = %.15f;\n", this.m_motorSpeed); log(" jd.maxMotorTorque = %.15f;\n", this.m_maxMotorTorque); log(" jd.stiffness = %.15f;\n", this.m_stiffness); log(" jd.damping = %.15f;\n", this.m_damping); log(" joints[%d] = this.m_world.CreateJoint(jd);\n", this.m_index); } Draw(draw) { const xfA = this.m_bodyA.GetTransform(); const xfB = this.m_bodyB.GetTransform(); const pA = b2Transform.MulXV(xfA, this.m_localAnchorA, b2WheelJoint.Draw_s_pA); const pB = b2Transform.MulXV(xfB, this.m_localAnchorB, b2WheelJoint.Draw_s_pB); // b2Vec2 axis = b2Mul(xfA.q, m_localXAxisA); const axis = b2Rot.MulRV(xfA.q, this.m_localXAxisA, b2WheelJoint.Draw_s_axis); const c1 = b2WheelJoint.Draw_s_c1; // b2Color c1(0.7f, 0.7f, 0.7f); const c2 = b2WheelJoint.Draw_s_c2; // b2Color c2(0.3f, 0.9f, 0.3f); const c3 = b2WheelJoint.Draw_s_c3; // b2Color c3(0.9f, 0.3f, 0.3f); const c4 = b2WheelJoint.Draw_s_c4; // b2Color c4(0.3f, 0.3f, 0.9f); const c5 = b2WheelJoint.Draw_s_c5; // b2Color c5(0.4f, 0.4f, 0.4f); draw.DrawSegment(pA, pB, c5); if (this.m_enableLimit) { // b2Vec2 lower = pA + m_lowerTranslation * axis; const lower = b2Vec2.AddVMulSV(pA, this.m_lowerTranslation, axis, b2WheelJoint.Draw_s_lower); // b2Vec2 upper = pA + m_upperTranslation * axis; const upper = b2Vec2.AddVMulSV(pA, this.m_upperTranslation, axis, b2WheelJoint.Draw_s_upper); // b2Vec2 perp = b2Mul(xfA.q, m_localYAxisA); const perp = b2Rot.MulRV(xfA.q, this.m_localYAxisA, b2WheelJoint.Draw_s_perp); // draw.DrawSegment(lower, upper, c1); draw.DrawSegment(lower, upper, c1); // draw.DrawSegment(lower - 0.5f * perp, lower + 0.5f * perp, c2); draw.DrawSegment(b2Vec2.AddVMulSV(lower, -0.5, perp, b2Vec2.s_t0), b2Vec2.AddVMulSV(lower, 0.5, perp, b2Vec2.s_t1), c2); // draw.DrawSegment(upper - 0.5f * perp, upper + 0.5f * perp, c3); draw.DrawSegment(b2Vec2.AddVMulSV(upper, -0.5, perp, b2Vec2.s_t0), b2Vec2.AddVMulSV(upper, 0.5, perp, b2Vec2.s_t1), c3); } else { // draw.DrawSegment(pA - 1.0f * axis, pA + 1.0f * axis, c1); draw.DrawSegment(b2Vec2.AddVMulSV(pA, -1.0, axis, b2Vec2.s_t0), b2Vec2.AddVMulSV(pA, 1.0, axis, b2Vec2.s_t1), c1); } draw.DrawPoint(pA, 5.0, c1); draw.DrawPoint(pB, 5.0, c4); } } b2WheelJoint.InitVelocityConstraints_s_d = new b2Vec2(); b2WheelJoint.InitVelocityConstraints_s_P = new b2Vec2(); b2WheelJoint.SolveVelocityConstraints_s_P = new b2Vec2(); b2WheelJoint.SolvePositionConstraints_s_d = new b2Vec2(); b2WheelJoint.SolvePositionConstraints_s_P = new b2Vec2(); /// b2WheelJoint.Draw_s_pA = new b2Vec2(); b2WheelJoint.Draw_s_pB = new b2Vec2(); b2WheelJoint.Draw_s_axis = new b2Vec2(); b2WheelJoint.Draw_s_c1 = new b2Color(0.7, 0.7, 0.7); b2WheelJoint.Draw_s_c2 = new b2Color(0.3, 0.9, 0.3); b2WheelJoint.Draw_s_c3 = new b2Color(0.9, 0.3, 0.3); b2WheelJoint.Draw_s_c4 = new b2Color(0.3, 0.3, 0.9); b2WheelJoint.Draw_s_c5 = new b2Color(0.4, 0.4, 0.4); b2WheelJoint.Draw_s_lower = new b2Vec2(); b2WheelJoint.Draw_s_upper = new b2Vec2(); b2WheelJoint.Draw_s_perp = new b2Vec2(); /* * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /// The world class manages all physics entities, dynamic simulation, /// and asynchronous queries. The world also contains efficient memory /// management facilities. class b2World { /// Construct a world object. /// @param gravity the world gravity vector. constructor(gravity) { this.m_contactManager = new b2ContactManager(); this.m_bodyList = null; this.m_jointList = null; this.m_bodyCount = 0; this.m_jointCount = 0; this.m_gravity = new b2Vec2(); this.m_allowSleep = true; this.m_destructionListener = null; this.m_debugDraw = null; // This is used to compute the time step ratio to // support a variable time step. this.m_inv_dt0 = 0; this.m_newContacts = false; this.m_locked = false; this.m_clearForces = true; // These are for debugging the solver. this.m_warmStarting = true; this.m_continuousPhysics = true; this.m_subStepping = false; this.m_stepComplete = true; this.m_profile = new b2Profile(); this.m_island = new b2Island(); this.s_stack = []; this.m_gravity.Copy(gravity); } /// Register a destruction listener. The listener is owned by you and must /// remain in scope. SetDestructionListener(listener) { this.m_destructionListener = listener; } /// Register a contact filter to provide specific control over collision. /// Otherwise the default filter is used (b2_defaultFilter). The listener is /// owned by you and must remain in scope. SetContactFilter(filter) { this.m_contactManager.m_contactFilter = filter; } /// Register a contact event listener. The listener is owned by you and must /// remain in scope. SetContactListener(listener) { this.m_contactManager.m_contactListener = listener; } /// Register a routine for debug drawing. The debug draw functions are called /// inside with b2World::DebugDraw method. The debug draw object is owned /// by you and must remain in scope. SetDebugDraw(debugDraw) { this.m_debugDraw = debugDraw; } /// Create a rigid body given a definition. No reference to the definition /// is retained. /// @warning This function is locked during callbacks. CreateBody(def = {}) { if (this.IsLocked()) { throw new Error(); } const b = 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. DestroyBody(b) { // DEBUG: b2Assert(this.m_bodyCount > 0); if (this.IsLocked()) { throw new Error(); } // Delete the attached joints. let je = b.m_jointList; while (je) { const je0 = je; je = je.next; if (this.m_destructionListener) { this.m_destructionListener.SayGoodbyeJoint(je0.joint); } this.DestroyJoint(je0.joint); b.m_jointList = je; } b.m_jointList = null; // Delete the attached contacts. let ce = b.m_contactList; while (ce) { const ce0 = 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 = b.m_fixtureList; while (f) { const f0 = f; f = f.m_next; if (this.m_destructionListener) { this.m_destructionListener.SayGoodbyeFixture(f0); } f0.DestroyProxies(); f0.Reset(); b.m_fixtureList = f; b.m_fixtureCount -= 1; } 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; } static _Joint_Create(def) { switch (def.type) { case exports.b2JointType.e_distanceJoint: return new b2DistanceJoint(def); case exports.b2JointType.e_mouseJoint: return new b2MouseJoint(def); case exports.b2JointType.e_prismaticJoint: return new b2PrismaticJoint(def); case exports.b2JointType.e_revoluteJoint: return new b2RevoluteJoint(def); case exports.b2JointType.e_pulleyJoint: return new b2PulleyJoint(def); case exports.b2JointType.e_gearJoint: return new b2GearJoint(def); case exports.b2JointType.e_wheelJoint: return new b2WheelJoint(def); case exports.b2JointType.e_weldJoint: return new b2WeldJoint(def); case exports.b2JointType.e_frictionJoint: return new b2FrictionJoint(def); case exports.b2JointType.e_motorJoint: return new b2MotorJoint(def); case exports.b2JointType.e_areaJoint: return new b2AreaJoint(def); } throw new Error(); } static _Joint_Destroy(joint) { } CreateJoint(def) { if (this.IsLocked()) { throw new Error(); } const j = b2World._Joint_Create(def); // 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.other = j.m_bodyB; // done in b2Joint constructor 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.other = j.m_bodyA; // done in b2Joint constructor 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 = j.m_bodyA; const bodyB = j.m_bodyB; const collideConnected = j.m_collideConnected; // If the joint prevents collisions, then flag any contacts for filtering. if (!collideConnected) { let edge = 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. DestroyJoint(j) { if (this.IsLocked()) { throw new Error(); } // 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 = j.m_bodyA; const bodyB = j.m_bodyB; const collideConnected = j.m_collideConnected; // 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.Reset(); // 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.Reset(); b2World._Joint_Destroy(j); // DEBUG: b2Assert(this.m_jointCount > 0); --this.m_jointCount; // If the joint prevents collisions, then flag any contacts for filtering. if (!collideConnected) { let edge = 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; } } } Step(dt, velocityIterations, positionIterations) { const stepTimer = b2World.Step_s_stepTimer.Reset(); // If new fixtures were added, we need to find the new contacts. if (this.m_newContacts) { this.m_contactManager.FindNewContacts(); this.m_newContacts = false; } this.m_locked = true; const step = b2World.Step_s_step; step.dt = dt; step.velocityIterations = velocityIterations; step.positionIterations = positionIterations; if (dt > 0) { step.inv_dt = 1 / dt; } else { step.inv_dt = 0; } step.dtRatio = this.m_inv_dt0 * dt; step.warmStarting = this.m_warmStarting; // Update contacts. This is where some contacts are destroyed. const timer = b2World.Step_s_timer.Reset(); this.m_contactManager.Collide(); this.m_profile.collide = timer.GetMilliseconds(); // Integrate velocities, solve velocity constraints, and integrate positions. if (this.m_stepComplete && step.dt > 0) { const timer = b2World.Step_s_timer.Reset(); this.Solve(step); this.m_profile.solve = timer.GetMilliseconds(); } // Handle TOI events. if (this.m_continuousPhysics && step.dt > 0) { const timer = b2World.Step_s_timer.Reset(); this.SolveTOI(step); this.m_profile.solveTOI = timer.GetMilliseconds(); } if (step.dt > 0) { this.m_inv_dt0 = step.inv_dt; } if (this.m_clearForces) { this.ClearForces(); } this.m_locked = false; this.m_profile.step = stepTimer.GetMilliseconds(); } /// Manually clear the force buffer on all bodies. By default, forces are cleared automatically /// after each call to Step. The default behavior is modified by calling SetAutoClearForces. /// The purpose of this function is to support sub-stepping. Sub-stepping is often used to maintain /// a fixed sized time step under a variable frame-rate. /// When you perform sub-stepping you will disable auto clearing of forces and instead call /// ClearForces after all sub-steps are complete in one pass of your game loop. /// @see SetAutoClearForces ClearForces() { for (let body = this.m_bodyList; body; body = body.m_next) { body.m_force.SetZero(); body.m_torque = 0; } } DebugDraw() { if (this.m_debugDraw === null) { return; } const flags = this.m_debugDraw.GetFlags(); const color = b2World.DebugDraw_s_color.SetRGB(0, 0, 0); if (flags & exports.b2DrawFlags.e_shapeBit) { for (let b = this.m_bodyList; b; b = b.m_next) { const xf = b.m_xf; this.m_debugDraw.PushTransform(xf); for (let f = b.GetFixtureList(); f; f = f.m_next) { if (b.GetType() === exports.b2BodyType.b2_dynamicBody && b.m_mass === 0.0) { // Bad body this.DrawShape(f, new b2Color(1.0, 0.0, 0.0)); } else if (!b.IsEnabled()) { color.SetRGB(0.5, 0.5, 0.3); this.DrawShape(f, color); } else if (b.GetType() === exports.b2BodyType.b2_staticBody) { color.SetRGB(0.5, 0.9, 0.5); this.DrawShape(f, color); } else if (b.GetType() === exports.b2BodyType.b2_kinematicBody) { color.SetRGB(0.5, 0.5, 0.9); this.DrawShape(f, color); } else if (!b.IsAwake()) { color.SetRGB(0.6, 0.6, 0.6); this.DrawShape(f, color); } else { color.SetRGB(0.9, 0.7, 0.7); this.DrawShape(f, color); } } this.m_debugDraw.PopTransform(xf); } } if (flags & exports.b2DrawFlags.e_jointBit) { for (let j = this.m_jointList; j; j = j.m_next) { j.Draw(this.m_debugDraw); } } if (flags & exports.b2DrawFlags.e_pairBit) { color.SetRGB(0.3, 0.9, 0.9); for (let contact = this.m_contactManager.m_contactList; contact; contact = contact.m_next) { const fixtureA = contact.GetFixtureA(); const fixtureB = contact.GetFixtureB(); const indexA = contact.GetChildIndexA(); const indexB = contact.GetChildIndexB(); const cA = fixtureA.GetAABB(indexA).GetCenter(); const cB = fixtureB.GetAABB(indexB).GetCenter(); this.m_debugDraw.DrawSegment(cA, cB, color); } } if (flags & exports.b2DrawFlags.e_aabbBit) { color.SetRGB(0.9, 0.3, 0.9); const vs = b2World.DebugDraw_s_vs; for (let b = this.m_bodyList; b; b = b.m_next) { if (!b.IsEnabled()) { continue; } for (let f = b.GetFixtureList(); f; f = f.m_next) { for (let i = 0; i < f.m_proxyCount; ++i) { const proxy = f.m_proxies[i]; const aabb = proxy.treeNode.aabb; 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 & exports.b2DrawFlags.e_centerOfMassBit) { for (let b = this.m_bodyList; b; b = b.m_next) { const xf = b2World.DebugDraw_s_xf; xf.q.Copy(b.m_xf.q); xf.p.Copy(b.GetWorldCenter()); this.m_debugDraw.DrawTransform(xf); } } } QueryAABB(...args) { if (args[0] instanceof b2QueryCallback) { this._QueryAABB(args[0], args[1]); } else { this._QueryAABB(null, args[0], args[1]); } } _QueryAABB(callback, aabb, fn) { this.m_contactManager.m_broadPhase.Query(aabb, (proxy) => { const fixture_proxy = proxy.userData; // DEBUG: b2Assert(fixture_proxy instanceof b2FixtureProxy); const fixture = fixture_proxy.fixture; if (callback) { return callback.ReportFixture(fixture); } else if (fn) { return fn(fixture); } return true; }); } QueryAllAABB(aabb, out = []) { this.QueryAABB(aabb, (fixture) => { out.push(fixture); return true; }); return out; } QueryPointAABB(...args) { if (args[0] instanceof b2QueryCallback) { this._QueryPointAABB(args[0], args[1]); } else { this._QueryPointAABB(null, args[0], args[1]); } } _QueryPointAABB(callback, point, fn) { this.m_contactManager.m_broadPhase.QueryPoint(point, (proxy) => { const fixture_proxy = proxy.userData; // DEBUG: b2Assert(fixture_proxy instanceof b2FixtureProxy); const fixture = fixture_proxy.fixture; if (callback) { return callback.ReportFixture(fixture); } else if (fn) { return fn(fixture); } return true; }); } QueryAllPointAABB(point, out = []) { this.QueryPointAABB(point, (fixture) => { out.push(fixture); return true; }); return out; } QueryFixtureShape(...args) { if (args[0] instanceof b2QueryCallback) { this._QueryFixtureShape(args[0], args[1], args[2], args[3]); } else { this._QueryFixtureShape(null, args[0], args[1], args[2], args[3]); } } _QueryFixtureShape(callback, shape, index, transform, fn) { const aabb = b2World.QueryFixtureShape_s_aabb; shape.ComputeAABB(aabb, transform, index); this.m_contactManager.m_broadPhase.Query(aabb, (proxy) => { const fixture_proxy = proxy.userData; // DEBUG: b2Assert(fixture_proxy instanceof b2FixtureProxy); const fixture = fixture_proxy.fixture; if (b2TestOverlapShape(shape, index, fixture.GetShape(), fixture_proxy.childIndex, transform, fixture.GetBody().GetTransform())) { if (callback) { return callback.ReportFixture(fixture); } else if (fn) { return fn(fixture); } } return true; }); } QueryAllFixtureShape(shape, index, transform, out = []) { this.QueryFixtureShape(shape, index, transform, (fixture) => { out.push(fixture); return true; }); return out; } QueryFixturePoint(...args) { if (args[0] instanceof b2QueryCallback) { this._QueryFixturePoint(args[0], args[1]); } else { this._QueryFixturePoint(null, args[0], args[1]); } } _QueryFixturePoint(callback, point, fn) { this.m_contactManager.m_broadPhase.QueryPoint(point, (proxy) => { const fixture_proxy = proxy.userData; // DEBUG: b2Assert(fixture_proxy instanceof b2FixtureProxy); const fixture = fixture_proxy.fixture; if (fixture.TestPoint(point)) { if (callback) { return callback.ReportFixture(fixture); } else if (fn) { return fn(fixture); } } return true; }); } QueryAllFixturePoint(point, out = []) { this.QueryFixturePoint(point, (fixture) => { out.push(fixture); return true; }); return out; } RayCast(...args) { if (args[0] instanceof b2RayCastCallback) { this._RayCast(args[0], args[1], args[2]); } else { this._RayCast(null, args[0], args[1], args[2]); } } _RayCast(callback, point1, point2, fn) { const input = b2World.RayCast_s_input; input.maxFraction = 1; input.p1.Copy(point1); input.p2.Copy(point2); this.m_contactManager.m_broadPhase.RayCast(input, (input, proxy) => { const fixture_proxy = proxy.userData; // DEBUG: b2Assert(fixture_proxy instanceof b2FixtureProxy); const fixture = fixture_proxy.fixture; const index = fixture_proxy.childIndex; const output = b2World.RayCast_s_output; const hit = fixture.RayCast(output, input, index); if (hit) { const fraction = output.fraction; const point = b2World.RayCast_s_point; point.Set((1 - fraction) * point1.x + fraction * point2.x, (1 - fraction) * point1.y + fraction * point2.y); if (callback) { return callback.ReportFixture(fixture, point, output.normal, fraction); } else if (fn) { return fn(fixture, point, output.normal, fraction); } } return input.maxFraction; }); } RayCastOne(point1, point2) { let result = null; let min_fraction = 1; this.RayCast(point1, point2, (fixture, point, normal, fraction) => { if (fraction < min_fraction) { min_fraction = fraction; result = fixture; } return min_fraction; }); return result; } RayCastAll(point1, point2, out = []) { this.RayCast(point1, point2, (fixture, point, normal, fraction) => { out.push(fixture); return 1; }); return out; } /// 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. GetBodyList() { 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. GetJointList() { 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 created and destroyed in the middle of a time step. /// Use b2ContactListener to avoid missing contacts. GetContactList() { return this.m_contactManager.m_contactList; } /// Enable/disable sleep. SetAllowSleeping(flag) { if (flag === this.m_allowSleep) { return; } this.m_allowSleep = flag; if (!this.m_allowSleep) { for (let b = this.m_bodyList; b; b = b.m_next) { b.SetAwake(true); } } } GetAllowSleeping() { return this.m_allowSleep; } /// Enable/disable warm starting. For testing. SetWarmStarting(flag) { this.m_warmStarting = flag; } GetWarmStarting() { return this.m_warmStarting; } /// Enable/disable continuous physics. For testing. SetContinuousPhysics(flag) { this.m_continuousPhysics = flag; } GetContinuousPhysics() { return this.m_continuousPhysics; } /// Enable/disable single stepped continuous physics. For testing. SetSubStepping(flag) { this.m_subStepping = flag; } GetSubStepping() { return this.m_subStepping; } /// Get the number of broad-phase proxies. GetProxyCount() { return this.m_contactManager.m_broadPhase.GetProxyCount(); } /// Get the number of bodies. GetBodyCount() { return this.m_bodyCount; } /// Get the number of joints. GetJointCount() { return this.m_jointCount; } /// Get the number of contacts (each may have 0 or more contact points). GetContactCount() { return this.m_contactManager.m_contactCount; } /// Get the height of the dynamic tree. GetTreeHeight() { return this.m_contactManager.m_broadPhase.GetTreeHeight(); } /// Get the balance of the dynamic tree. GetTreeBalance() { return this.m_contactManager.m_broadPhase.GetTreeBalance(); } /// Get the quality metric of the dynamic tree. The smaller the better. /// The minimum is 1. GetTreeQuality() { return this.m_contactManager.m_broadPhase.GetTreeQuality(); } /// Change the global gravity vector. SetGravity(gravity, wake = true) { if (!b2Vec2.IsEqualToV(this.m_gravity, gravity)) { this.m_gravity.Copy(gravity); if (wake) { for (let b = this.m_bodyList; b; b = b.m_next) { b.SetAwake(true); } } } } /// Get the global gravity vector. GetGravity() { return this.m_gravity; } /// Is the world locked (in the middle of a time step). IsLocked() { return this.m_locked; } /// Set flag to control automatic clearing of forces after each time step. SetAutoClearForces(flag) { this.m_clearForces = flag; } /// Get the flag that controls automatic clearing of forces after each time step. GetAutoClearForces() { return this.m_clearForces; } /// Shift the world origin. Useful for large worlds. /// The body shift formula is: position -= newOrigin /// @param newOrigin the new origin with respect to the old origin ShiftOrigin(newOrigin) { if (this.IsLocked()) { throw new Error(); } for (let b = this.m_bodyList; b; b = b.m_next) { b.m_xf.p.SelfSub(newOrigin); b.m_sweep.c0.SelfSub(newOrigin); b.m_sweep.c.SelfSub(newOrigin); } for (let j = this.m_jointList; j; j = j.m_next) { j.ShiftOrigin(newOrigin); } this.m_contactManager.m_broadPhase.ShiftOrigin(newOrigin); } /// Get the contact manager for testing. GetContactManager() { return this.m_contactManager; } /// Get the current profile. GetProfile() { return this.m_profile; } /// Dump the world into the log file. /// @warning this should be called outside of a time step. Dump(log) { if (this.m_locked) { return; } // b2OpenDump("box2d_dump.inl"); log("const g: b2Vec2 = new b2Vec2(%.15f, %.15f);\n", this.m_gravity.x, this.m_gravity.y); log("this.m_world.SetGravity(g);\n"); log("const bodies: b2Body[] = [];\n"); log("const joints: b2Joint[] = [];\n"); let i = 0; for (let b = this.m_bodyList; b; b = b.m_next) { b.m_islandIndex = i; b.Dump(log); ++i; } i = 0; for (let j = this.m_jointList; j; j = j.m_next) { j.m_index = i; ++i; } // First pass on joints, skip gear joints. for (let j = this.m_jointList; j; j = j.m_next) { if (j.m_type === exports.b2JointType.e_gearJoint) { continue; } log("{\n"); j.Dump(log); log("}\n"); } // Second pass on joints, only gear joints. for (let j = this.m_jointList; j; j = j.m_next) { if (j.m_type !== exports.b2JointType.e_gearJoint) { continue; } log("{\n"); j.Dump(log); log("}\n"); } // b2CloseDump(); } DrawShape(fixture, color) { if (this.m_debugDraw === null) { return; } const shape = fixture.GetShape(); switch (shape.m_type) { case exports.b2ShapeType.e_circleShape: { const circle = shape; const center = circle.m_p; const radius = circle.m_radius; const axis = b2Vec2.UNITX; this.m_debugDraw.DrawSolidCircle(center, radius, axis, color); break; } case exports.b2ShapeType.e_edgeShape: { const edge = shape; const v1 = edge.m_vertex1; const v2 = edge.m_vertex2; this.m_debugDraw.DrawSegment(v1, v2, color); if (edge.m_oneSided === false) { this.m_debugDraw.DrawPoint(v1, 4.0, color); this.m_debugDraw.DrawPoint(v2, 4.0, color); } break; } case exports.b2ShapeType.e_chainShape: { const chain = shape; const count = chain.m_count; const vertices = chain.m_vertices; let v1 = vertices[0]; for (let i = 1; i < count; ++i) { const v2 = vertices[i]; this.m_debugDraw.DrawSegment(v1, v2, color); v1 = v2; } break; } case exports.b2ShapeType.e_polygonShape: { const poly = shape; const vertexCount = poly.m_count; const vertices = poly.m_vertices; this.m_debugDraw.DrawSolidPolygon(vertices, vertexCount, color); break; } } } Solve(step) { this.m_profile.solveInit = 0; this.m_profile.solveVelocity = 0; this.m_profile.solvePosition = 0; // Size the island for the worst case. const island = this.m_island; island.Initialize(this.m_bodyCount, this.m_contactManager.m_contactCount, this.m_jointCount, this.m_contactManager.m_contactListener); // Clear all the island flags. for (let b = this.m_bodyList; b; b = b.m_next) { b.m_islandFlag = false; } for (let c = this.m_contactManager.m_contactList; c; c = c.m_next) { c.m_islandFlag = false; } for (let j = this.m_jointList; j; j = j.m_next) { j.m_islandFlag = false; } // Build and simulate all awake islands. // DEBUG: const stackSize: number = this.m_bodyCount; const stack = this.s_stack; for (let seed = this.m_bodyList; seed; seed = seed.m_next) { if (seed.m_islandFlag) { continue; } if (!seed.IsAwake() || !seed.IsEnabled()) { continue; } // The seed can be dynamic or kinematic. if (seed.GetType() === exports.b2BodyType.b2_staticBody) { continue; } // Reset island and stack. island.Clear(); let stackCount = 0; stack[stackCount++] = seed; seed.m_islandFlag = true; // 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. const b = stack[--stackCount]; if (!b) { throw new Error(); } // DEBUG: b2Assert(b.IsEnabled()); island.AddBody(b); // To keep islands as small as possible, we don't // propagate islands across static bodies. if (b.GetType() === exports.b2BodyType.b2_staticBody) { continue; } // Make sure the body is awake. (without resetting sleep timer). b.m_awakeFlag = true; // Search all contacts connected to this body. for (let ce = b.m_contactList; ce; ce = ce.next) { const contact = ce.contact; // Has this contact already been added to an island? if (contact.m_islandFlag) { continue; } // Is this contact solid and touching? if (!contact.IsEnabled() || !contact.IsTouching()) { continue; } // Skip sensors. const sensorA = contact.m_fixtureA.m_isSensor; const sensorB = contact.m_fixtureB.m_isSensor; if (sensorA || sensorB) { continue; } island.AddContact(contact); contact.m_islandFlag = true; const other = ce.other; // Was the other body already added to this island? if (other.m_islandFlag) { continue; } // DEBUG: b2Assert(stackCount < stackSize); stack[stackCount++] = other; other.m_islandFlag = true; } // Search all joints connect to this body. for (let je = b.m_jointList; je; je = je.next) { if (je.joint.m_islandFlag) { continue; } const other = je.other; // Don't simulate joints connected to disabled bodies. if (!other.IsEnabled()) { continue; } island.AddJoint(je.joint); je.joint.m_islandFlag = true; if (other.m_islandFlag) { continue; } // DEBUG: b2Assert(stackCount < stackSize); stack[stackCount++] = other; other.m_islandFlag = true; } } const profile = new b2Profile(); island.Solve(profile, step, this.m_gravity, this.m_allowSleep); this.m_profile.solveInit += profile.solveInit; this.m_profile.solveVelocity += profile.solveVelocity; this.m_profile.solvePosition += profile.solvePosition; // Post solve cleanup. for (let i = 0; i < island.m_bodyCount; ++i) { // Allow static bodies to participate in other islands. const b = island.m_bodies[i]; if (b.GetType() === exports.b2BodyType.b2_staticBody) { b.m_islandFlag = false; } } } for (let i = 0; i < stack.length; ++i) { if (!stack[i]) { break; } stack[i] = null; } const timer = new b2Timer(); // Synchronize fixtures, check for out of range bodies. for (let b = this.m_bodyList; b; b = b.m_next) { // If a body was not in an island then it did not move. if (!b.m_islandFlag) { continue; } if (b.GetType() === exports.b2BodyType.b2_staticBody) { continue; } // Update fixtures (for broad-phase). b.SynchronizeFixtures(); } // Look for new contacts. this.m_contactManager.FindNewContacts(); this.m_profile.broadphase = timer.GetMilliseconds(); } SolveTOI(step) { const island = this.m_island; island.Initialize(2 * b2_maxTOIContacts, b2_maxTOIContacts, 0, this.m_contactManager.m_contactListener); if (this.m_stepComplete) { for (let b = this.m_bodyList; b; b = b.m_next) { b.m_islandFlag = false; b.m_sweep.alpha0 = 0; } for (let c = this.m_contactManager.m_contactList; c; c = c.m_next) { // Invalidate TOI c.m_toiFlag = false; c.m_islandFlag = false; c.m_toiCount = 0; c.m_toi = 1; } } // Find TOI events and solve them. for (;;) { // Find the first TOI. let minContact = null; let minAlpha = 1; for (let c = this.m_contactManager.m_contactList; c; c = c.m_next) { // Is this contact disabled? if (!c.IsEnabled()) { continue; } // Prevent excessive sub-stepping. if (c.m_toiCount > b2_maxSubSteps) { continue; } let alpha = 1; if (c.m_toiFlag) { // This contact has a valid cached TOI. alpha = c.m_toi; } else { const fA = c.GetFixtureA(); const fB = c.GetFixtureB(); // Is there a sensor? if (fA.IsSensor() || fB.IsSensor()) { continue; } const bA = fA.GetBody(); const bB = fB.GetBody(); const typeA = bA.m_type; const typeB = bB.m_type; // DEBUG: b2Assert(typeA !== b2BodyType.b2_staticBody || typeB !== b2BodyType.b2_staticBody); const activeA = bA.IsAwake() && typeA !== exports.b2BodyType.b2_staticBody; const activeB = bB.IsAwake() && typeB !== exports.b2BodyType.b2_staticBody; // Is at least one body active (awake and dynamic or kinematic)? if (!activeA && !activeB) { continue; } const collideA = bA.IsBullet() || typeA !== exports.b2BodyType.b2_dynamicBody; const collideB = bB.IsBullet() || typeB !== exports.b2BodyType.b2_dynamicBody; // Are these two non-bullet dynamic bodies? if (!collideA && !collideB) { continue; } // Compute the TOI for this contact. // Put the sweeps onto the same time interval. let alpha0 = bA.m_sweep.alpha0; if (bA.m_sweep.alpha0 < bB.m_sweep.alpha0) { alpha0 = bB.m_sweep.alpha0; bA.m_sweep.Advance(alpha0); } else if (bB.m_sweep.alpha0 < bA.m_sweep.alpha0) { alpha0 = bA.m_sweep.alpha0; bB.m_sweep.Advance(alpha0); } // DEBUG: b2Assert(alpha0 < 1); const indexA = c.GetChildIndexA(); const indexB = c.GetChildIndexB(); // Compute the time of impact in interval [0, minTOI] const input = b2World.SolveTOI_s_toi_input; input.proxyA.SetShape(fA.GetShape(), indexA); input.proxyB.SetShape(fB.GetShape(), indexB); input.sweepA.Copy(bA.m_sweep); input.sweepB.Copy(bB.m_sweep); input.tMax = 1; const output = b2World.SolveTOI_s_toi_output; b2TimeOfImpact(output, input); // Beta is the fraction of the remaining portion of the . const beta = output.t; if (output.state === exports.b2TOIOutputState.e_touching) { alpha = b2Min(alpha0 + (1 - alpha0) * beta, 1); } else { alpha = 1; } c.m_toi = alpha; c.m_toiFlag = true; } if (alpha < minAlpha) { // This is the minimum TOI found so far. minContact = c; minAlpha = alpha; } } if (minContact === null || 1 - 10 * b2_epsilon < minAlpha) { // No more TOI events. Done! this.m_stepComplete = true; break; } // Advance the bodies to the TOI. const fA = minContact.GetFixtureA(); const fB = minContact.GetFixtureB(); const bA = fA.GetBody(); const bB = fB.GetBody(); const backup1 = b2World.SolveTOI_s_backup1.Copy(bA.m_sweep); const backup2 = b2World.SolveTOI_s_backup2.Copy(bB.m_sweep); bA.Advance(minAlpha); bB.Advance(minAlpha); // The TOI contact likely has some new contact points. minContact.Update(this.m_contactManager.m_contactListener); minContact.m_toiFlag = false; ++minContact.m_toiCount; // Is the contact solid? if (!minContact.IsEnabled() || !minContact.IsTouching()) { // Restore the sweeps. minContact.SetEnabled(false); bA.m_sweep.Copy(backup1); bB.m_sweep.Copy(backup2); bA.SynchronizeTransform(); bB.SynchronizeTransform(); continue; } bA.SetAwake(true); bB.SetAwake(true); // Build the island island.Clear(); island.AddBody(bA); island.AddBody(bB); island.AddContact(minContact); bA.m_islandFlag = true; bB.m_islandFlag = true; minContact.m_islandFlag = true; // Get contacts on bodyA and bodyB. // const bodies: b2Body[] = [bA, bB]; for (let i = 0; i < 2; ++i) { const body = (i === 0) ? (bA) : (bB); // bodies[i]; if (body.m_type === exports.b2BodyType.b2_dynamicBody) { for (let ce = body.m_contactList; ce; ce = ce.next) { if (island.m_bodyCount === island.m_bodyCapacity) { break; } if (island.m_contactCount === island.m_contactCapacity) { break; } const contact = ce.contact; // Has this contact already been added to the island? if (contact.m_islandFlag) { continue; } // Only add static, kinematic, or bullet bodies. const other = ce.other; if (other.m_type === exports.b2BodyType.b2_dynamicBody && !body.IsBullet() && !other.IsBullet()) { continue; } // Skip sensors. const sensorA = contact.m_fixtureA.m_isSensor; const sensorB = contact.m_fixtureB.m_isSensor; if (sensorA || sensorB) { continue; } // Tentatively advance the body to the TOI. const backup = b2World.SolveTOI_s_backup.Copy(other.m_sweep); if (!other.m_islandFlag) { other.Advance(minAlpha); } // Update the contact points contact.Update(this.m_contactManager.m_contactListener); // Was the contact disabled by the user? if (!contact.IsEnabled()) { other.m_sweep.Copy(backup); other.SynchronizeTransform(); continue; } // Are there contact points? if (!contact.IsTouching()) { other.m_sweep.Copy(backup); other.SynchronizeTransform(); continue; } // Add the contact to the island contact.m_islandFlag = true; island.AddContact(contact); // Has the other body already been added to the island? if (other.m_islandFlag) { continue; } // Add the other body to the island. other.m_islandFlag = true; if (other.m_type !== exports.b2BodyType.b2_staticBody) { other.SetAwake(true); } island.AddBody(other); } } } const subStep = b2World.SolveTOI_s_subStep; subStep.dt = (1 - minAlpha) * step.dt; subStep.inv_dt = 1 / subStep.dt; subStep.dtRatio = 1; subStep.positionIterations = 20; subStep.velocityIterations = step.velocityIterations; subStep.warmStarting = false; island.SolveTOI(subStep, bA.m_islandIndex, bB.m_islandIndex); // Reset island flags and synchronize broad-phase proxies. for (let i = 0; i < island.m_bodyCount; ++i) { const body = island.m_bodies[i]; body.m_islandFlag = false; if (body.m_type !== exports.b2BodyType.b2_dynamicBody) { continue; } body.SynchronizeFixtures(); // Invalidate all contact TOIs on this displaced body. for (let ce = body.m_contactList; ce; ce = ce.next) { ce.contact.m_toiFlag = false; ce.contact.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(); if (this.m_subStepping) { this.m_stepComplete = false; break; } } } } /// 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. b2World.Step_s_step = new b2TimeStep(); b2World.Step_s_stepTimer = new b2Timer(); b2World.Step_s_timer = new b2Timer(); /// Call this to draw shapes and other debug draw data. b2World.DebugDraw_s_color = new b2Color(0, 0, 0); b2World.DebugDraw_s_vs = b2Vec2.MakeArray(4); b2World.DebugDraw_s_xf = new b2Transform(); b2World.QueryFixtureShape_s_aabb = new b2AABB(); b2World.RayCast_s_input = new b2RayCastInput(); b2World.RayCast_s_output = new b2RayCastOutput(); b2World.RayCast_s_point = new b2Vec2(); b2World.SolveTOI_s_subStep = new b2TimeStep(); b2World.SolveTOI_s_backup = new b2Sweep(); b2World.SolveTOI_s_backup1 = new b2Sweep(); b2World.SolveTOI_s_backup2 = new b2Sweep(); b2World.SolveTOI_s_toi_input = new b2TOIInput(); b2World.SolveTOI_s_toi_output = new b2TOIOutput(); // MIT License exports.b2StretchingModel = void 0; (function (b2StretchingModel) { b2StretchingModel[b2StretchingModel["b2_pbdStretchingModel"] = 0] = "b2_pbdStretchingModel"; b2StretchingModel[b2StretchingModel["b2_xpbdStretchingModel"] = 1] = "b2_xpbdStretchingModel"; })(exports.b2StretchingModel || (exports.b2StretchingModel = {})); exports.b2BendingModel = void 0; (function (b2BendingModel) { b2BendingModel[b2BendingModel["b2_springAngleBendingModel"] = 0] = "b2_springAngleBendingModel"; b2BendingModel[b2BendingModel["b2_pbdAngleBendingModel"] = 1] = "b2_pbdAngleBendingModel"; b2BendingModel[b2BendingModel["b2_xpbdAngleBendingModel"] = 2] = "b2_xpbdAngleBendingModel"; b2BendingModel[b2BendingModel["b2_pbdDistanceBendingModel"] = 3] = "b2_pbdDistanceBendingModel"; b2BendingModel[b2BendingModel["b2_pbdHeightBendingModel"] = 4] = "b2_pbdHeightBendingModel"; b2BendingModel[b2BendingModel["b2_pbdTriangleBendingModel"] = 5] = "b2_pbdTriangleBendingModel"; })(exports.b2BendingModel || (exports.b2BendingModel = {})); /// class b2RopeTuning { constructor() { this.stretchingModel = exports.b2StretchingModel.b2_pbdStretchingModel; this.bendingModel = exports.b2BendingModel.b2_pbdAngleBendingModel; this.damping = 0.0; this.stretchStiffness = 1.0; this.stretchHertz = 0.0; this.stretchDamping = 0.0; this.bendStiffness = 0.5; this.bendHertz = 1.0; this.bendDamping = 0.0; this.isometric = false; this.fixedEffectiveMass = false; this.warmStart = false; } Copy(other) { this.stretchingModel = other.stretchingModel; this.bendingModel = other.bendingModel; this.damping = other.damping; this.stretchStiffness = other.stretchStiffness; this.stretchHertz = other.stretchHertz; this.stretchDamping = other.stretchDamping; this.bendStiffness = other.bendStiffness; this.bendHertz = other.bendHertz; this.bendDamping = other.bendDamping; this.isometric = other.isometric; this.fixedEffectiveMass = other.fixedEffectiveMass; this.warmStart = other.warmStart; return this; } } /// class b2RopeDef { constructor() { this.position = new b2Vec2(); // b2Vec2* vertices; this.vertices = []; // int32 count; this.count = 0; // float* masses; this.masses = []; // b2Vec2 gravity; this.gravity = new b2Vec2(); // b2RopeTuning tuning; this.tuning = new b2RopeTuning(); } } class b2RopeStretch { constructor() { this.i1 = 0; this.i2 = 0; this.invMass1 = 0.0; this.invMass2 = 0.0; this.L = 0.0; this.lambda = 0.0; this.spring = 0.0; this.damper = 0.0; } } class b2RopeBend { constructor() { this.i1 = 0; this.i2 = 0; this.i3 = 0; this.invMass1 = 0.0; this.invMass2 = 0.0; this.invMass3 = 0.0; this.invEffectiveMass = 0.0; this.lambda = 0.0; this.L1 = 0.0; this.L2 = 0.0; this.alpha1 = 0.0; this.alpha2 = 0.0; this.spring = 0.0; this.damper = 0.0; } } /// class b2Rope { constructor() { this.m_position = new b2Vec2(); this.m_count = 0; this.m_stretchCount = 0; this.m_bendCount = 0; // b2RopeStretch* m_stretchConstraints; this.m_stretchConstraints = []; // b2RopeBend* m_bendConstraints; this.m_bendConstraints = []; // b2Vec2* m_bindPositions; this.m_bindPositions = []; // b2Vec2* m_ps; this.m_ps = []; // b2Vec2* m_p0s; this.m_p0s = []; // b2Vec2* m_vs; this.m_vs = []; // float* m_invMasses; this.m_invMasses = []; // b2Vec2 m_gravity; this.m_gravity = new b2Vec2(); this.m_tuning = new b2RopeTuning(); } Create(def) { // b2Assert(def.count >= 3); this.m_position.Copy(def.position); this.m_count = def.count; function make_array(array, count, make) { for (let index = 0; index < count; ++index) { array[index] = make(index); } } // this.m_bindPositions = (b2Vec2*)b2Alloc(this.m_count * sizeof(b2Vec2)); make_array(this.m_bindPositions, this.m_count, () => new b2Vec2()); // this.m_ps = (b2Vec2*)b2Alloc(this.m_count * sizeof(b2Vec2)); make_array(this.m_ps, this.m_count, () => new b2Vec2()); // this.m_p0s = (b2Vec2*)b2Alloc(this.m_count * sizeof(b2Vec2)); make_array(this.m_p0s, this.m_count, () => new b2Vec2()); // this.m_vs = (b2Vec2*)b2Alloc(this.m_count * sizeof(b2Vec2)); make_array(this.m_vs, this.m_count, () => new b2Vec2()); // this.m_invMasses = (float*)b2Alloc(this.m_count * sizeof(float)); make_array(this.m_invMasses, this.m_count, () => 0.0); for (let i = 0; i < this.m_count; ++i) { this.m_bindPositions[i].Copy(def.vertices[i]); // this.m_ps[i] = def.vertices[i] + this.m_position; this.m_ps[i].Copy(def.vertices[i]).SelfAdd(this.m_position); // this.m_p0s[i] = def.vertices[i] + this.m_position; this.m_p0s[i].Copy(def.vertices[i]).SelfAdd(this.m_position); this.m_vs[i].SetZero(); const m = def.masses[i]; if (m > 0.0) { this.m_invMasses[i] = 1.0 / m; } else { this.m_invMasses[i] = 0.0; } } this.m_stretchCount = this.m_count - 1; this.m_bendCount = this.m_count - 2; // this.m_stretchConstraints = (b2RopeStretch*)b2Alloc(this.m_stretchCount * sizeof(b2RopeStretch)); make_array(this.m_stretchConstraints, this.m_stretchCount, () => new b2RopeStretch()); // this.m_bendConstraints = (b2RopeBend*)b2Alloc(this.m_bendCount * sizeof(b2RopeBend)); make_array(this.m_bendConstraints, this.m_bendCount, () => new b2RopeBend()); for (let i = 0; i < this.m_stretchCount; ++i) { const c = this.m_stretchConstraints[i]; const p1 = this.m_ps[i]; const p2 = this.m_ps[i + 1]; c.i1 = i; c.i2 = i + 1; c.L = b2Vec2.DistanceVV(p1, p2); c.invMass1 = this.m_invMasses[i]; c.invMass2 = this.m_invMasses[i + 1]; c.lambda = 0.0; c.damper = 0.0; c.spring = 0.0; } for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const p1 = this.m_ps[i]; const p2 = this.m_ps[i + 1]; const p3 = this.m_ps[i + 2]; c.i1 = i; c.i2 = i + 1; c.i3 = i + 2; c.invMass1 = this.m_invMasses[i]; c.invMass2 = this.m_invMasses[i + 1]; c.invMass3 = this.m_invMasses[i + 2]; c.invEffectiveMass = 0.0; c.L1 = b2Vec2.DistanceVV(p1, p2); c.L2 = b2Vec2.DistanceVV(p2, p3); c.lambda = 0.0; // Pre-compute effective mass (TODO use flattened config) const e1 = b2Vec2.SubVV(p2, p1, new b2Vec2()); const e2 = b2Vec2.SubVV(p3, p2, new b2Vec2()); const L1sqr = e1.LengthSquared(); const L2sqr = e2.LengthSquared(); if (L1sqr * L2sqr === 0.0) { continue; } // b2Vec2 Jd1 = (-1.0 / L1sqr) * e1.Skew(); const Jd1 = new b2Vec2().Copy(e1).SelfSkew().SelfMul(-1.0 / L1sqr); // b2Vec2 Jd2 = (1.0 / L2sqr) * e2.Skew(); const Jd2 = new b2Vec2().Copy(e2).SelfSkew().SelfMul(1.0 / L2sqr); // b2Vec2 J1 = -Jd1; const J1 = Jd1.Clone().SelfNeg(); // b2Vec2 J2 = Jd1 - Jd2; const J2 = Jd1.Clone().SelfSub(Jd2); // b2Vec2 J3 = Jd2; const J3 = Jd2.Clone(); c.invEffectiveMass = c.invMass1 * b2Vec2.DotVV(J1, J1) + c.invMass2 * b2Vec2.DotVV(J2, J2) + c.invMass3 * b2Vec2.DotVV(J3, J3); // b2Vec2 r = p3 - p1; const r = b2Vec2.SubVV(p3, p1, new b2Vec2()); const rr = r.LengthSquared(); if (rr === 0.0) { continue; } // a1 = h2 / (h1 + h2) // a2 = h1 / (h1 + h2) c.alpha1 = b2Vec2.DotVV(e2, r) / rr; c.alpha2 = b2Vec2.DotVV(e1, r) / rr; } this.m_gravity.Copy(def.gravity); this.SetTuning(def.tuning); } SetTuning(tuning) { this.m_tuning.Copy(tuning); // Pre-compute spring and damper values based on tuning const bendOmega = 2.0 * b2_pi * this.m_tuning.bendHertz; for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const L1sqr = c.L1 * c.L1; const L2sqr = c.L2 * c.L2; if (L1sqr * L2sqr === 0.0) { c.spring = 0.0; c.damper = 0.0; continue; } // Flatten the triangle formed by the two edges const J2 = 1.0 / c.L1 + 1.0 / c.L2; const sum = c.invMass1 / L1sqr + c.invMass2 * J2 * J2 + c.invMass3 / L2sqr; if (sum === 0.0) { c.spring = 0.0; c.damper = 0.0; continue; } const mass = 1.0 / sum; c.spring = mass * bendOmega * bendOmega; c.damper = 2.0 * mass * this.m_tuning.bendDamping * bendOmega; } const stretchOmega = 2.0 * b2_pi * this.m_tuning.stretchHertz; for (let i = 0; i < this.m_stretchCount; ++i) { const c = this.m_stretchConstraints[i]; const sum = c.invMass1 + c.invMass2; if (sum === 0.0) { continue; } const mass = 1.0 / sum; c.spring = mass * stretchOmega * stretchOmega; c.damper = 2.0 * mass * this.m_tuning.stretchDamping * stretchOmega; } } Step(dt, iterations, position) { if (dt === 0.0) { return; } const inv_dt = 1.0 / dt; const d = Math.exp(-dt * this.m_tuning.damping); // Apply gravity and damping for (let i = 0; i < this.m_count; ++i) { if (this.m_invMasses[i] > 0.0) { // this.m_vs[i] *= d; this.m_vs[i].x *= d; this.m_vs[i].y *= d; // this.m_vs[i] += dt * this.m_gravity; this.m_vs[i].x += dt * this.m_gravity.x; this.m_vs[i].y += dt * this.m_gravity.y; } else { // this.m_vs[i] = inv_dt * (this.m_bindPositions[i] + position - this.m_p0s[i]); this.m_vs[i].x = inv_dt * (this.m_bindPositions[i].x + position.x - this.m_p0s[i].x); this.m_vs[i].y = inv_dt * (this.m_bindPositions[i].y + position.y - this.m_p0s[i].y); } } // Apply bending spring if (this.m_tuning.bendingModel === exports.b2BendingModel.b2_springAngleBendingModel) { this.ApplyBendForces(dt); } for (let i = 0; i < this.m_bendCount; ++i) { this.m_bendConstraints[i].lambda = 0.0; } for (let i = 0; i < this.m_stretchCount; ++i) { this.m_stretchConstraints[i].lambda = 0.0; } // Update position for (let i = 0; i < this.m_count; ++i) { // this.m_ps[i] += dt * this.m_vs[i]; this.m_ps[i].x += dt * this.m_vs[i].x; this.m_ps[i].y += dt * this.m_vs[i].y; } // Solve constraints for (let i = 0; i < iterations; ++i) { if (this.m_tuning.bendingModel === exports.b2BendingModel.b2_pbdAngleBendingModel) { this.SolveBend_PBD_Angle(); } else if (this.m_tuning.bendingModel === exports.b2BendingModel.b2_xpbdAngleBendingModel) { this.SolveBend_XPBD_Angle(dt); } else if (this.m_tuning.bendingModel === exports.b2BendingModel.b2_pbdDistanceBendingModel) { this.SolveBend_PBD_Distance(); } else if (this.m_tuning.bendingModel === exports.b2BendingModel.b2_pbdHeightBendingModel) { this.SolveBend_PBD_Height(); } else if (this.m_tuning.bendingModel === exports.b2BendingModel.b2_pbdTriangleBendingModel) { this.SolveBend_PBD_Triangle(); } if (this.m_tuning.stretchingModel === exports.b2StretchingModel.b2_pbdStretchingModel) { this.SolveStretch_PBD(); } else if (this.m_tuning.stretchingModel === exports.b2StretchingModel.b2_xpbdStretchingModel) { this.SolveStretch_XPBD(dt); } } // Constrain velocity for (let i = 0; i < this.m_count; ++i) { // this.m_vs[i] = inv_dt * (this.m_ps[i] - this.m_p0s[i]); this.m_vs[i].x = inv_dt * (this.m_ps[i].x - this.m_p0s[i].x); this.m_vs[i].y = inv_dt * (this.m_ps[i].y - this.m_p0s[i].y); this.m_p0s[i].Copy(this.m_ps[i]); } } Reset(position) { this.m_position.Copy(position); for (let i = 0; i < this.m_count; ++i) { // this.m_ps[i] = this.m_bindPositions[i] + this.m_position; this.m_ps[i].x = this.m_bindPositions[i].x + this.m_position.x; this.m_ps[i].y = this.m_bindPositions[i].y + this.m_position.y; // this.m_p0s[i] = this.m_bindPositions[i] + this.m_position; this.m_p0s[i].x = this.m_bindPositions[i].x + this.m_position.x; this.m_p0s[i].y = this.m_bindPositions[i].y + this.m_position.y; this.m_vs[i].SetZero(); } for (let i = 0; i < this.m_bendCount; ++i) { this.m_bendConstraints[i].lambda = 0.0; } for (let i = 0; i < this.m_stretchCount; ++i) { this.m_stretchConstraints[i].lambda = 0.0; } } Draw(draw) { const c = new b2Color(0.4, 0.5, 0.7); const pg = new b2Color(0.1, 0.8, 0.1); const pd = new b2Color(0.7, 0.2, 0.4); for (let i = 0; i < this.m_count - 1; ++i) { draw.DrawSegment(this.m_ps[i], this.m_ps[i + 1], c); const pc = this.m_invMasses[i] > 0.0 ? pd : pg; draw.DrawPoint(this.m_ps[i], 5.0, pc); } const pc = this.m_invMasses[this.m_count - 1] > 0.0 ? pd : pg; draw.DrawPoint(this.m_ps[this.m_count - 1], 5.0, pc); } SolveStretch_PBD() { const stiffness = this.m_tuning.stretchStiffness; for (let i = 0; i < this.m_stretchCount; ++i) { const c = this.m_stretchConstraints[i]; const p1 = this.m_ps[c.i1].Clone(); const p2 = this.m_ps[c.i2].Clone(); // b2Vec2 d = p2 - p1; const d = p2.Clone().SelfSub(p1); const L = d.Normalize(); const sum = c.invMass1 + c.invMass2; if (sum === 0.0) { continue; } const s1 = c.invMass1 / sum; const s2 = c.invMass2 / sum; // p1 -= stiffness * s1 * (c.L - L) * d; p1.x -= stiffness * s1 * (c.L - L) * d.x; p1.y -= stiffness * s1 * (c.L - L) * d.y; // p2 += stiffness * s2 * (c.L - L) * d; p2.x += stiffness * s2 * (c.L - L) * d.x; p2.y += stiffness * s2 * (c.L - L) * d.y; this.m_ps[c.i1].Copy(p1); this.m_ps[c.i2].Copy(p2); } } SolveStretch_XPBD(dt) { // b2Assert(dt > 0.0); for (let i = 0; i < this.m_stretchCount; ++i) { const c = this.m_stretchConstraints[i]; const p1 = this.m_ps[c.i1].Clone(); const p2 = this.m_ps[c.i2].Clone(); const dp1 = p1.Clone().SelfSub(this.m_p0s[c.i1]); const dp2 = p2.Clone().SelfSub(this.m_p0s[c.i2]); // b2Vec2 u = p2 - p1; const u = p2.Clone().SelfSub(p1); const L = u.Normalize(); // b2Vec2 J1 = -u; const J1 = u.Clone().SelfNeg(); // b2Vec2 J2 = u; const J2 = u; const sum = c.invMass1 + c.invMass2; if (sum === 0.0) { continue; } const alpha = 1.0 / (c.spring * dt * dt); // 1 / kg const beta = dt * dt * c.damper; // kg * s const sigma = alpha * beta / dt; // non-dimensional const C = L - c.L; // This is using the initial velocities const Cdot = b2Vec2.DotVV(J1, dp1) + b2Vec2.DotVV(J2, dp2); const B = C + alpha * c.lambda + sigma * Cdot; const sum2 = (1.0 + sigma) * sum + alpha; const impulse = -B / sum2; // p1 += (c.invMass1 * impulse) * J1; p1.x += (c.invMass1 * impulse) * J1.x; p1.y += (c.invMass1 * impulse) * J1.y; // p2 += (c.invMass2 * impulse) * J2; p2.x += (c.invMass2 * impulse) * J2.x; p2.y += (c.invMass2 * impulse) * J2.y; this.m_ps[c.i1].Copy(p1); this.m_ps[c.i2].Copy(p2); c.lambda += impulse; } } SolveBend_PBD_Angle() { const stiffness = this.m_tuning.bendStiffness; for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const p1 = this.m_ps[c.i1]; const p2 = this.m_ps[c.i2]; const p3 = this.m_ps[c.i3]; // b2Vec2 d1 = p2 - p1; const d1 = p2.Clone().SelfSub(p1); // b2Vec2 d2 = p3 - p2; const d2 = p3.Clone().SelfSub(p2); const a = b2Vec2.CrossVV(d1, d2); const b = b2Vec2.DotVV(d1, d2); const angle = b2Atan2(a, b); let L1sqr = 0.0, L2sqr = 0.0; if (this.m_tuning.isometric) { L1sqr = c.L1 * c.L1; L2sqr = c.L2 * c.L2; } else { L1sqr = d1.LengthSquared(); L2sqr = d2.LengthSquared(); } if (L1sqr * L2sqr === 0.0) { continue; } // b2Vec2 Jd1 = (-1.0 / L1sqr) * d1.Skew(); const Jd1 = new b2Vec2().Copy(d1).SelfSkew().SelfMul(-1.0 / L1sqr); // b2Vec2 Jd2 = (1.0 / L2sqr) * d2.Skew(); const Jd2 = new b2Vec2().Copy(d2).SelfSkew().SelfMul(1.0 / L2sqr); // b2Vec2 J1 = -Jd1; const J1 = Jd1.Clone().SelfNeg(); // b2Vec2 J2 = Jd1 - Jd2; const J2 = Jd1.Clone().SelfSub(Jd2); // b2Vec2 J3 = Jd2; const J3 = Jd2; let sum = 0.0; if (this.m_tuning.fixedEffectiveMass) { sum = c.invEffectiveMass; } else { sum = c.invMass1 * b2Vec2.DotVV(J1, J1) + c.invMass2 * b2Vec2.DotVV(J2, J2) + c.invMass3 * b2Vec2.DotVV(J3, J3); } if (sum === 0.0) { sum = c.invEffectiveMass; } const impulse = -stiffness * angle / sum; // p1 += (c.invMass1 * impulse) * J1; p1.x += (c.invMass1 * impulse) * J1.x; p1.y += (c.invMass1 * impulse) * J1.y; // p2 += (c.invMass2 * impulse) * J2; p2.x += (c.invMass2 * impulse) * J2.x; p2.y += (c.invMass2 * impulse) * J2.y; // p3 += (c.invMass3 * impulse) * J3; p3.x += (c.invMass3 * impulse) * J3.x; p3.y += (c.invMass3 * impulse) * J3.y; this.m_ps[c.i1].Copy(p1); this.m_ps[c.i2].Copy(p2); this.m_ps[c.i3].Copy(p3); } } SolveBend_XPBD_Angle(dt) { // b2Assert(dt > 0.0); for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const p1 = this.m_ps[c.i1]; const p2 = this.m_ps[c.i2]; const p3 = this.m_ps[c.i3]; const dp1 = p1.Clone().SelfSub(this.m_p0s[c.i1]); const dp2 = p2.Clone().SelfSub(this.m_p0s[c.i2]); const dp3 = p3.Clone().SelfSub(this.m_p0s[c.i3]); // b2Vec2 d1 = p2 - p1; const d1 = p2.Clone().SelfSub(p1); // b2Vec2 d2 = p3 - p2; const d2 = p3.Clone().SelfSub(p2); let L1sqr, L2sqr; if (this.m_tuning.isometric) { L1sqr = c.L1 * c.L1; L2sqr = c.L2 * c.L2; } else { L1sqr = d1.LengthSquared(); L2sqr = d2.LengthSquared(); } if (L1sqr * L2sqr === 0.0) { continue; } const a = b2Vec2.CrossVV(d1, d2); const b = b2Vec2.DotVV(d1, d2); const angle = b2Atan2(a, b); // b2Vec2 Jd1 = (-1.0 / L1sqr) * d1.Skew(); // b2Vec2 Jd2 = (1.0 / L2sqr) * d2.Skew(); // b2Vec2 J1 = -Jd1; // b2Vec2 J2 = Jd1 - Jd2; // b2Vec2 J3 = Jd2; // b2Vec2 Jd1 = (-1.0 / L1sqr) * d1.Skew(); const Jd1 = new b2Vec2().Copy(d1).SelfSkew().SelfMul(-1.0 / L1sqr); // b2Vec2 Jd2 = (1.0 / L2sqr) * d2.Skew(); const Jd2 = new b2Vec2().Copy(d2).SelfSkew().SelfMul(1.0 / L2sqr); // b2Vec2 J1 = -Jd1; const J1 = Jd1.Clone().SelfNeg(); // b2Vec2 J2 = Jd1 - Jd2; const J2 = Jd1.Clone().SelfSub(Jd2); // b2Vec2 J3 = Jd2; const J3 = Jd2; let sum; if (this.m_tuning.fixedEffectiveMass) { sum = c.invEffectiveMass; } else { sum = c.invMass1 * b2Vec2.DotVV(J1, J1) + c.invMass2 * b2Vec2.DotVV(J2, J2) + c.invMass3 * b2Vec2.DotVV(J3, J3); } if (sum === 0.0) { continue; } const alpha = 1.0 / (c.spring * dt * dt); const beta = dt * dt * c.damper; const sigma = alpha * beta / dt; const C = angle; // This is using the initial velocities const Cdot = b2Vec2.DotVV(J1, dp1) + b2Vec2.DotVV(J2, dp2) + b2Vec2.DotVV(J3, dp3); const B = C + alpha * c.lambda + sigma * Cdot; const sum2 = (1.0 + sigma) * sum + alpha; const impulse = -B / sum2; // p1 += (c.invMass1 * impulse) * J1; p1.x += (c.invMass1 * impulse) * J1.x; p1.y += (c.invMass1 * impulse) * J1.y; // p2 += (c.invMass2 * impulse) * J2; p2.x += (c.invMass2 * impulse) * J2.x; p2.y += (c.invMass2 * impulse) * J2.y; // p3 += (c.invMass3 * impulse) * J3; p3.x += (c.invMass3 * impulse) * J3.x; p3.y += (c.invMass3 * impulse) * J3.y; this.m_ps[c.i1].Copy(p1); this.m_ps[c.i2].Copy(p2); this.m_ps[c.i3].Copy(p3); c.lambda += impulse; } } SolveBend_PBD_Distance() { const stiffness = this.m_tuning.bendStiffness; for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const i1 = c.i1; const i2 = c.i3; const p1 = this.m_ps[i1].Clone(); const p2 = this.m_ps[i2].Clone(); // b2Vec2 d = p2 - p1; const d = p2.Clone().SelfSub(p1); const L = d.Normalize(); const sum = c.invMass1 + c.invMass3; if (sum === 0.0) { continue; } const s1 = c.invMass1 / sum; const s2 = c.invMass3 / sum; // p1 -= stiffness * s1 * (c.L1 + c.L2 - L) * d; p1.x -= stiffness * s1 * (c.L1 + c.L2 - L) * d.x; p1.y -= stiffness * s1 * (c.L1 + c.L2 - L) * d.y; // p2 += stiffness * s2 * (c.L1 + c.L2 - L) * d; p2.x += stiffness * s2 * (c.L1 + c.L2 - L) * d.x; p2.y += stiffness * s2 * (c.L1 + c.L2 - L) * d.y; this.m_ps[i1].Copy(p1); this.m_ps[i2].Copy(p2); } } SolveBend_PBD_Height() { const stiffness = this.m_tuning.bendStiffness; for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const p1 = this.m_ps[c.i1].Clone(); const p2 = this.m_ps[c.i2].Clone(); const p3 = this.m_ps[c.i3].Clone(); // Barycentric coordinates are held constant const d = new b2Vec2(); // b2Vec2 d = c.alpha1 * p1 + c.alpha2 * p3 - p2; d.x = c.alpha1 * p1.x + c.alpha2 * p3.x - p2.x; d.y = c.alpha1 * p1.y + c.alpha2 * p3.y - p2.y; const dLen = d.Length(); if (dLen === 0.0) { continue; } // b2Vec2 dHat = (1.0 / dLen) * d; const dHat = d.Clone().SelfMul(1.0 / dLen); // b2Vec2 J1 = c.alpha1 * dHat; const J1 = dHat.Clone().SelfMul(c.alpha1); // b2Vec2 J2 = -dHat; const J2 = dHat.Clone().SelfNeg(); // b2Vec2 J3 = c.alpha2 * dHat; const J3 = dHat.Clone().SelfMul(c.alpha2); const sum = c.invMass1 * c.alpha1 * c.alpha1 + c.invMass2 + c.invMass3 * c.alpha2 * c.alpha2; if (sum === 0.0) { continue; } const C = dLen; const mass = 1.0 / sum; const impulse = -stiffness * mass * C; // p1 += (c.invMass1 * impulse) * J1; p1.x += (c.invMass1 * impulse) * J1.x; p1.y += (c.invMass1 * impulse) * J1.y; // p2 += (c.invMass2 * impulse) * J2; p2.x += (c.invMass2 * impulse) * J2.x; p2.y += (c.invMass2 * impulse) * J2.y; // p3 += (c.invMass3 * impulse) * J3; p3.x += (c.invMass3 * impulse) * J3.x; p3.y += (c.invMass3 * impulse) * J3.y; this.m_ps[c.i1].Copy(p1); this.m_ps[c.i2].Copy(p2); this.m_ps[c.i3].Copy(p3); } } // M. Kelager: A Triangle Bending Constraint Model for PBD SolveBend_PBD_Triangle() { const stiffness = this.m_tuning.bendStiffness; for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const b0 = this.m_ps[c.i1].Clone(); const v = this.m_ps[c.i2].Clone(); const b1 = this.m_ps[c.i3].Clone(); const wb0 = c.invMass1; const wv = c.invMass2; const wb1 = c.invMass3; const W = wb0 + wb1 + 2.0 * wv; const invW = stiffness / W; const d = new b2Vec2(); d.x = v.x - (1.0 / 3.0) * (b0.x + v.x + b1.x); d.y = v.y - (1.0 / 3.0) * (b0.y + v.y + b1.y); const db0 = new b2Vec2(); db0.x = 2.0 * wb0 * invW * d.x; db0.y = 2.0 * wb0 * invW * d.y; const dv = new b2Vec2(); dv.x = -4.0 * wv * invW * d.x; dv.y = -4.0 * wv * invW * d.y; const db1 = new b2Vec2(); db1.x = 2.0 * wb1 * invW * d.x; db1.y = 2.0 * wb1 * invW * d.y; b0.SelfAdd(db0); v.SelfAdd(dv); b1.SelfAdd(db1); this.m_ps[c.i1].Copy(b0); this.m_ps[c.i2].Copy(v); this.m_ps[c.i3].Copy(b1); } } ApplyBendForces(dt) { // omega = 2 * pi * hz const omega = 2.0 * b2_pi * this.m_tuning.bendHertz; for (let i = 0; i < this.m_bendCount; ++i) { const c = this.m_bendConstraints[i]; const p1 = this.m_ps[c.i1].Clone(); const p2 = this.m_ps[c.i2].Clone(); const p3 = this.m_ps[c.i3].Clone(); const v1 = this.m_vs[c.i1]; const v2 = this.m_vs[c.i2]; const v3 = this.m_vs[c.i3]; // b2Vec2 d1 = p2 - p1; const d1 = p1.Clone().SelfSub(p1); // b2Vec2 d2 = p3 - p2; const d2 = p3.Clone().SelfSub(p2); let L1sqr, L2sqr; if (this.m_tuning.isometric) { L1sqr = c.L1 * c.L1; L2sqr = c.L2 * c.L2; } else { L1sqr = d1.LengthSquared(); L2sqr = d2.LengthSquared(); } if (L1sqr * L2sqr === 0.0) { continue; } const a = b2Vec2.CrossVV(d1, d2); const b = b2Vec2.DotVV(d1, d2); const angle = b2Atan2(a, b); // b2Vec2 Jd1 = (-1.0 / L1sqr) * d1.Skew(); // b2Vec2 Jd2 = (1.0 / L2sqr) * d2.Skew(); // b2Vec2 J1 = -Jd1; // b2Vec2 J2 = Jd1 - Jd2; // b2Vec2 J3 = Jd2; // b2Vec2 Jd1 = (-1.0 / L1sqr) * d1.Skew(); const Jd1 = new b2Vec2().Copy(d1).SelfSkew().SelfMul(-1.0 / L1sqr); // b2Vec2 Jd2 = (1.0 / L2sqr) * d2.Skew(); const Jd2 = new b2Vec2().Copy(d2).SelfSkew().SelfMul(1.0 / L2sqr); // b2Vec2 J1 = -Jd1; const J1 = Jd1.Clone().SelfNeg(); // b2Vec2 J2 = Jd1 - Jd2; const J2 = Jd1.Clone().SelfSub(Jd2); // b2Vec2 J3 = Jd2; const J3 = Jd2; let sum = 0.0; if (this.m_tuning.fixedEffectiveMass) { sum = c.invEffectiveMass; } else { sum = c.invMass1 * b2Vec2.DotVV(J1, J1) + c.invMass2 * b2Vec2.DotVV(J2, J2) + c.invMass3 * b2Vec2.DotVV(J3, J3); } if (sum === 0.0) { continue; } const mass = 1.0 / sum; const spring = mass * omega * omega; const damper = 2.0 * mass * this.m_tuning.bendDamping * omega; const C = angle; const Cdot = b2Vec2.DotVV(J1, v1) + b2Vec2.DotVV(J2, v2) + b2Vec2.DotVV(J3, v3); const impulse = -dt * (spring * C + damper * Cdot); // this.m_vs[c.i1] += (c.invMass1 * impulse) * J1; this.m_vs[c.i1].x += (c.invMass1 * impulse) * J1.x; this.m_vs[c.i1].y += (c.invMass1 * impulse) * J1.y; // this.m_vs[c.i2] += (c.invMass2 * impulse) * J2; this.m_vs[c.i2].x += (c.invMass2 * impulse) * J2.x; this.m_vs[c.i2].y += (c.invMass2 * impulse) * J2.y; // this.m_vs[c.i3] += (c.invMass3 * impulse) * J3; this.m_vs[c.i3].x += (c.invMass3 * impulse) * J3.x; this.m_vs[c.i3].y += (c.invMass3 * impulse) * J3.y; } } } const staticBody = exports.b2BodyType.b2_staticBody; const kinematicBody = exports.b2BodyType.b2_kinematicBody; const dynamicBody = exports.b2BodyType.b2_dynamicBody; const springAngleBendingModel = exports.b2BendingModel.b2_springAngleBendingModel; const pbdAngleBendingModel = exports.b2BendingModel.b2_pbdAngleBendingModel; const xpbdAngleBendingModel = exports.b2BendingModel.b2_xpbdAngleBendingModel; const pbdDistanceBendingModel = exports.b2BendingModel.b2_pbdDistanceBendingModel; const pbdHeightBendingModel = exports.b2BendingModel.b2_pbdHeightBendingModel; const pbdTriangleBendingModel = exports.b2BendingModel.b2_pbdTriangleBendingModel; const pbdStretchingModel = exports.b2StretchingModel.b2_pbdStretchingModel; const xpbdStretchingModel = exports.b2StretchingModel.b2_xpbdStretchingModel; exports.b2AABB = b2AABB; exports.b2Abs = b2Abs; exports.b2Acos = b2Acos; exports.b2Alloc = b2Alloc; exports.b2AngularStiffness = b2AngularStiffness; exports.b2AreaJoint = b2AreaJoint; exports.b2AreaJointDef = b2AreaJointDef; exports.b2Asin = b2Asin; exports.b2Assert = b2Assert; exports.b2Atan2 = b2Atan2; exports.b2BlockAllocator = b2BlockAllocator; exports.b2Body = b2Body; exports.b2BodyDef = b2BodyDef; exports.b2BroadPhase = b2BroadPhase; exports.b2ChainAndCircleContact = b2ChainAndCircleContact; exports.b2ChainAndPolygonContact = b2ChainAndPolygonContact; exports.b2ChainShape = b2ChainShape; exports.b2CircleContact = b2CircleContact; exports.b2CircleShape = b2CircleShape; exports.b2Clamp = b2Clamp; exports.b2ClipSegmentToLine = b2ClipSegmentToLine; exports.b2ClipVertex = b2ClipVertex; exports.b2CollideCircles = b2CollideCircles; exports.b2CollideEdgeAndCircle = b2CollideEdgeAndCircle; exports.b2CollideEdgeAndPolygon = b2CollideEdgeAndPolygon; exports.b2CollidePolygonAndCircle = b2CollidePolygonAndCircle; exports.b2CollidePolygons = b2CollidePolygons; exports.b2Color = b2Color; exports.b2Contact = b2Contact; exports.b2ContactEdge = b2ContactEdge; exports.b2ContactFactory = b2ContactFactory; exports.b2ContactFeature = b2ContactFeature; exports.b2ContactFilter = b2ContactFilter; exports.b2ContactID = b2ContactID; exports.b2ContactImpulse = b2ContactImpulse; exports.b2ContactListener = b2ContactListener; exports.b2ContactManager = b2ContactManager; exports.b2ContactPositionConstraint = b2ContactPositionConstraint; exports.b2ContactRegister = b2ContactRegister; exports.b2ContactSolver = b2ContactSolver; exports.b2ContactSolverDef = b2ContactSolverDef; exports.b2ContactVelocityConstraint = b2ContactVelocityConstraint; exports.b2Cos = b2Cos; exports.b2Counter = b2Counter; exports.b2DegToRad = b2DegToRad; exports.b2DestructionListener = b2DestructionListener; exports.b2Distance = b2Distance; exports.b2DistanceInput = b2DistanceInput; exports.b2DistanceJoint = b2DistanceJoint; exports.b2DistanceJointDef = b2DistanceJointDef; exports.b2DistanceOutput = b2DistanceOutput; exports.b2DistanceProxy = b2DistanceProxy; exports.b2Draw = b2Draw; exports.b2DynamicTree = b2DynamicTree; exports.b2EdgeAndCircleContact = b2EdgeAndCircleContact; exports.b2EdgeAndPolygonContact = b2EdgeAndPolygonContact; exports.b2EdgeShape = b2EdgeShape; exports.b2Filter = b2Filter; exports.b2Fixture = b2Fixture; exports.b2FixtureDef = b2FixtureDef; exports.b2FixtureProxy = b2FixtureProxy; exports.b2Free = b2Free; exports.b2FrictionJoint = b2FrictionJoint; exports.b2FrictionJointDef = b2FrictionJointDef; exports.b2GearJoint = b2GearJoint; exports.b2GearJointDef = b2GearJointDef; exports.b2GetPointStates = b2GetPointStates; exports.b2GrowableStack = b2GrowableStack; exports.b2InvSqrt = b2InvSqrt; exports.b2IsPowerOfTwo = b2IsPowerOfTwo; exports.b2IsValid = b2IsValid; exports.b2Island = b2Island; exports.b2Jacobian = b2Jacobian; exports.b2Joint = b2Joint; exports.b2JointDef = b2JointDef; exports.b2JointEdge = b2JointEdge; exports.b2LinearStiffness = b2LinearStiffness; exports.b2Log = b2Log; exports.b2MakeArray = b2MakeArray; exports.b2MakeNullArray = b2MakeNullArray; exports.b2MakeNumberArray = b2MakeNumberArray; exports.b2Manifold = b2Manifold; exports.b2ManifoldPoint = b2ManifoldPoint; exports.b2MassData = b2MassData; exports.b2Mat22 = b2Mat22; exports.b2Mat33 = b2Mat33; exports.b2Max = b2Max; exports.b2Maybe = b2Maybe; exports.b2Min = b2Min; exports.b2MixFriction = b2MixFriction; exports.b2MixRestitution = b2MixRestitution; exports.b2MixRestitutionThreshold = b2MixRestitutionThreshold; exports.b2MotorJoint = b2MotorJoint; exports.b2MotorJointDef = b2MotorJointDef; exports.b2MouseJoint = b2MouseJoint; exports.b2MouseJointDef = b2MouseJointDef; exports.b2NextPowerOfTwo = b2NextPowerOfTwo; exports.b2Pair = b2Pair; exports.b2ParseInt = b2ParseInt; exports.b2ParseUInt = b2ParseUInt; exports.b2PolygonAndCircleContact = b2PolygonAndCircleContact; exports.b2PolygonContact = b2PolygonContact; exports.b2PolygonShape = b2PolygonShape; exports.b2Position = b2Position; exports.b2PositionSolverManifold = b2PositionSolverManifold; exports.b2Pow = b2Pow; exports.b2PrismaticJoint = b2PrismaticJoint; exports.b2PrismaticJointDef = b2PrismaticJointDef; exports.b2Profile = b2Profile; exports.b2PulleyJoint = b2PulleyJoint; exports.b2PulleyJointDef = b2PulleyJointDef; exports.b2QueryCallback = b2QueryCallback; exports.b2RadToDeg = b2RadToDeg; exports.b2Random = b2Random; exports.b2RandomRange = b2RandomRange; exports.b2RayCastCallback = b2RayCastCallback; exports.b2RayCastInput = b2RayCastInput; exports.b2RayCastOutput = b2RayCastOutput; exports.b2RevoluteJoint = b2RevoluteJoint; exports.b2RevoluteJointDef = b2RevoluteJointDef; exports.b2Rope = b2Rope; exports.b2RopeDef = b2RopeDef; exports.b2RopeTuning = b2RopeTuning; exports.b2Rot = b2Rot; exports.b2SeparationFunction = b2SeparationFunction; exports.b2Shape = b2Shape; exports.b2ShapeCast = b2ShapeCast; exports.b2ShapeCastInput = b2ShapeCastInput; exports.b2ShapeCastOutput = b2ShapeCastOutput; exports.b2Simplex = b2Simplex; exports.b2SimplexCache = b2SimplexCache; exports.b2SimplexVertex = b2SimplexVertex; exports.b2Sin = b2Sin; exports.b2SolverData = b2SolverData; exports.b2Sq = b2Sq; exports.b2Sqrt = b2Sqrt; exports.b2StackAllocator = b2StackAllocator; exports.b2Swap = b2Swap; exports.b2Sweep = b2Sweep; exports.b2TOIInput = b2TOIInput; exports.b2TOIOutput = b2TOIOutput; exports.b2TestOverlapAABB = b2TestOverlapAABB; exports.b2TestOverlapShape = b2TestOverlapShape; exports.b2TimeOfImpact = b2TimeOfImpact; exports.b2TimeStep = b2TimeStep; exports.b2Timer = b2Timer; exports.b2Transform = b2Transform; exports.b2TreeNode = b2TreeNode; exports.b2Vec2 = b2Vec2; exports.b2Vec2_zero = b2Vec2_zero; exports.b2Vec3 = b2Vec3; exports.b2Velocity = b2Velocity; exports.b2VelocityConstraintPoint = b2VelocityConstraintPoint; exports.b2Version = b2Version; exports.b2WeldJoint = b2WeldJoint; exports.b2WeldJointDef = b2WeldJointDef; exports.b2WheelJoint = b2WheelJoint; exports.b2WheelJointDef = b2WheelJointDef; exports.b2World = b2World; exports.b2WorldManifold = b2WorldManifold; exports.b2_180_over_pi = b2_180_over_pi; exports.b2_aabbExtension = b2_aabbExtension; exports.b2_aabbMultiplier = b2_aabbMultiplier; exports.b2_angularSleepTolerance = b2_angularSleepTolerance; exports.b2_angularSlop = b2_angularSlop; exports.b2_baumgarte = b2_baumgarte; exports.b2_branch = b2_branch; exports.b2_commit = b2_commit; exports.b2_epsilon = b2_epsilon; exports.b2_epsilon_sq = b2_epsilon_sq; exports.b2_gjk_reset = b2_gjk_reset; exports.b2_lengthUnitsPerMeter = b2_lengthUnitsPerMeter; exports.b2_linearSleepTolerance = b2_linearSleepTolerance; exports.b2_linearSlop = b2_linearSlop; exports.b2_maxAngularCorrection = b2_maxAngularCorrection; exports.b2_maxFloat = b2_maxFloat; exports.b2_maxLinearCorrection = b2_maxLinearCorrection; exports.b2_maxManifoldPoints = b2_maxManifoldPoints; exports.b2_maxPolygonVertices = b2_maxPolygonVertices; exports.b2_maxRotation = b2_maxRotation; exports.b2_maxRotationSquared = b2_maxRotationSquared; exports.b2_maxSubSteps = b2_maxSubSteps; exports.b2_maxTOIContacts = b2_maxTOIContacts; exports.b2_maxTranslation = b2_maxTranslation; exports.b2_maxTranslationSquared = b2_maxTranslationSquared; exports.b2_minPulleyLength = b2_minPulleyLength; exports.b2_pi = b2_pi; exports.b2_pi_over_180 = b2_pi_over_180; exports.b2_polygonRadius = b2_polygonRadius; exports.b2_timeToSleep = b2_timeToSleep; exports.b2_toiBaumgarte = b2_toiBaumgarte; exports.b2_toi_reset = b2_toi_reset; exports.b2_two_pi = b2_two_pi; exports.b2_version = b2_version; exports.dynamicBody = dynamicBody; exports.get_g_blockSolve = get_g_blockSolve; exports.kinematicBody = kinematicBody; exports.pbdAngleBendingModel = pbdAngleBendingModel; exports.pbdDistanceBendingModel = pbdDistanceBendingModel; exports.pbdHeightBendingModel = pbdHeightBendingModel; exports.pbdStretchingModel = pbdStretchingModel; exports.pbdTriangleBendingModel = pbdTriangleBendingModel; exports.set_g_blockSolve = set_g_blockSolve; exports.springAngleBendingModel = springAngleBendingModel; exports.staticBody = staticBody; exports.xpbdAngleBendingModel = xpbdAngleBendingModel; exports.xpbdStretchingModel = xpbdStretchingModel; Object.defineProperty(exports, '__esModule', { value: true }); return exports; }({})); (function (exports, Laya) { 'use strict'; class IPhysics { } IPhysics.RigidBody = null; IPhysics.Physics = null; class ColliderBase extends Laya.Component { constructor() { super(...arguments); this._isSensor = false; this._density = 10; this._friction = 0.2; this._restitution = 0; } getDef() { if (!this._def) { var def = new window.box2d.b2FixtureDef(); def.density = this.density; def.friction = this.friction; def.isSensor = this.isSensor; def.restitution = this.restitution; def.shape = this._shape; this._def = def; } return this._def; } _onEnable() { if (this.rigidBody) { this.refresh(); } else { Laya.Laya.systemTimer.callLater(this, this._checkRigidBody); } } _checkRigidBody() { if (!this.rigidBody) { var comp = this.owner.getComponent(IPhysics.RigidBody); if (comp) { this.rigidBody = comp; this.refresh(); } } } _onDestroy() { if (this.rigidBody) { if (this.fixture) { if (this.fixture.GetBody() == this.rigidBody._getOriBody()) { this.rigidBody.body.DestroyFixture(this.fixture); } this.fixture = null; } this.rigidBody = null; this._shape = null; this._def = null; } } get isSensor() { return this._isSensor; } set isSensor(value) { this._isSensor = value; if (this._def) { this._def.isSensor = value; this.refresh(); } } get density() { return this._density; } set density(value) { this._density = value; if (this._def) { this._def.density = value; this.refresh(); } } get friction() { return this._friction; } set friction(value) { this._friction = value; if (this._def) { this._def.friction = value; this.refresh(); } } get restitution() { return this._restitution; } set restitution(value) { this._restitution = value; if (this._def) { this._def.restitution = value; this.refresh(); } } refresh() { if (this.enabled && this.rigidBody) { var body = this.rigidBody.body; if (this.fixture) { if (this.fixture.GetBody() == this.rigidBody.body) { this.rigidBody.body.DestroyFixture(this.fixture); } this.fixture.Destroy(); this.fixture = null; } var def = this.getDef(); def.filter.groupIndex = this.rigidBody.group; def.filter.categoryBits = this.rigidBody.category; def.filter.maskBits = this.rigidBody.mask; this.fixture = body.CreateFixture(def); this.fixture.collider = this; } } resetShape(re = true) { } get isSingleton() { return false; } } Laya.ClassUtils.regClass("laya.physics.ColliderBase", ColliderBase); Laya.ClassUtils.regClass("Laya.ColliderBase", ColliderBase); class RigidBody extends Laya.Component { constructor() { super(...arguments); this._type = "dynamic"; this._allowSleep = true; this._angularVelocity = 0; this._angularDamping = 0; this._linearVelocity = { x: 0, y: 0 }; this._linearDamping = 0; this._bullet = false; this._allowRotation = true; this._gravityScale = 1; this.group = 0; this.category = 1; this.mask = -1; this.label = "RigidBody"; } _createBody() { if (this._body || !this.owner) return; var sp = this.owner; var box2d = window.box2d; var def = new box2d.b2BodyDef(); var point = sp.localToGlobal(Laya.Point.TEMP.setTo(0, 0), false, IPhysics.Physics.I.worldRoot); def.position.Set(point.x / IPhysics.Physics.PIXEL_RATIO, point.y / IPhysics.Physics.PIXEL_RATIO); def.angle = Laya.Utils.toRadian(sp.rotation); def.allowSleep = this._allowSleep; def.angularDamping = this._angularDamping; def.angularVelocity = this._angularVelocity; def.bullet = this._bullet; def.fixedRotation = !this._allowRotation; def.gravityScale = this._gravityScale; def.linearDamping = this._linearDamping; var obj = this._linearVelocity; if (obj && obj.x != 0 || obj.y != 0) { def.linearVelocity = new box2d.b2Vec2(obj.x, obj.y); } def.type = box2d.b2BodyType["b2_" + this._type + "Body"]; this._body = IPhysics.Physics.I._createBody(def); this.resetCollider(false); } _onAwake() { this._createBody(); } _onEnable() { var _$this = this; this._createBody(); Laya.Laya.physicsTimer.frameLoop(1, this, this._sysPhysicToNode); var sp = this.owner; if (this.accessGetSetFunc(sp, "x", "set") && !sp._changeByRigidBody) { sp._changeByRigidBody = true; function setX(value) { _$this.accessGetSetFunc(sp, "x", "set")(value); _$this._sysPosToPhysic(); } this._overSet(sp, "x", setX); function setY(value) { _$this.accessGetSetFunc(sp, "y", "set")(value); _$this._sysPosToPhysic(); } this._overSet(sp, "y", setY); function setRotation(value) { _$this.accessGetSetFunc(sp, "rotation", "set")(value); _$this._sysNodeToPhysic(); } this._overSet(sp, "rotation", setRotation); function setScaleX(value) { _$this.accessGetSetFunc(sp, "scaleX", "set")(value); _$this.resetCollider(true); } this._overSet(sp, "scaleX", setScaleX); function setScaleY(value) { _$this.accessGetSetFunc(sp, "scaleY", "set")(value); _$this.resetCollider(true); } this._overSet(sp, "scaleY", setScaleY); } } accessGetSetFunc(obj, prop, accessor) { if (["get", "set"].indexOf(accessor) === -1) { return; } let privateProp = `_$${accessor}_${prop}`; if (obj[privateProp]) { return obj[privateProp]; } let ObjConstructor = obj.constructor; let des; while (ObjConstructor) { des = Object.getOwnPropertyDescriptor(ObjConstructor.prototype, prop); if (des && des[accessor]) { obj[privateProp] = des[accessor].bind(obj); break; } ObjConstructor = Object.getPrototypeOf(ObjConstructor); } return obj[privateProp]; } resetCollider(resetShape) { var comps = this.owner.getComponents(ColliderBase); if (comps) { for (var i = 0, n = comps.length; i < n; i++) { var collider = comps[i]; collider.rigidBody = this; if (resetShape) collider.resetShape(); else collider.refresh(); } } } _sysPhysicToNode() { if (this.type != "static" && this._body.IsAwake()) { var pos = this._body.GetPosition(); var ang = this._body.GetAngle(); var sp = this.owner; this.accessGetSetFunc(sp, "rotation", "set")(Laya.Utils.toAngle(ang) - sp.parent.globalRotation); var point = sp.globalToLocal(Laya.Point.TEMP.setTo(pos.x * IPhysics.Physics.PIXEL_RATIO, pos.y * IPhysics.Physics.PIXEL_RATIO), false, IPhysics.Physics.I.worldRoot); point.x += sp.pivotX; point.y += sp.pivotY; point = sp.toParentPoint(point); this.accessGetSetFunc(sp, "x", "set")(point.x); this.accessGetSetFunc(sp, "y", "set")(point.y); } } _sysNodeToPhysic() { var sp = this.owner; this._body.SetAngle(Laya.Utils.toRadian(sp.rotation)); var p = sp.localToGlobal(Laya.Point.TEMP.setTo(0, 0), false, IPhysics.Physics.I.worldRoot); this._body.SetPositionXY(p.x / IPhysics.Physics.PIXEL_RATIO, p.y / IPhysics.Physics.PIXEL_RATIO); } _sysPosToPhysic() { var sp = this.owner; var p = sp.localToGlobal(Laya.Point.TEMP.setTo(0, 0), false, IPhysics.Physics.I.worldRoot); this._body.SetPositionXY(p.x / IPhysics.Physics.PIXEL_RATIO, p.y / IPhysics.Physics.PIXEL_RATIO); } _overSet(sp, prop, getfun) { Object.defineProperty(sp, prop, { get: this.accessGetSetFunc(sp, prop, "get"), set: getfun, enumerable: false, configurable: true }); } _onDisable() { Laya.Laya.physicsTimer.clear(this, this._sysPhysicToNode); this._body && IPhysics.Physics.I._removeBody(this._body); this._body = null; var owner = this.owner; if (owner._changeByRigidBody) { this._overSet(owner, "x", this.accessGetSetFunc(owner, "x", "set")); this._overSet(owner, "y", this.accessGetSetFunc(owner, "y", "set")); this._overSet(owner, "rotation", this.accessGetSetFunc(owner, "rotation", "set")); this._overSet(owner, "scaleX", this.accessGetSetFunc(owner, "scaleX", "set")); this._overSet(owner, "scaleY", this.accessGetSetFunc(owner, "scaleY", "set")); owner._changeByRigidBody = false; } } getBody() { if (!this._body) this._onAwake(); return this._body; } _getOriBody() { return this._body; } get body() { if (!this._body) this._onAwake(); return this._body; } applyForce(position, force) { if (!this._body) this._onAwake(); this._body.ApplyForce(force, position); } applyForceToCenter(force) { if (!this._body) this._onAwake(); this._body.ApplyForceToCenter(force); } applyLinearImpulse(position, impulse) { if (!this._body) this._onAwake(); this._body.ApplyLinearImpulse(impulse, position); } applyLinearImpulseToCenter(impulse) { if (!this._body) this._onAwake(); this._body.ApplyLinearImpulseToCenter(impulse); } applyTorque(torque) { if (!this._body) this._onAwake(); this._body.ApplyTorque(torque); } setVelocity(velocity) { if (!this._body) this._onAwake(); this._body.SetLinearVelocity(velocity); } setAngle(value) { if (!this._body) this._onAwake(); this._body.SetAngle(value); this._body.SetAwake(true); } getMass() { return this._body ? this._body.GetMass() : 0; } getCenter() { if (!this._body) this._onAwake(); var p = this._body.GetLocalCenter(); p.x = p.x * IPhysics.Physics.PIXEL_RATIO; p.y = p.y * IPhysics.Physics.PIXEL_RATIO; return p; } getWorldCenter() { if (!this._body) this._onAwake(); var p = this._body.GetWorldCenter(); p.x = p.x * IPhysics.Physics.PIXEL_RATIO; p.y = p.y * IPhysics.Physics.PIXEL_RATIO; return p; } get type() { return this._type; } set type(value) { this._type = value; if (this._body) this._body.SetType(window.box2d.b2BodyType["b2_" + this._type + "Body"]); } get gravityScale() { return this._gravityScale; } set gravityScale(value) { this._gravityScale = value; if (this._body) this._body.SetGravityScale(value); } get allowRotation() { return this._allowRotation; } set allowRotation(value) { this._allowRotation = value; if (this._body) this._body.SetFixedRotation(!value); } get allowSleep() { return this._allowSleep; } set allowSleep(value) { this._allowSleep = value; if (this._body) this._body.SetSleepingAllowed(value); } get angularDamping() { return this._angularDamping; } set angularDamping(value) { this._angularDamping = value; if (this._body) this._body.SetAngularDamping(value); } get angularVelocity() { if (this._body) return this._body.GetAngularVelocity(); return this._angularVelocity; } set angularVelocity(value) { this._angularVelocity = value; if (this._body) this._body.SetAngularVelocity(value); } get linearDamping() { return this._linearDamping; } set linearDamping(value) { this._linearDamping = value; if (this._body) this._body.SetLinearDamping(value); } get linearVelocity() { if (this._body) { var vec = this._body.GetLinearVelocity(); return { x: vec.x, y: vec.y }; } return this._linearVelocity; } set linearVelocity(value) { if (!value) return; if (value instanceof Array) { value = { x: value[0], y: value[1] }; } this._linearVelocity = value; if (this._body) this._body.SetLinearVelocity(new window.box2d.b2Vec2(value.x, value.y)); } get bullet() { return this._bullet; } set bullet(value) { this._bullet = value; if (this._body) this._body.SetBullet(value); } } Laya.ClassUtils.regClass("laya.physics.RigidBody", RigidBody); Laya.ClassUtils.regClass("Laya.RigidBody", RigidBody); class DestructionListener { SayGoodbyeJoint(params) { params.m_userData && (params.m_userData.isDestroy = true); } SayGoodbyeFixture(params) { } SayGoodbyeParticleGroup(params) { } SayGoodbyeParticle(params) { } } class Physics extends Laya.EventDispatcher { constructor() { super(); this.box2d = window.box2d; this.velocityIterations = 8; this.positionIterations = 3; this._eventList = []; } static get I() { return Physics._I || (Physics._I = new Physics()); } static enable(options = null) { Physics.I.start(options); IPhysics.RigidBody = RigidBody; IPhysics.Physics = this; } start(options = null) { if (!this._enabled) { this._enabled = true; options || (options = {}); var box2d = window.box2d; if (box2d == null) { console.error("Can not find box2d libs, you should request box2d.js first."); return; } var gravity = new box2d.b2Vec2(0, options.gravity || 500 / Physics.PIXEL_RATIO); this.world = new box2d.b2World(gravity); this.world.SetDestructionListener(new DestructionListener()); this.world.SetContactListener(new ContactListener()); this.allowSleeping = options.allowSleeping == null ? true : options.allowSleeping; if (!options.customUpdate) Laya.Laya.physicsTimer.frameLoop(1, this, this._update); this._emptyBody = this._createBody(new window.box2d.b2BodyDef()); } } _update() { var delta = Laya.Laya.timer.delta / 1000; if (delta > .033) { delta = .033; } this.world.Step(delta, this.velocityIterations, this.positionIterations, 3); var len = this._eventList.length; if (len > 0) { for (var i = 0; i < len; i += 2) { this._sendEvent(this._eventList[i], this._eventList[i + 1]); } this._eventList.length = 0; } } _sendEvent(type, contact) { var colliderA = contact.GetFixtureA().collider; var colliderB = contact.GetFixtureB().collider; var ownerA = colliderA.owner; var ownerB = colliderB.owner; contact.getHitInfo = function () { var manifold = new this.box2d.b2WorldManifold(); this.GetWorldManifold(manifold); var p = manifold.points[0]; p.x *= Physics.PIXEL_RATIO; p.y *= Physics.PIXEL_RATIO; return manifold; }; if (ownerA) { var args = [colliderB, colliderA, contact]; if (type === 0) { ownerA.event(Laya.Event.TRIGGER_ENTER, args); if (!ownerA["_triggered"]) { ownerA["_triggered"] = true; } else { ownerA.event(Laya.Event.TRIGGER_STAY, args); } } else { ownerA["_triggered"] = false; ownerA.event(Laya.Event.TRIGGER_EXIT, args); } } if (ownerB) { args = [colliderA, colliderB, contact]; if (type === 0) { ownerB.event(Laya.Event.TRIGGER_ENTER, args); if (!ownerB["_triggered"]) { ownerB["_triggered"] = true; } else { ownerB.event(Laya.Event.TRIGGER_STAY, args); } } else { ownerB["_triggered"] = false; ownerB.event(Laya.Event.TRIGGER_EXIT, args); } } } _createBody(def) { if (this.world) { return this.world.CreateBody(def); } else { console.error('The physical engine should be initialized first.use "Physics.enable()"'); return null; } } _removeBody(body) { if (this.world) { this.world.DestroyBody(body); } else { console.error('The physical engine should be initialized first.use "Physics.enable()"'); } } _createJoint(def) { if (this.world) { let joint = this.world.CreateJoint(def); joint.m_userData = {}; joint.m_userData.isDestroy = false; return joint; } else { console.error('The physical engine should be initialized first.use "Physics.enable()"'); return null; } } _removeJoint(joint) { if (this.world) { this.world.DestroyJoint(joint); } else { console.error('The physical engine should be initialized first.use "Physics.enable()"'); } } stop() { Laya.Laya.physicsTimer.clear(this, this._update); } get allowSleeping() { return this.world.GetAllowSleeping(); } set allowSleeping(value) { this.world.SetAllowSleeping(value); } get gravity() { return this.world.GetGravity(); } set gravity(value) { this.world.SetGravity(value); } getBodyCount() { return this.world.GetBodyCount(); } getContactCount() { return this.world.GetContactCount(); } getJointCount() { return this.world.GetJointCount(); } get worldRoot() { return this._worldRoot || Laya.Laya.stage; } set worldRoot(value) { this._worldRoot = value; if (value) { var p = value.localToGlobal(Laya.Point.TEMP.setTo(0, 0)); this.world.ShiftOrigin({ x: -p.x / Physics.PIXEL_RATIO, y: -p.y / Physics.PIXEL_RATIO }); } } updatePhysicsByWorldRoot() { if (!!this.worldRoot) { var p = this.worldRoot.localToGlobal(Laya.Point.TEMP.setTo(0, 0)); this.world.ShiftOrigin({ x: -p.x / Physics.PIXEL_RATIO, y: -p.y / Physics.PIXEL_RATIO }); } } } Physics.PIXEL_RATIO = 50; Laya.ClassUtils.regClass("laya.physics.Physics", Physics); Laya.ClassUtils.regClass("Laya.Physics", Physics); class ContactListener { BeginContact(contact) { Physics.I._eventList.push(0, contact); } EndContact(contact) { Physics.I._eventList.push(1, contact); } PreSolve(contact, oldManifold) { } PostSolve(contact, impulse) { } } class BoxCollider extends ColliderBase { constructor() { super(...arguments); this._x = 0; this._y = 0; this._width = 100; this._height = 100; } getDef() { if (!this._shape) { this._shape = new window.box2d.b2PolygonShape(); this._setShape(false); } this.label = (this.label || "BoxCollider"); return super.getDef(); } _setShape(re = true) { var scaleX = (this.owner["scaleX"] || 1); var scaleY = (this.owner["scaleY"] || 1); this._shape.SetAsBox(this._width / 2 / Physics.PIXEL_RATIO * scaleX, this._height / 2 / Physics.PIXEL_RATIO * scaleY, new window.box2d.b2Vec2((this._width / 2 + this._x) / Physics.PIXEL_RATIO * scaleX, (this._height / 2 + this._y) / Physics.PIXEL_RATIO * scaleY)); if (re) this.refresh(); } get x() { return this._x; } set x(value) { this._x = value; if (this._shape) this._setShape(); } get y() { return this._y; } set y(value) { this._y = value; if (this._shape) this._setShape(); } get width() { return this._width; } set width(value) { if (value <= 0) throw "BoxCollider size cannot be less than 0"; this._width = value; if (this._shape) this._setShape(); } get height() { return this._height; } set height(value) { if (value <= 0) throw "BoxCollider size cannot be less than 0"; this._height = value; if (this._shape) this._setShape(); } resetShape(re = true) { this._setShape(); } } Laya.ClassUtils.regClass("laya.physics.BoxCollider", BoxCollider); Laya.ClassUtils.regClass("Laya.BoxCollider", BoxCollider); class ChainCollider extends ColliderBase { constructor() { super(...arguments); this._x = 0; this._y = 0; this._points = "0,0,100,0"; this._loop = false; } getDef() { if (!this._shape) { this._shape = new window.box2d.b2ChainShape(); this._setShape(false); } this.label = (this.label || "ChainCollider"); return super.getDef(); } _setShape(re = true) { var arr = this._points.split(","); var len = arr.length; if (len % 2 == 1) throw "ChainCollider points lenth must a multiplier of 2"; var ps = []; for (var i = 0, n = len; i < n; i += 2) { ps.push(new window.box2d.b2Vec2((this._x + parseInt(arr[i])) / Physics.PIXEL_RATIO, (this._y + parseInt(arr[i + 1])) / Physics.PIXEL_RATIO)); } this._loop ? this._shape.CreateLoop(ps, len / 2) : this._shape.CreateChain(ps, len / 2, new window.box2d.b2Vec2(0, 0), new window.box2d.b2Vec2(0, 0)); if (re) this.refresh(); } get x() { return this._x; } set x(value) { this._x = value; if (this._shape) this._setShape(); } get y() { return this._y; } set y(value) { this._y = value; if (this._shape) this._setShape(); } get points() { return this._points; } set points(value) { if (!value) throw "ChainCollider points cannot be empty"; this._points = value; if (this._shape) this._setShape(); } get loop() { return this._loop; } set loop(value) { this._loop = value; if (this._shape) this._setShape(); } } Laya.ClassUtils.regClass("laya.physics.ChainCollider", ChainCollider); Laya.ClassUtils.regClass("Laya.ChainCollider", ChainCollider); class CircleCollider extends ColliderBase { constructor() { super(...arguments); this._x = 0; this._y = 0; this._radius = 50; } getDef() { if (!this._shape) { this._shape = new window.box2d.b2CircleShape(); this._setShape(false); } this.label = (this.label || "CircleCollider"); return super.getDef(); } _setShape(re = true) { var scale = this.owner["scaleX"] || 1; this._shape.m_radius = this._radius / Physics.PIXEL_RATIO * scale; this._shape.m_p.Set((this._radius + this._x) / Physics.PIXEL_RATIO * scale, (this._radius + this._y) / Physics.PIXEL_RATIO * scale); if (re) this.refresh(); } get x() { return this._x; } set x(value) { this._x = value; if (this._shape) this._setShape(); } get y() { return this._y; } set y(value) { this._y = value; if (this._shape) this._setShape(); } get radius() { return this._radius; } set radius(value) { if (value <= 0) throw "CircleCollider radius cannot be less than 0"; this._radius = value; if (this._shape) this._setShape(); } resetShape(re = true) { this._setShape(); } } Laya.ClassUtils.regClass("laya.physics.CircleCollider", CircleCollider); Laya.ClassUtils.regClass("Laya.CircleCollider", CircleCollider); class EdgeCollider extends ColliderBase { constructor() { super(...arguments); this._x = 0; this._y = 0; this._points = "0,0,100,0"; } getDef() { if (!this._shape) { this._shape = new window.box2d.b2EdgeShape(); this._setShape(false); } this.label = (this.label || "EdgeCollider"); return super.getDef(); } _setShape(re = true) { var arr = this._points.split(","); var len = arr.length; if (len % 2 == 1) throw "EdgeCollider points lenth must a multiplier of 2"; var ps = []; for (var i = 0, n = len; i < n; i += 2) { ps.push(new window.box2d.b2Vec2((this._x + parseInt(arr[i])) / Physics.PIXEL_RATIO, (this._y + parseInt(arr[i + 1])) / Physics.PIXEL_RATIO)); } this._shape.SetTwoSided(ps[0], ps[1]); if (re) this.refresh(); } get x() { return this._x; } set x(value) { this._x = value; if (this._shape) this._setShape(); } get y() { return this._y; } set y(value) { this._y = value; if (this._shape) this._setShape(); } get points() { return this._points; } set points(value) { if (!value) throw "EdgeCollider points cannot be empty"; this._points = value; if (this._shape) this._setShape(); } } Laya.ClassUtils.regClass("laya.physics.EdgeCollider", EdgeCollider); Laya.ClassUtils.regClass("Laya.EdgeCollider", EdgeCollider); class PhysicsDebugDraw extends Laya.Sprite { constructor() { super(); this.m_drawFlags = 99; if (!PhysicsDebugDraw._inited) { PhysicsDebugDraw._inited = true; PhysicsDebugDraw.init(); } this._camera = {}; this._camera.m_center = new PhysicsDebugDraw.box2d.b2Vec2(0, 0); this._camera.m_extent = 25; this._camera.m_zoom = 1; this._camera.m_width = 1280; this._camera.m_height = 800; this._mG = new Laya.Graphics(); this.graphics = this._mG; this._textSp = new Laya.Sprite(); this._textG = this._textSp.graphics; this.addChild(this._textSp); } static init() { PhysicsDebugDraw.box2d = Laya.Browser.window.box2d; PhysicsDebugDraw.DrawString_s_color = new PhysicsDebugDraw.box2d.b2Color(0.9, 0.6, 0.6); PhysicsDebugDraw.DrawStringWorld_s_p = new PhysicsDebugDraw.box2d.b2Vec2(); PhysicsDebugDraw.DrawStringWorld_s_cc = new PhysicsDebugDraw.box2d.b2Vec2(); PhysicsDebugDraw.DrawStringWorld_s_color = new PhysicsDebugDraw.box2d.b2Color(0.5, 0.9, 0.5); } render(ctx, x, y) { this._renderToGraphic(); super.render(ctx, x, y); } _renderToGraphic() { if (this.world) { this._textG.clear(); this._mG.clear(); this._mG.save(); this._mG.scale(Physics.PIXEL_RATIO, Physics.PIXEL_RATIO); this.lineWidth = 1 / Physics.PIXEL_RATIO; if (this.world.DebugDraw) this.world.DebugDraw(); else this.world.DrawDebugData(); this._mG.restore(); } } SetFlags(flags) { this.m_drawFlags = flags; } GetFlags() { return this.m_drawFlags; } AppendFlags(flags) { this.m_drawFlags |= flags; } ClearFlags(flags) { this.m_drawFlags &= ~flags; } PushTransform(xf) { this._mG.save(); this._mG.translate(xf.p.x, xf.p.y); this._mG.rotate(xf.q.GetAngle()); } PopTransform(xf) { this._mG.restore(); } DrawPolygon(vertices, vertexCount, color) { var i, len; len = vertices.length; var points; points = []; for (i = 0; i < vertexCount; i++) { points.push(vertices[i].x, vertices[i].y); } this._mG.drawPoly(0, 0, points, null, color.MakeStyleString(1), this.lineWidth); } DrawSolidPolygon(vertices, vertexCount, color) { var i, len; len = vertices.length; var points; points = []; for (i = 0; i < vertexCount; i++) { points.push(vertices[i].x, vertices[i].y); } this._mG.drawPoly(0, 0, points, color.MakeStyleString(0.5), color.MakeStyleString(1), this.lineWidth); } DrawCircle(center, radius, color) { this._mG.drawCircle(center.x, center.y, radius, null, color.MakeStyleString(1), this.lineWidth); } DrawSolidCircle(center, radius, axis, color) { var cx = center.x; var cy = center.y; this._mG.drawCircle(cx, cy, radius, color.MakeStyleString(0.5), color.MakeStyleString(1), this.lineWidth); this._mG.drawLine(cx, cy, (cx + axis.x * radius), (cy + axis.y * radius), color.MakeStyleString(1), this.lineWidth); } DrawParticles(centers, radius, colors, count) { if (colors !== null) { for (var i = 0; i < count; ++i) { var center = centers[i]; var color = colors[i]; this._mG.drawCircle(center.x, center.y, radius, color.MakeStyleString(), null, this.lineWidth); } } else { for (i = 0; i < count; ++i) { center = centers[i]; this._mG.drawCircle(center.x, center.y, radius, "#ffff00", null, this.lineWidth); } } } DrawSegment(p1, p2, color) { this._mG.drawLine(p1.x, p1.y, p2.x, p2.y, color.MakeStyleString(1), this.lineWidth); } DrawTransform(xf) { this.PushTransform(xf); this._mG.drawLine(0, 0, 1, 0, PhysicsDebugDraw.box2d.b2Color.RED.MakeStyleString(1), this.lineWidth); this._mG.drawLine(0, 0, 0, 1, PhysicsDebugDraw.box2d.b2Color.GREEN.MakeStyleString(1), this.lineWidth); this.PopTransform(xf); } DrawPoint(p, size, color) { size *= this._camera.m_zoom; size /= this._camera.m_extent; var hsize = size / 2; this._mG.drawRect(p.x - hsize, p.y - hsize, size, size, color.MakeStyleString(), null); } DrawString(x, y, message) { this._textG.fillText(message, x, y, "15px DroidSans", PhysicsDebugDraw.DrawString_s_color.MakeStyleString(), "left"); } DrawStringWorld(x, y, message) { this.DrawString(x, y, message); } DrawAABB(aabb, color) { var x = aabb.lowerBound.x; var y = aabb.lowerBound.y; var w = aabb.upperBound.x - aabb.lowerBound.x; var h = aabb.upperBound.y - aabb.lowerBound.y; this._mG.drawRect(x, y, w, h, null, color.MakeStyleString(), this.lineWidth); } static enable(flags = 99) { if (!PhysicsDebugDraw.I) { var debug = new PhysicsDebugDraw(); debug.world = Physics.I.world; debug.world.SetDebugDraw(debug); debug.zOrder = 1000; debug.m_drawFlags = flags; Laya.Laya.stage.addChild(debug); PhysicsDebugDraw.I = debug; } return debug; } } PhysicsDebugDraw._inited = false; Laya.ClassUtils.regClass("laya.physics.PhysicsDebugDraw", PhysicsDebugDraw); Laya.ClassUtils.regClass("Laya.PhysicsDebugDraw", PhysicsDebugDraw); class PolygonCollider extends ColliderBase { constructor() { super(...arguments); this._x = 0; this._y = 0; this._points = "50,0,100,100,0,100"; } getDef() { if (!this._shape) { this._shape = new window.box2d.b2PolygonShape(); this._setShape(false); } this.label = (this.label || "PolygonCollider"); return super.getDef(); } _setShape(re = true) { var arr = this._points.split(","); var len = arr.length; if (len < 6) throw "PolygonCollider points must be greater than 3"; if (len % 2 == 1) throw "PolygonCollider points lenth must a multiplier of 2"; var ps = []; for (var i = 0, n = len; i < n; i += 2) { ps.push(new window.box2d.b2Vec2((this._x + parseInt(arr[i])) / Physics.PIXEL_RATIO, (this._y + parseInt(arr[i + 1])) / Physics.PIXEL_RATIO)); } this._shape.Set(ps, len / 2); if (re) this.refresh(); } get x() { return this._x; } set x(value) { this._x = value; if (this._shape) this._setShape(); } get y() { return this._y; } set y(value) { this._y = value; if (this._shape) this._setShape(); } get points() { return this._points; } set points(value) { if (!value) throw "PolygonCollider points cannot be empty"; this._points = value; if (this._shape) this._setShape(); } } Laya.ClassUtils.regClass("laya.physics.PolygonCollider", PolygonCollider); Laya.ClassUtils.regClass("Laya.PolygonCollider", PolygonCollider); class JointBase extends Laya.Component { get joint() { if (!this._joint) this._createJoint(); return this._joint; } _onEnable() { this._createJoint(); } _onAwake() { this._createJoint(); } _createJoint() { } _onDisable() { if (this._joint && this._joint.m_userData && !this._joint.m_userData.isDestroy) { Physics.I._removeJoint(this._joint); } this._joint = null; } get isSingleton() { return false; } } Laya.ClassUtils.regClass("laya.physics.joint.JointBase", JointBase); Laya.ClassUtils.regClass("Laya.JointBase", JointBase); class DistanceJoint extends JointBase { constructor() { super(...arguments); this.selfAnchor = [0, 0]; this.otherAnchor = [0, 0]; this.collideConnected = false; this._length = 0; this._maxLength = -1; this._minLength = -1; this._frequency = 1; this._dampingRatio = 0; } _createJoint() { if (!this._joint) { this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = DistanceJoint._temp || (DistanceJoint._temp = new box2d.b2DistanceJointDef()); def.bodyA = this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody; def.bodyB = this.selfBody.getBody(); def.localAnchorA.Set(this.otherAnchor[0] / Physics.PIXEL_RATIO, this.otherAnchor[1] / Physics.PIXEL_RATIO); def.localAnchorB.Set(this.selfAnchor[0] / Physics.PIXEL_RATIO, this.selfAnchor[1] / Physics.PIXEL_RATIO); box2d.b2LinearStiffness(def, this._frequency, this._dampingRatio, def.bodyA, def.bodyB); def.collideConnected = this.collideConnected; var p1 = def.bodyA.GetWorldPoint(def.localAnchorA, new box2d.b2Vec2()); var p2 = def.bodyB.GetWorldPoint(def.localAnchorB, new box2d.b2Vec2()); def.length = this._length / Physics.PIXEL_RATIO || box2d.b2Vec2.SubVV(p2, p1, new box2d.b2Vec2()).Length(); def.maxLength = box2d.b2_maxFloat; def.minLength = 0; if (this._maxLength >= 0) def.maxLength = this._maxLength / Physics.PIXEL_RATIO; if (this._minLength >= 0) def.minLength = this._minLength / Physics.PIXEL_RATIO; this._joint = Physics.I._createJoint(def); } } get length() { return this._length; } set length(value) { this._length = value; if (this._joint) this._joint.SetLength(value / Physics.PIXEL_RATIO); } get minLength() { return this._minLength; } set minLength(value) { this._minLength = value; if (this._joint) this._joint.SetMinLength(value / Physics.PIXEL_RATIO); } get maxLength() { return this._maxLength; } set maxLength(value) { this._maxLength = value; if (this._joint) this._joint.SetMaxLength(value / Physics.PIXEL_RATIO); } get frequency() { return this._frequency; } set frequency(value) { this._frequency = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2LinearStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetStiffness(out.stiffness); this._joint.SetDamping(out.damping); } } get damping() { return this._dampingRatio; } set damping(value) { this._dampingRatio = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2LinearStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetDamping(out.damping); } } } Laya.ClassUtils.regClass("laya.physics.joint.DistanceJoint", DistanceJoint); Laya.ClassUtils.regClass("Laya.DistanceJoint", DistanceJoint); class GearJoint extends JointBase { constructor() { super(...arguments); this.collideConnected = false; this._ratio = 1; } _createJoint() { if (!this._joint) { if (!this.joint1) throw "Joint1 can not be empty"; if (!this.joint2) throw "Joint2 can not be empty"; var box2d = window.box2d; var def = GearJoint._temp || (GearJoint._temp = new box2d.b2GearJointDef()); def.bodyA = this.joint1.owner.getComponent(RigidBody).getBody(); def.bodyB = this.joint2.owner.getComponent(RigidBody).getBody(); def.joint1 = this.joint1.joint; def.joint2 = this.joint2.joint; def.ratio = this._ratio; def.collideConnected = this.collideConnected; this._joint = Physics.I._createJoint(def); } } get ratio() { return this._ratio; } set ratio(value) { this._ratio = value; if (this._joint) this._joint.SetRatio(value); } } Laya.ClassUtils.regClass("laya.physics.joint.GearJoint", GearJoint); Laya.ClassUtils.regClass("Laya.GearJoint", GearJoint); class MotorJoint extends JointBase { constructor() { super(...arguments); this.collideConnected = false; this._linearOffset = [0, 0]; this._angularOffset = 0; this._maxForce = 1000; this._maxTorque = 1000; this._correctionFactor = 0.3; } _createJoint() { if (!this._joint) { if (!this.otherBody) throw "otherBody can not be empty"; this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = MotorJoint._temp || (MotorJoint._temp = new box2d.b2MotorJointDef()); def.Initialize(this.otherBody.getBody(), this.selfBody.getBody()); def.linearOffset = new box2d.b2Vec2(this._linearOffset[0] / Physics.PIXEL_RATIO, this._linearOffset[1] / Physics.PIXEL_RATIO); def.angularOffset = this._angularOffset; def.maxForce = this._maxForce; def.maxTorque = this._maxTorque; def.correctionFactor = this._correctionFactor; def.collideConnected = this.collideConnected; this._joint = Physics.I._createJoint(def); } } get linearOffset() { return this._linearOffset; } set linearOffset(value) { this._linearOffset = value; if (this._joint) this._joint.SetLinearOffset(new window.box2d.b2Vec2(value[0] / Physics.PIXEL_RATIO, value[1] / Physics.PIXEL_RATIO)); } get angularOffset() { return this._angularOffset; } set angularOffset(value) { this._angularOffset = value; if (this._joint) this._joint.SetAngularOffset(value); } get maxForce() { return this._maxForce; } set maxForce(value) { this._maxForce = value; if (this._joint) this._joint.SetMaxForce(value); } get maxTorque() { return this._maxTorque; } set maxTorque(value) { this._maxTorque = value; if (this._joint) this._joint.SetMaxTorque(value); } get correctionFactor() { return this._correctionFactor; } set correctionFactor(value) { this._correctionFactor = value; if (this._joint) this._joint.SetCorrectionFactor(value); } } Laya.ClassUtils.regClass("laya.physics.joint.MotorJoint", MotorJoint); Laya.ClassUtils.regClass("Laya.MotorJoint", MotorJoint); class MouseJoint extends JointBase { constructor() { super(...arguments); this._maxForce = 10000; this._frequency = 5; this._dampingRatio = 0.7; } _onEnable() { this.owner.on(Laya.Event.MOUSE_DOWN, this, this.onMouseDown); } _onAwake() { } onMouseDown() { this._createJoint(); Laya.Laya.stage.on(Laya.Event.MOUSE_MOVE, this, this.onMouseMove); Laya.Laya.stage.once(Laya.Event.MOUSE_UP, this, this.onStageMouseUp); Laya.Laya.stage.once(Laya.Event.MOUSE_OUT, this, this.onStageMouseUp); } _createJoint() { if (!this._joint) { this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = MouseJoint._temp || (MouseJoint._temp = new box2d.b2MouseJointDef()); if (this.anchor) { var anchorPos = this.selfBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.anchor[0], this.anchor[1]), false, Physics.I.worldRoot); } else { anchorPos = Physics.I.worldRoot.globalToLocal(Laya.Point.TEMP.setTo(Laya.Laya.stage.mouseX, Laya.Laya.stage.mouseY)); } var anchorVec = new box2d.b2Vec2(anchorPos.x / Physics.PIXEL_RATIO, anchorPos.y / Physics.PIXEL_RATIO); def.bodyA = Physics.I._emptyBody; def.bodyB = this.selfBody.getBody(); def.target = anchorVec; box2d.b2LinearStiffness(def, this._frequency, this._dampingRatio, def.bodyA, def.bodyB); def.maxForce = this._maxForce; this._joint = Physics.I._createJoint(def); } } onStageMouseUp() { Laya.Laya.stage.off(Laya.Event.MOUSE_MOVE, this, this.onMouseMove); Laya.Laya.stage.off(Laya.Event.MOUSE_UP, this, this.onStageMouseUp); Laya.Laya.stage.off(Laya.Event.MOUSE_OUT, this, this.onStageMouseUp); super._onDisable(); } onMouseMove() { this._joint.SetTarget(new window.box2d.b2Vec2(Physics.I.worldRoot.mouseX / Physics.PIXEL_RATIO, Physics.I.worldRoot.mouseY / Physics.PIXEL_RATIO)); } _onDisable() { this.owner.off(Laya.Event.MOUSE_DOWN, this, this.onMouseDown); super._onDisable(); } get maxForce() { return this._maxForce; } set maxForce(value) { this._maxForce = value; if (this._joint) this._joint.SetMaxForce(value); } get frequency() { return this._frequency; } set frequency(value) { this._frequency = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2LinearStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetStiffness(out.stiffness); this._joint.SetDamping(out.damping); } } get damping() { return this._dampingRatio; } set damping(value) { this._dampingRatio = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2LinearStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetDamping(out.damping); } } } Laya.ClassUtils.regClass("laya.physics.joint.MouseJoint", MouseJoint); Laya.ClassUtils.regClass("Laya.MouseJoint", MouseJoint); class PrismaticJoint extends JointBase { constructor() { super(...arguments); this.anchor = [0, 0]; this.axis = [1, 0]; this.collideConnected = false; this._enableMotor = false; this._motorSpeed = 0; this._maxMotorForce = 10000; this._enableLimit = false; this._lowerTranslation = 0; this._upperTranslation = 0; } _createJoint() { if (!this._joint) { this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = PrismaticJoint._temp || (PrismaticJoint._temp = new box2d.b2PrismaticJointDef()); var anchorPos = this.selfBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.anchor[0], this.anchor[1]), false, Physics.I.worldRoot); var anchorVec = new box2d.b2Vec2(anchorPos.x / Physics.PIXEL_RATIO, anchorPos.y / Physics.PIXEL_RATIO); def.Initialize(this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody, this.selfBody.getBody(), anchorVec, new box2d.b2Vec2(this.axis[0], this.axis[1])); def.enableMotor = this._enableMotor; def.motorSpeed = this._motorSpeed; def.maxMotorForce = this._maxMotorForce; def.enableLimit = this._enableLimit; def.lowerTranslation = this._lowerTranslation / Physics.PIXEL_RATIO; def.upperTranslation = this._upperTranslation / Physics.PIXEL_RATIO; def.collideConnected = this.collideConnected; this._joint = Physics.I._createJoint(def); } } get enableMotor() { return this._enableMotor; } set enableMotor(value) { this._enableMotor = value; if (this._joint) this._joint.EnableMotor(value); } get motorSpeed() { return this._motorSpeed; } set motorSpeed(value) { this._motorSpeed = value; if (this._joint) this._joint.SetMotorSpeed(value); } get maxMotorForce() { return this._maxMotorForce; } set maxMotorForce(value) { this._maxMotorForce = value; if (this._joint) this._joint.SetMaxMotorForce(value); } get enableLimit() { return this._enableLimit; } set enableLimit(value) { this._enableLimit = value; if (this._joint) this._joint.EnableLimit(value); } get lowerTranslation() { return this._lowerTranslation; } set lowerTranslation(value) { this._lowerTranslation = value; if (this._joint) this._joint.SetLimits(value, this._upperTranslation); } get upperTranslation() { return this._upperTranslation; } set upperTranslation(value) { this._upperTranslation = value; if (this._joint) this._joint.SetLimits(this._lowerTranslation, value); } } Laya.ClassUtils.regClass("laya.physics.joint.PrismaticJoint", PrismaticJoint); Laya.ClassUtils.regClass("Laya.PrismaticJoint", PrismaticJoint); class PulleyJoint extends JointBase { constructor() { super(...arguments); this.selfAnchor = [0, 0]; this.otherAnchor = [0, 0]; this.selfGroundPoint = [0, 0]; this.otherGroundPoint = [0, 0]; this.ratio = 1.5; this.collideConnected = false; } _createJoint() { if (!this._joint) { if (!this.otherBody) throw "otherBody can not be empty"; this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = PulleyJoint._temp || (PulleyJoint._temp = new box2d.b2PulleyJointDef()); var posA = this.otherBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.otherAnchor[0], this.otherAnchor[1]), false, Physics.I.worldRoot); var anchorVecA = new box2d.b2Vec2(posA.x / Physics.PIXEL_RATIO, posA.y / Physics.PIXEL_RATIO); var posB = this.selfBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.selfAnchor[0], this.selfAnchor[1]), false, Physics.I.worldRoot); var anchorVecB = new box2d.b2Vec2(posB.x / Physics.PIXEL_RATIO, posB.y / Physics.PIXEL_RATIO); var groundA = this.otherBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.otherGroundPoint[0], this.otherGroundPoint[1]), false, Physics.I.worldRoot); var groundVecA = new box2d.b2Vec2(groundA.x / Physics.PIXEL_RATIO, groundA.y / Physics.PIXEL_RATIO); var groundB = this.selfBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.selfGroundPoint[0], this.selfGroundPoint[1]), false, Physics.I.worldRoot); var groundVecB = new box2d.b2Vec2(groundB.x / Physics.PIXEL_RATIO, groundB.y / Physics.PIXEL_RATIO); def.Initialize(this.otherBody.getBody(), this.selfBody.getBody(), groundVecA, groundVecB, anchorVecA, anchorVecB, this.ratio); def.collideConnected = this.collideConnected; this._joint = Physics.I._createJoint(def); } } } Laya.ClassUtils.regClass("laya.physics.joint.PulleyJoint", PulleyJoint); Laya.ClassUtils.regClass("Laya.PulleyJoint", PulleyJoint); class RevoluteJoint extends JointBase { constructor() { super(...arguments); this.anchor = [0, 0]; this.collideConnected = false; this._enableMotor = false; this._motorSpeed = 0; this._maxMotorTorque = 10000; this._enableLimit = false; this._lowerAngle = 0; this._upperAngle = 0; } _createJoint() { if (!this._joint) { this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = RevoluteJoint._temp || (RevoluteJoint._temp = new box2d.b2RevoluteJointDef()); var anchorPos = this.selfBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.anchor[0], this.anchor[1]), false, Physics.I.worldRoot); var anchorVec = new box2d.b2Vec2(anchorPos.x / Physics.PIXEL_RATIO, anchorPos.y / Physics.PIXEL_RATIO); def.Initialize(this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody, this.selfBody.getBody(), anchorVec); def.enableMotor = this._enableMotor; def.motorSpeed = this._motorSpeed; def.maxMotorTorque = this._maxMotorTorque; def.enableLimit = this._enableLimit; def.lowerAngle = this._lowerAngle; def.upperAngle = this._upperAngle; def.collideConnected = this.collideConnected; this._joint = Physics.I._createJoint(def); } } get enableMotor() { return this._enableMotor; } set enableMotor(value) { this._enableMotor = value; if (this._joint) this._joint.EnableMotor(value); } get motorSpeed() { return this._motorSpeed; } set motorSpeed(value) { this._motorSpeed = value; if (this._joint) this._joint.SetMotorSpeed(value); } get maxMotorTorque() { return this._maxMotorTorque; } set maxMotorTorque(value) { this._maxMotorTorque = value; if (this._joint) this._joint.SetMaxMotorTorque(value); } get enableLimit() { return this._enableLimit; } set enableLimit(value) { this._enableLimit = value; if (this._joint) this._joint.EnableLimit(value); } get lowerAngle() { return this._lowerAngle; } set lowerAngle(value) { this._lowerAngle = value; if (this._joint) this._joint.SetLimits(value, this._upperAngle); } get upperAngle() { return this._upperAngle; } set upperAngle(value) { this._upperAngle = value; if (this._joint) this._joint.SetLimits(this._lowerAngle, value); } } Laya.ClassUtils.regClass("laya.physics.joint.RevoluteJoint", RevoluteJoint); Laya.ClassUtils.regClass("Laya.RevoluteJoint", RevoluteJoint); class WeldJoint extends JointBase { constructor() { super(...arguments); this.anchor = [0, 0]; this.collideConnected = false; this._frequency = 5; this._dampingRatio = 0.7; } _createJoint() { if (!this._joint) { if (!this.otherBody) throw "otherBody can not be empty"; this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = WeldJoint._temp || (WeldJoint._temp = new box2d.b2WeldJointDef()); var anchorPos = this.selfBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.anchor[0], this.anchor[1]), false, Physics.I.worldRoot); var anchorVec = new box2d.b2Vec2(anchorPos.x / Physics.PIXEL_RATIO, anchorPos.y / Physics.PIXEL_RATIO); def.Initialize(this.otherBody.getBody(), this.selfBody.getBody(), anchorVec); box2d.b2AngularStiffness(def, this._frequency, this._dampingRatio, def.bodyA, def.bodyB); def.collideConnected = this.collideConnected; this._joint = Physics.I._createJoint(def); } } get frequency() { return this._frequency; } set frequency(value) { this._frequency = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2AngularStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetStiffness(out.stiffness); this._joint.SetDamping(out.damping); } } get damping() { return this._dampingRatio; } set damping(value) { this._dampingRatio = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2AngularStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetDamping(out.damping); } } } Laya.ClassUtils.regClass("laya.physics.joint.WeldJoint", WeldJoint); Laya.ClassUtils.regClass("Laya.WeldJoint", WeldJoint); class WheelJoint extends JointBase { constructor() { super(...arguments); this.anchor = [0, 0]; this.collideConnected = false; this.axis = [1, 0]; this._frequency = 5; this._dampingRatio = 0.7; this._enableMotor = false; this._motorSpeed = 0; this._maxMotorTorque = 10000; this._enableLimit = true; this._lowerTranslation = 0; this._upperTranslation = 0; } _createJoint() { if (!this._joint) { if (!this.otherBody) throw "otherBody can not be empty"; this.selfBody = this.selfBody || this.owner.getComponent(RigidBody); if (!this.selfBody) throw "selfBody can not be empty"; var box2d = window.box2d; var def = WheelJoint._temp || (WheelJoint._temp = new box2d.b2WheelJointDef()); var anchorPos = this.selfBody.owner.localToGlobal(Laya.Point.TEMP.setTo(this.anchor[0], this.anchor[1]), false, Physics.I.worldRoot); var anchorVec = new box2d.b2Vec2(anchorPos.x / Physics.PIXEL_RATIO, anchorPos.y / Physics.PIXEL_RATIO); def.Initialize(this.otherBody.getBody(), this.selfBody.getBody(), anchorVec, new box2d.b2Vec2(this.axis[0], this.axis[1])); def.enableMotor = this._enableMotor; def.motorSpeed = this._motorSpeed; def.maxMotorTorque = this._maxMotorTorque; box2d.b2LinearStiffness(def, this._frequency, this._dampingRatio, def.bodyA, def.bodyB); def.collideConnected = this.collideConnected; def.enableLimit = this._enableLimit; def.lowerTranslation = this._lowerTranslation / Physics.PIXEL_RATIO; def.upperTranslation = this._upperTranslation / Physics.PIXEL_RATIO; this._joint = Physics.I._createJoint(def); } } get frequency() { return this._frequency; } set frequency(value) { this._frequency = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2LinearStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetStiffness(out.stiffness); this._joint.SetDamping(out.damping); } } get damping() { return this._dampingRatio; } set damping(value) { this._dampingRatio = value; if (this._joint) { let out = {}; let box2d = window.box2d; let bodyA = this.otherBody ? this.otherBody.getBody() : Physics.I._emptyBody; let bodyB = this.selfBody.getBody(); box2d.b2LinearStiffness(out, this._frequency, this._dampingRatio, bodyA, bodyB); this._joint.SetDamping(out.damping); } } get enableMotor() { return this._enableMotor; } set enableMotor(value) { this._enableMotor = value; if (this._joint) this._joint.EnableMotor(value); } get motorSpeed() { return this._motorSpeed; } set motorSpeed(value) { this._motorSpeed = value; if (this._joint) this._joint.SetMotorSpeed(value); } get maxMotorTorque() { return this._maxMotorTorque; } set maxMotorTorque(value) { this._maxMotorTorque = value; if (this._joint) this._joint.SetMaxMotorTorque(value); } get enableLimit() { return this._enableLimit; } set enableLimit(value) { this._enableLimit = value; if (this._joint) this._joint.EnableLimit(value); } get lowerTranslation() { return this._lowerTranslation; } set lowerTranslation(value) { this._lowerTranslation = value; if (this._joint) this._joint.SetLimits(value, this._upperTranslation); } get upperTranslation() { return this._upperTranslation; } set upperTranslation(value) { this._upperTranslation = value; if (this._joint) this._joint.SetLimits(this._lowerTranslation, value); } } Laya.ClassUtils.regClass("laya.physics.joint.WheelJoint", WheelJoint); Laya.ClassUtils.regClass("Laya.WheelJoint", WheelJoint); exports.BoxCollider = BoxCollider; exports.ChainCollider = ChainCollider; exports.CircleCollider = CircleCollider; exports.ColliderBase = ColliderBase; exports.DestructionListener = DestructionListener; exports.DistanceJoint = DistanceJoint; exports.EdgeCollider = EdgeCollider; exports.GearJoint = GearJoint; exports.IPhysics = IPhysics; exports.JointBase = JointBase; exports.MotorJoint = MotorJoint; exports.MouseJoint = MouseJoint; exports.Physics = Physics; exports.PhysicsDebugDraw = PhysicsDebugDraw; exports.PolygonCollider = PolygonCollider; exports.PrismaticJoint = PrismaticJoint; exports.PulleyJoint = PulleyJoint; exports.RevoluteJoint = RevoluteJoint; exports.RigidBody = RigidBody; exports.WeldJoint = WeldJoint; exports.WheelJoint = WheelJoint; }(window.Laya = window.Laya || {}, Laya));