UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

517 lines (453 loc) • 12.1 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. * * Backed by a `Float64Array` of length 4, so instances are usable directly as typed-array views and indexed access `v[0]`..`v[3]` aliases `v.x`..`v.w`. * * @implements Iterable<number> */ export class Vector4 extends Float64Array { /** * * @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 ) { super(4); this[0] = x; this[1] = y; this[2] = z; this[3] = w; /** * Dispatched when the value changes * @readonly * @type {Signal<number,number,number,number,number,number,number,number>} */ this.onChanged = new Signal(); } /** * Ensure that built-in `TypedArray` methods (`map`, `slice`, `subarray`, ...) do not * try to construct a `Vector4` via the buffer-form constructor. * @returns {Float64ArrayConstructor} */ static get [Symbol.species]() { return Float64Array; } /** * * @returns {number} */ get x() { return this[0]; } /** * * @param {number} v */ set x(v) { this[0] = v; } /** * * @returns {number} */ get y() { return this[1]; } /** * * @param {number} v */ set y(v) { this[1] = v; } /** * * @returns {number} */ get z() { return this[2]; } /** * * @param {number} v */ set z(v) { this[2] = v; } /** * * @returns {number} */ get w() { return this[3]; } /** * * @param {number} v */ set w(v) { this[3] = 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[0]; array[offset + 1] = this[1]; array[offset + 2] = this[2]; array[offset + 3] = this[3]; } /** * * @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[0]; const _y = this[1]; const _z = this[2]; const _w = this[3]; if (_x !== x || _y !== y || _z !== z || _w !== w) { this[0] = x; this[1] = y; this[2] = z; this[3] = 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[0] * v3.x; const y = this[1] * v3.y; const z = this[2] * v3.z; this.set(x, y, z, this[3]); } /** * * @param {Number} value * @returns {Vector4} */ multiplyScalar(value) { assert.isNumber(value, 'value'); assert.notNaN(value, 'value'); return this.set(this[0] * value, this[1] * value, this[2] * value, this[3] * 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[0]; const _y = this[1]; const _z = this[2]; const _w = this[3]; 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[0] * other.x + this[1] * other.y + this[2] * other.z + this[3] * other.w; } /** * Add XYZ components from another vector * @param {Vector3|Vector4} v3 * @returns {Vector4} */ add3(v3) { return this._add( v3.x, v3.y, v3.z, 0 ); } /** * * @param {number} x * @param {number} y * @param {number} z * @param {number} w * @returns {this} */ _add(x,y,z,w){ return this.set( this[0] + x, this[1] + y, this[2] + z, this[3] + w ); } /** * `this = this + other * scale` * @param {Vector4} other * @param {number} scale * @returns {this} */ addScaled(other, scale){ return this._add( other.x * scale, other.y * scale, other.z * scale, other.w * scale ); } /** * @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[0]; const y = this[1]; const z = this[2]; const w = this[3]; 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[0] === vec4.x && this[1] === vec4.y && this[2] === vec4.z && this[3] === vec4.w; } /** * * @return {number} */ hash() { return combine_hash( computeHashFloat(this[0]), computeHashFloat(this[1]), computeHashFloat(this[2]), computeHashFloat(this[3]) ); } /** * @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[0]; result[1] = this[1]; result[2] = this[2]; result[3] = this[3]; } /** * @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[0], y: this[1], z: this[2], w: this[3] }; } fromJSON(json) { this.copy(json); } /** * * @param {BinaryBuffer} buffer */ toBinaryBuffer(buffer) { buffer.writeFloat64(this[0]); buffer.writeFloat64(this[1]); buffer.writeFloat64(this[2]); buffer.writeFloat64(this[3]); } /** * * @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); } /** * * @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;