UNPKG

3dmol

Version:

JavaScript/TypeScript molecular visualization library

1,534 lines (1,289 loc) 31.7 kB
import { Quaternion } from "./Quaternion"; import { degToRad } from "./utils/degToRad"; import { clamp } from "./utils/clamp"; import { Sphere } from "../shapes"; var mRotation: Matrix4; var mScale: Matrix4; var x: Vector3; var y: Vector3; var z: Vector3; /** @class * @subcategory Math * */ export class Matrix4 { elements: Float32Array; constructor(n11: Array<number>); constructor( n11?: number, n12?: number, n13?: number, n14?: number, n21?: number, n22?: number, n23?: number, n24?: number, n31?: number, n32?: number, n33?: number, n34?: number, n41?: number, n42?: number, n43?: number, n44?: number ); constructor( n11: Array<number> | number = 1, n12: number = 0, n13: number = 0, n14: number = 0, n21: number = 0, n22: number = 1, n23: number = 0, n24: number = 0, n31: number = 0, n32: number = 0, n33: number = 1, n34: number = 0, n41: number = 0, n42: number = 0, n43: number = 0, n44: number = 1 ) { if (typeof n11 !== "undefined" && typeof n11 !== "number") { // passing list like initialization this.elements = new Float32Array(n11); } else { this.elements = new Float32Array(16); this.elements[0] = n11; this.elements[4] = n12; this.elements[8] = n13; this.elements[12] = n14; this.elements[1] = n21; this.elements[5] = n22; this.elements[9] = n23; this.elements[13] = n24; this.elements[2] = n31; this.elements[6] = n32; this.elements[10] = n33; this.elements[14] = n34; this.elements[3] = n41; this.elements[7] = n42; this.elements[11] = n43; this.elements[15] = n44; } } // eslint-disable-next-line no-unused-vars, class-methods-use-this makeScale(x: any, y: any, z: any) { throw new Error("Method not implemented."); } set( n11: number, n12: number, n13: number, n14: number, n21: number, n22: number, n23: number, n24: number, n31: number, n32: number, n33: number, n34: number, n41: number, n42: number, n43: number, n44: number ) { const te = this.elements; te[0] = n11; te[4] = n12; te[8] = n13; te[12] = n14; te[1] = n21; te[5] = n22; te[9] = n23; te[13] = n24; te[2] = n31; te[6] = n32; te[10] = n33; te[14] = n34; te[3] = n41; te[7] = n42; te[11] = n43; te[15] = n44; return this; } identity() { this.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); return this; } copy(m: { elements: any }) { const me = m.elements; this.set( me[0], me[4], me[8], me[12], me[1], me[5], me[9], me[13], me[2], me[6], me[10], me[14], me[3], me[7], me[11], me[15] ); return this; } matrix3FromTopLeft() { const te = this.elements; return new Matrix3( te[0], te[4], te[8], te[1], te[5], te[9], te[2], te[6], te[10] ); } setRotationFromEuler(v: Vector3, order?: string) { const te = this.elements; const { x, y, z } = v; const a = Math.cos(x); const b = Math.sin(x); const c = Math.cos(y); const d = Math.sin(y); const e = Math.cos(z); const f = Math.sin(z); if (order === undefined || order === "XYZ") { const ae = a * e; const af = a * f; const be = b * e; const bf = b * f; te[0] = c * e; te[4] = -c * f; te[8] = d; te[1] = af + be * d; te[5] = ae - bf * d; te[9] = -b * c; te[2] = bf - ae * d; te[6] = be + af * d; te[10] = a * c; } else console.error(`Error with matrix4 setRotationFromEuler. Order: ${order}`); return this; } setRotationFromQuaternion(q: Quaternion) { const te = this.elements; const { x, y, z, w } = q; const x2 = x + x; const y2 = y + y; const z2 = z + z; const xx = x * x2; const xy = x * y2; const xz = x * z2; const yy = y * y2; const yz = y * z2; const zz = z * z2; const wx = w * x2; const wy = w * y2; const wz = w * z2; te[0] = 1 - (yy + zz); te[4] = xy - wz; te[8] = xz + wy; te[1] = xy + wz; te[5] = 1 - (xx + zz); te[9] = yz - wx; te[2] = xz - wy; te[6] = yz + wx; te[10] = 1 - (xx + yy); return this; } multiplyMatrices(a: { elements: any }, b: Matrix4) { const ae = a.elements; const be = b.elements; const te = this.elements; const a11 = ae[0]; const a12 = ae[4]; const a13 = ae[8]; const a14 = ae[12]; const a21 = ae[1]; const a22 = ae[5]; const a23 = ae[9]; const a24 = ae[13]; const a31 = ae[2]; const a32 = ae[6]; const a33 = ae[10]; const a34 = ae[14]; const a41 = ae[3]; const a42 = ae[7]; const a43 = ae[11]; const a44 = ae[15]; const b11 = be[0]; const b12 = be[4]; const b13 = be[8]; const b14 = be[12]; const b21 = be[1]; const b22 = be[5]; const b23 = be[9]; const b24 = be[13]; const b31 = be[2]; const b32 = be[6]; const b33 = be[10]; const b34 = be[14]; const b41 = be[3]; const b42 = be[7]; const b43 = be[11]; const b44 = be[15]; te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; return this; } multiplyScalar(s: number) { const te = this.elements; te[0] *= s; te[4] *= s; te[8] *= s; te[12] *= s; te[1] *= s; te[5] *= s; te[9] *= s; te[13] *= s; te[2] *= s; te[6] *= s; te[10] *= s; te[14] *= s; te[3] *= s; te[7] *= s; te[11] *= s; te[15] *= s; return this; } makeTranslation(x: any, y: any, z: any) { this.set(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1); return this; } // snap values close to integers to their integer value // useful and identifying identity matrices snap(digits: number) { if (!digits) digits = 4; const mult = 10 ** 4; const te = this.elements; for (let i = 0; i < 16; i++) { const rounded = Math.round(te[i]); if (rounded === Math.round(te[i] * mult) / mult) { te[i] = rounded; } } return this; } transpose() { const te = this.elements; let tmp: any; tmp = te[1]; te[1] = te[4]; te[4] = tmp; tmp = te[2]; te[2] = te[8]; te[8] = tmp; tmp = te[6]; te[6] = te[9]; te[9] = tmp; tmp = te[3]; te[3] = te[12]; te[12] = tmp; tmp = te[7]; te[7] = te[13]; te[13] = tmp; tmp = te[11]; te[11] = te[14]; te[14] = tmp; return this; } setPosition(v: Vector3) { const te = this.elements; te[12] = v.x; te[13] = v.y; te[14] = v.z; return this; } translate(v: Vector3) { const te = this.elements; te[12] += v.x; te[13] += v.y; te[14] += v.z; return this; } getInverse(m: Matrix4, throwOnInvertible?: boolean) { // based on // http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm const te = this.elements; const me = m.elements; const n11 = me[0]; const n12 = me[4]; const n13 = me[8]; const n14 = me[12]; const n21 = me[1]; const n22 = me[5]; const n23 = me[9]; const n24 = me[13]; const n31 = me[2]; const n32 = me[6]; const n33 = me[10]; const n34 = me[14]; const n41 = me[3]; const n42 = me[7]; const n43 = me[11]; const n44 = me[15]; te[0] = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44; te[4] = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44; te[8] = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44; te[12] = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; te[1] = n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44; te[5] = n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44; te[9] = n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44; te[13] = n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34; te[2] = n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44; te[6] = n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44; te[10] = n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44; te[14] = n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34; te[3] = n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43; te[7] = n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43; te[11] = n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43; te[15] = n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33; const det = n11 * te[0] + n21 * te[4] + n31 * te[8] + n41 * te[12]; if (det === 0) { const msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0"; if (throwOnInvertible || false) { throw new Error(msg); } else { console.warn(msg); } this.identity(); return this; } this.multiplyScalar(1 / det); return this; } isReflected() { const te = this.elements; const m0 = te[0]; const m3 = te[4]; const m6 = te[8]; const m1 = te[1]; const m4 = te[5]; const m7 = te[9]; const m2 = te[2]; const m5 = te[6]; const m8 = te[10]; const determinant = m0 * m4 * m8 + // +aei m1 * m5 * m6 + // +bfg m2 * m3 * m7 - // +cdh m2 * m4 * m6 - // -ceg m1 * m3 * m8 - // -bdi m0 * m5 * m7; // -afh return determinant < 0; } scale(v: { x?: any; y?: any; z?: any }) { const te = this.elements; const { x } = v; const { y } = v; const { z } = v; te[0] *= x; te[4] *= y; te[8] *= z; te[1] *= x; te[5] *= y; te[9] *= z; te[2] *= x; te[6] *= y; te[10] *= z; te[3] *= x; te[7] *= y; te[11] *= z; return this; } getMaxScaleOnAxis() { const te = this.elements; const scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2]; const scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6]; const scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10]; return Math.sqrt(Math.max(scaleXSq, Math.max(scaleYSq, scaleZSq))); } makeFrustum( left: number, right: number, bottom: number, top: number, near: number, far: number ) { const te = this.elements; const x = (2 * near) / (right - left); const y = (2 * near) / (top - bottom); const a = (right + left) / (right - left); const b = (top + bottom) / (top - bottom); const c = -(far + near) / (far - near); const d = (-2 * far * near) / (far - near); te[0] = x; te[4] = 0; te[8] = a; te[12] = 0; te[1] = 0; te[5] = y; te[9] = b; te[13] = 0; te[2] = 0; te[6] = 0; te[10] = c; te[14] = d; te[3] = 0; te[7] = 0; te[11] = -1; te[15] = 0; return this; } makePerspective(fov: number, aspect: number, near: number, far: any) { const ymax = near * Math.tan(degToRad(fov * 0.5)); const ymin = -ymax; const xmin = ymin * aspect; const xmax = ymax * aspect; return this.makeFrustum(xmin, xmax, ymin, ymax, near, far); } makeOrthographic( left: number, right: number, top: number, bottom: number, near: number, far: number ) { const te = this.elements; const w = 1.0 / (right - left); const h = 1.0 / (top - bottom); const p = 1.0 / (far - near); const x = (right + left) * w; const y = (top + bottom) * h; const z = (far + near) * p; te[0] = 2 * w; te[4] = 0; te[8] = 0; te[12] = -x; te[1] = 0; te[5] = 2 * h; te[9] = 0; te[13] = -y; te[2] = 0; te[6] = 0; te[10] = -2 * p; te[14] = -z; te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1; return this; } isEqual(m: { elements: any }) { const me = m.elements; const te = this.elements; if ( te[0] === me[0] && te[4] === me[4] && te[8] === me[8] && te[12] === me[12] && te[1] === me[1] && te[5] === me[5] && te[9] === me[9] && te[13] === me[13] && te[2] === me[2] && te[6] === me[6] && te[10] === me[10] && te[14] === me[14] && te[3] === me[3] && te[7] === me[7] && te[11] === me[11] && te[15] === me[15] ) { return true; } return false; } clone() { const te = this.elements; return new Matrix4( te[0], te[4], te[8], te[12], te[1], te[5], te[9], te[13], te[2], te[6], te[10], te[14], te[3], te[7], te[11], te[15] ); } isIdentity() { const te = this.elements; if ( te[0] === 1 && te[4] === 0 && te[8] === 0 && te[12] === 0 && te[1] === 0 && te[5] === 1 && te[9] === 0 && te[13] === 0 && te[2] === 0 && te[6] === 0 && te[10] === 1 && te[14] === 0 && te[3] === 0 && te[7] === 0 && te[11] === 0 && te[15] === 1 ) { return true; } return false; } // return true if elements are with digits of identity isNearlyIdentity(digits: any) { const snapped = this.clone().snap(digits); return snapped.isIdentity(); } getScale(scale?: Vector3): Vector3 { const te = this.elements; scale = scale || new Vector3(); // grab the axis vectors x.set(te[0], te[1], te[2]); y.set(te[4], te[5], te[6]); z.set(te[8], te[9], te[10]); scale.x = x.length(); scale.y = y.length(); scale.z = z.length(); return scale; } lookAt(eye: Vector3, target: Vector3, up: Vector3) { const te = this.elements; z.subVectors(eye, target).normalize(); if (z.length() === 0) { z.z = 1; } x.crossVectors(up, z).normalize(); if (x.length() === 0) { z.x += 0.0001; x.crossVectors(up, z).normalize(); } y.crossVectors(z, x); te[0] = x.x; te[4] = y.x; te[8] = z.x; te[1] = x.y; te[5] = y.y; te[9] = z.y; te[2] = x.z; te[6] = y.z; te[10] = z.z; return this; } compose(translation: Vector3, rotation: Quaternion, scale: Vector3) { const te = this.elements; mRotation.identity(); mRotation.setRotationFromQuaternion(rotation); mScale.makeScale(scale.x, scale.y, scale.z); this.multiplyMatrices(mRotation, mScale); te[12] = translation.x; te[13] = translation.y; te[14] = translation.z; return this; } } mRotation = new Matrix4(); mScale = new Matrix4(); /** * @interface */ export interface XYZ { /** */ x: number; /** */ y: number; /** */ z: number; } /** @class * @subcategory Math * */ export class Vector3 { // unaccounted for assignents to vector3 properties in other parts of the code // look in glcartoon.js for example color?: any; resi?: any; style?: any; smoothen?: any; atom?: any; skip?: any; atomid?: undefined; x: number; y: number; z: number; constructor(x?: number, y?: number, z?: number) { this.x = x || 0.0; this.y = y || 0.0; this.z = z || 0.0; this.atomid = undefined; } set(x: any, y: any, z: any) { this.x = x; this.y = y; this.z = z; return this; } copy(v: { x: any; y: any; z: any }) { this.x = v.x; this.y = v.y; this.z = v.z; return this; } add(v: { x: any; y: any; z: any }) { this.x += v.x; this.y += v.y; this.z += v.z; return this; } addVectors(a: { x: any; y: any; z: any }, b: { x: any; y: any; z: any }) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; return this; } multiplyVectors( a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number } ) { // elementwise this.x = a.x * b.x; this.y = a.y * b.y; this.z = a.z * b.z; return this; } sub(v: { x: number; y: number; z: number }) { this.x -= v.x; this.y -= v.y; this.z -= v.z; return this; } subVectors( a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number } ) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; return this; } multiplyScalar(s: number) { this.x *= s; this.y *= s; this.z *= s; return this; } divideScalar(s: number) { if (s !== 0) { this.x /= s; this.y /= s; this.z /= s; } else { this.x = 0; this.y = 0; this.z = 0; } return this; } // accumulate maximum max(s: { x: number; y: number; z: number }) { this.x = Math.max(this.x, s.x); this.y = Math.max(this.y, s.y); this.z = Math.max(this.z, s.z); return this; } // accumulate min min(s: { x: number; y: number; z: number }) { this.x = Math.min(this.x, s.x); this.y = Math.min(this.y, s.y); this.z = Math.min(this.z, s.z); return this; } distanceTo(v: any) { return Math.sqrt(this.distanceToSquared(v)); } distanceToSquared(v: { x: number; y: number; z: number }) { const dx = this.x - v.x; const dy = this.y - v.y; const dz = this.z - v.z; return dx * dx + dy * dy + dz * dz; } applyMatrix3(m: { elements: any }) { const { x } = this; const { y } = this; const { z } = this; const e = m.elements; // column major ordering this.x = e[0] * x + e[3] * y + e[6] * z; this.y = e[1] * x + e[4] * y + e[7] * z; this.z = e[2] * x + e[5] * y + e[8] * z; return this; } applyMatrix4(m: { elements: any }) { const { x } = this; const { y } = this; const { z } = this; const e = m.elements; this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; return this; } applyProjection(m: { elements: any }) { // input: Matrix4 projection matrix const { x } = this; const { y } = this; const { z } = this; const e = m.elements; const d = e[3] * x + e[7] * y + e[11] * z + e[15]; this.x = (e[0] * x + e[4] * y + e[8] * z + e[12]) / d; this.y = (e[1] * x + e[5] * y + e[9] * z + e[13]) / d; this.z = (e[2] * x + e[6] * y + e[10] * z + e[14]) / d; return this; } applyQuaternion(q: Quaternion): Vector3 { // save values const { x } = this; const { y } = this; const { z } = this; const qx = q.x; const qy = q.y; const qz = q.z; const qw = q.w; // compute this as // t = 2 * cross(q.xyz, v) // newv = v + q.w * t + cross(q.xyz, t) // this from molecularmusings // http://molecularmusings.wordpress.com/2013/05/24/a-faster-quaternion-vector-multiplication/ const t: { x?: number; y?: number; z?: number } = {}; t.x = 2 * (y * qz - z * qy); t.y = 2 * (z * qx - x * qz); t.z = 2 * (x * qy - y * qx); // cross t with q const t2: { x?: number; y?: number; z?: number } = {}; t2.x = t.y * qz - t.z * qy; t2.y = t.z * qx - t.x * qz; t2.z = t.x * qy - t.y * qx; this.x = x + qw * t.x + t2.x; this.y = y + qw * t.y + t2.y; this.z = z + qw * t.z + t2.z; return this; } negate() { return this.multiplyScalar(-1); } dot(v: Vector3) { return this.x * v.x + this.y * v.y + this.z * v.z; } length() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } lengthSq() { return this.x * this.x + this.y * this.y + this.z * this.z; } normalize() { return this.divideScalar(this.length()); } cross(v: Vector3) { const { x } = this; const { y } = this; const { z } = this; this.x = y * v.z - z * v.y; this.y = z * v.x - x * v.z; this.z = x * v.y - y * v.x; return this; } crossVectors(a: Vector3, b: Vector3) { this.x = a.y * b.z - a.z * b.y; this.y = a.z * b.x - a.x * b.z; this.z = a.x * b.y - a.y * b.x; return this; } equals(b: Vector3) { return this.x == b.x && this.y == b.y && this.z == b.z; } getPositionFromMatrix(m: Matrix4) { this.x = m.elements[12]; this.y = m.elements[13]; this.z = m.elements[14]; return this; } setEulerFromRotationMatrix(m: Matrix4, order: string) { // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) const te = m.elements; const m11 = te[0]; const m12 = te[4]; const m13 = te[8]; // var m21 = te[1]; const m22 = te[5]; const m23 = te[9]; // var m31 = te[2]; const m32 = te[6]; const m33 = te[10]; if (order === undefined || order === "XYZ") { this.y = Math.asin(clamp(m13, -1, 1)); if (Math.abs(m13) < 0.99999) { this.x = Math.atan2(-m23, m33); this.z = Math.atan2(-m12, m11); } else { this.x = Math.atan2(m32, m22); this.z = 0; } } else { console.error( `Error with vector's setEulerFromRotationMatrix: Unknown order: ${order}` ); } return this; } rotateAboutVector(axis: Vector3, ang: number) { axis.normalize(); const cosang = Math.cos(ang); const sinang = Math.sin(ang); // Rodrigues' rotation formula, from wikipedia const term1 = this.clone().multiplyScalar(cosang); const term2 = axis.clone().cross(this).multiplyScalar(sinang); const term3 = axis .clone() .multiplyScalar(axis.clone().dot(this)) .multiplyScalar(1 - cosang); const rot = term1.add(term2).add(term3); this.x = rot.x; this.y = rot.y; this.z = rot.z; return this; } setFromMatrixPosition(m: { elements: any }) { const e = m.elements; this.x = e[12]; this.y = e[13]; this.z = e[14]; return this; } // unproject is defined after Matrix4 transformDirection(m: { elements: any }) { // input: THREE.Matrix4 affine matrix // vector interpreted as a direction const { x } = this; const { y } = this; const { z } = this; const e = m.elements; this.x = e[0] * x + e[4] * y + e[8] * z; this.y = e[1] * x + e[5] * y + e[9] * z; this.z = e[2] * x + e[6] * y + e[10] * z; return this.normalize(); } clone() { return new Vector3(this.x, this.y, this.z); } unproject(camera: { matrixWorld: any; projectionMatrix: any }) { const mat4 = mRotation; mat4.multiplyMatrices( camera.matrixWorld, mat4.getInverse(camera.projectionMatrix) ); return this.applyMatrix4(mat4); } } x = new Vector3(); y = new Vector3(); z = new Vector3(); /* * @interface Matrix3 */ export interface Matrix3 { elements: Float32Array; } /** @class * @subcategory Math * */ export class Matrix3 { constructor( n11: number = 1, n12: number = 0, n13: number = 0, n21: number = 0, n22: number = 1, n23: number = 0, n31: number = 0, n32: number = 0, n33: number = 1 ) { this.elements = new Float32Array(9); this.set(n11, n12, n13, n21, n22, n23, n31, n32, n33); } set( n11: number, n12: number, n13: number, n21: number, n22: number, n23: number, n31: number, n32: number, n33: number ) { const te = this.elements; te[0] = n11; te[3] = n12; te[6] = n13; te[1] = n21; te[4] = n22; te[7] = n23; te[2] = n31; te[5] = n32; te[8] = n33; return this; } identity() { this.set(1, 0, 0, 0, 1, 0, 0, 0, 1); return this; } copy(m: { elements: any }) { const me = m.elements; this.set(me[0], me[3], me[6], me[1], me[4], me[7], me[2], me[5], me[8]); } multiplyScalar(s: number) { const te = this.elements; te[0] *= s; te[3] *= s; te[6] *= s; te[1] *= s; te[4] *= s; te[7] *= s; te[2] *= s; te[5] *= s; te[8] *= s; return this; } getInverse3(matrix: { elements: any }) { // input: Matrix3 const me = matrix.elements; const te = this.elements; te[0] = me[4] * me[8] - me[5] * me[7]; te[3] = me[6] * me[5] - me[3] * me[8]; te[6] = me[3] * me[7] - me[6] * me[4]; te[1] = me[7] * me[2] - me[1] * me[8]; te[4] = me[0] * me[8] - me[6] * me[2]; te[7] = me[1] * me[6] - me[0] * me[7]; te[2] = me[1] * me[5] - me[2] * me[4]; te[5] = me[2] * me[3] - me[0] * me[5]; te[8] = me[0] * me[4] - me[1] * me[3]; const det = me[0] * te[0] + me[3] * te[1] + me[6] * te[2]; this.multiplyScalar(1.0 / det); return this; } getInverse(matrix: { elements: any }, throwOnInvertible: any) { // input: Matrix4 const me = matrix.elements; const te = this.elements; te[0] = me[10] * me[5] - me[6] * me[9]; te[1] = -me[10] * me[1] + me[2] * me[9]; te[2] = me[6] * me[1] - me[2] * me[5]; te[3] = -me[10] * me[4] + me[6] * me[8]; te[4] = me[10] * me[0] - me[2] * me[8]; te[5] = -me[6] * me[0] + me[2] * me[4]; te[6] = me[9] * me[4] - me[5] * me[8]; te[7] = -me[9] * me[0] + me[1] * me[8]; te[8] = me[5] * me[0] - me[1] * me[4]; const det = me[0] * te[0] + me[1] * te[3] + me[2] * te[6]; // no inverse if (det === 0) { const msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0"; if (throwOnInvertible || false) { throw new Error(msg); } else { console.warn(msg); } this.identity(); return this; } this.multiplyScalar(1.0 / det); return this; } // https://en.wikipedia.org/wiki/Determinant getDeterminant() { const m = this.elements; /* * |a b c| |d e f| |g h i| */ const determinant = m[0] * m[4] * m[8] + // +aei m[1] * m[5] * m[6] + // +bfg m[2] * m[3] * m[7] - // +cdh m[2] * m[4] * m[6] - // -ceg m[1] * m[3] * m[8] - // -bdi m[0] * m[5] * m[7]; // -afh return determinant; } transpose() { let tmp: any; const m = this.elements; tmp = m[1]; m[1] = m[3]; m[3] = tmp; tmp = m[2]; m[2] = m[6]; m[6] = tmp; tmp = m[5]; m[5] = m[7]; m[7] = tmp; return this; } clone() { const te = this.elements; return new Matrix3( te[0], te[3], te[6], te[1], te[4], te[7], te[2], te[5], te[8] ); } getMatrix4() { const m = this.elements; return new Matrix4( m[0], m[3], m[6], 0, m[1], m[4], m[7], 0, m[2], m[5], m[8], 0 ); } } /** @class * @subcategory Math * */ export class Ray { origin: Vector3; direction: Vector3; constructor(origin?: Vector3, direction?: Vector3) { this.origin = origin !== undefined ? origin : new Vector3(); this.direction = direction !== undefined ? direction : new Vector3(); } set(origin: Vector3, direction: Vector3) { this.origin.copy(origin); this.direction.copy(direction); return this; } copy(ray: Ray) { this.origin.copy(ray.origin); this.direction.copy(ray.direction); return this; } at(t: number, optionalTarget: Vector3) { const result = optionalTarget || new Vector3(); return result.copy(this.direction).multiplyScalar(t).add(this.origin); } recast(t: any) { const v1 = x; this.origin.copy(this.at(t, v1)); return this; } closestPointToPoint(point: Vector3, optionalTarget: Vector3) { const result = optionalTarget || new Vector3(); result.subVectors(point, this.origin); const directionDistance = result.dot(this.direction); // returns a point on this ray return result .copy(this.direction) .multiplyScalar(directionDistance) .add(this.origin); } distanceToPoint(point: Vector3) { const v1 = x; const directionDistance = v1 .subVectors(point, this.origin) .dot(this.direction); v1.copy(this.direction).multiplyScalar(directionDistance).add(this.origin); return v1.distanceTo(point); } // eslint-disable-next-line class-methods-use-this isIntersectionCylinder() {} isIntersectionSphere(sphere: Sphere) { return this.distanceToPoint(sphere.center) <= sphere.radius; } isIntersectionPlane(plane: any) { const denominator = plane.normal.dot(this.direction); // plane and ray are not perpendicular if (denominator !== 0) return true; if (plane.distanceToPoint(this.origin) === 0) return true; return false; } distanceToPlane(plane: any) { const denominator = plane.normal.dot(this.direction); if (denominator === 0) { // line is coplanar if (plane.distanceToPoint(this.origin) === 0) return 0; // ray is parallel return undefined; } const t = -(this.origin.dot(plane.normal) + plane.constant) / denominator; return t; } intersectPlane(plane: any, optionalTarget: any) { const t = this.distanceToPlane(plane); if (t === undefined) return undefined; return this.at(t, optionalTarget); } applyMatrix4(matrix4: any) { this.direction.add(this.origin).applyMatrix4(matrix4); this.origin.applyMatrix4(matrix4); this.direction.sub(this.origin); return this; } clone() { return new Ray().copy(this); } }