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