gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
249 lines (247 loc) • 9.41 kB
JavaScript
class Quat {
/**
* Quat constructor
* @param [elements] - initial array to use
* @param [axisOrder='XYZ'] - axis order to use
*/
constructor(elements = new Float32Array([0, 0, 0, 1]), axisOrder = "XYZ") {
this.type = "Quat";
this.elements = elements;
this.axisOrder = axisOrder;
}
/**
* Sets the {@link Quat} values from an array
* @param array - an array of at least 4 elements
* @returns - this {@link Quat} after being set
*/
setFromArray(array = new Float32Array([0, 0, 0, 1])) {
this.elements[0] = array[0];
this.elements[1] = array[1];
this.elements[2] = array[2];
this.elements[3] = array[3];
return this;
}
/**
* Sets the {@link Quat} axis order
* @param axisOrder - axis order to use
* @returns - this {@link Quat} after axis order has been set
*/
setAxisOrder(axisOrder = "XYZ") {
axisOrder = axisOrder.toUpperCase();
switch (axisOrder) {
case "XYZ":
case "YXZ":
case "ZXY":
case "ZYX":
case "YZX":
case "XZY":
this.axisOrder = axisOrder;
break;
default:
this.axisOrder = "XYZ";
}
return this;
}
/**
* Copy a {@link Quat} into this {@link Quat}
* @param quaternion - {@link Quat} to copy
* @returns - this {@link Quat} after copy
*/
copy(quaternion = new Quat()) {
this.elements.set(quaternion.elements);
this.axisOrder = quaternion.axisOrder;
return this;
}
/**
* Clone a {@link Quat}
* @returns - cloned {@link Quat}
*/
clone() {
return new Quat().copy(this);
}
/**
* Check if 2 {@link Quat} are equal
* @param quaternion - {@link Quat} to check against
* @returns - whether the {@link Quat} are equal or not
*/
equals(quaternion = new Quat()) {
return this.elements[0] === quaternion.elements[0] && this.elements[1] === quaternion.elements[1] && this.elements[2] === quaternion.elements[2] && this.elements[3] === quaternion.elements[3] && this.axisOrder === quaternion.axisOrder;
}
/**
* Sets a rotation {@link Quat} using Euler angles {@link Vec3 | vector} and its axis order
* @param vector - rotation {@link Vec3 | vector} to set our {@link Quat} from
* @returns - {@link Quat} after having applied the rotation
*/
setFromVec3(vector) {
const ax = vector.x * 0.5;
const ay = vector.y * 0.5;
const az = vector.z * 0.5;
const cosx = Math.cos(ax);
const cosy = Math.cos(ay);
const cosz = Math.cos(az);
const sinx = Math.sin(ax);
const siny = Math.sin(ay);
const sinz = Math.sin(az);
if (this.axisOrder === "XYZ") {
this.elements[0] = sinx * cosy * cosz + cosx * siny * sinz;
this.elements[1] = cosx * siny * cosz - sinx * cosy * sinz;
this.elements[2] = cosx * cosy * sinz + sinx * siny * cosz;
this.elements[3] = cosx * cosy * cosz - sinx * siny * sinz;
} else if (this.axisOrder === "YXZ") {
this.elements[0] = sinx * cosy * cosz + cosx * siny * sinz;
this.elements[1] = cosx * siny * cosz - sinx * cosy * sinz;
this.elements[2] = cosx * cosy * sinz - sinx * siny * cosz;
this.elements[3] = cosx * cosy * cosz + sinx * siny * sinz;
} else if (this.axisOrder === "ZXY") {
this.elements[0] = sinx * cosy * cosz - cosx * siny * sinz;
this.elements[1] = cosx * siny * cosz + sinx * cosy * sinz;
this.elements[2] = cosx * cosy * sinz + sinx * siny * cosz;
this.elements[3] = cosx * cosy * cosz - sinx * siny * sinz;
} else if (this.axisOrder === "ZYX") {
this.elements[0] = sinx * cosy * cosz - cosx * siny * sinz;
this.elements[1] = cosx * siny * cosz + sinx * cosy * sinz;
this.elements[2] = cosx * cosy * sinz - sinx * siny * cosz;
this.elements[3] = cosx * cosy * cosz + sinx * siny * sinz;
} else if (this.axisOrder === "YZX") {
this.elements[0] = sinx * cosy * cosz + cosx * siny * sinz;
this.elements[1] = cosx * siny * cosz + sinx * cosy * sinz;
this.elements[2] = cosx * cosy * sinz - sinx * siny * cosz;
this.elements[3] = cosx * cosy * cosz - sinx * siny * sinz;
} else if (this.axisOrder === "XZY") {
this.elements[0] = sinx * cosy * cosz - cosx * siny * sinz;
this.elements[1] = cosx * siny * cosz - sinx * cosy * sinz;
this.elements[2] = cosx * cosy * sinz + sinx * siny * cosz;
this.elements[3] = cosx * cosy * cosz + sinx * siny * sinz;
}
return this;
}
/**
* Set a {@link Quat} from a rotation axis {@link Vec3 | vector} and an angle
* @param axis - normalized {@link Vec3 | vector} around which to rotate
* @param angle - angle (in radians) to rotate
* @returns - {@link Quat} after having applied the rotation
*/
setFromAxisAngle(axis, angle = 0) {
const halfAngle = angle / 2, s = Math.sin(halfAngle);
this.elements[0] = axis.x * s;
this.elements[1] = axis.y * s;
this.elements[2] = axis.z * s;
this.elements[3] = Math.cos(halfAngle);
return this;
}
/**
* Set a {@link Quat} from a rotation {@link Mat4 | matrix}
* @param matrix - rotation {@link Mat4 | matrix} to use
* @returns - {@link Quat} after having applied the rotation
*/
setFromRotationMatrix(matrix) {
const te = matrix.elements, m11 = te[0], m12 = te[4], m13 = te[8], m21 = te[1], m22 = te[5], m23 = te[9], m31 = te[2], m32 = te[6], m33 = te[10], trace = m11 + m22 + m33;
if (trace > 0) {
const s = 0.5 / Math.sqrt(trace + 1);
this.elements[3] = 0.25 / s;
this.elements[0] = (m32 - m23) * s;
this.elements[1] = (m13 - m31) * s;
this.elements[2] = (m21 - m12) * s;
} else if (m11 > m22 && m11 > m33) {
const s = 2 * Math.sqrt(1 + m11 - m22 - m33);
this.elements[3] = (m32 - m23) / s;
this.elements[0] = 0.25 * s;
this.elements[1] = (m12 + m21) / s;
this.elements[2] = (m13 + m31) / s;
} else if (m22 > m33) {
const s = 2 * Math.sqrt(1 + m22 - m11 - m33);
this.elements[3] = (m13 - m31) / s;
this.elements[0] = (m12 + m21) / s;
this.elements[1] = 0.25 * s;
this.elements[2] = (m23 + m32) / s;
} else {
const s = 2 * Math.sqrt(1 + m33 - m11 - m22);
this.elements[3] = (m21 - m12) / s;
this.elements[0] = (m13 + m31) / s;
this.elements[1] = (m23 + m32) / s;
this.elements[2] = 0.25 * s;
}
return this;
}
/**
* Get the square length of this {@link Quat}.
* @returns - square length of this {@link Quat}.
*/
lengthSq() {
return this.elements[0] * this.elements[0] + this.elements[1] * this.elements[1] + this.elements[2] * this.elements[2] + this.elements[3] * this.elements[3];
}
/**
* Get the length of this {@link Quat}.
* @returns - length of this {@link Quat}.
*/
length() {
return Math.sqrt(this.lengthSq());
}
/**
* Normalize this {@link Quat}.
* @returns - normalized {@link Quat}.
*/
normalize() {
let l = this.length();
if (l === 0) {
this.elements[0] = 0;
this.elements[1] = 0;
this.elements[2] = 0;
this.elements[3] = 1;
} else {
l = 1 / l;
this.elements[0] = this.elements[0] * l;
this.elements[1] = this.elements[1] * l;
this.elements[2] = this.elements[2] * l;
this.elements[3] = this.elements[3] * l;
}
return this;
}
/**
* Calculate the spherical linear interpolation of this {@link Quat} by given {@link Quat} and alpha, where alpha is the percent distance.
* @param quat - {@link Quat} to interpolate towards.
* @param alpha - spherical interpolation factor in the [0, 1] interval.
* @returns - this {@link Quat} after spherical linear interpolation.
*/
slerp(quat = new Quat(), alpha = 0) {
if (alpha === 0) return this;
if (alpha === 1) return this.copy(quat);
const x = this.elements[0], y = this.elements[1], z = this.elements[2], w = this.elements[3];
let cosHalfTheta = w * quat.elements[3] + x * quat.elements[0] + y * quat.elements[1] + z * quat.elements[2];
if (cosHalfTheta < 0) {
this.elements[3] = -quat.elements[3];
this.elements[0] = -quat.elements[0];
this.elements[1] = -quat.elements[1];
this.elements[2] = -quat.elements[2];
cosHalfTheta = -cosHalfTheta;
} else {
this.copy(quat);
}
if (cosHalfTheta >= 1) {
this.elements[3] = w;
this.elements[0] = x;
this.elements[1] = y;
this.elements[2] = z;
return this;
}
const sqrSinHalfTheta = 1 - cosHalfTheta * cosHalfTheta;
if (sqrSinHalfTheta <= Number.EPSILON) {
const s = 1 - alpha;
this.elements[3] = s * w + alpha * this.elements[3];
this.elements[0] = s * x + alpha * this.elements[0];
this.elements[1] = s * y + alpha * this.elements[1];
this.elements[2] = s * z + alpha * this.elements[2];
this.normalize();
return this;
}
const sinHalfTheta = Math.sqrt(sqrSinHalfTheta);
const halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta);
const ratioA = Math.sin((1 - alpha) * halfTheta) / sinHalfTheta, ratioB = Math.sin(alpha * halfTheta) / sinHalfTheta;
this.elements[3] = w * ratioA + this.elements[3] * ratioB;
this.elements[0] = x * ratioA + this.elements[0] * ratioB;
this.elements[1] = y * ratioA + this.elements[1] * ratioB;
this.elements[2] = z * ratioA + this.elements[2] * ratioB;
return this;
}
}
export { Quat };