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 inochi2d.core.param;
10 import inochi2d.fmt.serialize;
11 import inochi2d.math.serialization;
12 import inochi2d.core;
13 import inochi2d.math;
14 import std.exception;
15 import std.array;
16 import std.algorithm.mutation;
17 import std.stdio;
18 
19 public import inochi2d.core.param.binding;
20 
21 /**
22     A parameter
23 */
24 class Parameter {
25 public:
26     /**
27         Unique ID of parameter
28     */
29     uint uuid;
30 
31     /**
32         Name of the parameter
33     */
34     string name;
35 
36     /**
37         Optimized indexable name generated at runtime
38 
39         DO NOT SERIALIZE THIS.
40     */
41     string indexableName;
42 
43     /**
44         Whether this parameter updates the model
45     */
46     bool active = true;
47 
48     /**
49         Automator calculated offset to apply 
50     */
51     vec2 offset = vec2(0);
52 
53     /**
54         The current parameter value
55     */
56     vec2 value = vec2(0);
57 
58     /**
59         The default value
60     */
61     vec2 defaults = vec2(0);
62 
63     /**
64         Whether the parameter is 2D
65     */
66     bool isVec2;
67 
68     /**
69         The parameter's minimum bounds
70     */
71     vec2 min = vec2(0, 0);
72 
73     /**
74         The parameter's maximum bounds
75     */
76     vec2 max = vec2(1, 1);
77 
78     /**
79         Position of the keypoints along each axis
80     */
81     float[][2] axisPoints = [[0, 1], [0, 1]];
82 
83     /**
84         Binding to targets
85     */
86     ParameterBinding[] bindings;
87 
88     /**
89         Gets the value normalized to the internal range (0.0->1.0)
90     */
91     vec2 normalizedValue() {
92         return this.mapValue(value);
93     }
94 
95     /**
96         Sets the value normalized up from the internal range (0.0->1.0)
97         to the user defined range.
98     */
99     void normalizedValue(vec2 value) {
100         this.value = vec2(
101             value.x * (max.x-min.x) + min.x,
102             value.y * (max.y-min.y) + min.y
103         );
104     }
105 
106     /**
107         For serialization
108     */
109     this() { }
110 
111     /**
112         Unload UUID on clear
113     */
114     ~this() {
115         inUnloadUUID(this.uuid);
116     }
117 
118     /**
119         Create new parameter
120     */
121     this(string name, bool isVec2) {
122         this.uuid = inCreateUUID();
123         this.name = name;
124         this.isVec2 = isVec2;
125         if (!isVec2)
126             axisPoints[1] = [0];
127         
128         this.makeIndexable();
129     }
130 
131     /**
132         Clone this parameter
133     */
134     Parameter dup() {
135         Parameter newParam = new Parameter(name ~ " (Copy)", isVec2);
136 
137         newParam.min = min;
138         newParam.max = max;
139         newParam.axisPoints = axisPoints.dup;
140 
141         foreach(binding; bindings) {
142             ParameterBinding newBinding = newParam.createBinding(
143                 binding.getNode(),
144                 binding.getName(),
145                 false
146             );
147             newBinding.interpolateMode = binding.interpolateMode;
148             foreach(x; 0..axisPointCount(0)) {
149                 foreach(y; 0..axisPointCount(1)) {
150                     binding.copyKeypointToBinding(vec2u(x, y), newBinding, vec2u(x, y));
151                 }
152             }
153             newParam.addBinding(newBinding);
154         }
155 
156         return newParam;
157     }
158 
159     /**
160         Serializes a parameter
161     */
162     void serialize(S)(ref S serializer) {
163         auto state = serializer.objectBegin;
164             serializer.putKey("uuid");
165             serializer.putValue(uuid);
166             serializer.putKey("name");
167             serializer.putValue(name);
168             serializer.putKey("is_vec2");
169             serializer.putValue(isVec2);
170             serializer.putKey("min");
171             min.serialize(serializer);
172             serializer.putKey("max");
173             max.serialize(serializer);
174             serializer.putKey("defaults");
175             defaults.serialize(serializer);
176             serializer.putKey("axis_points");
177             serializer.serializeValue(axisPoints);
178             serializer.putKey("bindings");
179             auto arrstate = serializer.arrayBegin();
180                 foreach(binding; bindings) {
181                     serializer.elemBegin();
182                     binding.serializeSelf(serializer);
183                 }
184             serializer.arrayEnd(arrstate);
185         serializer.objectEnd(state);
186     }
187 
188     /**
189         Deserializes a parameter
190     */
191     SerdeException deserializeFromFghj(Fghj data) {
192         data["uuid"].deserializeValue(this.uuid);
193         data["name"].deserializeValue(this.name);
194         if (!data["is_vec2"].isEmpty) data["is_vec2"].deserializeValue(this.isVec2);
195         if (!data["min"].isEmpty) min.deserialize(data["min"]);
196         if (!data["max"].isEmpty) max.deserialize(data["max"]);
197         if (!data["axis_points"].isEmpty) data["axis_points"].deserializeValue(this.axisPoints);
198         if (!data["defaults"].isEmpty) defaults.deserialize(data["defaults"]);
199 
200         if (!data["bindings"].isEmpty) {
201             foreach(Fghj child; data["bindings"].byElement) {
202                 
203                 // Skip empty children
204                 if (child["param_name"].isEmpty) continue;
205 
206                 string paramName;
207                 child["param_name"].deserializeValue(paramName);
208 
209                 if (paramName == "deform") {
210                     auto binding = new DeformationParameterBinding(this);
211                     binding.deserializeFromFghj(child);
212                     bindings ~= binding;
213                 } else {
214                     auto binding = new ValueParameterBinding(this);
215                     binding.deserializeFromFghj(child);
216                     bindings ~= binding;
217                 }
218             }
219         }
220 
221         return null;
222     }
223 
224     /**
225         Finalize loading of parameter
226     */
227     void finalize(Puppet puppet) {
228         this.makeIndexable();
229         this.value = defaults;
230 
231         ParameterBinding[] validBindingList;
232         foreach(i, binding; bindings) {
233             if (puppet.find!Node(binding.getNodeUUID())) {
234                 binding.finalize(puppet);
235                 validBindingList ~= binding;
236             }
237         }
238         
239         bindings = validBindingList;
240     }
241 
242     void findOffset(vec2 offset, out vec2u index, out vec2 outOffset) {
243         void interpAxis(uint axis, float val, out uint index, out float offset) {
244             float[] pos = axisPoints[axis];
245 
246             foreach(i; 0..pos.length - 1) {
247                 if (pos[i + 1] > val || i == (pos.length - 2)) {
248                     index = cast(uint)i;
249                     offset = (val - pos[i]) / (pos[i + 1] - pos[i]);
250                     return;
251                 }
252             }
253         }
254 
255         interpAxis(0, offset.x, index.x, outOffset.x);
256         if (isVec2) interpAxis(1, offset.y, index.y, outOffset.y);
257     }
258 
259     void preUpdate() {
260         offset = vec2(0);
261     }
262 
263     void update() {
264         vec2u index;
265         vec2 offset_;
266 
267         if (!active)
268             return;
269 
270         findOffset(this.mapValue(value + offset), index, offset_);
271         foreach(binding; bindings) {
272             binding.apply(index, offset_);
273         }
274     }
275 
276     /**
277         Get number of points for an axis
278     */
279     uint axisPointCount(uint axis = 0) {
280         return cast(uint)axisPoints[axis].length;
281     }
282 
283     /**
284         Move an axis point to a new offset
285     */
286     void moveAxisPoint(uint axis, uint oldidx, float newoff) {
287         assert(oldidx > 0 && oldidx < this.axisPointCount(axis)-1, "invalid point index");
288         assert(newoff > 0 && newoff < 1, "offset out of bounds");
289         if (isVec2)
290             assert(axis <= 1, "bad axis");
291         else
292             assert(axis == 0, "bad axis");
293 
294         // Find the index at which to insert
295         uint index;
296         for(index = 1; index < axisPoints[axis].length; index++) {
297             if (axisPoints[axis][index+1] > newoff)
298                 break;
299         }
300         
301         if (oldidx != index) {
302             // BUG: Apparently deleting the oldindex and replacing it with newindex causes a crash.
303 
304             // Insert it into the new position in the list
305             auto swap = axisPoints[oldidx];
306             axisPoints[axis] = axisPoints[axis].remove(oldidx);
307             axisPoints[axis].insertInPlace(index, swap);
308             debug writeln("after move ", this.axisPointCount(0));
309         }
310 
311         // Tell all bindings to reinterpolate
312         foreach(binding; bindings) {
313             binding.moveKeypoints(axis, oldidx, index);
314         }
315     }
316 
317     /**
318         Add a new axis point at the given offset
319     */
320     void insertAxisPoint(uint axis, float off) {
321         assert(off > 0 && off < 1, "offset out of bounds");
322         if (isVec2)
323             assert(axis <= 1, "bad axis");
324         else
325             assert(axis == 0, "bad axis");
326 
327         // Find the index at which to insert
328         uint index;
329         for(index = 1; index < axisPoints[axis].length; index++) {
330             if (axisPoints[axis][index] > off)
331                 break;
332         }
333 
334         // Insert it into the position list
335         axisPoints[axis].insertInPlace(index, off);
336 
337         // Tell all bindings to insert space into their arrays
338         foreach(binding; bindings) {
339             binding.insertKeypoints(axis, index);
340         }
341     }
342 
343     /**
344         Delete a specified axis point by index
345     */
346     void deleteAxisPoint(uint axis, uint index) {
347         if (isVec2)
348             assert(axis <= 1, "bad axis");
349         else
350             assert(axis == 0, "bad axis");
351 
352         assert(index > 0, "cannot delete axis point at 0");
353         assert(index < (axisPoints[axis].length - 1), "cannot delete axis point at 1");
354 
355         // Remove the keypoint
356         axisPoints[axis] = axisPoints[axis].remove(index);
357 
358         // Tell all bindings to remove it from their arrays
359         foreach(binding; bindings) {
360             binding.deleteKeypoints(axis, index);
361         }
362     }
363 
364     /**
365         Flip the mapping across an axis
366     */
367     void reverseAxis(uint axis) {
368         axisPoints[axis].reverse();
369         foreach(ref i; axisPoints[axis]) {
370             i = 1 - i;
371         }
372         foreach(binding; bindings) {
373             binding.reverseAxis(axis);
374         }
375     }
376 
377     /**
378         Get the offset (0..1) of a specified keypoint index
379     */
380     vec2 getKeypointOffset(vec2u index) {
381         return vec2(axisPoints[0][index.x], axisPoints[1][index.y]);
382     }
383 
384     /**
385         Get the value at a specified keypoint index
386     */
387     vec2 getKeypointValue(vec2u index) {
388         return unmapValue(getKeypointOffset(index));
389     }
390 
391     /**
392         Maps an input value to an offset (0.0->1.0)
393     */
394     vec2 mapValue(vec2 value) {
395         vec2 range = max - min;
396         vec2 tmp = (value - min);
397         vec2 off = vec2(tmp.x / range.x, tmp.y / range.y);
398 
399         vec2 clamped = vec2(
400             clamp(off.x, 0, 1),
401             clamp(off.y, 0, 1),
402         );
403         return clamped;
404     }
405 
406     /**
407         Maps an offset (0.0->1.0) to a value
408     */
409     vec2 unmapValue(vec2 offset) {
410         vec2 range = max - min;
411         return vec2(range.x * offset.x, range.y * offset.y) + min;
412     }
413 
414     /**
415         Maps an input value to an offset (0.0->1.0) for an axis
416     */
417     float mapAxis(uint axis, float value) {
418         vec2 input = min;
419         if (axis == 0) input.x = value;
420         else input.y = value;
421         vec2 output = mapValue(input);
422         if (axis == 0) return output.x;
423         else return output.y;
424     }
425 
426     /**
427         Maps an internal value (0.0->1.0) to the input range for an axis
428     */
429     float unmapAxis(uint axis, float offset) {
430         vec2 input = min;
431         if (axis == 0) input.x = offset;
432         else input.y = offset;
433         vec2 output = unmapValue(input);
434         if (axis == 0) return output.x;
435         else return output.y;
436     }
437 
438     /**
439         Gets the axis point closest to a given offset
440     */
441     uint getClosestAxisPointIndex(uint axis, float offset) {
442         uint closestPoint = 0;
443         float closestDist = float.infinity;
444 
445         foreach(i, pointVal; axisPoints[axis]) {
446             float dist = abs(pointVal - offset);
447             if (dist < closestDist) {
448                 closestDist = dist;
449                 closestPoint = cast(uint)i;
450             }
451         }
452 
453         return closestPoint;
454     }
455 
456     /**
457         Find the keypoint closest to the current value
458     */
459     vec2u findClosestKeypoint() {
460         return findClosestKeypoint(value);
461     }
462 
463     /**
464         Find the keypoint closest to a value
465     */
466     vec2u findClosestKeypoint(vec2 value) {
467         vec2 mapped = mapValue(value);
468         uint x = getClosestAxisPointIndex(0, mapped.x);
469         uint y = getClosestAxisPointIndex(1, mapped.y);
470 
471         return vec2u(x, y);
472     }
473 
474     /**
475         Find the keypoint closest to the current value
476     */
477     vec2 getClosestKeypointValue() {
478         return getKeypointValue(findClosestKeypoint());
479     }
480 
481     /**
482         Find the keypoint closest to a value
483     */
484     vec2 getClosestKeypointValue(vec2 value) {
485         return getKeypointValue(findClosestKeypoint(value));
486     }
487 
488     /**
489         Find a binding by node ref and name
490     */
491     ParameterBinding getBinding(Node n, string bindingName) {
492         foreach(ref binding; bindings) {
493             if (binding.getNode() != n) continue;
494             if (binding.getName == bindingName) return binding;
495         }
496         return null;
497     }
498 
499     /**
500         Check if a binding exists for a given node and name
501     */
502     bool hasBinding(Node n, string bindingName) {
503         foreach(ref binding; bindings) {
504             if (binding.getNode() != n) continue;
505             if (binding.getName == bindingName) return true;
506         }
507         return false;
508     }
509 
510     /**
511         Create a new binding (without adding it) for a given node and name
512     */
513     ParameterBinding createBinding(Node n, string bindingName, bool setZero = true) {
514         ParameterBinding b;
515         if (bindingName == "deform") {
516             b = new DeformationParameterBinding(this, n, bindingName);
517         } else {
518             b = new ValueParameterBinding(this, n, bindingName);
519         }
520 
521         if (setZero) {
522             vec2u zeroIndex = findClosestKeypoint(vec2(0, 0));
523             vec2 zero = getKeypointValue(zeroIndex);
524             if (abs(zero.x) < 0.001 && abs(zero.y) < 0.001) b.reset(zeroIndex);
525         }
526 
527         return b;
528     }
529 
530     /**
531         Find a binding if it exists, or create and add a new one, and return it
532     */
533     ParameterBinding getOrAddBinding(Node n, string bindingName, bool setZero = true) {
534         ParameterBinding binding = getBinding(n, bindingName);
535         if (binding is null) {
536             binding = createBinding(n, bindingName, setZero);
537             addBinding(binding);
538         }
539         return binding;
540     }
541 
542     /**
543         Add a new binding (must not exist)
544     */
545     void addBinding(ParameterBinding binding) {
546         assert(!hasBinding(binding.getNode, binding.getName));
547         bindings ~= binding;
548     }
549 
550     /**
551         Remove an existing binding by ref
552     */
553     void removeBinding(ParameterBinding binding) {
554         import std.algorithm.searching : countUntil;
555         import std.algorithm.mutation : remove;
556         ptrdiff_t idx = bindings.countUntil(binding);
557         if (idx >= 0) {
558             bindings = bindings.remove(idx);
559         }
560     }
561 
562     void makeIndexable() {
563         import std.uni : toLower;
564         indexableName = name.toLower;
565     }
566 }