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