checkpoint rebuilder

This commit is contained in:
Stewart Allen 2022-01-27 11:34:37 -05:00
commit d634a0ce1c
4 changed files with 116 additions and 55 deletions

View file

@ -65,6 +65,7 @@
t = p.x; t = p.x;
p.x = p.z; p.x = p.z;
p.z = t; p.z = t;
return this;
}; };
PRO.swapYZ = function() { PRO.swapYZ = function() {
@ -72,6 +73,7 @@
t = p.y; t = p.y;
p.y = p.z; p.y = p.z;
p.z = t; p.z = t;
return this;
}; };
PRO.round = function(precision) { PRO.round = function(precision) {

View file

@ -27,12 +27,14 @@
zMax = options.zmax || 0, zMax = options.zmax || 0,
zInc = options.zinc || 0, zInc = options.zinc || 0,
zIndexes = options.indices || [], zIndexes = options.indices || [],
minStep = options.minstep || 0,
zFlat = {}, // map area of z index flat areas zFlat = {}, // map area of z index flat areas
zList = {}, // fast map of z indexes zList = {}, // fast map of z indexes
zScale, // bucket span in z units zScale, // bucket span in z units
zSum = 0.0, // sanity check that points enclose non-zere volume zSum = 0.0, // sanity check that points enclose non-zere volume
buckets = [], // banded/grouped faces to speed up slice/search buckets = [], // banded/grouped faces to speed up slice/search
onupdate = options.onupdate || function() {}, onupdate = options.onupdate || function() {},
{ debug, flat, autoDim } = options,
i, j, p1, p2, p3; i, j, p1, p2, p3;
if (!(points && points.length)) { if (!(points && points.length)) {
@ -40,25 +42,35 @@
} }
// convert threejs position array into points array // convert threejs position array into points array
if (options.flat) { if (flat) {
let array = []; let array = [];
for (i=0, j=points.length; i<j; ) { for (i=0, j=points.length; i<j; ) {
array.push(newPoint(points[i++], points[i++], points[i++])); array.push(newPoint(
points[i++].round(3),
points[i++].round(3),
points[i++].round(3)
));
} }
points = array; points = array;
} else {
// round points
for (i = 0; i < points.length; i++) {
points[i] = points[i].round(3);
}
} }
// gather z-index stats // gather z-index stats
for (i = 0; i < points.length;) { for (i = 0; i < points.length;) {
p1 = points[i++].round(3); p1 = points[i++];
p2 = points[i++].round(3); p2 = points[i++];
p3 = points[i++].round(3); p3 = points[i++];
// used to calculate buckets // used to calculate buckets (rough sum of z span)
zSum += (Math.abs(p1.z - p2.z) + Math.abs(p2.z - p3.z) + Math.abs(p3.z - p1.z)); zSum += (Math.abs(p1.z - p2.z) + Math.abs(p2.z - p3.z) + Math.abs(p3.z - p1.z));
// use co-flat and co-line detection to adjust slice Z // use co-flat and co-line detection to adjust slice Z
if (p1.z === p2.z && p2.z === p3.z && p1.z >= zMin) { if (p1.z === p2.z && p2.z === p3.z && p1.z >= zMin) {
// detect faces co-planar with Z and sum the enclosed area // detect faces co-planar with Z and sum the enclosed area
let zkey = p1.z.toFixed(5), let zkey = p1.z,
area = Math.abs(util.area2(p1,p2,p3)) / 2; area = Math.abs(util.area2(p1,p2,p3)) / 2;
if (!zFlat[zkey]) { if (!zFlat[zkey]) {
zFlat[zkey] = area; zFlat[zkey] = area;
@ -66,7 +78,7 @@
zFlat[zkey] += area; zFlat[zkey] += area;
} }
} }
if (options.autoDim) { if (autoDim) {
zMin = Math.min(zMin, p1.z, p2.z, p3.z); zMin = Math.min(zMin, p1.z, p2.z, p3.z);
zMax = Math.max(zMax, p1.z, p2.z, p3.z); zMax = Math.max(zMax, p1.z, p2.z, p3.z);
} }
@ -81,6 +93,17 @@
} }
} else { } else {
zIndexes = Object.values(zList).sort((a,b) => a - b); zIndexes = Object.values(zList).sort((a,b) => a - b);
if (minStep > 0) {
let lastOut;
zIndexes = zIndexes.filter(v => {
if (lastOut !== undefined && v - lastOut < minStep) {
return false;
} else {
lastOut = v;
return true;
}
});
}
} }
/** short-circuit for microscopic and invalid objects */ /** short-circuit for microscopic and invalid objects */
@ -92,30 +115,38 @@
* bucket polygons into z-bounded groups (inside or crossing) * bucket polygons into z-bounded groups (inside or crossing)
* to reduce the search space in complex models * to reduce the search space in complex models
*/ */
let zSpan = zMax - zMin;
let zSpanAvg = zSum / points.length;
let bucketCount = options.bucket !== false ? let bucketCount = options.bucket !== false ?
Math.max(1, Math.ceil(zMax / (zSum / points.length)) - 1) : 1; Math.max(1, Math.floor(zSpan / zSpanAvg)) : 1;
zScale = 1 / (zMax / bucketCount); zScale = 1 / (zSpan / bucketCount);
// console.log({zMin, zMax, zIndexes, points, bucketCount, zScale, zSum}); if (debug) {
console.log({
zMin, zMax, zIndexes, zScale, zSum, zSpanAvg,
points, bucketCount,
options, buckets
});
}
// create empty buckets
for (i = 0; i < bucketCount + 1; i++) {
buckets.push({ points: [], slices: [] });
}
if (bucketCount > 1) { if (bucketCount > 1) {
// create empty buckets
for (i = 0; i < bucketCount + 1; i++) {
buckets.push({ points: [], slices: [] });
}
// copy triples into all matching z-buckets // copy triples into all matching z-buckets
for (i = 0; i < points.length;) { for (i = 0; i < points.length;) {
p1 = points[i++]; p1 = points[i++];
p2 = points[i++]; p2 = points[i++];
p3 = points[i++]; p3 = points[i++];
let zm = Math.min(p1.z, p2.z, p3.z), let zm = Math.min(p1.z, p2.z, p3.z) - zMin,
zM = Math.max(p1.z, p2.z, p3.z), zM = Math.max(p1.z, p2.z, p3.z) - zMin,
bm = Math.floor(zm * zScale), bm = Math.floor(zm * zScale),
bM = Math.ceil(zM * zScale); bM = Math.ceil(zM * zScale);
if (bm < 0) bm = 0; // add point to all buckets in range
for (j = bm; j < bM; j++) { for (j = bm; j <= bM; j++) {
buckets[j].points.push(p1); buckets[j].points.push(p1);
buckets[j].points.push(p2); buckets[j].points.push(p2);
buckets[j].points.push(p3); buckets[j].points.push(p3);
@ -126,25 +157,19 @@
} }
// create buckets data structure // create buckets data structure
for (let i = 0; i < zIndexes.length; i++) { for (let z of zIndexes) {
bucketZ(i, zIndexes[i]); buckets[Math.floor((z - zMin) * zScale)].slices.push(z);
onupdate((i / zIndexes.length) * 0.1); onupdate((i / zIndexes.length) * 0.1);
} }
function bucketZ(index, z) {
buckets[Math.floor(z * zScale)].slices.push({
index, z, total: zIndexes.length
});
}
async function sliceBuckets() { async function sliceBuckets() {
let output = []; let output = [];
let count = 0; let count = 0;
let opt = { ...options, zmin: zMin }; let opt = { ...options, zMin, zMax };
for (let bucket of buckets) { for (let bucket of buckets) {
let { points, slices } = bucket; let { points, slices } = bucket;
for (let params of slices) { for (let z of slices) {
output.push(sliceZ(params.z, points, opt)); output.push(sliceZ(z, points, opt));
onupdate(0.1 + (count++ / zIndexes.length) * 0.9); onupdate(0.1 + (count++ / zIndexes.length) * 0.9);
} }
} }
@ -153,7 +178,9 @@
// create slices from each bucketed region // create slices from each bucketed region
let slices = await sliceBuckets(); let slices = await sliceBuckets();
return slices.sort((a,b) => a.index - b.index); slices = slices.sort((a,b) => a.z - b.z);
return { slices, points, zMin, zMax, zIndexes, zFlat };
} }
/** /**
@ -235,10 +262,10 @@
* @param {number} z * @param {number} z
*/ */
function sliceZ(z, points, options = {}) { function sliceZ(z, points, options = {}) {
let phash = {}, let { zMin, zMax, under, over, both } = options,
phash = {},
lines = [], lines = [],
p1, p2, p3, p1, p2, p3;
zmin = options.zmin;
// iterate over matching buckets for this z offset // iterate over matching buckets for this z offset
for (let i = 0; i < points.length; ) { for (let i = 0; i < points.length; ) {
@ -255,7 +282,10 @@
// one side of triangle is on the Z plane and 3rd is below // one side of triangle is on the Z plane and 3rd is below
// drop lines with 3rd above because that leads to ambiguities // drop lines with 3rd above because that leads to ambiguities
// with complex nested polygons on flat surface // with complex nested polygons on flat surface
if (where.under.length === 1 || z === zmin) { let add2 = both ||
(over && (where.over.length === 1 || z === zMax)) ||
(under && (where.under.length === 1 || z === zMin));
if (add2) {
lines.push(makeZLine(phash, where.on[0], where.on[1], false, true)); lines.push(makeZLine(phash, where.on[0], where.on[1], false, true));
} }
} else if (where.on.length === 3) { } else if (where.on.length === 3) {
@ -284,11 +314,16 @@
// de-dup and group lines // de-dup and group lines
lines = removeDuplicateLines(lines); lines = removeDuplicateLines(lines);
let groups = options.connect !== false ?
connectLines(lines, z, { debug: options.debug }) :
undefined;
return { lines, groups }; // connect lines into longest contiguous chains
if (options.group) {
return {
z, lines,
groups: connectLines(lines, z, { debug: options.debug })
};
} else {
return { z, lines };
}
} }
/** /**

View file

@ -160,14 +160,8 @@ mesh.model = class MeshModel extends mesh.object {
}).then(data => { }).then(data => {
console.log({rebuild: this.id, data}); console.log({rebuild: this.id, data});
let points = []; let points = [];
for (let arr of data) { for (let i=0; i<data.length;) {
arr.push(arr[0]); points.push(new THREE.Vector3(data[i++], data[i++], data[i++]));
for (let i=0; i<arr.length-1; i++) {
let p1 = arr[i];
let p2 = arr[i+1];
points.push(new THREE.Vector3().fromArray(p1));
points.push(new THREE.Vector3().fromArray(p2));
}
} }
let material = new THREE.LineBasicMaterial( { color: 0xdddddd } ); let material = new THREE.LineBasicMaterial( { color: 0xdddddd } );
let geometry = new THREE.BufferGeometry().setFromPoints(points); let geometry = new THREE.BufferGeometry().setFromPoints(points);

View file

@ -22,6 +22,7 @@ gapp.finalize("mesh.work", [
"moto.client", // dep: moto.client "moto.client", // dep: moto.client
"moto.worker", // dep: moto.worker "moto.worker", // dep: moto.worker
"mesh.tool", // dep: mesh.tool "mesh.tool", // dep: mesh.tool
"mesh.util", // dep: mesh.util
"add.three", // dep: add.three "add.three", // dep: add.three
]); ]);
@ -31,6 +32,7 @@ let { Matrix4, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = T
let core_matrix = new Matrix4().makeRotationX(Math.PI / 2); let core_matrix = new Matrix4().makeRotationX(Math.PI / 2);
let { client, worker } = moto; let { client, worker } = moto;
let { util } = mesh;
let cache = {}; let cache = {};
function log(msg) { function log(msg) {
@ -297,17 +299,45 @@ let model = {
log(`${id} | rebuilding...`); log(`${id} | rebuilding...`);
let points = translate_encode(id, matrix); let points = translate_encode(id, matrix);
log(`${id} | ${points.length} points`); log(`${id} | ${points.length} points`);
base.slice(points, { flat: true, autoDim: true, zinc: 0 }) base.slice(points, {
.then(sliced => { autoDim: true,
let layers = []; flat: true,
log(`${id} | ${sliced.length} slices`); zinc: 0,
for (let slice of sliced) { both: true,
for (let poly of slice.groups) { // debug: true,
layers.push(poly.points.map(p => [ p.x, p.y, p.z ])); minstep: 0.25,
}).then(output => {
let layers = [];
let { points, slices } = output;
log(`${id} | ${slices.length} slices Z`);
for (let slice of slices) {
for (let line of slice.lines) {
layers.appendAll(util.extract(line.p1));
layers.appendAll(util.extract(line.p2));
}
}
// send.done(layers);
for (let p of points) p.swapXZ();
base.slice(points, {
autoDim: true,
zinc: 0,
both: true,
// debug: true,
minstep: 0.25,
}).then(output => {
let { points, slices } = output;
log(`${id} | ${slices.length} slices X`);
for (let slice of slices) {
for (let line of slice.lines) {
if (!line.p1.swapped) { line.p1.swapXZ().swapped = true }
if (!line.p2.swapped) { line.p2.swapXZ().swapped = true }
layers.appendAll(util.extract(line.p1));
layers.appendAll(util.extract(line.p2));
} }
} }
send.done(layers); send.done(layers);
}); });
});
} }
}; };