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 }