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);
}