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