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 }