1 /**
2     Inochi2D Visual 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.visual;
14 import inochi2d.nodes;
15 import inochi2d.common;
16 import inochi2d.core;
17 import nulib.collections;
18 import nulib.math.fixed;
19 import nulib.string;
20 import nulib.math;
21 import numem;
22 
23 public import inochi2d.nodes.visual.part;
24 public import inochi2d.nodes.visual.animatedpart;
25 public import inochi2d.nodes.visual.composite;
26 public import inochi2d.nodes.visual.solo;
27 public import inochi2d.nodes.visual.mask;
28 
29 /**
30     A node which can be drawn to the screen.
31 */
32 @TypeId("Visual", IN_MAKE_TAG!(0, 1))
33 @TypeIdAbstract
34 abstract
35 class Visual : Node {
36 private:
37 @nogc:
38     int zSort_ = 0;
39 
40 protected:
41 
42     /**
43         Constructs a new visual
44     */
45     this(Node parent = null) {
46         super(parent);
47     }
48 
49     /**
50         Constructs a new visual
51     */
52     this(GUID guid, Node parent = null) {
53         super(guid, parent);
54     }
55 
56     /**
57         Serializes this node to a DataNode.
58 
59         Params:
60             object =    The DataNode to serialize to.
61     */
62     override
63     void onSerialize(ref DataNode object) @nogc {
64         super.onSerialize(object);
65         object["zsort"] = zSort_;
66     }
67 
68     /**
69         Deserializes this node from a DataNode.
70 
71         Params:
72             object =    The DataNode to deserialize from.
73             state =     The state of the deserializer.
74     */
75     override
76     void onDeserialize(ref DataNode object, ref ModelState state) {
77         super.onDeserialize(object, state);
78         object.tryGetRef(state, zSort_, "zsort");
79 
80         // Upgrade masks from previous format to new format.
81         if (state.doUpgrade08 && !cast(Mask)this) {
82             state.info(nstring("Upgrading legacy z-sorting values for ", name, "..."));
83 
84             float newZSort = (state.upctx["zsort"] * 100.0);
85             this.zSort_ = cast(int)newZSort;
86 
87             if ("masks" in object && object["masks"].length > 0) {
88                 state.info(nstring("0.8->0.9: wrapping ", this.name[], " in Mask node..."));
89 
90                 // Create a new mask object to wrap ourselves in.
91                 Visual mask = nogc_new!Mask(inNewGUID(), this.parent);
92                 mask.onDeserialize(object, state);
93                 mask.zSort_ = this.zSort_;
94                 mask.name = "Mask";
95                 this.parent = mask;
96                 this.zSort_ = 0;
97             }
98         }
99     }
100 
101     /**
102         Called when the node is to finalize its deserialization from disk.
103 
104         Params:
105             state =     The state of the deserializer.
106     */
107     override
108     void onFinalize(ref ModelState state) @nogc {
109         super.onFinalize(state);
110     }
111 
112     /**
113         Callback for when the Visual is being requested to find its
114         sub-visuals.
115 
116         Params:
117             visuals =           The array to append the visuals to.
118             recurseDelegates =  Whether to recurse through delegate visuals.
119             append =            Whether to append to the visuals list.
120     */
121     void onDelegateFindVisuals(ref weak_vector!Visual visuals, bool recurseDelegates, bool append) {
122     }
123 
124     /**
125         Called when the node should be drawn to a mask.
126         
127         Params:
128             delta =     Time since the last frame.
129             drawList =  The drawlist for the active scene.
130             mode =      The masking mode to draw with.
131     */
132     void onDrawMask(float delta, DrawList drawList, MaskingMode mode) {
133     }
134 
135     /**
136         Called when the node is to define its properties.
137 
138         Call $(D propList.define) with a quark to do this.
139 
140         Params:
141             propList = The property list to populate.
142     */
143     override void onDefineProperties(ref PropertyStore propList) {
144         super.onDefineProperties(propList);
145         propList.define(PROP_ZSORT, 0);
146     }
147 
148 public:
149 
150     /**
151         Whether the renderer should delegate rendering logic
152         to the visual node.
153     */
154     @property bool isDelegated() @nogc nothrow pure => false;
155 
156     /**
157         Whether the node can be used as a source of masking operations.
158     */
159     @property bool isMasking() @nogc nothrow pure => false;
160 
161     /**
162         World-space Z-sorting value
163     */
164     @property ref int zSort() @nogc nothrow pure => zSort_;
165 
166     /**
167         World-space z-sorting value taking in to account properties.
168     */
169     @property float zSortRender() @nogc nothrow pure => (zSort_ + props.get!float(PROP_ZSORT));
170 
171     /// Destructor
172     ~this() {
173     }
174 
175     /**
176         Draws this visual as a mask.
177         
178         Params:
179             delta =     Time since the last frame.
180             drawList =  The drawlist for the active scene.
181             mode =      The masking mode to draw with.
182     */
183     final void drawMask(float delta, DrawList drawList, MaskingMode mode) @nogc {
184         this.onDrawMask(delta, drawList, mode);
185     }
186 
187     /**
188         Requests that the list gather sub-visuals to be rendered, if applicable.
189 
190         Params:
191             visuals =           The list to write to, the list may be resized by the
192                                 implementation.
193             recurseDelegates =  Whether to recurse through delegate visuals.
194             append =            Whether to append to the visuals list.
195     */
196     void findVisuals(ref weak_vector!Visual visuals, bool recurseDelegates = false, bool append = false) {
197         this.onDelegateFindVisuals(visuals, recurseDelegates, append);
198     }
199 }
200 
201 mixin Register!(Visual, in_node_registry);
202 // dfmt off
203 
204 
205 
206 
207 //
208 //          QUARKS
209 //
210 
211 mixin RegisterQuarks!();
212 
213 /**
214     z-sort value.
215 */
216 @propkey("zsort")
217 __gshared immutable(quark) PROP_ZSORT;
218 
219 /**
220     Opacity.
221 */
222 @propkey("opacity")
223 __gshared immutable(quark) PROP_OPACITY;
224 
225 /**
226     Tint RGB
227 */
228 @propkey("tint.rgb")
229 __gshared immutable(quark) PROP_TINT_RGB;
230 
231 /**
232     Tint red
233 */
234 @propkey("tint.r")
235 __gshared immutable(quark) PROP_TINT_R;
236 
237 /**
238     Tint green
239 */
240 @propkey("tint.g")
241 __gshared immutable(quark) PROP_TINT_G;
242 
243 /**
244     Tint blue
245 */
246 @propkey("tint.b")
247 __gshared immutable(quark) PROP_TINT_B;
248 
249 /**
250     Screen-tint RGB
251 */
252 @propkey("screenTint.rgb")
253 __gshared immutable(quark) PROP_SCREEN_RGB;
254 
255 /**
256     Screen-tint red
257 */
258 @propkey("screenTint.r")
259 __gshared immutable(quark) PROP_SCREEN_R;
260 
261 /**
262     Screen-tint green
263 */
264 @propkey("screenTint.g")
265 __gshared immutable(quark) PROP_SCREEN_G;
266 
267 /**
268     Screen-tint blue
269 */
270 @propkey("screenTint.b")
271 __gshared immutable(quark) PROP_SCREEN_B;
272 
273 /**
274     Emission strength
275 */
276 @propkey("emissionStrength")
277 __gshared immutable(quark) PROP_EMISSION_STRENGTH;
278 
279 //
280 //          HELPER FUNCTIONS
281 //
282 
283 /**
284     Finds visuals that are within the hirearchy of the given node.
285 
286     Params:
287         root =              The root node to start looking from
288         visuals =           The list to write to, the list may be resized by the
289                             implementation.
290         recurseDelegates =  Whether to recurse through delegate visuals.
291         sort =              Whether to sort the list of visuals.
292         append =            Whether to append to the visuals list.
293 */
294 void findVisuals(Node root, ref weak_vector!Visual visuals, bool recurseDelegates = false, bool sort = true, bool append = false) @nogc {
295     static void findVisualsImpl(Node node, ref weak_vector!Visual visuals, ref size_t i, bool recurseDelegates = false) @nogc {
296         if (!node)
297             return;
298 
299         if (auto visual = cast(Visual)node) {
300             if (!visual.enabled)
301                 return;
302 
303             visuals ~= visual;
304             if (!visual.isDelegated || recurseDelegates) {
305                 foreach (child; node.children) {
306                     findVisualsImpl(child, visuals, i, recurseDelegates);
307                 }
308             } else if (visual.isDelegated) {
309                 visual.findVisuals(visuals, recurseDelegates, true);
310             }
311         } else {
312 
313             // Non-part nodes just need to be recursed through,
314             // they don't draw anything.
315             foreach (child; node.children) {
316                 findVisualsImpl(child, visuals, i, recurseDelegates);
317             }
318         }
319     }
320 
321     if (!append)
322         visuals.clear();
323 
324     // Find all visuals in children.
325     size_t i = 0;
326     foreach (child; root.children) {
327         findVisualsImpl(child, visuals, i, recurseDelegates);
328     }
329 
330     if (sort)
331         sortNodes(visuals.value);
332 }