@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
517 lines (453 loc) • 12.1 kB
JavaScript
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;