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 module creator.viewport.common.mesheditor; 10 import i18n; 11 import creator.viewport; 12 import creator.viewport.common; 13 import creator.viewport.common.mesh; 14 import creator.viewport.common.spline; 15 import creator.core.input; 16 import creator.widgets; 17 import creator; 18 import inochi2d; 19 import inochi2d.core.dbg; 20 import bindbc.opengl; 21 import bindbc.imgui; 22 import std.algorithm.mutation; 23 import std.algorithm.searching; 24 25 enum VertexToolMode { 26 Points, 27 Connect, 28 PathDeform, 29 } 30 31 class IncMeshEditor { 32 private: 33 bool deformOnly = false; 34 bool vertexMapDirty = false; 35 36 Drawable target; 37 VertexToolMode toolMode = VertexToolMode.Points; 38 MeshVertex*[] selected; 39 MeshVertex*[] mirrorSelected; 40 MeshVertex*[] newSelected; 41 42 vec2 lastMousePos; 43 vec2 mousePos; 44 45 bool isDragging = false; 46 bool isSelecting = false; 47 bool mutateSelection = false; 48 bool invertSelection = false; 49 MeshVertex* maybeSelectOne; 50 MeshVertex* vtxAtMouse; 51 vec2 selectOrigin; 52 IncMesh previewMesh; 53 54 bool deforming = false; 55 CatmullSpline path; 56 CatmullSpline targetPath; 57 uint pathDragTarget; 58 59 bool isSelected(MeshVertex* vert) { 60 import std.algorithm.searching : canFind; 61 return selected.canFind(vert); 62 } 63 64 void toggleSelect(MeshVertex* vert) { 65 import std.algorithm.searching : countUntil; 66 import std.algorithm.mutation : remove; 67 auto idx = selected.countUntil(vert); 68 if (isSelected(vert)) { 69 selected = selected.remove(idx); 70 } else { 71 selected ~= vert; 72 } 73 updateMirrorSelected(); 74 } 75 76 MeshVertex* selectOne(MeshVertex* vert) { 77 if (selected.length > 0) { 78 auto lastSel = selected[$-1]; 79 80 selected = [vert]; 81 return lastSel; 82 } 83 84 selected = [vert]; 85 updateMirrorSelected(); 86 return null; 87 } 88 89 void deselectAll() { 90 selected.length = 0; 91 updateMirrorSelected(); 92 } 93 94 vec2 mirrorH(vec2 point) { 95 return 2 * vec2(mirrorOrigin.x, 0) + vec2(-point.x, point.y); 96 } 97 98 vec2 mirrorV(vec2 point) { 99 return 2 * vec2(0, mirrorOrigin.x) + vec2(point.x, -point.y); 100 } 101 102 vec2 mirrorHV(vec2 point) { 103 return 2 * mirrorOrigin - point; 104 } 105 106 vec2 mirror(uint axis, vec2 point) { 107 switch (axis) { 108 case 0: return point; 109 case 1: return mirrorH(point); 110 case 2: return mirrorV(point); 111 case 3: return mirrorHV(point); 112 default: assert(false, "bad axis"); 113 } 114 } 115 116 vec2 mirrorDelta(uint axis, vec2 point) { 117 switch (axis) { 118 case 0: return point; 119 case 1: return vec2(-point.x, point.y); 120 case 2: return vec2(point.x, -point.y); 121 case 3: return vec2(-point.x, -point.y); 122 default: assert(false, "bad axis"); 123 } 124 } 125 126 MeshVertex *mirrorVertex(uint axis, MeshVertex *vtx) { 127 if (axis == 0) return vtx; 128 MeshVertex *v = mesh.getVertexFromPoint(mirror(axis, vtx.position)); 129 if (v is vtx) return null; 130 return v; 131 } 132 133 void foreachMirror(void delegate(uint axis) func) { 134 if (mirrorHoriz) func(1); 135 if (mirrorVert) func(2); 136 if (mirrorHoriz && mirrorVert) func(3); 137 func(0); 138 } 139 140 void updateMirrorSelected() { 141 mirrorSelected.length = 0; 142 if (!mirrorHoriz && !mirrorVert) return; 143 144 // Avoid duplicate selections... 145 MeshVertex*[] tmpSelected; 146 foreach(v; selected) { 147 if (mirrorSelected.canFind(v)) continue; 148 tmpSelected ~= v; 149 150 foreachMirror((uint axis) { 151 MeshVertex *v2 = mirrorVertex(axis, v); 152 if (v2 is null) return; 153 if (axis != 0) { 154 if (!tmpSelected.canFind(v2) && !mirrorSelected.canFind(v2)) 155 mirrorSelected ~= v2; 156 } 157 }); 158 } 159 160 selected = tmpSelected; 161 } 162 163 164 public: 165 IncMesh mesh; 166 bool previewTriangulate = false; 167 bool mirrorHoriz = false; 168 bool mirrorVert = false; 169 vec2 mirrorOrigin = vec2(0, 0); 170 171 this(bool deformOnly) { 172 this.deformOnly = deformOnly; 173 } 174 175 Drawable getTarget() { 176 return target; 177 } 178 179 void setTarget(Drawable target) { 180 this.target = target; 181 mesh = new IncMesh(target.getMesh()); 182 refreshMesh(); 183 } 184 185 ref IncMesh getMesh() { 186 return mesh; 187 } 188 189 VertexToolMode getToolMode() { 190 return toolMode; 191 } 192 193 void setToolMode(VertexToolMode toolMode) { 194 assert(!deformOnly || toolMode != VertexToolMode.Connect); 195 this.toolMode = toolMode; 196 isDragging = false; 197 isSelecting = false; 198 pathDragTarget = -1; 199 deselectAll(); 200 } 201 202 bool previewingTriangulation() { 203 return previewTriangulate && toolMode == VertexToolMode.Points; 204 } 205 206 void resetMesh() { 207 mesh.reset(); 208 } 209 210 void refreshMesh() { 211 mesh.refresh(); 212 if (previewingTriangulation()) { 213 previewMesh = mesh.autoTriangulate(); 214 } else { 215 previewMesh = null; 216 } 217 updateMirrorSelected(); 218 } 219 220 void importMesh(MeshData data) { 221 mesh.import_(data); 222 mesh.refresh(); 223 } 224 225 void applyOffsets(vec2[] offsets) { 226 assert(deformOnly); 227 228 mesh.applyOffsets(offsets); 229 } 230 231 vec2[] getOffsets() { 232 assert(deformOnly); 233 234 return mesh.getOffsets(); 235 } 236 237 void applyToTarget() { 238 // Export mesh 239 MeshData data = mesh.export_(); 240 data.fixWinding(); 241 242 // Fix UVs 243 foreach(i; 0..data.uvs.length) { 244 if (Part part = cast(Part)target) { 245 246 // Texture 0 is always albedo texture 247 auto tex = part.textures[0]; 248 249 // By dividing by width and height we should get the values in UV coordinate space. 250 data.uvs[i].x /= cast(float)tex.width; 251 data.uvs[i].y /= cast(float)tex.height; 252 data.uvs[i] += vec2(0.5, 0.5); 253 } 254 } 255 256 if (data.vertices.length != target.vertices.length) 257 vertexMapDirty = true; 258 259 if (vertexMapDirty) { 260 // Remove incompatible Deforms 261 262 foreach (param; incActivePuppet().parameters) { 263 ParameterBinding binding = param.getBinding(target, "deform"); 264 if (binding) param.removeBinding(binding); 265 } 266 vertexMapDirty = false; 267 } 268 269 // Apply the model 270 target.rebuffer(data); 271 } 272 273 void applyPreview() { 274 mesh = previewMesh; 275 previewMesh = null; 276 previewTriangulate = false; 277 } 278 279 bool update(ImGuiIO* io, Camera camera) { 280 bool changed = false; 281 282 lastMousePos = mousePos; 283 284 mousePos = incInputGetMousePosition(); 285 if (deformOnly) { 286 vec4 pIn = vec4(-mousePos.x, -mousePos.y, 0, 1); 287 mat4 tr = target.transform.matrix().inverse(); 288 vec4 pOut = tr * pIn; 289 mousePos = vec2(pOut.x, pOut.y); 290 } else { 291 mousePos = -mousePos; 292 } 293 294 vtxAtMouse = mesh.getVertexFromPoint(mousePos); 295 296 if (incInputIsMouseReleased(ImGuiMouseButton.Left)) { 297 isDragging = false; 298 if (isSelecting) { 299 if (mutateSelection) { 300 if (!invertSelection) { 301 foreach(v; newSelected) { 302 auto idx = selected.countUntil(v); 303 if (idx == -1) selected ~= v; 304 } 305 } else { 306 foreach(v; newSelected) { 307 auto idx = selected.countUntil(v); 308 if (idx != -1) selected = selected.remove(idx); 309 } 310 } 311 updateMirrorSelected(); 312 newSelected.length = 0; 313 } else { 314 selected = newSelected; 315 newSelected = []; 316 updateMirrorSelected(); 317 } 318 319 isSelecting = false; 320 } 321 } 322 323 if (igIsMouseClicked(ImGuiMouseButton.Left)) maybeSelectOne = null; 324 325 switch(toolMode) { 326 case VertexToolMode.Points: 327 void addOrRemoveVertex(bool selectedOnly) { 328 if (deformOnly) return; 329 // Check if mouse is over a vertex 330 if (vtxAtMouse !is null) { 331 332 // In the case that it is, double clicking would remove an item 333 if (!selectedOnly || isSelected(vtxAtMouse)) { 334 foreachMirror((uint axis) { 335 mesh.removeVertexAt(mirror(axis, mousePos)); 336 }); 337 refreshMesh(); 338 vertexMapDirty = true; 339 changed = true; 340 selected.length = 0; 341 updateMirrorSelected(); 342 maybeSelectOne = null; 343 vtxAtMouse = null; 344 } 345 } else { 346 ulong off = mesh.vertices.length; 347 foreachMirror((uint axis) { 348 mesh.vertices ~= new MeshVertex(mirror(axis, mousePos)); 349 }); 350 refreshMesh(); 351 vertexMapDirty = true; 352 changed = true; 353 selectOne(mesh.vertices[off]); 354 } 355 } 356 357 // Key actions 358 if (!deformOnly && incInputIsKeyPressed(ImGuiKey.Delete)) { 359 foreachMirror((uint axis) { 360 foreach(v; selected) { 361 MeshVertex *v2 = mirrorVertex(axis, v); 362 if (v2 !is null) mesh.remove(v2); 363 } 364 }); 365 selected = []; 366 updateMirrorSelected(); 367 refreshMesh(); 368 vertexMapDirty = true; 369 changed = true; 370 } 371 void shiftSelection(vec2 delta) { 372 float magnitude = 10.0; 373 if (io.KeyAlt) magnitude = 1.0; 374 else if (io.KeyShift) magnitude = 100.0; 375 delta *= magnitude; 376 377 foreachMirror((uint axis) { 378 vec2 mDelta = mirrorDelta(axis, delta); 379 foreach(v; selected) { 380 MeshVertex *v2 = mirrorVertex(axis, v); 381 if (v2 !is null) v2.position += mDelta; 382 } 383 }); 384 refreshMesh(); 385 changed = true; 386 } 387 388 if (incInputIsKeyPressed(ImGuiKey.LeftArrow)) { 389 shiftSelection(vec2(-1, 0)); 390 } else if (incInputIsKeyPressed(ImGuiKey.RightArrow)) { 391 shiftSelection(vec2(1, 0)); 392 } else if (incInputIsKeyPressed(ImGuiKey.DownArrow)) { 393 shiftSelection(vec2(0, 1)); 394 } else if (incInputIsKeyPressed(ImGuiKey.UpArrow)) { 395 shiftSelection(vec2(0, -1)); 396 } 397 398 // Left click selection 399 if (igIsMouseClicked(ImGuiMouseButton.Left)) { 400 if (!deformOnly && io.KeyCtrl && !io.KeyShift) { 401 // Add/remove action 402 addOrRemoveVertex(false); 403 } else { 404 // Select / drag start 405 if (mesh.isPointOverVertex(mousePos)) { 406 if (io.KeyShift) toggleSelect(vtxAtMouse); 407 else if (!isSelected(vtxAtMouse)) selectOne(vtxAtMouse); 408 else maybeSelectOne = vtxAtMouse; 409 } else { 410 selectOrigin = mousePos; 411 isSelecting = true; 412 } 413 } 414 } 415 if (!isDragging && !isSelecting && 416 incInputIsMouseReleased(ImGuiMouseButton.Left) && maybeSelectOne !is null) { 417 selectOne(maybeSelectOne); 418 } 419 420 // Left double click action 421 if (!deformOnly && igIsMouseDoubleClicked(ImGuiMouseButton.Left) && !io.KeyShift && !io.KeyCtrl) { 422 addOrRemoveVertex(true); 423 } 424 425 // Dragging 426 if (igIsMouseDown(ImGuiMouseButton.Left) && incInputIsDragRequested(ImGuiMouseButton.Left)) { 427 if (!isSelecting) isDragging = true; 428 } 429 430 if (isDragging) { 431 foreach(select; selected) { 432 foreachMirror((uint axis) { 433 MeshVertex *v = mirrorVertex(axis, select); 434 if (v is null) return; 435 v.position += mirror(axis, mousePos - lastMousePos); 436 }); 437 } 438 changed = true; 439 refreshMesh(); 440 } 441 442 break; 443 case VertexToolMode.Connect: 444 assert(!deformOnly); 445 446 if (igIsMouseClicked(ImGuiMouseButton.Left)) { 447 if (vtxAtMouse !is null) { 448 auto prev = selectOne(vtxAtMouse); 449 if (prev !is null) { 450 if (prev != selected[$-1]) { 451 452 // Connect or disconnect between previous and this node 453 if (!prev.isConnectedTo(selected[$-1])) { 454 foreachMirror((uint axis) { 455 MeshVertex *mPrev = mirrorVertex(axis, prev); 456 MeshVertex *mSel = mirrorVertex(axis, selected[$-1]); 457 if (mPrev !is null && mSel !is null) mPrev.connect(mSel); 458 }); 459 changed = true; 460 } else { 461 foreachMirror((uint axis) { 462 MeshVertex *mPrev = mirrorVertex(axis, prev); 463 MeshVertex *mSel = mirrorVertex(axis, selected[$-1]); 464 if (mPrev !is null && mSel !is null) mPrev.disconnect(mSel); 465 }); 466 changed = true; 467 } 468 if (!io.KeyShift) deselectAll(); 469 } else { 470 471 // Selecting the same vert twice unselects it 472 deselectAll(); 473 } 474 } 475 476 refreshMesh(); 477 } else { 478 // Clicking outside a vert deselect verts 479 deselectAll(); 480 } 481 } 482 break; 483 case VertexToolMode.PathDeform: 484 vtxAtMouse = null; // Do not need this in this mode 485 486 if (incInputIsKeyPressed(ImGuiKey.Tab)) { 487 if (path.target is null) path.createTarget(mesh); 488 deforming = !deforming; 489 if (deforming) path.updateTarget(mesh); 490 else path.resetTarget(mesh); 491 changed = true; 492 } 493 494 CatmullSpline editPath = path; 495 if (deforming) editPath = path.target; 496 497 if (igIsMouseDoubleClicked(ImGuiMouseButton.Left) && !deforming) { 498 int idx = path.findPoint(mousePos); 499 if (idx != -1) path.removePoint(idx); 500 else path.addPoint(mousePos); 501 pathDragTarget = -1; 502 path.mapReference(); 503 } else if (igIsMouseClicked(ImGuiMouseButton.Left)) { 504 pathDragTarget = editPath.findPoint(mousePos); 505 } 506 507 if (igIsMouseDown(ImGuiMouseButton.Left) && incInputIsDragRequested(ImGuiMouseButton.Left)) { 508 if (pathDragTarget != -1) isDragging = true; 509 } 510 511 if (isDragging && pathDragTarget != -1) { 512 editPath.points[pathDragTarget].position += mousePos - lastMousePos; 513 editPath.update(); 514 if (deforming) { 515 path.updateTarget(mesh); 516 changed = true; 517 } else { 518 path.mapReference(); 519 } 520 } 521 522 if (changed) refreshMesh(); 523 524 break; 525 default: assert(0); 526 } 527 528 if (isSelecting) { 529 newSelected = mesh.getInRect(selectOrigin, mousePos); 530 mutateSelection = io.KeyShift; 531 invertSelection = io.KeyCtrl; 532 } 533 534 if (changed) 535 mesh.changed = true; 536 537 if (mesh.changed) { 538 if (previewingTriangulation()) 539 previewMesh = mesh.autoTriangulate(); 540 mesh.changed = false; 541 } 542 return changed; 543 } 544 545 void draw(Camera camera) { 546 mat4 trans = mat4.identity; 547 if (deformOnly) trans = target.transform.matrix(); 548 549 if (vtxAtMouse !is null && !isSelecting) { 550 MeshVertex*[] one = [vtxAtMouse]; 551 mesh.drawPointSubset(one, vec4(1, 1, 1, 0.3), trans, 15); 552 } 553 554 if (previewMesh) { 555 previewMesh.drawLines(trans, vec4(0.7, 0.7, 0, 1)); 556 mesh.drawPoints(trans); 557 } else { 558 mesh.draw(trans); 559 } 560 561 if (selected.length) { 562 if (isSelecting && !mutateSelection) 563 mesh.drawPointSubset(selected, vec4(0.6, 0, 0, 1), trans); 564 else 565 mesh.drawPointSubset(selected, vec4(1, 0, 0, 1), trans); 566 } 567 568 if (mirrorSelected.length) 569 mesh.drawPointSubset(mirrorSelected, vec4(1, 0, 1, 1), trans); 570 571 if (isSelecting) { 572 vec3[] rectLines = incCreateRectBuffer(selectOrigin, mousePos); 573 inDbgSetBuffer(rectLines); 574 if (!mutateSelection) inDbgDrawLines(vec4(1, 0, 0, 1), trans); 575 else if(invertSelection) inDbgDrawLines(vec4(0, 1, 1, 0.8), trans); 576 else inDbgDrawLines(vec4(0, 1, 0, 0.8), trans); 577 578 if (newSelected.length) { 579 if (mutateSelection && invertSelection) 580 mesh.drawPointSubset(newSelected, vec4(1, 0, 1, 1), trans); 581 else 582 mesh.drawPointSubset(newSelected, vec4(1, 0, 0, 1), trans); 583 } 584 } 585 586 vec2 camSize = camera.getRealSize(); 587 vec2 camPosition = camera.position; 588 vec3[] axisLines; 589 if (mirrorHoriz) { 590 axisLines ~= incCreateLineBuffer( 591 vec2(mirrorOrigin.x, -camSize.y - camPosition.y), 592 vec2(mirrorOrigin.x, camSize.y - camPosition.y) 593 ); 594 } 595 if (mirrorVert) { 596 axisLines ~= incCreateLineBuffer( 597 vec2(-camSize.x - camPosition.x, mirrorOrigin.y), 598 vec2(camSize.x - camPosition.x, mirrorOrigin.y) 599 ); 600 } 601 602 if (axisLines.length > 0) { 603 inDbgSetBuffer(axisLines); 604 inDbgDrawLines(vec4(0.8, 0, 0.8, 1), trans); 605 } 606 607 if (path && path.target && deforming) { 608 path.draw(trans, vec4(0, 0.6, 0.6, 1)); 609 path.target.draw(trans, vec4(0, 1, 0, 1)); 610 } else if (path) { 611 if (path.target) path.target.draw(trans, vec4(0, 0.6, 0, 1)); 612 path.draw(trans, vec4(0, 1, 1, 1)); 613 } 614 } 615 616 void viewportOverlay() { 617 igPushStyleVar(ImGuiStyleVar.ItemSpacing, ImVec2(0, 0)); 618 if (incButtonColored("", ImVec2(0, 0), getToolMode() == VertexToolMode.Points ? ImVec4.init : ImVec4(0.6, 0.6, 0.6, 1))) { 619 setToolMode(VertexToolMode.Points); 620 path = null; 621 refreshMesh(); 622 } 623 incTooltip(_("Vertex Tool")); 624 625 if (!deformOnly) { 626 igSameLine(0, 0); 627 if (incButtonColored("", ImVec2(0, 0), getToolMode() == VertexToolMode.Connect ? ImVec4.init : ImVec4(0.6, 0.6, 0.6, 1))) { 628 setToolMode(VertexToolMode.Connect); 629 path = null; 630 refreshMesh(); 631 } 632 incTooltip(_("Edge Tool")); 633 } 634 635 igSameLine(0, 0); 636 if (incButtonColored("", ImVec2(0, 0), getToolMode() == VertexToolMode.PathDeform ? ImVec4.init : ImVec4(0.6, 0.6, 0.6, 1))) { 637 setToolMode(VertexToolMode.PathDeform); 638 path = new CatmullSpline; 639 deforming = false; 640 refreshMesh(); 641 } 642 incTooltip(_("Path Deform Tool")); 643 644 igPopStyleVar(); 645 } 646 }