diff --git a/packages/melonjs/src/camera/camera2d.js b/packages/melonjs/src/camera/camera2d.js index e4383dc9b9..8284e2e550 100644 --- a/packages/melonjs/src/camera/camera2d.js +++ b/packages/melonjs/src/camera/camera2d.js @@ -1,7 +1,5 @@ import Vector2d from "./../math/vector2.js"; import Vector3d from "./../math/vector3.js"; -import ObservableVector2d from "./../math/observable_vector2.js"; -import ObservableVector3d from "./../math/observable_vector3.js"; import Matrix2d from "./../math/matrix2.js"; import Matrix3d from "./../math/matrix3.js"; import Rect from "./../geometries/rectangle.js"; @@ -322,12 +320,7 @@ export default class Camera2d extends Renderable { follow(target, axis, damping) { if (target instanceof Renderable) { this.target = target.pos; - } else if ( - target instanceof Vector2d || - target instanceof Vector3d || - target instanceof ObservableVector2d || - target instanceof ObservableVector3d - ) { + } else if (target instanceof Vector2d || target instanceof Vector3d) { this.target = target; } else { throw new Error("invalid target for me.Camera2d.follow"); diff --git a/packages/melonjs/src/geometries/ellipse.js b/packages/melonjs/src/geometries/ellipse.js index 65efa2687b..7a31359533 100644 --- a/packages/melonjs/src/geometries/ellipse.js +++ b/packages/melonjs/src/geometries/ellipse.js @@ -2,7 +2,7 @@ import pool from "./../system/pooling.js"; /** * additional import for TypeScript - * @import Vector2d from "./../math/vector2.js"; + * @import Vector3d from "./../math/vector3.js"; * @import Matrix2d from "./../math/matrix2.js"; * @import Bounds from "./../physics/bounds.js"; */ @@ -21,9 +21,9 @@ export default class Ellipse { /** * the center coordinates of the ellipse * @public - * @type {Vector2d} + * @type {Vector3d} */ - this.pos = pool.pull("Vector2d"); + this.pos = pool.pull("Vector3d"); /** * The bounding rectangle for this shape diff --git a/packages/melonjs/src/geometries/poly.js b/packages/melonjs/src/geometries/poly.js index 6af8fead0e..cdbe7c3749 100644 --- a/packages/melonjs/src/geometries/poly.js +++ b/packages/melonjs/src/geometries/poly.js @@ -3,7 +3,7 @@ import { earcut } from "./earcut.js"; /** * additional import for TypeScript - * @import Vector2d from "./../math/vector2.js"; + * @import Vector3d from "./../math/vector3.js"; * @import Matrix2d from "./../math/matrix2.js"; * @import Bounds from "./../physics/bounds.js"; */ @@ -25,9 +25,27 @@ export default class Polygon { constructor(x = 0, y = 0, points) { /** * origin point of the Polygon - * @type {Vector2d} + * @type {Vector3} */ - this.pos = pool.pull("Vector2d"); + this.pos = pool.pull("Vector3d"); + /** + * Proxy to the pos Vector3d object + * @ignore + */ + this.posProxy = new Proxy(this.pos, { + set: (obj, prop, value) => { + // only update bounds if x or y has changed (ignore z) + if (prop !== "z") { + const newX = prop === "x" ? value : obj.x; + const newY = prop === "y" ? value : obj.y; + this.getBounds().translate(newX - obj.x, newY - obj.y); + } + + obj[prop] = value; + + return true; + }, + }); /** * Array of points defining the Polygon
@@ -86,8 +104,8 @@ export default class Polygon { * @returns {Polygon} this instance for objecf chaining */ setShape(x, y, points) { - this.pos.set(x, y); this.setVertices(points); + this.pos.set(x, y); return this; } @@ -334,7 +352,6 @@ export default class Polygon { this.pos.x += _x; this.pos.y += _y; - this.getBounds().translate(_x, _y); return this; } @@ -362,7 +379,6 @@ export default class Polygon { } this.pos.x = _x; this.pos.y = _y; - this.updateBounds(); } /** diff --git a/packages/melonjs/src/index.js b/packages/melonjs/src/index.js index ee35bfeac5..ef8e9fb738 100644 --- a/packages/melonjs/src/index.js +++ b/packages/melonjs/src/index.js @@ -5,8 +5,6 @@ import "./polyfill/index.ts"; import Color from "./math/color.js"; import Vector2d from "./math/vector2.js"; import Vector3d from "./math/vector3.js"; -import ObservableVector2d from "./math/observable_vector2.js"; -import ObservableVector3d from "./math/observable_vector3.js"; import Matrix2d from "./math/matrix2.js"; import Matrix3d from "./math/matrix3.js"; import Polygon from "./geometries/poly.js"; @@ -101,8 +99,6 @@ export { Color, Vector2d, Vector3d, - ObservableVector2d, - ObservableVector3d, Matrix2d, Matrix3d, Polygon, @@ -228,8 +224,6 @@ export function boot() { pool.register("me.ColorLayer", ColorLayer, true); pool.register("me.Vector2d", Vector2d, true); pool.register("me.Vector3d", Vector3d, true); - pool.register("me.ObservableVector2d", ObservableVector2d, true); - pool.register("me.ObservableVector3d", ObservableVector3d, true); pool.register("me.Matrix2d", Matrix2d, true); pool.register("me.Matrix3d", Matrix3d, true); pool.register("me.Rect", Rect, true); @@ -258,8 +252,6 @@ export function boot() { pool.register("ColorLayer", ColorLayer, true); pool.register("Vector2d", Vector2d, true); pool.register("Vector3d", Vector3d, true); - pool.register("ObservableVector2d", ObservableVector2d, true); - pool.register("ObservableVector3d", ObservableVector3d, true); pool.register("Matrix2d", Matrix2d, true); pool.register("Matrix3d", Matrix3d, true); pool.register("Rect", Rect, true); diff --git a/packages/melonjs/src/level/tiled/TMXTileMap.js b/packages/melonjs/src/level/tiled/TMXTileMap.js index 682d6c0b29..809874450c 100644 --- a/packages/melonjs/src/level/tiled/TMXTileMap.js +++ b/packages/melonjs/src/level/tiled/TMXTileMap.js @@ -493,7 +493,7 @@ export default class TMXTileMap { obj.body = new Body(obj, shape); obj.body.setStatic(true); // set the obj z order - obj.pos.setMuted(settings.x, settings.y, settings.z); + obj.pos.set(settings.x, settings.y, settings.z); } else { // pull the corresponding object from the object pool if (typeof settings.name !== "undefined" && settings.name !== "") { diff --git a/packages/melonjs/src/math/observable_vector2.js b/packages/melonjs/src/math/observable_vector2.js deleted file mode 100644 index 7e3e6f9926..0000000000 --- a/packages/melonjs/src/math/observable_vector2.js +++ /dev/null @@ -1,425 +0,0 @@ -import pool from "./../system/pooling.js"; -import Vector2d from "./vector2.js"; -import { clamp } from "./math.ts"; - -/** - * A Vector2d object that provide notification by executing the given callback when the vector is changed. - */ -export default class ObservableVector2d extends Vector2d { - /** - * @param {number} x - x value of the vector - * @param {number} y - y value of the vector - * @param {object} settings - additional required parameters - * @param {Function} settings.onUpdate - the callback to be executed when the vector is changed - * @param {Function} [settings.scope] - the value to use as this when calling onUpdate - */ - constructor(x = 0, y = 0, settings) { - super(x, y); - if (typeof settings === "undefined") { - throw new Error("undefined `onUpdate` callback"); - } - this.setCallback(settings.onUpdate, settings.scope); - } - - /** - * @ignore - */ - onResetEvent(x = 0, y = 0, settings) { - // init is call by the constructor and does not trigger the cb - this.setMuted(x, y); - if (typeof settings !== "undefined") { - this.setCallback(settings.onUpdate, settings.scope); - } - } - - /** - * x value of the vector - * @type {number} - */ - - get x() { - return this._x; - } - - set x(value) { - const ret = this.onUpdate.call( - this.scope, - value, - this._y, - this._x, - this._y, - ); - if (ret && "x" in ret) { - this._x = ret.x; - } else { - this._x = value; - } - } - - /** - * y value of the vector - * @type {number} - */ - - get y() { - return this._y; - } - - set y(value) { - const ret = this.onUpdate.call( - this.scope, - this._x, - value, - this._x, - this._y, - ); - if (ret && "y" in ret) { - this._y = ret.y; - } else { - this._y = value; - } - } - - /** @ignore */ - _set(x, y) { - const ret = this.onUpdate.call(this.scope, x, y, this._x, this._y); - if (ret && "x" in ret && "y" in ret) { - this._x = ret.x; - this._y = ret.y; - } else { - this._x = x; - this._y = y; - } - return this; - } - - /** - * set the vector value without triggering the callback - * @param {number} x - x value of the vector - * @param {number} y - y value of the vector - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - setMuted(x, y) { - this._x = x; - this._y = y; - return this; - } - - /** - * set the callback to be executed when the vector is changed - * @param {Function} fn - callback - * @param {Function} [scope=null] - scope - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - setCallback(fn, scope = null) { - if (typeof fn !== "function") { - throw new Error("invalid `onUpdate` callback"); - } - this.onUpdate = fn; - this.scope = scope; - return this; - } - - /** - * Add the passed vector to this vector - * @param {ObservableVector2d} v - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - add(v) { - return this._set(this._x + v.x, this._y + v.y); - } - - /** - * Substract the passed vector to this vector - * @param {ObservableVector2d} v - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - sub(v) { - return this._set(this._x - v.x, this._y - v.y); - } - - /** - * Multiply this vector values by the given scalar - * @param {number} x - * @param {number} [y=x] - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - scale(x, y = x) { - return this._set(this._x * x, this._y * y); - } - - /** - * Multiply this vector values by the passed vector - * @param {ObservableVector2d} v - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - scaleV(v) { - return this._set(this._x * v.x, this._y * v.y); - } - - /** - * Divide this vector values by the passed value - * @param {number} n - the value to divide the vector by - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - div(n) { - return this._set(this._x / n, this._y / n); - } - - /** - * Update this vector values to absolute values - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - abs() { - return this._set( - this._x < 0 ? -this._x : this._x, - this._y < 0 ? -this._y : this._y, - ); - } - - /** - * Clamp the vector value within the specified value range - * @param {number} low - * @param {number} high - * @returns {ObservableVector2d} new me.ObservableVector2d - */ - clamp(low, high) { - return new ObservableVector2d( - clamp(this.x, low, high), - clamp(this.y, low, high), - { onUpdate: this.onUpdate, scope: this.scope }, - ); - } - - /** - * Clamp this vector value within the specified value range - * @param {number} low - * @param {number} high - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - clampSelf(low, high) { - return this._set(clamp(this._x, low, high), clamp(this._y, low, high)); - } - - /** - * Update this vector with the minimum value between this and the passed vector - * @param {ObservableVector2d} v - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - minV(v) { - return this._set( - this._x < v.x ? this._x : v.x, - this._y < v.y ? this._y : v.y, - ); - } - - /** - * Update this vector with the maximum value between this and the passed vector - * @param {ObservableVector2d} v - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - maxV(v) { - return this._set( - this._x > v.x ? this._x : v.x, - this._y > v.y ? this._y : v.y, - ); - } - - /** - * Floor the vector values - * @returns {ObservableVector2d} new me.ObservableVector2d - */ - floor() { - return new ObservableVector2d(Math.floor(this._x), Math.floor(this._y), { - onUpdate: this.onUpdate, - scope: this.scope, - }); - } - - /** - * Floor this vector values - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - floorSelf() { - return this._set(Math.floor(this._x), Math.floor(this._y)); - } - - /** - * Ceil the vector values - * @returns {ObservableVector2d} new me.ObservableVector2d - */ - ceil() { - return new ObservableVector2d(Math.ceil(this._x), Math.ceil(this._y), { - onUpdate: this.onUpdate, - scope: this.scope, - }); - } - - /** - * Ceil this vector values - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - ceilSelf() { - return this._set(Math.ceil(this._x), Math.ceil(this._y)); - } - - /** - * Negate the vector values - * @returns {ObservableVector2d} new me.ObservableVector2d - */ - negate() { - return new ObservableVector2d(-this._x, -this._y, { - onUpdate: this.onUpdate, - scope: this.scope, - }); - } - - /** - * Negate this vector values - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - negateSelf() { - return this._set(-this._x, -this._y); - } - - /** - * Copy the x,y values of the passed vector to this one - * @param {ObservableVector2d} v - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - copy(v) { - return this._set(v.x, v.y); - } - - /** - * return true if the two vectors are the same - * @param {ObservableVector2d} v - * @returns {boolean} - */ - equals(v) { - return this._x === v.x && this._y === v.y; - } - - /** - * change this vector to be perpendicular to what it was before.
- * (Effectively rotates it 90 degrees in a clockwise direction) - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - perp() { - return this._set(this._y, -this._x); - } - - /** - * Rotate this vector (counter-clockwise) by the specified angle (in radians). - * @param {number} angle - The angle to rotate (in radians) - * @param {Vector2d|ObservableVector2d} [v] - an optional point to rotate around - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - rotate(angle, v) { - let cx = 0; - let cy = 0; - - if (typeof v === "object") { - cx = v.x; - cy = v.y; - } - - const x = this._x - cx; - const y = this._y - cy; - - const c = Math.cos(angle); - const s = Math.sin(angle); - - return this._set(x * c - y * s + cx, x * s + y * c + cy); - } - - /** - * return the dot product of this vector and the passed one - * @param {Vector2d|ObservableVector2d} v - * @returns {number} The dot product. - */ - dot(v) { - return this._x * v.x + this._y * v.y; - } - - /** - * return the cross product of this vector and the passed one - * @param {Vector2d|ObservableVector2d} v - * @returns {number} The cross product. - */ - cross(v) { - return this._x * v.y - this._y * v.x; - } - - /** - * Linearly interpolate between this vector and the given one. - * @param {Vector2d|ObservableVector2d} v - * @param {number} alpha - distance along the line (alpha = 0 will be this vector, and alpha = 1 will be the given one). - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - lerp(v, alpha) { - return this._set( - this._x + (v.x - this._x) * alpha, - this._y + (v.y - this._y) * alpha, - ); - } - - /** - * interpolate the position of this vector towards the given one while nsure that the distance never exceeds the given step. - * @param {Vector2d|ObservableVector2d} target - * @param {number} step - the maximum step per iteration (Negative values will push the vector away from the target) - * @returns {ObservableVector2d} Reference to this object for method chaining - */ - moveTowards(target, step) { - const angle = Math.atan2(target.y - this._y, target.x - this._x); - - const distance = this.distance(target); - - if (distance === 0 || (step >= 0 && distance <= step * step)) { - return target; - } - - this._x += Math.cos(angle) * step; - this._y += Math.sin(angle) * step; - - return this; - } - - /** - * return the distance between this vector and the passed one - * @param {ObservableVector2d} v - * @returns {number} - */ - distance(v) { - return Math.sqrt( - (this._x - v.x) * (this._x - v.x) + (this._y - v.y) * (this._y - v.y), - ); - } - - /** - * return a clone copy of this vector - * @returns {ObservableVector2d} new me.ObservableVector2d - */ - clone() { - return pool.pull("ObservableVector2d", this._x, this._y, { - onUpdate: this.onUpdate, - scope: this.scope, - }); - } - - /** - * return a `me.Vector2d` copy of this `me.ObservableVector2d` object - * @returns {Vector2d} new me.Vector2d - */ - toVector2d() { - return pool.pull("Vector2d", this._x, this._y); - } - - /** - * convert the object to a string representation - * @returns {string} - */ - toString() { - return "x:" + this._x + ",y:" + this._y; - } -} diff --git a/packages/melonjs/src/math/observable_vector3.js b/packages/melonjs/src/math/observable_vector3.js deleted file mode 100644 index 0cccfd7f65..0000000000 --- a/packages/melonjs/src/math/observable_vector3.js +++ /dev/null @@ -1,513 +0,0 @@ -import Vector3d from "./vector3.js"; -import { clamp } from "./math.ts"; -import pool from "./../system/pooling.js"; - -/** - * additional import for TypeScript - * @import ObservableVector2d from "./observable_vector2.js"; - * @import Vector2d from "./vector2.js"; - */ - -/** - * A Vector3d object that provide notification by executing the given callback when the vector is changed. - */ -export default class ObservableVector3d extends Vector3d { - /** - * @param {number} x - x value of the vector - * @param {number} y - y value of the vector - * @param {number} z - z value of the vector - * @param {object} settings - additional required parameters - * @param {Function} settings.onUpdate - the callback to be executed when the vector is changed - * @param {object} [settings.scope] - the value to use as this when calling onUpdate - */ - constructor(x = 0, y = 0, z = 0, settings) { - super(x, y, z); - if (typeof settings === "undefined") { - throw new Error("undefined `onUpdate` callback"); - } - this.setCallback(settings.onUpdate, settings.scope); - } - - /** - * @ignore - */ - onResetEvent(x = 0, y = 0, z = 0, settings) { - // init is call by the constructor and does not trigger the cb - this.setMuted(x, y, z); - if (typeof settings !== "undefined") { - this.setCallback(settings.onUpdate, settings.scope); - } - return this; - } - - /** - * x value of the vector - * @type {number} - */ - - get x() { - return this._x; - } - - set x(value) { - const ret = this.onUpdate.call( - this.scope, - value, - this._y, - this._z, - this._x, - this._y, - this._z, - ); - if (ret && "x" in ret) { - this._x = ret.x; - } else { - this._x = value; - } - } - - /** - * y value of the vector - * @type {number} - */ - - get y() { - return this._y; - } - - set y(value) { - const ret = this.onUpdate.call( - this.scope, - this._x, - value, - this._z, - this._x, - this._y, - this._z, - ); - if (ret && "y" in ret) { - this._y = ret.y; - } else { - this._y = value; - } - } - - /** - * z value of the vector - * @type {number} - */ - - get z() { - return this._z; - } - - set z(value) { - const ret = this.onUpdate.call( - this.scope, - this._x, - this._y, - value, - this._x, - this._y, - this._z, - ); - if (ret && "z" in ret) { - this._z = ret.z; - } else { - this._z = value; - } - } - - /** - * @ignore - */ - _set(x, y, z) { - const ret = this.onUpdate.call( - this.scope, - x, - y, - z, - this._x, - this._y, - this._z, - ); - if (ret && "x" in ret && "y" in ret && "z" in ret) { - this._x = ret.x; - this._y = ret.y; - this._z = ret.z; - } else { - this._x = x; - this._y = y; - this._z = z || 0; - } - return this; - } - - /** - * set the vector value without triggering the callback - * @param {number} x - x value of the vector - * @param {number} y - y value of the vector - * @param {number} [z=0] - z value of the vector - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - setMuted(x, y, z) { - this._x = x; - this._y = y; - this._z = z || 0; - return this; - } - - /** - * set the callback to be executed when the vector is changed - * @param {Function} fn - callback - * @param {Function} [scope=null] - scope - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - setCallback(fn, scope = null) { - if (typeof fn !== "function") { - throw new Error("invalid `onUpdate` callback"); - } - this.onUpdate = fn; - this.scope = scope; - return this; - } - - /** - * Add the passed vector to this vector - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - add(v) { - return this._set(this._x + v.x, this._y + v.y, this._z + (v.z || 0)); - } - - /** - * Substract the passed vector to this vector - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - sub(v) { - return this._set(this._x - v.x, this._y - v.y, this._z - (v.z || 0)); - } - - /** - * Multiply this vector values by the given scalar - * @param {number} x - * @param {number} [y=x] - * @param {number} [z=1] - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - scale(x, y = x, z = 1) { - return this._set(this._x * x, this._y * y, this._z * z); - } - - /** - * Multiply this vector values by the passed vector - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - scaleV(v) { - return this._set(this._x * v.x, this._y * v.y, this._z * (v.z || 1)); - } - - /** - * Divide this vector values by the passed value - * @param {number} n - the value to divide the vector by - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - div(n) { - return this._set(this._x / n, this._y / n, this._z / n); - } - - /** - * Update this vector values to absolute values - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - abs() { - return this._set( - this._x < 0 ? -this._x : this._x, - this._y < 0 ? -this._y : this._y, - this._Z < 0 ? -this._z : this._z, - ); - } - - /** - * Clamp the vector value within the specified value range - * @param {number} low - * @param {number} high - * @returns {ObservableVector3d} new me.ObservableVector3d - */ - clamp(low, high) { - return new ObservableVector3d( - clamp(this._x, low, high), - clamp(this._y, low, high), - clamp(this._z, low, high), - { onUpdate: this.onUpdate, scope: this.scope }, - ); - } - - /** - * Clamp this vector value within the specified value range - * @param {number} low - * @param {number} high - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - clampSelf(low, high) { - return this._set( - clamp(this._x, low, high), - clamp(this._y, low, high), - clamp(this._z, low, high), - ); - } - - /** - * Update this vector with the minimum value between this and the passed vector - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - minV(v) { - const _vz = v.z || 0; - return this._set( - this._x < v.x ? this._x : v.x, - this._y < v.y ? this._y : v.y, - this._z < _vz ? this._z : _vz, - ); - } - - /** - * Update this vector with the maximum value between this and the passed vector - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - maxV(v) { - const _vz = v.z || 0; - return this._set( - this._x > v.x ? this._x : v.x, - this._y > v.y ? this._y : v.y, - this._z > _vz ? this._z : _vz, - ); - } - - /** - * Floor the vector values - * @returns {ObservableVector3d} new me.ObservableVector3d - */ - floor() { - return new ObservableVector3d( - Math.floor(this._x), - Math.floor(this._y), - Math.floor(this._z), - { onUpdate: this.onUpdate, scope: this.scope }, - ); - } - - /** - * Floor this vector values - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - floorSelf() { - return this._set( - Math.floor(this._x), - Math.floor(this._y), - Math.floor(this._z), - ); - } - - /** - * Ceil the vector values - * @returns {ObservableVector3d} new me.ObservableVector3d - */ - ceil() { - return new ObservableVector3d( - Math.ceil(this._x), - Math.ceil(this._y), - Math.ceil(this._z), - { onUpdate: this.onUpdate, scope: this.scope }, - ); - } - - /** - * Ceil this vector values - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - ceilSelf() { - return this._set( - Math.ceil(this._x), - Math.ceil(this._y), - Math.ceil(this._z), - ); - } - - /** - * Negate the vector values - * @returns {ObservableVector3d} new me.ObservableVector3d - */ - negate() { - return new ObservableVector3d(-this._x, -this._y, -this._z, { - onUpdate: this.onUpdate, - scope: this.scope, - }); - } - - /** - * Negate this vector values - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - negateSelf() { - return this._set(-this._x, -this._y, -this._z); - } - - /** - * Copy the components of the given vector into this one - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - copy(v) { - return this._set(v.x, v.y, v.z || 0); - } - - /** - * return true if the two vectors are the same - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {boolean} - */ - equals(v) { - return this._x === v.x && this._y === v.y && this._z === (v.z || this._z); - } - - /** - * change this vector to be perpendicular to what it was before.
- * (Effectively rotates it 90 degrees in a clockwise direction) - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - perp() { - return this._set(this._y, -this._x, this._z); - } - - /** - * Rotate this vector (counter-clockwise) by the specified angle (in radians). - * @param {number} angle - The angle to rotate (in radians) - * @param {Vector2d|ObservableVector2d} [v] - an optional point to rotate around (on the same z axis) - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - rotate(angle, v) { - let cx = 0; - let cy = 0; - - if (typeof v === "object") { - cx = v.x; - cy = v.y; - } - - // TODO also rotate on the z axis if the given vector is a 3d one - const x = this.x - cx; - const y = this.y - cy; - - const c = Math.cos(angle); - const s = Math.sin(angle); - - return this._set(x * c - y * s + cx, x * s + y * c + cy, this.z); - } - - /** - * return the dot product of this vector and the passed one - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {number} The dot product. - */ - dot(v) { - return this._x * v.x + this._y * v.y + this._z * (v.z || 1); - } - - /** - * calculate the cross product of this vector and the passed one - * @param {Vector3d|ObservableVector3d} v - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - cross(v) { - const ax = this._x; - const ay = this._y; - const az = this._z; - const bx = v.x; - const by = v.y; - const bz = v.z; - - return this._set(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx); - } - - /** - * Linearly interpolate between this vector and the given one. - * @param {Vector3d|ObservableVector3d} v - * @param {number} alpha - distance along the line (alpha = 0 will be this vector, and alpha = 1 will be the given one). - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - lerp(v, alpha) { - return this._set( - this._x + (v.x - this._x) * alpha, - this._y + (v.y - this._y) * alpha, - this._z + (v.z - this._z) * alpha, - ); - } - - /** - * interpolate the position of this vector on the x and y axis towards the given one while ensure that the distance never exceeds the given step. - * @param {Vector2d|ObservableVector2d|Vector3d|ObservableVector3d} target - * @param {number} step - the maximum step per iteration (Negative values will push the vector away from the target) - * @returns {ObservableVector3d} Reference to this object for method chaining - */ - moveTowards(target, step) { - const angle = Math.atan2(target.y - this._y, target.x - this._x); - - const dx = this._x - target.x; - const dy = this._y - target.y; - - const distance = Math.sqrt(dx * dx + dy * dy); - - if (distance === 0 || (step >= 0 && distance <= step * step)) { - return target; - } - - return this._set( - this._x + Math.cos(angle) * step, - this._y + Math.sin(angle) * step, - this._z, - ); - } - - /** - * return the distance between this vector and the passed one - * @param {Vector2d|Vector3d|ObservableVector2d|ObservableVector3d} v - * @returns {number} - */ - distance(v) { - const dx = this._x - v.x; - const dy = this._y - v.y; - const dz = this._z - (v.z || 0); - return Math.sqrt(dx * dx + dy * dy + dz * dz); - } - - /** - * return a clone copy of this vector - * @returns {ObservableVector3d} new me.ObservableVector3d - */ - clone() { - return pool.pull("ObservableVector3d", this._x, this._y, this._z, { - onUpdate: this.onUpdate, - }); - } - - /** - * return a `me.Vector3d` copy of this `me.ObservableVector3d` object - * @returns {Vector3d} new me.Vector3d - */ - toVector3d() { - return pool.pull("Vector3d", this._x, this._y, this._z); - } - - /** - * convert the object to a string representation - * @returns {string} - */ - toString() { - return "x:" + this._x + ",y:" + this._y + ",z:" + this._z; - } -} diff --git a/packages/melonjs/src/renderable/entity/entity.js b/packages/melonjs/src/renderable/entity/entity.js index cab875396d..51f2adc7e2 100644 --- a/packages/melonjs/src/renderable/entity/entity.js +++ b/packages/melonjs/src/renderable/entity/entity.js @@ -53,10 +53,10 @@ export default class Entity extends Renderable { // Update anchorPoint if (settings.anchorPoint) { - this.anchorPoint.setMuted(settings.anchorPoint.x, settings.anchorPoint.y); + this.anchorPoint.set(settings.anchorPoint.x, settings.anchorPoint.y); } else { // for backward compatibility - this.anchorPoint.setMuted(0, 0); + this.anchorPoint.set(0, 0); } // set the sprite name if specified diff --git a/packages/melonjs/src/renderable/renderable.js b/packages/melonjs/src/renderable/renderable.js index f4bcca7660..3830074d99 100644 --- a/packages/melonjs/src/renderable/renderable.js +++ b/packages/melonjs/src/renderable/renderable.js @@ -1,5 +1,3 @@ -import ObservableVector2d from "./../math/observable_vector2.js"; -import ObservableVector3d from "./../math/observable_vector3.js"; import Rect from "./../geometries/rectangle.js"; import pool from "./../system/pooling.js"; import { releaseAllPointerEvents } from "./../input/input.js"; @@ -40,25 +38,7 @@ export default class Renderable extends Rect { // parent constructor super(x, y, width, height); - if (this.pos instanceof ObservableVector3d) { - this.pos.setMuted(x, y, 0).setCallback(this.updateBoundsPos, this); - } else { - /** - * Position of the Renderable relative to its parent container - * @public - * @type {ObservableVector3d} - */ - this.pos = pool.pull("ObservableVector3d", x, y, 0, { - onUpdate: this.updateBoundsPos, - scope: this, - }); - } - - if (this.anchorPoint instanceof ObservableVector2d) { - this.anchorPoint - .setMuted(0.5, 0.5) - .setCallback(this.onAnchorUpdate, this); - } else { + if (typeof this.anchorPoint === "undefined") { /** * The anchor point is used for attachment behavior, and/or when applying transformations.
* The coordinate system places the origin at the top left corner of the frame (0, 0) and (1, 1) means the bottom-right corner
@@ -67,13 +47,25 @@ export default class Renderable extends Rect { *
* Note: Object created through Tiled will have their anchorPoint set to (0, 0) to match Tiled Level editor implementation. * To specify a value through Tiled, use a json expression like `json:{"x":0.5,"y":0.5}`. - * @type {ObservableVector2d} + * @type {Vector2d} * @default <0.5,0.5> */ - this.anchorPoint = pool.pull("ObservableVector2d", 0.5, 0.5, { - onUpdate: this.onAnchorUpdate, - scope: this, + this.anchorPoint = pool.pull("Vector2d", 0.5, 0.5); + /** + * proxy to the anchorPoint vector + * @ignore + */ + this.anchorPointProxy = new Proxy(this.anchorPoint, { + set: (obj, prop, value) => { + // store the new value + obj[prop] = value; + this.updateBounds(); + this.isDirty = true; + return true; + }, }); + } else { + this.anchorPoint.set(0.5, 0.5); } if (typeof this.currentTransform === "undefined") { @@ -556,7 +548,7 @@ export default class Renderable extends Rect { /** * Rotate this renderable by the specified angle (in radians). * @param {number} angle - The angle to rotate (in radians) - * @param {Vector2d|ObservableVector2d} [v] - an optional point to rotate around + * @param {Vector2d} [v] - an optional point to rotate around * @returns {Renderable} Reference to this object for method chaining */ rotate(angle, v) { @@ -650,14 +642,6 @@ export default class Renderable extends Rect { } } - /** - * update the renderable's bounding rect (private) - * @ignore - */ - updateBoundsPos(newX = this.pos.x, newY = this.pos.y) { - this.getBounds().translate(newX - this.pos.x, newY - this.pos.y); - } - /** * return the renderable absolute position in the game world * @returns {Vector2d} @@ -678,21 +662,6 @@ export default class Renderable extends Rect { return this._absPos; } - /** - * called when the anchor point value is changed - * @private - * @param {number} x - the new X value to be set for the anchor - * @param {number} y - the new Y value to be set for the anchor - */ - onAnchorUpdate(x, y) { - // since the callback is called before setting the new value - // manually update the anchor point (required for updateBoundsPos) - this.anchorPoint.setMuted(x, y); - // then call updateBounds - this.updateBounds(); - this.isDirty = true; - } - /** * Prepare the rendering context before drawing (automatically called by melonJS). * This will apply any defined transforms, anchor point, tint or blend mode and translate the context accordingly to this renderable position. diff --git a/packages/melonjs/src/renderable/sprite.js b/packages/melonjs/src/renderable/sprite.js index 3562ab291f..f530f10369 100644 --- a/packages/melonjs/src/renderable/sprite.js +++ b/packages/melonjs/src/renderable/sprite.js @@ -549,7 +549,7 @@ export default class Sprite extends Renderable { this.height = this.current.height = region.height; // set global anchortPoint if defined if (region.anchorPoint) { - this.anchorPoint.setMuted( + this.anchorPoint.set( this._flip.x && region.trimmed === true ? 1 - region.anchorPoint.x : region.anchorPoint.x, diff --git a/packages/melonjs/tests/observableVect2d.spec.js b/packages/melonjs/tests/observableVect2d.spec.js deleted file mode 100644 index a0c54ed040..0000000000 --- a/packages/melonjs/tests/observableVect2d.spec.js +++ /dev/null @@ -1,227 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { ObservableVector2d, Vector2d, math } from "../src/index.js"; - -describe("ObservableVector2d", () => { - const x = 1; - const y = 2; - - let a; - let b; - let c; - - let _newX; - let _newY; - let _oldX; - let _oldY; - - const callback = function (newX, newY, oldX, oldY) { - // this will also validate the argument list - _newX = newX; - _newY = newY; - _oldX = oldX; - _oldY = oldY; - }; - - const callback_with_ret = function () { - return { - x: 10, - y: 10, - }; - }; - - it("should be initialized to a (0, 0) 2d vector", function () { - a = new ObservableVector2d(0, 0, { - onUpdate: callback.bind(this), - }); - b = new ObservableVector2d(0, 0, { - onUpdate: callback.bind(this), - }); - c = new ObservableVector2d(0, 0, { - onUpdate: callback.bind(this), - }); - - expect(a.toString()).toEqual("x:0,y:0"); - }); - - it("setting the vector triggers the callback", () => { - a.set(10, 100); - expect(a.x + a.y).toEqual(_newX + _newY); - }); - - it("callback returns a vector value", function () { - const d = new ObservableVector2d(0, 0, { - onUpdate: callback_with_ret.bind(this), - }); - d.set(100, 100); - expect(d.x + d.y).toEqual(20); // 10 + 10 - }); - - it("add a vector triggers the callback", () => { - a.add(new Vector2d(10, 10)); - expect(a.y).toEqual(_oldY + 10); - }); - - it("sub a vector triggers the callback", () => { - a.sub(new Vector2d(10, 10)); - expect(a.x).toEqual(_oldX - 10); - }); - - it("a(1, 2) should be copied into b", () => { - a.set(x, y); - b.copy(a); - - expect(b.equals(a)).toEqual(true); - }); - - it("scale (1, 2) by (-1, -2)", () => { - a.set(x, y); - b.set(-x, -y); - - expect(a.scaleV(b).toString()).toEqual("x:" + x * -x + ",y:" + y * -y); - }); - - it("negate (1, 2)", () => { - a.set(x, y); - - expect(a.negateSelf().toString()).toEqual("x:" + -x + ",y:" + -y); - }); - - it("dot Product (1, 2) and (-1, -2)", () => { - a.set(x, y); - b.set(-x, -y); - - // calculate the dot product - expect(a.dot(b)).toEqual(-x * x - y * y); - }); - - it("cross Product (2, 3) and (5, 6)", () => { - a.set(2, 3); - b.set(5, 6); - - // calculate the cross product - expect(a.cross(b)).toEqual(-3); - }); - - it("length/lengthSqrt functions", () => { - a.set(x, 0); - b.set(0, -y); - c.set(0, 0); - - expect(a.length()).toEqual(x); - expect(a.length2()).toEqual(x * x); - expect(b.length()).toEqual(y); - expect(b.length2()).toEqual(y * y); - expect(c.length()).toEqual(0); - expect(c.length2()).toEqual(0); - - a.set(x, y); - expect(a.length()).toEqual(Math.sqrt(x * x + y * y)); - expect(a.length2()).toEqual(x * x + y * y); - }); - - it("lerp functions", () => { - a.set(x, 0); - b.set(0, -y); - - expect(a.clone().lerp(a, 0).equals(a.lerp(a, 0.5))).toEqual(true); - expect(a.clone().lerp(a, 0).equals(a.lerp(a, 1))).toEqual(true); - - expect(a.clone().lerp(b, 0).equals(a)).toEqual(true); - - expect(a.clone().lerp(b, 0.5).x).toEqual(x * 0.5); - expect(a.clone().lerp(b, 0.5).y).toEqual(-y * 0.5); - - expect(a.clone().lerp(b, 1).equals(b)).toEqual(true); - }); - - it("normalize function", () => { - a.set(x, 0); - b.set(0, -y); - - a.normalize(); - expect(a.length()).toEqual(1); - expect(a.x).toEqual(1); - - b.normalize(); - expect(b.length()).toEqual(1); - expect(b.y).toEqual(-1); - }); - - it("distance function", () => { - a.set(x, 0); - b.set(0, -y); - c.set(0, 0); - - expect(a.distance(c)).toEqual(x); - expect(b.distance(c)).toEqual(y); - }); - - it("min/max/clamp", () => { - a.set(x, y); - b.set(-x, -y); - c.set(0, 0); - - c.copy(a).minV(b); - expect(c.x).toEqual(-x); - expect(c.y).toEqual(-y); - - c.copy(a).maxV(b); - expect(c.x).toEqual(x); - expect(c.y).toEqual(y); - - c.set(-2 * x, 2 * x); - c.clampSelf(-x, x); - expect(c.x).toEqual(-x); - expect(c.y).toEqual(x); - }); - - it("ceil/floor", () => { - expect( - a.setMuted(-0.1, 0.1).floorSelf().equals(new Vector2d(-1, 0)), - ).toEqual(true); - expect( - b.setMuted(-0.5, 0.5).floorSelf().equals(new Vector2d(-1, 0)), - ).toEqual(true); - expect( - c.setMuted(-0.9, 0.9).floorSelf().equals(new Vector2d(-1, 0)), - ).toEqual(true); - - expect(a.setMuted(-0.1, 0.1).ceilSelf().equals(new Vector2d(0, 1))).toEqual( - true, - ); - expect(b.setMuted(-0.5, 0.5).ceilSelf().equals(new Vector2d(0, 1))).toEqual( - true, - ); - expect(c.setMuted(-0.9, 0.9).ceilSelf().equals(new Vector2d(0, 1))).toEqual( - true, - ); - }); - - it("project a on b", () => { - a.set(x, y); - b.set(-x, -y); - - // the following only works with (-)1, (-)2style of values - expect(a.project(b).equals(b)).toEqual(true); - }); - - it("angle between a and b", () => { - a.set(x, y); - b.set(-x, -y); - - // why is this not perfectly 180 degrees ? - expect(Math.round(math.radToDeg(a.angle(b)))).toEqual(180); - - b.set(4 * x, -y); - expect(a.angle(b)).toEqual(Math.PI / 2); - }); - - it("perp and rotate function", () => { - a.set(x, y); - b.copy(a).perp(); - // perp rotate the vector by 90 degree clockwise on the z axis - c.copy(a).rotate(Math.PI / 2); - - expect(a.angle(b)).toEqual(a.angle(c)); - }); -}); diff --git a/packages/melonjs/tests/observableVect3d.spec.js b/packages/melonjs/tests/observableVect3d.spec.js deleted file mode 100644 index 271a29d39f..0000000000 --- a/packages/melonjs/tests/observableVect3d.spec.js +++ /dev/null @@ -1,281 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { ObservableVector3d, Vector3d, math } from "../src/index.js"; - -describe("ObservableVector3d", () => { - const x = 1; - const y = 2; - const z = 3; - - let a; - let b; - let c; - let d; - - let _newX; - let _newY; - let _newZ; - let _oldX; - let _oldY; - let _oldZ; - - const callback = function (newX, newY, newZ, oldX, oldY, oldZ) { - // this will also validate the argument list - _newX = newX; - _newY = newY; - _newZ = newZ; - _oldX = oldX; - _oldY = oldY; - _oldZ = oldZ; - }; - - const callback_with_ret = function () { - return { - x: 10, - y: 10, - z: 10, - }; - }; - - it("should be initialized to a (0, 0, 0) 3d vector", function () { - a = new ObservableVector3d(0, 0, 0, { - onUpdate: callback.bind(this), - }); - b = new ObservableVector3d(x, 0, 0, { - onUpdate: callback.bind(this), - }); - c = new ObservableVector3d(x, y, 0, { - onUpdate: callback.bind(this), - }); - - d = new ObservableVector3d(x, y, z, { - onUpdate: callback.bind(this), - }); - - expect(a.toString()).toEqual("x:0,y:0,z:0"); - }); - - it("setting the vector triggers the callback", () => { - a.set(10, 100, 20); - - expect(a.x + a.y + a.z).toEqual(_newX + _newY + _newZ); - }); - - it("callback returns a vector value", function () { - const d = new ObservableVector3d(0, 0, 0, { - onUpdate: callback_with_ret.bind(this), - }); - d.set(100, 100, 100); - expect(d.x + d.y + d.z).toEqual(30); // 10 + 10 + 10 - }); - - it("add a vector triggers the callback", () => { - a.add(new Vector3d(10, 10, 10)); - - expect(a.y).toEqual(_oldY + 10); - }); - - it("sub a vector triggers the callback", () => { - a.sub(new Vector3d(10, 10, 10)); - - expect(a.x).toEqual(_oldX - 10); - }); - - it("scale a vector triggers the callback", () => { - a.scaleV(new Vector3d(10, 10, 10)); - - expect(a.x).toEqual(_oldX * 10); - expect(a.y).toEqual(_oldY * 10); - expect(a.z).toEqual(_oldZ * 10); - }); - - it("negate (1, 2, 3)", () => { - a.set(x, y, z); - - expect(a.negateSelf().toString()).toEqual( - "x:" + -x + ",y:" + -y + ",z:" + -z, - ); - }); - - it("dot Product (1, 2, 3) and (-1, -2, -3)", () => { - a.set(x, y, z); - b.set(-x, -y, -z); - - // calculate the dot product - expect(a.dot(b)).toEqual(-x * x - y * y - z * z); - }); - - it("cross Product (2, 3, 4) and (5, 6, 7)", () => { - a.set(2, 3, 4); - b.set(5, 6, 7); - - const crossed = new Vector3d(-3, 6, -3); - - // calculate the cross product - a.cross(b); - expect(Math.abs(a.x - crossed.x)).toBeCloseTo(0, 4); - expect(Math.abs(a.y - crossed.y)).toBeCloseTo(0, 4); - expect(Math.abs(a.z - crossed.z)).toBeCloseTo(0, 4); - }); - - it("length/lengthSqrt functions", () => { - a.set(x, 0, 0); - b.set(0, -y, 0); - c.set(0, 0, z); - d.set(0, 0, 0); - - expect(a.length()).toEqual(x); - expect(a.length2()).toEqual(x * x); - expect(b.length()).toEqual(y); - expect(b.length2()).toEqual(y * y); - expect(c.length()).toEqual(z); - expect(c.length2()).toEqual(z * z); - expect(d.length()).toEqual(0); - expect(d.length2()).toEqual(0); - - a.set(x, y, z); - - expect(a.length()).toEqual(Math.sqrt(x * x + y * y + z * z)); - expect(a.length2()).toEqual(x * x + y * y + z * z); - }); - - it("lerp functions", () => { - a.set(x, 0, z); - b.set(0, -y, 0); - - expect(a.clone().lerp(a, 0).equals(a.lerp(a, 0.5))).toEqual(true); - expect(a.clone().lerp(a, 0).equals(a.lerp(a, 1))).toEqual(true); - - expect(a.clone().lerp(b, 0).equals(a)).toEqual(true); - - expect(a.clone().lerp(b, 0.5).x).toEqual(x * 0.5); - expect(a.clone().lerp(b, 0.5).y).toEqual(-y * 0.5); - expect(a.clone().lerp(b, 0.5).z).toEqual(z * 0.5); - - expect(a.clone().lerp(b, 1).equals(b)).toEqual(true); - }); - - it("normalize function", () => { - a.set(x, 0, 0); - b.set(0, -y, 0); - c.set(0, 0, z); - - a.normalize(); - expect(a.length()).toEqual(1); - expect(a.x).toEqual(1); - - b.normalize(); - expect(b.length()).toEqual(1); - expect(b.y).toEqual(-1); - - c.normalize(); - expect(c.length()).toEqual(1); - expect(c.z).toEqual(1); - }); - - it("distance function", () => { - a.set(x, 0, 0); - b.set(0, -y, 0); - c.set(0, 0, z); - d.set(0, 0, 0); - - expect(a.distance(d)).toEqual(x); - expect(b.distance(d)).toEqual(y); - expect(c.distance(d)).toEqual(z); - }); - - it("min/max/clamp", () => { - a.set(x, y, z); - b.set(-x, -y, -z); - c.set(0, 0, 0); - - c.copy(a).minV(b); - expect(c.x).toEqual(-x); - expect(c.y).toEqual(-y); - expect(c.z).toEqual(-z); - - c.copy(a).maxV(b); - expect(c.x).toEqual(x); - expect(c.y).toEqual(y); - expect(c.z).toEqual(z); - - c.set(-2 * x, 2 * x, 2 * z); - c.clampSelf(-x, x); - expect(c.x).toEqual(-x); - expect(c.y).toEqual(x); - expect(c.z).toEqual(x); - }); - - it("ceil/floor", () => { - expect( - a - .set(-0.1, 0.1, 0.3) - .floorSelf() - .equals(new Vector3d(-1, 0, 0)), - ).toEqual(true); - expect( - a - .set(-0.5, 0.5, 0.6) - .floorSelf() - .equals(new Vector3d(-1, 0, 0)), - ).toEqual(true); - expect( - a - .set(-0.9, 0.9, 0.8) - .floorSelf() - .equals(new Vector3d(-1, 0, 0)), - ).toEqual(true); - - expect( - a - .set(-0.1, 0.1, 0.3) - .ceilSelf() - .equals(new Vector3d(0, 1, 1)), - ).toEqual(true); - expect( - a - .set(-0.5, 0.5, 0.6) - .ceilSelf() - .equals(new Vector3d(0, 1, 1)), - ).toEqual(true); - expect( - a - .set(-0.9, 0.9, 0.9) - .ceilSelf() - .equals(new Vector3d(0, 1, 1)), - ).toEqual(true); - }); - - it("project a on b", () => { - a.set(x, y, z); - b.set(-x, -y, -z); - - // the following only works with (-)1, (-)2, (-)3 style of values - expect(a.project(b).equals(b)).toEqual(true); - }); - - it("angle between a and b", () => { - a.set(0, -0.18851655680720186, 0.9820700116639124); - b.set(0, 0.18851655680720186, -0.9820700116639124); - - expect(a.angle(a)).toEqual(0); - expect(a.angle(b)).toEqual(Math.PI); - - a.set(x, y, 0); - b.set(-x, -y, 0); - - // why is this not perfectly 180 degrees ? - expect(math.round(math.radToDeg(a.angle(b)))).toEqual(180); - - b.set(4 * x, -y, 0); - expect(a.angle(b)).toEqual(Math.PI / 2); - }); - - it("perp and rotate function", () => { - a.set(x, y, z); - b.copy(a).perp(); - // perp rotate the vector by 90 degree clockwise on the z axis - c.copy(a).rotate(Math.PI / 2); - - expect(a.angle(b)).toEqual(a.angle(c)); - }); -});