src/viewer/scene/model/rebucketPositions.js
/**
* @author https://github.com/tmarti, with support from https://tribia.com/
* @license MIT
**/
const MAX_RE_BUCKET_FAN_OUT = 8;
let bucketsForIndices = null;
function compareBuckets(a, b) {
const aa = a * 3;
const bb = b * 3;
let aa1, aa2, aa3, bb1, bb2, bb3;
const minBucketA = Math.min(
aa1 = bucketsForIndices[aa],
aa2 = bucketsForIndices[aa + 1],
aa3 = bucketsForIndices[aa + 2]
);
const minBucketB = Math.min(
bb1 = bucketsForIndices[bb],
bb2 = bucketsForIndices[bb + 1],
bb3 = bucketsForIndices[bb + 2]
);
if (minBucketA !== minBucketB) {
return minBucketA - minBucketB;
}
const maxBucketA = Math.max(aa1, aa2, aa3);
const maxBucketB = Math.max(bb1, bb2, bb3,);
if (maxBucketA !== maxBucketB) {
return maxBucketA - maxBucketB;
}
return 0;
}
function preSortIndices(indices, bitsPerBucket) {
const seq = new Int32Array(indices.length / 3);
for (let i = 0, len = seq.length; i < len; i++) {
seq[i] = i;
}
bucketsForIndices = new Int32Array(indices.length);
for (let i = 0, len = indices.length; i < len; i++) {
bucketsForIndices[i] = indices[i] >> bitsPerBucket;
}
seq.sort(compareBuckets);
const sortedIndices = new Int32Array(indices.length);
for (let i = 0, len = seq.length; i < len; i++) {
sortedIndices[i * 3 + 0] = indices[seq[i] * 3 + 0];
sortedIndices[i * 3 + 1] = indices[seq[i] * 3 + 1];
sortedIndices[i * 3 + 2] = indices[seq[i] * 3 + 2];
}
return sortedIndices;
}
let compareEdgeIndices = null;
function compareIndices(a, b) {
let retVal = compareEdgeIndices[a * 2] - compareEdgeIndices[b * 2];
if (retVal !== 0) {
return retVal;
}
return compareEdgeIndices[a * 2 + 1] - compareEdgeIndices[b * 2 + 1];
}
function preSortEdgeIndices(edgeIndices) {
if ((edgeIndices || []).length === 0) {
return [];
}
let seq = new Int32Array(edgeIndices.length / 2);
for (let i = 0, len = seq.length; i < len; i++) {
seq[i] = i;
}
for (let i = 0, j = 0, len = edgeIndices.length; i < len; i += 2) {
if (edgeIndices[i] > edgeIndices[i + 1]) {
let tmp = edgeIndices[i];
edgeIndices[i] = edgeIndices[i + 1];
edgeIndices[i + 1] = tmp;
}
}
compareEdgeIndices = new Int32Array(edgeIndices);
seq.sort(compareIndices);
const sortedEdgeIndices = new Int32Array(edgeIndices.length);
for (let i = 0, len = seq.length; i < len; i++) {
sortedEdgeIndices[i * 2 + 0] = edgeIndices[seq[i] * 2 + 0];
sortedEdgeIndices[i * 2 + 1] = edgeIndices[seq[i] * 2 + 1];
}
return sortedEdgeIndices;
}
/**
* @param {{positionsCompressed: number[], indices: number[], edgeIndices: number[]}} mesh
* @param {number} bitsPerBucket
* @param {boolean} checkResult
*
* @returns {{positionsCompressed: number[], indices: number[], edgeIndices: number[]}[]}
*/
function rebucketPositions(mesh, bitsPerBucket, checkResult = false) {
const positionsCompressed = (mesh.positionsCompressed || []);
const indices = preSortIndices(mesh.indices || [], bitsPerBucket);
const edgeIndices = preSortEdgeIndices(mesh.edgeIndices || []);
function edgeSearch(el0, el1) { // Code adapted from https://stackoverflow.com/questions/22697936/binary-search-in-javascript
if (el0 > el1) {
let tmp = el0;
el0 = el1;
el1 = tmp;
}
function compare_fn(a, b) {
if (a !== el0) {
return el0 - a;
}
if (b !== el1) {
return el1 - b;
}
return 0;
}
let m = 0;
let n = (edgeIndices.length >> 1) - 1;
while (m <= n) {
const k = (n + m) >> 1;
const cmp = compare_fn(edgeIndices[k * 2], edgeIndices[k * 2 + 1]);
if (cmp > 0) {
m = k + 1;
} else if (cmp < 0) {
n = k - 1;
} else {
return k;
}
}
return -m - 1;
}
const alreadyOutputEdgeIndices = new Int32Array(edgeIndices.length / 2);
alreadyOutputEdgeIndices.fill(0);
const numPositions = positionsCompressed.length / 3;
if (numPositions > ((1 << bitsPerBucket) * MAX_RE_BUCKET_FAN_OUT)) {
return [mesh];
}
const bucketIndicesRemap = new Int32Array(numPositions);
bucketIndicesRemap.fill(-1);
const buckets = [];
function addEmptyBucket() {
bucketIndicesRemap.fill(-1);
const newBucket = {
positionsCompressed: [],
indices: [],
edgeIndices: [],
maxNumPositions: (1 << bitsPerBucket) - bitsPerBucket,
numPositions: 0,
bucketNumber: buckets.length,
};
buckets.push(newBucket);
return newBucket;
}
let currentBucket = addEmptyBucket();
// let currentBucket = 0;
let retVal = 0;
for (let i = 0, len = indices.length; i < len; i += 3) {
let additonalPositionsInBucket = 0;
const ii0 = indices[i];
const ii1 = indices[i + 1];
const ii2 = indices[i + 2];
if (bucketIndicesRemap[ii0] === -1) {
additonalPositionsInBucket++;
}
if (bucketIndicesRemap[ii1] === -1) {
additonalPositionsInBucket++;
}
if (bucketIndicesRemap[ii2] === -1) {
additonalPositionsInBucket++;
}
if ((additonalPositionsInBucket + currentBucket.numPositions) > currentBucket.maxNumPositions) {
currentBucket = addEmptyBucket();
}
if (currentBucket.bucketNumber > MAX_RE_BUCKET_FAN_OUT) {
return [mesh];
}
if (bucketIndicesRemap[ii0] === -1) {
bucketIndicesRemap[ii0] = currentBucket.numPositions++;
currentBucket.positionsCompressed.push(positionsCompressed[ii0 * 3]);
currentBucket.positionsCompressed.push(positionsCompressed[ii0 * 3 + 1]);
currentBucket.positionsCompressed.push(positionsCompressed[ii0 * 3 + 2]);
}
if (bucketIndicesRemap[ii1] === -1) {
bucketIndicesRemap[ii1] = currentBucket.numPositions++;
currentBucket.positionsCompressed.push(positionsCompressed[ii1 * 3]);
currentBucket.positionsCompressed.push(positionsCompressed[ii1 * 3 + 1]);
currentBucket.positionsCompressed.push(positionsCompressed[ii1 * 3 + 2]);
}
if (bucketIndicesRemap[ii2] === -1) {
bucketIndicesRemap[ii2] = currentBucket.numPositions++;
currentBucket.positionsCompressed.push(positionsCompressed[ii2 * 3]);
currentBucket.positionsCompressed.push(positionsCompressed[ii2 * 3 + 1]);
currentBucket.positionsCompressed.push(positionsCompressed[ii2 * 3 + 2]);
}
currentBucket.indices.push(bucketIndicesRemap[ii0]);
currentBucket.indices.push(bucketIndicesRemap[ii1]);
currentBucket.indices.push(bucketIndicesRemap[ii2]);
// Check possible edge1
let edgeIndex;
if ((edgeIndex = edgeSearch(ii0, ii1)) >= 0) {
if (alreadyOutputEdgeIndices[edgeIndex] === 0) {
alreadyOutputEdgeIndices[edgeIndex] = 1;
currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]);
currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]);
}
}
if ((edgeIndex = edgeSearch(ii0, ii2)) >= 0) {
if (alreadyOutputEdgeIndices[edgeIndex] === 0) {
alreadyOutputEdgeIndices[edgeIndex] = 1;
currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]);
currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]);
}
}
if ((edgeIndex = edgeSearch(ii1, ii2)) >= 0) {
if (alreadyOutputEdgeIndices[edgeIndex] === 0) {
alreadyOutputEdgeIndices[edgeIndex] = 1;
currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]);
currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]);
}
}
}
const prevBytesPerIndex = bitsPerBucket / 8 * 2;
const newBytesPerIndex = bitsPerBucket / 8;
const originalSize = positionsCompressed.length * 2 + (indices.length + edgeIndices.length) * prevBytesPerIndex;
let newSize = 0;
let newPositions = -positionsCompressed.length / 3;
buckets.forEach(bucket => {
newSize += bucket.positionsCompressed.length * 2 + (bucket.indices.length + bucket.edgeIndices.length) * newBytesPerIndex;
newPositions += bucket.positionsCompressed.length / 3;
});
if (newSize > originalSize) {
return [mesh];
}
if (checkResult) {
doCheckResult(buckets, mesh);
}
return buckets;
}
function unbucket(buckets) {
const positionsCompressed = [];
const indices = [];
const edgeIndices = [];
let positionsBase = 0;
buckets.forEach(bucket => {
bucket.positionsCompressed.forEach(coord => {
positionsCompressed.push(coord);
});
bucket.indices.forEach(index => {
indices.push(index + positionsBase);
});
bucket.edgeIndices.forEach(edgeIndex => {
edgeIndices.push(edgeIndex + positionsBase);
});
positionsBase += positionsCompressed.length / 3;
});
return {positionsCompressed, indices, edgeIndices};
}
function doCheckResult(buckets, mesh) {
const meshDict = {};
const edgesDict = {};
let edgeIndicesCount = 0;
buckets.forEach(bucket => {
const indices = bucket.indices;
const edgeIndices = bucket.edgeIndices;
const positionsCompressed = bucket.positionsCompressed;
for (let i = 0, len = indices.length; i < len; i += 3) {
const key = positionsCompressed[indices[i] * 3] + "_" + positionsCompressed[indices[i] * 3 + 1] + "_" + positionsCompressed[indices[i] * 3 + 2] + "/" +
positionsCompressed[indices[i + 1] * 3] + "_" + positionsCompressed[indices[i + 1] * 3 + 1] + "_" + positionsCompressed[indices[i + 1] * 3 + 2] + "/" +
positionsCompressed[indices[i + 2] * 3] + "_" + positionsCompressed[indices[i + 2] * 3 + 1] + "_" + positionsCompressed[indices[i + 2] * 3 + 2];
meshDict[key] = true;
}
edgeIndicesCount += bucket.edgeIndices.length / 2;
for (let i = 0, len = edgeIndices.length; i < len; i += 2) {
const key = positionsCompressed[edgeIndices[i] * 3] + "_" + positionsCompressed[edgeIndices[i] * 3 + 1] + "_" + positionsCompressed[edgeIndices[i] * 3 + 2] + "/" +
positionsCompressed[edgeIndices[i + 1] * 3] + "_" + positionsCompressed[edgeIndices[i + 1] * 3 + 1] + "_" + positionsCompressed[edgeIndices[i + 1] * 3 + 2] + "/";
edgesDict[key] = true;
}
});
{
const indices = mesh.indices;
const edgeIndices = mesh.edgeIndices;
const positionsCompressed = mesh.positionsCompressed;
for (let i = 0, len = indices.length; i < len; i += 3) {
const key = positionsCompressed[indices[i] * 3] + "_" + positionsCompressed[indices[i] * 3 + 1] + "_" + positionsCompressed[indices[i] * 3 + 2] + "/" +
positionsCompressed[indices[i + 1] * 3] + "_" + positionsCompressed[indices[i + 1] * 3 + 1] + "_" + positionsCompressed[indices[i + 1] * 3 + 2] + "/" +
positionsCompressed[indices[i + 2] * 3] + "_" + positionsCompressed[indices[i + 2] * 3 + 1] + "_" + positionsCompressed[indices[i + 2] * 3 + 2];
if (!(key in meshDict)) {
console.log("Not found " + key);
throw "Ohhhh!";
}
}
// for (var i = 0, len = edgeIndices.length; i < len; i+=2)
// {
// var key = positionsCompressed[edgeIndices[i]*3] + "_" + positionsCompressed[edgeIndices[i]*3+1] + "_" + positionsCompressed[edgeIndices[i]*3+2] + "/" +
// positionsCompressed[edgeIndices[i+1]*3] + "_" + positionsCompressed[edgeIndices[i+1]*3+1] + "_" + positionsCompressed[edgeIndices[i+1]*3+2] + "/";
// if (!(key in edgesDict)) {
// var key2 = edgeIndices[i] + "_" + edgeIndices[i+1];
// console.log (" - Not found " + key);
// console.log (" - Not found " + key2);
// // throw "Ohhhh2!";
// }
// }
}
}
export {rebucketPositions}