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 }