src/viewer/scene/math/geometryCompressionUtils.js
/**
* 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;
const ywid = aabb[4] - ymin;
const zwid = aabb[5] - zmin;
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};