From 27f6b463b92b8a3c4a2aa52f4596affd1e77a8be Mon Sep 17 00:00:00 2001 From: seagetch Date: Wed, 10 Aug 2022 12:57:24 +0900 Subject: [PATCH] Enhance undo impl2 (#63) * Added license comment. * Enhance undo/redo for path deformation and drawable editing. * - remember and undo "isSet" flags for undo/redo of DeformationParameterBinding. - Undo work well with path deformation across different keypoints. --- source/creator/actions/drawable.d | 91 +++++++++ source/creator/actions/mesh.d | 126 ------------ source/creator/actions/mesheditor.d | 216 ++++++++++++++++++++ source/creator/actions/package.d | 3 +- source/creator/viewport/common/mesheditor.d | 63 ++++-- 5 files changed, 354 insertions(+), 145 deletions(-) create mode 100644 source/creator/actions/drawable.d delete mode 100644 source/creator/actions/mesh.d create mode 100644 source/creator/actions/mesheditor.d diff --git a/source/creator/actions/drawable.d b/source/creator/actions/drawable.d new file mode 100644 index 000000000..7de34e097 --- /dev/null +++ b/source/creator/actions/drawable.d @@ -0,0 +1,91 @@ +/* + Copyright © 2020,2022 Inochi2D Project + Distributed under the 2-Clause BSD License, see LICENSE file. +*/ +module creator.actions.drawable; +import creator.core.actionstack; +import creator.actions; +import creator; +import inochi2d; +import std.format; +import i18n; + +/** + Action to add parameter to active puppet. +*/ +class DrawableChangeAction : GroupAction, LazyBoundAction { +private: + void copy(ref MeshData src, ref MeshData dst) { + dst.vertices = src.vertices.dup; + dst.uvs = src.uvs.dup; + dst.indices = src.indices.dup; + dst.origin = src.origin; + } +public: + Drawable self; + string name; + + MeshData oldMesh; + MeshData newMesh; + + this(string name, Drawable self) { + super(); + this.name = name; + this.self = self; + copy(self.getMesh(), oldMesh); + } + + override + void updateNewState() { + copy(self.getMesh(), newMesh); + } + + void addBinding(Parameter param, ParameterBinding binding) { + addAction(new ParameterBindingRemoveAction(param, binding)); + } + + /** + Rollback + */ + override + void rollback() { + self.rebuffer(oldMesh); + super.rollback(); + } + + /** + Redo + */ + override + void redo() { + self.rebuffer(newMesh); + super.redo(); + } + + /** + Describe the action + */ + override + string describe() { + return _("Changed drawable mesh of %s").format(self.name); + } + + /** + Describe the action + */ + override + string describeUndo() { + return _("Drawable %s was changed").format(self.name); + } + + /** + Gets name of this action + */ + override + string getName() { + return this.stringof; + } + + override bool merge(Action other) { return false; } + override bool canMerge(Action other) { return false; } +} diff --git a/source/creator/actions/mesh.d b/source/creator/actions/mesh.d deleted file mode 100644 index 2877aee59..000000000 --- a/source/creator/actions/mesh.d +++ /dev/null @@ -1,126 +0,0 @@ -module creator.actions.mesh; - -import creator.core.actionstack; -import creator.viewport.common.mesheditor; -import creator.viewport.common.mesh; -import creator.viewport.vertex; -import creator.viewport.common.spline; -import creator.viewport; -import creator.actions; -import creator; -import inochi2d; -import std.format; -import std.range; -import i18n; - -/** - Action for change of binding values at once -*/ -class MeshEditorDeformationAction : LazyBoundAction { - alias TSelf = typeof(this); - string name; - bool dirty; - IncMeshEditor self; - Parameter param; - Drawable target; - DeformationParameterBinding deform; - vec2[] oldVertices; - vec2[] newVertices; - vec2u keypoint; - - this(string name, IncMeshEditor self, void delegate() update = null) { - this.name = name; - this.self = self; - this.clear(); - - if (update !is null) { - update(); - this.updateNewState(); - } - } - - void addVertex(MeshVertex* vertex) { - } - - void markAsDirty() { dirty = true; } - - void updateNewState() { - newVertices = self.getOffsets(); - } - - void clear() { - this.target = self.getTarget(); - this.param = incArmedParameter(); - this.keypoint = param.findClosestKeypoint(); - this.oldVertices = self.getOffsets(); - this.newVertices = null; - this.deform = cast(DeformationParameterBinding)param.getOrAddBinding(this.target, "deform"); - this.dirty = false; - } - - /** - Rollback - */ - void rollback() { - deform.update(this.keypoint, oldVertices); - if (self.getTarget() == this.target && incArmedParameter() == this.param && - incArmedParameter().findClosestKeypoint() == this.keypoint && - self.getOffsets().length == this.oldVertices.length) { - - self.mesh.setBackOffsets(oldVertices); - } - } - - /** - Redo - */ - void redo() { - if (newVertices) { - deform.update(this.keypoint, newVertices); - if (self.getTarget() == this.target && incArmedParameter() == this.param && - incArmedParameter().findClosestKeypoint() == this.keypoint && - self.getOffsets().length == this.newVertices.length) { - - self.mesh.setBackOffsets(newVertices); - } - } - } - - /** - Describe the action - */ - string describe() { - return _("%s->Edited deformation of %s.").format("deform", name); - } - - /** - Describe the action - */ - string describeUndo() { - return _("%s->deformation of %s was edited.").format("deform", name); - } - - /** - Gets name of this action - */ - string getName() { - return this.stringof; - } - - /** - Merge - */ - bool merge(Action other) { - if (this.canMerge(other)) { - return true; - } - return false; - } - - /** - Gets whether this node can merge with an other - */ - bool canMerge(Action other) { - return false; - } -}; diff --git a/source/creator/actions/mesheditor.d b/source/creator/actions/mesheditor.d new file mode 100644 index 000000000..c054f3842 --- /dev/null +++ b/source/creator/actions/mesheditor.d @@ -0,0 +1,216 @@ +/* + Copyright © 2020,2022 Inochi2D Project + Distributed under the 2-Clause BSD License, see LICENSE file. +*/ +module creator.actions.mesheditor; + +import creator.core.actionstack; +import creator.viewport.common.mesheditor; +import creator.viewport.common.mesh; +import creator.viewport.vertex; +import creator.viewport.common.spline; +import creator.viewport; +import creator.actions; +import creator; +import inochi2d; +import std.format; +import std.range; +import i18n; + +/** + Action for change of binding values at once +*/ +class MeshEditorDeformationAction : LazyBoundAction { + alias TSelf = typeof(this); + string name; + bool dirty; + IncMeshEditor self; + Parameter param; + Drawable target; + DeformationParameterBinding deform; + bool oldIsSet; + bool newIsSet; + vec2[] oldVertices; + vec2[] newVertices; + vec2u keypoint; + + this(string name, IncMeshEditor self, void delegate() update = null) { + this.name = name; + this.self = self; + this.clear(); + + if (update !is null) { + update(); + this.updateNewState(); + } + } + + void addVertex(MeshVertex* vertex) { + } + + void markAsDirty() { dirty = true; } + + void updateNewState() { + newVertices = self.getOffsets(); + this.newIsSet = deform.isSet_[keypoint.x][keypoint.y]; + } + + void clear() { + this.target = self.getTarget(); + this.param = incArmedParameter(); + this.keypoint = param.findClosestKeypoint(); + this.oldVertices = self.getOffsets(); + this.newVertices = null; + this.deform = cast(DeformationParameterBinding)param.getOrAddBinding(this.target, "deform"); + this.oldIsSet = deform.isSet_[keypoint.x][keypoint.y]; + this.dirty = false; + } + + bool isApplyable() { + return self.getTarget() == this.target && incArmedParameter() == this.param && + incArmedParameter().findClosestKeypoint() == this.keypoint; + } + + /** + Rollback + */ + void rollback() { + deform.update(this.keypoint, oldVertices); + deform.isSet_[keypoint.x][keypoint.y] = oldIsSet; + deform.reInterpolate(); + if (isApplyable() && self.getOffsets().length == this.oldVertices.length) { + self.mesh.setBackOffsets(oldVertices); + } + self.getCleanDeformAction(); + } + + /** + Redo + */ + void redo() { + if (newVertices) { + deform.update(this.keypoint, newVertices); + deform.isSet_[keypoint.x][keypoint.y] = newIsSet; + deform.reInterpolate(); + if (isApplyable() && self.getOffsets().length == this.newVertices.length) { + self.mesh.setBackOffsets(newVertices); + } + self.getCleanDeformAction(); + } + } + + /** + Describe the action + */ + string describe() { + return _("%s->Edited deformation of %s.").format("deform", name); + } + + /** + Describe the action + */ + string describeUndo() { + return _("%s->deformation of %s was edited.").format("deform", name); + } + + /** + Gets name of this action + */ + string getName() { + return this.stringof; + } + + /** + Merge + */ + bool merge(Action other) { + if (this.canMerge(other)) { + return true; + } + return false; + } + + /** + Gets whether this node can merge with an other + */ + bool canMerge(Action other) { + return false; + } +}; + +class MeshEditorPathDeformAction : MeshEditorDeformationAction { +public: + CatmullSpline path; + SplinePoint[] oldPathPoints; + SplinePoint[] oldTargetPathPoints; + SplinePoint[] newPathPoints; + SplinePoint[] newTargetPathPoints; + + this(string name, IncMeshEditor self, CatmullSpline path, void delegate() update = null) { + this.path = path; + super(name, self, update); + oldPathPoints = this.path.points.dup; + if (this.path.target !is null) + oldTargetPathPoints = this.path.target.points.dup; + } + + override + void updateNewState() { + super.updateNewState(); + newPathPoints = this.path.points.dup; + if (this.path.target !is null) + newTargetPathPoints = this.path.target.points.dup; + } + + override + void clear() { + super.clear(); + oldPathPoints = this.path.points.dup; + if (this.path.target !is null) + oldTargetPathPoints = this.path.target.points.dup; + newPathPoints = null; + newTargetPathPoints = null; + } + + /** + Rollback + */ + override + void rollback() { + if (self.getTarget() == this.target && incArmedParameter() == this.param && + incArmedParameter().findClosestKeypoint() == this.keypoint) { + if (oldPathPoints !is null && oldPathPoints.length > 0) { + this.path.points = oldPathPoints.dup; + this.path.update(); + } + if (oldTargetPathPoints !is null && oldTargetPathPoints.length > 0) { + this.path.target.points = oldTargetPathPoints.dup; + this.path.target.update(); + this.path.target.updateTarget(self.mesh); + } + this.self.refreshMesh(); + } + super.rollback(); + } + + /** + Redo + */ + override + void redo() { + if (self.getTarget() == this.target && incArmedParameter() == this.param && + incArmedParameter().findClosestKeypoint() == this.keypoint) { + if (newPathPoints !is null && newPathPoints.length > 0) { + this.path.points = newPathPoints.dup; + this.path.update(); + } + if (newTargetPathPoints !is null && newTargetPathPoints.length > 0) { + this.path.target.points = newTargetPathPoints.dup; + this.path.target.update(); + this.path.updateTarget(self.mesh); + } + this.self.refreshMesh(); + } + super.redo(); + } +} \ No newline at end of file diff --git a/source/creator/actions/package.d b/source/creator/actions/package.d index d7fef49fb..a54dc690a 100644 --- a/source/creator/actions/package.d +++ b/source/creator/actions/package.d @@ -8,7 +8,8 @@ module creator.actions; public import creator.actions.node; public import creator.actions.parameter; public import creator.actions.binding; -public import creator.actions.mesh; +public import creator.actions.mesheditor; +public import creator.actions.drawable; /** An undo/redo-able action diff --git a/source/creator/viewport/common/mesheditor.d b/source/creator/viewport/common/mesheditor.d index 7a0d28cfe..d771625a9 100644 --- a/source/creator/viewport/common/mesheditor.d +++ b/source/creator/viewport/common/mesheditor.d @@ -55,7 +55,6 @@ private: bool deforming = false; CatmullSpline path; - CatmullSpline targetPath; uint pathDragTarget; MeshEditorDeformationAction deformAction = null; @@ -270,18 +269,25 @@ public: if (data.vertices.length != target.vertices.length) vertexMapDirty = true; + // Apply the model + auto action = new DrawableChangeAction(target.name, target); + target.rebuffer(data); + if (vertexMapDirty) { // Remove incompatible Deforms foreach (param; incActivePuppet().parameters) { ParameterBinding binding = param.getBinding(target, "deform"); - if (binding) param.removeBinding(binding); + if (binding) { + param.removeBinding(binding); + action.addBinding(param, binding); + } } vertexMapDirty = false; } - // Apply the model - target.rebuffer(data); + action.updateNewState(); + incActionPush(action); } void applyPreview() { @@ -298,15 +304,26 @@ public: } } - MeshEditorDeformationAction getCleanDeformAction() { - pushDeformAction(); - if (!deformAction) { - deformAction = new MeshEditorDeformationAction("test", this); + MeshEditorDeformationAction getDeformAction(bool reset = false)() { + if (reset) + pushDeformAction(); + if (deformAction is null || !deformAction.isApplyable()) { + switch (toolMode) { + case VertexToolMode.Points: + deformAction = new MeshEditorDeformationAction("test", this); + break; + case VertexToolMode.PathDeform: + deformAction = new MeshEditorPathDeformAction("test", this, path); + break; + default: + } } else { - deformAction.clear(); + if (reset) + deformAction.clear(); } return deformAction; } + alias getCleanDeformAction = getDeformAction!true; bool update(ImGuiIO* io, Camera camera) { bool changed = false; @@ -340,11 +357,6 @@ public: if (idx != -1) selected = selected.remove(idx); } } - if (deformAction !is null) { - foreach (v; selected) { - deformAction.addVertex(v); - } - } updateMirrorSelected(); newSelected.length = 0; } else { @@ -467,7 +479,10 @@ public: // Dragging if (igIsMouseDown(ImGuiMouseButton.Left) && incInputIsDragRequested(ImGuiMouseButton.Left)) { - if (!isSelecting) isDragging = true; + if (!isSelecting) { + isDragging = true; + getDeformAction(); + } } if (isDragging) { @@ -532,8 +547,8 @@ public: if (incInputIsKeyPressed(ImGuiKey.Tab)) { if (path.target is null) { - getCleanDeformAction(); path.createTarget(mesh); + getCleanDeformAction(); } else { if (deformAction !is null) { pushDeformAction(); @@ -567,7 +582,10 @@ public: } if (igIsMouseDown(ImGuiMouseButton.Left) && incInputIsDragRequested(ImGuiMouseButton.Left)) { - if (pathDragTarget != -1) isDragging = true; + if (pathDragTarget != -1) { + isDragging = true; + getDeformAction(); + } } if (isDragging && pathDragTarget != -1) { @@ -706,5 +724,14 @@ public: incTooltip(_("Path Deform Tool")); igPopStyleVar(); - } + } + + + CatmullSpline getPath() { + return path; + } + void setPath(CatmullSpline path) { + this.path = path; + + } } \ No newline at end of file