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 }