1 /** 2 Inochi2D Node 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inochi2d.nodes; 14 import inochi2d.core.serde; 15 import inochi2d.core.math; 16 import inochi2d.core.guid; 17 import inochi2d.core; 18 import inochi2d.common; 19 import nulib.string; 20 import numem; 21 import nulib; 22 23 public import inochi2d.puppet; 24 public import inochi2d.nodes.deformer; 25 public import inochi2d.nodes.visual; 26 public import inochi2d.nodes.legacy; 27 public import inochi2d.nodes.bone; 28 public import inochi2d.core.registry; 29 public import inochi2d.core.property; 30 public import inochi2d.core.render; 31 public import nulib.quark; 32 33 /** 34 A node in the Inochi2D rendering tree 35 */ 36 @TypeId("Node", IN_MAKE_TAG!(0, 0)) 37 @RegisterFallback 38 class Node : NuRefCounted, IPropertyOwner, ISerializable, IDeserializable!ModelState { 39 private: 40 @nogc: 41 Puppet puppet_; 42 Node parent_; 43 vector!Node children_; 44 GUID guid_; 45 string nodePath_; 46 uint nid_; 47 48 // The property store for the node. 49 PropertyStore props_; 50 51 bool lockToRoot_; 52 Basis globalMatrix_; 53 Basis globalMatrixNoParam_; 54 55 Transform localTransform_; 56 57 // Implementation of the transform update algorithm. 58 void transformUpdateImpl() { 59 60 // Set base matrices. 61 globalMatrix_ = (localTransform_ + localTransformOffset).matrix(); 62 globalMatrixNoParam_ = localTransform_.matrix(); 63 64 if (lockToRoot_) { 65 globalMatrix_.matrix = puppet.root.localTransform_.matrix() * globalMatrix_; 66 globalMatrixNoParam_.matrix = puppet.root.localTransform_.matrix() * globalMatrixNoParam_; 67 } else if (parent_ !is null) { 68 globalMatrix_ = parent_.globalMatrix_ * globalMatrix_; 69 globalMatrixNoParam_ = parent_.globalMatrixNoParam_ * globalMatrixNoParam_; 70 } 71 } 72 73 // Define property list 74 void defineProperties(ref PropertyStore propList) { 75 propList.define!float(PROP_TRANSLATE_X, 0); 76 propList.define!float(PROP_TRANSLATE_Y, 0); 77 propList.define!float(PROP_TRANSLATE_Z, 0); 78 propList.define!float(PROP_ROTATE_X, 0); 79 propList.define!float(PROP_ROTATE_Y, 0); 80 propList.define!float(PROP_ROTATE_Z, 0); 81 propList.define!float(PROP_SCALE_X, 1); 82 propList.define!float(PROP_SCALE_Y, 1); 83 84 // Overlays that lets us get the values in bulk. 85 propList.defineOverlay!Transform(PROP_TRANSFORM, propList.offsetOf(PROP_TRANSLATE_X)); 86 87 this.onDefineProperties(propList); 88 propList.resetAll(); 89 } 90 91 package(inochi2d): 92 93 /** 94 Needed for deserialization 95 */ 96 void setPuppet(Puppet puppet) @nogc { 97 this.puppet_ = puppet; 98 } 99 100 protected: 101 102 /** 103 The Node's numeric ID 104 */ 105 final @property uint nid() @nogc pure => nid_; 106 107 /** 108 Serializes this node to a DataNode. 109 110 Params: 111 object = The DataNode to serialize to. 112 */ 113 void onSerialize(ref DataNode object) @nogc { 114 } 115 116 /** 117 Deserializes this node from a DataNode. 118 119 Params: 120 object = The DataNode to deserialize from. 121 state = The state of the deserializer. 122 */ 123 void onDeserialize(ref DataNode object, ref ModelState state) @nogc { 124 } 125 126 /** 127 Called when the node is to finalize its deserialization from disk. 128 129 Params: 130 state = The state of the deserializer. 131 */ 132 void onFinalize(ref ModelState state) @nogc { 133 } 134 135 /** 136 Called during the early update phase of a new frame. 137 138 Params: 139 drawList = The drawlist for the active scene. 140 */ 141 void onPreUpdate(DrawList drawList) @nogc { 142 } 143 144 /** 145 Called during the update phase of a new frame. 146 147 Params: 148 delta = Time since the last frame. 149 drawList = The drawlist for the active scene. 150 */ 151 void onUpdate(float delta, DrawList drawList) @nogc { 152 } 153 154 /** 155 Called during the late update phase of a new frame. 156 157 Params: 158 drawList = The drawlist for the active scene. 159 */ 160 void onPostUpdate(DrawList drawList) @nogc { 161 } 162 163 /** 164 Called when the node is asked to update its transform. 165 */ 166 void onTransformUpdate() @nogc { 167 } 168 169 /** 170 Called when the node is to be redrawn. 171 172 Params: 173 delta = Time since the last frame. 174 drawList = The drawlist for the active scene. 175 */ 176 void onDraw(float delta, DrawList drawList) @nogc { 177 } 178 179 /** 180 Called when the node is moved from one parent 181 to another. 182 183 Params: 184 from = The node that used to be this node's parent. 185 to = The node it was moved to. 186 index = The index the node was moved to. 187 */ 188 void onMoved(Node from, Node to, ptrdiff_t index) { 189 } 190 191 /** 192 Called when the node is to define its properties. 193 194 Call $(D propList.define) with a quark to do this. 195 196 Params: 197 propList = The property list to populate. 198 */ 199 void onDefineProperties(ref PropertyStore propList) { 200 } 201 202 public: 203 204 /** 205 Whether the node is enabled 206 */ 207 bool enabled = true; 208 209 /** 210 Visual name of the node 211 */ 212 nstring name = "Unnamed Node"; 213 214 /** 215 The puppet this node is attached to 216 */ 217 final @property Puppet puppet() @nogc nothrow pure => parent_ !is null ? parent_.puppet : puppet_; 218 219 /** 220 The parent of this node 221 */ 222 final @property Node parent() @nogc nothrow pure => parent_; 223 final @property void parent(Node node) @nogc { 224 if (node) { 225 node.addChild(this); 226 } else if (parent_) { 227 parent_.removeChild(this); 228 } 229 } 230 231 /** 232 The index of this node within ints parent. 233 */ 234 final @property ptrdiff_t parentIndex() => parent ? parent.findChild(this) : -1; 235 236 /** 237 Gets a list of this node's children 238 */ 239 final @property Node[] children() @nogc nothrow pure => children_; 240 241 /** 242 The Node's Type ID 243 */ 244 final @property TypeId typeId() @nogc => in_node_registry.lookup(this); 245 246 /** 247 The node's GUID. 248 */ 249 @property ref GUID guid() @nogc nothrow pure => guid_; 250 251 /** 252 The node's property store, should generally not be directly used. 253 */ 254 final @property ref PropertyStore props() @nogc nothrow pure => props_; 255 256 /** 257 The transform in local-space. 258 */ 259 @property ref Transform localTransform() @nogc => localTransform_; 260 261 /** 262 The offset transform in local-space. 263 */ 264 @property Transform localTransformOffset() @nogc => props_.get!Transform(PROP_TRANSFORM); 265 266 /** 267 The global basis matrix. 268 */ 269 @property Basis matrix() @nogc => globalMatrix_; 270 271 /** 272 The global basis matrix with parameter omitted. 273 */ 274 @property Basis baseMatrix() @nogc => globalMatrixNoParam_; 275 276 /** 277 Whether transformation is locked to the root node. 278 */ 279 @property bool lockToRoot() @nogc nothrow pure => lockToRoot_; 280 @property void lockToRoot(bool value) @nogc { 281 lockToRoot_ = value; 282 } 283 284 /** 285 The depth of this node in the node hirearchy 286 */ 287 final @property int depth() { 288 int depthV; 289 Node parent = this; 290 while (parent !is null) { 291 depthV++; 292 parent = parent.parent; 293 } 294 return depthV; 295 } 296 297 /// Destructor 298 ~this() { 299 foreach (child; children_) { 300 child.release(); 301 } 302 children_.clear(); 303 } 304 305 /** 306 Constructs a new puppet root node. 307 308 Params: 309 parent = The puppet this node will belong to. 310 */ 311 this(Puppet parent) @nogc { 312 this.defineProperties(props_); 313 this.guid_ = inNewGUID(); 314 this.puppet_ = parent; 315 } 316 317 /** 318 Constructs a new node 319 320 Params: 321 parent = The node to parent the new node to. 322 */ 323 this(Node parent = null) @nogc { 324 this(inNewGUID(), parent); 325 } 326 327 /** 328 Constructs a new node with an GUID 329 330 Params: 331 guid = The GUID to apply to the node. 332 parent = The node to parent the new node to. 333 */ 334 this(GUID guid, Node parent = null) @nogc { 335 this.defineProperties(props_); 336 this.parent = parent; 337 this.guid_ = guid; 338 } 339 340 /** 341 Removes all children from this node 342 */ 343 final void clearChildren() { 344 foreach (child; children_) { 345 child.parent_ = null; 346 } 347 this.children_.clear(); 348 } 349 350 /** 351 Gets whether this node can be moved to the 352 given target node. 353 354 Params: 355 to = The move destination. 356 357 Returns: 358 $(D true) if the node can be moved inside of the given node, 359 $(D false) otherwise. 360 */ 361 final bool canMoveTo(Node to) { 362 Node tmp = to; 363 while (tmp !is null) { 364 if (tmp.guid == this.guid) 365 return false; 366 367 // Check next up 368 tmp = tmp.parent; 369 } 370 return true; 371 } 372 373 /** 374 Finds the index of the given direct child node 375 within this node. 376 377 Params: 378 child = The node to find. 379 380 Returns: 381 The index of the node, 382 $(D -1) if not found. 383 */ 384 final ptrdiff_t findChild(Node child) { 385 return children_.find(child); 386 } 387 388 /** 389 Removes a given node from this node's children. 390 391 Params: 392 child = A direct child to remove. 393 394 Returns: 395 $(D true) if the given node was removed, 396 $(D false) otherwise. 397 */ 398 final bool removeChild(Node child) { 399 auto idx = this.findChild(child); 400 if (idx >= 0) { 401 child.onMoved(this, null, -1); 402 child.release(); 403 404 this.children_.removeAt(idx); 405 child.parent_ = null; 406 return true; 407 } 408 return false; 409 } 410 411 /** 412 Adds a node as a child of this node. 413 */ 414 final void addChild(Node child) { 415 child.onMoved(child.parent_, this, this.children_.length); 416 child.retain(); 417 418 // Remove this node from its parent, if needed. 419 if (child.parent_) { 420 child.parent_.removeChild(child); 421 } 422 423 this.children_ ~= child; 424 child.parent_ = this; 425 } 426 427 /** 428 Moves the child to the given offset in this 429 node's child list. 430 431 Params: 432 child = The child the move 433 to = The index to move to. 434 */ 435 final void moveChild(Node child, ptrdiff_t to) { 436 auto idx = this.findChild(child); 437 if (idx >= 0) { 438 size_t dst = to < 0 ? children_.length - (abs(to) + 1) : to; 439 440 // Don't swap with itself. 441 if (dst == idx) 442 return; 443 444 // Swap if valid. 445 if (dst < this.children_.length) { 446 child.onMoved(child.parent_, child.parent_, dst); 447 nu_swap(this.children_[idx], this.children_[to]); 448 } 449 } 450 } 451 452 /** 453 Set new Parent 454 */ 455 void reparent(Node parent, size_t pOffset) { 456 parent.addChild(this); 457 parent.moveChild(this, pOffset); 458 } 459 460 /** 461 Serializes this node to a DataNode. 462 463 Params: 464 recursive = Whether to recurse through children. 465 */ 466 final DataNode serialize(bool recursive = true) @nogc { 467 auto result = DataNode.createObject(); 468 this.serialize(result, recursive); 469 return result; 470 } 471 472 /** 473 Serializes this node to a DataNode. 474 475 Params: 476 object = The DataNode to serialize to. 477 recursive = Whether to recurse through children. 478 */ 479 final void serialize(ref DataNode object, bool recursive = true) @nogc { 480 nstring guid = guid_.toString(); 481 object["guid"] = guid[]; 482 object["name"] = name[]; 483 object["type"] = typeId.sid; 484 object["enabled"] = enabled; 485 object["transform"] = localTransform_.serialize(); 486 object["lockToRoot"] = lockToRoot_; 487 488 // Call callback and iterate to children. 489 this.onSerialize(object); 490 491 // Recurse through children if enabled. 492 if (recursive) { 493 object["children"] = DataNode.createArray(); 494 foreach (child; children) { 495 auto childObject = DataNode.createObject(); 496 child.serialize(childObject); 497 498 object["children"] ~= childObject; 499 } 500 } 501 } 502 503 /** 504 Deserializes this node from a DataNode. 505 506 Params: 507 object = The DataNode to deserialize from. 508 state = The state of the deserializer. 509 */ 510 final void deserialize(ref DataNode object, ref ModelState state) @nogc { 511 this.guid_ = object.tryGetGUID(state, "uuid", "guid"); 512 object.tryGetRef(state, enabled, "enabled"); 513 object.tryGetRef(state, name, "name"); 514 object.tryGetRef(state, localTransform_, "transform"); 515 object.tryGetRef(state, lockToRoot_, "lockToRoot"); 516 517 // This ugly hack exists to upgrade legacy masks to the new masks. 518 float zsort08 = 0; 519 if (state.doUpgrade08) { 520 if ("zsort" !in state.upctx) 521 state.upctx["zsort"] = zsort08; 522 523 object.tryGetRef(state, zsort08, "zsort"); 524 zsort08 += state.upctx["zsort"]; 525 state.upctx["zsort"] = zsort08; 526 } 527 528 // Call callback and iterate to children. 529 this.onDeserialize(object, state); 530 531 // Pre-populate our children with the correct types 532 if ("children" in object && object["children"].isArray) { 533 foreach (ref child; object["children"].array) { 534 535 // This ugly hack exists to upgrade legacy masks to the new masks. 536 if (state.doUpgrade08) { 537 state.upctx["zsort"] = zsort08; 538 } 539 540 // NOTE: inInstantiateNode implicitly handles setting the 541 // Parent-child relationship, so we don't need to do 542 // anything else besides pass it onto the child's 543 // deserializer. 544 if (Node n = in_node_registry.tryCreateFrom(child)) { 545 n.parent = this; 546 n.deserialize(child, state); 547 } 548 } 549 } 550 } 551 552 /** 553 Finalizes this node and its children. 554 555 Params: 556 state = The state of the deserializer. 557 */ 558 final void finalize(ref ModelState state) @nogc { 559 nid_ = typeId.nid; 560 561 // Call callback and iterate to children. 562 this.onFinalize(state); 563 foreach (child; this.children_) { 564 child.finalize(state); 565 } 566 } 567 568 /** 569 Updates the transform of the node and all nodes underneath it. 570 571 Note: 572 Children which have been disabled will not be updated. 573 */ 574 final void updateTransform() @nogc { 575 576 // Do the base algorithm first, 577 // then pass on to callback and iterate to children. 578 this.transformUpdateImpl(); 579 this.onTransformUpdate(); 580 581 foreach (child; children_) { 582 child.updateTransform(); 583 } 584 } 585 586 /** 587 Runs a pre-update cycle for this node and its enabled children. 588 589 Params: 590 drawList = The drawlist for the active scene. 591 592 Note: 593 This is generally called by the puppet and shouldn't be called 594 by you outside of circumstances where the puppet isn't 595 controlling rendering. 596 */ 597 final void preUpdate(DrawList drawList) @nogc { 598 if (!enabled) 599 return; 600 601 this.onPreUpdate(drawList); 602 foreach (child; children_) { 603 child.preUpdate(drawList); 604 } 605 } 606 607 /** 608 Updates the node 609 610 Params: 611 delta = Time since the last frame. 612 drawList = The drawlist for the active scene. 613 614 Note: 615 This is generally called by the puppet and shouldn't be called 616 by you outside of circumstances where the puppet isn't 617 controlling rendering. 618 */ 619 final void update(float delta, DrawList drawList) @nogc { 620 if (!enabled) 621 return; 622 623 this.onUpdate(delta, drawList); 624 foreach (child; children) { 625 child.update(delta, drawList); 626 } 627 } 628 629 /** 630 Update sequence run after the main update sequence. 631 632 Params: 633 drawList = The drawlist for the active scene. 634 635 Note: 636 This is generally called by the puppet and shouldn't be called 637 by you outside of circumstances where the puppet isn't 638 controlling rendering. 639 */ 640 final void postUpdate(DrawList drawList) @nogc { 641 if (!enabled) 642 return; 643 644 this.onPostUpdate(drawList); 645 foreach (child; children_) { 646 child.postUpdate(drawList); 647 } 648 } 649 650 /** 651 Draws this node and it's subnodes 652 653 Params: 654 delta = Time since the last frame. 655 drawList = The drawlist for the active scene. 656 657 Note: 658 This is generally called by the puppet and shouldn't be called 659 by you outside of circumstances where the puppet isn't 660 controlling rendering. 661 */ 662 final void draw(float delta, DrawList drawList) @nogc { 663 this.onDraw(delta, drawList); 664 } 665 666 /** 667 Gets whether a property with the given name exists 668 in the object. 669 670 Params: 671 key = The name of the property. 672 673 Returns: 674 $(D true) if the property exists, 675 $(D false) otherwise. 676 */ 677 bool hasProperty(quark key) const @nogc nothrow { 678 return props_.offsetOf(key) != -1; 679 } 680 681 /** 682 Gets the value of a given property. 683 684 Params: 685 key = The name of the property. 686 687 Returns: 688 The floating point value of the property. 689 */ 690 float getProperty(quark key) const @nogc nothrow { 691 return props_.get!float(key); 692 } 693 694 /** 695 Gets the default value of a given property. 696 697 Params: 698 key = The name of the property. 699 700 Returns: 701 The default value of the property. 702 */ 703 float getPropertyDefault(quark key) const @nogc nothrow { 704 return props_.getDefault!float(key); 705 } 706 707 /** 708 Sets the value of the property. 709 710 Params: 711 key = The name of the property. 712 value = The value to set the property to. 713 */ 714 void setProperty(quark key, float value) @nogc nothrow { 715 return props_.set!float(key, value); 716 } 717 718 /** 719 Resets the given property. 720 721 Params: 722 key = The name of the property. 723 */ 724 void resetProperty(quark key) @nogc nothrow { 725 props_.reset(key); 726 } 727 728 /** 729 Resets all properties. 730 */ 731 void resetProperties() @nogc nothrow { 732 props_.resetAll(); 733 } 734 735 /** 736 Gets the string representation of this object. 737 */ 738 override 739 string toString() const { 740 return name[]; 741 } 742 } 743 744 mixin Register!(Node, in_node_registry); 745 // dfmt off 746 747 748 749 750 // 751 // TYPES AND REGISTRIES 752 // 753 754 /** 755 The public node registry. 756 */ 757 __gshared TypeRegistry!Node in_node_registry; 758 759 760 761 762 // 763 // QUARKS 764 // 765 766 // Register quarks for this file. 767 mixin RegisterQuarks!(); 768 769 /** 770 A transform 771 */ 772 @propkey("transform") 773 __gshared immutable(quark) PROP_TRANSFORM; 774 775 /** 776 X translation. 777 */ 778 @propkey("transform.t.x") 779 __gshared immutable(quark) PROP_TRANSLATE_X; 780 781 /** 782 Y translation. 783 */ 784 @propkey("transform.t.y") 785 __gshared immutable(quark) PROP_TRANSLATE_Y; 786 787 /** 788 Z translation. 789 */ 790 @propkey("transform.t.z") 791 __gshared immutable(quark) PROP_TRANSLATE_Z; 792 793 /** 794 X rotation. 795 */ 796 @propkey("transform.r.x") 797 __gshared immutable(quark) PROP_ROTATE_X; 798 799 /** 800 y rotation. 801 */ 802 @propkey("transform.r.y") 803 __gshared immutable(quark) PROP_ROTATE_Y; 804 805 /** 806 z rotation. 807 */ 808 @propkey("transform.r.z") 809 __gshared immutable(quark) PROP_ROTATE_Z; 810 811 /** 812 X scale. 813 */ 814 @propkey("transform.s.x") 815 __gshared immutable(quark) PROP_SCALE_X; 816 817 /** 818 y scale. 819 */ 820 @propkey("transform.s.y") 821 __gshared immutable(quark) PROP_SCALE_Y; 822 823 824 825 826 // 827 // HELPER FUNCTIONS 828 // 829 830 /** 831 Finds all nodes of the given type (and subtypes) in the node tree. 832 833 Params: 834 root = The root node to start searching from. 835 list = The list to write the results to. 836 */ 837 void findNodes(T)(Node root, ref T[] list) @nogc if (is(T : Node)) { 838 static void findNodesImpl(Node node, ref T[] list) @nogc { 839 if (!node) 840 return; 841 842 if (auto found = cast(T)node) { 843 list = list.nu_resize(list.length + 1); 844 list[$ - 1] = found; 845 } 846 847 // Non-part nodes just need to be recursed through, 848 // they don't draw anything. 849 foreach (child; node.children) { 850 findNodesImpl(child, list); 851 } 852 } 853 854 nu_cleara(list); 855 findNodesImpl(root, list); 856 } 857 858 /** 859 Sorts a slice of visuals in-place. 860 861 Params: 862 slice = The slice to sort. 863 */ 864 void sortNodes(T)(T[] slice) @nogc nothrow if (is(T : Node)) { 865 import nulib.math.fixed : fixed32; 866 import numem.sorting : nu_sort; 867 868 // HACK: nulib doesn't have a float cmp function yet, 869 // as such we convert sorting values to fixed. 870 nu_sort!((a, b) @nogc => a.zSortRender > b.zSortRender)(slice); 871 }