1 /*
2     Copyright © 2022, Inochi2D Project
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4 
5     Authors:
6     - Luna Nielsen
7     - Asahi Lina
8 
9     in_circle() from poly2tri, licensed under BSD-3:
10         Copyright (c) 2009-2018, Poly2Tri Contributors
11         https://github.com/jhasse/poly2tri
12 */
13 module creator.viewport.common.mesh;
14 import creator.viewport;
15 import inochi2d;
16 import inochi2d.core.dbg;
17 import bindbc.opengl;
18 import std.algorithm.mutation;
19 
20 struct MeshVertex {
21     vec2 position;
22     MeshVertex*[] connections;
23 }
24 
25 void connect(MeshVertex* self, MeshVertex* other) {
26     if (isConnectedTo(self, other)) return;
27 
28     self.connections ~= other;
29     other.connections ~= self;
30 }
31  
32 void disconnect(MeshVertex* self, MeshVertex* other) {
33     import std.algorithm.searching : countUntil;
34     import std.algorithm.mutation : remove;
35     
36     auto idx = other.connections.countUntil(self);
37     if (idx != -1) other.connections = remove(other.connections, idx);
38 
39     idx = self.connections.countUntil(other);
40     if (idx != -1) self.connections = remove(self.connections, idx);
41 }
42 
43 void disconnectAll(MeshVertex* self) {
44     while(self.connections.length > 0) {
45         self.disconnect(self.connections[0]);
46     }
47 }
48 
49 bool isConnectedTo(MeshVertex* self, MeshVertex* other) {
50     if (other == null) return false;
51 
52     foreach(conn; other.connections) {
53         if (conn == self) return true;
54     }
55     return false;
56 }
57 
58 class IncMesh {
59 private:
60     MeshData* data;
61     vec2 eOrigin;
62 
63     void mImport(ref MeshData data) {
64         // Reset vertex length
65         vertices.length = 0;
66         eOrigin = data.origin;
67 
68         // Iterate over flat mesh and extract it in to
69         // vertices and "connections"
70         MeshVertex*[] iVertices;
71 
72         iVertices.length = data.vertices.length;
73         foreach(idx, vertex; data.vertices) {
74             iVertices[idx] = new MeshVertex(vertex, []);
75         }
76 
77         foreach(i; 0..data.indices.length/3) {
78             auto index = data.indices[i*3];
79             auto nindex = data.indices[(i*3)+1];
80             auto nnindex = data.indices[(i*3)+2];
81 
82             if (!iVertices[index].isConnectedTo(iVertices[nindex])) iVertices[index].connect(iVertices[nindex]);
83             if (!iVertices[nindex].isConnectedTo(iVertices[nnindex])) iVertices[nindex].connect(iVertices[nnindex]);
84             if (!iVertices[nnindex].isConnectedTo(iVertices[index])) iVertices[nnindex].connect(iVertices[index]);
85         }
86         
87         void printConnections(MeshVertex* v) {
88             import std.stdio;
89             ushort[] conns;
90             vec2[] coords;
91             foreach(conn; v.connections) {
92                 foreach(key, value; iVertices) {
93                     if (value == conn) {
94                         conns ~= cast(ushort)key;
95                         coords ~= value.position;
96                         break;
97                     }
98                 }
99             }
100         }
101 
102         foreach(i, vertex; iVertices) {
103             printConnections(vertex);
104             vertices ~= vertex;
105         }
106 
107         refresh();
108     }
109 
110     MeshData mExport() {
111         import std.algorithm.searching : canFind;
112         MeshData* newData = new MeshData;
113         newData.origin = eOrigin;
114 
115         ushort[MeshVertex*] indices;
116         ushort indiceIdx = 0;
117         foreach(vertex; vertices) {
118             newData.vertices ~= vertex.position;
119             newData.uvs ~= vertex.position;
120             indices[vertex] = indiceIdx++;
121         }
122 
123         bool goesBackToRoot(MeshVertex* root, MeshVertex* vert) {
124             foreach(MeshVertex* conn; vert.connections) {
125                 if (conn == root) return true;
126             }
127             return false;
128         }
129 
130         bool hasIndiceSeq(ushort a, ushort b, ushort c) {
131             foreach(i; 0..newData.indices.length/3) {
132                 int score = 0;
133 
134                 if (newData.indices[(i*3)+0] == a || newData.indices[(i*3)+0] == b || newData.indices[(i*3)+0] == c) score++;
135                 if (newData.indices[(i*3)+1] == a || newData.indices[(i*3)+1] == b || newData.indices[(i*3)+1] == c) score++;
136                 if (newData.indices[(i*3)+2] == a || newData.indices[(i*3)+2] == b || newData.indices[(i*3)+2] == c) score++;
137 
138                 if (score == 3) return true;
139             }
140             return false;
141         }
142 
143         bool isAnyEdgeIntersecting(vec2[3] t1, vec2[3] t2) {
144             vec2 t1p1, t1p2, t2p1, t2p2;
145             static foreach(i; 0..3) {
146                 static foreach(j; 0..3) {
147                     t1p1 = t1[i];
148                     t1p2 = t1[(i+1)%3];
149                     t2p1 = t2[j];
150                     t2p2 = t2[(j+1)%3];
151 
152                     if (areLineSegmentsIntersecting(t1p1, t1p2, t2p1, t2p2)) return true;
153                 }
154             }
155             return false;
156         }
157 
158         bool isIntersectingWithTris(vec2[3] t1) {
159             foreach(i; 0..newData.indices.length/3) {
160                 vec2[3] verts = [
161                     newData.vertices[newData.indices[(i*3)+0]],
162                     newData.vertices[newData.indices[(i*3)+0]],
163                     newData.vertices[newData.indices[(i*3)+0]]
164                 ];
165                 if (isAnyEdgeIntersecting(t1, verts)) return true;
166             }
167             return false;
168         }
169 
170         MeshVertex*[] visited;
171         void mExportVisit(MeshVertex* v) {
172             visited ~= v;
173 
174             MeshVertex* findFreeIndice() {
175                 foreach (key; indices.keys) {
176                     if (indices[key] != newData.indices[$-1] && 
177                         indices[key] != newData.indices[$-2] && 
178                         indices[key] != newData.indices[$-3] && 
179                         !visited.canFind(key)) return cast(MeshVertex*)key;
180                 }
181                 return null;
182             }
183 
184             // Second vertex
185             foreach(MeshVertex* conn; v.connections) {
186                 if (conn == v) continue;
187 
188                 // Third vertex
189                 foreach(MeshVertex* conn2; conn.connections) {
190                     if (goesBackToRoot(v, conn2)) {
191 
192                         // Skip repeat sequences
193                         if (hasIndiceSeq(indices[v], indices[conn], indices[conn2])) continue;
194                         if (isIntersectingWithTris([v.position, conn.position, conn2.position])) continue;
195                         
196 
197                         // Add new indices
198                         newData.indices ~= [
199                             indices[v],
200                             indices[conn],
201                             indices[conn2]
202                         ];
203                         break;
204                     }
205                 }
206             }
207 
208             foreach(MeshVertex* conn; v.connections) {
209                 if (!visited.canFind(conn)) mExportVisit(conn);
210             }
211         }
212 
213         // Run the export
214         foreach(ref vert; vertices) {
215             if (!visited.canFind(vert)) {
216                 mExportVisit(vert);
217             }
218         }
219 
220         // Save the data as the new data and refresh
221         data = newData;
222         reset();
223         return *newData;
224     }
225 
226     vec3[] points;
227     vec3[] lines;
228     vec3[] wlines;
229     void regen() {
230         points.length = 0;
231         
232         // Updates all point positions
233         foreach(i, vert; vertices) {
234             points ~= vec3(vert.position, 0);
235         }
236     }
237 
238     void regenConnections() {
239         import std.algorithm.searching : canFind;
240 
241         // setup
242         lines.length = 0;
243         wlines.length = 0;
244         MeshVertex*[] visited;
245         
246         // our crazy recursive func
247         void recurseLines(MeshVertex* cur) {
248             visited ~= cur;
249 
250             // First add the lines
251             foreach(conn; cur.connections) {
252 
253                 // Skip already scanned connections
254                 if (!visited.canFind(conn)) {
255                     lines ~= [vec3(cur.position, 0), vec3(conn.position, 0)];
256                 }
257             }
258             // Then scan the next unvisited point
259             foreach(conn; cur.connections) {
260 
261                 // Skip already scanned connections
262                 if (!visited.canFind(conn)) {
263                     recurseLines(conn);
264                 }
265             }
266         }
267 
268         foreach(ref vert; vertices) {
269             if (!visited.canFind(vert)) {
270                 recurseLines(vert);
271             }
272         }
273     }
274 
275 public:
276     float selectRadius = 16f;
277     MeshVertex*[] vertices;
278     bool changed;
279 
280     /**
281         Constructs a new IncMesh
282     */
283     this(ref MeshData mesh) {
284         import_(mesh);
285     }
286 
287     final
288     void import_(ref MeshData mesh) {
289         data = &mesh;
290         mImport(mesh);
291     }
292     
293     /**
294         Exports the working mesh to a MeshData object.
295     */
296     final
297     MeshData export_() {
298         return mExport();
299     }
300 
301     final
302     size_t getEdgeCount() {
303         if (lines.length == 0) {
304             regenConnections();
305         }
306         return lines.length;
307     }
308 
309     final
310     size_t getVertexCount() {
311         return vertices.length;
312     }
313 
314     /**
315         Resets mesh to prior state
316     */
317     void reset() {
318         mImport(*data);
319         refresh();
320         changed = true;
321     }
322 
323     /**
324         Clears the mesh of everything
325     */
326     void clear() {
327         vertices.length = 0;
328         refresh();
329         changed = true;
330     }
331 
332     /**
333         Refreshes graphical portion of the mesh
334     */
335     void refresh() {
336         regen();
337         regenConnections();
338     }
339 
340     /**
341         Draws the mesh
342     */
343     void drawLines(mat4 trans = mat4.identity, vec4 color = vec4(0.7, 0.7, 0.7, 1)) {
344         if (lines.length > 0) {
345             inDbgSetBuffer(lines);
346             inDbgDrawLines(color, trans);
347         }
348 
349         if (wlines.length > 0) {
350             inDbgSetBuffer(wlines);
351             inDbgDrawLines(vec4(0.7, 0.2, 0.2, 1), trans);
352         }
353     }
354 
355     void drawPoints(mat4 trans = mat4.identity) {
356         if (points.length > 0) {
357             inDbgSetBuffer(points);
358             inDbgPointsSize(10);
359             inDbgDrawPoints(vec4(0, 0, 0, 1), trans);
360             inDbgPointsSize(6);
361             inDbgDrawPoints(vec4(1, 1, 1, 1), trans);
362         }
363     }
364 
365     void drawPointSubset(MeshVertex*[] subset, vec4 color, mat4 trans = mat4.identity, float size=6) {
366         vec3[] subPoints;
367 
368         if (subset.length == 0) return;
369 
370         // Updates all point positions
371         foreach(vtx; subset) {
372             subPoints ~= vec3(vtx.position, 0);
373         }
374         inDbgSetBuffer(subPoints);
375         inDbgPointsSize(size);
376         inDbgDrawPoints(color, trans);
377     }
378 
379     void drawPoint(vec2 point, vec4 color, mat4 trans = mat4.identity, float size=6) {
380         inDbgSetBuffer([vec3(point, 0)]);
381         inDbgPointsSize(size);
382         inDbgDrawPoints(color, trans);
383     }
384 
385     void draw(mat4 trans = mat4.identity) {
386         drawLines(trans);
387         drawPoints(trans);
388     }
389 
390     bool isPointOverVertex(vec2 point) {
391         foreach(vert; vertices) {
392             if (abs(vert.position.distance(point)) < selectRadius/incViewportZoom) return true;
393         }
394         return false;
395     }
396 
397     void removeVertexAt(vec2 point) {
398         foreach(i; 0..vertices.length) {
399             if (abs(vertices[i].position.distance(point)) < selectRadius/incViewportZoom) {
400                 this.remove(vertices[i]);
401                 return;
402             }
403         }
404     }
405 
406     MeshVertex* getVertexFromPoint(vec2 point) {
407         foreach(ref vert; vertices) {
408             if (abs(vert.position.distance(point)) < selectRadius/incViewportZoom) return vert;
409         }
410         return null;
411     }
412 
413     void remove(MeshVertex* vert) {
414         import std.algorithm.searching : countUntil;
415         import std.algorithm.mutation : remove;
416         
417         auto idx = vertices.countUntil(vert);
418         if (idx != -1) {
419             disconnectAll(vert);
420             vertices = vertices.remove(idx);
421         }
422         changed = true;
423     }
424 
425     vec2[] getOffsets() {
426         vec2[] offsets;
427 
428         offsets.length = vertices.length;
429         foreach(idx, vertex; vertices) {
430             offsets[idx] = vertex.position - data.vertices[idx];
431         }
432         return offsets;
433     }
434 
435     void setBackOffsets(vec2[] offsets) {
436         foreach(idx, vertex; offsets) {
437             vertices[idx].position = offsets[idx] + data.vertices[idx];
438         }
439         regen();
440         regenConnections();
441         changed = true;
442     }
443 
444     void applyOffsets(vec2[] offsets) {
445         foreach(idx, vertex; vertices) {
446             vertex.position += offsets[idx];
447         }
448         regen();
449         regenConnections();
450         changed = true;
451     }
452 
453     /**
454         Flips all vertices horizontally
455     */
456     void flipHorz() {
457         foreach(ref vert; vertices) {
458             vert.position.x *= -1;
459         }
460         refresh();
461         changed = true;
462     }
463 
464     /**
465         Flips all vertices vertically
466     */
467     void flipVert() {
468         foreach(ref vert; vertices) {
469             vert.position.y *= -1;
470         }
471         refresh();
472         changed = true;
473     }
474 
475     void getBounds(out vec2 min, out vec2 max) {
476         min = vec2(float.infinity, float.infinity);
477         max = vec2(-float.infinity, -float.infinity);
478 
479         foreach(idx, vertex; vertices) {
480             if (min.x > vertex.position.x) min.x = vertex.position.x;
481             if (min.y > vertex.position.y) min.y = vertex.position.y;
482             if (max.x < vertex.position.x) max.x = vertex.position.x;
483             if (max.y < vertex.position.y) max.y = vertex.position.y;
484         }
485     }
486 
487     MeshVertex*[] getInRect(vec2 min, vec2 max) {
488         if (min.x > max.x) swap(min.x, max.x);
489         if (min.y > max.y) swap(min.y, max.y);
490 
491         MeshVertex*[] matching;
492         foreach(idx, vertex; vertices) {
493             if (min.x > vertex.position.x) continue;
494             if (min.y > vertex.position.y) continue;
495             if (max.x < vertex.position.x) continue;
496             if (max.y < vertex.position.y) continue;
497             matching ~= vertex;
498         }
499 
500         return matching;
501     }
502 
503     IncMesh autoTriangulate() {
504         import std.stdio;
505         debug(delaunay) writeln("==== autoTriangulate ====");
506         if (vertices.length < 3) return new IncMesh(*data);
507 
508         IncMesh newMesh = new IncMesh(*data);
509         newMesh.changed = true;
510 
511         vec2 min, max;
512         getBounds(min, max);
513 
514         // Pad (fudge factors are a hack to work around contains() instability, TODO: fix)
515         vec2 range = max - min;
516         min -= range + vec2(range.y, range.x) + vec2(0.123, 0.125);
517         max += range + vec2(range.y, range.x) + vec2(0.127, 0.129);
518 
519         vec3u[] tris;
520         vec3u[] tri2edge;
521         vec2u[] edge2tri;
522 
523         vec2[] vtx;
524         vtx.length = 4;
525 
526         // Define initial state (two tris)
527         vtx[0] = vec2(min.x, min.y);
528         vtx[1] = vec2(min.x, max.y);
529         vtx[2] = vec2(max.x, max.y);
530         vtx[3] = vec2(max.x, min.y);
531         tris ~= vec3u(0, 1, 3);
532         tris ~= vec3u(1, 2, 3);
533         tri2edge ~= vec3u(0, 1, 2);
534         tri2edge ~= vec3u(3, 4, 1);
535         edge2tri ~= vec2u(0, 0);
536         edge2tri ~= vec2u(0, 1);
537         edge2tri ~= vec2u(0, 0);
538         edge2tri ~= vec2u(1, 1);
539         edge2tri ~= vec2u(1, 1);
540 
541         // Helpers
542         float sign(vec2 p1, vec2 p2, vec2 p3) {
543             return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
544         }
545 
546         bool contains(vec3u tri, vec2 pt) {
547             float d1, d2, d3;
548             bool hasNeg, hasPos;
549 
550             d1 = sign(pt, vtx[tri.x], vtx[tri.y]);
551             d2 = sign(pt, vtx[tri.y], vtx[tri.z]);
552             d3 = sign(pt, vtx[tri.z], vtx[tri.x]);
553 
554             hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
555             hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);
556 
557             return !(hasNeg && hasPos);
558         }
559 
560         void replaceE2T(ref vec2u e2t, uint from, uint to) {
561             if (e2t.x == from) {
562                 e2t.x = to;
563                 if (e2t.y == from) e2t.y = to;
564             } else if (e2t.y == from) {
565                 e2t.y = to;
566             } else assert(false, "edge mismatch");
567         }
568 
569         void orientTri(uint tri, uint edge) {
570             vec3u t2e = tri2edge[tri];
571             vec3u pt = tris[tri];
572             if (t2e.x == edge) {
573                 return;
574             } else if (t2e.y == edge) {
575                 tri2edge[tri] = vec3u(t2e.y, t2e.z, t2e.x);
576                 tris[tri] = vec3u(pt.y, pt.z, pt.x);
577             } else if (t2e.z == edge) {
578                 tri2edge[tri] = vec3u(t2e.z, t2e.x, t2e.y);
579                 tris[tri] = vec3u(pt.z, pt.x, pt.y);
580             } else {
581                 assert(false, "triangle does not own edge");
582             }
583         }
584 
585         void splitEdges() {
586             uint edgeCnt = cast(uint)edge2tri.length;
587             for(uint e = 0; e < edgeCnt; e++) {
588                 vec2u tr = edge2tri[e];
589 
590                 if (tr.x != tr.y) continue; // Only handle outer edges
591 
592                 orientTri(tr.x, e);
593 
594                 uint t1 = tr.x;
595                 uint t2 = cast(uint)tris.length;
596                 uint l = tris[t1].x;
597                 uint r = tris[t1].y;
598                 uint z = tris[t1].z;
599                 uint m = cast(uint)vtx.length;
600                 vtx ~= (vtx[l] + vtx[r]) / 2;
601 
602                 uint xe = cast(uint)edge2tri.length;
603                 uint me = xe + 1;
604                 uint re = tri2edge[t1].y;
605 
606                 tris[t1].y = m;
607                 tri2edge[t1].y = me;
608                 tris ~= vec3u(m, r, z);
609                 tri2edge ~= vec3u(xe, re, me);
610                 edge2tri ~= vec2u(t2, t2);
611                 edge2tri ~= vec2u(t1, t2);
612                 replaceE2T(edge2tri[re], t1, t2);
613             }
614         }
615 
616         bool inCircle(vec2 pa, vec2 pb, vec2 pc, vec2 pd) {
617             debug(delaunay) writefln("in_circle(%s, %s, %s, %s)", pa, pb, pc, pd);
618             float adx = pa.x - pd.x;
619             float ady = pa.y - pd.y;
620             float bdx = pb.x - pd.x;
621             float bdy = pb.y - pd.y;
622 
623             float adxbdy = adx * bdy;
624             float bdxady = bdx * ady;
625             float oabd = adxbdy - bdxady;
626 
627             if (oabd <= 0) return false;
628 
629             float cdx = pc.x - pd.x;
630             float cdy = pc.y - pd.y;
631 
632             float cdxady = cdx * ady;
633             float adxcdy = adx * cdy;
634             float ocad = cdxady - adxcdy;
635 
636             if (ocad <= 0) return false;
637 
638             float bdxcdy = bdx * cdy;
639             float cdxbdy = cdx * bdy;
640 
641             float alift = adx * adx + ady * ady;
642             float blift = bdx * bdx + bdy * bdy;
643             float clift = cdx * cdx + cdy * cdy;
644 
645             float det = alift * (bdxcdy - cdxbdy) + blift * ocad + clift * oabd;
646 
647             debug(delaunay) writefln("det=%s", det);
648             return det > 0;
649         }
650 
651         splitEdges();
652         splitEdges();
653         splitEdges();
654         splitEdges();
655 
656         uint dropVertices = cast(uint)vtx.length;
657 
658         // Add vertices, preserving Delaunay condition
659         foreach(orig_i, vertex; vertices) {
660             uint i = cast(uint)orig_i + dropVertices;
661             debug(delaunay) writefln("Add @%d: %s", i, vertex.position);
662             vtx ~= vertex.position;
663             bool found = false;
664 
665             uint[] affectedEdges;
666 
667             foreach(a_, tri; tris) {
668                 if (!contains(tri, vertex.position)) continue;
669 
670                 /*
671                            x
672                   Y-----------------X
673                    \`,            '/    XYZ = original vertices
674                     \ `q   a   p' /     a = original triangle
675                      \  `,    '  /      bc = new triangles
676                       \   `i'   /       xyz = original edges
677                      y \ b | c / z      pqr = new edges
678                         \  r  /
679                          \ | /
680                           \|/
681                            Z
682                 */
683 
684                 // Subdivide containing triangle
685                 // New triangles
686                 uint a = cast(uint)a_;
687                 uint b = cast(uint)tris.length;
688                 uint c = b + 1;
689                 tris[a] = vec3u(tri.x, tri.y, i);
690                 tris ~= vec3u(tri.y, tri.z, i); // b
691                 tris ~= vec3u(tri.z, tri.x, i); // c
692 
693                 debug(delaunay) writefln("*** Tri %d: %s Edges: %s", a, tris[a], tri2edge[a]);
694 
695                 // New inner edges
696                 uint p = cast(uint)edge2tri.length;
697                 uint q = p + 1;
698                 uint r = q + 1;
699 
700                 // Get outer edges
701                 uint x = tri2edge[a].x;
702                 uint y = tri2edge[a].y;
703                 uint z = tri2edge[a].z;
704 
705                 // Update triangle to edge mappings
706                 tri2edge[a] = vec3u(x, q, p);
707                 tri2edge ~= vec3u(y, r, q);
708                 tri2edge ~= vec3u(z, p, r);
709 
710                 debug(delaunay) writefln("  * Tri a %d: %s Edges: %s", a, tris[a], tri2edge[a]);
711                 debug(delaunay) writefln("  + Tri b %d: %s Edges: %s", b, tris[b], tri2edge[b]);
712                 debug(delaunay) writefln("  + Tri c %d: %s Edges: %s", c, tris[c], tri2edge[c]);
713 
714                 // Save new edges
715                 edge2tri ~= vec2u(c, a);
716                 edge2tri ~= vec2u(a, b);
717                 edge2tri ~= vec2u(b, c);
718                 debug(delaunay) writefln("  + Edg p %d: Tris %s", p, edge2tri[p]);
719                 debug(delaunay) writefln("  + Edg q %d: Tris %s", q, edge2tri[q]);
720                 debug(delaunay) writefln("  + Edg r %d: Tris %s", r, edge2tri[r]);
721 
722                 // Update two outer edges
723                 debug(delaunay) writefln("  - Edg y %d: Tris %s", y, edge2tri[y]);
724                 replaceE2T(edge2tri[y], a, b);
725                 debug(delaunay) writefln("  + Edg y %d: Tris %s", y, edge2tri[y]);
726                 debug(delaunay) writefln("  - Edg z %d: Tris %s", y, edge2tri[z]);
727                 replaceE2T(edge2tri[z], a, c);
728                 debug(delaunay) writefln("  + Edg z %d: Tris %s", z, edge2tri[z]);
729 
730                 // Keep track of what edges we have to look at
731                 affectedEdges ~= [x, y, z, p, q, r];
732 
733                 found = true;
734                 break;
735             }
736             if (!found) {
737                 debug(delaunay) writeln("FAILED!");
738                 break;
739             }
740 
741             bool[] checked;
742             checked.length = edge2tri.length;
743 
744             for (uint j = 0; j < affectedEdges.length; j++) {
745                 uint e = affectedEdges[j];
746                 vec2u t = edge2tri[e];
747 
748                 debug(delaunay) writefln(" ## Edge %d: T %s: %s %s", e, t, tris[t.x], tris[t.y]);
749 
750                 if (t.x == t.y) {
751                     debug(delaunay) writefln("  + Outer edge");
752                     continue; // Outer edge
753                 }
754 
755                 // Orient triangles so 1st edge is shared
756                 orientTri(t.x, e);
757                 orientTri(t.y, e);
758 
759                 assert(tris[t.x].x == tris[t.y].y, "triangles do not share edge");
760                 assert(tris[t.y].x == tris[t.x].y, "triangles do not share edge");
761 
762                 uint a = tris[t.x].x;
763                 uint c = tris[t.x].y;
764                 uint d = tris[t.x].z;
765                 uint b = tris[t.y].z;
766 
767                 // Delaunay check
768                 if (!inCircle(vtx[b], vtx[a], vtx[c], vtx[d])) {
769                     // We're good
770                     debug(delaunay) writefln("  + Meets condition");
771                     continue;
772                 }
773 
774                 debug(delaunay) writefln("  - Flip!");
775 
776                 // Flip edge
777                 /*
778                    c          c
779                   /|\      r / \ q
780                  / | \      / x \
781                 d x|y b -> d-----b
782                  \ | /      \ y /
783                   \|/      s \ / p
784                    a          a
785                 */
786                 uint r = tri2edge[t.x].y;
787                 uint s = tri2edge[t.x].z;
788                 uint p = tri2edge[t.y].y;
789                 uint q = tri2edge[t.y].z;
790 
791                 tris[t.x] = vec3u(d, b, c);
792                 tris[t.t] = vec3u(b, d, a);
793                 tri2edge[t.x] = vec3u(e, q, r);
794                 tri2edge[t.y] = vec3u(e, s, p);
795                 replaceE2T(edge2tri[q], t.y, t.x);
796                 replaceE2T(edge2tri[s], t.x, t.y);
797 
798                 // Mark it as checked
799                 checked[e] = true;
800 
801                 // Check the neighboring edges
802                 if (!checked[p]) affectedEdges ~= p;
803                 if (!checked[q]) affectedEdges ~= q;
804                 if (!checked[r]) affectedEdges ~= r;
805                 if (!checked[s]) affectedEdges ~= s;
806             }
807         }
808 
809         // Copy vertices
810         newMesh.vertices.length = 0;
811         foreach(v; vtx) {
812             newMesh.vertices ~= new MeshVertex(v, []);
813         }
814 
815         // Extract tris into connections
816         foreach(tri; tris) {
817             connect(newMesh.vertices[tri.x], newMesh.vertices[tri.y]);
818             connect(newMesh.vertices[tri.y], newMesh.vertices[tri.z]);
819             connect(newMesh.vertices[tri.z], newMesh.vertices[tri.x]);
820         }
821 
822         // Get rid of corners
823         foreach(i; 0..dropVertices)
824             newMesh.remove(newMesh.vertices[0]);
825 
826         newMesh.refresh();
827         debug(delaunay) writeln("==== autoTriangulate done ====");
828         return newMesh;
829     }
830 }