fix boolean algorithm bugs

This commit is contained in:
Val Erastov 2017-01-20 19:45:25 -08:00
parent 15598fb740
commit 12095e4a25

View file

@ -60,11 +60,18 @@ export function BooleanAlgorithm( shell1, shell2, type ) {
intersectFaces(shell1, shell2, type !== TYPE.UNION);
const allFaces = [];
for (let faceData of facesData) {
fixCoincidentEdges(faceData, type !== TYPE.UNION)
}
const allFaces = [];
//__DEBUG__.AddSegment(shell2.faces[0].outerLoop.halfEdges[0].vertexA.point, shell2.faces[0].outerLoop.halfEdges[0].vertexB.point)
const newLoops = new Set();
for (let faceData of facesData) {
const face = faceData.face;
__DEBUG__.Clear();
for (let l of face.loops) l.halfEdges.forEach(he => __DEBUG__.AddHalfEdge(he, 0x00ff00));
const loops = [];
const seen = new Set();
const edges = [];
@ -80,7 +87,7 @@ export function BooleanAlgorithm( shell1, shell2, type ) {
}
const loop = new Loop();
while (edge) {
//__DEBUG__.AddHalfEdge(edge);
__DEBUG__.AddHalfEdge(edge);
const isNew = faceData.newEdges.indexOf(edge) != -1;
if (isNew) newLoops.add(loop);
@ -90,7 +97,7 @@ export function BooleanAlgorithm( shell1, shell2, type ) {
if (!candidates) {
break;
}
edge = findMaxTurningLeft(edge, candidates);
edge = findMaxTurningLeft(edge, candidates, face.surface.normal);
if (seen.has(edge)) {
break;
}
@ -118,6 +125,43 @@ export function BooleanAlgorithm( shell1, shell2, type ) {
return result;
}
function fixCoincidentEdges(faceData, keep) {
const newEdges = [];
const toRemove = [];
for (let newEdge of faceData.newEdges) {
const oldEdge = findCoincidentEdge(newEdge, faceData.face);
if (oldEdge != null) {
deleteHalfEdge(oldEdge);
//deleteHalfEdge(newEdge.twin());
if (oldEdge.edge.halfEdge1 == oldEdge) {
oldEdge.edge.halfEdge1 = newEdge;
} else {
oldEdge.edge.halfEdge2 = newEdge;
}
if (keep) {
} else {
//deleteEdge(newEdge.edge);
//deleteEdge(oldEdge.edge);
}
} else {
newEdges.push(newEdge);
}
}
//faceData.newEdges = newEdges;
}
function findCoincidentEdge(edge, face) {
for (let loop of face.loops) {
for (let he of loop.halfEdges) {
if (edge.vertexA == he.vertexA && edge.vertexB == he.vertexB) {
return he;
}
}
}
return null;
}
function filterFaces(faces, newLoops, validLoops) {
const validFaces = new Set(faces);
const result = new Set();
@ -236,7 +280,7 @@ function initSolveData(shell, facesData) {
}
}
function findMaxTurningLeft(edge, edges) {
function findMaxTurningLeft(edge, edges, normal) {
edges = edges.slice();
function edgeVector(edge) {
return edge.vertexB.point.minus(edge.vertexA.point)._normalize();
@ -244,7 +288,7 @@ function findMaxTurningLeft(edge, edges) {
const edgeV = edgeVector(edge);
function leftTurningMeasure(v1, v2) {
let measure = v1.dot(v2);
if (v1.cross(v1) < 0) {
if (v1.cross(v2).dot(normal) < 0) {
measure *= -1;
measure += 2;
}
@ -281,16 +325,19 @@ function intersectFaces(shell1, shell2, inverseCrossEdgeDirection) {
newEdges.forEach(e => {
//__DEBUG__.AddHalfEdge(e.halfEdge1);
face1.data[MY].newEdges.push(e.halfEdge1);
face2.data[MY].newEdges.push(e.halfEdge2);
addToListInMap(face1.data[MY].vertexToEdge, e.halfEdge1.vertexA, e.halfEdge1);
addToListInMap(face2.data[MY].vertexToEdge, e.halfEdge2.vertexA, e.halfEdge2);
addNewEdge(face1, e.halfEdge1);
addNewEdge(face2, e.halfEdge2);
});
}
}
}
function addNewEdge(face, halfEdge) {
face.data[MY].newEdges.push(halfEdge);
addToListInMap(face.data[MY].vertexToEdge, halfEdge.vertexA, halfEdge);
}
function collectNodesOfIntersectionOfFace(splittingFace, face, nodes) {
//__DEBUG__.Clear();
@ -378,11 +425,28 @@ function split(nodes, result, onCurve, direction) {
edge.halfEdge2 = halfEdgeSameDir;
halfEdgeNegativeDir.edge = edge;
halfEdgeSameDir.edge = edge;
result.push(edge);
if (!containsEdges(result, edge)) {
result.push(edge);
}
}
}
function containsEdges(edges, edge) {
for (let e of edges) {
if (isSameEdge(e, edge)) {
return true;
}
}
return false;
}
function isSameEdge(e1, e2) {
return e1.halfEdge1.vertexA == e2.halfEdge1.vertexA &&
e1.halfEdge2.vertexB == e2.halfEdge2.vertexB;
}
function splitEdgeByVertex(originHalfEdge, vertex, splittingFace) {
function splitHalfEdge(h) {
@ -455,9 +519,12 @@ function intersectFaceWithEdge(face, edge, result, vertecies) {
const pointOfIntersection = edgeLine.parametricEquation(t);
//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)) {
const classRes = classifyPointToFace(pointOfIntersection, face);
if (classRes.inside) {
let vertexOfIntersection;
if (math.areVectorsEqual(edge.vertexA.point, pointOfIntersection, TOLERANCE)) {
if (classRes.vertex) {
vertexOfIntersection = classRes.vertex;
} else if (math.areVectorsEqual(edge.vertexA.point, pointOfIntersection, TOLERANCE)) {
vertecies.add(edge.vertexA);
vertexOfIntersection = edge.vertexA;
//console.log("point A on surface");
@ -471,8 +538,11 @@ function intersectFaceWithEdge(face, edge, result, vertecies) {
}
const edgeNormal = edge.loop.face.surface.normal.cross(v)._normalize() ;
result.push(new Node(vertexOfIntersection, edgeNormal, edge));
const node = new Node(vertexOfIntersection, edgeNormal, edge);
result.push(node);
if (classRes.edge) {
splitEdgeByVertex(classRes.edge, vertexOfIntersection, edge.loop.face);
}
}
}
}
@ -490,28 +560,33 @@ function deleteHalfEdge(he) {
removeFromListInMap(he.loop.face.data[MY].vertexToEdge, he.vertexA, he);
}
function pointBelongsToFace(point, face) {
function classifyPointToFace(point, face) {
const tr = face.surface.get2DTransformation();
if (pointInsideLoop(point, face.outerLoop, tr)) {
for (let innerLoop of face.innerLoops) {
if (pointInsideLoop(point, innerLoop, tr)) {
return false;
}
}
return true;
}
return false;
}
function pointInsideLoop(point, loop, tr) {
const polygon = loop.asPolygon().map(p => tr.apply(p));
const point2d = tr.apply(point);
return pointInsidePolygon(point2d, polygon);
const result = classifyPointInsideLoop(point2d, face.outerLoop, tr);
if (result.inside) {
if (result.vertex || result.edge) {
return result;
} else {
for (let innerLoop of face.innerLoops) {
const innerResult = classifyPointInsideLoop(point2d, innerLoop, tr);
if (innerResult.inside) {
if (innerResult.vertex || innerResult.edge) {
return innerResult;
} else {
return {inside: false};
}
}
}
return result;
}
}
return result;
}
function pointInsidePolygon(point, polygon) {
//TODO: absolutely unacceptable way. should be done honoring intersecting edges and vertices. see TODOs above
return math.isPointInsidePolygon(point, polygon);
return math.isPointInsidePolygon(point, polygon, TOLERANCE);
}
function edgeNormal(edge) {
@ -605,6 +680,74 @@ class FaceSolveData {
}
}
export function classifyPointInsideLoop( inPt, loop, tr ) {
var EPSILON = TOLERANCE;
function VertexResult(vertex) {
this.inside = true;
this.vertex = vertex;
}
function EdgeResult(edge) {
this.inside = true;
this.edge = edge;
}
const _2dCoords = new Map();
for( let edge of loop.halfEdges ) {
const p = tr.apply(edge.vertexA.point);
if (math.areEqual(inPt.y, p.y, TOLERANCE) && math.areEqual(inPt.x, p.x, TOLERANCE)) {
return new VertexResult(edge.vertexA);
}
_2dCoords.set(edge.vertexA, p);
}
// inPt on polygon contour => immediate success or
// toggling of inside/outside at every single! intersection point of an edge
// with the horizontal line through inPt, left of inPt
// not counting lowerY endpoints of edges and whole edges on that line
var inside = false;
for( let edge of loop.halfEdges ) {
const a = _2dCoords.get(edge.vertexA);
const b = _2dCoords.get(edge.vertexB);
var edgeLowPt = a;
var edgeHighPt = b;
var edgeDx = edgeHighPt.x - edgeLowPt.x;
var edgeDy = edgeHighPt.y - edgeLowPt.y;
if ( Math.abs(edgeDy) > EPSILON ) { // not parallel
if ( edgeDy < 0 ) {
edgeLowPt = b; edgeDx = - edgeDx;
edgeHighPt = a; edgeDy = - edgeDy;
}
if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue;
if ( math.areEqual(inPt.y, edgeLowPt.y, TOLERANCE) ) {
if ( math.areEqual(inPt.x, edgeLowPt.x, TOLERANCE) ) new VertexResult(edgeLowPt); // inPt is on contour ?
// continue; // no intersection or edgeLowPt => doesn't count !!!
} else {
var perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y);
if ( math.areEqual(perpEdge, 0, TOLERANCE_SQ) ) return new EdgeResult(edge); // inPt is on contour ?
if ( perpEdge < 0 ) continue;
inside = ! inside; // true intersection left of inPt
}
} else { // parallel or colinear
if ( !math.areEqual(inPt.y, edgeLowPt.y, TOLERANCE) ) continue; // parallel
// egde lies on the same horizontal line as inPt
if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return new EdgeResult(edge); // inPt: Point on contour !
// continue;
}
}
return {inside};
}
function addToListInMap(map, key, value) {
let list = map.get(key);
if (!list) {