progress toward complex/nested loops/patches
This commit is contained in:
parent
c4899c7261
commit
3d3980dcf8
2 changed files with 165 additions and 6 deletions
105
src/mesh/geom.js
Normal file
105
src/mesh/geom.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
(function() {
|
||||
|
||||
// dep: ext.three
|
||||
// dep: ext.three-bgu
|
||||
gapp.register("mesh.geom", [
|
||||
"moto.license", // dep: moto.license
|
||||
"add.array", // dep: add.array
|
||||
]);
|
||||
|
||||
let mesh = self.mesh = self.mesh || {};
|
||||
if (mesh.geom) return;
|
||||
|
||||
let { Matrix4, Matrix3, Vector3, Box3 } = THREE;
|
||||
|
||||
// geometry helper functions
|
||||
let geom = mesh.geom = {
|
||||
|
||||
// given a closed polyline as an expanded point array
|
||||
// return enclosed area with sign indicating winding order
|
||||
areaSigned(points, axes = 3) {
|
||||
let area = 0;
|
||||
let len = points.length;
|
||||
for (let p1, p2, pi = 0; pi < len; ) {
|
||||
p1 = points.slice(pi, pi + axes);
|
||||
pi += axes;
|
||||
p2 = pi < len - axes ?
|
||||
points.slice(pi, pi + axes) :
|
||||
points.slice(0, axes);
|
||||
area += (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
||||
}
|
||||
return area / 2;
|
||||
},
|
||||
|
||||
// returns an unsigned area generally more useful for comparison
|
||||
area(points) {
|
||||
return Math.abs(geom.areaSigned(points));
|
||||
},
|
||||
|
||||
// given an expanded point array, return bounds
|
||||
bounds(points, axes = 3) {
|
||||
let box = new Box3();
|
||||
let p3 = new Vector3();
|
||||
for (let p, pi = 0, len = points.length; pi < len; pi += axes) {
|
||||
box.expandByPoint(p3.fromArray(points, pi));
|
||||
}
|
||||
return box;
|
||||
},
|
||||
|
||||
// given two nest loop records, determine if
|
||||
// inner is completely inside outer without overlap
|
||||
isInside(outer, inner) {
|
||||
return outer.bounds.containsBox(inner.bounds);
|
||||
},
|
||||
|
||||
// given an array of closed polyline arrays, determine parentage
|
||||
nest(loops) {
|
||||
// create record per loop
|
||||
let recs = loops.map(points => {
|
||||
return {
|
||||
points,
|
||||
area: 0,
|
||||
depth: 0,
|
||||
inner: [],
|
||||
bounds: geom.bounds(points)
|
||||
}
|
||||
});
|
||||
|
||||
// sort loops by area ascending and cache loop area
|
||||
recs = recs.sort((a,b) => {
|
||||
a.area = a.area || geom.areaSigned(a.points);
|
||||
b.area = b.area || geom.areaSigned(b.points);
|
||||
return Math.abs(a.area) - Math.abs(b.area);
|
||||
});
|
||||
|
||||
// deep nesting
|
||||
let count = recs.length;
|
||||
outer: for (let i = 0; i < count - 1; i++) {
|
||||
let child = recs[i];
|
||||
for (let j = i + 1; j < count; j++) {
|
||||
let parent = recs[j];
|
||||
if (geom.isInside(parent, child)) {
|
||||
child.parent = parent;
|
||||
parent.inner.push(child);
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate inner depth (steps from topmost)
|
||||
// let inners = recs.filter(r => r.parent);
|
||||
for (let rec of recs) {
|
||||
for (let p = rec.parent, d = 0; p ; p = p.parent) {
|
||||
rec.depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return recs;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -5,11 +5,15 @@
|
|||
(function() {
|
||||
|
||||
// dep: ext.earcut
|
||||
gapp.register("mesh.tool");
|
||||
gapp.register("mesh.tool", [
|
||||
"mesh.geom", // dep: mesh.geom
|
||||
]);
|
||||
|
||||
let mesh = self.mesh = self.mesh || {};
|
||||
if (mesh.tool) return;
|
||||
|
||||
let geom = mesh.geom;
|
||||
|
||||
/** tool for identiying defects and healing them **/
|
||||
mesh.tool = class MeshTool {
|
||||
constructor(params = {}) {
|
||||
|
|
@ -352,7 +356,7 @@ mesh.tool = class MeshTool {
|
|||
swap = 0;
|
||||
}
|
||||
if (swap < 2) {
|
||||
// console.log({swap_axis: swap});
|
||||
// swap x or y for z axes
|
||||
for (let point of points) {
|
||||
let tmpz = point[2];
|
||||
point[2] = point[swap];
|
||||
|
|
@ -367,20 +371,70 @@ mesh.tool = class MeshTool {
|
|||
function emitFaces(loop) {
|
||||
let { index, points } = loop;
|
||||
let ec = earcut(points, undefined, 3);
|
||||
// map earcut point indexes back to vertex indexes
|
||||
return ec.map(p => index[p]);
|
||||
}
|
||||
|
||||
function emitTop(rec) {
|
||||
let points = rec.loop.points.slice();
|
||||
let index = rec.loop.index.slice();
|
||||
let holesAt = index.length;
|
||||
for (let inner of rec.inner) {
|
||||
points.appendAll(inner.loop.points);
|
||||
index.appendAll(inner.loop.index);
|
||||
}
|
||||
let holes = index.slice(holesAt).map((v,i) => i + holesAt);
|
||||
let ec = earcut(points, holes, 3);
|
||||
// eliminate faces consisting only of hole indices
|
||||
let ef = ec.group(3)
|
||||
.filter(g => {
|
||||
return !(
|
||||
holes.contains(g[0]) &&
|
||||
holes.contains(g[1]) &&
|
||||
holes.contains(g[2]) )
|
||||
})
|
||||
.flat();
|
||||
return ef.map(p => index[p]);
|
||||
}
|
||||
|
||||
// new patch areas
|
||||
let areas = this.areas = [];
|
||||
|
||||
// start disabled code path for complex (nested) paths
|
||||
if (false) {
|
||||
|
||||
// group loops by normal (using swap as crude proxy for now)
|
||||
// then attempt to nest them (find holes)
|
||||
// let polys = loops.map(loop => {
|
||||
// });
|
||||
let polys = loops.map(loop => {
|
||||
return emitLoop(loop);
|
||||
});
|
||||
|
||||
// return nested mapping
|
||||
let nested = geom.nest(polys.map(p => p.points));
|
||||
// recover and map loops from point arrays
|
||||
for (let rec of nested) {
|
||||
rec.loop = polys.filter(loop => loop.points === rec.points)[0];
|
||||
// keep parent/child winding reversed
|
||||
let CCW = rec.depth % 2 === 0;
|
||||
if ((CCW && rec.area > 0) || (!CCW && rec.area < 0)) {
|
||||
rec.points = rec.loop.points = rec.points.group(3).reverse().flat();
|
||||
rec.loop.index = rec.loop.index.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
// extract tops which have nesting depth mod 2 === 0
|
||||
let tops = nested.filter(rec => rec.depth % 2 === 0);
|
||||
for (let top of tops) {
|
||||
areas.push(emitTop(top));
|
||||
}
|
||||
|
||||
} // end of disbled code
|
||||
|
||||
// create area faces covering loops
|
||||
let areas = this.areas = [];
|
||||
for (let loop of loops) {
|
||||
let l2 = emitLoop(loop);
|
||||
let fs = emitFaces(l2);
|
||||
this.areas.push(fs);
|
||||
areas.push(fs);
|
||||
}
|
||||
|
||||
// auto-merge newly found enclosed areas
|
||||
|
|
|
|||
Loading…
Reference in a new issue