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+data.origin, []); 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 drawPoint(vec2 point, vec4 color, mat4 trans = mat4.identity, float size=6) { 364 inDbgSetBuffer([vec3(point, 0)]); 365 inDbgPointsSize(size); 366 inDbgDrawPoints(color, trans); 367 } 368 369 void draw(mat4 trans = mat4.identity) { 370 drawLines(trans); 371 drawPoints(trans); 372 } 373 374 bool isPointOverVertex(vec2 point) { 375 foreach(vert; vertices) { 376 if (abs(vert.position.distance(point)) < selectRadius/incViewportZoom) return true; 377 } 378 return false; 379 } 380 381 void removeVertexAt(vec2 point) { 382 foreach(i; 0..vertices.length) { 383 if (abs(vertices[i].position.distance(point)) < selectRadius/incViewportZoom) { 384 this.remove(vertices[i]); 385 return; 386 } 387 } 388 } 389 390 MeshVertex* getVertexFromPoint(vec2 point) { 391 foreach(ref vert; vertices) { 392 if (abs(vert.position.distance(point)) < selectRadius/incViewportZoom) return vert; 393 } 394 return null; 395 } 396 397 void remove(MeshVertex* vert) { 398 import std.algorithm.searching : countUntil; 399 import std.algorithm.mutation : remove; 400 401 auto idx = vertices.countUntil(vert); 402 if (idx != -1) { 403 disconnectAll(vert); 404 vertices = vertices.remove(idx); 405 } 406 changed = true; 407 } 408 409 vec2[] getOffsets() { 410 vec2[] offsets; 411 412 offsets.length = vertices.length; 413 foreach(idx, vertex; vertices) { 414 offsets[idx] = vertex.position - data.vertices[idx]; 415 } 416 return offsets; 417 } 418 419 void applyOffsets(vec2[] offsets) { 420 foreach(idx, vertex; vertices) { 421 vertex.position += offsets[idx]; 422 } 423 regen(); 424 regenConnections(); 425 changed = true; 426 } 427 428 /** 429 Flips all vertices horizontally 430 */ 431 void flipHorz() { 432 foreach(ref vert; vertices) { 433 vert.position.x *= -1; 434 } 435 refresh(); 436 changed = true; 437 } 438 439 /** 440 Flips all vertices vertically 441 */ 442 void flipVert() { 443 foreach(ref vert; vertices) { 444 vert.position.y *= -1; 445 } 446 refresh(); 447 changed = true; 448 } 449 450 void getBounds(out vec2 min, out vec2 max) { 451 min = vec2(float.infinity, float.infinity); 452 max = vec2(-float.infinity, -float.infinity); 453 454 foreach(idx, vertex; vertices) { 455 if (min.x > vertex.position.x) min.x = vertex.position.x; 456 if (min.y > vertex.position.y) min.y = vertex.position.y; 457 if (max.x < vertex.position.x) max.x = vertex.position.x; 458 if (max.y < vertex.position.y) max.y = vertex.position.y; 459 } 460 } 461 462 MeshVertex*[] getInRect(vec2 min, vec2 max) { 463 if (min.x > max.x) swap(min.x, max.x); 464 if (min.y > max.y) swap(min.y, max.y); 465 466 MeshVertex*[] matching; 467 foreach(idx, vertex; vertices) { 468 if (min.x > vertex.position.x) continue; 469 if (min.y > vertex.position.y) continue; 470 if (max.x < vertex.position.x) continue; 471 if (max.y < vertex.position.y) continue; 472 matching ~= vertex; 473 } 474 475 return matching; 476 } 477 478 IncMesh autoTriangulate() { 479 import std.stdio; 480 debug(delaunay) writeln("==== autoTriangulate ===="); 481 if (vertices.length < 3) return new IncMesh(*data); 482 483 IncMesh newMesh = new IncMesh(*data); 484 newMesh.changed = true; 485 486 vec2 min, max; 487 getBounds(min, max); 488 489 // Pad (fudge factors are a hack to work around contains() instability, TODO: fix) 490 vec2 range = max - min; 491 min -= range + vec2(range.y, range.x) + vec2(0.123, 0.125); 492 max += range + vec2(range.y, range.x) + vec2(0.127, 0.129); 493 494 vec3u[] tris; 495 vec3u[] tri2edge; 496 vec2u[] edge2tri; 497 498 vec2[] vtx; 499 vtx.length = 4; 500 501 // Define initial state (two tris) 502 vtx[0] = vec2(min.x, min.y); 503 vtx[1] = vec2(min.x, max.y); 504 vtx[2] = vec2(max.x, max.y); 505 vtx[3] = vec2(max.x, min.y); 506 tris ~= vec3u(0, 1, 3); 507 tris ~= vec3u(1, 2, 3); 508 tri2edge ~= vec3u(0, 1, 2); 509 tri2edge ~= vec3u(3, 4, 1); 510 edge2tri ~= vec2u(0, 0); 511 edge2tri ~= vec2u(0, 1); 512 edge2tri ~= vec2u(0, 0); 513 edge2tri ~= vec2u(1, 1); 514 edge2tri ~= vec2u(1, 1); 515 516 // Helpers 517 float sign(vec2 p1, vec2 p2, vec2 p3) { 518 return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); 519 } 520 521 bool contains(vec3u tri, vec2 pt) { 522 float d1, d2, d3; 523 bool hasNeg, hasPos; 524 525 d1 = sign(pt, vtx[tri.x], vtx[tri.y]); 526 d2 = sign(pt, vtx[tri.y], vtx[tri.z]); 527 d3 = sign(pt, vtx[tri.z], vtx[tri.x]); 528 529 hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0); 530 hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0); 531 532 return !(hasNeg && hasPos); 533 } 534 535 void replaceE2T(ref vec2u e2t, uint from, uint to) { 536 if (e2t.x == from) { 537 e2t.x = to; 538 if (e2t.y == from) e2t.y = to; 539 } else if (e2t.y == from) { 540 e2t.y = to; 541 } else assert(false, "edge mismatch"); 542 } 543 544 void orientTri(uint tri, uint edge) { 545 vec3u t2e = tri2edge[tri]; 546 vec3u pt = tris[tri]; 547 if (t2e.x == edge) { 548 return; 549 } else if (t2e.y == edge) { 550 tri2edge[tri] = vec3u(t2e.y, t2e.z, t2e.x); 551 tris[tri] = vec3u(pt.y, pt.z, pt.x); 552 } else if (t2e.z == edge) { 553 tri2edge[tri] = vec3u(t2e.z, t2e.x, t2e.y); 554 tris[tri] = vec3u(pt.z, pt.x, pt.y); 555 } else { 556 assert(false, "triangle does not own edge"); 557 } 558 } 559 560 void splitEdges() { 561 uint edgeCnt = cast(uint)edge2tri.length; 562 for(uint e = 0; e < edgeCnt; e++) { 563 vec2u tr = edge2tri[e]; 564 565 if (tr.x != tr.y) continue; // Only handle outer edges 566 567 orientTri(tr.x, e); 568 569 uint t1 = tr.x; 570 uint t2 = cast(uint)tris.length; 571 uint l = tris[t1].x; 572 uint r = tris[t1].y; 573 uint z = tris[t1].z; 574 uint m = cast(uint)vtx.length; 575 vtx ~= (vtx[l] + vtx[r]) / 2; 576 577 uint xe = cast(uint)edge2tri.length; 578 uint me = xe + 1; 579 uint re = tri2edge[t1].y; 580 581 tris[t1].y = m; 582 tri2edge[t1].y = me; 583 tris ~= vec3u(m, r, z); 584 tri2edge ~= vec3u(xe, re, me); 585 edge2tri ~= vec2u(t2, t2); 586 edge2tri ~= vec2u(t1, t2); 587 replaceE2T(edge2tri[re], t1, t2); 588 } 589 } 590 591 bool inCircle(vec2 pa, vec2 pb, vec2 pc, vec2 pd) { 592 debug(delaunay) writefln("in_circle(%s, %s, %s, %s)", pa, pb, pc, pd); 593 float adx = pa.x - pd.x; 594 float ady = pa.y - pd.y; 595 float bdx = pb.x - pd.x; 596 float bdy = pb.y - pd.y; 597 598 float adxbdy = adx * bdy; 599 float bdxady = bdx * ady; 600 float oabd = adxbdy - bdxady; 601 602 if (oabd <= 0) return false; 603 604 float cdx = pc.x - pd.x; 605 float cdy = pc.y - pd.y; 606 607 float cdxady = cdx * ady; 608 float adxcdy = adx * cdy; 609 float ocad = cdxady - adxcdy; 610 611 if (ocad <= 0) return false; 612 613 float bdxcdy = bdx * cdy; 614 float cdxbdy = cdx * bdy; 615 616 float alift = adx * adx + ady * ady; 617 float blift = bdx * bdx + bdy * bdy; 618 float clift = cdx * cdx + cdy * cdy; 619 620 float det = alift * (bdxcdy - cdxbdy) + blift * ocad + clift * oabd; 621 622 debug(delaunay) writefln("det=%s", det); 623 return det > 0; 624 } 625 626 splitEdges(); 627 splitEdges(); 628 splitEdges(); 629 splitEdges(); 630 631 uint dropVertices = cast(uint)vtx.length; 632 633 // Add vertices, preserving Delaunay condition 634 foreach(orig_i, vertex; vertices) { 635 uint i = cast(uint)orig_i + dropVertices; 636 debug(delaunay) writefln("Add @%d: %s", i, vertex.position); 637 vtx ~= vertex.position; 638 bool found = false; 639 640 uint[] affectedEdges; 641 642 foreach(a_, tri; tris) { 643 if (!contains(tri, vertex.position)) continue; 644 645 /* 646 x 647 Y-----------------X 648 \`, '/ XYZ = original vertices 649 \ `q a p' / a = original triangle 650 \ `, ' / bc = new triangles 651 \ `i' / xyz = original edges 652 y \ b | c / z pqr = new edges 653 \ r / 654 \ | / 655 \|/ 656 Z 657 */ 658 659 // Subdivide containing triangle 660 // New triangles 661 uint a = cast(uint)a_; 662 uint b = cast(uint)tris.length; 663 uint c = b + 1; 664 tris[a] = vec3u(tri.x, tri.y, i); 665 tris ~= vec3u(tri.y, tri.z, i); // b 666 tris ~= vec3u(tri.z, tri.x, i); // c 667 668 debug(delaunay) writefln("*** Tri %d: %s Edges: %s", a, tris[a], tri2edge[a]); 669 670 // New inner edges 671 uint p = cast(uint)edge2tri.length; 672 uint q = p + 1; 673 uint r = q + 1; 674 675 // Get outer edges 676 uint x = tri2edge[a].x; 677 uint y = tri2edge[a].y; 678 uint z = tri2edge[a].z; 679 680 // Update triangle to edge mappings 681 tri2edge[a] = vec3u(x, q, p); 682 tri2edge ~= vec3u(y, r, q); 683 tri2edge ~= vec3u(z, p, r); 684 685 debug(delaunay) writefln(" * Tri a %d: %s Edges: %s", a, tris[a], tri2edge[a]); 686 debug(delaunay) writefln(" + Tri b %d: %s Edges: %s", b, tris[b], tri2edge[b]); 687 debug(delaunay) writefln(" + Tri c %d: %s Edges: %s", c, tris[c], tri2edge[c]); 688 689 // Save new edges 690 edge2tri ~= vec2u(c, a); 691 edge2tri ~= vec2u(a, b); 692 edge2tri ~= vec2u(b, c); 693 debug(delaunay) writefln(" + Edg p %d: Tris %s", p, edge2tri[p]); 694 debug(delaunay) writefln(" + Edg q %d: Tris %s", q, edge2tri[q]); 695 debug(delaunay) writefln(" + Edg r %d: Tris %s", r, edge2tri[r]); 696 697 // Update two outer edges 698 debug(delaunay) writefln(" - Edg y %d: Tris %s", y, edge2tri[y]); 699 replaceE2T(edge2tri[y], a, b); 700 debug(delaunay) writefln(" + Edg y %d: Tris %s", y, edge2tri[y]); 701 debug(delaunay) writefln(" - Edg z %d: Tris %s", y, edge2tri[z]); 702 replaceE2T(edge2tri[z], a, c); 703 debug(delaunay) writefln(" + Edg z %d: Tris %s", z, edge2tri[z]); 704 705 // Keep track of what edges we have to look at 706 affectedEdges ~= [x, y, z, p, q, r]; 707 708 found = true; 709 break; 710 } 711 if (!found) { 712 debug(delaunay) writeln("FAILED!"); 713 break; 714 } 715 716 bool[] checked; 717 checked.length = edge2tri.length; 718 719 for (uint j = 0; j < affectedEdges.length; j++) { 720 uint e = affectedEdges[j]; 721 vec2u t = edge2tri[e]; 722 723 debug(delaunay) writefln(" ## Edge %d: T %s: %s %s", e, t, tris[t.x], tris[t.y]); 724 725 if (t.x == t.y) { 726 debug(delaunay) writefln(" + Outer edge"); 727 continue; // Outer edge 728 } 729 730 // Orient triangles so 1st edge is shared 731 orientTri(t.x, e); 732 orientTri(t.y, e); 733 734 assert(tris[t.x].x == tris[t.y].y, "triangles do not share edge"); 735 assert(tris[t.y].x == tris[t.x].y, "triangles do not share edge"); 736 737 uint a = tris[t.x].x; 738 uint c = tris[t.x].y; 739 uint d = tris[t.x].z; 740 uint b = tris[t.y].z; 741 742 // Delaunay check 743 if (!inCircle(vtx[b], vtx[a], vtx[c], vtx[d])) { 744 // We're good 745 debug(delaunay) writefln(" + Meets condition"); 746 continue; 747 } 748 749 debug(delaunay) writefln(" - Flip!"); 750 751 // Flip edge 752 /* 753 c c 754 /|\ r / \ q 755 / | \ / x \ 756 d x|y b -> d-----b 757 \ | / \ y / 758 \|/ s \ / p 759 a a 760 */ 761 uint r = tri2edge[t.x].y; 762 uint s = tri2edge[t.x].z; 763 uint p = tri2edge[t.y].y; 764 uint q = tri2edge[t.y].z; 765 766 tris[t.x] = vec3u(d, b, c); 767 tris[t.t] = vec3u(b, d, a); 768 tri2edge[t.x] = vec3u(e, q, r); 769 tri2edge[t.y] = vec3u(e, s, p); 770 replaceE2T(edge2tri[q], t.y, t.x); 771 replaceE2T(edge2tri[s], t.x, t.y); 772 773 // Mark it as checked 774 checked[e] = true; 775 776 // Check the neighboring edges 777 if (!checked[p]) affectedEdges ~= p; 778 if (!checked[q]) affectedEdges ~= q; 779 if (!checked[r]) affectedEdges ~= r; 780 if (!checked[s]) affectedEdges ~= s; 781 } 782 } 783 784 // Copy vertices 785 newMesh.vertices.length = 0; 786 foreach(v; vtx) { 787 newMesh.vertices ~= new MeshVertex(v, []); 788 } 789 790 // Extract tris into connections 791 foreach(tri; tris) { 792 connect(newMesh.vertices[tri.x], newMesh.vertices[tri.y]); 793 connect(newMesh.vertices[tri.y], newMesh.vertices[tri.z]); 794 connect(newMesh.vertices[tri.z], newMesh.vertices[tri.x]); 795 } 796 797 // Get rid of corners 798 foreach(i; 0..dropVertices) 799 newMesh.remove(newMesh.vertices[0]); 800 801 newMesh.refresh(); 802 debug(delaunay) writeln("==== autoTriangulate done ===="); 803 return newMesh; 804 } 805 }