1 /* 2 Inochi2D Node 3 4 Copyright © 2020, Inochi2D Project 5 Distributed under the 2-Clause BSD License, see LICENSE file. 6 7 Authors: Luna Nielsen 8 */ 9 module inochi2d.core.nodes; 10 import inochi2d.math; 11 import inochi2d.fmt.serialize; 12 import inochi2d.math.serialization; 13 import inochi2d.core.dbg; 14 import inochi2d.core; 15 16 public import inochi2d.core.nodes.part; 17 public import inochi2d.core.nodes.mask; 18 public import inochi2d.core.nodes.pathdeform; 19 public import inochi2d.core.nodes.drawable; 20 public import inochi2d.core.nodes.composite; 21 public import inochi2d.core.nodes.drivers; 22 //public import inochi2d.core.nodes.shapes; // This isn't mainline yet! 23 24 import std.exception; 25 26 private { 27 uint[] takenUUIDs; 28 } 29 30 package(inochi2d) { 31 void inInitNodes() { 32 inRegisterNodeType!Node; 33 } 34 } 35 36 enum InInvalidUUID = uint.max; 37 38 /** 39 Creates a new UUID for a node 40 */ 41 uint inCreateUUID() { 42 import std.algorithm.searching : canFind; 43 import std.random : uniform; 44 45 uint id = uniform(uint.min, InInvalidUUID); 46 while (takenUUIDs.canFind(id)) { id = uniform!uint(); } // Make sure the ID is actually unique in the current context 47 48 return id; 49 } 50 51 /** 52 Unloads a single UUID from the internal listing, freeing it up for reuse 53 */ 54 void inUnloadUUID(uint id) { 55 import std.algorithm.searching : countUntil; 56 import std.algorithm.mutation : remove; 57 ptrdiff_t idx = takenUUIDs.countUntil(id); 58 if (idx != -1) takenUUIDs.remove(idx); 59 } 60 61 /** 62 Clears all UUIDs from the internal listing 63 */ 64 void inClearUUIDs() { 65 takenUUIDs.length = 0; 66 } 67 68 /** 69 A node in the Inochi2D rendering tree 70 */ 71 @TypeId("Node") 72 class Node : ISerializable { 73 private: 74 this() { } 75 76 @Ignore 77 Puppet puppet_; 78 79 @Ignore 80 Node parent_; 81 82 @Ignore 83 Node[] children_; 84 85 @Ignore 86 uint uuid_; 87 88 @Name("zsort") 89 float zsort_ = 0; 90 91 @Name("lockToRoot") 92 bool lockToRoot_; 93 94 @Ignore 95 string nodePath_; 96 97 protected: 98 99 /** 100 The offset to the transform to apply 101 */ 102 Transform offsetTransform; 103 104 /** 105 The offset to apply to sorting 106 */ 107 float offsetSort = 0f; 108 109 // Send mask reset request one node up 110 void resetMask() { 111 if (parent !is null) parent.resetMask(); 112 } 113 114 void serializeSelf(ref InochiSerializer serializer) { 115 116 serializer.putKey("uuid"); 117 serializer.putValue(uuid); 118 119 serializer.putKey("name"); 120 serializer.putValue(name); 121 122 serializer.putKey("type"); 123 serializer.putValue(typeId); 124 125 serializer.putKey("enabled"); 126 serializer.putValue(enabled); 127 128 serializer.putKey("zsort"); 129 serializer.putValue(zsort_); 130 131 serializer.putKey("transform"); 132 serializer.serializeValue(this.localTransform); 133 134 serializer.putKey("lockToRoot"); 135 serializer.serializeValue(this.lockToRoot_); 136 137 if (children.length > 0) { 138 serializer.putKey("children"); 139 auto childArray = serializer.arrayBegin(); 140 foreach(child; children) { 141 serializer.elemBegin; 142 serializer.serializeValue(child); 143 } 144 serializer.arrayEnd(childArray); 145 } 146 } 147 148 void serializeSelf(ref InochiSerializerCompact serializer) { 149 serializer.putKey("uuid"); 150 serializer.putValue(uuid); 151 152 serializer.putKey("name"); 153 serializer.putValue(name); 154 155 serializer.putKey("type"); 156 serializer.putValue(typeId); 157 158 serializer.putKey("enabled"); 159 serializer.putValue(enabled); 160 161 serializer.putKey("zsort"); 162 serializer.putValue(zsort_); 163 164 serializer.putKey("transform"); 165 serializer.serializeValue(this.localTransform); 166 167 serializer.putKey("lockToRoot"); 168 serializer.serializeValue(this.lockToRoot_); 169 170 if (children.length > 0) { 171 serializer.putKey("children"); 172 auto childArray = serializer.arrayBegin(); 173 foreach(child; children) { 174 serializer.elemBegin; 175 serializer.serializeValue(child); 176 } 177 serializer.arrayEnd(childArray); 178 } 179 } 180 181 package(inochi2d): 182 183 /** 184 Needed for deserialization 185 */ 186 void setPuppet(Puppet puppet) { 187 this.puppet_ = puppet; 188 } 189 190 public: 191 192 /** 193 Whether the node is enabled 194 */ 195 bool enabled = true; 196 197 /** 198 Whether the node is enabled for rendering 199 200 Disabled nodes will not be drawn. 201 202 This happens recursively 203 */ 204 bool renderEnabled() { 205 if (parent) return !parent.renderEnabled ? false : enabled; 206 return enabled; 207 } 208 209 /** 210 Visual name of the node 211 */ 212 string name = "Unnamed Node"; 213 214 /** 215 Name of node as a null-terminated C string 216 */ 217 const(char)* cName() { 218 import std.string : toStringz; 219 return name.toStringz; 220 } 221 222 /** 223 Returns the unique identifier for this node 224 */ 225 uint uuid() { 226 return uuid_; 227 } 228 229 /** 230 This node's type ID 231 */ 232 string typeId() { return "Node"; } 233 234 /** 235 Gets the relative Z sorting 236 */ 237 float relZSort() { 238 return zsort_; 239 } 240 241 /** 242 Gets the basis zSort offset. 243 */ 244 float zSortBase() { 245 return parent !is null ? parent.zSort() : 0; 246 } 247 248 /** 249 Gets the Z sorting without parameter offsets 250 */ 251 float zSortNoOffset() { 252 return zSortBase + relZSort; 253 } 254 255 /** 256 Gets the Z sorting 257 */ 258 float zSort() { 259 return zSortBase + relZSort + offsetSort; 260 } 261 262 /** 263 Sets the (relative) Z sorting 264 */ 265 void zSort(float value) { 266 zsort_ = value; 267 } 268 269 /** 270 Lock translation to root 271 */ 272 ref bool lockToRoot() { 273 return lockToRoot_; 274 } 275 276 /** 277 Lock translation to root 278 */ 279 void lockToRoot(bool value) { 280 281 // Automatically handle converting lock space and proper world space. 282 if (value && !lockToRoot_) { 283 localTransform.translation = transformNoLock().translation; 284 } else if (!value && lockToRoot_) { 285 localTransform.translation = localTransform.translation-parent.transformNoLock().translation; 286 } 287 288 lockToRoot_ = value; 289 } 290 291 /** 292 Constructs a new puppet root node 293 */ 294 this(Puppet puppet) { 295 puppet_ = puppet; 296 } 297 298 /** 299 Constructs a new node 300 */ 301 this(Node parent = null) { 302 this(inCreateUUID(), parent); 303 } 304 305 /** 306 Constructs a new node with an UUID 307 */ 308 this(uint uuid, Node parent = null) { 309 this.parent = parent; 310 this.uuid_ = uuid; 311 } 312 313 /** 314 The local transform of the node 315 */ 316 Transform localTransform; 317 318 /** 319 The cached world space transform of the node 320 */ 321 @Ignore 322 Transform globalTransform; 323 324 @Ignore 325 bool recalculateTransform = true; 326 327 /** 328 The transform in world space 329 */ 330 @Ignore 331 Transform transform(bool ignoreParam=false)() { 332 if (recalculateTransform) { 333 localTransform.update(); 334 offsetTransform.update(); 335 336 static if (!ignoreParam) { 337 if (lockToRoot_) 338 globalTransform = localTransform.calcOffset(offsetTransform) * puppet.root.localTransform; 339 else if (parent !is null) 340 globalTransform = localTransform.calcOffset(offsetTransform) * parent.transform(); 341 else 342 globalTransform = localTransform.calcOffset(offsetTransform); 343 344 recalculateTransform = false; 345 } else { 346 347 if (lockToRoot_) 348 globalTransform = localTransform * puppet.root.localTransform; 349 else if (parent !is null) 350 globalTransform = localTransform * parent.transform(); 351 else 352 globalTransform = localTransform; 353 354 recalculateTransform = false; 355 } 356 } 357 358 return globalTransform; 359 } 360 361 /** 362 The transform in world space without locking 363 */ 364 @Ignore 365 Transform transformNoLock() { 366 localTransform.update(); 367 368 if (parent !is null) return localTransform * parent.transform(); 369 return localTransform; 370 } 371 372 /** 373 Calculates the relative position between 2 nodes and applies the offset. 374 You should call this before reparenting nodes. 375 */ 376 void setRelativeTo(Node to) { 377 setRelativeTo(to.transformNoLock.matrix); 378 this.zSort = this.zSortNoOffset-to.zSortNoOffset; 379 } 380 381 /** 382 Calculates the relative position between this node and a matrix and applies the offset. 383 This does not handle zSorting. Pass a Node for that. 384 */ 385 void setRelativeTo(mat4 to) { 386 this.localTransform.translation = getRelativePosition(to, this.transformNoLock.matrix); 387 } 388 389 /** 390 Gets a relative position for 2 matrices 391 */ 392 static 393 vec3 getRelativePosition(mat4 m1, mat4 m2) { 394 auto cm = (m1.inverse * m2).translation; 395 return vec3(cm * vec4(0, 0, 0, 1)); 396 } 397 398 /** 399 Gets the path to the node. 400 */ 401 final 402 string getNodePath() { 403 import std.array : join; 404 if (nodePath_.length > 0) return nodePath_; 405 406 string[] pathSegments; 407 Node parent = this; 408 while(parent !is null) { 409 pathSegments = parent.name ~ pathSegments; 410 parent = parent.parent; 411 } 412 413 nodePath_ = "/"~pathSegments.join("/"); 414 return nodePath_; 415 } 416 417 /** 418 Gets the depth of this node 419 */ 420 final int depth() { 421 int depthV; 422 Node parent = this; 423 while(parent !is null) { 424 depthV++; 425 parent = parent.parent; 426 } 427 return depthV; 428 } 429 430 /** 431 Gets a list of this node's children 432 */ 433 final Node[] children() { 434 return children_; 435 } 436 437 /** 438 Gets the parent of this node 439 */ 440 final ref Node parent() { 441 return parent_; 442 } 443 444 /** 445 The puppet this node is attached to 446 */ 447 final Puppet puppet() { 448 return parent_ !is null ? parent_.puppet : puppet_; 449 } 450 451 /** 452 Removes all children from this node 453 */ 454 final void clearChildren() { 455 foreach(child; children_) { 456 child.parent_ = null; 457 } 458 this.children_ = []; 459 } 460 461 /** 462 Adds a node as a child of this node. 463 */ 464 final void addChild(Node child) { 465 child.parent = this; 466 } 467 468 /** 469 Sets the parent of this node 470 */ 471 final void parent(Node node) { 472 this.insertInto(node, OFFSET_END); 473 } 474 475 final ptrdiff_t getIndexInParent() { 476 import std.algorithm.searching : countUntil; 477 return parent_.children_.countUntil(this); 478 } 479 480 final ptrdiff_t getIndexInNode(Node n) { 481 import std.algorithm.searching : countUntil; 482 return n.children_.countUntil(this); 483 } 484 485 enum OFFSET_START = size_t.min; 486 enum OFFSET_END = size_t.max; 487 final void insertInto(Node node, size_t offset) { 488 nodePath_ = null; 489 import std.algorithm.mutation : remove; 490 import std.algorithm.searching : countUntil; 491 492 // Remove ourselves from our current parent if we are 493 // the child of one already. 494 if (parent_ !is null) { 495 496 // Try to find ourselves in our parent 497 // note idx will be -1 if we can't be found 498 ptrdiff_t idx = parent_.children_.countUntil(this); 499 assert(idx >= 0, "Invalid parent-child relationship!"); 500 501 // Remove ourselves 502 parent_.children_ = parent_.children_.remove(idx); 503 } 504 505 // If we want to become parentless we need to handle that 506 // seperately, as null parents have no children to update 507 if (node is null) { 508 this.parent_ = null; 509 return; 510 } 511 512 // Update our relationship with our new parent 513 this.parent_ = node; 514 515 // Update position 516 if (offset == OFFSET_START) { 517 this.parent_.children_ = this ~ this.parent_.children_; 518 } else if (offset == OFFSET_END || offset >= parent_.children_.length) { 519 this.parent_.children_ ~= this; 520 } else { 521 this.parent_.children_ = this.parent_.children_[0..offset] ~ this ~ this.parent_.children_[offset..$]; 522 } 523 if (this.puppet !is null) this.puppet.rescanNodes(); 524 } 525 526 /** 527 Return whether this node supports a parameter 528 */ 529 bool hasParam(string key) { 530 switch(key) { 531 case "zSort": 532 case "transform.t.x": 533 case "transform.t.y": 534 case "transform.t.z": 535 case "transform.r.x": 536 case "transform.r.y": 537 case "transform.r.z": 538 case "transform.s.x": 539 case "transform.s.y": 540 return true; 541 default: 542 return false; 543 } 544 } 545 546 /** 547 Gets the default offset value 548 */ 549 float getDefaultValue(string key) { 550 switch(key) { 551 case "zSort": 552 case "transform.t.x": 553 case "transform.t.y": 554 case "transform.t.z": 555 case "transform.r.x": 556 case "transform.r.y": 557 case "transform.r.z": 558 return 0; 559 case "transform.s.x": 560 case "transform.s.y": 561 return 1; 562 default: 563 return float(); 564 } 565 } 566 567 /** 568 Sets offset value 569 */ 570 bool setValue(string key, float value) { 571 switch(key) { 572 case "zSort": 573 offsetSort += value; 574 return true; 575 case "transform.t.x": 576 offsetTransform.translation.x += value; 577 transformChanged(); 578 return true; 579 case "transform.t.y": 580 offsetTransform.translation.y += value; 581 transformChanged(); 582 return true; 583 case "transform.t.z": 584 offsetTransform.translation.z += value; 585 transformChanged(); 586 return true; 587 case "transform.r.x": 588 offsetTransform.rotation.x += value; 589 transformChanged(); 590 return true; 591 case "transform.r.y": 592 offsetTransform.rotation.y += value; 593 transformChanged(); 594 return true; 595 case "transform.r.z": 596 offsetTransform.rotation.z += value; 597 transformChanged(); 598 return true; 599 case "transform.s.x": 600 offsetTransform.scale.x *= value; 601 transformChanged(); 602 return true; 603 case "transform.s.y": 604 offsetTransform.scale.y *= value; 605 transformChanged(); 606 return true; 607 default: return false; 608 } 609 } 610 611 /** 612 Scale an offset value, given an axis and a scale 613 614 If axis is -1, apply magnitude and sign to signed properties. 615 If axis is 0 or 1, apply magnitude only unless the property is 616 signed and aligned with that axis. 617 618 Note that scale adjustments are not considered aligned, 619 since we consider preserving aspect ratio to be the user 620 intent by default. 621 */ 622 float scaleValue(string key, float value, int axis, float scale) { 623 if (axis == -1) return value * scale; 624 625 float newVal = abs(scale) * value; 626 switch(key) { 627 case "transform.r.z": // Z-rotation is XY-mirroring 628 newVal = scale * value; 629 break; 630 case "transform.r.y": // Y-rotation is X-mirroring 631 case "transform.t.x": 632 if (axis == 0) newVal = scale * value; 633 break; 634 case "transform.r.x": // X-rotation is Y-mirroring 635 case "transform.t.y": 636 if (axis == 1) newVal = scale * value; 637 break; 638 default: 639 break; 640 } 641 return newVal; 642 } 643 644 /** 645 Draws this node and it's subnodes 646 */ 647 void draw() { 648 if (!renderEnabled) return; 649 650 foreach(child; children) { 651 child.draw(); 652 } 653 } 654 655 /** 656 Draws this node. 657 */ 658 void drawOne() { } 659 660 661 /** 662 Finalizes this node and any children 663 */ 664 void finalize() { 665 foreach(child; children) { 666 child.finalize(); 667 } 668 } 669 670 void beginUpdate() { 671 offsetSort = 0; 672 offsetTransform.clear(); 673 674 // Iterate through children 675 foreach(child; children_) { 676 child.beginUpdate(); 677 } 678 } 679 680 /** 681 Updates the node 682 */ 683 void update() { 684 if (!enabled) return; 685 686 foreach(child; children) { 687 child.update(); 688 } 689 } 690 691 /** 692 Marks this node's transform (and its descendents') as dirty 693 */ 694 void transformChanged() { 695 recalculateTransform = true; 696 697 foreach(child; children) { 698 child.transformChanged(); 699 } 700 } 701 702 override 703 string toString() { 704 return name; 705 } 706 707 /** 708 Allows serializing a node (with pretty serializer) 709 */ 710 void serialize(S)(ref S serializer) { 711 auto state = serializer.objectBegin(); 712 this.serializeSelf(serializer); 713 serializer.objectEnd(state); 714 } 715 716 /** 717 Deserializes node from Fghj formatted JSON data. 718 */ 719 SerdeException deserializeFromFghj(Fghj data) { 720 721 if (auto exc = data["uuid"].deserializeValue(this.uuid_)) return exc; 722 723 if (!data["name"].isEmpty) { 724 if (auto exc = data["name"].deserializeValue(this.name)) return exc; 725 } 726 727 if (auto exc = data["enabled"].deserializeValue(this.enabled)) return exc; 728 729 if (auto exc = data["zsort"].deserializeValue(this.zsort_)) return exc; 730 731 if (auto exc = data["transform"].deserializeValue(this.localTransform)) return exc; 732 733 if (!data["lockToRoot"].isEmpty) { 734 if (auto exc = data["lockToRoot"].deserializeValue(this.lockToRoot_)) return exc; 735 } 736 737 // Pre-populate our children with the correct types 738 foreach(child; data["children"].byElement) { 739 740 // Fetch type from json 741 string type; 742 if (auto exc = child["type"].deserializeValue(type)) return exc; 743 744 // Skips unknown node types 745 // TODO: A logging system that shows a warning for this? 746 if (!inHasNodeType(type)) continue; 747 748 // instantiate it 749 Node n = inInstantiateNode(type, this); 750 if (auto exc = child.deserializeValue(n)) return exc; 751 } 752 753 754 return null; 755 } 756 757 /** 758 Force sets the node's ID 759 760 THIS IS NOT A SAFE OPERATION. 761 */ 762 final void forceSetUUID(uint uuid) { 763 this.uuid_ = uuid; 764 } 765 766 /** 767 Gets the combined bounds of the node 768 */ 769 vec4 getCombinedBounds() { 770 auto tr = transform; 771 vec4 combined = vec4(tr.translation.x, tr.translation.y, tr.translation.x, tr.translation.y); 772 773 // Get Bounds as drawable 774 if (Drawable drawable = cast(Drawable)this) { 775 combined = drawable.bounds; 776 } 777 778 foreach(child; children) { 779 vec4 cbounds = child.getCombinedBounds(); 780 if (cbounds.x < combined.x) combined.x = cbounds.x; 781 if (cbounds.y < combined.y) combined.y = cbounds.y; 782 if (cbounds.z > combined.z) combined.z = cbounds.z; 783 if (cbounds.w > combined.w) combined.w = cbounds.w; 784 } 785 786 return combined; 787 } 788 789 /** 790 Gets whether nodes can be reparented 791 */ 792 bool canReparent(Node to) { 793 Node tmp = to; 794 while(tmp !is null) { 795 if (tmp.uuid == this.uuid) return false; 796 797 // Check next up 798 tmp = tmp.parent; 799 } 800 return true; 801 } 802 803 /** 804 Draws orientation of the node 805 */ 806 void drawOrientation() { 807 auto trans = transform.matrix(); 808 inDbgLineWidth(4); 809 810 // X 811 inDbgSetBuffer([vec3(0, 0, 0), vec3(32, 0, 0)], [0, 1]); 812 inDbgDrawLines(vec4(1, 0, 0, 0.7), trans); 813 814 // Y 815 inDbgSetBuffer([vec3(0, 0, 0), vec3(0, -32, 0)], [0, 1]); 816 inDbgDrawLines(vec4(0, 1, 0, 0.7), trans); 817 818 // Z 819 inDbgSetBuffer([vec3(0, 0, 0), vec3(0, 0, -32)], [0, 1]); 820 inDbgDrawLines(vec4(0, 0, 1, 0.7), trans); 821 822 inDbgLineWidth(1); 823 } 824 825 /** 826 Draws bounds 827 */ 828 void drawBounds() { 829 vec4 bounds = this.getCombinedBounds; 830 831 float width = bounds.z-bounds.x; 832 float height = bounds.w-bounds.y; 833 inDbgSetBuffer([ 834 vec3(bounds.x, bounds.y, 0), 835 vec3(bounds.x + width, bounds.y, 0), 836 837 vec3(bounds.x + width, bounds.y, 0), 838 vec3(bounds.x + width, bounds.y+height, 0), 839 840 vec3(bounds.x + width, bounds.y+height, 0), 841 vec3(bounds.x, bounds.y+height, 0), 842 843 vec3(bounds.x, bounds.y+height, 0), 844 vec3(bounds.x, bounds.y, 0), 845 ]); 846 inDbgLineWidth(3); 847 inDbgDrawLines(vec4(.5, .5, .5, 1)); 848 inDbgLineWidth(1); 849 } 850 } 851 852 // 853 // SERIALIZATION SHENNANIGANS 854 // 855 private { 856 Node delegate(Node parent)[string] typeFactories; 857 858 Node inInstantiateNode(string id, Node parent = null) { 859 return typeFactories[id](parent); 860 } 861 } 862 863 void inRegisterNodeType(T)() if (is(T : Node)) { 864 import std.traits : getUDAs; 865 typeFactories[getUDAs!(T, TypeId)[0].id] = (Node parent) { 866 return new T(parent); 867 }; 868 } 869 870 /** 871 Gets whether a node type is present in the factories 872 */ 873 bool inHasNodeType(string id) { 874 return (id in typeFactories) !is null; 875 } 876 877 mixin template InNode(T) { 878 static this() { 879 inRegisterNodeType!(T); 880 } 881 }