onsight
Version:
Interactive, easy to use JavaScript game framework.
425 lines (367 loc) • 11.1 kB
JavaScript
const EPSILON = 0.000001;
class Vector3 {
constructor(x = 0, y = 0, z = 0) {
if (typeof x === 'object') {
this.x = x.x;
this.y = x.y;
this.z = x.z;
} else {
this.x = x;
this.y = y;
this.z = z;
}
}
set(x, y, z) {
if (typeof x === 'object') return this.copy(x);
this.x = x;
this.y = y;
this.z = z;
return this;
}
setScalar(scalar) {
this.x = scalar;
this.y = scalar;
this.z = scalar;
return this;
}
clone() {
return new Vector3(this.x, this.y, this.z);
}
copy(x, y, z) {
if (typeof x === 'object') {
this.x = x.x;
this.y = x.y;
this.z = x.z;
} else {
this.x = x;
this.y = y;
this.z = z;
}
return this;
}
add(x, y, z) {
if (typeof x === 'object') {
this.x += x.x;
this.y += x.y;
this.z += x.z;
} else {
this.x += x;
this.y += y;
this.z += z;
}
return this;
}
addScalar(scalar) {
this.x += scalar;
this.y += scalar;
this.z += scalar;
return this;
}
/** Add two vectors and store the result in this vector */
addVectors(a, b) {
this.x = a.x + b.x;
this.y = a.y + b.y;
this.z = a.z + b.z;
return this;
}
/** Scale a vector components and add the result to this vector */
addScaledVector(vec, scale) {
this.x += vec.x * scale;
this.y += vec.y * scale;
this.z += vec.z * scale;
return this;
}
sub(x, y, z) {
if (typeof x === 'object') {
this.x -= x.x;
this.y -= x.y;
this.z -= x.z;
} else {
this.x -= x;
this.y -= y;
this.z -= z;
}
return this;
}
subScalar(scalar) {
this.x -= scalar;
this.y -= scalar;
this.z -= scalar;
return this;
}
/** Subtract two vectors and store the result in this vector */
subVectors(a, b) {
this.x = a.x - b.x;
this.y = a.y - b.y;
this.z = a.z - b.z;
return this;
}
multiply(x, y, z) {
if (typeof x === 'object') {
this.x *= x.x;
this.y *= x.y;
this.z *= x.z;
} else {
this.x *= x;
this.y *= y;
this.z *= z;
}
return this;
}
multiplyScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
return this;
}
divide(x, y) {
if (typeof x === 'object') {
this.x /= x.x;
this.y /= x.y;
this.z /= x.z;
} else {
this.x /= x;
this.y /= y;
this.z /= z;
}
return this;
}
divideScalar(scalar) {
return this.multiplyScalar(1 / scalar);
}
/** Set components as the minimum values found between two vectors */
min(vec) {
this.x = Math.min(this.x, vec.x);
this.y = Math.min(this.y, vec.y);
this.z = Math.min(this.z, vec.z);
return this;
}
/** Set components as the maximum values found between two vectors */
max(vec) {
this.x = Math.max(this.x, vec.x);
this.y = Math.max(this.y, vec.y);
this.z = Math.max(this.z, vec.z);
return this;
}
/** Clamp the vector coordinates to the range defined by two vectors */
clamp(minv, maxv) {
if (minv.x < maxv.x) this.x = Math.max(minv.x, Math.min(maxv.x, this.x));
else this.x = Math.max(maxv.x, Math.min(minv.x, this.x));
if (minv.y < maxv.y) this.y = Math.max(minv.y, Math.min(maxv.y, this.y));
else this.y = Math.max(maxv.y, Math.min(minv.y, this.y));
if (minv.z < maxv.z) this.z = Math.max(minv.z, Math.min(maxv.z, this.z));
else this.z = Math.max(maxv.z, Math.min(minv.z, this.z));
return this;
}
/** Clamp the vector coordinates to the range defined by two scalars */
clampScalar(minVal, maxVal) {
this.x = Math.max(minVal, Math.min(maxVal, this.x));
this.y = Math.max(minVal, Math.min(maxVal, this.y));
this.z = Math.max(minVal, Math.min(maxVal, this.z));
return this;
}
clampLength(min, max) {
const length = this.length();
return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length)));
}
floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
this.z = Math.floor(this.z);
return this;
}
ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
this.z = Math.ceil(this.z);
return this;
}
round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
this.z = Math.round(this.z);
return this;
}
negate() {
this.x = -this.x;
this.y = -this.y;
this.z = -this.z;
return this;
}
abs() {
this.x = Math.abs(this.x);
this.y = Math.abs(this.y);
this.z = Math.abs(this.z);
return this;
}
dot(vec) {
return this.x * vec.x + this.y * vec.y + this.z * vec.z;
}
cross(vec) {
return this.crossVectors(this, vec);
}
crossVectors(a, b) {
const ax = a.x, ay = a.y, az = a.z;
const bx = b.x, by = b.y, bz = b.z;
this.x = ay * bz - az * by;
this.y = az * bx - ax * bz;
this.z = ax * by - ay * bx;
return this;
}
/** Length of the vector */
length() {
return Math.sqrt(this.lengthSq());
}
/** Squared length of the vector (faster for comparions) */
lengthSq() {
return this.x * this.x + this.y * this.y + this.z * this.z;
}
manhattanLength() {
return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z);
}
normalize() {
return this.divideScalar(this.length() || 1);
}
/** Computes the angle (in radians) between this and another vector */
angle(vec) {
_temp1.copy(this).normalize();
_temp2.copy(vec).normalize();
const cosine = _temp1.dot(_temp2);
if (cosine > 1.0) return 0;
if (cosine < -1.0) return Math.PI;
return Math.acos(cosine);
}
/** Distance between two vector positions */
distanceTo(vec) {
return Math.sqrt(this.distanceToSquared(vec));
}
/** Distance between two vector positions squared, faster for comparisons */
distanceToSquared(vec) {
const dx = this.x - vec.x;
const dy = this.y - vec.y;
const dz = this.z - vec.z;
return dx * dx + dy * dy + dz * dz;
}
manhattanDistanceTo(vec) {
return Math.abs(this.x - vec.x) + Math.abs(this.y - vec.y) + Math.abs(this.z - vec.z);
}
lerp(vec, t) {
return this.lerpVectors(this, vec, t);
}
lerpVectors(a, b, t) {
this.x = a.x + ((b.x - a.x) * t);
this.y = a.y + ((b.y - a.y) * t);
this.z = a.z + ((b.z - a.z) * t);
return this;
}
/** Transforms with a 3x3 matrix */
applyMatrix3(mat3) {
this.x = this.x * mat3[0] + this.y * mat3[3] + this.z * mat3[6];
this.y = this.x * mat3[1] + this.y * mat3[4] + this.z * mat3[7];
this.z = this.x * mat3[2] + this.y * mat3[5] + this.z * mat3[8];
return this;
}
/** Transforms with a 3x3 matrix, 4th vector component is implicitly '1' */
applyMatrix4(mat3) {
const m = mat3.m;
if (!m) return this;
let x = this.x;
let y = this.y;
let z = this.z;
let w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
this.x = (m[0] * x + m[4] * y + m[ 8] * z + m[12]) / w;
this.y = (m[1] * x + m[5] * y + m[ 9] * z + m[13]) / w;
this.z = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
return this;
}
/** Same as above but doesn't apply translation (useful for rays) */
scaleRotateMatrix4(mat4) {
const m = mat4.m;
if (!m) return this;
let x = this.x;
let y = this.y;
let z = this.z;
let w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
this.x = (m[0] * x + m[4] * y + m[ 8] * z) / w;
this.y = (m[1] * x + m[5] * y + m[ 9] * z) / w;
this.z = (m[2] * x + m[6] * y + m[10] * z) / w;
return this;
}
/** Transforms the with a Quaternion */
applyQuaternion(q) {
let x = this.x;
let y = this.y;
let z = this.z;
let qx = q[0];
let qy = q[1];
let qz = q[2];
let qw = q[3];
let uvx = qy * z - qz * y;
let uvy = qz * x - qx * z;
let uvz = qx * y - qy * x;
let uuvx = qy * uvz - qz * uvy;
let uuvy = qz * uvx - qx * uvz;
let uuvz = qx * uvy - qy * uvx;
let w2 = qw * 2;
uvx *= w2;
uvy *= w2;
uvz *= w2;
uuvx *= 2;
uuvy *= 2;
uuvz *= 2;
this.x = x + uvx + uuvx;
this.y = y + uvy + uuvy;
this.z = z + uvz + uuvz;
return this;
}
transformDirection(mat4) {
const x = this.x;
const y = this.y;
const z = this.z;
this.x = mat4[0] * x + mat4[4] * y + mat4[ 8] * z;
this.y = mat4[1] * x + mat4[5] * y + mat4[ 9] * z;
this.z = mat4[2] * x + mat4[6] * y + mat4[10] * z;
return this.normalize();
}
calculateNormal(target = new Vector3(), a, b, c) {
_temp1.subVectors(a, b);
target.subVectors(b, c);
target.cross(_temp1);
target.normalize();
return target;
}
equals(vec) {
return ((vec.x === this.x) && (vec.y === this.y) && (vec.z === this.z));
}
fuzzyEquals(vec, tolerance = 0.001) {
if (fuzzyFloat(this.x, vec.x, tolerance) === false) return false;
if (fuzzyFloat(this.y, vec.y, tolerance) === false) return false;
if (fuzzyFloat(this.z, vec.z, tolerance) === false) return false;
return true;
}
random() {
this.x = Math.random();
this.y = Math.random();
this.z = Math.random();
}
log(description = '') {
if (description !== '') description += ' - '
console.log(`${description}X: ${this.x}, Y: ${this.y}, Z: ${this.z}`);
return this;
}
toArray() {
return [ this.x, this.y, this.z ];
}
fromArray(array, offset = 0) {
this.set(array[offset + 0], array[offset + 1], array[offset + 2]);
return this;
}
}
export { Vector3 };
/******************** INTERNAL ********************/
const _temp1 = new Vector3();
const _temp2 = new Vector3();
/** Compares two decimal numbers to see if they're almost the same */
function fuzzyFloat(a, b, tolerance = 0.001) {
return ((a < (b + tolerance)) && (a > (b - tolerance)));
}