Delaunay三角网的局部更新

    xiaoxiao2021-03-25  105

    关于Delaunay三角网的构建,我自己也写了一个算法,但是效率很低,于是参考网上的相关算法,这里特别感谢“纸异兽”,他的文章讲的最为详细, http://www.cnblogs.com/zhiyishou/p/4430017.html 但是这个算法,不是一个完整的算法,最后删除辅助三角形的三个顶点,过于简单粗暴。

    纸异兽实现的算法完美适配的情况是“点集中的每个点恰好是凸多边形的顶点”。 有两点做得不好, 1)当点集只有4到10个点,算法的缺陷被放大,经常找不完全,甚至出现孤点,每个点都应该是三角形的顶点,不应该出现孤立的点; 当点集有很多时,比如50个点以上,经常出现,边界三角形被漏掉,最终三角网不是凸包, 而是凹包。

    2)当有3个以上邻接点共线时,会出现点在三角形的边上,而不是顶点。

    第二个问题很好解决,只需要加个判断即可,

    for (j = buffer.length; j;) { var obj = circumcircle(vertices, buffer[--j], buffer[--j], current); if (obj.r != Infinity) { open.push(obj); } }

    第一个问题,比较麻烦,我的思路是,一个点一个点地删除,每删除一个点,就要局部更新三角网。

    <html><head><title>Delaunay update</title> </head><body><div> <canvas id="canvas" width="1000" height="350" style="border: solid black 1px; cursor: default;"></canvas> </div> <div> <input type="button" value="clear" onclick="demo.clear();"> <input type="button" value="load from string" onclick="demo.load();"> <input type="button" value="save to string" onclick="demo.save();"> <input type="button" value="generate" onclick="demo.generate(true);"> <input type="button" value="smaller" onclick="demo.smaller();"> <input type="button" value="delete" id="deleteBtn" disabled="true"> </div> <br/> <textarea id="txtArea" style="margin: 0px; width: 1200px; height: 33px;"></textarea> <br/> <script type="text/javascript" src="MathUtil.js"></script> <script type="text/javascript"> var demo = new Demo(); var config = new Config(); function Config() { this.MIN_DISTANCE = 40; this.TOTAL_VERTICES = 80; this.OBSTACLE_PERCENT = 25; } var canvas = document.getElementById('canvas'); var context = canvas.getContext('2d'); function Demo() { var vertices = []; var obstacleFlags = []; var allTriangles; function initRandomVertices() { if (vertices.length == 0) { var first = createRandomPoint(); first.id = 0; vertices.push(first); } for (var i = 1; i < config.TOTAL_VERTICES; i++) { var p = createProperRandomPoint(); if (p != null) { p.id = i; vertices.push(p); } else { break; } } console.log("expected: " + config.TOTAL_VERTICES + ", actually: " + vertices.length); } function createProperRandomPoint() { for (var i = 0; i < 255; i++) { var v = createRandomPoint(); var closestDistance = getClosestPoint(v, vertices, []).distance; if (closestDistance >= config.MIN_DISTANCE) { return v; } } return null; } function createRandomPoint() { return new Vertex(getRandom(50, canvas.width-50), getRandom(50, canvas.height-50)); } function getTriangleByPoint(p) { if (allTriangles != null && allTriangles.length > 0) { for (var i = 0; i < allTriangles.length; i++) { if (allTriangles[i].contains(p)) { return allTriangles[i]; } } } return null; } var mouseOnTri = null; var priTris = []; var pd = null; this.onMouseMove = function(x, y) { if (allTriangles == null || allTriangles.length == 0) { return; } priTris = []; pd = null; mouseOnTri = null; document.getElementById("deleteBtn").disabled = true; var p = new Vertex(x, y); for (var i = 0; i < vertices.length; i++) { var v = vertices[i]; var dx = v.x - p.x; var dy = v.y - p.y; if (dx*dx + dy*dy < 225) { pd = v; priTris = []; this.render(); return; } } for (var i = 0; i < allTriangles.length; i++) { var tri = allTriangles[i]; if (tri.circumcircleContains(p)) { priTris.push(tri); if (tri.contains(p)) { mouseOnTri = tri; } } } this.render(); } this.clickAt = function(x, y) { var p = new Vertex(x, y); var newTris = []; if (pd != null) { var trisDelete = point_triangles[pd.toString()]; var isUpdatingBorder = false; var edges = point_edges[pd.toString()]; for (var i = 0; i < edges.length; i++) { var edge = edges[i]; var str1 = edge[0].toString() + "," + edge[1].toString(); var str2 = edge[1].toString() + "," + edge[0].toString(); var tris = edge_triangles[str1]; if (tris == null) { tris = edge_triangles[str2]; } if (tris != null && tris.length == 1) { isUpdatingBorder = true; break; } } this.deleteTriangles(trisDelete); if (isUpdatingBorder) { //update border, delet point on border newTris = deletePointFromBorder(trisDelete, vertices, pd); } else { newTris = updateTriangles(trisDelete, vertices, null, pd); } vertices.splice(pd.id, 1); for (var i = pd.id; i < vertices.length; i++) { vertices[i].id = i; } } else if (priTris != null && priTris.length > 0) { p.id = vertices.length; vertices.push(p); this.deleteTriangles(priTris); if (mouseOnTri == null) { newTris = addPointOutOfBorder(p, vertices, priTris); priTris = []; } else { newTris = updateTriangles(priTris, vertices, p, null); priTris = []; } } else { //update border, add point out of border p.id = vertices.length; vertices.push(p); newTris = addPointOutOfCircles(p, vertices); } allTriangles = allTriangles.concat(newTris); //rebuild neighborhood clearNeighborhood(); for (var i = 0; i < allTriangles.length; i++) { var t = allTriangles[i]; t.index = i; saveToPointAndEdgeMap(t); } initNeighborhood(allTriangles); //draw new triangles this.render(); } this.deleteTriangles = function(needDelete) { for (var i = 0; i < needDelete.length; i++) { for (var j = 0; j < allTriangles.length; j++) { if (allTriangles[j].index == needDelete[i].index) { allTriangles.splice(j, 1); break; } } } } this.save = function() { document.getElementById("txtArea").value = vertices.toString(); } var isLoading = false; this.load = function() { this.clearVertices(); isLoading = true; var txt = document.getElementById("txtArea").value; if (txt == null || txt.length < 12) { return; } var arr = txt.split("<>"); var points = arr[0].split(","); for (var i=0; i<points.length; i++) { var p = new Vertex(0, 0); p.init(points[i]); p.id = i; vertices.push(p); } if (arr.length > 1) { var obstaclesStr = arr[1]; obstacleFlags = obstaclesStr.split(","); } if (obstacleFlags.length > 0) { this.generateObstacles(); } else if (vertices.length > 0) { this.generate(true); } isLoading = false; } this.generate = function(needDraw) { if (!isLoading) { this.clear(); initRandomVertices(); document.getElementById("txtArea").value = vertices.toString(); } allTriangles = getAllDelaunayTriangles(vertices, true); for (var i = 0; i < allTriangles.length; i++) { allTriangles[i].index = i; saveToPointAndEdgeMap(allTriangles[i]); } initNeighborhood(allTriangles); if (needDraw) { this.render(); } } this.generateObstacles = function() { } this.smaller = function() { if (vertices.length > 0) { var ret = []; for (var i= 0; i < vertices.length; i++) { var p = vertices[i]; p.x = p.x / 2; p.y = p.y / 2; ret.push(p); } document.getElementById("txtArea").value = ret.toString(); } } this.clear = function() { this.clearVertices(); document.getElementById("txtArea").value = ""; } this.clearVertices = function() { clearNeighborhood(); vertices = []; obstacleFlags = []; context.clearRect(0, 0, Number(canvas.width), Number(canvas.height)); } this.render = function() { context.clearRect(0, 0, Number(canvas.width), Number(canvas.height)); if (!canvas.getContext) return; if (vertices.length <= 0) { return; } var validCount = 0; for (var i = 0; i < allTriangles.length; i++) { allTriangles[i].draw(context); if (allTriangles[i].isValid) { validCount++; } } console.log("delaunay triangle total: " + allTriangles.length + ", validCount:" + validCount); vertices.forEach(function(vertex) { vertex.draw(context); context.font = "23px Courier New"; context.fillStyle = "orange"; context.fillText(vertex.id, vertex.x, vertex.y); }); if (pd != null) { pd.drawWith(context, "#00aa00"); } for (var i = 0; i < priTris.length; i++) { var t = priTris[i]; t.drawWith(context, "#22bb22"); } if (mouseOnTri != null) { mouseOnTri.drawCircumcircleWith(context, "#33aa33"); } } }; var EPSILON = Number.EPSILON || Math.pow(2,-52); function getAllDelaunayTriangles(vertices, needScanBorder) { var n, i, j, indices, buffer, open, closed, current, dx, dy; if ((n = vertices.length) < 3) return []; vertices = vertices.concat(); //create a array base vertices's key for (i = n, indices = new Array(n); i--;) indices[i] = i; //sort the indices base on the x coordinate of vertices //from bigger to smaller indices.sort(function (a, b) { return vertices[b].x - vertices[a].x; }); //create a supertriangle to buffer and push the vertex to vertices Array buffer = supertriangle(vertices); buffer[0].id = n; buffer[1].id = n+1; buffer[2].id = n+2; vertices.push(buffer[0], buffer[1], buffer[2]); //attach supertriangle to open for a start triangle open = [circumcircle(vertices, n + 0, n + 1, n + 2)]; closed = []; buffer = []; //starting loop from smallest x of vertex for (i = indices.length; i--; buffer.length = 0) { current = indices[i]; for (j = open.length; j--;) { dx = vertices[current].x - open[j].x; if (dx > 0 && dx * dx > open[j].r) { closed.push(open[j]); open.splice(j, 1); continue; } //skip this point if it is outside this circumcircle dy = vertices[current].y - open[j].y; if (dx * dx + dy * dy - open[j].r > EPSILON) continue; //add edges of this triangle to buffer buffer.push(open[j].i, open[j].j, open[j].j, open[j].k, open[j].k, open[j].i); open.splice(j, 1); } dedup(buffer); for (j = buffer.length; j;) { var obj = circumcircle(vertices, buffer[--j], buffer[--j], current); if (obj.r != Infinity) { open.push(obj); } } } for (i = open.length; i--;) closed.push(open[i]); var ret = []; // for (var i = 0; i < closed.length; i++) { // var obj = closed[i]; // var t = new Triangle(vertices[obj.i], vertices[obj.j], vertices[obj.k]); // t.circumcircle = circumcircle(vertices, obj.i, obj.j, obj.k); // ret.push(t); // } var deletingTris; var closed2; var removed = []; for (var j = 0; j < 3; j++) { deletingTris = []; closed2 = []; var lastIndex = vertices.length - 1; for (var i = 0; i < closed.length; i++) { var circumcircleObj = closed[i]; var t = new Triangle(vertices[circumcircleObj.i], vertices[circumcircleObj.j], vertices[circumcircleObj.k]); if (circumcircleObj.i == lastIndex || circumcircleObj.j == lastIndex || circumcircleObj.k == lastIndex) { deletingTris.push(t); } else { closed2.push(closed[i]); } } var deletingPoint = vertices[lastIndex]; for (var k = 0; k < ret.length; k++) { if (ret[k].isAnglePoint(deletingPoint)) { removed.push(ret[k]); deletingTris.push(ret[k]); } } var tris = deletePointFromBorder(deletingTris, vertices, deletingPoint); ret = ret.concat(tris); closed = closed2; vertices.pop(); } ret = subtract(ret, removed); for (var i = 0; i < closed.length; i++) { var obj = closed[i]; var t = new Triangle(vertices[obj.i], vertices[obj.j], vertices[obj.k]); t.circumcircle = obj; ret.push(t); } return ret; } function supertriangle(v) { var xmin = Number.POSITIVE_INFINITY, ymin = xmin, ymax = 0, xmax = 0, i, xl, yl, xlh; for (i = v.length; i--;) v[i].x < xmin && (xmin = v[i].x), v[i].x > xmax && (xmax = v[i].x), v[i].y < ymin && (ymin = v[i].y), v[i].y > ymax && (ymax = v[i].y); xl = xmax - xmin; yl = ymax - ymin; xlh = xl / 2; return [ new Vertex(xmin - xlh, ymax), //the left vertex's coordinate new Vertex(xmin + xlh, ymin - yl ), //the top vertex's coordinate new Vertex(xmax + xlh, ymax) //teh right vertex's coordinate ]; } //multiplex the circumcircle algorithm function circumcircle(v, i, j, k) { var x1 = v[i].x, y1 = v[i].y, x2 = v[j].x, y2 = v[j].y, x3 = v[k].x, y3 = v[k].y, fabsy1y2 = Math.abs(y1 - y2), fabsy2y3 = Math.abs(y2 - y3), xc, yc, m1, m2, mx1, mx2, my1, my2, dx, dy; if (fabsy1y2 < EPSILON) { m2 = -((x3 - x2) / (y3 - y2)); mx2 = (x2 + x3) / 2.0; my2 = (y2 + y3) / 2.0; xc = (x2 + x1) / 2.0; yc = m2 * (xc - mx2) + my2; } else if (fabsy2y3 < EPSILON) { m1 = -((x2 - x1) / (y2 - y1)); mx1 = (x1 + x2) / 2.0; my1 = (y1 + y2) / 2.0; xc = (x3 + x2) / 2.0; yc = m1 * (xc - mx1) + my1; } else { m1 = -((x2 - x1) / (y2 - y1)); m2 = -((x3 - x2) / (y3 - y2)); mx1 = (x1 + x2) / 2.0; mx2 = (x2 + x3) / 2.0; my1 = (y1 + y2) / 2.0; my2 = (y2 + y3) / 2.0; xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2); yc = (fabsy1y2 > fabsy2y3) ? m1 * (xc - mx1) + my1 : m2 * (xc - mx2) + my2; } dx = x2 - xc; dy = y2 - yc; return {i: i, j: j, k: k, x: xc, y: yc, r: dx * dx + dy * dy, r2: Math.sqrt(dx * dx + dy * dy)}; } function dedup(edges) { var i, j, a, b, m, n; for (j = edges.length; j;) { b = edges[--j]; a = edges[--j]; for (i = j; i;) { n = edges[--i]; m = edges[--i]; if ((a === m && b === n) || (a === n && b === m)) { edges.splice(j, 2); edges.splice(i, 2); break; } } } } function saveTriangleToPointMap(triangle, key) { if (point_triangles[key] == null) { point_triangles[key] = [triangle]; } else { point_triangles[key].push(triangle); } } function saveTriangleToEdgeMap(triangle, str1, str2) { if (edge_triangles[str1 + "," + str2] != null) { edge_triangles[str1 + "," + str2].push(triangle); } else if (edge_triangles[str2 + "," + str1] != null) { edge_triangles[str2 + "," + str1].push(triangle); } else { edge_triangles[str1 + "," + str2] = [triangle]; } } function saveToPointEdgesMap(tri, p1, p2, p3) { if (point_edges[p1] == null) { point_edges[p1] = []; } if (point_edges[p2] == null) { point_edges[p2] = []; } if (point_edges[p3] == null) { point_edges[p3] = []; } addToPointEdges(p1, [tri.v1, tri.v3]); addToPointEdges(p1, [tri.v1, tri.v2]); addToPointEdges(p2, [tri.v2, tri.v3]); addToPointEdges(p2, [tri.v2, tri.v1]); addToPointEdges(p3, [tri.v3, tri.v1]); addToPointEdges(p3, [tri.v3, tri.v2]); } function addToPointEdges(pStr, edge) { if (!containEdge(point_edges[pStr], edge)) { point_edges[pStr].push(edge); } } function containEdge(arr, edge) { for (var i = 0; i < arr.length; i++) { var e = arr[i]; if ((e[0].toString() == edge[0].toString() && e[1].toString() == edge[1].toString()) || (e[0].toString() == edge[1].toString() && e[1].toString() == edge[0].toString())) { return true; } } return false; } // return arr1 - arr2 elements function getSubtract(arr1, arr2) { if (arr2 == null || arr2.length == 0) { return arr1; } var arr = []; for (var i=0; i<arr1.length; i++) { if (contains(arr1[i], arr2)) { continue; } arr.push(arr1[i]); } return arr; } function contains(p, arr) { for (var i=0; i<arr.length; i++) { if (arr[i].x == p.x && arr[i].y == p.y) { return true; } } return false; } function getClosestPoint(p, array, uncheckArr) { var arr = getSubtract(array, uncheckArr); var min = -1; var v; for (var i=0; i<arr.length; i++) { var distance = getDistanceSquare(p, arr[i]); if (min < 0) { min = distance; v = arr[i]; } else if (min > distance) { min = distance; v = arr[i]; } } v.distance = Math.sqrt(min); return v; } var point_triangles = []; var edge_triangles = []; var point_edges = []; var triangle_neighbor = []; function clearNeighborhood() { point_triangles = []; edge_triangles = []; point_edges = []; triangle_neighbor = []; } function saveToPointAndEdgeMap(triangle) { var str1 = triangle.v1.toString(); var str2 = triangle.v2.toString(); var str3 = triangle.v3.toString(); saveTriangleToPointMap(triangle, str1); saveTriangleToPointMap(triangle, str2); saveTriangleToPointMap(triangle, str3); saveTriangleToEdgeMap(triangle, str1, str2); saveTriangleToEdgeMap(triangle, str2, str3); saveTriangleToEdgeMap(triangle, str3, str1); saveToPointEdgesMap(triangle, str1, str2, str3); } function initNeighborhood(allTriangles) { for (var i = 0; i < allTriangles.length; i++) { var triangle = allTriangles[i]; var neighbor = new Neighbor(triangle); triangle_neighbor[triangle.toString()] = neighbor; var edge12_triangles = subtract(getTrianglesByEdge(triangle.v1, triangle.v2), [triangle]); var edge23_triangles = subtract(getTrianglesByEdge(triangle.v2, triangle.v3), [triangle]); var edge31_triangles = subtract(getTrianglesByEdge(triangle.v3, triangle.v1), [triangle]); var v1_triangles = subtract(point_triangles[triangle.v1.toString()], [triangle].concat(edge12_triangles).concat(edge31_triangles)); var v2_triangles = subtract(point_triangles[triangle.v2.toString()], [triangle].concat(edge12_triangles).concat(edge23_triangles)); var v3_triangles = subtract(point_triangles[triangle.v3.toString()], [triangle].concat(edge23_triangles).concat(edge31_triangles)); neighbor.edgeNeighbors = edge12_triangles.concat(edge23_triangles).concat(edge31_triangles); neighbor.pointNeighbors = v1_triangles.concat(v2_triangles).concat(v3_triangles); if (edge12_triangles.length > 0) neighbor.triangle_edge[edge12_triangles[0].toString()] = [triangle.v1, triangle.v2]; if (edge23_triangles.length > 0) neighbor.triangle_edge[edge23_triangles[0].toString()] = [triangle.v2, triangle.v3]; if (edge31_triangles.length > 0) neighbor.triangle_edge[edge31_triangles[0].toString()] = [triangle.v3, triangle.v1]; } } function getPathOfTriangles(srcTriangle, dstTriangle) { var start = triangle_neighbor[srcTriangle.toString()]; start.parentNeighbor = "none"; if (start.me.equals(dstTriangle)) { return start; } var openedItems = start.getPassableNeighbors(); var closedNeighbors = [start]; for (var i = 0; i < openedItems.length; i++) { openedItems[i].parentNeighbor = start; } while (openedItems.length > 0) { var neighbor = openedItems.shift(); if (neighbor.me.equals(dstTriangle)) { return neighbor; } var newComingItems = neighbor.getPassableNeighbors(); for (var i = 0; i < newComingItems.length; i++) { // if (newComingItems[i].parentNeighbor == null) { // newComingItems[i].parentNeighbor = neighbor; // openedItems.push(newComingItems[i]); // } var newItem = newComingItems[i]; if (!hasNeighbor(openedItems, newItem) && !hasNeighbor(closedNeighbors, newItem)) { newItem.parentNeighbor = neighbor; openedItems.push(newItem); } } closedNeighbors.push(neighbor); } return null; } function hasNeighbor(arr, neighbor) { for (var i = 0; i < arr.length; i++) { if (arr[i].equals(neighbor)) { return true; } } return false; } function Neighbor(triangle) { this.id = triangle.id; this.me = triangle; this.triangle_edge = []; this.pointNeighbors = []; this.edgeNeighbors = []; this.getAllNeighbors = function() { return this.edgeNeighbors.concat(this.pointNeighbors); } this.getPassableNeighbors = function () { var ret = []; for (var i = 0; i < this.edgeNeighbors.length; i++) { if (this.edgeNeighbors[i].isValid && !this.edgeNeighbors[i].isObstacle) { ret.push(triangle_neighbor[this.edgeNeighbors[i].toString()]); } } return ret; } this.equals = function(other) { if (other == null) return false; if (other.id == this.id) { return true; } else { return false; } } } function getTrianglesByEdge(v1, v2) { var str1 = v1.toString(); var str2 = v2.toString(); if (edge_triangles[str1 + "," + str2] != null) { return edge_triangles[str1 + "," + str2]; } else if (edge_triangles[str2 + "," + str1] != null) { return edge_triangles[str2 + "," + str1]; } return []; } function addPointEdgeMap(edges, edge) { var flag = false; for (var i = 0; i < edges.length; i++) { var e = edges[i]; if ((e[0] == edge[0] && e[1] == edge[1]) || (e[0] == edge[1] && e[1] == edge[0])) { flag = true; break; } } if (!flag) { edges.push(edge); } } //for debug function pointsToStr(arr) { var ret = []; for (var i = 0; i < arr.length; i++) { ret.push(arr[i].id); } return ret.toString(); } function changeIndices(arr) { var ret = []; for (var i = 0; i < arr.length; i++) { ret.push(arr[i].id); arr[i].id = i; } return ret; } function loadIndices(arr, indexArr) { for (var i = 0; i < arr.length; i++) { arr[i].id = indexArr[i]; } } function updateTriangles(deleteLst, vertices, newPoint, deletingPoint) { var ret = []; if (newPoint != null) { var borderPoints = getBorderByNearTriangles(deleteLst, vertices); for (var i = 0; i < borderPoints.length; i++) { var p1 = borderPoints[i]; var p2 = borderPoints[getValidIndex(i+1, borderPoints.length)]; var t = new Triangle(p1, p2, newPoint); t.circumcircle = circumcircle(vertices, p1.id, p2.id, newPoint.id); ret.push(t); } return ret; } else { processDeleteLst(deleteLst, null, deletingPoint, ret); return ret; } } function deletePointFromBorder(deleteLst, vertices, deletingPoint) { var points = getBorderByNearTriangles(deleteLst, vertices); var index; for (var i = 0; i < points.length; i++) { var p = points[i]; var preIndex = getValidIndex(i-1, points.length); var nxtIndex = getValidIndex(i+1, points.length); if (deletingPoint != null && p.id == deletingPoint.id) { index = i; break; } } var arr = points.splice(index, points.length - index); points = arr.concat(points); var tris = scanForEdgeTris(points, deletingPoint, true); return tris; } function addPointOutOfBorder(outerPoint, vertices, deleteLst) { var ret = []; var points = processDeleteLst(deleteLst, outerPoint, null, ret); //scan border var triLst = addPointOutOfCircles(outerPoint, vertices); for (var i = 0; i < triLst.length; i++) { var t = triLst[i]; var arr = [t.v1, t.v2, t.v3]; var arr2 = subtract(arr, points); if (arr2.length > 0) { ret.push(t); } } return ret; } //retrieve triangles from delete triangle lst, return points of the return triangles function processDeleteLst(deleteLst, outerPoint, erasingPoint, outputTris) { var remains = []; var points = []; for (var i = 0; i < deleteLst.length; i++) { var tri = deleteLst[i]; var neighbor = triangle_neighbor[tri.toString()]; var lst = neighbor.getAllNeighbors(); var arr = subtract(lst, deleteLst); var arr2 = subtract(arr, remains); remains = remains.concat(arr2); var pts = [tri.v1, tri.v2, tri.v3]; var pts2 = subtract(pts, points); points = points.concat(pts2); } if (outerPoint != null) { points.push(outerPoint); } if (erasingPoint != null) { points = subtract(points, [erasingPoint]); } var indexBac = changeIndices(points); var tris = getAllDelaunayTriangles(points, true); loadIndices(points, indexBac); for (var i = 0; i < tris.length; i++) { var has = false; var crossWith = false; var t = tris[i]; for (var j = 0; j < remains.length; j++) { var tri = remains[j]; if (tri.isSame(t)) { has = true; break; } if (tri.crossWith(t)) { crossWith = true; break; } } if (!has && !crossWith) { outputTris.push(t); } } return points; } function addPointOutOfCircles(outerPoint, vertices) { //retrieve max border var edgeLst = []; var pEdgesMap = []; for (var key in edge_triangles) { var tris = edge_triangles[key]; if (tris.length == 1) { var arr = key.split(","); var p1 = new Vertex(), p2 = new Vertex(); p1.init(arr[0]); p2.init(arr[1]); p1 = getVertex(vertices, p1); p2 = getVertex(vertices, p2); var edge = [p1.id, p2.id]; edgeLst.push(edge); if (pEdgesMap[p1.id] == null) { pEdgesMap[p1.id] = [edge]; } else { pEdgesMap[p1.id].push(edge); } if (pEdgesMap[p2.id] == null) { pEdgesMap[p2.id] = [edge]; } else { pEdgesMap[p2.id].push(edge); } } } var points = getBorderSqence(pEdgesMap, edgeLst[0][0], edgeLst[0][1], vertices); var ret = []; for (var i = 0; i < points.length; i++) { var next = getValidIndex(i + 1, points.length); if (!isCrossWithBorder(points, i, outerPoint)) { if (!isCrossWithBorder(points, next, outerPoint)) { var t = new Triangle(outerPoint, points[i], points[next]); t.circumcircle = circumcircle(vertices, outerPoint.id, points[i].id, points[next].id); ret.push(t); } } } return ret; } function getVertex(vertices, p) { for (var i = 0; i < vertices.length; i++) { var current = vertices[i]; current.x = parseInt(current.x); current.y = parseInt(current.y); if (current.x == p.x && current.y == p.y) { return current; } } return null; } function getBorderByNearTriangles(triangles, vertices) { var edges = []; var eArr = []; for (var i = 0; i < triangles.length; i++) { var t = triangles[i]; eArr.push([t.v1, t.v2]); eArr.push([t.v2, t.v3]); eArr.push([t.v3, t.v1]); } for (var i = 0; i < eArr.length; i++) { var edge = eArr[i]; var index = getEdgeIndex(edges, edge); if (index < 0) { edges.push(edge); } else { edges.splice(index, 1); } } var pEdgesMap = []; for (var i = 0; i < edges.length; i++) { var edge = edges[i]; if (pEdgesMap[edge[0].id] == null) { pEdgesMap[edge[0].id] = [[edge[0].id, edge[1].id]]; } else { addPointEdgeMap(pEdgesMap[edge[0].id], [edge[0].id, edge[1].id]); } if (pEdgesMap[edge[1].id] == null) { pEdgesMap[edge[1].id] = [[edge[0].id, edge[1].id]]; } else { addPointEdgeMap(pEdgesMap[edge[1].id], [edge[0].id, edge[1].id]); } } return getBorderSqence(pEdgesMap, edges[0][0].id, edges[0][1].id, vertices); } function getEdgeIndex(edges, edge) { for (var i = 0; i < edges.length; i++) { if ((edges[i][0].id == edge[0].id && edges[i][1].id == edge[1].id) || (edges[i][1].id == edge[0].id && edges[i][0].id == edge[1].id)) { return i; } } return -1; } function isCrossWithBorder(borderPoints, index, outerPoint) { var p1 = getValidIndex(index - 1, borderPoints.length); var p2 = getValidIndex(index + 1, borderPoints.length); var needSkip = [p1, p2]; for (var i = 0; i < borderPoints.length; i++) { var edgeIndex = (i+1) % borderPoints.length; if ((index == i && needSkip.indexOf(edgeIndex) >= 0) || (index == edgeIndex && needSkip.indexOf(i) >= 0)) { continue; } if (lineSegmentCross(borderPoints[i], borderPoints[edgeIndex], borderPoints[index], outerPoint)) { return true; } } return false; } function getBorderSqence(pEdgeMap, e0, e1, vertices) { var squence = [e0]; var next = pEdgeMap[e0]; var p0 = e0, p1 = e1; if (next == null) { return; } if (next.length == 1) { squence.push(e1); next = pEdgeMap[e1]; } while (next != null) { var e = findNextEdge(next, p0, p1); squence.push(e[0]); next = pEdgeMap[e[0]]; p0 = e[0]; p1 = e[1]; if (p0 == e0) { squence.pop(); next = null; } if (p0 == e1) { next == null; } } var points = []; for (var i = 0; i < squence.length; i++) { var p = vertices[squence[i]]; p.location = squence[i]; points.push(p); } return points; } function copyArr(arr) { var ret = []; for (var i in arr) { ret.push(arr[i]); } return ret; } function scanForEdgeTris(points, deletingPoint, needReverse) { if (points.length < 4) { return []; } var ret = []; var count=0; while(true) { count++; if (count > 10) break; var len = ret.length; var isClockwise = isClockwiseSquence(points); if (needReverse) { isClockwise = !isClockwise; } var concavMark = isClockwise ? "left" : "right"; var removed = []; points[1].mark = points[points.length-1].mark = "end"; var curSize = points.length; for (var i = 2; i < curSize - 1; i++) { var p = points[i]; if (p.mark != concavMark) { continue; } var preIndex = getValidIndex(i-1, points.length); var nxtIndex = getValidIndex(i+1, points.length); var pre = points[preIndex]; var nxt = points[nxtIndex]; if (pre.mark != concavMark && nxt.mark != concavMark) { var t = new Triangle(pre, p, nxt); t.circumcircle = circumcircle(points, preIndex, i, nxtIndex); ret.push(t); removed.push(p); } else if (nxt.mark == concavMark) { removed.push(p, nxt); var arr = [pre, p, nxt]; var j = i + 2; for (; j < curSize - 1; j++) { if (points[j].mark == concavMark) { arr.push(points[j]); removed.push(points[j]); } else { break; } } arr.push(points[j]); getTrisFromConvexPolygon(arr, ret); i = j + 1; } } if (len == ret.length) { break; } for (var i = 0; i < removed.length; i++) { for (var j = 0; j < points.length; j++) { if (points[j].id == removed[i].id) { points.splice(j, 1); break; } } } if (points.length < 4) { break; } } return ret; } function getTrisFromConvexPolygon(points, output) { if (points.length < 3) { return; } else if (points.length == 3) { var t = new Triangle(points[0], points[1], points[2]); t.circumcircle = circumcircle(points, 0, 1, 2); return output.push(t); } var i = 2; for (;i < points.length; i++) { var tri = new Triangle(points[0], points[1], points[i]); tri.circumcircle = circumcircle(points, 0, 1, i); var isValid = true; for (var j = 2; j < points.length; j++) { if (j == i) continue; var p = points[j]; if (tri.circumcircleContains(p)) { isValid = false; break; } } if (isValid) { output.push(tri); break; } } if (i == points.length-1) { points.splice(0, 1); getTrisFromConvexPolygon(points, output); } else if (i == 2) { points.splice(1, 1); getTrisFromConvexPolygon(points, output); } else { var item = points[i]; var arr = points.splice(1, i); var item0 = points.shift(); points.push(item0), points.unshift(item); getTrisFromConvexPolygon(arr, output); getTrisFromConvexPolygon(points, output); } } function findNextEdge(edges, e0, e1) { if (edges[0][0] != e0 && edges[0][0] != e1) return [edges[0][0], edges[0][1]]; if (edges[0][1] != e0 && edges[0][1] != e1) return [edges[0][1], edges[0][0]]; if (edges[1][0] != e0 && edges[1][0] != e1) return [edges[1][0], edges[1][1]]; if (edges[1][1] != e0 && edges[1][1] != e1) return [edges[1][1], edges[1][0]]; } function isClockwiseSquence(vertices) { var leftAngleSum = 0; var rightAngleSum = 0; for (var i = 0; i < vertices.length; i++) { var v2Index = getValidIndex(i + 1, vertices.length); var pIndex = getValidIndex(i + 2, vertices.length); var curSide = getPointOnVectorSide(vertices[i], vertices[v2Index], vertices[pIndex]); var angle = Math.PI - getAngle(vertices[i], vertices[v2Index], vertices[pIndex]); if (curSide == ON_LEFT_SIDE) { vertices[v2Index].mark = "left"; leftAngleSum += angle; } else if (curSide == ON_RIGHT_SIDE) { vertices[v2Index].mark = "right"; rightAngleSum += angle; } } //Math.abs(rightAngleSum - leftAngleSum)/2 equal with Math.PI if (rightAngleSum > leftAngleSum) { return true; } return false; } window.onload = function() { document.getElementById("canvas").onclick = function(e) { e = e ? e : window.event; var rect = this.getBoundingClientRect(); demo.clickAt(e.clientX - rect.left, e.clientY - rect.top); } document.getElementById("canvas").onmousemove = function(e) { e = e ? e : window.event; var rect = this.getBoundingClientRect(); demo.onMouseMove(e.clientX - rect.left, e.clientY - rect.top); } }; </script> </body></html>
    转载请注明原文地址: https://ju.6miu.com/read-24164.html

    最新回复(0)