1 /* 2 Copyright © 2022, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: 6 - Luna Nielsen 7 - Asahi Lina 8 */ 9 module inochi2d.core.param; 10 import inochi2d.fmt.serialize; 11 import inochi2d.math.serialization; 12 import inochi2d.core; 13 import inochi2d.math; 14 import std.exception; 15 import std.array; 16 import std.algorithm.mutation; 17 import std.stdio; 18 19 public import inochi2d.core.param.binding; 20 21 enum ParamMergeMode { 22 23 /** 24 Parameters are merged additively 25 */ 26 @serdeFlexible 27 @serdeKeys("Additive", "additive") 28 Additive, 29 30 /** 31 Parameters are merged with a weighted average 32 */ 33 @serdeFlexible 34 @serdeKeys("Weighted", "weighted") 35 Weighted, 36 37 /** 38 Parameters are merged multiplicatively 39 */ 40 @serdeFlexible 41 @serdeKeys("Multiplicative", "multiplicative") 42 Multiplicative, 43 44 /** 45 Forces parameter to be given value 46 */ 47 @serdeFlexible 48 @serdeKeys("Forced", "forced") 49 Forced, 50 51 /** 52 Merge mode is passthrough 53 */ 54 @serdeFlexible 55 @serdeKeys("Passthrough", "passthrough") 56 Passthrough, 57 } 58 59 /** 60 A parameter 61 */ 62 class Parameter { 63 private: 64 struct Combinator { 65 vec2[] ivalues; 66 float[] iweights; 67 int isum; 68 69 void clear() { 70 isum = 0; 71 } 72 73 void resize(int reqLength) { 74 ivalues.length = reqLength; 75 iweights.length = reqLength; 76 } 77 78 void add(vec2 value, float weight) { 79 if (isum >= ivalues.length) resize(isum+8); 80 81 ivalues[isum] = value; 82 iweights[isum] = weight; 83 isum++; 84 } 85 86 void add(int axis, float value, float weight) { 87 if (isum >= ivalues.length) resize(isum+8); 88 89 ivalues[isum] = vec2(axis == 0 ? value : 1, axis == 1 ? value : 1); 90 iweights[isum] = weight; 91 isum++; 92 } 93 94 vec2 csum() { 95 vec2 val = vec2(0, 0); 96 foreach(i; 0..isum) { 97 val += ivalues[i]; 98 } 99 return val; 100 } 101 102 vec2 avg() { 103 if (isum == 0) return vec2(1, 1); 104 105 vec2 val = vec2(0, 0); 106 foreach(i; 0..isum) { 107 val += ivalues[i]*iweights[i]; 108 } 109 return val/isum; 110 } 111 } 112 113 Combinator iadd; 114 Combinator imul; 115 protected: 116 void serializeSelf(ref InochiSerializer serializer) { 117 serializer.putKey("uuid"); 118 serializer.putValue(uuid); 119 serializer.putKey("name"); 120 serializer.putValue(name); 121 serializer.putKey("is_vec2"); 122 serializer.putValue(isVec2); 123 serializer.putKey("min"); 124 min.serialize(serializer); 125 serializer.putKey("max"); 126 max.serialize(serializer); 127 serializer.putKey("defaults"); 128 defaults.serialize(serializer); 129 serializer.putKey("axis_points"); 130 serializer.serializeValue(axisPoints); 131 serializer.putKey("merge_mode"); 132 serializer.serializeValue(mergeMode); 133 serializer.putKey("bindings"); 134 auto arrstate = serializer.arrayBegin(); 135 foreach(binding; bindings) { 136 serializer.elemBegin(); 137 binding.serializeSelf(serializer); 138 } 139 serializer.arrayEnd(arrstate); 140 } 141 142 public: 143 /** 144 Unique ID of parameter 145 */ 146 uint uuid; 147 148 /** 149 Name of the parameter 150 */ 151 string name; 152 153 /** 154 Optimized indexable name generated at runtime 155 156 DO NOT SERIALIZE THIS. 157 */ 158 string indexableName; 159 160 /** 161 Whether this parameter updates the model 162 */ 163 bool active = true; 164 165 /** 166 The current parameter value 167 */ 168 vec2 value = vec2(0); 169 170 /** 171 The previous internal value offset 172 */ 173 vec2 lastInternal = vec2(0); 174 175 /** 176 Parameter merge mode 177 */ 178 ParamMergeMode mergeMode; 179 180 /** 181 The default value 182 */ 183 vec2 defaults = vec2(0); 184 185 /** 186 Whether the parameter is 2D 187 */ 188 bool isVec2; 189 190 /** 191 The parameter's minimum bounds 192 */ 193 vec2 min = vec2(0, 0); 194 195 /** 196 The parameter's maximum bounds 197 */ 198 vec2 max = vec2(1, 1); 199 200 /** 201 Position of the keypoints along each axis 202 */ 203 float[][2] axisPoints = [[0, 1], [0, 1]]; 204 205 /** 206 Binding to targets 207 */ 208 ParameterBinding[] bindings; 209 210 /** 211 Gets the value normalized to the internal range (0.0->1.0) 212 */ 213 vec2 normalizedValue() { 214 return this.mapValue(value); 215 } 216 217 /** 218 Sets the value normalized up from the internal range (0.0->1.0) 219 to the user defined range. 220 */ 221 void normalizedValue(vec2 value) { 222 this.value = vec2( 223 value.x * (max.x-min.x) + min.x, 224 value.y * (max.y-min.y) + min.y 225 ); 226 } 227 228 /** 229 For serialization 230 */ 231 this() { } 232 233 /** 234 Unload UUID on clear 235 */ 236 ~this() { 237 inUnloadUUID(this.uuid); 238 } 239 240 /** 241 Create new parameter 242 */ 243 this(string name, bool isVec2) { 244 this.uuid = inCreateUUID(); 245 this.name = name; 246 this.isVec2 = isVec2; 247 if (!isVec2) 248 axisPoints[1] = [0]; 249 250 this.makeIndexable(); 251 } 252 253 /** 254 Clone this parameter 255 */ 256 Parameter dup() { 257 Parameter newParam = new Parameter(name ~ " (Copy)", isVec2); 258 259 newParam.min = min; 260 newParam.max = max; 261 newParam.axisPoints = axisPoints.dup; 262 263 foreach(binding; bindings) { 264 ParameterBinding newBinding = newParam.createBinding( 265 binding.getNode(), 266 binding.getName(), 267 false 268 ); 269 newBinding.interpolateMode = binding.interpolateMode; 270 foreach(x; 0..axisPointCount(0)) { 271 foreach(y; 0..axisPointCount(1)) { 272 binding.copyKeypointToBinding(vec2u(x, y), newBinding, vec2u(x, y)); 273 } 274 } 275 newParam.addBinding(newBinding); 276 } 277 278 return newParam; 279 } 280 281 /** 282 Serializes a parameter 283 */ 284 void serialize(ref InochiSerializer serializer) { 285 auto state = serializer.objectBegin; 286 serializeSelf(serializer); 287 serializer.objectEnd(state); 288 } 289 290 /** 291 Deserializes a parameter 292 */ 293 SerdeException deserializeFromFghj(Fghj data) { 294 data["uuid"].deserializeValue(this.uuid); 295 data["name"].deserializeValue(this.name); 296 if (!data["is_vec2"].isEmpty) data["is_vec2"].deserializeValue(this.isVec2); 297 if (!data["min"].isEmpty) min.deserialize(data["min"]); 298 if (!data["max"].isEmpty) max.deserialize(data["max"]); 299 if (!data["axis_points"].isEmpty) data["axis_points"].deserializeValue(this.axisPoints); 300 if (!data["defaults"].isEmpty) defaults.deserialize(data["defaults"]); 301 if (!data["merge_mode"].isEmpty) data["merge_mode"].deserializeValue(this.mergeMode); 302 303 if (!data["bindings"].isEmpty) { 304 foreach(Fghj child; data["bindings"].byElement) { 305 306 // Skip empty children 307 if (child["param_name"].isEmpty) continue; 308 309 string paramName; 310 child["param_name"].deserializeValue(paramName); 311 312 if (paramName == "deform") { 313 auto binding = new DeformationParameterBinding(this); 314 binding.deserializeFromFghj(child); 315 bindings ~= binding; 316 } else { 317 auto binding = new ValueParameterBinding(this); 318 binding.deserializeFromFghj(child); 319 bindings ~= binding; 320 } 321 } 322 } 323 324 return null; 325 } 326 327 void reconstruct(Puppet puppet) { 328 foreach(i, binding; bindings) { 329 binding.reconstruct(puppet); 330 } 331 } 332 333 /** 334 Finalize loading of parameter 335 */ 336 void finalize(Puppet puppet) { 337 this.makeIndexable(); 338 this.value = defaults; 339 340 ParameterBinding[] validBindingList; 341 foreach(i, binding; bindings) { 342 if (puppet.find!Node(binding.getNodeUUID())) { 343 binding.finalize(puppet); 344 validBindingList ~= binding; 345 } 346 } 347 348 bindings = validBindingList; 349 } 350 351 void findOffset(vec2 offset, out vec2u index, out vec2 outOffset) { 352 void interpAxis(uint axis, float val, out uint index, out float offset) { 353 float[] pos = axisPoints[axis]; 354 355 foreach(i; 0..pos.length - 1) { 356 if (pos[i + 1] > val || i == (pos.length - 2)) { 357 index = cast(uint)i; 358 offset = (val - pos[i]) / (pos[i + 1] - pos[i]); 359 return; 360 } 361 } 362 } 363 364 interpAxis(0, offset.x, index.x, outOffset.x); 365 if (isVec2) interpAxis(1, offset.y, index.y, outOffset.y); 366 } 367 368 void update() { 369 vec2u index; 370 vec2 offset_; 371 372 if (!active) 373 return; 374 375 lastInternal = (value + iadd.csum()) * imul.avg(); 376 377 findOffset(this.mapValue(lastInternal), index, offset_); 378 foreach(binding; bindings) { 379 binding.apply(index, offset_); 380 } 381 382 // Reset combinatorics 383 iadd.clear(); 384 imul.clear(); 385 } 386 387 void pushIOffset(vec2 offset, ParamMergeMode mode = ParamMergeMode.Passthrough, float weight=1) { 388 if (mode == ParamMergeMode.Passthrough) mode = mergeMode; 389 switch(mode) { 390 case ParamMergeMode.Forced: 391 this.value = offset; 392 break; 393 case ParamMergeMode.Additive: 394 iadd.add(offset, 1); 395 break; 396 case ParamMergeMode.Multiplicative: 397 imul.add(offset, 1); 398 break; 399 case ParamMergeMode.Weighted: 400 imul.add(offset, weight); 401 break; 402 default: break; 403 } 404 } 405 406 void pushIOffsetAxis(int axis, float offset, ParamMergeMode mode = ParamMergeMode.Passthrough, float weight=1) { 407 if (mode == ParamMergeMode.Passthrough) mode = mergeMode; 408 switch(mode) { 409 case ParamMergeMode.Forced: 410 this.value.vector[axis] = offset; 411 break; 412 case ParamMergeMode.Additive: 413 iadd.add(axis, offset, 1); 414 break; 415 case ParamMergeMode.Multiplicative: 416 imul.add(axis, offset, 1); 417 break; 418 case ParamMergeMode.Weighted: 419 imul.add(axis, offset, weight); 420 break; 421 default: break; 422 } 423 } 424 425 /** 426 Get number of points for an axis 427 */ 428 uint axisPointCount(uint axis = 0) { 429 return cast(uint)axisPoints[axis].length; 430 } 431 432 /** 433 Move an axis point to a new offset 434 */ 435 void moveAxisPoint(uint axis, uint oldidx, float newoff) { 436 assert(oldidx > 0 && oldidx < this.axisPointCount(axis)-1, "invalid point index"); 437 assert(newoff > 0 && newoff < 1, "offset out of bounds"); 438 if (isVec2) 439 assert(axis <= 1, "bad axis"); 440 else 441 assert(axis == 0, "bad axis"); 442 443 // Find the index at which to insert 444 uint index; 445 for(index = 1; index < axisPoints[axis].length; index++) { 446 if (axisPoints[axis][index+1] > newoff) 447 break; 448 } 449 450 if (oldidx != index) { 451 // BUG: Apparently deleting the oldindex and replacing it with newindex causes a crash. 452 453 // Insert it into the new position in the list 454 auto swap = axisPoints[oldidx]; 455 axisPoints[axis] = axisPoints[axis].remove(oldidx); 456 axisPoints[axis].insertInPlace(index, swap); 457 debug writeln("after move ", this.axisPointCount(0)); 458 } 459 460 // Tell all bindings to reinterpolate 461 foreach(binding; bindings) { 462 binding.moveKeypoints(axis, oldidx, index); 463 } 464 } 465 466 /** 467 Add a new axis point at the given offset 468 */ 469 void insertAxisPoint(uint axis, float off) { 470 assert(off > 0 && off < 1, "offset out of bounds"); 471 if (isVec2) 472 assert(axis <= 1, "bad axis"); 473 else 474 assert(axis == 0, "bad axis"); 475 476 // Find the index at which to insert 477 uint index; 478 for(index = 1; index < axisPoints[axis].length; index++) { 479 if (axisPoints[axis][index] > off) 480 break; 481 } 482 483 // Insert it into the position list 484 axisPoints[axis].insertInPlace(index, off); 485 486 // Tell all bindings to insert space into their arrays 487 foreach(binding; bindings) { 488 binding.insertKeypoints(axis, index); 489 } 490 } 491 492 /** 493 Delete a specified axis point by index 494 */ 495 void deleteAxisPoint(uint axis, uint index) { 496 if (isVec2) 497 assert(axis <= 1, "bad axis"); 498 else 499 assert(axis == 0, "bad axis"); 500 501 assert(index > 0, "cannot delete axis point at 0"); 502 assert(index < (axisPoints[axis].length - 1), "cannot delete axis point at 1"); 503 504 // Remove the keypoint 505 axisPoints[axis] = axisPoints[axis].remove(index); 506 507 // Tell all bindings to remove it from their arrays 508 foreach(binding; bindings) { 509 binding.deleteKeypoints(axis, index); 510 } 511 } 512 513 /** 514 Flip the mapping across an axis 515 */ 516 void reverseAxis(uint axis) { 517 axisPoints[axis].reverse(); 518 foreach(ref i; axisPoints[axis]) { 519 i = 1 - i; 520 } 521 foreach(binding; bindings) { 522 binding.reverseAxis(axis); 523 } 524 } 525 526 /** 527 Get the offset (0..1) of a specified keypoint index 528 */ 529 vec2 getKeypointOffset(vec2u index) { 530 return vec2(axisPoints[0][index.x], axisPoints[1][index.y]); 531 } 532 533 /** 534 Get the value at a specified keypoint index 535 */ 536 vec2 getKeypointValue(vec2u index) { 537 return unmapValue(getKeypointOffset(index)); 538 } 539 540 /** 541 Maps an input value to an offset (0.0->1.0) 542 */ 543 vec2 mapValue(vec2 value) { 544 vec2 range = max - min; 545 vec2 tmp = (value - min); 546 vec2 off = vec2(tmp.x / range.x, tmp.y / range.y); 547 548 vec2 clamped = vec2( 549 clamp(off.x, 0, 1), 550 clamp(off.y, 0, 1), 551 ); 552 return clamped; 553 } 554 555 /** 556 Maps an offset (0.0->1.0) to a value 557 */ 558 vec2 unmapValue(vec2 offset) { 559 vec2 range = max - min; 560 return vec2(range.x * offset.x, range.y * offset.y) + min; 561 } 562 563 /** 564 Maps an input value to an offset (0.0->1.0) for an axis 565 */ 566 float mapAxis(uint axis, float value) { 567 vec2 input = min; 568 if (axis == 0) input.x = value; 569 else input.y = value; 570 vec2 output = mapValue(input); 571 if (axis == 0) return output.x; 572 else return output.y; 573 } 574 575 /** 576 Maps an internal value (0.0->1.0) to the input range for an axis 577 */ 578 float unmapAxis(uint axis, float offset) { 579 vec2 input = min; 580 if (axis == 0) input.x = offset; 581 else input.y = offset; 582 vec2 output = unmapValue(input); 583 if (axis == 0) return output.x; 584 else return output.y; 585 } 586 587 /** 588 Gets the axis point closest to a given offset 589 */ 590 uint getClosestAxisPointIndex(uint axis, float offset) { 591 uint closestPoint = 0; 592 float closestDist = float.infinity; 593 594 foreach(i, pointVal; axisPoints[axis]) { 595 float dist = abs(pointVal - offset); 596 if (dist < closestDist) { 597 closestDist = dist; 598 closestPoint = cast(uint)i; 599 } 600 } 601 602 return closestPoint; 603 } 604 605 /** 606 Find the keypoint closest to the current value 607 */ 608 vec2u findClosestKeypoint() { 609 return findClosestKeypoint(value); 610 } 611 612 /** 613 Find the keypoint closest to a value 614 */ 615 vec2u findClosestKeypoint(vec2 value) { 616 vec2 mapped = mapValue(value); 617 uint x = getClosestAxisPointIndex(0, mapped.x); 618 uint y = getClosestAxisPointIndex(1, mapped.y); 619 620 return vec2u(x, y); 621 } 622 623 /** 624 Find the keypoint closest to the current value 625 */ 626 vec2 getClosestKeypointValue() { 627 return getKeypointValue(findClosestKeypoint()); 628 } 629 630 /** 631 Find the keypoint closest to a value 632 */ 633 vec2 getClosestKeypointValue(vec2 value) { 634 return getKeypointValue(findClosestKeypoint(value)); 635 } 636 637 /** 638 Find a binding by node ref and name 639 */ 640 ParameterBinding getBinding(Node n, string bindingName) { 641 foreach(ref binding; bindings) { 642 if (binding.getNode() != n) continue; 643 if (binding.getName == bindingName) return binding; 644 } 645 return null; 646 } 647 648 /** 649 Check if a binding exists for a given node and name 650 */ 651 bool hasBinding(Node n, string bindingName) { 652 foreach(ref binding; bindings) { 653 if (binding.getNode() != n) continue; 654 if (binding.getName == bindingName) return true; 655 } 656 return false; 657 } 658 659 /** 660 Check if any bindings exists for a given node 661 */ 662 bool hasAnyBinding(Node n) { 663 foreach(ref binding; bindings) { 664 if (binding.getNode() == n) return true; 665 } 666 return false; 667 } 668 669 /** 670 Create a new binding (without adding it) for a given node and name 671 */ 672 ParameterBinding createBinding(Node n, string bindingName, bool setZero = true) { 673 ParameterBinding b; 674 if (bindingName == "deform") { 675 b = new DeformationParameterBinding(this, n, bindingName); 676 } else { 677 b = new ValueParameterBinding(this, n, bindingName); 678 } 679 680 if (setZero) { 681 vec2u zeroIndex = findClosestKeypoint(vec2(0, 0)); 682 vec2 zero = getKeypointValue(zeroIndex); 683 if (abs(zero.x) < 0.001 && abs(zero.y) < 0.001) b.reset(zeroIndex); 684 } 685 686 return b; 687 } 688 689 /** 690 Find a binding if it exists, or create and add a new one, and return it 691 */ 692 ParameterBinding getOrAddBinding(Node n, string bindingName, bool setZero = true) { 693 ParameterBinding binding = getBinding(n, bindingName); 694 if (binding is null) { 695 binding = createBinding(n, bindingName, setZero); 696 addBinding(binding); 697 } 698 return binding; 699 } 700 701 /** 702 Add a new binding (must not exist) 703 */ 704 void addBinding(ParameterBinding binding) { 705 assert(!hasBinding(binding.getNode, binding.getName)); 706 bindings ~= binding; 707 } 708 709 /** 710 Remove an existing binding by ref 711 */ 712 void removeBinding(ParameterBinding binding) { 713 import std.algorithm.searching : countUntil; 714 import std.algorithm.mutation : remove; 715 ptrdiff_t idx = bindings.countUntil(binding); 716 if (idx >= 0) { 717 bindings = bindings.remove(idx); 718 } 719 } 720 721 void makeIndexable() { 722 import std.uni : toLower; 723 indexableName = name.toLower; 724 } 725 } 726 727 private { 728 Parameter delegate(Fghj) createFunc; 729 } 730 731 Parameter inParameterCreate(Fghj data) { 732 return createFunc(data); 733 } 734 735 void inParameterSetFactory(Parameter delegate(Fghj) createFunc_) { 736 createFunc = createFunc_; 737 }