@inweb/viewer-visualize
Version:
JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS
374 lines (316 loc) • 10.2 kB
text/typescript
/**
* 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;
}