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