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 }