UNPKG

@xeokit/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

366 lines (337 loc) 11.6 kB
/** * Private geometry compression and decompression utilities. */ import {math} from "./math.js"; /** * @private * @param array * @returns {{min: Float32Array, max: Float32Array}} */ function getPositionsBounds(array) { const min = new Float32Array(3); const max = new Float32Array(3); let i, j; for (i = 0; i < 3; i++) { min[i] = Number.MAX_VALUE; max[i] = -Number.MAX_VALUE; } for (i = 0; i < array.length; i += 3) { for (j = 0; j < 3; j++) { min[j] = Math.min(min[j], array[i + j]); max[j] = Math.max(max[j], array[i + j]); } } return { min: min, max: max }; } const createPositionsDecodeMatrix = (function () { const translate = math.mat4(); const scale = math.mat4(); return function (aabb, positionsDecodeMatrix) { positionsDecodeMatrix = positionsDecodeMatrix || math.mat4(); const xmin = aabb[0]; const ymin = aabb[1]; const zmin = aabb[2]; const xwid = (aabb[3] - xmin) || 1; const ywid = (aabb[4] - ymin) || 1; const zwid = (aabb[5] - zmin) || 1; const maxInt = 65535; math.identityMat4(translate); math.translationMat4v(aabb, translate); math.identityMat4(scale); math.scalingMat4v([xwid / maxInt, ywid / maxInt, zwid / maxInt], scale); math.mulMat4(translate, scale, positionsDecodeMatrix); return positionsDecodeMatrix; }; })(); /** * @private */ var compressPositions = (function () { // http://cg.postech.ac.kr/research/mesh_comp_mobile/mesh_comp_mobile_conference.pdf const translate = math.mat4(); const scale = math.mat4(); return function (array, min, max) { const quantized = new Uint16Array(array.length); const multiplier = new Float32Array([ max[0] !== min[0] ? 65535 / (max[0] - min[0]) : 0, max[1] !== min[1] ? 65535 / (max[1] - min[1]) : 0, max[2] !== min[2] ? 65535 / (max[2] - min[2]) : 0 ]); let i; for (i = 0; i < array.length; i += 3) { quantized[i + 0] = Math.max(0, Math.min(65535, Math.floor((array[i + 0] - min[0]) * multiplier[0]))); quantized[i + 1] = Math.max(0, Math.min(65535, Math.floor((array[i + 1] - min[1]) * multiplier[1]))); quantized[i + 2] = Math.max(0, Math.min(65535, Math.floor((array[i + 2] - min[2]) * multiplier[2]))); } math.identityMat4(translate); math.translationMat4v(min, translate); math.identityMat4(scale); math.scalingMat4v([ (max[0] - min[0]) / 65535, (max[1] - min[1]) / 65535, (max[2] - min[2]) / 65535 ], scale); const decodeMat = math.mulMat4(translate, scale, math.identityMat4()); return { quantized: quantized, decodeMatrix: decodeMat }; }; })(); function compressPosition(p, aabb, q) { const multiplier = new Float32Array([ aabb[3] !== aabb[0] ? 65535 / (aabb[3] - aabb[0]) : 0, aabb[4] !== aabb[1] ? 65535 / (aabb[4] - aabb[1]) : 0, aabb[5] !== aabb[2] ? 65535 / (aabb[5] - aabb[2]) : 0 ]); q[0] = Math.max(0, Math.min(65535, Math.floor((p[0] - aabb[0]) * multiplier[0]))); q[1] = Math.max(0, Math.min(65535, Math.floor((p[1] - aabb[1]) * multiplier[1]))); q[2] = Math.max(0, Math.min(65535, Math.floor((p[2] - aabb[2]) * multiplier[2]))); } function decompressPosition(position, decodeMatrix, dest) { dest[0] = position[0] * decodeMatrix[0] + decodeMatrix[12]; dest[1] = position[1] * decodeMatrix[5] + decodeMatrix[13]; dest[2] = position[2] * decodeMatrix[10] + decodeMatrix[14]; return dest; } function decompressAABB(aabb, decodeMatrix, dest = aabb) { dest[0] = aabb[0] * decodeMatrix[0] + decodeMatrix[12]; dest[1] = aabb[1] * decodeMatrix[5] + decodeMatrix[13]; dest[2] = aabb[2] * decodeMatrix[10] + decodeMatrix[14]; dest[3] = aabb[3] * decodeMatrix[0] + decodeMatrix[12]; dest[4] = aabb[4] * decodeMatrix[5] + decodeMatrix[13]; dest[5] = aabb[5] * decodeMatrix[10] + decodeMatrix[14]; return dest; } /** * @private */ function decompressPositions(positions, decodeMatrix, dest = new Float32Array(positions.length)) { for (let i = 0, len = positions.length; i < len; i += 3) { dest[i + 0] = positions[i + 0] * decodeMatrix[0] + decodeMatrix[12]; dest[i + 1] = positions[i + 1] * decodeMatrix[5] + decodeMatrix[13]; dest[i + 2] = positions[i + 2] * decodeMatrix[10] + decodeMatrix[14]; } return dest; } //--------------- UVs -------------------------------------------------------------------------------------------------- /** * @private * @param array * @returns {{min: Float32Array, max: Float32Array}} */ function getUVBounds(array) { const min = new Float32Array(2); const max = new Float32Array(2); let i, j; for (i = 0; i < 2; i++) { min[i] = Number.MAX_VALUE; max[i] = -Number.MAX_VALUE; } for (i = 0; i < array.length; i += 2) { for (j = 0; j < 2; j++) { min[j] = Math.min(min[j], array[i + j]); max[j] = Math.max(max[j], array[i + j]); } } return { min: min, max: max }; } /** * @private */ var compressUVs = (function () { const translate = math.mat3(); const scale = math.mat3(); return function (array, min, max) { const quantized = new Uint16Array(array.length); const multiplier = new Float32Array([ 65535 / (max[0] - min[0]), 65535 / (max[1] - min[1]) ]); let i; for (i = 0; i < array.length; i += 2) { quantized[i + 0] = Math.max(0, Math.min(65535, Math.floor((array[i + 0] - min[0]) * multiplier[0]))); quantized[i + 1] = Math.max(0, Math.min(65535, Math.floor((array[i + 1] - min[1]) * multiplier[1]))); } math.identityMat3(translate); math.translationMat3v(min, translate); math.identityMat3(scale); math.scalingMat3v([ (max[0] - min[0]) / 65535, (max[1] - min[1]) / 65535 ], scale); const decodeMat = math.mulMat3(translate, scale, math.identityMat3()); return { quantized: quantized, decodeMatrix: decodeMat }; }; })(); //--------------- Normals ---------------------------------------------------------------------------------------------- /** * @private */ function compressNormals(array) { // http://jcgt.org/published/0003/02/01/ // Note: three elements for each encoded normal, in which the last element in each triplet is redundant. // This is to work around a mysterious WebGL issue where 2-element normals just wouldn't work in the shader :/ const encoded = new Int8Array(array.length); let oct, dec, best, currentCos, bestCos; for (let i = 0; i < array.length; i += 3) { // Test various combinations of ceil and floor // to minimize rounding errors best = oct = octEncodeVec3(array, i, "floor", "floor"); dec = octDecodeVec2(oct); currentCos = bestCos = dot(array, i, dec); oct = octEncodeVec3(array, i, "ceil", "floor"); dec = octDecodeVec2(oct); currentCos = dot(array, i, dec); if (currentCos > bestCos) { best = oct; bestCos = currentCos; } oct = octEncodeVec3(array, i, "floor", "ceil"); dec = octDecodeVec2(oct); currentCos = dot(array, i, dec); if (currentCos > bestCos) { best = oct; bestCos = currentCos; } oct = octEncodeVec3(array, i, "ceil", "ceil"); dec = octDecodeVec2(oct); currentCos = dot(array, i, dec); if (currentCos > bestCos) { best = oct; bestCos = currentCos; } encoded[i] = best[0]; encoded[i + 1] = best[1]; } return encoded; } /** * @private */ function octEncodeVec3(array, i, xfunc, yfunc) { // Oct-encode single normal vector in 2 bytes let x = array[i] / (Math.abs(array[i]) + Math.abs(array[i + 1]) + Math.abs(array[i + 2])); let y = array[i + 1] / (Math.abs(array[i]) + Math.abs(array[i + 1]) + Math.abs(array[i + 2])); if (array[i + 2] < 0) { let tempx = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1); let tempy = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1); x = tempx; y = tempy; } return new Int8Array([ Math[xfunc](x * 127.5 + (x < 0 ? -1 : 0)), Math[yfunc](y * 127.5 + (y < 0 ? -1 : 0)) ]); } /** * Decode an oct-encoded normal */ function octDecodeVec2(oct) { let x = oct[0]; let y = oct[1]; x /= x < 0 ? 127 : 128; y /= y < 0 ? 127 : 128; const z = 1 - Math.abs(x) - Math.abs(y); if (z < 0) { x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1); y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1); } const length = Math.sqrt(x * x + y * y + z * z); return [ x / length, y / length, z / length ]; } /** * Dot product of a normal in an array against a candidate decoding * @private */ function dot(array, i, vec3) { return array[i] * vec3[0] + array[i + 1] * vec3[1] + array[i + 2] * vec3[2]; } /** * @private */ function decompressUV(uv, decodeMatrix, dest) { dest[0] = uv[0] * decodeMatrix[0] + decodeMatrix[6]; dest[1] = uv[1] * decodeMatrix[4] + decodeMatrix[7]; } /** * @private */ function decompressUVs(uvs, decodeMatrix, dest = new Float32Array(uvs.length)) { for (let i = 0, len = uvs.length; i < len; i += 3) { dest[i + 0] = uvs[i + 0] * decodeMatrix[0] + decodeMatrix[6]; dest[i + 1] = uvs[i + 1] * decodeMatrix[4] + decodeMatrix[7]; } return dest; } /** * @private */ function decompressNormal(oct, result) { let x = oct[0]; let y = oct[1]; x = (2 * x + 1) / 255; y = (2 * y + 1) / 255; const z = 1 - Math.abs(x) - Math.abs(y); if (z < 0) { x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1); y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1); } const length = Math.sqrt(x * x + y * y + z * z); result[0] = x / length; result[1] = y / length; result[2] = z / length; return result; } /** * @private */ function decompressNormals(octs, result) { for (let i = 0, j = 0, len = octs.length; i < len; i += 2) { let x = octs[i + 0]; let y = octs[i + 1]; x = (2 * x + 1) / 255; y = (2 * y + 1) / 255; const z = 1 - Math.abs(x) - Math.abs(y); if (z < 0) { x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1); y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1); } const length = Math.sqrt(x * x + y * y + z * z); result[j + 0] = x / length; result[j + 1] = y / length; result[j + 2] = z / length; j += 3; } return result; } /** * @private */ const geometryCompressionUtils = { getPositionsBounds: getPositionsBounds, createPositionsDecodeMatrix: createPositionsDecodeMatrix, compressPositions: compressPositions, compressPosition:compressPosition, decompressPositions: decompressPositions, decompressPosition: decompressPosition, decompressAABB: decompressAABB, getUVBounds: getUVBounds, compressUVs: compressUVs, decompressUVs: decompressUVs, decompressUV: decompressUV, compressNormals: compressNormals, decompressNormals: decompressNormals, decompressNormal: decompressNormal }; export {geometryCompressionUtils};