1 /* 2 Copyright © 2020,2022 Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 */ 5 module creator.actions.mesheditor; 6 7 import creator.core.actionstack; 8 import creator.viewport.common.mesheditor; 9 import creator.viewport.common.mesh; 10 import creator.viewport.common.spline; 11 import creator.viewport.model.deform; 12 import creator.viewport.vertex; 13 import creator.viewport; 14 import creator.actions; 15 import creator; 16 import inochi2d; 17 import std.format; 18 import std.range; 19 import i18n; 20 21 /** 22 Action for change of binding values at once 23 */ 24 class MeshEditorDeformationAction : LazyBoundAction { 25 alias TSelf = typeof(this); 26 string name; 27 bool dirty; 28 Parameter param; 29 Drawable target; 30 DeformationParameterBinding deform; 31 bool isSet; 32 vec2[] vertices; 33 vec2u keypoint; 34 bool bindingAdded; 35 bool undoable = true; 36 37 this(string name, void delegate() update = null) { 38 this.name = name; 39 this.bindingAdded = false; 40 this.clear(); 41 42 if (update !is null) { 43 update(); 44 this.updateNewState(); 45 } 46 } 47 48 auto self() { 49 return incViewportModelDeformGetEditor(); 50 } 51 52 void addVertex(MeshVertex* vertex) { 53 } 54 55 void markAsDirty() { dirty = true; } 56 57 void updateNewState() { 58 if (param) { 59 auto newDeform = cast(DeformationParameterBinding)param.getBinding(this.target, "deform"); 60 if (deform is null && newDeform !is null) 61 bindingAdded = true; 62 deform = newDeform; 63 } 64 } 65 66 void clear() { 67 if (self is null) { 68 target = null; 69 param = null; 70 deform = null; 71 bindingAdded = false; 72 dirty = false; 73 vertices = null; 74 isSet = false; 75 } else { 76 target = self.getTarget(); 77 param = incArmedParameter(); 78 keypoint = param.findClosestKeypoint(); 79 vertices = self.getOffsets(); 80 deform = cast(DeformationParameterBinding)param.getBinding(this.target, "deform"); 81 bindingAdded = false; 82 } 83 if (deform !is null) { 84 isSet = deform.isSet_[keypoint.x][keypoint.y]; 85 } 86 this.dirty = false; 87 } 88 89 bool isApplyable() { 90 return self !is null && self.getTarget() == this.target && incArmedParameter() == this.param && 91 incArmedParameter().findClosestKeypoint() == this.keypoint; 92 } 93 94 /** 95 Rollback 96 */ 97 void rollback() { 98 if (undoable) { 99 if (vertices) { 100 if (deform !is null) { 101 vec2[] tmpVertices = vertices; 102 bool tmpIsSet = isSet; 103 vertices = deform.values[keypoint.x][keypoint.y].vertexOffsets.dup; 104 isSet = deform.isSet_[keypoint.x][keypoint.y]; 105 deform.update(this.keypoint, tmpVertices); 106 deform.isSet_[keypoint.x][keypoint.y] = tmpIsSet; 107 deform.reInterpolate(); 108 if (bindingAdded) { 109 param.removeBinding(deform); 110 } 111 } 112 if (self !is null && self.getTarget() == this.target && incArmedParameter() == this.param) { 113 self.resetMesh(); 114 if (deform !is null) { 115 self.applyOffsets(deform.getValue(param.findClosestKeypoint()).vertexOffsets); 116 } 117 } 118 if (self !is null) 119 self.getCleanDeformAction(); 120 } 121 undoable = false; 122 } 123 } 124 125 /** 126 Redo 127 */ 128 void redo() { 129 if (!undoable) { 130 if (vertices) { 131 if (deform !is null) { 132 vec2[] tmpVertices = vertices; 133 bool tmpIsSet = isSet; 134 vertices = deform.values[keypoint.x][keypoint.y].vertexOffsets.dup; 135 isSet = deform.isSet_[keypoint.x][keypoint.y]; 136 deform.update(this.keypoint, tmpVertices); 137 deform.isSet_[keypoint.x][keypoint.y] = tmpIsSet; 138 deform.reInterpolate(); 139 if (bindingAdded) { 140 param.addBinding(deform); 141 } 142 } 143 if (self !is null && self.getTarget() == this.target && incArmedParameter() == this.param) { 144 self.resetMesh(); 145 if (deform !is null) { 146 self.applyOffsets(deform.getValue(param.findClosestKeypoint()).vertexOffsets); 147 } 148 } 149 if (self !is null) 150 self.getCleanDeformAction(); 151 } 152 undoable = true; 153 } 154 } 155 156 /** 157 Describe the action 158 */ 159 string describe() { 160 return _("%s->Edited deformation of %s.").format("deform", name); 161 } 162 163 /** 164 Describe the action 165 */ 166 string describeUndo() { 167 return _("%s->deformation of %s was edited.").format("deform", name); 168 } 169 170 /** 171 Gets name of this action 172 */ 173 string getName() { 174 return this.stringof; 175 } 176 177 /** 178 Merge 179 */ 180 bool merge(Action other) { 181 if (this.canMerge(other)) { 182 return true; 183 } 184 return false; 185 } 186 187 /** 188 Gets whether this node can merge with an other 189 */ 190 bool canMerge(Action other) { 191 return false; 192 } 193 }; 194 195 class MeshEditorPathDeformAction : MeshEditorDeformationAction { 196 public: 197 // CatmullSpline path; 198 SplinePoint[] oldPathPoints; 199 SplinePoint[] oldTargetPathPoints; 200 SplinePoint[] newPathPoints; 201 SplinePoint[] newTargetPathPoints; 202 203 auto path() { 204 if (self !is null) 205 return self.getPath(); 206 else 207 return null; 208 } 209 210 this(string name, void delegate() update = null) { 211 super(name, update); 212 if (path !is null) 213 oldPathPoints = path.points.dup; 214 else 215 oldPathPoints = null; 216 if (this.path && this.path.target !is null) 217 oldTargetPathPoints = this.path.target.points.dup; 218 else 219 oldTargetPathPoints = null; 220 } 221 222 override 223 void updateNewState() { 224 super.updateNewState(); 225 if (path !is null) 226 newPathPoints = path.points.dup; 227 if (path !is null && path.target !is null) 228 newTargetPathPoints = path.target.points.dup; 229 } 230 231 override 232 void clear() { 233 super.clear(); 234 if (path !is null) 235 oldPathPoints = path.points.dup; 236 else 237 oldPathPoints = null; 238 if (path !is null && path.target !is null) 239 oldTargetPathPoints = path.target.points.dup; 240 else 241 oldTargetPathPoints = null; 242 newPathPoints = null; 243 newTargetPathPoints = null; 244 } 245 246 /** 247 Rollback 248 */ 249 override 250 void rollback() { 251 if (isApplyable()) { 252 if (oldPathPoints !is null && oldPathPoints.length > 0 && path !is null) { 253 path.points = oldPathPoints.dup; 254 path.update(); 255 } 256 if (oldTargetPathPoints !is null && oldTargetPathPoints.length > 0 && path !is null && path.target !is null) { 257 path.target.points = oldTargetPathPoints.dup; 258 path.target.update(); 259 } 260 } 261 super.rollback(); 262 } 263 264 /** 265 Redo 266 */ 267 override 268 void redo() { 269 if (isApplyable()) { 270 if (newPathPoints !is null && newPathPoints.length > 0 && path !is null) { 271 this.path.points = newPathPoints.dup; 272 this.path.update(); 273 } 274 if (newTargetPathPoints !is null && newTargetPathPoints.length > 0 && path !is null && path.target !is null) { 275 this.path.target.points = newTargetPathPoints.dup; 276 this.path.target.update(); 277 } 278 } 279 super.redo(); 280 } 281 }