UNPKG

pixelscanjs

Version:

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

2,166 lines (1,800 loc) 850 kB
const PixelScan = (function() { class Vec2 { x; y; constructor(x, y) { this.x = x || 0; this.y = y || 0; } static copy(vec) { return new Vec2(vec.x, vec.y); } static fromAngle(angle) { return new Vec2(Math.cos(angle), Math.sin(angle)); } static set(x, y) { return new Vec2(x, y); } copy(vec) { this.x = vec.x; this.y = vec.y; return this; } set(x, y) { this.x = x; this.y = y; return this; } add(vec) { this.x += vec.x; this.y += vec.y; return this; } subtract(vec) { this.x -= vec.x; this.y -= vec.y; return this; } sub = this.subtract; round() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; } multiply(mat) { if (Number.isNaN(mat)) { const x = this.x * mat.v0 + this.y * mat.v3 + mat.v6; const y = this.x * mat.v1 + this.y * mat.v4 + mat.v7; this.x = x; this.y = y; } else { this.x *= mat; this.y *= mat; } return this; } mul = this.multiply; magnitude() { return Math.sqrt(this.x * this.x + this.y * this.y); } length = this.magnitude; magnitudeSquared() { return this.x * this.x + this.y * this.y; } square() { this.x = this.x * this.x; this.y = this.y * this.y; return this; } squareRoot() { this.x = Math.sqrt(this.x); this.y = Math.sqrt(this.y); return this; } sqrt = this.squareRoot; rotate(radians) { const x = this.x; const y = this.y; this.x = x * Math.cos(radians) - y * Math.sin(radians); this.y = y * Math.cos(radians) + x * Math.sin(radians); return this; } orthogonal() { const x = this.x; this.x = -this.y; this.y = x; return this; } ortho = this.orthogonal; normalize() { const length = this.magnitude(); if (length === 0) { return this; } this.x /= length; this.y /= length; return this; } norm = this.normalize; distance(vec) { const dx = vec.x - this.x; const dy = vec.y - this.y; return Math.sqrt(dx * dx + dy * dy); } dist = this.distance; distanceSquared(vec) { const dx = vec.x - this.x; const dy = vec.y - this.y; return dx * dx + dy * dy; } distSquared = this.distanceSquared; negate() { this.x = -this.x; this.y = -this.y; return this; } atan2() { return Math.atan2(this.y, this.x); } dot(vec) { return this.x * vec.x + this.y * vec.y; } cross(vec) { return this.x * vec.y - vec.x * this.y; } // returns a number between -1 and 1, // where 0 represents the two vectors are the same direction, // 0.5 represents the perpendicular normal, // and -0.5 is the inverted normal // valid for all vectors where the positive angle between them is < 180, not equal crossDot(vec) { const sign = Math.sign(this.cross(vec)) || 1; return (0.5 - this.dot(vec) / 2.0) * sign; } projectOnto(vec) { tempVec.copy(vec); tempVec.normalize(); const top = this.dot(tempVec); const bottom = tempVec.dot(tempVec); this.copy(tempVec); this.multiply(top / bottom); return this; } } tempVec = new Vec2(); class Mat3 { v0; v1; v2; v3; v4; v5; v6; v7; v8; // NOTE: libgdx's indices are transposed constructor() { this.v0 = 1; this.v1 = 0; this.v2 = 0; this.v3 = 0; this.v4 = 1; this.v5 = 0; this.v6 = 0; this.v7 = 0; this.v8 = 1; } copy(mat) { this.v0 = mat.v0; this.v1 = mat.v1; this.v2 = mat.v2; this.v3 = mat.v3; this.v4 = mat.v4; this.v5 = mat.v5; this.v6 = mat.v6; this.v7 = mat.v7; this.v8 = mat.v8; return this; } determinant() { return this.v0 * this.v4 * this.v8 + this.v1 * this.v5 * this.v6 + this.v2 * this.v3 * this.v7 - this.v0 * this.v5 * this.v7 - this.v1 * this.v3 * this.v8 - this.v2 * this.v4 * this.v6; } invert() { const det = this.determinant(); if (det === 0) { return null; } const inv = 1.0 / det; tempMat.v0 = this.v4 * this.v8 - this.v7 * this.v5; tempMat.v3 = this.v6 * this.v5 - this.v3 * this.v8; tempMat.v6 = this.v3 * this.v7 - this.v6 * this.v4; tempMat.v1 = this.v7 * this.v2 - this.v1 * this.v8; tempMat.v4 = this.v0 * this.v8 - this.v6 * this.v2; tempMat.v7 = this.v6 * this.v1 - this.v0 * this.v7; tempMat.v2 = this.v1 * this.v5 - this.v4 * this.v2; tempMat.v5 = this.v3 * this.v2 - this.v0 * this.v5; tempMat.v8 = this.v0 * this.v4 - this.v3 * this.v1; this.v0 = inv * tempMat.v0; this.v3 = inv * tempMat.v3; this.v6 = inv * tempMat.v6; this.v1 = inv * tempMat.v1; this.v4 = inv * tempMat.v4; this.v7 = inv * tempMat.v7; this.v2 = inv * tempMat.v2; this.v5 = inv * tempMat.v5; this.v8 = inv * tempMat.v8; return this; } multiply(mat) { const v00 = this.v0 * mat.v0 + this.v3 * mat.v1 + this.v6 * mat.v2; const v01 = this.v0 * mat.v3 + this.v3 * mat.v4 + this.v6 * mat.v5; const v02 = this.v0 * mat.v6 + this.v3 * mat.v7 + this.v6 * mat.v8; const v10 = this.v1 * mat.v0 + this.v4 * mat.v1 + this.v7 * mat.v2; const v11 = this.v1 * mat.v3 + this.v4 * mat.v4 + this.v7 * mat.v5; const v12 = this.v1 * mat.v6 + this.v4 * mat.v7 + this.v7 * mat.v8; const v20 = this.v2 * mat.v0 + this.v5 * mat.v1 + this.v8 * mat.v2; const v21 = this.v2 * mat.v3 + this.v5 * mat.v4 + this.v8 * mat.v5; const v22 = this.v2 * mat.v6 + this.v5 * mat.v7 + this.v8 * mat.v8; this.v0 = v00; this.v1 = v10; this.v2 = v20; this.v3 = v01; this.v4 = v11; this.v5 = v21; this.v6 = v02; this.v7 = v12; this.v8 = v22; return this; } leftMultiply(mat) { const v00 = mat.v0 * this.v0 + mat.v3 * this.v1 + mat.v6 * this.v2; const v01 = mat.v0 * this.v3 + mat.v3 * this.v4 + mat.v6 * this.v5; const v02 = mat.v0 * this.v6 + mat.v3 * this.v7 + mat.v6 * this.v8; const v10 = mat.v1 * this.v0 + mat.v4 * this.v1 + mat.v7 * this.v2; const v11 = mat.v1 * this.v3 + mat.v4 * this.v4 + mat.v7 * this.v5; const v12 = mat.v1 * this.v6 + mat.v4 * this.v7 + mat.v7 * this.v8; const v20 = mat.v2 * this.v0 + mat.v5 * this.v1 + mat.v8 * this.v2; const v21 = mat.v2 * this.v3 + mat.v5 * this.v4 + mat.v8 * this.v5; const v22 = mat.v2 * this.v6 + mat.v5 * this.v7 + mat.v8 * this.v8; this.v0 = v00; this.v1 = v10; this.v2 = v20; this.v3 = v01; this.v4 = v11; this.v5 = v21; this.v6 = v02; this.v7 = v12; this.v8 = v22; return this; } setToTranslation(vec) { this.v0 = 1; this.v1 = 0; this.v2 = 0; this.v3 = 0; this.v4 = 1; this.v5 = 0; this.v6 = vec.x; this.v7 = vec.y; this.v8 = 1; return this; } getTranslation(out) { out.x = this.v6; out.y = this.v7; return out; } setTranslation(vec) { const inverseVec = this.getTranslation(tempVec).negate(); const inverse = tempMat.setToTranslation(inverseVec); // translation * (inverse * self) this.leftMultiply(inverse); const correct = tempMat.setToTranslation(vec); return this.leftMultiply(correct) } translate(vec) { tempMat.setToTranslation(vec); return this.multiply(tempMat); } setToRotation(radians) { const cos = Math.cos(radians); const sin = Math.sin(radians); this.v0 = cos; this.v1 = sin; this.v2 = 0; this.v3 = -sin; this.v4 = cos; this.v5 = 0; this.v6 = 0; this.v7 = 0; this.v8 = 1; return this; } getRotation() { return Math.atan2(this.v1, this.v0); } setRotation(radians) { const inverse = tempMat.setToRotation(-this.getRotation()); this.multiply(inverse); const correct = tempMat.setToRotation(radians); return this.multiply(correct); } rotate(radians) { tempMat.setToRotation(radians); return this.multiply(tempMat); } } tempMat = new Mat3(); class Hash { static buffer = new ArrayBuffer(4); static byteBuffer = new Uint8Array(Hash.buffer); static intBuffer = new Int32Array(Hash.buffer); static integerHash(string) { let value = 0; let index = 0; while (index < string.length) { for (let i = 0; i < 4; i++) { if (index < string.length) { Hash.byteBuffer[i] = string.charCodeAt(index); index++; } else { Hash.byteBuffer[i] = 0; } } } value ^= Hash.intBuffer[0]; return value; } } class AABB { x; y; width; height; constructor(x, y, width, height) { this.x = x || 0; this.y = y || 0; this.width = width || 0; this.height = height || 0; } static copy(aabb) { return new AABB(aabb.x, aabb.y, aabb.width, aabb.height); } copy(aabb) { this.x = aabb.x; this.y = aabb.y; this.width = aabb.width; this.height = aabb.height; return this; } round() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; } contains(x, y) { return (x >= this.x) && (y >= this.y) && (x - this.x < this.width) && (y - this.y < this.height); } } class Camera { static position = new Vec2(); static aabb = new AABB(); static scale = new Vec2(1, 1); static nextPosition = new Vec2(); static nextScale = new Vec2(1, 1); static containers = []; static positionSpeedStrength = 0.5; static scaleSpeedStrength = 0.05; static shakeDuration = 15; static shakeIntensity = 10; static shakeFalloff = 0.75; static remainingShakeDuration = 0; static shakeSeedHorizontal = 0; static shakeSeedVertical = 0; static cameraHeight = 1080; static setPosition(position) { Camera.nextPosition.copy(position).round(); } static setPositionImmediate(position) { Camera.nextPosition.copy(position).round(); Camera.position.copy(position).round(); } static setScale(scale) { Camera.nextScale.copy(scale); } static setScaleImmediate(scale) { Camera.nextScale.copy(scale); Camera.scale.copy(scale); } static shake(intensity) { Camera.shakeIntensity = intensity || 15; Camera.remainingShakeDuration = Camera.shakeDuration; Camera.shakeSeedHorizontal = Math.random(); Camera.shakeSeedVertical = Math.random(); } static setSpeedProperties(strength) { this.positionSpeedStrength = strength; } static setScaleProperties(strength) { this.scaleAccel = accel; this.minimumScaleSpeed = minumumSpeed; this.maximumScaleSpeed = maximumSpeed; this.maximumScaleDistance = maximumScale; } static setShakeProperties(duration, falloff) { Camera.shakeDuration = duration; Camera.shakeFalloff = falloff; } static setCameraHeight(height) { Camera.cameraHeight = height; } static update() { let shakeX = 0; let shakeY = 0; if (Camera.remainingShakeDuration > 0) { Camera.remainingShakeDuration--; // its okay if progress goes past 1 because it wraps around const progress = (Camera.shakeDuration - Camera.remainingShakeDuration) / 30; const shake = Camera.shakeIntensity - Camera.shakeIntensity * progress * Camera.shakeFalloff; shakeX = PerlinNoise.getNoise(Camera.shakeSeedHorizontal, progress) * shake - shake / 2; shakeY = PerlinNoise.getNoise(Camera.shakeSeedVertical, progress) * shake - shake / 2; } const positionDeltaX = Camera.nextPosition.x - Camera.position.x; const positionDeltaY = Camera.nextPosition.y - Camera.position.y; const positionDeltaLength = Math.sqrt(positionDeltaX * positionDeltaX + positionDeltaY * positionDeltaY); if (positionDeltaLength <= 0.5) { Camera.position.copy(Camera.nextPosition); } else { Camera.position.x += positionDeltaX * Camera.positionSpeedStrength; Camera.position.y += positionDeltaY * Camera.positionSpeedStrength; } const scaleDelta = Camera.nextScale.x - Camera.scale.x; if (Math.abs(scaleDelta) <= 0.01) { Camera.scale.copy(Camera.nextScale); } else { Camera.scale.x += scaleDelta * Camera.scaleSpeedStrength; Camera.scale.y = Camera.scale.x; } const width = window.innerWidth; const height = window.innerHeight; const heightScale = height / Camera.cameraHeight; const scaleX = Camera.scale.x * heightScale; const scaleY = Camera.scale.y * heightScale; Camera.aabb.x = Camera.position.x - width / 2 / scaleX + shakeX; Camera.aabb.y = Camera.position.y - height / 2 / scaleY + shakeY; Camera.aabb.width = width / scaleX; Camera.aabb.height = height / scaleY; const x = Camera.aabb.width / 2 * scaleX - Camera.position.x * scaleX; const y = Camera.aabb.height / 2 * scaleY - Camera.position.y * scaleY; for (let i = 0; i < Camera.containers.length; i++) { Camera.containers[i].position.x = x + shakeX; Camera.containers[i].position.y = y + shakeY; Camera.containers[i].scale.x = scaleX; Camera.containers[i].scale.y = scaleY; } } static addContainer(container) { Camera.containers.push(container); } } class CPUTracker extends PIXI.Text { history; nextIndex; startTime; endTime; constructor(color) { super('CPU: 0.0%', {fill: color === undefined ? 0xffffff : color, fontSize: 16}); this.history = new Array(60).fill(0); this.nextIndex = 0; this.startTime = 0; this.endTime = 0; } beginFrame(time) { if (this.startTime > 0 && this.endTime > 0) { const totalTime = time - this.startTime; const frameTime = this.endTime - this.startTime; this.history[this.nextIndex] = Math.max(frameTime / totalTime, Number.MIN_VALUE); this.nextIndex = (this.nextIndex + 1) % this.history.length; let total = 0; let count = 0; for (let i = 0; i < this.history.length; i++) { if (this.history[i] === 0) { continue; } total += this.history[i]; count++; } const cpu = total / count * 100; const integer = Math.floor(cpu); const remainder = Math.floor((cpu - integer) * 100); this.text = 'CPU: ' + integer + '.' + remainder + '%'; } this.startTime = time; } endFrame(time) { this.endTime = time; } } class DebugCanvas extends PIXI.Graphics { constructor() { super(); } drawRect(x, y, width, height, color, alpha) { color = color || 0; alpha = alpha === undefined ? 1 : alpha; this.lineStyle(0); this.beginFill(color, alpha); super.drawRect(x, y, width, height); this.endFill(); } drawLine(x1, y1, x2, y2, color, alpha) { color = color || 0; alpha = alpha === undefined ? 1 : alpha; this.lineStyle(1, color, alpha); this.moveTo(x1, y1); this.lineTo(x2, y2); this.closePath(); } render(renderer) { super.render(renderer); this.clear(); } } class FPSTracker extends PIXI.Text { history; nextIndex; constructor(color) { super('FPS: 0.0', {fill: color === undefined ? 0xffffff : color, fontSize: 16}); this.history = new Array(60).fill(0); this.nextIndex = 0; } getFPS() { let startIndex = this.nextIndex; if (this.history[startIndex] === 0) { startIndex = 0; } const firstTime = this.history[startIndex]; if (startIndex === 0 && firstTime === 0) { return 0.0; } const lastIndex = (this.nextIndex + this.history.length - 1) % this.history.length; const lastTime = this.history[lastIndex]; const deltaTime = lastTime - firstTime; const deltaFrames = (lastIndex - startIndex + this.history.length) % this.history.length; if (deltaTime === 0) { return 0; } return deltaFrames / deltaTime * 1000; } tick(time) { this.history[this.nextIndex] = time; this.nextIndex = (this.nextIndex + 1) % this.history.length; const fps = this.getFPS(); const integer = Math.floor(fps); const remainder = Math.floor((fps - integer) * 100); this.text = 'FPS: ' + integer + '.' + remainder; } } class FramedSprite extends PIXI.Sprite { textures; animations; currentName; currentFrame; // left to right top to bottom sprite sheet constructor(texture, width, height, columns, count) { super(null); this.textures = []; this.textures.length = count; for (let i = 0; i < count; i++) { const row = Math.floor(i / columns); const column = i % columns; const x = column * width; const y = row * height; this.textures[i] = new PIXI.Texture(texture, new PIXI.Rectangle(x, y, width, height)); } this.texture = this.textures[0]; this.animations = {}; this.currentName = undefined; this.currentFrame = 0; } addAnimation(name, start, count) { const animation = { start: start, count: count, linked: {}, }; this.animations[name] = animation; } gotoAnimation(name, frame) { if (frame !== undefined) { this.currentFrame = frame; } else if (this.currentName !== name) { // if were on a different animation check if we need to reset the frame if (!this.animations[this.currentName] || !this.animations[this.currentName].linked[name]) { this.currentFrame = 0; } } this.currentName = name; if (this.animations[this.currentName]) { this.currentFrame = this.currentFrame % this.animations[this.currentName].count; } else { this.currentFrame = this.currentFrame } this.updateFrame(); } stepAnimation(name, frames, loop) { if (Number.isNaN(name)) { loop = frames; frames = name; name = undefined; } frames = frames || 1; if (loop === undefined) { loop = true; } if (this.currentName !== name) { if (!this.animations[this.currentName] || !this.animations[this.currentName].linked[name]) { this.currentFrame = 0; } } this.currentName = name; if (this.animations[this.currentName]) { if (loop) { this.currentFrame = (this.currentFrame + frames) % this.animations[this.currentName].count; } else { this.currentFrame = Math.min(this.currentFrame + frames, this.animations[this.currentName].count - 1); } } else { if (loop) { this.currentFrame = (this.currentFrame + frames) % this.textures.length; } else { this.currentFrame = Math.min(this.currentFrame + frames, this.textures.length - 1); } } this.updateFrame(); } // adds a linked animation so the animation will pick up from where it left off linkAnimations(name, linkedName) { this.animations[name].linked[linkedName] = true; this.animations[linkedName].linked[name] = true; } getFrame() { return this.currentFrame; } updateFrame() { if (this.animations[this.currentName]) { this.texture = this.textures[Math.floor(this.currentFrame + this.animations[this.currentName].start)]; } else { this.texture = this.textures[Math.floor(this.currentFrame)]; } } } class ParallaxSprite extends PIXI.Sprite { aabb; constructor(texture, aabb) { super(texture); this.aabb = aabb; } update(cameraAABB) { const cameraCenterX = cameraAABB.x + cameraAABB.width / 2; const cameraCenterY = cameraAABB.y + cameraAABB.height / 2; const minX = this.aabb.x + cameraAABB.width / 2; const minY = this.aabb.y + cameraAABB.height / 2; const maxX = this.aabb.x + this.aabb.width - cameraAABB.width / 2; const maxY = this.aabb.y + this.aabb.height - cameraAABB.height / 2; const progressX = Math.min(Math.max((cameraCenterX - minX) / (maxX - minX), 0), 1); const progressY = Math.min(Math.max((cameraCenterY - minY) / (maxY - minY), 0), 1); const spriteMaxDeltaX = this.width - cameraAABB.width; const spriteMaxDeltaY = this.height - cameraAABB.height; this.position.x = -spriteMaxDeltaX * progressX; this.position.y = -spriteMaxDeltaY * progressY; } } class Input { static KEY_0 = '0'; static KEY_1 = '1'; static KEY_2 = '2'; static KEY_3 = '3'; static KEY_4 = '4'; static KEY_5 = '5'; static KEY_6 = '6'; static KEY_7 = '7'; static KEY_8 = '8'; static KEY_9 = '9'; static KEY_A = 'a'; static KEY_B = 'b'; static KEY_C = 'c'; static KEY_D = 'd'; static KEY_E = 'e'; static KEY_F = 'f'; static KEY_G = 'g'; static KEY_H = 'h'; static KEY_I = 'i'; static KEY_J = 'j'; static KEY_K = 'k'; static KEY_L = 'l'; static KEY_M = 'm'; static KEY_N = 'n'; static KEY_O = 'o'; static KEY_P = 'p'; static KEY_Q = 'q'; static KEY_R = 'r'; static KEY_S = 's'; static KEY_T = 't'; static KEY_U = 'u'; static KEY_V = 'v'; static KEY_W = 'w'; static KEY_X = 'x'; static KEY_Y = 'y'; static KEY_Z = 'z'; static KEY_ESCAPE = 'escape'; static KEY_SHIFT = 'shift'; static KEY_SPACE = ' '; static keys = {}; static mouseDownLeft = false; static mouseDownRight = false; static mousePosition = null; } window.addEventListener('load', () => { Input.mousePosition = new Vec2(); window.addEventListener('keydown', event => { if (!event.key) { return true; } Input.keys[event.key.toLowerCase()] = true; return true; }, true); window.addEventListener('keyup', event => { if (!event.key) { return true; } delete Input.keys[event.key.toLowerCase()]; return true; }, true); window.addEventListener('mousedown', event => { if (event.button === 0) { Input.mouseDownLeft = true; } if (event.button === 2) { Input.mouseDownRight = true; } return true; }, true); window.addEventListener('mouseup', event => { if (event.button === 0) { Input.mouseDownLeft = false; } if (event.button === 2) { Input.mouseDownRight = false; } return true; }, true); window.addEventListener('mousemove', event => { Input.mousePosition[0] = event.clientX; Input.mousePosition[1] = event.clientY; return true; }, true); window.addEventListener('contextmenu', event => { event.preventDefault(); return false; }, true); }); 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;