UNPKG

pixelscanjs

Version:

Simple pixel based physics engine only supporting static environments and ropes!

2,174 lines (1,860 loc) 57.8 kB
const box2d = { b2_aabbExtension: 0.1, }; box2d.DEBUG = false; box2d.ENABLE_ASSERTS = box2d.DEBUG; /** * @export * @const * @type {number} */ box2d.b2_maxFloat = 1E+37; // FLT_MAX instead of Number.MAX_VALUE; /** * @export * @const * @type {number} */ box2d.b2_epsilon = 1E-5; // FLT_EPSILON instead of Number.MIN_VALUE; /** * 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. * @export * @const * @type {number} */ box2d.b2_aabbMultiplier = 2; box2d.b2Assert = function(condition, opt_message, var_args) { if (box2d.DEBUG) { if (!condition) { throw new Error(); } //goog.asserts.assert(condition, opt_message, var_args); } } /** * @export * @return {number} * @param {number} n */ box2d.b2Abs = Math.abs; /** * @export * @return {number} * @param {number} a * @param {number} b */ box2d.b2Min = Math.min; /** * @export * @return {number} * @param {number} a * @param {number} b */ box2d.b2Max = Math.max; /** * @export * @return {number} * @param {number} a * @param {number} lo * @param {number} hi */ box2d.b2Clamp = function(a, lo, hi) { return Math.min(Math.max(a, lo), hi); } /** * @export * @return {Array.<*>} * @param {number=} length * @param {function(number): *=} init */ box2d.b2MakeArray = function(length, init) { length = (typeof(length) === 'number') ? (length) : (0); var a = []; if (typeof(init) === 'function') { for (var i = 0; i < length; ++i) { a.push(init(i)); } } else { for (var i = 0; i < length; ++i) { a.push(null); } } return a; } /** * @export * @return {Array.<number>} * @param {number=} length */ box2d.b2MakeNumberArray = function(length) { return box2d.b2MakeArray(length, function(i) { return 0; }); } /** * 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. * @export * @constructor * @param {number} N */ box2d.b2GrowableStack = function(N) { this.m_stack = new Array(N); } /** * @export * @type {Array.<*>} */ box2d.b2GrowableStack.prototype.m_stack = null; /** * @export * @type {number} */ box2d.b2GrowableStack.prototype.m_count = 0; /** * @export * @return {box2d.b2GrowableStack} */ box2d.b2GrowableStack.prototype.Reset = function() { this.m_count = 0; return this; } /** * @export * @return {void} * @param {*} element */ box2d.b2GrowableStack.prototype.Push = function(element) { this.m_stack[this.m_count] = element; ++this.m_count; } /** * @export * @return {*} */ box2d.b2GrowableStack.prototype.Pop = function() { if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(this.m_count > 0); } --this.m_count; var element = this.m_stack[this.m_count]; this.m_stack[this.m_count] = null; return element; } /** * @export * @return {number} */ box2d.b2GrowableStack.prototype.GetCount = function() { return this.m_count; } /** * A 2D column vector. * @export * @constructor * @param {number=} x * @param {number=} y */ box2d.b2Vec2 = function(x, y) { this.x = x || 0.0; this.y = y || 0.0; //this.a = new Float32Array(2); //this.a[0] = x || 0; //this.a[1] = y || 0; } /** * @export * @type {number} */ box2d.b2Vec2.prototype.x = 0.0; /** * @export * @type {number} */ box2d.b2Vec2.prototype.y = 0.0; // /** // * @type {Float32Array} // */ // box2d.b2Vec2.prototype.a; // // box2d.b2Vec2.prototype.__defineGetter__('x', function () { return this.a[0]; }); // box2d.b2Vec2.prototype.__defineGetter__('y', function () { return this.a[1]; }); // box2d.b2Vec2.prototype.__defineSetter__('x', function (n) { this.a[0] = n; }); // box2d.b2Vec2.prototype.__defineSetter__('y', function (n) { this.a[1] = n; }); /** * @export * @const * @type {box2d.b2Vec2} */ box2d.b2Vec2_zero = new box2d.b2Vec2(); /** * @export * @const * @type {box2d.b2Vec2} */ box2d.b2Vec2.ZERO = new box2d.b2Vec2(); /** * @export * @const * @type {box2d.b2Vec2} */ box2d.b2Vec2.UNITX = new box2d.b2Vec2(1.0, 0.0); /** * @export * @const * @type {box2d.b2Vec2} */ box2d.b2Vec2.UNITY = new box2d.b2Vec2(0.0, 1.0); /** * @export * @type {box2d.b2Vec2} */ box2d.b2Vec2.s_t0 = new box2d.b2Vec2(); /** * @export * @type {box2d.b2Vec2} */ box2d.b2Vec2.s_t1 = new box2d.b2Vec2(); /** * @export * @type {box2d.b2Vec2} */ box2d.b2Vec2.s_t2 = new box2d.b2Vec2(); /** * @export * @type {box2d.b2Vec2} */ box2d.b2Vec2.s_t3 = new box2d.b2Vec2(); /** * @export * @return {Array.<box2d.b2Vec2>} * @param {number=} length */ box2d.b2Vec2.MakeArray = function(length) { return box2d.b2MakeArray(length, function(i) { return new box2d.b2Vec2(); }); } /** * @export * @return {box2d.b2Vec2} */ box2d.b2Vec2.prototype.Clone = function() { return new box2d.b2Vec2(this.x, this.y); } /** * Set this vector to all zeros. * @export * @return {box2d.b2Vec2} */ box2d.b2Vec2.prototype.SetZero = function() { this.x = 0.0; this.y = 0.0; return this; } /** * Set this vector to some specified coordinates. * @export * @return {box2d.b2Vec2} * @param {number} x * @param {number} y */ box2d.b2Vec2.prototype.Set = function(x, y) { this.x = x; this.y = y; return this; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} other */ box2d.b2Vec2.prototype.Copy = function(other) { //if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(this !== other); } this.x = other.x; this.y = other.y; return this; } /** * Add a vector to this vector. * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.SelfAdd = function(v) { this.x += v.x; this.y += v.y; return this; } /** * @export * @return {box2d.b2Vec2} * @param {number} x * @param {number} y */ box2d.b2Vec2.prototype.SelfAddXY = function(x, y) { this.x += x; this.y += y; return this; } /** * Subtract a vector from this vector. * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.SelfSub = function(v) { this.x -= v.x; this.y -= v.y; return this; } /** * @export * @return {box2d.b2Vec2} * @param {number} x * @param {number} y */ box2d.b2Vec2.prototype.SelfSubXY = function(x, y) { this.x -= x; this.y -= y; return this; } /** * Multiply this vector by a scalar. * @export * @return {box2d.b2Vec2} * @param {number} s */ box2d.b2Vec2.prototype.SelfMul = function(s) { this.x *= s; this.y *= s; return this; } /** * this += s * v * @export * @return {box2d.b2Vec2} * @param {number} s * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.SelfMulAdd = function(s, v) { this.x += s * v.x; this.y += s * v.y; return this; } /** * this -= s * v * @export * @return {box2d.b2Vec2} * @param {number} s * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.SelfMulSub = function(s, v) { this.x -= s * v.x; this.y -= s * v.y; return this; } /** * @export * @return {number} * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.Dot = function(v) { return this.x * v.x + this.y * v.y; } /** * @export * @return {number} * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.Cross = function(v) { return this.x * v.y - this.y * v.x; } /** * Get the length of this vector (the norm). * @export * @return {number} */ box2d.b2Vec2.prototype.Length = function() { var x = this.x, y = this.y; return Math.sqrt(x * x + y * y); } /** * Get the length squared. For performance, use this instead of * b2Vec2::Length (if possible). * @export * @return {number} */ box2d.b2Vec2.prototype.LengthSquared = function() { var x = this.x, y = this.y; return (x * x + y * y); } /** * Convert this vector into a unit vector. Returns the length. * @export * @return {number} */ box2d.b2Vec2.prototype.Normalize = function() { var length = this.Length(); if (length >= box2d.b2_epsilon) { var inv_length = 1.0 / length; this.x *= inv_length; this.y *= inv_length; } return length; } /** * @export * @return {box2d.b2Vec2} */ box2d.b2Vec2.prototype.SelfNormalize = function() { this.Normalize(); return this; } /** * @export * @return {box2d.b2Vec2} * @param {number} c * @param {number} s */ box2d.b2Vec2.prototype.SelfRotate = function(c, s) { var x = this.x, y = this.y; this.x = c * x - s * y; this.y = s * x + c * y; return this; } /** * @export * @return {box2d.b2Vec2} * @param {number} radians */ box2d.b2Vec2.prototype.SelfRotateAngle = function(radians) { return this.SelfRotate(Math.cos(radians), Math.sin(radians)); } /** * Does this vector contain finite coordinates? * @export * @return {boolean} */ box2d.b2Vec2.prototype.IsValid = function() { return isFinite(this.x) && isFinite(this.y); } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.SelfMin = function(v) { this.x = Math.min(this.x, v.x); this.y = Math.min(this.y, v.y); return this; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v */ box2d.b2Vec2.prototype.SelfMax = function(v) { this.x = Math.max(this.x, v.x); this.y = Math.max(this.y, v.y); return this; } /** * @export * @return {box2d.b2Vec2} */ box2d.b2Vec2.prototype.SelfAbs = function() { this.x = Math.abs(this.x); this.y = Math.abs(this.y); return this; } /** * @export * @return {box2d.b2Vec2} */ box2d.b2Vec2.prototype.SelfNeg = function() { this.x = (-this.x); this.y = (-this.y); return this; } /** * Get the skew vector such that dot(skew_vec, other) === * cross(vec, other) * @export * @return {box2d.b2Vec2} */ box2d.b2Vec2.prototype.SelfSkew = function() { var x = this.x; this.x = -this.y; this.y = x; return this; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v * @param {box2d.b2Vec2} out */ box2d.b2Abs_V2 = function(v, out) { out.x = Math.abs(v.x); out.y = Math.abs(v.y); return out; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2Min_V2_V2 = function(a, b, out) { out.x = Math.min(a.x, b.x); out.y = Math.min(a.y, b.y); return out; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2Max_V2_V2 = function(a, b, out) { out.x = Math.max(a.x, b.x); out.y = Math.max(a.y, b.y); return out; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v * @param {box2d.b2Vec2} lo * @param {box2d.b2Vec2} hi * @param {box2d.b2Vec2} out */ box2d.b2Clamp_V2_V2_V2 = function(v, lo, hi, out) { out.x = Math.min(Math.max(v.x, lo.x), hi.x); out.y = Math.min(Math.max(v.y, lo.y), hi.y); return out; } /** * Perform the dot product on two vectors. * a.x * b.x + a.y * b.y * @export * @return {number} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b */ box2d.b2Dot_V2_V2 = function(a, b) { return a.x * b.x + a.y * b.y; } /** * Perform the cross product on two vectors. In 2D this produces a scalar. * a.x * b.y - a.y * b.x * @export * @return {number} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b */ box2d.b2Cross_V2_V2 = function(a, b) { return a.x * b.y - a.y * b.x; } /** * Perform the cross product on a vector and a scalar. In 2D * this produces a vector. * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v * @param {number} s * @param {box2d.b2Vec2} out */ box2d.b2Cross_V2_S = function(v, s, out) { var v_x = v.x; out.x = s * v.y; out.y = -s * v_x; return out; } /** * Perform the cross product on a scalar and a vector. In 2D * this produces a vector. * @export * @return {box2d.b2Vec2} * @param {number} s * @param {box2d.b2Vec2} v * @param {box2d.b2Vec2} out */ box2d.b2Cross_S_V2 = function(s, v, out) { var v_x = v.x; out.x = -s * v.y; out.y = s * v_x; return out; } /** * Add two vectors component-wise. * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2Add_V2_V2 = function(a, b, out) { out.x = a.x + b.x; out.y = a.y + b.y; return out; } /** * Subtract two vectors component-wise. * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2Sub_V2_V2 = function(a, b, out) { out.x = a.x - b.x; out.y = a.y - b.y; return out; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v * @param {number} s * @param {box2d.b2Vec2} out */ box2d.b2Add_V2_S = function(v, s, out) { out.x = v.x + s; out.y = v.y + s; return out; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v * @param {number} s * @param {box2d.b2Vec2} out */ box2d.b2Sub_V2_S = function(v, s, out) { out.x = v.x - s; out.y = v.y - s; return out; } /** * @export * @return {box2d.b2Vec2} * @param {number} s * @param {box2d.b2Vec2} v * @param {box2d.b2Vec2} out */ box2d.b2Mul_S_V2 = function(s, v, out) { out.x = v.x * s; out.y = v.y * s; return out; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v * @param {number} s * @param {box2d.b2Vec2} out */ box2d.b2Mul_V2_S = function(v, s, out) { out.x = v.x * s; out.y = v.y * s; return out; } /** * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} v * @param {number} s * @param {box2d.b2Vec2} out */ box2d.b2Div_V2_S = function(v, s, out) { out.x = v.x / s; out.y = v.y / s; return out; } /** * out = a + (s * b) * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {number} s * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2AddMul_V2_S_V2 = function(a, s, b, out) { out.x = a.x + (s * b.x); out.y = a.y + (s * b.y); return out; } /** * out = a - (s * b) * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {number} s * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2SubMul_V2_S_V2 = function(a, s, b, out) { out.x = a.x - (s * b.x); out.y = a.y - (s * b.y); return out; } /** * out = a + b2Cross(s, v) * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {number} s * @param {box2d.b2Vec2} v * @param {box2d.b2Vec2} out */ box2d.b2AddCross_V2_S_V2 = function(a, s, v, out) { var v_x = v.x; out.x = a.x - (s * v.y); out.y = a.y + (s * v_x); return out; } /** * Get the center of two vectors. * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2Mid_V2_V2 = function(a, b, out) { out.x = (a.x + b.x) * 0.5; out.y = (a.y + b.y) * 0.5; return out; } /** * Get the extent of two vectors (half-widths). * @export * @return {box2d.b2Vec2} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b * @param {box2d.b2Vec2} out */ box2d.b2Ext_V2_V2 = function(a, b, out) { out.x = (b.x - a.x) * 0.5; out.y = (b.y - a.y) * 0.5; return out; } /** * @export * @return {number} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b */ box2d.b2Distance = function(a, b) { var c_x = a.x - b.x; var c_y = a.y - b.y; return Math.sqrt(c_x * c_x + c_y * c_y); } /** * @export * @return {number} * @param {box2d.b2Vec2} a * @param {box2d.b2Vec2} b */ box2d.b2DistanceSquared = function(a, b) { var c_x = a.x - b.x; var c_y = a.y - b.y; return (c_x * c_x + c_y * c_y); } /** * Ray-cast input data. The ray extends from p1 to p1 + * maxFraction * (p2 - p1). * @export * @constructor */ box2d.b2RayCastInput = function() { this.p1 = new box2d.b2Vec2(); this.p2 = new box2d.b2Vec2(); this.maxFraction = 1; } /** * @export * @type {box2d.b2Vec2} */ box2d.b2RayCastInput.prototype.p1 = null; /** * @export * @type {box2d.b2Vec2} */ box2d.b2RayCastInput.prototype.p2 = null; /** * @export * @type {number} */ box2d.b2RayCastInput.prototype.maxFraction = 1; /** * @export * @return {box2d.b2RayCastInput} * @param {box2d.b2RayCastInput} o */ box2d.b2RayCastInput.prototype.Copy = function(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 box2d.b2RayCastInput. * @export * @constructor */ box2d.b2RayCastOutput = function() { this.normal = new box2d.b2Vec2(); this.fraction = 0; }; /** * @export * @type {box2d.b2Vec2} */ box2d.b2RayCastOutput.prototype.normal = null; /** * @export * @type {number} */ box2d.b2RayCastOutput.prototype.fraction = 0; /** * @export * @return {box2d.b2RayCastOutput} * @param {box2d.b2RayCastOutput} o */ box2d.b2RayCastOutput.prototype.Copy = function(o) { this.normal.Copy(o.normal); this.fraction = o.fraction; return this; } /** * An axis aligned bounding box. * @export * @constructor */ box2d.b2AABB = function() { this.lowerBound = new box2d.b2Vec2(); this.upperBound = new box2d.b2Vec2(); this.m_out_center = new box2d.b2Vec2(); this.m_out_extent = new box2d.b2Vec2(); }; /** * @export * @type {box2d.b2Vec2} */ box2d.b2AABB.prototype.lowerBound = null; ///< the lower vertex /** * @export * @type {box2d.b2Vec2} */ box2d.b2AABB.prototype.upperBound = null; ///< the upper vertex /** * @export * @type {box2d.b2Vec2} */ box2d.b2AABB.prototype.m_out_center = null; // access using GetCenter() /** * @export * @type {box2d.b2Vec2} */ box2d.b2AABB.prototype.m_out_extent = null; // access using GetExtents() /** * @export * @return {box2d.b2AABB} */ box2d.b2AABB.prototype.Clone = function() { return new box2d.b2AABB().Copy(this); } /** * @export * @return {box2d.b2AABB} * @param {box2d.b2AABB} o */ box2d.b2AABB.prototype.Copy = function(o) { this.lowerBound.Copy(o.lowerBound); this.upperBound.Copy(o.upperBound); return this; } /** * Verify that the bounds are sorted. * @export * @return {boolean} */ box2d.b2AABB.prototype.IsValid = function() { var d_x = this.upperBound.x - this.lowerBound.x; var d_y = this.upperBound.y - this.lowerBound.y; var valid = d_x >= 0 && d_y >= 0; valid = valid && this.lowerBound.IsValid() && this.upperBound.IsValid(); return valid; } /** * Get the center of the AABB. * @export * @return {box2d.b2Vec2} */ box2d.b2AABB.prototype.GetCenter = function() { return box2d.b2Mid_V2_V2(this.lowerBound, this.upperBound, this.m_out_center); } /** * Get the extents of the AABB (half-widths). * @export * @return {box2d.b2Vec2} */ box2d.b2AABB.prototype.GetExtents = function() { return box2d.b2Ext_V2_V2(this.lowerBound, this.upperBound, this.m_out_extent); } /** * Get the perimeter length * @export * @return {number} */ box2d.b2AABB.prototype.GetPerimeter = function() { var wx = this.upperBound.x - this.lowerBound.x; var wy = this.upperBound.y - this.lowerBound.y; return 2 * (wx + wy); } /** * @return {box2d.b2AABB} * @param {box2d.b2AABB} a0 * @param {box2d.b2AABB=} a1 */ box2d.b2AABB.prototype.Combine = function(a0, a1) { switch (arguments.length) { case 1: return this.Combine1(a0); case 2: return this.Combine2(a0, a1 || new box2d.b2AABB()); default: throw new Error(); } } /** * Combine an AABB into this one. * @export * @return {box2d.b2AABB} * @param {box2d.b2AABB} aabb */ box2d.b2AABB.prototype.Combine1 = function(aabb) { this.lowerBound.x = box2d.b2Min(this.lowerBound.x, aabb.lowerBound.x); this.lowerBound.y = box2d.b2Min(this.lowerBound.y, aabb.lowerBound.y); this.upperBound.x = box2d.b2Max(this.upperBound.x, aabb.upperBound.x); this.upperBound.y = box2d.b2Max(this.upperBound.y, aabb.upperBound.y); return this; } /** * Combine two AABBs into this one. * @export * @return {box2d.b2AABB} * @param {box2d.b2AABB} aabb1 * @param {box2d.b2AABB} aabb2 */ box2d.b2AABB.prototype.Combine2 = function(aabb1, aabb2) { this.lowerBound.x = box2d.b2Min(aabb1.lowerBound.x, aabb2.lowerBound.x); this.lowerBound.y = box2d.b2Min(aabb1.lowerBound.y, aabb2.lowerBound.y); this.upperBound.x = box2d.b2Max(aabb1.upperBound.x, aabb2.upperBound.x); this.upperBound.y = box2d.b2Max(aabb1.upperBound.y, aabb2.upperBound.y); return this; } /** * @export * @return {box2d.b2AABB} * @param {box2d.b2AABB} aabb1 * @param {box2d.b2AABB} aabb2 * @param {box2d.b2AABB} out */ box2d.b2AABB.Combine = function(aabb1, aabb2, out) { out.Combine2(aabb1, aabb2); return out; } /** * Does this aabb contain the provided AABB. * @export * @return {boolean} * @param {box2d.b2AABB} aabb */ box2d.b2AABB.prototype.Contains = function(aabb) { var 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. * @export * @return {boolean} * @param {box2d.b2RayCastOutput} output * @param {box2d.b2RayCastInput} input */ box2d.b2AABB.prototype.RayCast = function(output, input) { var tmin = (-box2d.b2_maxFloat); var tmax = box2d.b2_maxFloat; var p_x = input.p1.x; var p_y = input.p1.y; var d_x = input.p2.x - input.p1.x; var d_y = input.p2.y - input.p1.y; var absD_x = box2d.b2Abs(d_x); var absD_y = box2d.b2Abs(d_y); var normal = output.normal; if (absD_x < box2d.b2_epsilon) { // Parallel. if (p_x < this.lowerBound.x || this.upperBound.x < p_x) { return false; } } else { var inv_d = 1 / d_x; var t1 = (this.lowerBound.x - p_x) * inv_d; var t2 = (this.upperBound.x - p_x) * inv_d; // Sign of the normal vector. var s = (-1); if (t1 > t2) { var 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 = box2d.b2Min(tmax, t2); if (tmin > tmax) { return false; } } if (absD_y < box2d.b2_epsilon) { // Parallel. if (p_y < this.lowerBound.y || this.upperBound.y < p_y) { return false; } } else { var inv_d = 1 / d_y; var t1 = (this.lowerBound.y - p_y) * inv_d; var t2 = (this.upperBound.y - p_y) * inv_d; // Sign of the normal vector. var s = (-1); if (t1 > t2) { var 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 = box2d.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; } /** * @export * @return {boolean} * @param {box2d.b2AABB} other */ box2d.b2AABB.prototype.TestOverlap = function(other) { var d1_x = other.lowerBound.x - this.upperBound.x; var d1_y = other.lowerBound.y - this.upperBound.y; var d2_x = this.lowerBound.x - other.upperBound.x; var d2_y = this.lowerBound.y - other.upperBound.y; if (d1_x > 0 || d1_y > 0) return false; if (d2_x > 0 || d2_y > 0) return false; return true; } /** * @export * @return {boolean} * @param {box2d.b2AABB} a * @param {box2d.b2AABB} b */ box2d.b2TestOverlap_AABB = function(a, b) { var d1_x = b.lowerBound.x - a.upperBound.x; var d1_y = b.lowerBound.y - a.upperBound.y; var d2_x = a.lowerBound.x - b.upperBound.x; var d2_y = a.lowerBound.y - b.upperBound.y; if (d1_x > 0 || d1_y > 0) return false; if (d2_x > 0 || d2_y > 0) return false; return true; } /** * A node in the dynamic tree. The client does not interact with * this directly. * @export * @constructor * @param {number=} id */ box2d.b2TreeNode = function(id) { this.m_id = id || 0; this.aabb = new box2d.b2AABB(); }; /** * @export * @type {number} */ box2d.b2TreeNode.prototype.m_id = 0; /** * Enlarged AABB * @export * @type {box2d.b2AABB} */ box2d.b2TreeNode.prototype.aabb = null; /** * @export * @type {*} */ box2d.b2TreeNode.prototype.userData = null; /** * @export * @type {box2d.b2TreeNode} */ box2d.b2TreeNode.prototype.parent = null; // or box2d.b2TreeNode.prototype.next /** * @export * @type {box2d.b2TreeNode} */ box2d.b2TreeNode.prototype.child1 = null; /** * @export * @type {box2d.b2TreeNode} */ box2d.b2TreeNode.prototype.child2 = null; /** * leaf = 0, free node = -1 * @export * @type {number} */ box2d.b2TreeNode.prototype.height = 0; /** * @export * @return {boolean} */ box2d.b2TreeNode.prototype.IsLeaf = function() { return this.child1 === null; } /** * A dynamic tree arranges data in a binary tree to accelerate * queries such as volume queries and ray casts. Leafs are proxies * with an AABB. In the tree we expand the proxy AABB by b2_fatAABBFactor * so that the proxy AABB is bigger than the client object. This allows the client * object to move by small amounts without triggering a tree update. * * Nodes are pooled and relocatable, so we use node indices rather than pointers. * @export * @constructor */ DynamicTree = function() {} /** * @export * @type {box2d.b2TreeNode} */ DynamicTree.prototype.m_root = null; //b2TreeNode* DynamicTree.prototype.m_nodes; //int32 DynamicTree.prototype.m_nodeCount; //int32 DynamicTree.prototype.m_nodeCapacity; /** * @export * @type {box2d.b2TreeNode} */ DynamicTree.prototype.m_freeList = null; /** * This is used to incrementally traverse the tree for * re-balancing. * @export * @type {number} */ DynamicTree.prototype.m_path = 0; /** * @export * @type {number} */ DynamicTree.prototype.m_insertionCount = 0; DynamicTree.s_stack = new box2d.b2GrowableStack(256); DynamicTree.s_r = new box2d.b2Vec2(); DynamicTree.s_v = new box2d.b2Vec2(); DynamicTree.s_abs_v = new box2d.b2Vec2(); DynamicTree.s_segmentAABB = new box2d.b2AABB(); DynamicTree.s_subInput = new box2d.b2RayCastInput(); DynamicTree.s_combinedAABB = new box2d.b2AABB(); DynamicTree.s_aabb = new box2d.b2AABB(); /** * Get proxy user data. * @export * @return {*} the proxy user data or 0 if the id is invalid. * @param {box2d.b2TreeNode} proxy */ DynamicTree.prototype.GetUserData = function(proxy) { if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(proxy !== null); } return proxy.userData; } /** * Get the fat AABB for a proxy. * @export * @return {box2d.b2AABB} * @param {box2d.b2TreeNode} proxy */ DynamicTree.prototype.GetFatAABB = function(proxy) { if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(proxy !== null); } return proxy.aabb; } /** * Query an AABB for overlapping proxies. The callback class is * called for each proxy that overlaps the supplied AABB. * @export * @return {void} * @param {function(box2d.b2TreeNode):boolean} callback * @param {box2d.b2AABB} aabb */ DynamicTree.prototype.Query = function(callback, aabb) { if (this.m_root === null) return; /** @type {box2d.b2GrowableStack} */ var stack = DynamicTree.s_stack.Reset(); stack.Push(this.m_root); while (stack.GetCount() > 0) { /** @type {box2d.b2TreeNode} */ var node = /** @type {box2d.b2TreeNode} */ (stack.Pop()); if (node === null) { continue; } if (node.aabb.TestOverlap(aabb)) { if (node.IsLeaf()) { /** @type {boolean} */ var proceed = callback(node); if (!proceed) { return; } } else { stack.Push(node.child1); stack.Push(node.child2); } } } } /** * Query an AABB for all entries. The callback class is * called for each proxy. * @export * @return {void} * @param {function(box2d.b2TreeNode):boolean} callback */ DynamicTree.prototype.Recurse = function(callback) { if (this.m_root === null) return; /** @type {box2d.b2GrowableStack} */ var stack = DynamicTree.s_stack.Reset(); stack.Push(this.m_root); while (stack.GetCount() > 0) { /** @type {box2d.b2TreeNode} */ var node = /** @type {box2d.b2TreeNode} */ (stack.Pop()); if (node === null) { continue; } if (node.IsLeaf()) { /** @type {boolean} */ var proceed = callback(node); if (!proceed) { return; } } else { stack.Push(node.child1); stack.Push(node.child2); } } } /** * 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. * @export * @return {void} * @param * {function(box2d.b2RayCastInput,box2d.b2TreeNode):number} * callback a callback class that is called for each * proxy that is hit by the ray. * @param {box2d.b2RayCastInput} input the ray-cast input data. * The ray extends from p1 to p1 + maxFraction * (p2 - * p1). */ DynamicTree.prototype.RayCast = function(callback, input) { if (this.m_root === null) return; /** @type {box2d.b2Vec2} */ var p1 = input.p1; /** @type {box2d.b2Vec2} */ var p2 = input.p2; /** @type {box2d.b2Vec2} */ var r = box2d.b2Sub_V2_V2(p2, p1, DynamicTree.s_r); if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(r.LengthSquared() > 0); } r.Normalize(); // v is perpendicular to the segment. /** @type {box2d.b2Vec2} */ var v = box2d.b2Cross_S_V2(1.0, r, DynamicTree.s_v); /** @type {box2d.b2Vec2} */ var abs_v = box2d.b2Abs_V2(v, DynamicTree.s_abs_v); // Separating axis for segment (Gino, p80). // |dot(v, p1 - c)| > dot(|v|, h) /** @type {number} */ var maxFraction = input.maxFraction; // Build a bounding box for the segment. /** @type {box2d.b2AABB} */ var segmentAABB = DynamicTree.s_segmentAABB; /** @type {number} */ var t_x = p1.x + maxFraction * (p2.x - p1.x); /** @type {number} */ var t_y = p1.y + maxFraction * (p2.y - p1.y); segmentAABB.lowerBound.x = box2d.b2Min(p1.x, t_x); segmentAABB.lowerBound.y = box2d.b2Min(p1.y, t_y); segmentAABB.upperBound.x = box2d.b2Max(p1.x, t_x); segmentAABB.upperBound.y = box2d.b2Max(p1.y, t_y); /** @type {box2d.b2GrowableStack} */ var stack = DynamicTree.s_stack.Reset(); stack.Push(this.m_root); while (stack.GetCount() > 0) { /** @type {box2d.b2TreeNode} */ var node = /** @type {box2d.b2TreeNode} */ (stack.Pop()); if (node === null) { continue; } if (!box2d.b2TestOverlap_AABB(node.aabb, segmentAABB)) { continue; } // Separating axis for segment (Gino, p80). // |dot(v, p1 - c)| > dot(|v|, h) /** @type {box2d.b2Vec2} */ var c = node.aabb.GetCenter(); /** @type {box2d.b2Vec2} */ var h = node.aabb.GetExtents(); /** @type {number} */ var separation = box2d.b2Abs(box2d.b2Dot_V2_V2(v, box2d.b2Sub_V2_V2(p1, c, box2d.b2Vec2.s_t0))) - box2d.b2Dot_V2_V2(abs_v, h); if (separation > 0) { continue; } if (node.IsLeaf()) { /** @type {box2d.b2RayCastInput} */ var subInput = DynamicTree.s_subInput; subInput.p1.Copy(input.p1); subInput.p2.Copy(input.p2); subInput.maxFraction = maxFraction; /** @type {number} */ var 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 = box2d.b2Min(p1.x, t_x); segmentAABB.lowerBound.y = box2d.b2Min(p1.y, t_y); segmentAABB.upperBound.x = box2d.b2Max(p1.x, t_x); segmentAABB.upperBound.y = box2d.b2Max(p1.y, t_y); } } else { stack.Push(node.child1); stack.Push(node.child2); } } } /** * @export * @return {box2d.b2TreeNode} */ DynamicTree.prototype.AllocateNode = function() { // Expand the node pool as needed. if (this.m_freeList) { /** @type {box2d.b2TreeNode} */ var 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.userData = null; return node; } return new box2d.b2TreeNode(DynamicTree.prototype.s_node_id++); } DynamicTree.prototype.s_node_id = 0; /** * @export * @return {void} * @param {box2d.b2TreeNode} node */ DynamicTree.prototype.FreeNode = function(node) { node.parent = this.m_freeList; // node.next = this.m_freeList; node.height = -1; this.m_freeList = node; } /** * Create a proxy. Provide a tight fitting AABB and a userData * pointer. * @export * @return {box2d.b2TreeNode} * @param {box2d.b2AABB} aabb * @param {*} userData */ DynamicTree.prototype.CreateProxy = function(aabb, userData) { /** @type {box2d.b2TreeNode} */ var node = this.AllocateNode(); // Fatten the aabb. /** @type {number} */ var r_x = box2d.b2_aabbExtension; /** @type {number} */ var r_y = box2d.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; this.InsertLeaf(node); return node; } /** * Destroy a proxy. This asserts if the id is invalid. * @export * @return {void} * @param {box2d.b2TreeNode} proxy */ DynamicTree.prototype.DestroyProxy = function(proxy) { if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(proxy.IsLeaf()); } this.RemoveLeaf(proxy); this.FreeNode(proxy); } /** * Move a proxy with a swepted AABB. If the proxy has moved * outside of its fattened AABB, then the proxy is removed from * the tree and re-inserted. Otherwise the function returns * immediately. * @export * @return {boolean} true if the proxy was re-inserted. * @param {box2d.b2TreeNode} proxy * @param {box2d.b2AABB} aabb * @param {box2d.b2Vec2} displacement */ DynamicTree.prototype.MoveProxy = function(proxy, aabb, displacement) { if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(proxy.IsLeaf()); } if (proxy.aabb.Contains(aabb)) { return false; } this.RemoveLeaf(proxy); // Extend AABB. // Predict AABB displacement. /** @type {number} */ var r_x = box2d.b2_aabbExtension + box2d.b2_aabbMultiplier * (displacement.x > 0 ? displacement.x : (-displacement.x)); /** @type {number} */ var r_y = box2d.b2_aabbExtension + box2d.b2_aabbMultiplier * (displacement.y > 0 ? displacement.y : (-displacement.y)); proxy.aabb.lowerBound.x = aabb.lowerBound.x - r_x; proxy.aabb.lowerBound.y = aabb.lowerBound.y - r_y; proxy.aabb.upperBound.x = aabb.upperBound.x + r_x; proxy.aabb.upperBound.y = aabb.upperBound.y + r_y; this.InsertLeaf(proxy); return true; } /** * @export * @return {void} * @param {box2d.b2TreeNode} leaf */ DynamicTree.prototype.InsertLeaf = function(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 /** @type {box2d.b2AABB} */ var leafAABB = leaf.aabb; /** @type {box2d.b2Vec2} */ var center = leafAABB.GetCenter(); /** @type {box2d.b2TreeNode} */ var index = this.m_root; /** @type {box2d.b2TreeNode} */ var child1; /** @type {box2d.b2TreeNode} */ var child2; while (!index.IsLeaf()) { child1 = index.child1; child2 = index.child2; /** @type {number} */ var area = index.aabb.GetPerimeter(); /** @type {box2d.b2AABB} */ var combinedAABB = DynamicTree.s_combinedAABB; combinedAABB.Combine2(index.aabb, leafAABB); /** @type {number} */ var combinedArea = combinedAABB.GetPerimeter(); // Cost of creating a new parent for this node and the new leaf /** @type {number} */ var cost = 2 * combinedArea; // Minimum cost of pushing the leaf further down the tree /** @type {number} */ var inheritanceCost = 2 * (combinedArea - area); // Cost of descending into child1 /** @type {number} */ var cost1; /** @type {box2d.b2AABB} */ var aabb = DynamicTree.s_aabb; /** @type {number} */ var oldArea; /** @type {number} */ var 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 /** @type {number} */ var 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) { index = child1; } else { index = child2; } } /** @type {box2d.b2TreeNode} */ var sibling = index; // Create a parent for the siblings. /** @type {box2d.b2TreeNode} */ var oldParent = sibling.parent; /** @type {box2d.b2TreeNode} */ var newParent = this.AllocateNode(); newParent.parent = oldParent; newParent.userData = null; newParent.aabb.Combine2(leafAABB, sibling.aabb); newParent.height = sibling.height + 1; if (oldParent) { // 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 index = leaf.parent; while (index !== null) { index = this.Balance(index); child1 = index.child1; child2 = index.child2; if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(child1 !== null); } if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(child2 !== null); } index.height = 1 + box2d.b2Max(child1.height, child2.height); index.aabb.Combine2(child1.aabb, child2.aabb); index = index.parent; } //this.Validate(); } /** * @export * @return {void} * @param {box2d.b2TreeNode} leaf */ DynamicTree.prototype.RemoveLeaf = function(leaf) { if (leaf === this.m_root) { this.m_root = null; return; } /** @type {box2d.b2TreeNode} */ var parent = leaf.parent; /** @type {box2d.b2TreeNode} */ var grandParent = parent.parent; /** @type {box2d.b2TreeNode} */ var sibling; if (parent.child1 === leaf) { sibling = parent.child2; } else { sibling = parent.child1; } if (grandParent) { // 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. /** @type {box2d.b2TreeNode} */ var index = grandParent; while (index) { index = this.Balance(index); /** @type {box2d.b2TreeNode} */ var child1 = index.child1; /** @type {box2d.b2TreeNode} */ var child2 = index.child2; index.aabb.Combine2(child1.aabb, child2.aabb); index.height = 1 + box2d.b2Max(child1.height, child2.height); index = index.parent; } } else { this.m_root = sibling; sibling.parent = null; this.FreeNode(parent); } //this.Validate(); } /** * Perform a left or right rotation if node A is imbalanced. * Returns the new root index. * @export * @param {box2d.b2TreeNode} A * @return {box2d.b2TreeNode} */ DynamicTree.prototype.Balance = function(A) { if (box2d.ENABLE_ASSERTS) { box2d.b2Assert(A !== null); } if (A.IsLeaf() || A.height < 2) { return A; } /** @type {box2d.b2TreeNode} */ var B = A.child1; /** @type {box2d.b2TreeNode} */ var C = A.child2; /** @type {number} */ var balance = C.height - B.height; // Rotate C up if (balance > 1) { /** @type {box2d.b2TreeNode} */ var F = C.child1; /** @type {box2d.b2TreeNode} */ var G = 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 { if (box2d.ENABLE_ASSERTS) { box2d.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 + box2d.b2Max(B.height, G.height); C.height = 1 + box2d.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 + box2d.b2Max(B.height, F.height); C.height = 1 + box2d.b2Max(A.height, G.height); } return C; } // Rotate B up if (balance < -1) { /** @type {box2d.b2TreeNode} */ var D = B.child1; /** @type {box2d.b2TreeNode} */ var E = 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 { if (box2d.ENABLE_ASSERTS) { box2d.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 + box2d.b2Max(C.height, E.height); B.height = 1 + box2d.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 + box2d.b2Max(C.height, D.height); B.height = 1 + box2d.b2Max(A.height, E.height); } return B; } return A; } /** * Compute the height of the binary tree in O(N) time. Should * not be called often. * @export * @return {number} */ DynamicTree.prototype.GetHeight = function() { if (this.m_root === null) { return 0; } return this.m_root.height; } /** * Get the ratio of the sum of the node areas to the root area. * @export * @return {number} */ DynamicTree.prototype.GetAreaRatio = function() { if (this.m_root === null) { return 0; } /** @type {box2d.b2TreeNode} */ var root = this.m_root; /** @type {number} */ var rootArea = root.aabb.GetPerimeter(); var GetAreaNode = function(node) { if (node === null) { return 0; } if (node.IsLeaf()) { return 0; } /** @type {number} */ var area = node.aabb.GetPerimeter(); area += GetAreaNode(node.child1); area += GetAreaNode(node.child2); return area; } /** @type {number} */ var totalArea = 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; } /** * Compute the height of a sub-tree. * @export * @return {number} * @param {box2d.b2TreeNode} node */ DynamicTree.prototype.ComputeHeightNode = function(node) { if (node.IsLeaf()) { return 0; } /** @type {number} */ var height1 = this.ComputeHeightNode(node.child1); /** @type {number} */ var height2 = this.ComputeHeightNode(node.child2); return 1 + box2d.b2Max(height1, height2); } /** * @export * @return {number} */ DynamicTree.prototype.ComputeHeight = function() { /**