UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

498 lines (434 loc) • 11.3 kB
import { assert } from "../assert.js"; import { combine_hash } from "../collection/array/combine_hash.js"; import { Signal } from "../events/signal/Signal.js"; import { lerp } from "../math/lerp.js"; import { computeHashFloat } from "../primitives/numbers/computeHashFloat.js"; /** * Class representing a 4D vector. A 4D vector is an ordered quadruplet of numbers (labeled x, y, z, and w), which can be used to represent a number of things, such as: * * A point in 4D space. * A direction and length in 4D space. The length will always be the Euclidean distance (straight-line distance) from `(0, 0, 0, 0)` to `(x, y, z, w)` and the direction is also measured from `(0, 0, 0, 0)` towards `(x, y, z, w)`. * Any arbitrary ordered quadruplet of numbers. * There are other things a 4D vector can be used to represent, however these are the most common uses. * * Iterating through a Vector4 instance will yield its components (x, y, z, w) in the corresponding order. * * @implements Iterable<number> */ export class Vector4 { /** * * @param {number} [x=0] * @param {number} [y=0] * @param {number} [z=0] * @param {number} [w=0] * @constructor * @class */ constructor( x = 0, y = 0, z = 0, w = 0 ) { /** * * @type {number} */ this.x = x; /** * * @type {number} */ this.y = y; /** * * @type {number} */ this.z = z; /** * * @type {number} */ this.w = w; /** * Dispatched when the value changes * @readonly * @type {Signal<number,number,number,number,number,number,number,number>} */ this.onChanged = new Signal(); } /** * * @returns {number} */ get 0() { return this.x; } /** * * @param {number} v */ set 0(v) { this.x = v; } /** * * @returns {number} */ get 1() { return this.y; } /** * * @param {number} v */ set 1(v) { this.y = v; } /** * * @returns {number} */ get 2() { return this.z; } /** * * @param {number} v */ set 2(v) { this.z = v; } /** * * @returns {number} */ get 3() { return this.w; } /** * * @param {number} v */ set 3(v) { this.w = v; } /** * * @param {number[]} array * @param {number} offset */ readFromArray(array, offset = 0) { this.set( array[offset], array[offset + 1], array[offset + 2], array[offset + 3] ); } /** * * @param {number[]} array * @param {number} offset */ writeToArray(array, offset = 0) { array[offset] = this.x; array[offset + 1] = this.y; array[offset + 2] = this.z; array[offset + 3] = this.w; } /** * * @param {Number} x * @param {Number} y * @param {Number} z * @param {Number} w * @returns {Vector4} */ set(x, y, z, w) { assert.equal(typeof x, "number", `X must be of type "number", instead was "${typeof x}"`); assert.equal(typeof y, "number", `Y must be of type "number", instead was "${typeof y}"`); assert.equal(typeof z, "number", `Z must be of type "number", instead was "${typeof z}"`); assert.equal(typeof w, "number", `W must be of type "number", instead was "${typeof w}"`); assert.ok(!Number.isNaN(x), `X must be a valid number, instead was NaN`); assert.ok(!Number.isNaN(y), `Y must be a valid number, instead was NaN`); assert.ok(!Number.isNaN(z), `Z must be a valid number, instead was NaN`); assert.ok(!Number.isNaN(w), `W must be a valid number, instead was NaN`); const _x = this.x; const _y = this.y; const _z = this.z; const _w = this.w; if (_x !== x || _y !== y || _z !== z || _w !== w) { this.x = x; this.y = y; this.z = z; this.w = w; if (this.onChanged.hasHandlers()) { this.onChanged.send8( x, y, z, w, _x, _y, _z, _w ); } } return this; } /** * * @param {Vector3} v3 */ multiplyVector3(v3) { const x = this.x * v3.x; const y = this.y * v3.y; const z = this.z * v3.z; this.set(x, y, z, this.w); } /** * * @param {Number} value * @returns {Vector4} */ multiplyScalar(value) { assert.isNumber(value, 'value'); assert.notNaN(value, 'value'); return this.set(this.x * value, this.y * value, this.z * value, this.w * value); } /** * * @param {number} a0 * @param {number} a1 * @param {number} a2 * @param {number} a3 * @param {number} b0 * @param {number} b1 * @param {number} b2 * @param {number} b3 * @param {number} c0 * @param {number} c1 * @param {number} c2 * @param {number} c3 * @param {number} d0 * @param {number} d1 * @param {number} d2 * @param {number} d3 * @returns {Vector4} this */ _applyMatrix4( a0, a1, a2, a3, b0, b1, b2, b3, c0, c1, c2, c3, d0, d1, d2, d3 ) { const _x = this.x; const _y = this.y; const _z = this.z; const _w = this.w; const x = a0 * _x + b0 * _y + c0 * _z + d0 * _w; const y = a1 * _x + b1 * _y + c1 * _z + d1 * _w; const z = a2 * _x + b2 * _y + c2 * _z + d2 * _w; const w = a3 * _x + b3 * _y + c3 * _z + d3 * _w; return this.set(x, y, z, w); } /** * * @param {Vector4} other * @returns {number} */ dot(other) { return this.x * other.x + this.y * other.y + this.z * other.z + this.w * other.w; } /** * Add XYZ components from another vector * @param {Vector3|Vector4} v3 * @returns {Vector4} */ add3(v3) { return this.set( this.x + v3.x, this.y + v3.y, this.z + v3.z, this.w ); } /** * @deprecated use {@link applyMatrix4} directly instead * @param {Matrix4} m * @returns {Vector4} this */ threeApplyMatrix4(m) { return this.applyMatrix4(m.elements); } /** * * @param {number[]|Float32Array} m 4x4 transformation matrix * @return {Vector4} */ applyMatrix4(m) { return this._applyMatrix4( m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15] ); } /** * * @param {Vector4} vec4 * @returns {Vector4} */ copy(vec4) { return this.set(vec4.x, vec4.y, vec4.z, vec4.w); } /** * * @returns {Vector4} */ clone() { const r = new Vector4(); r.copy(this); return r; } /** * * @param {Quaternion} q */ applyQuaternion(q) { const x = this.x; const y = this.y; const z = this.z; const w = this.w; const qx = q.x; const qy = q.y; const qz = q.z; const qw = q.w; // calculate quat * vec const ix = qw * x + qy * z - qz * y; const iy = qw * y + qz * x - qx * z; const iz = qw * z + qx * y - qy * x; const iw = -qx * x - qy * y - qz * z; // calculate result * inverse quat const _x = ix * qw + iw * -qx + iy * -qz - iz * -qy; const _y = iy * qw + iw * -qy + iz * -qx - ix * -qz; const _z = iz * qw + iw * -qz + ix * -qy - iy * -qx; this.set(_x, _y, _z, w); } /** * * @param {Vector4} vec4 * @returns {boolean} */ equals(vec4) { return this.x === vec4.x && this.y === vec4.y && this.z === vec4.z && this.w === vec4.w; } /** * * @return {number} */ hash() { return combine_hash( computeHashFloat(this.x), computeHashFloat(this.y), computeHashFloat(this.z), computeHashFloat(this.w) ); } /** * @param {Vector4} v0 * @param {Vector4} v1 * @param {Number} f interpolation fraction */ lerpVectors(v0, v1, f) { Vector4.lerp(v0, v1, f, this); } /** * * @param {number[]} result */ toArray(result) { result[0] = this.x; result[1] = this.y; result[2] = this.z; result[3] = this.w; } /** * @returns {number[]} */ asArray() { const result = []; this.toArray(result); return result; } /** * * @param {number[]} data * @param {number} offset */ setFromArray(data, offset) { this.set(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); } toJSON() { return { x: this.x, y: this.y, z: this.z, w: this.w }; } fromJSON(json) { this.copy(json); } /** * * @param {BinaryBuffer} buffer */ toBinaryBuffer(buffer) { buffer.writeFloat64(this.x); buffer.writeFloat64(this.y); buffer.writeFloat64(this.z); buffer.writeFloat64(this.w); } /** * * @param {BinaryBuffer} buffer */ fromBinaryBuffer(buffer) { const x = buffer.readFloat64(); const y = buffer.readFloat64(); const z = buffer.readFloat64(); const w = buffer.readFloat64(); this.set(x, y, z, w); } * [Symbol.iterator]() { yield this.x; yield this.y; yield this.z; yield this.w; } /** * * @param {Vector4} v0 * @param {Vector4} v1 * @param {Number} f interpolation fraction * @param {Vector4} result */ static lerp(v0, v1, f, result) { const x = lerp(v0.x, v1.x, f); const y = lerp(v0.y, v1.y, f); const z = lerp(v0.z, v1.z, f); const w = lerp(v0.w, v1.w, f); result.set(x, y, z, w); } } /** * @readonly * @type {boolean} */ Vector4.prototype.isVector4 = true; Vector4.prototype.fromArray = Vector4.prototype.readFromArray; Vector4.prototype.toArray = Vector4.prototype.writeToArray; /** * @readonly * @type {string} */ Vector4.typeName = "Vector4"; export default Vector4;