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 }