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.binding;
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 /**
20     A target to bind to
21 */
22 struct BindTarget {
23     /**
24         The node to bind to
25     */
26     Node node;
27 
28     /**
29         The parameter to bind
30     */
31     string paramName;
32 }
33 
34 /**
35     A binding to a parameter, of a given value type
36 */
37 abstract class ParameterBinding {
38 
39     /**
40         Restructure object before finalization
41     */
42     abstract void reconstruct(Puppet puppet);
43 
44     /**
45         Finalize loading of parameter
46     */
47     abstract void finalize(Puppet puppet);
48 
49     /**
50         Apply a binding to the model at the given parameter value
51     */
52     abstract void apply(vec2u leftKeypoint, vec2 offset);
53 
54     /**
55         Clear all keypoint data
56     */
57     abstract void clear();
58 
59     /**
60         Sets value at specified keypoint to the current value
61     */
62     abstract void setCurrent(vec2u point);
63 
64     /**
65         Unsets value at specified keypoint
66     */
67     abstract void unset(vec2u point);
68 
69     /**
70         Resets value at specified keypoint to default
71     */
72     abstract  void reset(vec2u point);
73 
74     /**
75         Returns whether the specified keypoint is set
76     */
77     abstract bool isSet(vec2u index);
78 
79     /**
80         Scales the value, optionally with axis awareness
81     */
82     abstract void scaleValueAt(vec2u index, int axis, float scale);
83 
84     /**
85         Extrapolates the value across an axis
86     */
87     abstract void extrapolateValueAt(vec2u index, int axis);
88 
89     /**
90         Copies the value to a point on another compatible binding
91     */
92     abstract void copyKeypointToBinding(vec2u src, ParameterBinding other, vec2u dest);
93 
94     /**
95         Swaps the value to a point on another compatible binding
96     */
97     abstract void swapKeypointWithBinding(vec2u src, ParameterBinding other, vec2u dest);
98 
99     /**
100         Flip the keypoints on an axis
101     */
102     abstract void reverseAxis(uint axis);
103 
104     /**
105         Update keypoint interpolation
106     */
107     abstract void reInterpolate();
108 
109     /**
110         Returns isSet_
111     */
112     abstract ref bool[][] getIsSet();
113 
114     /**
115         Gets how many breakpoints this binding is set to
116     */
117     abstract uint getSetCount();
118 
119     /**
120         Move keypoints to a new axis point
121     */
122     abstract void moveKeypoints(uint axis, uint oldindex, uint index);
123 
124     /**
125         Add keypoints along a new axis point
126     */
127     abstract void insertKeypoints(uint axis, uint index);
128 
129     /**
130         Remove keypoints along an axis point
131     */
132     abstract void deleteKeypoints(uint axis, uint index);
133 
134     /**
135         Gets target of binding
136     */
137     BindTarget getTarget();
138 
139     /**
140         Gets name of binding
141     */
142     abstract string getName();
143 
144     /**
145         Gets the node of the binding
146     */
147     abstract Node getNode();
148 
149     /**
150         Gets the uuid of the node of the binding
151     */
152     abstract uint getNodeUUID();
153 
154     /**
155         Checks whether a binding is compatible with another node
156     */
157     abstract bool isCompatibleWithNode(Node other);
158 
159     /**
160         Gets the interpolation mode
161     */
162     abstract InterpolateMode interpolateMode();
163 
164     /**
165         Sets the interpolation mode
166     */
167     abstract void interpolateMode(InterpolateMode mode);
168 
169     /**
170         Serialize
171     */
172     void serializeSelf(ref InochiSerializer serializer);
173 
174     /**
175         Deserialize
176     */
177     SerdeException deserializeFromFghj(Fghj data);
178 }
179 
180 /**
181     A binding to a parameter, of a given value type
182 */
183 abstract class ParameterBindingImpl(T) : ParameterBinding {
184 private:
185     /**
186         Node reference (for deserialization)
187     */
188     uint nodeRef;
189 
190     InterpolateMode interpolateMode_ = InterpolateMode.Linear;
191 
192 public:
193     /**
194         Parent Parameter owning this binding
195     */
196     Parameter parameter;
197 
198     /**
199         Reference to what parameter we're binding to
200     */
201     BindTarget target;
202 
203     /**
204         The value at each 2D keypoint
205     */
206     T[][] values;
207 
208     /**
209         Whether the value at each 2D keypoint is user-set
210     */
211     bool[][] isSet_;
212 
213     /**
214         Gets target of binding
215     */
216     override
217     BindTarget getTarget() {
218         return target;
219     }
220 
221     /**
222         Gets name of binding
223     */
224     override
225     string getName() {
226         return target.paramName;
227     }
228 
229     /**
230         Gets the node of the binding
231     */
232     override
233     Node getNode() {
234         return target.node;
235     }
236 
237     /**
238         Gets the uuid of the node of the binding
239     */
240     override
241     uint getNodeUUID() {
242         return nodeRef;
243     }
244 
245     /**
246         Returns isSet_
247     */
248     override
249     ref bool[][] getIsSet() {
250         return isSet_;
251     }
252 
253     /**
254         Gets how many breakpoints this binding is set to
255     */
256     override
257     uint getSetCount() {
258         uint count = 0;
259         foreach(x; 0..isSet_.length) {
260             foreach(y; 0..isSet_[x].length) {
261                 if (isSet_[x][y]) count++;
262             }
263         }
264         return count;
265     }
266 
267     this(Parameter parameter) {
268         this.parameter = parameter;
269     }
270 
271     this(Parameter parameter, Node targetNode, string paramName) {
272         this.parameter = parameter;
273         this.target.node = targetNode;
274         this.target.paramName = paramName;
275 
276         clear();
277     }
278 
279     /**
280         Serializes a binding
281     */
282     override
283     void serializeSelf(ref InochiSerializer serializer) {
284         auto state = serializer.objectBegin();
285             serializer.putKey("node");
286             serializer.putValue(target.node.uuid);
287             serializer.putKey("param_name");
288             serializer.putValue(target.paramName);
289             serializer.putKey("values");
290             serializer.serializeValue(values);
291             serializer.putKey("isSet");
292             serializer.serializeValue(isSet_);
293             serializer.putKey("interpolate_mode");
294             serializer.serializeValue(interpolateMode_);
295         serializer.objectEnd(state);
296     }
297 
298     /**
299         Deserializes a binding
300     */
301     override
302     SerdeException deserializeFromFghj(Fghj data) {
303         data["node"].deserializeValue(this.nodeRef);
304         data["param_name"].deserializeValue(this.target.paramName);
305         data["values"].deserializeValue(this.values);
306         data["isSet"].deserializeValue(this.isSet_);
307         auto mode = data["interpolate_mode"];
308         if (mode != Fghj.init) {
309             mode.deserializeValue(this.interpolateMode_);
310         } else {
311             this.interpolateMode_ = InterpolateMode.Linear;
312         }
313 
314         uint xCount = parameter.axisPointCount(0);
315         uint yCount = parameter.axisPointCount(1);
316 
317         enforce(this.values.length == xCount, "Mismatched X value count");
318         foreach(i; this.values) {
319             enforce(i.length == yCount, "Mismatched Y value count");
320         }
321 
322         enforce(this.isSet_.length == xCount, "Mismatched X isSet_ count");
323         foreach(i; this.isSet_) {
324             enforce(i.length == yCount, "Mismatched Y isSet_ count");
325         }
326 
327         return null;
328     }
329 
330     override
331     void reconstruct(Puppet puppet) { }
332 
333     /**
334         Finalize loading of parameter
335     */
336     override
337     void finalize(Puppet puppet) {
338 //        writefln("finalize binding %s", this.getName());
339 
340         this.target.node = puppet.find(nodeRef);
341 //        writefln("node for %d = %x", nodeRef, &(target.node));
342     }
343 
344     /**
345         Clear all keypoint data
346     */
347     override
348     void clear() {
349         uint xCount = parameter.axisPointCount(0);
350         uint yCount = parameter.axisPointCount(1);
351 
352         values.length = xCount;
353         isSet_.length = xCount;
354         foreach(x; 0..xCount) {
355             isSet_[x].length = 0;
356             isSet_[x].length = yCount;
357 
358             values[x].length = yCount;
359             foreach(y; 0..yCount) {
360                 clearValue(values[x][y]);
361             }
362         }
363     }
364 
365     void clearValue(ref T i) {
366         // Default: no-op
367     }
368 
369     /**
370         Gets the value at the specified point
371     */
372     ref T getValue(vec2u point) {
373         return values[point.x][point.y];
374     }
375 
376     /**
377         Sets value at specified keypoint
378     */
379     void setValue(vec2u point, T value) {
380         values[point.x][point.y] = value;
381         isSet_[point.x][point.y] = true;
382         
383         reInterpolate();
384     }
385 
386     /**
387         Sets value at specified keypoint to the current value
388     */
389     override
390     void setCurrent(vec2u point) {
391         isSet_[point.x][point.y] = true;
392 
393         reInterpolate();
394     }
395 
396     /**
397         Unsets value at specified keypoint
398     */
399     override
400     void unset(vec2u point) {
401         clearValue(values[point.x][point.y]);
402         isSet_[point.x][point.y] = false;
403 
404         reInterpolate();
405     }
406 
407     /**
408         Resets value at specified keypoint to default
409     */
410     override
411     void reset(vec2u point) {
412         clearValue(values[point.x][point.y]);
413         isSet_[point.x][point.y] = true;
414 
415         reInterpolate();
416     }
417 
418     /**
419         Returns whether the specified keypoint is set
420     */
421     override
422     bool isSet(vec2u index) {
423         return isSet_[index.x][index.y];
424     }
425 
426     /**
427         Flip the keypoints on an axis
428     */
429     override void reverseAxis(uint axis) {
430         if (axis == 0) {
431             values.reverse();
432             isSet_.reverse();
433         } else {
434             foreach(ref i; values) i.reverse();
435             foreach(ref i; isSet_) i.reverse();
436         }
437     }
438 
439     /**
440         Re-calculate interpolation
441     */
442     override
443     void reInterpolate() {
444         uint xCount = parameter.axisPointCount(0);
445         uint yCount = parameter.axisPointCount(1);
446 
447         // Currently valid points
448         bool[][] valid;
449         uint validCount = 0;
450         uint totalCount = xCount * yCount;
451 
452         // Initialize validity map to user-set points
453         foreach(x; 0..xCount) {
454             valid ~= isSet_[x].dup;
455             foreach(y; 0..yCount) {
456                 if (isSet_[x][y]) validCount++;
457             }
458         }
459 
460         // If there are zero valid points, just clear ourselves
461         if (validCount == 0) {
462             clear();
463             return;
464         }
465 
466         // Whether any given point was just set
467         bool[][] newlySet;
468         newlySet.length = xCount;
469 
470         // List of indices to commit
471         vec2u[] commitPoints;
472 
473         // Used by extendAndIntersect for x/y factor
474         float[][] interpDistance;
475         interpDistance.length = xCount;
476         foreach(x; 0..xCount) {
477             interpDistance[x].length = yCount;
478         }
479 
480         // Current interpolation axis
481         bool yMajor = false;
482 
483         // Helpers to handle interpolation across both axes more easily
484         uint majorCnt() {
485             if (yMajor) return yCount;
486             else return xCount;
487         }
488         uint minorCnt() {
489             if (yMajor) return xCount;
490             else return yCount;
491         }
492         bool isValid(uint maj, uint min) {
493             if (yMajor) return valid[min][maj];
494             else return valid[maj][min];
495         }
496         bool isNewlySet(uint maj, uint min) {
497             if (yMajor) return newlySet[min][maj];
498             else return newlySet[maj][min];
499         }
500         T get(uint maj, uint min) {
501             if (yMajor) return values[min][maj];
502             else return values[maj][min];
503         }
504         float getDistance(uint maj, uint min) {
505             if (yMajor) return interpDistance[min][maj];
506             else return interpDistance[maj][min];
507         }
508         void reset(uint maj, uint min, T val, float distance = 0) {
509             if (yMajor) {
510                 //debug writefln("set (%d, %d) -> %s", min, maj, val);
511                 assert(!valid[min][maj]);
512                 values[min][maj] = val;
513                 interpDistance[min][maj] = distance;
514                 newlySet[min][maj] = true;
515             } else {
516                 //debug writefln("set (%d, %d) -> %s", maj, min, val);
517                 assert(!valid[maj][min]);
518                 values[maj][min] = val;
519                 interpDistance[maj][min] = distance;
520                 newlySet[maj][min] = true;
521             }
522         }
523         void set(uint maj, uint min, T val, float distance = 0) {
524             reset(maj, min, val, distance);
525             if (yMajor) commitPoints ~= vec2u(min, maj);
526             else commitPoints ~= vec2u(maj, min);
527         }
528         float axisPoint(uint idx) {
529             if (yMajor) return parameter.axisPoints[0][idx];
530             else return parameter.axisPoints[1][idx];
531         }
532         T interp(uint maj, uint left, uint mid, uint right) {
533             float leftOff = axisPoint(left);
534             float midOff = axisPoint(mid);
535             float rightOff = axisPoint(right);
536             float off = (midOff - leftOff) / (rightOff - leftOff);
537 
538             //writefln("interp %d %d %d %d -> %f %f %f %f", maj, left, mid, right,
539             //leftOff, midOff, rightOff, off);
540             return get(maj, left) * (1 - off) + get(maj, right) * off;
541         }
542 
543         void interpolate1D2D(bool secondPass) {
544             yMajor = secondPass;
545             bool detectedIntersections = false;
546 
547             foreach(i; 0..majorCnt()) {
548                 uint l = 0;
549                 uint cnt = minorCnt();
550 
551                 // Find first element set
552                 for(; l < cnt && !isValid(i, l); l++) {}
553 
554                 // Empty row, we're done
555                 if (l >= cnt) continue;
556 
557                 while (true) {
558                     // Advance until before a missing element
559                     for(; l < cnt - 1 && isValid(i, l + 1); l++) {}
560 
561                     // Reached right side, done
562                     if (l >= (cnt - 1)) break;
563 
564                     // Find next set element
565                     uint r = l + 1;
566                     for(; r < cnt && !isValid(i, r); r++) {}
567 
568                     // If we ran off the edge, we're done
569                     if (r >= cnt) break;
570 
571                     // Interpolate between the pair of valid elements
572                     foreach (m; (l + 1)..r) {
573                         T val = interp(i, l, m, r);
574 
575                         // If we're running the second stage of intersecting 1D interpolation
576                         if (secondPass && isNewlySet(i, m)) {
577                             // Found an intersection, do not commit the previous points
578                             if (!detectedIntersections) {
579                                 //debug writefln("Intersection at %d, %d", i, m);
580                                 commitPoints.length = 0;
581                             }
582                             // Average out the point at the intersection
583                             set(i, m, (val + get(i, m)) * 0.5f);
584                             // From now on we're only computing intersection points
585                             detectedIntersections = true;
586                         }
587                         // If we've found no intersections so far, continue with normal
588                         // 1D interpolation.
589                         if (!detectedIntersections)
590                             set(i, m, val);
591                     }
592 
593                     // Look for the next pair
594                     l = r;
595                 }
596             }
597         }
598 
599         void extrapolateCorners() {
600             if (yCount <= 1 || xCount <= 1) return;
601 
602             void extrapolateCorner(uint baseX, uint baseY, uint offX, uint offY) {
603                 T base = values[baseX][baseY];
604                 T dX = values[baseX + offX][baseY] + (base * -1f);
605                 T dY = values[baseX][baseY + offY] + (base * -1f);
606                 values[baseX + offX][baseY + offY] = base + dX + dY;
607                 commitPoints ~= vec2u(baseX + offX, baseY + offY);
608             }
609 
610             foreach(x; 0..xCount - 1) {
611                 foreach(y; 0..yCount - 1) {
612                     if (valid[x][y] && valid[x + 1][y] && valid[x][y + 1] && !valid[x + 1][y + 1])
613                         extrapolateCorner(x, y, 1, 1);
614                     else if (valid[x][y] && valid[x + 1][y] && !valid[x][y + 1] && valid[x + 1][y + 1])
615                         extrapolateCorner(x + 1, y, -1, 1);
616                     else if (valid[x][y] && !valid[x + 1][y] && valid[x][y + 1] && valid[x + 1][y + 1])
617                         extrapolateCorner(x, y + 1, 1, -1);
618                     else if (!valid[x][y] && valid[x + 1][y] && valid[x][y + 1] && valid[x + 1][y + 1])
619                         extrapolateCorner(x + 1, y + 1, -1, -1);
620                 }
621             }
622         }
623 
624         void extendAndIntersect(bool secondPass) {
625             yMajor = secondPass;
626             bool detectedIntersections = false;
627 
628             void setOrAverage(uint maj, uint min, T val, float origin) {
629                 float minDist = abs(axisPoint(min) - origin);
630                 // Same logic as in interpolate1D2D
631                 if (secondPass && isNewlySet(maj, min)) {
632                     // Found an intersection, do not commit the previous points
633                     if (!detectedIntersections) {
634                         commitPoints.length = 0;
635                     }
636                     float majDist = getDistance(maj, min);
637                     float frac = minDist / (minDist + majDist * majDist / minDist);
638                     // Interpolate the point at the intersection
639                     set(maj, min, val * (1 - frac) + get(maj, min) * frac);
640                     // From now on we're only computing intersection points
641                     detectedIntersections = true;
642                 }
643                 // If we've found no intersections so far, continue with normal
644                 // 1D extension.
645                 if (!detectedIntersections) {
646                     set(maj, min, val, minDist);
647                 }
648             }
649 
650             foreach(i; 0..majorCnt()) {
651                 uint j;
652                 uint cnt = minorCnt();
653 
654                 // Find first element set
655                 for(j = 0; j < cnt && !isValid(i, j); j++) {}
656 
657                 // Empty row, we're done
658                 if (j >= cnt) continue;
659 
660                 // Replicate leftwards
661                 T val = get(i, j);
662                 float origin = axisPoint(j);
663                 foreach(k; 0..j)
664                     setOrAverage(i, k, val, origin);
665 
666                 // Find last element set
667                 for(j = cnt - 1; j < cnt && !isValid(i, j); j--) {}
668 
669                 // Replicate rightwards
670                 val = get(i, j);
671                 origin = axisPoint(j);
672                 foreach(k; (j + 1)..cnt)
673                     setOrAverage(i, k, val, origin);
674             }
675         }
676 
677         while (true) {
678             foreach(i; commitPoints) {
679                 assert(!valid[i.x][i.y], "trying to double-set a point");
680                 valid[i.x][i.y] = true;
681                 validCount++;
682             }
683             commitPoints.length = 0;
684 
685             // Are we done?
686             if (validCount == totalCount) break;
687 
688             // Reset the newlySet array
689             foreach(x; 0..xCount) {
690                 newlySet[x].length = 0;
691                 newlySet[x].length = yCount;
692             }
693 
694             // Try 1D interpolation in the X-Major direction
695             interpolate1D2D(false);
696             // Try 1D interpolation in the Y-Major direction, with intersection detection
697             // If this finds an intersection with the above, it will fall back to
698             // computing *only* the intersecting points as the average of the interpolated values.
699             // If that happens, the next loop will re-try normal 1D interpolation.
700             interpolate1D2D(true);
701             // Did we get work done? If so, commit and loop
702             if (commitPoints.length > 0) continue;
703 
704             // Now try corner extrapolation
705             extrapolateCorners();
706             // Did we get work done? If so, commit and loop
707             if (commitPoints.length > 0) continue;
708 
709             // Running out of options. Expand out points in both axes outwards, but if
710             // two expansions intersect then compute the average and commit only intersections.
711             // This works like interpolate1D2D, in two passes, one per axis, changing behavior
712             // once an intersection is detected.
713             extendAndIntersect(false);
714             extendAndIntersect(true);
715             // Did we get work done? If so, commit and loop
716             if (commitPoints.length > 0) continue;
717 
718             // Should never happen
719             break;
720         }
721 
722         // The above algorithm should be guaranteed to succeed in all cases.
723         enforce(validCount == totalCount, "Interpolation failed to complete");
724     }
725 
726     T interpolate(vec2u leftKeypoint, vec2 offset) {
727         switch (interpolateMode_) {
728             case InterpolateMode.Nearest:
729                 return interpolateNearest(leftKeypoint, offset);
730             case InterpolateMode.Linear:
731                 return interpolateLinear(leftKeypoint, offset);
732             case InterpolateMode.Cubic:
733                 return interpolateCubic(leftKeypoint, offset);
734             default: assert(0);
735         }
736     }
737 
738     T interpolateNearest(vec2u leftKeypoint, vec2 offset) {
739         ulong px = leftKeypoint.x + ((offset.x >= 0.5) ? 1 : 0);
740         if (parameter.isVec2) {
741             ulong py = leftKeypoint.y + ((offset.y >= 0.5) ? 1 : 0);
742             return values[px][py];
743         } else {
744             return values[px][0];
745         }
746     }
747 
748     T interpolateLinear(vec2u leftKeypoint, vec2 offset) {
749         T p0, p1;
750 
751         if (parameter.isVec2) {
752             T p00 = values[leftKeypoint.x][leftKeypoint.y];
753             T p01 = values[leftKeypoint.x][leftKeypoint.y + 1];
754             T p10 = values[leftKeypoint.x + 1][leftKeypoint.y];
755             T p11 = values[leftKeypoint.x + 1][leftKeypoint.y + 1];
756             p0 = p00.lerp(p01, offset.y);
757             p1 = p10.lerp(p11, offset.y);
758         } else {
759             p0 = values[leftKeypoint.x][0];
760             p1 = values[leftKeypoint.x + 1][0];
761         }
762 
763         return p0.lerp(p1, offset.x);
764     }
765 
766     T interpolateCubic(vec2u leftKeypoint, vec2 offset) {
767         T p0, p1, p2, p3;
768 
769         T bicubicInterp(vec2u left, float xt, float yt) {
770             T p01, p02, p03, p04;
771             T[4] pOut;
772 
773             size_t xlen = values.length-1;
774             size_t ylen = values[0].length-1;
775             ptrdiff_t xkp = cast(ptrdiff_t)leftKeypoint.x;
776             ptrdiff_t ykp = cast(ptrdiff_t)leftKeypoint.y;
777 
778             foreach(y; 0..4) {
779                 size_t yp = clamp(ykp+y-1, 0, ylen);
780 
781                 p01 = values[max(xkp-1, 0)][yp];
782                 p02 = values[xkp][yp];
783                 p03 = values[xkp+1][yp];  
784                 p04 = values[min(xkp+2, xlen)][yp];
785                 pOut[y] = cubic(p01, p02, p03, p04, xt);
786             }
787 
788             return cubic(pOut[0], pOut[1], pOut[2], pOut[3], yt);
789         }
790 
791         if (parameter.isVec2) {
792             return bicubicInterp(leftKeypoint, offset.x, offset.y);
793         } else {
794             ptrdiff_t xkp = cast(ptrdiff_t)leftKeypoint.x;
795             size_t xlen = values.length-1;
796 
797             p0 = values[max(xkp - 1, 0)][0];
798             p1 = values[xkp][0];
799             p2 = values[xkp + 1][0];     
800             p3 = values[min(xkp + 2, xlen)][0];
801             return cubic(p0, p1, p2, p3, offset.x);
802         }
803     }
804 
805     override
806     void apply(vec2u leftKeypoint, vec2 offset) {
807         applyToTarget(interpolate(leftKeypoint, offset));
808     }
809 
810     override
811     void insertKeypoints(uint axis, uint index) {
812         assert(axis == 0 || axis == 1);
813 
814         if (axis == 0) {
815             uint yCount = parameter.axisPointCount(1);
816 
817             values.insertInPlace(index, cast(T[])[]);
818             values[index].length = yCount;
819             isSet_.insertInPlace(index, cast(bool[])[]);
820             isSet_[index].length = yCount;
821         } else if (axis == 1) {
822             foreach(ref i; this.values) {
823                 i.insertInPlace(index, T.init);
824             }
825             foreach(ref i; this.isSet_) {
826                 i.insertInPlace(index, false);
827             }
828         }
829 
830         reInterpolate();
831     }
832 
833     override
834     void moveKeypoints(uint axis, uint oldindex, uint newindex) {
835         assert(axis == 0 || axis == 1);
836 
837         if (axis == 0) {
838             {
839                 auto swap = values[oldindex];
840                 values = values.remove(oldindex);
841                 values.insertInPlace(newindex, swap);
842             }
843 
844             {
845                 auto swap = isSet_[oldindex];
846                 isSet_ = isSet_.remove(oldindex);
847                 isSet_.insertInPlace(newindex, swap);
848             }
849         } else if (axis == 1) {
850             foreach(ref i; this.values) {
851                 {
852                     auto swap = i[oldindex];
853                     i = i.remove(oldindex);
854                     i.insertInPlace(newindex, swap);
855                 }
856             }
857             foreach(i; this.isSet_) {
858                 {
859                     auto swap = i[oldindex];
860                     i = i.remove(oldindex);
861                     i.insertInPlace(newindex, swap);
862                 }
863             }
864         }
865 
866         reInterpolate();
867     }
868 
869     override
870     void deleteKeypoints(uint axis, uint index) {
871         assert(axis == 0 || axis == 1);
872 
873         if (axis == 0) {
874             values = values.remove(index);
875             isSet_ = isSet_.remove(index);
876         } else if (axis == 1) {
877             foreach(i; 0..this.values.length) {
878                 values[i] = values[i].remove(index);
879             }
880             foreach(i; 0..this.isSet_.length) {
881                 isSet_[i] = isSet_[i].remove(index);
882             }
883         }
884 
885         reInterpolate();
886     }
887 
888     override void scaleValueAt(vec2u index, int axis, float scale)
889     {
890         /* Default to just scalar scale */
891         setValue(index, getValue(index) * scale);
892     }
893 
894     override void extrapolateValueAt(vec2u index, int axis)
895     {
896         vec2 offset = parameter.getKeypointOffset(index);
897 
898         switch (axis) {
899             case -1: offset = vec2(1, 1) - offset; break;
900             case 0: offset.x = 1 - offset.x; break;
901             case 1: offset.y = 1 - offset.y; break;
902             default: assert(false, "bad axis");
903         }
904 
905         vec2u srcIndex;
906         vec2 subOffset;
907         parameter.findOffset(offset, srcIndex, subOffset);
908 
909         T srcVal = interpolate(srcIndex, subOffset);
910 
911         setValue(index, srcVal);
912         scaleValueAt(index, axis, -1);
913     }
914 
915     override void copyKeypointToBinding(vec2u src, ParameterBinding other, vec2u dest)
916     {
917         if (!isSet(src)) {
918             other.unset(dest);
919         } else if (auto o = cast(ParameterBindingImpl!T)(other)) {
920             o.setValue(dest, getValue(src));
921         } else {
922             assert(false, "ParameterBinding class mismatch");
923         }
924     }
925 
926     override void swapKeypointWithBinding(vec2u src, ParameterBinding other, vec2u dest)
927     {
928         if (auto o = cast(ParameterBindingImpl!T)(other)) {
929             bool thisSet = isSet(src);
930             bool otherSet = other.isSet(dest);
931             T thisVal = getValue(src);
932             T otherVal = o.getValue(dest);
933 
934             // Swap directly, to avoid clobbering by update
935             o.values[dest.x][dest.y] = thisVal;
936             o.isSet_[dest.x][dest.y] = thisSet;
937             values[src.x][src.y] = otherVal;
938             isSet_[src.x][src.y] = otherSet;
939 
940             reInterpolate();
941             o.reInterpolate();
942         } else {
943             assert(false, "ParameterBinding class mismatch");
944         }
945     }
946 
947     /**
948         Get the interpolation mode
949     */
950     override InterpolateMode interpolateMode() {
951         return interpolateMode_;
952     }
953 
954     /**
955         Set the interpolation mode
956     */
957     override void interpolateMode(InterpolateMode mode) {
958         interpolateMode_ = mode;
959     }
960 
961     /**
962         Apply parameter to target node
963     */
964     abstract void applyToTarget(T value);
965 }
966 
967 class ValueParameterBinding : ParameterBindingImpl!float {
968     this(Parameter parameter) {
969         super(parameter);
970     }
971 
972     this(Parameter parameter, Node targetNode, string paramName) {
973         super(parameter, targetNode, paramName);
974     }
975 
976     override
977     void applyToTarget(float value) {
978         target.node.setValue(target.paramName, value);
979     }
980 
981     override
982     void clearValue(ref float val) {
983         val = target.node.getDefaultValue(target.paramName);
984     }
985 
986     override void scaleValueAt(vec2u index, int axis, float scale)
987     {
988         /* Nodes know how to do axis-aware scaling */
989         setValue(index, target.node.scaleValue(target.paramName, getValue(index), axis, scale));
990     }
991 
992     override bool isCompatibleWithNode(Node other)
993     {
994         return other.hasParam(this.target.paramName);
995     }
996 }
997 
998 class DeformationParameterBinding : ParameterBindingImpl!Deformation {
999     this(Parameter parameter) {
1000         super(parameter);
1001     }
1002 
1003     this(Parameter parameter, Node targetNode, string paramName) {
1004         super(parameter, targetNode, paramName);
1005     }
1006 
1007     void update(vec2u point, vec2[] offsets) {
1008         this.isSet_[point.x][point.y] = true;
1009         this.values[point.x][point.y].vertexOffsets = offsets.dup;
1010         this.reInterpolate();
1011     }
1012 
1013     override
1014     void applyToTarget(Deformation value) {
1015         enforce(this.target.paramName == "deform");
1016 
1017         if (Drawable d = cast(Drawable)target.node) {
1018             d.deformStack.push(value);
1019         }
1020     }
1021 
1022     override
1023     void clearValue(ref Deformation val) {
1024         // Reset deformation to identity, with the right vertex count
1025         if (Drawable d = cast(Drawable)target.node) {
1026             val.vertexOffsets.length = d.vertices.length;
1027             foreach(i; 0..d.vertices.length) {
1028                 val.vertexOffsets[i] = vec2(0);
1029             }
1030         }
1031     }
1032 
1033     override
1034     void scaleValueAt(vec2u index, int axis, float scale)
1035     {
1036         vec2 vecScale;
1037 
1038         switch (axis) {
1039             case -1: vecScale = vec2(scale, scale); break;
1040             case 0: vecScale = vec2(scale, 1); break;
1041             case 1: vecScale = vec2(1, scale); break;
1042             default: assert(false, "Bad axis");
1043         }
1044 
1045         /* Default to just scalar scale */
1046         setValue(index, getValue(index) * vecScale);
1047     }
1048 
1049     override bool isCompatibleWithNode(Node other)
1050     {
1051         if (Drawable d = cast(Drawable)target.node) {
1052             if (Drawable o = cast(Drawable)other) {
1053                 return d.vertices.length == o.vertices.length;
1054             } else {
1055                 return false;
1056             }
1057         } else {
1058             return false;
1059         }
1060     }
1061 }
1062 
1063 @("TestInterpolation")
1064 unittest {
1065     void printArray(float[][] arr) {
1066         foreach(row; arr) {
1067             writefln(" %s", row);
1068         }
1069     }
1070 
1071     void runTest(float[][] input, float[][] expect, float[][2] axisPoints, string description) {
1072         Parameter param = new Parameter();
1073         param.axisPoints = axisPoints;
1074 
1075         ValueParameterBinding bind = new ValueParameterBinding(param);
1076 
1077         // Assign values to ValueParameterBinding and consider NaN as !isSet_
1078         bind.values = input;
1079         bind.isSet_.length = input.length;
1080         foreach(x; 0..input.length) {
1081             bind.isSet_[x].length = input[0].length;
1082             foreach(y; 0..input[0].length) {
1083                 bind.isSet_[x][y] = !isNaN(input[x][y]);
1084             }
1085         }
1086 
1087         // Run the interpolation
1088         bind.reInterpolate();
1089 
1090         // Check results with a fudge factor for rounding error
1091         const float epsilon = 0.0001;
1092         foreach(x; 0..bind.values.length) {
1093             foreach(y; 0..bind.values[0].length) {
1094                 float delta = abs(expect[x][y] - bind.values[x][y]);
1095                 if (isNaN(delta) || delta > epsilon) {
1096                     debug writefln("Output mismatch at %d, %d", x, y);
1097                     debug writeln("Expected:");
1098                     printArray(expect);
1099                     debug writeln("Output:");
1100                     printArray(bind.values);
1101                     assert(false, description);
1102                 }
1103             }
1104         }
1105     }
1106 
1107     void runTestUniform(float[][] input, float[][] expect, string description) {
1108         float[][2] axisPoints = [[0], [0]];
1109 
1110         // Initialize axisPoints as uniformly spaced
1111         axisPoints[0].length = input.length;
1112         axisPoints[1].length = input[0].length;
1113         if (input.length > 1) {
1114             foreach(x; 0..input.length) {
1115                 axisPoints[0][x] = x / cast(float)(input.length - 1);
1116             }
1117         }
1118         if (input[0].length > 1) {
1119             foreach(y; 0..input[0].length) {
1120 
1121                 axisPoints[1][y] = y / cast(float)(input[0].length - 1);
1122             }
1123         }
1124 
1125         runTest(input, expect, axisPoints, description);
1126     }
1127 
1128     float x = float.init;
1129 
1130     runTestUniform(
1131         [[1f], [ x], [ x], [4f]],
1132         [[1f], [2f], [3f], [4f]],
1133         "1d-uniform-interpolation"
1134     );
1135 
1136     runTest(
1137         [[0f], [ x], [ x], [4f]],
1138         [[0f], [1f], [3f], [4f]],
1139         [[0f, 0.25f, 0.75f, 1f], [0f]],
1140         "1d-nonuniform-interpolation"
1141     );
1142 
1143     runTestUniform(
1144         [
1145             [ 4,  x,  x, 10],
1146             [ x,  x,  x,  x],
1147             [ x,  x,  x,  x],
1148             [ 1,  x,  x,  7]
1149         ],
1150         [
1151             [ 4,  6,  8, 10],
1152             [ 3,  5,  7,  9],
1153             [ 2,  4,  6,  8],
1154             [ 1,  3,  5,  7]
1155         ],
1156         "square-interpolation"
1157     );
1158 
1159     runTestUniform(
1160         [
1161             [ 4,  x,  x,  x],
1162             [ x,  x,  x,  x],
1163             [ x,  x,  x,  x],
1164             [ 1,  x,  x,  7]
1165         ],
1166         [
1167             [ 4,  6,  8, 10],
1168             [ 3,  5,  7,  9],
1169             [ 2,  4,  6,  8],
1170             [ 1,  3,  5,  7]
1171         ],
1172         "corner-extrapolation"
1173     );
1174 
1175     runTestUniform(
1176         [
1177             [ 9,  x,  x,  0],
1178             [ x,  x,  x,  x],
1179             [ x,  x,  x,  x],
1180             [ 0,  x,  x,  9]
1181         ],
1182         [
1183             [ 9,  6,  3,  0],
1184             [ 6,  5,  4,  3],
1185             [ 3,  4,  5,  6],
1186             [ 0,  3,  6,  9]
1187         ],
1188         "cross-interpolation"
1189     );
1190 
1191     runTestUniform(
1192         [
1193             [ x,  x,  2,  x,  x],
1194             [ x,  x,  x,  x,  x],
1195             [ 0,  x,  x,  x,  4],
1196             [ x,  x,  x,  x,  x],
1197             [ x,  x, 10,  x,  x]
1198         ],
1199         [
1200             [-2,  0,  2,  2,  2],
1201             [-1,  1,  3,  3,  3],
1202             [ 0,  2,  4,  4,  4],
1203             [ 3,  5,  7,  7,  7],
1204             [ 6,  8, 10, 10, 10]
1205         ],
1206         "diamond-interpolation"
1207     );
1208 
1209     runTestUniform(
1210         [
1211             [ x,  x,  x,  x],
1212             [ x,  3,  4,  x],
1213             [ x,  1,  2,  x],
1214             [ x,  x,  x,  x]
1215         ],
1216         [
1217             [ 3,  3,  4,  4],
1218             [ 3,  3,  4,  4],
1219             [ 1,  1,  2,  2],
1220             [ 1,  1,  2,  2]
1221         ],
1222         "edge-expansion"
1223     );
1224 
1225     runTestUniform(
1226         [
1227             [ x,  x,  x,  x],
1228             [ x,  x,  4,  x],
1229             [ x,  x,  x,  x],
1230             [ 0,  x,  x,  x]
1231         ],
1232         [
1233             [ 2,  3,  4,  4],
1234             [ 2,  3,  4,  4],
1235             [ 1,  2,  3,  3],
1236             [ 0,  1,  2,  2]
1237         ],
1238         "intersecting-expansion"
1239     );
1240 
1241     runTestUniform(
1242         [
1243             [ x,  5,  x],
1244             [ x,  x,  x],
1245             [ 0,  x,  x]
1246         ],
1247         [
1248             [ 4,  5,  5],
1249             [ 2,  3,  3],
1250             [ 0,  1,  1]
1251         ],
1252         "nondiagonal-gradient"
1253     );
1254 }