src/XKTModel/lib/geometryCompression.js
import {math} from "../../lib/math.js";
function quantizePositions (positions, lenPositions, aabb, quantizedPositions) {
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;
const xMultiplier = maxInt / xwid;
const yMultiplier = maxInt / ywid;
const zMultiplier = maxInt / zwid;
const verify = (num) => num >= 0 ? num : 0;
for (let i = 0; i < lenPositions; i += 3) {
quantizedPositions[i + 0] = Math.max(0, Math.min(65535,Math.floor(verify(positions[i + 0] - xmin) * xMultiplier)));
quantizedPositions[i + 1] = Math.max(0, Math.min(65535,Math.floor(verify(positions[i + 1] - ymin) * yMultiplier)));
quantizedPositions[i + 2] = Math.max(0, Math.min(65535,Math.floor(verify(positions[i + 2] - zmin) * zMultiplier)));
}
}
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])));
}
var 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;
};
})();
function transformAndOctEncodeNormals(modelNormalMatrix, normals, lenNormals, compressedNormals, lenCompressedNormals) {
// http://jcgt.org/published/0003/02/01/
let oct, dec, best, currentCos, bestCos;
let i, ei;
let localNormal = math.vec3();
let worldNormal = math.vec3();
for (i = 0; i < lenNormals; i += 3) {
localNormal[0] = normals[i];
localNormal[1] = normals[i + 1];
localNormal[2] = normals[i + 2];
math.transformVec3(modelNormalMatrix, localNormal, worldNormal);
math.normalizeVec3(worldNormal, worldNormal);
// Test various combinations of ceil and floor to minimize rounding errors
best = oct = octEncodeVec3(worldNormal, 0, "floor", "floor");
dec = octDecodeVec2(oct);
currentCos = bestCos = dot(worldNormal, 0, dec);
oct = octEncodeVec3(worldNormal, 0, "ceil", "floor");
dec = octDecodeVec2(oct);
currentCos = dot(worldNormal, 0, dec);
if (currentCos > bestCos) {
best = oct;
bestCos = currentCos;
}
oct = octEncodeVec3(worldNormal, 0, "floor", "ceil");
dec = octDecodeVec2(oct);
currentCos = dot(worldNormal, 0, dec);
if (currentCos > bestCos) {
best = oct;
bestCos = currentCos;
}
oct = octEncodeVec3(worldNormal, 0, "ceil", "ceil");
dec = octDecodeVec2(oct);
currentCos = dot(worldNormal, 0, dec);
if (currentCos > bestCos) {
best = oct;
bestCos = currentCos;
}
compressedNormals[lenCompressedNormals + i + 0] = best[0];
compressedNormals[lenCompressedNormals + i + 1] = best[1];
compressedNormals[lenCompressedNormals + i + 2] = 0.0; // Unused
}
lenCompressedNormals += lenNormals;
return lenCompressedNormals;
}
function octEncodeNormals(normals, lenNormals, compressedNormals, lenCompressedNormals) { // http://jcgt.org/published/0003/02/01/
let oct, dec, best, currentCos, bestCos;
for (let i = 0; i < lenNormals; i += 3) {
// Test various combinations of ceil and floor to minimize rounding errors
best = oct = octEncodeVec3(normals, i, "floor", "floor");
dec = octDecodeVec2(oct);
currentCos = bestCos = dot(normals, i, dec);
oct = octEncodeVec3(normals, i, "ceil", "floor");
dec = octDecodeVec2(oct);
currentCos = dot(normals, i, dec);
if (currentCos > bestCos) {
best = oct;
bestCos = currentCos;
}
oct = octEncodeVec3(normals, i, "floor", "ceil");
dec = octDecodeVec2(oct);
currentCos = dot(normals, i, dec);
if (currentCos > bestCos) {
best = oct;
bestCos = currentCos;
}
oct = octEncodeVec3(normals, i, "ceil", "ceil");
dec = octDecodeVec2(oct);
currentCos = dot(normals, i, dec);
if (currentCos > bestCos) {
best = oct;
bestCos = currentCos;
}
compressedNormals[lenCompressedNormals + i + 0] = best[0];
compressedNormals[lenCompressedNormals + i + 1] = best[1];
compressedNormals[lenCompressedNormals + i + 2] = 0.0; // Unused
}
lenCompressedNormals += lenNormals;
return lenCompressedNormals;
}
/**
* @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
*/
const geometryCompression = {
quantizePositions,
compressPosition,
createPositionsDecodeMatrix,
transformAndOctEncodeNormals,
octEncodeNormals,
};
export {geometryCompression}