-1

I've recently finished implementing the Bowyer-Watson algorithm for calculating Delaunay triangulations in the plane in Javascript. The Wikipedia article (Wikipedia Watson algorithm) says it's O(n log n) to O(n^2) in especially degenerate cases. How come my implementation always does it in more than O(n^2)? Is the fault in my implementation, or in the algorithm itself? I'm running the function efter an onclick event and in Google Chrome if that helps.

I noticed it when checking how long it took to calculate from different amounts of nodes. It only takes around 4ms for 100 nodes, then 150ms for 1'000 nodes, then all the way to 60'000ms for 10'000 nodes. Another curious thing is that the first two runs of the function after refreshing the tab always take the longest. For 100 nodes, it goes 30ms -> 16ms -> 4ms -> 4ms -> ... for example.

Here comes the relevant code. If you can't bother reading through someone else's sloppy code (I wouldn't blame you), please go to the Wikipedia article above and see if you can find the problem in the pseudocode there. I've followed it very closely, so any problems you can find in my program might be coming from the way this algorithm is constructed. Good luck, and thanks in advance.

// This is the function where the problem lies
function get_delaunay_triangulation(nodeList){
    var triangulation = []; // The list of triangles the function will output
    var badTriangles = []; // List of triangles no longer valid due to the insertion
    var nextNode; // Node inserted in each iteration
    var polygonHole = [];

    // The supertriangle has to contain all other nodes inside it
    /* Node is a class defined by its coords, and are accessed with node.x and node.y */
    var sTVertex1 = new Node(0, 0);
    // c.width and c.height are just the width and height of the screen
    var sTVertex2 = new Node(2 * c.width + 1, 0); 
    var sTVertex3 = new Node(0, 2 * c.height + 1);
    /* Edge is a class defined by its vertices, and are accessed with 
    edge.vertex1 and edge.vertex2 */
    var sTEdge1 = new Edge(sTVertex1, sTVertex2);
    var sTEdge2 = new Edge(sTVertex2, sTVertex3);
    var sTEdge3 = new Edge(sTVertex3, sTVertex1);
    /* Triangle is a class defined by its edges which you can access with triangle.edges, 
    but you can also access its vertices with triangle.vertices */
    var superTriangle = new Triangle([sTEdge1, sTEdge2, sTEdge3]);
    triangulation.push(superTriangle);

    for (var i = 0; i < nodeList.length; i++){
        nextNode = nodeList[i]; // The next point to be inserted

        badTriangles = []; // Resets badTriangles for every new point

        /* This loops through every triangle in the triangulation added so far. This 
        might be the cause of the slowdown, but I don't see why it would do that, and 
        why the wikipedia article wouldn't say anything about that. */
        for (var j = 0; j < triangulation.length; j++){
            var maybeBadTriangle = triangulation[j];
            var verticesInST = maybeBadTriangle.get_vertices_in(
            superTriangle.vertices);
            /* This checks every triangle in triangulation and adds the ones that
            are no longer valid due to the insertion. This part works well and is not
            likely to be the cause of the slowdown. */
            if (verticesInST.length == 0){
                if (maybeBadTriangle.circumcircle_contains(nextNode)){
                    badTriangles.push(maybeBadTriangle);
                }
            } else if (verticesInST.length == 1) {
                var otherVertices = [...maybeBadTriangle.vertices];
                otherVertices.splice(
                    otherVertices.indexOf(verticesInST[0]), 1);
                var tempEdge = new Edge(otherVertices[0], otherVertices[1]);
                if (verticesInST[0].isRightOfEdge(tempEdge) == 
                nextNode.isRightOfEdge(tempEdge)){
                    badTriangles.push(maybeBadTriangle);
                }
            } else if (verticesInST.length == 2) {
                var otherVertices = [...maybeBadTriangle.vertices];
                var otherVertex;
                for (var k = 0; k < 3; k++){
                    if (!superTriangle.vertices.includes(otherVertices[k])){
                        otherVertex = otherVertices[k];
                        break;
                    }
                }
                var tempEdge = new Edge(otherVertex, new Node(
                otherVertex.x + verticesInST[1].x - verticesInST[0].x,
                otherVertex.y + verticesInST[1].y - verticesInST[0].y)
                );
                if (nextNode.isRightOfEdge(tempEdge) == 
                verticesInST[0].isRightOfEdge(tempEdge)){
                    badTriangles.push(maybeBadTriangle);
                }
            } else {
                badTriangles.push(maybeBadTriangle);
            }
        }
        /* This part gathers the edges in badTriangles that are not shared by any other 
        triangle in badTriangles, so that polygonHole will contain the boundary of 
        the hole left behind when the bad triangles are removed. */
        polygonHole = [];
        for (var j = 0; j < badTriangles.length; j++){
            // Kollar varje kant i triangeln
            for (var k = 0; k < 3; k++){
                var testEdge = badTriangles[j].edges[k];
                var testEdgeIsUnique = true;
                for (var l = 0; l < badTriangles.length; l++){
                    for (var m = 0; m < 3; m++){
                        if (testEdge.is_equal_to(badTriangles[l].edges[m]) &&
                        l != j){
                            testEdgeIsUnique = false;
                            break;
                        }
                    }
                    if (!testEdgeIsUnique){ break; }
                }
                if (testEdgeIsUnique){
                    polygonHole.push(testEdge);
                }
            }
        }

        // Removes the triangles in badTriangles from triangulation
        for (var j = 0; j < badTriangles.length; j++){
            var index = triangulation.indexOf(badTriangles[j]);
            if (index != -1){
                triangulation.splice(index, 1);
            }
        }
        // Makes a new triangle from every edge of polygonHole to nextNode
        var polygonEdge;
        for (var j = 0; j < polygonHole.length; j++){
            polygonEdge = polygonHole[j];
            triangulation.push(
                new Triangle([
                    polygonEdge, 
                    new Edge(polygonEdge.vertex1, nextNode), 
                    new Edge(polygonEdge.vertex2, nextNode)
                ])
            );
        }
    }
    /* When every point is inserted, the triangles which have a vertex in the original 
    superTriangle are removed from triangulation */
    var i = 0;
    while (i < triangulation.length){
        testVertices = triangulation[i].vertices;
        if (testVertices.includes(sTVertex1) ||
            testVertices.includes(sTVertex2) ||
            testVertices.includes(sTVertex3)){
            triangulation.splice(i, 1);
        } else {
            i++;
        }
    }  

    return new Triangle_Mesh(triangulation);
}
pix
  • 1,264
  • 19
  • 32
  • Possible duplicate of [Implementing Bowyer-Watson algorithm for delaunay triangulation](https://stackoverflow.com/questions/40934453/implementing-bowyer-watson-algorithm-for-delaunay-triangulation) – Michel Jun 19 '19 at 18:00

1 Answers1

-1

The implementation can be made o(n.log(n)), but the implementation given by Wikipedia is o(n^2).

See Implementing Bowyer-Watson algorithm for delaunay triangulation

Michel
  • 338
  • 1
  • 13
  • Thanks. Is there some easy fix or additional algorithm to make it faster? Or do I have to implement a completely different algorithm? – Truls Henriksson Jun 20 '19 at 21:10
  • 1
    You can probably do something about it. AFAIK, the difference between an o(n^2) and an o(n.log(n)) implementation of this algorithm is the part where you look for the "bad" triangles. In your algorithm, the search is o(n), while it can be done in o(log(n)). Though, the trick to get it done in o(log(n)) is to use some kind of data structure to speed up the process. The main idea is that instead of going through all of you triangles to find the bad ones, you could use the connectivity of the triangles to "walk" towards and in the vicinity of the newly added point. – Michel Jun 24 '19 at 16:33