Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/excaliburjs/Excalibur. 🎮 Your friendly TypeScript 2D game engine for the web 🗡️ excaliburjs.com
excalibur excaliburjs game-development game-engine game-framework gamedev games html5-canvas typescript
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607import { sign } from './util';import { Vector, vec } from './vector';import { canonicalizeAngle } from './util';import { vec3, Vector3 } from './vector3';import { vec4, Vector4 } from './vector4';
export enum MatrixLocations { X = 12, Y = 13}
/** * Excalibur Matrix helper for 4x4 matrices * * Useful for webgl 4x4 matrices */export class Matrix { /** * 4x4 matrix in column major order * * | | | | | * | ------- | ------- | -------- | -------- | * | data[0] | data[4] | data[8] | data[12] | * | data[1] | data[5] | data[9] | data[13] | * | data[2] | data[6] | data[10] | data[14] | * | data[3] | data[7] | data[11] | data[15] | * */ public data: Float32Array = new Float32Array(16);
/** * Creates an orthographic (flat non-perspective) projection * https://en.wikipedia.org/wiki/Orthographic_projection * @param left * @param right * @param bottom * @param top * @param near * @param far */ public static ortho(left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix { const mat = new Matrix(); mat.data[0] = 2 / (right - left); mat.data[1] = 0; mat.data[2] = 0; mat.data[3] = 0;
mat.data[4] = 0; mat.data[5] = 2 / (top - bottom); mat.data[6] = 0; mat.data[7] = 0;
mat.data[8] = 0; mat.data[9] = 0; mat.data[10] = -2 / (far - near); mat.data[11] = 0;
mat.data[12] = -(right + left) / (right - left); mat.data[13] = -(top + bottom) / (top - bottom); mat.data[14] = -(far + near) / (far - near); mat.data[15] = 1; return mat; }
/** * Creates a new Matrix with the same data as the current 4x4 */ public clone(dest?: Matrix): Matrix { const mat = dest || new Matrix(); mat.data[0] = this.data[0]; mat.data[1] = this.data[1]; mat.data[2] = this.data[2]; mat.data[3] = this.data[3];
mat.data[4] = this.data[4]; mat.data[5] = this.data[5]; mat.data[6] = this.data[6]; mat.data[7] = this.data[7];
mat.data[8] = this.data[8]; mat.data[9] = this.data[9]; mat.data[10] = this.data[10]; mat.data[11] = this.data[11];
mat.data[12] = this.data[12]; mat.data[13] = this.data[13]; mat.data[14] = this.data[14]; mat.data[15] = this.data[15]; return mat; }
/** * Converts the current matrix into a DOMMatrix * * This is useful when working with the browser Canvas context * @returns {DOMMatrix} DOMMatrix */ public toDOMMatrix(): DOMMatrix { return new DOMMatrix([...this.data]); }
public static fromFloat32Array(data: Float32Array) { const matrix = new Matrix(); matrix.data = data; return matrix; }
/** * Creates a new identity matrix (a matrix that when applied does nothing) */ public static identity(): Matrix { const mat = new Matrix(); mat.data[0] = 1; mat.data[1] = 0; mat.data[2] = 0; mat.data[3] = 0;
mat.data[4] = 0; mat.data[5] = 1; mat.data[6] = 0; mat.data[7] = 0;
mat.data[8] = 0; mat.data[9] = 0; mat.data[10] = 1; mat.data[11] = 0;
mat.data[12] = 0; mat.data[13] = 0; mat.data[14] = 0; mat.data[15] = 1; return mat; }
/** * Resets the current matrix to the identity matrix, mutating it * @returns {Matrix} Current matrix as identity */ public reset(): Matrix { const mat = this; mat.data[0] = 1; mat.data[1] = 0; mat.data[2] = 0; mat.data[3] = 0;
mat.data[4] = 0; mat.data[5] = 1; mat.data[6] = 0; mat.data[7] = 0;
mat.data[8] = 0; mat.data[9] = 0; mat.data[10] = 1; mat.data[11] = 0;
mat.data[12] = 0; mat.data[13] = 0; mat.data[14] = 0; mat.data[15] = 1; return mat; }
/** * Creates a brand new translation matrix at the specified 3d point * @param x * @param y */ public static translation(x: number, y: number): Matrix { const mat = Matrix.identity(); mat.data[12] = x; mat.data[13] = y; return mat; }
/** * Creates a brand new translation matrix at the specified 3d point * @param vec */ public static translation3d(vec: Vector3): Matrix { const mat = Matrix.identity(); mat.data[12] = vec.x; mat.data[13] = vec.y; mat.data[14] = vec.z; return mat; }
/** * Creates a brand new translation matrix at the specified 4d point * @param vec * */ public static translation4d(vec: Vector4): Matrix { const mat = Matrix.identity(); mat.data[12] = vec.x; mat.data[13] = vec.y; mat.data[14] = vec.z; mat.data[15] = vec.w; return mat; }
/** * Creates a brand new scaling matrix with the specified scaling factor * @param sx * @param sy */ public static scale(sx: number, sy: number): Matrix { const mat = Matrix.identity(); mat.data[0] = sx; mat.data[5] = sy; mat.data[10] = 1; mat.data[15] = 1; return mat; }
/** * Creates a brand new rotation matrix with the specified angle in radians * @param angle */ public static rotation(angle: number): Matrix { const mat = Matrix.identity(); mat.data[0] = Math.cos(angle); mat.data[4] = -Math.sin(angle); mat.data[1] = Math.sin(angle); mat.data[5] = Math.cos(angle); return mat; }
/** * Multiply the current matrix by a vector producing a new vector * @param vector * @param dest */ multiply(vector: Vector, dest?: Vector): Vector; /** * Multiply the current matrix by another matrix producing a new matrix * @param matrix * @param dest */ multiply(matrix: Matrix, dest?: Matrix): Matrix; /** * Multiply the current matrix by a 4D vector producing a new 4D vector * @param vector * @param dest */ multiply(vector: Vector4, dest?: Vector4): Vector4; /** * Multiply the current matrix by a 3D vector producing a new 3D vector * @param vector * @param dest */ multiply(vector: Vector3, dest?: Vector3): Vector3; multiply( vectorOrMatrix: Vector | Vector3 | Vector4 | Matrix, dest?: Vector | Vector3 | Vector4 | Matrix ): Vector | Vector3 | Vector4 | Matrix { if (vectorOrMatrix instanceof Vector) { const result = (dest as Vector) || new Vector(0, 0); const vector = vectorOrMatrix; // these shenanigans are to allow dest and vector to be the same instance const resultX = vector.x * this.data[0] + vector.y * this.data[4] + this.data[12]; const resultY = vector.x * this.data[1] + vector.y * this.data[5] + this.data[13];
result.x = resultX; result.y = resultY; return result; } else if (vectorOrMatrix instanceof Vector3) { const result = (dest as Vector3) || vec3(0, 0, 0); const vector = vectorOrMatrix; // Treats Vector3 as a 3D point (w = 1.0) const resultX = vector.x * this.data[0] + vector.y * this.data[4] + vector.z * this.data[8] + this.data[12]; const resultY = vector.x * this.data[1] + vector.y * this.data[5] + vector.z * this.data[9] + this.data[13]; const resultZ = vector.x * this.data[2] + vector.y * this.data[6] + vector.z * this.data[10] + this.data[14];
result.x = resultX; result.y = resultY; result.z = resultZ; return result; } else if (vectorOrMatrix instanceof Vector4) { const result = (dest as Vector4) || vec4(0, 0, 0, 0); const vector = vectorOrMatrix; const resultX = vector.x * this.data[0] + vector.y * this.data[4] + vector.z * this.data[8] + vector.w * this.data[12]; const resultY = vector.x * this.data[1] + vector.y * this.data[5] + vector.z * this.data[9] + vector.w * this.data[13]; const resultZ = vector.x * this.data[2] + vector.y * this.data[6] + vector.z * this.data[10] + vector.w * this.data[14]; const resultW = vector.x * this.data[3] + vector.y * this.data[7] + vector.z * this.data[11] + vector.w * this.data[15];
result.x = resultX; result.y = resultY; result.z = resultZ; result.w = resultW; return result; } else { // Matrix const result = (dest as Matrix) || new Matrix(); const other = vectorOrMatrix; const a11 = this.data[0]; const a21 = this.data[1]; const a31 = this.data[2]; const a41 = this.data[3];
const a12 = this.data[4]; const a22 = this.data[5]; const a32 = this.data[6]; const a42 = this.data[7];
const a13 = this.data[8]; const a23 = this.data[9]; const a33 = this.data[10]; const a43 = this.data[11];
const a14 = this.data[12]; const a24 = this.data[13]; const a34 = this.data[14]; const a44 = this.data[15];
const b11 = other.data[0]; const b21 = other.data[1]; const b31 = other.data[2]; const b41 = other.data[3];
const b12 = other.data[4]; const b22 = other.data[5]; const b32 = other.data[6]; const b42 = other.data[7];
const b13 = other.data[8]; const b23 = other.data[9]; const b33 = other.data[10]; const b43 = other.data[11];
const b14 = other.data[12]; const b24 = other.data[13]; const b34 = other.data[14]; const b44 = other.data[15];
result.data[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; result.data[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; result.data[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; result.data[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
result.data[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; result.data[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; result.data[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; result.data[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
result.data[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; result.data[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; result.data[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; result.data[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
result.data[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; result.data[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; result.data[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; result.data[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
const s = this.getScale(); result._scaleSignX = sign(s.x) * sign(result._scaleSignX); result._scaleSignY = sign(s.y) * sign(result._scaleSignY);
return result; } }
/** * Applies translation to the current matrix mutating it * @param x * @param y */ translate(x: number, y: number) { const a11 = this.data[0]; const a21 = this.data[1]; const a31 = this.data[2]; const a41 = this.data[3];
const a12 = this.data[4]; const a22 = this.data[5]; const a32 = this.data[6]; const a42 = this.data[7];
const a13 = this.data[8]; const a23 = this.data[9]; const a33 = this.data[10]; const a43 = this.data[11];
const a14 = this.data[12]; const a24 = this.data[13]; const a34 = this.data[14]; const a44 = this.data[15];
// Doesn't change z const z = 0; const w = 1; this.data[12] = a11 * x + a12 * y + a13 * z + a14 * w; this.data[13] = a21 * x + a22 * y + a23 * z + a24 * w; this.data[14] = a31 * x + a32 * y + a33 * z + a34 * w; this.data[15] = a41 * x + a42 * y + a43 * z + a44 * w;
return this; }
public setPosition(x: number, y: number) { this.data[12] = x; this.data[13] = y; }
public getPosition(): Vector { return vec(this.data[12], this.data[13]); }
/** * Applies rotation to the current matrix mutating it * @param angle in Radians */ rotate(angle: number) { const a11 = this.data[0]; const a21 = this.data[1]; const a31 = this.data[2]; const a41 = this.data[3];
const a12 = this.data[4]; const a22 = this.data[5]; const a32 = this.data[6]; const a42 = this.data[7];
const sine = Math.sin(angle); const cosine = Math.cos(angle);
this.data[0] = cosine * a11 + sine * a12; this.data[1] = cosine * a21 + sine * a22; this.data[2] = cosine * a31 + sine * a32; this.data[3] = cosine * a41 + sine * a42;
this.data[4] = cosine * a12 - sine * a11; this.data[5] = cosine * a22 - sine * a21; this.data[6] = cosine * a32 - sine * a31; this.data[7] = cosine * a42 - sine * a41;
return this; }
/** * Applies scaling to the current matrix mutating it * @param x * @param y */ scale(x: number, y: number) { const a11 = this.data[0]; const a21 = this.data[1]; const a31 = this.data[2]; const a41 = this.data[3];
const a12 = this.data[4]; const a22 = this.data[5]; const a32 = this.data[6]; const a42 = this.data[7];
this.data[0] = a11 * x; this.data[1] = a21 * x; this.data[2] = a31 * x; this.data[3] = a41 * x;
this.data[4] = a12 * y; this.data[5] = a22 * y; this.data[6] = a32 * y; this.data[7] = a42 * y;
return this; }
public setRotation(angle: number) { const currentScale = this.getScale(); const sine = Math.sin(angle); const cosine = Math.cos(angle);
this.data[0] = cosine * currentScale.x; this.data[1] = sine * currentScale.x; this.data[4] = -sine * currentScale.y; this.data[5] = cosine * currentScale.y; }
public getRotation(): number { const angle = Math.atan2(this.data[1] / this.getScaleX(), this.data[0] / this.getScaleX()); return canonicalizeAngle(angle); }
public getScaleX(): number { const xscale = vec(this.data[0], this.data[1]).magnitude; return this._scaleSignX * xscale; }
public getScaleY(): number { const yscale = vec(this.data[4], this.data[5]).magnitude; return this._scaleSignY * yscale; }
/** * Get the scale of the matrix */ public getScale(): Vector { return vec(this.getScaleX(), this.getScaleY()); }
private _scaleX = 1; private _scaleSignX = 1; public setScaleX(val: number) { if (this._scaleX === val) { return; }
this._scaleSignX = sign(val); const xscale = vec(this.data[0] * this._scaleSignX, this.data[1] * this._scaleSignX).normalize(); this.data[0] = xscale.x * val; this.data[1] = xscale.y * val; this._scaleX = val; }
private _scaleY = 1; private _scaleSignY = 1; public setScaleY(val: number) { if (this._scaleY === val) { return; } this._scaleSignY = sign(val); const yscale = vec(this.data[4] * this._scaleSignY, this.data[5] * this._scaleSignY).normalize(); this.data[4] = yscale.x * val; this.data[5] = yscale.y * val; this._scaleY = val; }
public setScale(scale: Vector) { this.setScaleX(scale.x); this.setScaleY(scale.y); }
/** * Determinant of the upper left 2x2 matrix */ public getBasisDeterminant() { return this.data[0] * this.data[5] - this.data[1] * this.data[4]; }
/** * Return the affine inverse, optionally store it in a target matrix. * * It's recommended you call .reset() the target unless you know what you're doing * @param target */ public getAffineInverse(target?: Matrix): Matrix { // See http://negativeprobability.blogspot.com/2011/11/affine-transformations-and-their.html // See https://www.mathsisfun.com/algebra/matrix-inverse.html // Since we are actually only doing 2D transformations we can use this hack // We don't actually use the 3rd or 4th dimension
const det = this.getBasisDeterminant(); const inverseDet = 1 / det; // todo zero check const a = this.data[0]; const b = this.data[4]; const c = this.data[1]; const d = this.data[5];
const m = target || Matrix.identity(); // inverts rotation and scale m.data[0] = d * inverseDet; m.data[1] = -c * inverseDet; m.data[4] = -b * inverseDet; m.data[5] = a * inverseDet;
const tx = this.data[12]; const ty = this.data[13]; // invert translation // transform translation into the matrix basis created by rot/scale m.data[12] = -(tx * m.data[0] + ty * m.data[4]); m.data[13] = -(tx * m.data[1] + ty * m.data[5]);
return m; }
public isIdentity(): boolean { return ( this.data[0] === 1 && this.data[1] === 0 && this.data[2] === 0 && this.data[3] === 0 && this.data[4] === 0 && this.data[5] === 1 && this.data[6] === 0 && this.data[7] === 0 && this.data[8] === 0 && this.data[9] === 0 && this.data[10] === 1 && this.data[11] === 0 && this.data[12] === 0 && this.data[13] === 0 && this.data[14] === 0 && this.data[15] === 1 ); }
public toString() { return `[${this.data[0]} ${this.data[4]} ${this.data[8]} ${this.data[12]}][${this.data[1]} ${this.data[5]} ${this.data[9]} ${this.data[13]}][${this.data[2]} ${this.data[6]} ${this.data[10]} ${this.data[14]}][${this.data[3]} ${this.data[7]} ${this.data[11]} ${this.data[15]}]`; }}