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