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 }