@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
498 lines (434 loc) • 11.3 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.
*
* @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;