boolean manifold

This commit is contained in:
Val Erastov 2017-10-23 00:37:16 -07:00
parent 1714daefe8
commit bd490f13b7
7 changed files with 267 additions and 73 deletions

View file

@ -88,17 +88,22 @@ function addGlobalDebugActions(app) {
AddFace: (face, color) => {
for (let e of face.edges) __DEBUG__.AddHalfEdge(e, color);
},
AddLoop: (loop, color) => {
for (let e of loop.halfEdges) __DEBUG__.AddHalfEdge(e, color);
},
AddVolume: (shell, color) => {
color = color || 0xffffff;
const geometry = new THREE.Geometry();
triangulateToThree(shell.faces, geometry);
triangulateToThree(shell, geometry);
const mesh = new THREE.Mesh(geometry, createSolidMaterial({
color,
transparent: true,
opacity: 0.5,
opacity: 0.3,
depthWrite: false,
depthTest: false
}));
debugVolumeGroup.add(mesh);
window.__DEBUG__.AddWireframe(shell, color);
// window.__DEBUG__.AddWireframe(shell, color);
app.viewer.render();
},
AddWireframe: (shell, color) => {

View file

@ -236,9 +236,9 @@ App.prototype.test5 = function() {
App.prototype.scratchCode = function() {
// const app = this;
// this.test3();
// this.cylTest();
this.cylTest();
// return
return
// let curve1 = new NurbsCurve(new verb.geom.NurbsCurve({"degree":6,"controlPoints":[[150,149.99999999999997,-249.99999999999994,1],[108.33333333333051,150.00000000000907,-250.00000000001975,1],[66.6666666666712,149.99999999998562,-249.99999999996987,1],[24.99999999999545,150.00000000001364,-250.00000000002711,1],[-16.66666666666362,149.99999999999145,-249.9999999999837,1],[-58.33333333333436,150.0000000000029,-250.00000000000531,1],[-99.99999999999997,150,-250,1]],"knots":[0,0,0,0,0,0,0,1,1,1,1,1,1,1]}));
// let curve2 = new NurbsCurve(new verb.geom.NurbsCurve({"degree":9,"controlPoints":[[100,-250,-250,1],[99.9999999999927,-194.44444444444687,-250.00000000000028,1],[100.00000000002228,-138.8888888888811,-249.99999999999838,1],[99.99999999995923,-83.33333333334777,-250.00000000000287,1],[100.00000000005268,-27.77777777775936,-249.99999999999744,1],[99.9999999999493,27.777777777760704,-250.0000000000008,1],[100.00000000003591,83.33333333334477,-250.00000000000063,1],[99.99999999998269,138.88888888888374,-249.99999999999966,1],[100.00000000000443,194.44444444444562,-249.99999999999986,1],[100,250,-250,1]],"knots":[0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]}));

View file

@ -82,9 +82,9 @@ function Viewer(bus, container) {
axisGeom.vertices.push(axis.multiply(1000).three());
scene.add(new THREE.Line(axisGeom, lineMaterial));
}
addAxis(AXIS.X, 0xFF0000);
addAxis(AXIS.Y, 0x00FF00);
addAxis(AXIS.Z, 0x0000FF);
// addAxis(AXIS.X, 0xFF0000);
// addAxis(AXIS.Y, 0x00FF00);
// addAxis(AXIS.Z, 0x0000FF);
this.updateControlsAndHelpers = function() {
trackballControls.update();

View file

@ -1,6 +1,6 @@
import * as vec from "../../../math/vec";
import * as math from '../../../math/math'
import {eqEps, TOLERANCE, TOLERANCE_SQ} from '../tolerance';
import {eqEps, TOLERANCE, TOLERANCE_01, TOLERANCE_SQ} from '../tolerance';
import {fmin_bfgs} from "../../../math/optim";
export function curveStep(curve, u, tessTol, scale) {
@ -100,7 +100,7 @@ export function surfaceIntersect(surface0, surface1) {
fixTessNaNPoitns(surface0, tess0);
fixTessNaNPoitns(surface1, tess1);
const resApprox = verb.eval.Intersect.meshes(tess0,tess1);
const resApprox = meshesIntersect(tess0,tess1, TOLERANCE, TOLERANCE_SQ, TOLERANCE_01);
const exactPls = resApprox.map(function(pl) {
return pl.map(function(inter) {
return verb.eval.Intersect.surfacesAtPointWithEstimate(surface0,surface1,inter.uv0,inter.uv1,TOLERANCE);
@ -118,6 +118,30 @@ export function surfaceIntersect(surface0, surface1) {
});
}
function meshesIntersect(mesh0,mesh1, TOLERANCE, TOLERANCE_SQ, TOLERANCE_01) {
let bbtree0 = new verb.core.LazyMeshBoundingBoxTree(mesh0);
let bbtree1 = new verb.core.LazyMeshBoundingBoxTree(mesh1);
let bbints = verb.eval.Intersect.boundingBoxTrees(bbtree0,bbtree1,TOLERANCE);
let segments = verb.core.ArrayExtensions.unique(bbints.map(function(ids) {
return verb.eval.Intersect.triangles(mesh0,ids.item0,mesh1,ids.item1);
}).filter(function(x) {
return x != null;
}).filter(function(x1) {
return verb.core.Vec.distSquared(x1.min.point,x1.max.point) > TOLERANCE_SQ;
}),function(a,b) {
let s1 = verb.core.Vec.sub(a.min.uv0,b.min.uv0);
let d1 = verb.core.Vec.dot(s1,s1);
let s2 = verb.core.Vec.sub(a.max.uv0,b.max.uv0);
let d2 = verb.core.Vec.dot(s2,s2);
let s3 = verb.core.Vec.sub(a.min.uv0,b.max.uv0);
let d3 = verb.core.Vec.dot(s3,s3);
let s4 = verb.core.Vec.sub(a.max.uv0,b.min.uv0);
let d4 = verb.core.Vec.dot(s4,s4);
return d1 < TOLERANCE_01 && d2 < TOLERANCE_01 || d3 < TOLERANCE_01 && d4 < TOLERANCE_01;
});
return verb.eval.Intersect.makeMeshIntersectionPolylines(segments);
}
export function surfaceMaxDegree(surface) {
return Math.max(surface.degreeU, surface.degreeV);
}

View file

@ -336,8 +336,8 @@ export class NurbsSurface extends Surface {
return b.minus(a).cross(c.minus(a))._normalize().dot(surface.normalUV(0, 0)) < 0;
}
intersectSurfaceForSameClass(other, tol) {
let curves = ext.surfaceIntersect(this.data, other.data, tol);
intersectSurfaceForSameClass(other) {
let curves = ext.surfaceIntersect(this.data, other.data);
let inverted = this.inverted !== other.inverted;
if (inverted) {
curves = curves.map(curve => ext.curveInvert(curve));

View file

@ -5,11 +5,11 @@ import {Shell} from '../topo/shell';
import {Vertex} from '../topo/vertex';
import {evolveFace} from './evolve-face'
import * as math from '../../math/math';
import {eqTol, TOLERANCE, ueq, veq} from '../geom/tolerance';
import {eqEps, eqTol, TOLERANCE, ueq, veq} from '../geom/tolerance';
const DEBUG = {
OPERANDS_MODE: false,
LOOP_DETECTION: false,
LOOP_DETECTION: true,
FACE_FACE_INTERSECTION: false,
NOOP: () => {}
};
@ -57,7 +57,7 @@ export function BooleanAlgorithm( shell1, shell2, type ) {
let facesData = [];
mergeVertices(shell1, shell2);
initVertexFactory(shell1, shell2)
initVertexFactory(shell1, shell2);
intersectEdges(shell1, shell2);
@ -70,20 +70,29 @@ export function BooleanAlgorithm( shell1, shell2, type ) {
faceData.initGraph();
}
const allFaces = [];
const newLoops = new Set();
for (let faceData of facesData) {
const face = faceData.face;
const loops = detectLoops(faceData.face);
for (let loop of loops) {
for (let edge of loop.halfEdges) {
if (isNew(edge)) newLoops.add(loop);
}
}
loopsToFaces(face, loops, allFaces);
faceData.detectedLoops = detectLoops(faceData.face);
}
let faces = allFaces;
faces = filterFaces(faces, newLoops);
let detectedLoops = new Set();
for (let faceData of facesData) {
for (let loop of faceData.detectedLoops) {
detectedLoops.add(loop);
}
}
// let invalidLoops = invalidateLoops(detectedLoops);
let faces = [];
for (let faceData of facesData) {
// faceData.detectedLoops = faceData.detectedLoops.filter(l => !invalidLoops.has(l));
loopsToFaces(faceData.face, faceData.detectedLoops, faces);
}
faces = filterFaces(faces);
const result = new Shell();
faces.forEach(face => {
face.shell = result;
@ -93,7 +102,7 @@ export function BooleanAlgorithm( shell1, shell2, type ) {
cleanUpSolveData(result);
BREPValidator.validateToConsole(result);
__DEBUG__.ClearVolumes();
// __DEBUG__.ClearVolumes();
// __DEBUG__.Clear();
return result;
}
@ -108,12 +117,8 @@ function detectLoops(face) {
const loops = [];
const seen = new Set();
let edges = [];
for (let e of face.edges) {
edges.push(e);
}
while (true) {
let edge = edges.pop();
let edge = faceData.graphEdges.pop();
if (!edge) {
break;
}
@ -122,12 +127,19 @@ function detectLoops(face) {
}
const loop = new Loop(null);
let surface = face.surface;
while (edge) {
if (DEBUG.LOOP_DETECTION) {
__DEBUG__.AddHalfEdge(edge);
}
loop.halfEdges.push(edge);
seen.add(edge);
loop.halfEdges.push(edge);
if (loop.halfEdges[0].vertexA === edge.vertexB) {
loop.link();
loops.push(loop);
break;
}
let candidates = faceData.vertexToEdge.get(edge.vertexB);
if (!candidates) {
break;
@ -137,11 +149,6 @@ function detectLoops(face) {
break;
}
}
if (loop.halfEdges[0].vertexA === loop.halfEdges[loop.halfEdges.length - 1].vertexB) {
loop.link();
loops.push(loop);
}
}
return loops;
}
@ -170,29 +177,54 @@ export function mergeVertices(shell1, shell2) {
}
}
function filterFaces(faces, newLoops) {
const validFaces = new Set(faces);
const result = new Set();
for (let face of faces) {
traverseFaces(face, validFaces, (it) => {
if (result.has(it) || isFaceContainNewLoop(it, newLoops)) {
result.add(face);
return true;
}
});
}
return result;
function filterFaces(faces) {
return faces.filter(raycastFilter);
//
// function isFaceContainNewEdge(face) {
// for (let e of face.edges) {
// if (isNewNM(e)) {
// return true;
// }
// }
// return false;
// }
//
// const validFaces = new Set(faces);
// const result = new Set();
// for (let face of faces) {
// __DEBUG__.Clear();
// __DEBUG__.AddFace(face);
// traverseFaces(face, validFaces, (it) => {
// if (result.has(it) || isFaceContainNewEdge(it)) {
// result.add(face);
// return true;
// }
// });
// }
// return result;
}
function isFaceContainNewLoop(face, newLoops) {
for (let loop of face.loops) {
if (newLoops.has(loop)) {
return true;
}
function raycastFilter(face, shell, opType) {
let testPt = getPointOnFace(face);
let testCurve = ;
for (let testFace of face.faces) {
let pts = testFace.surface.intersectCurve(testCurve)
}
return false;
}
function traverseFaces(face, validFaces, callback) {
const stack = [face];
const seen = new Set();
@ -203,18 +235,64 @@ function traverseFaces(face, validFaces, callback) {
if (callback(face) === true) {
return;
}
if (!validFaces.has(face)) continue;
for (let loop of face.loops) {
if (!validFaces.has(face)) continue;
for (let halfEdge of loop.halfEdges) {
const twin = halfEdge.twin();
if (validFaces.has(twin.loop.face)) {
stack.push(twin.loop.face)
for (let twin of halfEdge.twins()) {
if (validFaces.has(twin.loop.face)) {
stack.push(twin.loop.face)
}
}
}
}
}
}
function invalidateLoops(newLoops) {
// __DEBUG__.Clear();
const invalid = new Set();
for (let loop of newLoops) {
// __DEBUG__.AddLoop(loop);
for (let e of loop.halfEdges) {
if (e.manifold !== null) {
let manifold = [e, ...e.manifold];
manifold.filter(me => newLoops.has(me.twin().loop));
if (manifold.length === 0) {
invalid.add(loop);
} else {
let [me, ...rest] = manifold;
e.edge = me.edge;
e.manifold = rest.length === 0 ? null : rest;
}
} else {
if (!newLoops.has(e.twin().loop)) {
invalid.add(loop);
break;
}
}
}
}
// const seen = new Set();
//
// const stack = Array.from(invalid);
//
// while (stack.length !== 0) {
// let loop = stack.pop();
// if (!seen.has(loop)) continue;
// seen.add(loop);
//
// for (let he of loop.halfEdges) {
// let twins = he.twins();
// for (let twin of twins) {
// invalid.add(twin.loop);
// stack.push(twin.loop);
// }
// }
// }
return invalid;
}
export function loopsToFaces(originFace, loops, out) {
const newFaces = evolveFace(originFace, loops);
for (let newFace of newFaces) {
@ -250,13 +328,20 @@ function findMaxTurningLeft(pivotEdge, edges, surface) {
const pivot = pivotEdge.tangent(pivotEdge.vertexB.point).negate();
const normal = surface.normal(pivotEdge.vertexB.point);
edges.sort((e1, e2) => {
return leftTurningMeasure(pivot, edgeVector(e1), normal) - leftTurningMeasure(pivot, edgeVector(e2), normal);
let delta = leftTurningMeasure(pivot, edgeVector(e1), normal) - leftTurningMeasure(pivot, edgeVector(e2), normal);
if (ueq(delta, 0)) {
return isNew(e1) ? (isNew(e2) ? 0 : -1) : (isNew(e2) ? 1 : 0)
}
return delta;
});
return edges[0];
}
function leftTurningMeasure(v1, v2, normal) {
let measure = v1.dot(v2);
if (ueq(measure, 1)) {
return 0;
}
measure += 3; //-1..1 => 2..4
if (v1.cross(v2).dot(normal) < 0) {
measure = 4 - measure;
@ -365,7 +450,7 @@ function intersectFaces(shell1, shell2, operationType) {
}
}
let curves = face1.surface.intersectSurface(face2.surface, TOLERANCE);
let curves = face1.surface.intersectSurface(face2.surface);
for (let curve of curves) {
// __DEBUG__.AddCurve(curve);
@ -394,7 +479,6 @@ function addNewEdge(face, halfEdge) {
data.loopOfNew.halfEdges.push(halfEdge);
halfEdge.loop = data.loopOfNew;
EdgeSolveData.createIfEmpty(halfEdge).newEdgeFlag = true;
//addToListInMap(data.vertexToEdge, halfEdge.vertexA, halfEdge);
return true;
}
@ -563,6 +647,18 @@ function isNew(edge) {
return EdgeSolveData.get(edge).newEdgeFlag === true
}
function isNewNM(edge) {
if (edge.manifold === null) {
return isNew(edge);
}
for (let me of edge.manifold) {
if (isNew(me)) {
return true;
}
}
return isNew(edge);
}
function Node(vertex, edge, curve, u) {
this.vertex = vertex;
this.edge = edge;
@ -625,6 +721,7 @@ class FaceSolveData {
this.loopOfNew = new Loop(face);
face.innerLoops.push(this.loopOfNew);
this.vertexToEdge = new Map();
this.graphEdges = [];
}
initGraph() {
@ -632,20 +729,46 @@ class FaceSolveData {
for (let he of this.face.edges) {
this.addToGraph(he);
}
this.removeOppositeEdges();
}
addToGraph(he) {
addToListInMap(this.vertexToEdge, he.vertexA, he);
// __DEBUG__.Clear();
// __DEBUG__.AddFace(he.loop.face);
// __DEBUG__.AddHalfEdge(he, 0xffffff);
let list = this.vertexToEdge.get(he.vertexA);
if (!list) {
list = [];
this.vertexToEdge.set(he.vertexA, list);
} else {
for (let ex of list) {
if (he.vertexB === ex.vertexB && isSameEdge(he, ex)) {
ex.attachManifold(he);
return;
}
}
}
list.push(he);
this.graphEdges.push(he);
}
}
function addToListInMap(map, key, value) {
let list = map.get(key);
if (!list) {
list = [];
map.set(key, list);
removeOppositeEdges() {
let toRemove = new Set();
for (let e1 of this.graphEdges) {
let others = this.vertexToEdge.get(e1.vertexB);
for (let e2 of others) {
if (e1 === e2) continue;
if (e1.vertexA === e2.vertexB && isSameEdge(e1, e2)) {
toRemove.add(e1);
toRemove.add(e2);
}
}
}
for (let e of toRemove) {
removeFromListInMap(this.vertexToEdge, e.vertexA, e);
}
this.graphEdges = this.graphEdges.filter(e => !toRemove.has(e));
}
list.push(value);
}
function removeFromListInMap(map, key, value) {
@ -658,6 +781,17 @@ function removeFromListInMap(map, key, value) {
}
}
function isSameEdge(e1, e2) {
let tess = e1.tessellate();
for (let pt1 of tess) {
let pt2 = e2.edge.curve.point(e2.edge.curve.param(pt1));
if (!veq(pt1, pt2)) {
return false;
}
}
return true;
}
function $DEBUG_OPERANDS(shell1, shell2) {
if (DEBUG.OPERANDS_MODE) {
__DEBUG__.HideSolids();
@ -676,3 +810,4 @@ function assert(name, cond) {
const MY = '__BOOLEAN_ALGORITHM_DATA__';

View file

@ -40,10 +40,17 @@ class HalfEdge extends TopoObject {
this.loop = null;
this.next = null;
this.prev = null;
this.manifold = null;
this.manifoldHolder = null;
}
twin() {
return this.edge.halfEdge1 === this ? this.edge.halfEdge2 : this.edge.halfEdge1;
let twin = this.edge.halfEdge1 === this ? this.edge.halfEdge2 : this.edge.halfEdge1;
return twin.manifoldHolder === null ? twin : twin.manifoldHolder;
}
twins() {
return this.manifold === null ? [this.twin()] : [this.twin(), ...this.manifold.map(me => me.twin())];
}
tangent(point) {
@ -54,4 +61,27 @@ class HalfEdge extends TopoObject {
}
return tangent;
}
tessellate() {
let res = this.edge.curve.tessellate.apply(this.edge.curve, arguments);
if (this.inverted) {
res = res.slice().reverse();
}
return res;
}
attachManifold(he, shallow) {
if (this.manifold === null) {
this.manifold = [];
}
if (this.manifold.indexOf(he) === -1) {
this.manifold.push(he);
}
he.manifoldHolder = this;
// if (shallow === true) {
// return;
// }
// this.twin().attachManifold()
// he.attachManifold(, true);
}
}