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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317import { AffineMatrix } from './affine-matrix';import { canonicalizeAngle, sign } from './util';import type { Vector } from './vector';import { vec } from './vector';import { VectorView } from './vector-view';import { WatchVector } from './watch-vector';
export class Transform { private _parent: Transform | null = null; get parent() { return this._parent; } set parent(transform: Transform | null) { if (this._parent) { const index = this._parent._children.indexOf(this); if (index > -1) { this._parent._children.splice(index, 1); } } this._parent = transform; if (this._parent) { this._parent._children.push(this); } this.flagDirty(); } get children(): readonly Transform[] { return this._children; } private _children: Transform[] = [];
private _pos: Vector = new WatchVector(vec(0, 0), () => { this.flagDirty(); }); set pos(v: Vector) { this._pos.x = v.x; this._pos.y = v.y; } get pos() { return this._pos; }
set globalPos(v: Vector) { let localPos = v.clone(); if (this.parent) { localPos = this.parent.inverse.multiply(v); } if (!localPos.equals(this._pos)) { this._pos = localPos; this.flagDirty(); } } private _globalPos = new VectorView({ getX: () => this.matrix.data[4], getY: () => this.matrix.data[5], setX: (x) => { if (this.parent) { const { x: newX } = this.parent.inverse.multiply(vec(x, this.pos.y)); this.pos.x = newX; } else { this.pos.x = x; } if (x !== this.matrix.data[4]) { this.flagDirty(); } }, setY: (y) => { if (this.parent) { const { y: newY } = this.parent.inverse.multiply(vec(this.pos.x, y)); this.pos.y = newY; } else { this.pos.y = y; } if (y !== this.matrix.data[5]) { this.flagDirty(); } } }); get globalPos() { return this._globalPos; }
private _rotation: number = 0; set rotation(rotation: number) { const canonRotation = canonicalizeAngle(rotation); if (canonRotation !== this._rotation) { this.flagDirty(); } this._rotation = canonRotation; } get rotation() { return this._rotation; }
set globalRotation(rotation: number) { let inverseRotation = 0; if (this.parent) { inverseRotation = this.parent.globalRotation; } const canonRotation = canonicalizeAngle(rotation + inverseRotation); if (canonRotation !== this._rotation) { this.flagDirty(); } this._rotation = canonRotation; }
get globalRotation() { if (this.parent) { return this.matrix.getRotation(); } return this.rotation; }
private _scale: Vector = new WatchVector(vec(1, 1), () => { this.flagDirty(); }); set scale(v: Vector) { this._scale.x = v.x; this._scale.y = v.y; } get scale() { return this._scale; }
set globalScale(v: Vector) { let inverseScale = vec(1, 1); if (this.parent) { inverseScale = this.parent.globalScale; } this.scale = v.scale(vec(1 / inverseScale.x, 1 / inverseScale.y)); }
private _globalScale = new VectorView({ getX: () => (this.parent ? this.matrix.getScaleX() : this.scale.x), getY: () => (this.parent ? this.matrix.getScaleY() : this.scale.y), setX: (x) => { if (this.parent) { const globalScaleX = this.parent.globalScale.x; this.scale.x = x / globalScaleX; } else { this.scale.x = x; } }, setY: (y) => { if (this.parent) { const globalScaleY = this.parent.globalScale.y; this.scale.y = y / globalScaleY; } else { this.scale.y = y; } } }); get globalScale() { return this._globalScale; }
private _z: number = 0;
set z(z: number) { this._z = z; this.flagDirty(); }
get z() { return this._z; }
set globalZ(z: number) { if (this.parent) { this.z = z - this.parent.globalZ; } else { this.z = z; } }
get globalZ() { if (this.parent) { return this.z + this.parent.globalZ; } return this.z; }
private _isDirty = false; private _isInverseDirty = false; private _matrix = AffineMatrix.identity(); private _inverse = AffineMatrix.identity();
/** * Calculates and returns the matrix representation of this transform * * Avoid mutating the matrix to update the transform, it is not the source of truth. * Update the transform pos, rotation, scale. */ public get matrix(): AffineMatrix { if (this._isDirty) { if (this.parent === null) { this._calculateMatrix().clone(this._matrix); } else { this.parent.matrix.multiply(this._calculateMatrix()).clone(this._matrix); } this._isDirty = false; } return this._matrix; }
/** * Calculates and returns the inverse matrix representation of this transform */ public get inverse(): AffineMatrix { if (this._isInverseDirty) { this.matrix.inverse(this._inverse); this._isInverseDirty = false; } return this._inverse; }
private _scratch = AffineMatrix.identity(); // sin/cos of the last rotation the matrix was built with, position only changes (the common case in the // collision position solver) rebuild the matrix without touching trig private _trigRotation = NaN; private _cos = 1; private _sin = 0;
private _calculateMatrix(): AffineMatrix { if (this._rotation !== this._trigRotation) { this._trigRotation = this._rotation; this._cos = Math.cos(this._rotation); this._sin = Math.sin(this._rotation); } this._scratch.data[0] = this._cos; this._scratch.data[1] = this._sin; this._scratch.data[2] = -this._sin; this._scratch.data[3] = this._cos; this._scratch.data[4] = this._pos.x; this._scratch.data[5] = this._pos.y; this._scratch.scale(this._scale.x, this._scale.y); return this._scratch; }
private _version = 0; /** * Increments every time this transform (or one of its parents) changes, cheap change detection for caches built * from this transform */ public get version(): number { return this._version; }
public flagDirty() { this._version++; this._isDirty = true; this._isInverseDirty = true; for (let i = 0; i < this._children.length; i++) { this._children[i].flagDirty(); } }
public apply(point: Vector): Vector { return this.matrix.multiply(point); }
public applyInverse(point: Vector): Vector { return this.inverse.multiply(point); }
public setTransform(pos: Vector, rotation: number, scale: Vector) { this._pos.x = pos.x; this._pos.y = pos.y; this._rotation = canonicalizeAngle(rotation); this._scale.x = scale.x; this._scale.y = scale.y; this.flagDirty(); }
/** * Returns true if the transform has a negative x scale or y scale, but not both */ public isMirrored(): boolean { const signBitX = sign(this.scale.x) >>> 31; const signBitY = sign(this.scale.y) >>> 31; const mirrored = signBitX ^ signBitY; return !!mirrored; }
/** * Clones the current transform * **Warning does not clone the parent** * @param dest */ public clone(dest?: Transform): Transform { const target = dest ?? new Transform(); this._pos.clone(target._pos); target._z = this._z; target._rotation = this._rotation; this._scale.clone(target._scale); target.flagDirty(); return target; }
/** * Clones but keeps the same parent reference */ public cloneWithParent(dest?: Transform): Transform { const target = dest ?? new Transform(); this._pos.clone(target._pos); target._z = this._z; target._rotation = this._rotation; this._scale.clone(target._scale); target.parent = this.parent; target.flagDirty(); return target; }
public toString(): string { return this.matrix.toString(); }}