UNPKG

@inweb/viewer-visualize

Version:

JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS

374 lines (316 loc) 10.2 kB
/** * Convert world coordinates to screen normalized (-1, 1) * * @param viewMatrix Camera world matrix * @param projectionMatrix Camera projection matrix * @param worldPoint World point */ export function worldToScreenNormalized( viewMatrix: Array<number>, projectionMatrix: Array<number>, worldPoint: [x: number, y: number, z: number] ): [x: number, y: number, z: number] { function applyMatrixToPoint3(matrix, point: [x: number, y: number, z: number]): [x: number, y: number, z: number] { const [x, y, z] = point; const e = matrix; const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]); return [ (e[0] * x + e[4] * y + e[8] * z + e[12]) * w, (e[1] * x + e[5] * y + e[9] * z + e[13]) * w, (e[2] * x + e[6] * y + e[10] * z + e[14]) * w, ]; } const [x, y, z] = applyMatrixToPoint3(projectionMatrix, applyMatrixToPoint3(invertMatrix(viewMatrix), worldPoint)); return [x, y, z]; } export function multiplyMatrices(a: Array<number>, b: Array<number>): Array<number> { const ae = a; const be = b; const te = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; const a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12]; const a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13]; const a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14]; const a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15]; const b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12]; const b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13]; const b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14]; const b41 = be[3], b42 = be[7], b43 = be[11], 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 te; } /** * @param matrix * @returns Matrix */ export function invertMatrix(matrix: Array<number>): Array<number> { const te = matrix, n11 = te[0], n21 = te[1], n31 = te[2], n41 = te[3], n12 = te[4], n22 = te[5], n32 = te[6], n42 = te[7], n13 = te[8], n23 = te[9], n33 = te[10], n43 = te[11], n14 = te[12], n24 = te[13], n34 = te[14], n44 = te[15], t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; if (det === 0) return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; const detInv = 1 / det; return [ t11 * detInv, (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * detInv, (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * detInv, (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * detInv, t12 * detInv, (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * detInv, (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * detInv, (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * detInv, t13 * detInv, (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * detInv, (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * detInv, (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * detInv, t14 * detInv, (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * detInv, (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * detInv, (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * detInv, ]; } /** * Convert world coordinates to screen (screenWidth, screenHeight) * * @param viewMatrix Camera world matrix * @param projectionMatrix Camera projection matrix * @param worldPoint World point * @param screenWidth Screen width * @param screenHeight Screen height */ export function worldToScreen( viewMatrix: Array<number>, projectionMatrix: Array<number>, worldPoint: [x: number, y: number, z: number], screenWidth: number, screenHeight: number ): number[] { const widthHalf = screenWidth / 2; const heightHalf = screenHeight / 2; const [x, y] = worldToScreenNormalized(viewMatrix, projectionMatrix, worldPoint); const p = [x * widthHalf + widthHalf, -(y * heightHalf) + heightHalf]; return p; } /** * @param a * @returns */ function lengthVector(a: { x: number; y: number; z: number }): number { return Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z); } function normalizeVector(a: { x: number; y: number; z: number }): { x: number; y: number; z: number } { const len = lengthVector(a); if (len === 0) return a; return { x: a.x / len, y: a.y / len, z: a.z / len, }; } function crossVectors(a, b) { const ax = a.x, ay = a.y, az = a.z; const bx = b.x, by = b.y, bz = b.z; return { x: ay * bz - az * by, y: az * bx - ax * bz, z: ax * by - ay * bx, }; } function normalizedProjection( width, height, nearClipPlaneDist, farClipPlaneDist, normalizedCenter, normalizedWidth, normalizedHeight, normalizedDepth ) { const mtx = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; mtx[0] = -normalizedWidth / width; mtx[1] = 0; mtx[2] = 0.0; mtx[3] = normalizedCenter.x; mtx[4] = 0.0; mtx[5] = -normalizedHeight / height; mtx[6] = 0.0; mtx[7] = normalizedCenter.y; mtx[8] = 0; mtx[9] = 0.0; mtx[10] = normalizedDepth / (farClipPlaneDist - nearClipPlaneDist); mtx[11] = -mtx[10] * nearClipPlaneDist; mtx[12] = mtx[13] = mtx[14] = 0.0; mtx[15] = 1.0; return mtx; } export function createOrthoProjectionMatrix(camera, width, height) { const nearClipPlaneDist = 0.1; const farClipPlaneDist = 1000; const fieldWidth = camera.field_width; const fieldHeight = camera.field_height; let viewportNormalizedWidth = 2; let viewportNormalizedHeight = 2; if (width > height) { const aspect = height / width; const aspectFiled = fieldHeight / fieldWidth; viewportNormalizedWidth = (2 / aspectFiled) * aspect; viewportNormalizedHeight = 2; } else { const aspect = width / height; const aspectFiled = fieldWidth / fieldHeight; viewportNormalizedWidth = 2; viewportNormalizedHeight = (2 / aspectFiled) * aspect; } const viewportNormalizedCenter = { x: 0, y: 0, }; const projectionMatrix = normalizedProjection( fieldWidth, fieldHeight, nearClipPlaneDist, farClipPlaneDist, viewportNormalizedCenter, viewportNormalizedWidth, viewportNormalizedHeight, 1.0 ); return projectionMatrix; } export function createViewMatrix(camera) { const position = camera.view_point; const viewMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, position.x, position.y, position.z, 1]; const target = addVectors(camera.direction, position); matrixLookAt(viewMatrix, position, target, camera.up_vector); return viewMatrix; } export function bcfWorldToScreenFromCamera( camera, canvas: HTMLCanvasElement, point: [x: number, y: number, z: number] ) { const { width, height } = canvas; if (lengthVector(camera.direction) >= 1.0001) { camera = { ...camera, direction: normalizeVector(subVectors(camera.direction, camera.view_point)), }; } const projectionMatrix = createOrthoProjectionMatrix(camera, width, height); const viewMatrix = createViewMatrix(camera); return worldToScreen(viewMatrix, projectionMatrix, point, width, height); } function subVectors( a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number } ): { x: number; y: number; z: number } { return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; } function addVectors( a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number } ): { x: number; y: number; z: number } { return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; } export function matrixLookAt(matrix, eye, target, up) { let z = subVectors(eye, target); if (lengthVector(z) === 0) { z.z = 1; } z = normalizeVector(z); let x = crossVectors(z, up); if (lengthVector(x) === 0) { if (Math.abs(up.z) === 1) { z.x += 0.0001; } else { z.z += 0.0001; } z = normalizeVector(z); x = crossVectors(up, z); } x = normalizeVector(x); const y = crossVectors(z, x); const m = matrix; m[0] = x.x; m[4] = y.x; m[8] = z.x; m[1] = x.y; m[5] = y.y; m[9] = z.y; m[2] = x.z; m[6] = y.z; m[10] = z.z; return m; }