diff --git a/web/app/3d/debug.js b/web/app/3d/debug.js index ba2fa47a..6afd13df 100644 --- a/web/app/3d/debug.js +++ b/web/app/3d/debug.js @@ -11,16 +11,34 @@ export function AddDebugSupport(app) { } function addGlobalDebugActions(app) { + const debugGroup = new THREE.Object3D(); + app.viewer.workGroup.add(debugGroup); window.__DEBUG__ = { AddLine: (a, b) => { - app.viewer.workGroup.add(createLine(a, b)); + debugGroup.add(createLine(a, b)); app.viewer.render(); }, - AddPoint: (coordinates, or, vector) => { - app.viewer.workGroup.add(createPoint(coordinates, or, vector)); + AddSegment: (a, b) => { + debugGroup.add(createLine(a, b)); + debugGroup.add(createPoint(a, 0x000088)); + debugGroup.add(createPoint(b, 0x880000)); + app.viewer.render(); + }, + AddPoint: (coordinates, or, vector, andColorAtTheEnd) => { + debugGroup.add(createPoint(coordinates, or, vector, andColorAtTheEnd)); + app.viewer.render(); + }, + AddVertex: (v) => { + window.__DEBUG__.AddPoint(v.point); + }, + AddHalfEdge: (he) => { + window.__DEBUG__.AddSegment(he.vertexA.point, he.vertexB.point); + }, + Clear: () => { + while (debugGroup.children.length) debugGroup.remove(debugGroup.children[0]); app.viewer.render(); } - }; + } } function createLine(a, b) { @@ -31,14 +49,16 @@ function createLine(a, b) { return new THREE.Line(lg, debugLineMaterial); } -function createPoint(x, y, z) { - if (!y) { +function createPoint(x, y, z, color) { + if (!z) { + color = y; y = x.y; z = x.z; x = x.x; } + color = color || 0x00ff00; var geometry = new THREE.SphereGeometry( 5, 16, 16 ); - var material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); + var material = new THREE.MeshBasicMaterial( {color} ); var sphere = new THREE.Mesh(geometry, material); sphere.position.x = x; sphere.position.y = y; diff --git a/web/app/3d/modeler-app.js b/web/app/3d/modeler-app.js index 8630edd2..1739589c 100644 --- a/web/app/3d/modeler-app.js +++ b/web/app/3d/modeler-app.js @@ -71,6 +71,10 @@ function App() { } App.prototype.BREPTest = function() { + setTimeout(() => this.BREPTestImpl()); +}; + +App.prototype.BREPTestImpl = function() { const addToScene = (shell) => { const sceneSolid = new SceneSolid(shell); this.viewer.workGroup.add(sceneSolid.cadGroup); @@ -78,11 +82,14 @@ App.prototype.BREPTest = function() { const box1 = BREPPrimitives.box(500, 500, 500); const box2 = BREPPrimitives.box(500, 500, 500, new Matrix3().translate(250, 250, 250)); - addToScene(box1); - addToScene(box2); + //box1.faces = [box1.faces[2]]; + //box2.faces = [box2.faces[5]]; + + //addToScene(box1); + //addToScene(box2); const result = BREPBool.union(box1, box2); - //addToScene(result); + addToScene(result); this.viewer.render() diff --git a/web/app/brep/brep-builder.js b/web/app/brep/brep-builder.js index 90fea70c..1a0e3cfd 100644 --- a/web/app/brep/brep-builder.js +++ b/web/app/brep/brep-builder.js @@ -16,33 +16,41 @@ export function createPrism(basePoints, height) { const lidNormal = normal.multiply(-1); const offVector = lidNormal.multiply(height); - const lidPoints = basePoints.map(p => p.plus(offVector)); - const lidLoop = createPlaneLoop(lidPoints.map(p => new Vertex(p))); - const lidFace = createPlaneFace(lidNormal, lidLoop); + + const lidSegments = []; + iterateSegments(basePoints.map(p => p.plus(offVector)), (a, b) => lidSegments.push({a, b})); + const lidLoop = new Loop(); const shell = new Shell(); - - for (let i = 0; i < baseLoop.halfEdges.length; i++) { - const baseHalfEdge = baseLoop.halfEdges[i]; - const lidHalfEdge = lidLoop.halfEdges[i]; - const wallLoop = createPlaneLoop([baseHalfEdge.vertexA, baseHalfEdge.vertexB, lidHalfEdge.vertexB, lidHalfEdge.vertexA]); - const baseEdge = new Edge(new Line()); + const n = baseLoop.halfEdges.length; + for (let i = 0; i < n; i++) { + const baseHalfEdge = baseLoop.halfEdges[i]; + const lidSegment = lidSegments[i]; + const lidHalfEdge = createHalfEdge(lidLoop, new Vertex(lidSegment.b), new Vertex(lidSegment.a)); + + const wallPolygon = [baseHalfEdge.vertexB, baseHalfEdge.vertexA, lidHalfEdge.vertexB, lidHalfEdge.vertexA]; + const wallLoop = createPlaneLoop(wallPolygon); + + const baseEdge = new Edge(Line.fromSegment(baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point)); linkHalfEdges(baseEdge, baseHalfEdge, wallLoop.halfEdges[0]); - const lidEdge = new Edge(new Line()); + const lidEdge = new Edge(Line.fromSegment(lidHalfEdge.vertexA.point, lidHalfEdge.vertexB.point)); linkHalfEdges(lidEdge, lidHalfEdge, wallLoop.halfEdges[2]); - const wallNormal = cad_utils.normalOfCCWSeq([baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point, lidHalfEdge.vertexB.point]); + const wallNormal = cad_utils.normalOfCCWSeq(wallPolygon.map(v => v.point)); const wallFace = createPlaneFace(wallNormal, wallLoop); wallFace.debugName = 'wall_' + i; shell.faces.push(wallFace); } - + const lidFace = createPlaneFace(lidNormal, lidLoop); iterateSegments(shell.faces, (a, b) => { - linkHalfEdges(new Edge(new Line()), a.outerLoop.halfEdges[1], b.outerLoop.halfEdges[3]); + const halfEdgeA = a.outerLoop.halfEdges[3]; + const halfEdgeB = b.outerLoop.halfEdges[1]; + const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point); + linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB); }); baseFace.debugName = 'base'; @@ -67,8 +75,6 @@ export function linkHalfEdges(edge, halfEdge1, halfEdge2) { halfEdge2.edge = edge; edge.halfEdge1 = halfEdge1; edge.halfEdge2 = halfEdge2; - halfEdge1.vertexA.edges.push(edge); - halfEdge1.vertexB.edges.push(edge); } export function createPlaneLoop(vertices) { @@ -76,17 +82,22 @@ export function createPlaneLoop(vertices) { const loop = new Loop(); iterateSegments(vertices, (a, b) => { - const halfEdge = new HalfEdge(); - halfEdge.loop = loop; - halfEdge.vertexA = a; - halfEdge.vertexB = b; - loop.halfEdges.push(halfEdge); + createHalfEdge(loop, a, b) }); linkSegments(loop.halfEdges); return loop; } +export function createHalfEdge(loop, a, b) { + const halfEdge = new HalfEdge(); + halfEdge.loop = loop; + halfEdge.vertexA = a; + halfEdge.vertexB = b; + loop.halfEdges.push(halfEdge); + return halfEdge; +} + export function linkSegments(halfEdges) { iterateSegments(halfEdges, (prev, next) => { prev.next = next; diff --git a/web/app/brep/operations/boolean.js b/web/app/brep/operations/boolean.js index 64b7d853..24d27ab7 100644 --- a/web/app/brep/operations/boolean.js +++ b/web/app/brep/operations/boolean.js @@ -6,6 +6,10 @@ import {Shell} from '../topo/shell'; import {Vertex} from '../topo/vertex'; import {Line} from '../geom/impl/line'; import Vector from '../../math/vector'; +import * as math from '../../math/math'; +import {Matrix3} from '../../math/l3space'; + +export const TOLERANCE = 1e-8; export function union( shell1, shell2 ) { @@ -17,16 +21,17 @@ export function union( shell1, shell2 ) { intersectFaces(shell1, shell2); const result = new Shell(); + //__DEBUG__.AddSegment(shell2.faces[0].outerLoop.halfEdges[0].vertexA.point, shell2.faces[0].outerLoop.halfEdges[0].vertexB.point) for (let faceData of facesData) { const seen = new Set(); const face = faceData.face; - if (shell2.faces.indexOf(face) != -1) { - continue; - } - const edges = faceData.newEdges.concat(face.outerLoop.halfEdges); - edges.forEach(e => __DEBUG__.AddLine(e.vertexA.point, e.vertexB.point)); + //if (shell2.faces.indexOf(face) != -1) { + // continue; + //} + const edges = face.outerLoop.halfEdges.concat(faceData.newEdges); + //edges.forEach(e => __DEBUG__.AddLine(e.vertexA.point, e.vertexB.point)); while (true) { let edge = edges.pop(); if (!edge) { @@ -37,6 +42,8 @@ export function union( shell1, shell2 ) { } const loop = new Loop(); while (edge) { + //__DEBUG__.AddHalfEdge(edge); + loop.halfEdges.push(edge); seen.add(edge); let candidates = faceData.vertexToEdge.get(edge.vertexB); @@ -49,11 +56,13 @@ export function union( shell1, shell2 ) { } } - BREPBuilder.linkSegments(loop.halfEdges); - const newFace = new Face(face.surface); - newFace.outerLoop = loop; - newFace.outerLoop.face = newFace; - result.faces.push(newFace); + if (loop.halfEdges[0].vertexA == loop.halfEdges[loop.halfEdges.length - 1].vertexB) { + BREPBuilder.linkSegments(loop.halfEdges); + const newFace = new Face(face.surface); + newFace.outerLoop = loop; + newFace.outerLoop.face = newFace; + result.faces.push(newFace); + } } } return result; @@ -97,31 +106,31 @@ function intersectFaces(shell1, shell2) { const face1 = shell1.faces[i]; const face2 = shell2.faces[j]; - if (face1.debugName == 'base' && face2.debugName == 'wall_3') { - console.log('there'); - } - const curve = face1.surface.intersect(face2.surface); + + const nodes = []; + collectNodesOfIntersection(face2, face1.outerLoop, nodes); + collectNodesOfIntersection(face1, face2.outerLoop, nodes); const newEdges = []; const direction = face1.surface.normal.cross(face2.surface.normal); - split(face2, face1.outerLoop, newEdges, curve, direction); - split(face1, face2.outerLoop, newEdges, curve, direction); + split(nodes, newEdges, curve, direction); newEdges.forEach(e => { + //__DEBUG__.AddHalfEdge(e.halfEdge1); + console.log("new edge"); face1.__faceSolveData.newEdges.push(e.halfEdge1); - addToListInMap(face1.__faceSolveData.vertexToEdge, e.halfEdge1.vertexA, e.halfEdge1); - }); - newEdges.forEach(e => { face2.__faceSolveData.newEdges.push(e.halfEdge2); + + addToListInMap(face1.__faceSolveData.vertexToEdge, e.halfEdge1.vertexA, e.halfEdge1); addToListInMap(face2.__faceSolveData.vertexToEdge, e.halfEdge2.vertexA, e.halfEdge2); }); } } } -function split(face, loop, result, onCurve, direction) { - const nodes = []; +function collectNodesOfIntersection(face, loop, nodes) { + const verticesCases = new Set(); for (let edge of loop.halfEdges) { const edgeSolveData = EdgeSolveData.get(edge); if (edgeSolveData.skipFace.has(face)) { @@ -129,27 +138,40 @@ function split(face, loop, result, onCurve, direction) { } const preExistVertex = edgeSolveData.splitByFace.get(face); if (preExistVertex) { - nodes.push(new Node(preExistVertex, edgeNormal(edge), edge)); + __DEBUG__.AddVertex(preExistVertex); + nodes.push(new Node(preExistVertex, edgeNormal(edge), edge, face)); continue } - intersectSurfaceWithEdge(face.surface, edge, nodes); + intersectFaceWithEdge(face, edge, nodes, verticesCases); } +} + +function split(nodes, result, onCurve, direction) { for (let i = 0; i < nodes.length; i++) { let inNode = nodes[i]; + //if (i == 0) __DEBUG__.AddPoint(inNode.vertex.point); + if (inNode == null) continue; nodes[i] = null; - let closestIdx = findCloserProjection(nodes, inNode.point); + let closestIdx = findCloserProjection(nodes, inNode); if (closestIdx == -1) { continue; } let outNode = nodes[closestIdx]; - + //if (i == 1) __DEBUG__.AddPoint(outNode.vertex.point); + //if (i == 1) __DEBUG__.AddSegment(inNode.point, inNode.point.plus(inNode.normal.multiply(1000))); + //__DEBUG__.AddSegment(new Vector(), outNode.normal.multiply(100)); + if (outNode.normal.dot(inNode.normal) > 0) { continue; } nodes[closestIdx] = null; - + + //__DEBUG__.AddPoint(inNode.vertex.point); + //__DEBUG__.AddPoint(outNode.vertex.point); + + const halfEdge1 = new HalfEdge(); halfEdge1.vertexA = inNode.vertex; halfEdge1.vertexB = outNode.vertex; @@ -158,8 +180,12 @@ function split(face, loop, result, onCurve, direction) { halfEdge2.vertexB = halfEdge1.vertexA; halfEdge2.vertexA = halfEdge1.vertexB; - splitEdgeByVertex(inNode.edge, halfEdge1.vertexA); - splitEdgeByVertex(outNode.edge, halfEdge1.vertexB); + //__DEBUG__.AddHalfEdge(halfEdge1); + //__DEBUG__.AddSegment(new Vector(), direction.multiply(100)); + + + splitEdgeByVertex(inNode.edge, halfEdge1.vertexA, inNode.splittingFace); + splitEdgeByVertex(outNode.edge, halfEdge1.vertexB, outNode.splittingFace); const sameDirection = direction.dot(outNode.point.minus(inNode.point)) > 0; @@ -175,48 +201,52 @@ function split(face, loop, result, onCurve, direction) { } } -function splitEdgeByVertex(originHalfEdge, vertex) { +function splitEdgeByVertex(originHalfEdge, vertex, splittingFace) { + + function splitHalfEdge(h) { + const newEdge = new HalfEdge(); + newEdge.vertexA = vertex; + newEdge.vertexB = h.vertexB; + h.vertexB = newEdge.vertexA; + addToListInMap(h.loop.face.__faceSolveData.vertexToEdge, vertex, newEdge); + return newEdge; + } + const orig = originHalfEdge; - - const halfEdge1 = new HalfEdge(); - halfEdge1.vertexA = vertex; - halfEdge1.vertexB = orig.vertexB; - - const halfEdge2 = new HalfEdge(); - halfEdge2.vertexA = halfEdge1.vertexB; - halfEdge2.vertexB = halfEdge1.vertexA; - - const newEdge = new Edge(orig.edge); - BREPBuilder.linkHalfEdges(newEdge, halfEdge1, halfEdge2); - const twin = orig.twin(); - orig.vertexB = vertex; - twin.vertexA = vertex; + + if (orig.vertexA == vertex || orig.vertexB == vertex) { + return; + } - orig.loop.halfEdges.push(halfEdge1); - twin.loop.halfEdges.push(halfEdge2); + const newOrig = splitHalfEdge(orig); + const newTwin = splitHalfEdge(twin); - halfEdge1.loop = orig.loop; - halfEdge2.loop = twin.loop; - EdgeSolveData.transfer(orig, halfEdge1); - EdgeSolveData.transfer(twin, halfEdge2); + BREPBuilder.linkHalfEdges(orig.edge, orig, newTwin); + BREPBuilder.linkHalfEdges(new Edge(orig.edge.curve), twin, newOrig); + + orig.loop.halfEdges.splice(orig.loop.halfEdges.indexOf(orig) + 1, 0, newOrig); + twin.loop.halfEdges.splice(twin.loop.halfEdges.indexOf(twin) + 1, 0, newTwin); - EdgeSolveData.createIfEmpty(twin).splitByFace.set(orig.loop.face, vertex); - EdgeSolveData.createIfEmpty(halfEdge2).skipFace.add(orig.loop.face); + newOrig.loop = orig.loop; + newTwin.loop = twin.loop; + + EdgeSolveData.transfer(orig, newOrig); + EdgeSolveData.transfer(twin, newTwin); - addToListInMap(orig.loop.face.__faceSolveData.vertexToEdge, vertex, halfEdge1); - addToListInMap(twin.loop.face.__faceSolveData.vertexToEdge, vertex, halfEdge2); + EdgeSolveData.createIfEmpty(twin).splitByFace.set(splittingFace, vertex); + EdgeSolveData.createIfEmpty(newTwin).skipFace.add(splittingFace); } -function findCloserProjection(nodes, point) { +function findCloserProjection(nodes, toNode) { let hero = -1; let heroDistance = Number.MAX_VALUE; for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; if (node == null) continue; - let projectionDistance = node.normal.dot(node.point.minus(point)); - if (hero == -1 || (projectionDistance > 0 && projectionDistance < heroDistance)) { + let projectionDistance = toNode.normal.dot(node.point.minus(toNode.point)); + if (projectionDistance > 0 && projectionDistance < heroDistance) { hero = i; heroDistance = projectionDistance; } @@ -224,20 +254,57 @@ function findCloserProjection(nodes, point) { return hero; } -function intersectSurfaceWithEdge(surface, edge, result) { +function intersectFaceWithEdge(face, edge, result, vertecies) { + + if (vertecies.has(edge.vertexA) || vertecies.has(edge.vertexB)) { + return; + } + const p0 = edge.vertexA.point; const ab = edge.vertexB.point.minus(p0); const length = ab.length(); const v = ab._multiply(1 / length); const edgeLine = new Line(p0, v); - const t = edgeLine.intersectSurface(surface); + const t = edgeLine.intersectSurface(face.surface); if (t >= 0 && t <= length) { const pointOfIntersection = edgeLine.parametricEquation(t); - const edgeNormal = edge.loop.face.surface.normal.cross(v)._normalize() ; - result.push(new Node(new Vertex(pointOfIntersection), edgeNormal, edge)); + //TODO: should check if point on an edge then exclude that edge from further intersection test cuz it would produce two identical Nodes + //TODO: should check if point on a vertex then exclude two edges of the vertex from further intersection test cuz it would produce three identical Nodes + if (pointBelongsToFace(pointOfIntersection, face)) { + let vertexOfIntersection; + if (math.areVectorsEqual(edge.vertexA.point, pointOfIntersection, TOLERANCE)) { + vertecies.add(edge.vertexA); + vertexOfIntersection = edge.vertexA; + //console.log("point A on surface"); + } if (math.areVectorsEqual(edge.vertexB.point, pointOfIntersection, TOLERANCE)) { + vertecies.add(edge.vertexB); + vertexOfIntersection = edge.vertexB; + //console.log("point B on surface"); + } else { + vertexOfIntersection = new Vertex(pointOfIntersection); + duplicatePointTest(pointOfIntersection); + } + + const edgeNormal = edge.loop.face.surface.normal.cross(v)._normalize() ; + result.push(new Node(vertexOfIntersection, edgeNormal, edge)); + + } } } +function pointBelongsToFace(point, face) { + //TODO: holes case + const tr = new Matrix3().setBasis(face.surface.calculateBasis()); + const polygon = face.outerLoop.asPolygon().map(p => tr.apply(p)); + const point2d = tr.apply(point); + return pointInsidePolygon(point2d, polygon); +} + +function pointInsidePolygon(point, polygon) { + //TODO: absolutely unacceptable way. should be done honoring intersecting edges and vertices. see TODOs above + return math.isPointInsidePolygon(point, polygon); +} + function edgeNormal(edge) { return edge.loop.face.surface.normal.cross( edge.vertexB.point.minus(edge.vertexA.point) )._normalize(); } @@ -285,14 +352,36 @@ EdgeSolveData.transfer = function(from, to) { to.__edgeSolveData = from.__edgeSolveData; }; -function Node(vertex, normal, splitsEdge) { +function Node(vertex, normal, splitsEdge, splittingFace) { this.vertex = vertex; this.normal = normal; this.point = vertex.point; this.edge = splitsEdge; + this.splittingFace = splittingFace; __DEBUG__.AddPoint(this.point); } + +let __DEBUG_POINT_DUPS = []; +function duplicatePointTest(point, data) { + data = data || {}; + let res = false; + for (let entry of __DEBUG_POINT_DUPS) { + let other = entry[0]; + if (math.areVectorsEqual(point, other, TOLERANCE)) { + res = true; + break; + } + } + __DEBUG_POINT_DUPS.push([point, data]); + if (res) { + __DEBUG__.Clear(); + __DEBUG__.AddPoint(point); + console.error('DUPLICATE DETECTED: ' + point) + } + return res; +} + class SolveData { constructor() { this.faceData = []; @@ -315,3 +404,5 @@ function addToListInMap(map, key, value) { } list.push(value); } + +let xxx = 0; diff --git a/web/app/brep/viz/scene-solid.js b/web/app/brep/viz/scene-solid.js index 60cc8ad0..1f30cb8b 100644 --- a/web/app/brep/viz/scene-solid.js +++ b/web/app/brep/viz/scene-solid.js @@ -69,10 +69,10 @@ export class SceneSolid { const visited = new Set(); for (let face of this.shell.faces) { for (let halfEdge of face.outerLoop.halfEdges) { - if (!visited.has(halfEdge.edge)) { + //if (!visited.has(halfEdge.edge)) { visited.add(halfEdge.edge); this.addLineToScene(halfEdge.vertexA.point, halfEdge.vertexB.point, halfEdge.edge); - } + //} } } } @@ -134,7 +134,7 @@ function createSolidMaterial() { polygonOffset : true, polygonOffsetFactor : 1, polygonOffsetUnits : 2, - side : THREE.DoubleSide + //side : THREE.DoubleSide }); }