Delaunay三角网的局部更新

关于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>
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值