UNPKG

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
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 };