From bd490f13b733e07f433953fef3cf2b1474b4491f Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Mon, 23 Oct 2017 00:37:16 -0700 Subject: [PATCH] boolean manifold --- web/app/3d/debug.js | 11 +- web/app/3d/modeler-app.js | 4 +- web/app/3d/viewer.js | 6 +- web/app/brep/geom/impl/nurbs-ext.js | 28 ++- web/app/brep/geom/impl/nurbs.js | 4 +- web/app/brep/operations/boolean.js | 255 +++++++++++++++++++++------- web/app/brep/topo/edge.js | 32 +++- 7 files changed, 267 insertions(+), 73 deletions(-) diff --git a/web/app/3d/debug.js b/web/app/3d/debug.js index a8c4f09f..45820f90 100644 --- a/web/app/3d/debug.js +++ b/web/app/3d/debug.js @@ -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) => { diff --git a/web/app/3d/modeler-app.js b/web/app/3d/modeler-app.js index f38fc719..7bc5cf25 100644 --- a/web/app/3d/modeler-app.js +++ b/web/app/3d/modeler-app.js @@ -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]})); diff --git a/web/app/3d/viewer.js b/web/app/3d/viewer.js index 69a47f5c..844fce73 100644 --- a/web/app/3d/viewer.js +++ b/web/app/3d/viewer.js @@ -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(); diff --git a/web/app/brep/geom/impl/nurbs-ext.js b/web/app/brep/geom/impl/nurbs-ext.js index f855fc2a..d0b06cd9 100644 --- a/web/app/brep/geom/impl/nurbs-ext.js +++ b/web/app/brep/geom/impl/nurbs-ext.js @@ -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); } diff --git a/web/app/brep/geom/impl/nurbs.js b/web/app/brep/geom/impl/nurbs.js index 5c0c3a56..a19a8ce1 100644 --- a/web/app/brep/geom/impl/nurbs.js +++ b/web/app/brep/geom/impl/nurbs.js @@ -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)); diff --git a/web/app/brep/operations/boolean.js b/web/app/brep/operations/boolean.js index ee3797c9..3ba66a73 100644 --- a/web/app/brep/operations/boolean.js +++ b/web/app/brep/operations/boolean.js @@ -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__'; + diff --git a/web/app/brep/topo/edge.js b/web/app/brep/topo/edge.js index 5616591f..37fc336e 100644 --- a/web/app/brep/topo/edge.js +++ b/web/app/brep/topo/edge.js @@ -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); + } }