1 /**
2     Inochi2D Mask 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.mask;
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.conv;
21 import nulib.math;
22 import numem;
23 
24 public import inochi2d.core.render.state;
25 
26 /**
27     A binding between a mask and a mode
28 */
29 struct MaskSource {
30 private:
31 @nogc:
32     GUID maskSrcGUID;
33 
34 public:
35     MaskingMode mode;
36     Visual maskSrc;
37 
38     /**
39         Serialization function
40     */
41     void onSerialize(ref DataNode object, bool recursive = true) {
42         if (maskSrc) {
43             auto srcGuid = maskSrc.guid.toString();
44             object["source"] = srcGuid[];
45             object["mode"] = cast(uint)mode;
46         }
47     }
48 
49     /**
50         Deserialization function
51     */
52     void onDeserialize(ref DataNode object, ref ModelState state) {
53 
54         // 0.8 uses a string for the masking mode.
55         if (state.doUpgrade08) {
56             this.maskSrcGUID = object.tryGetGUID(state, "source", "source");
57             this.mode = object.tryGet!nstring(state, "mode").toMaskingMode;
58             return;
59         }
60 
61         this.maskSrcGUID = object.tryGetGUID(state, "source", "source");
62         this.mode = cast(MaskingMode)object.tryGet!uint(state, "mode");
63     }
64 
65     /**
66         Finalizer
67     */
68     void onFinalize(Mask mask) {
69         this.maskSrc = mask.puppet.find!Visual(maskSrcGUID);
70     }
71 }
72 
73 /**
74     A mask.
75 */
76 @TypeId("Mask", IN_MAKE_TAG!(5, 1))
77 class Mask : Visual {
78 private:
79 @nogc:
80     weak_vector!Visual visuals_;
81     vector!MaskSource sources_;
82 
83 protected:
84 
85     /**
86         Serializes this node to a DataNode.
87 
88         Params:
89             object =    The DataNode to serialize to.
90     */
91     override
92     void onSerialize(ref DataNode object) {
93         super.onSerialize(object);
94 
95         // Serialize masks, if any are applied.
96         if (sources_.length > 0) {
97             object["masks"] = DataNode.createArray();
98             foreach (mask; sources_) {
99                 object["masks"] ~= mask.serialize();
100             }
101         }
102     }
103 
104     /**
105         Deserializes this node from a DataNode.
106 
107         Params:
108             object =    The DataNode to deserialize from.
109             state =     The state of the deserializer.
110     */
111     override
112     void onDeserialize(ref DataNode object, ref ModelState state) {
113         super.onDeserialize(object, state);
114 
115         // Deserialize masks, if any are present.
116         if ("masks" in object && object["masks"].isArray) {
117             sources_.resize(object["masks"].length);
118             foreach (i, ref value; object["masks"].array) {
119                 sources_[i] = value.deserialize!MaskSource(state);
120             }
121         }
122     }
123 
124     /**
125         Called when the node is to finalize its deserialization from disk.
126 
127         Params:
128             state =     The state of the deserializer.
129     */
130     override
131     void onFinalize(ref ModelState state) @nogc {
132         super.onFinalize(state);
133 
134         // Finalize all masks.
135         foreach_reverse (i; 0 .. sources_.length) {
136             sources_[i].onFinalize(this);
137 
138             // Remove invalid masks.
139             if (!sources_[i].maskSrc) {
140                 state.warning(nstring("Removed mask source ", i.toString(), " from ", this.name[], ", ID was invalid..."));
141                 sources_.removeAt(i);
142             }
143         }
144     }
145 
146     /**
147         Called when the node is to be redrawn.
148         
149         Params:
150             delta =     Time since the last frame.
151             drawList =  The drawlist for the active scene.
152     */
153     override
154     void onDraw(float delta, DrawList drawList) {
155         if (!enabled)
156             return;
157 
158         if (sources_.length > 0) {
159             drawList.pushMask(sources_[0].mode);
160 
161             visuals_.sortNodes();
162             foreach (ref src; sources_) {
163                 src.maskSrc.drawMask(delta, drawList, src.mode);
164             }
165 
166             foreach (ref child; visuals_) {
167                 child.draw(delta, drawList);
168             }
169             drawList.popMask();
170             return;
171         }
172 
173         // No mask sources, just draw children.
174         visuals_.sortNodes();
175         foreach (ref child; visuals_) {
176             child.draw(delta, drawList);
177         }
178     }
179 
180     /**
181         Requests that the list gather sub-visuals to be rendered, if applicable.
182 
183         Params:
184             visuals =           The list to write to, the list may be resized by the
185                                 implementation.
186             recurseDelegates =  Whether to recurse through delegate visuals.
187             append =            Whether to append to the visuals list.
188     */
189     override
190     void onDelegateFindVisuals(ref weak_vector!Visual visuals, bool recurseDelegates, bool append) {
191         // dfmt off
192         .findVisuals(this, visuals_, false, false, false);
193         // dfmt on
194     }
195 
196 public:
197 
198     /**
199         Whether the renderer should delegate rendering logic
200         to the visual node.
201     */
202     override @property bool isDelegated() @nogc => true;
203 
204     /**
205         Whether the node can be used as a source of masking operations.
206     */
207     override @property bool isMasking() @nogc nothrow pure => false;
208 
209     /**
210         List of mask sources.
211     */
212     @property MaskSource[] sources() => sources_[0 .. $];
213 
214     /// Destructor
215     ~this() {
216         sources_.clear();
217     }
218 
219     /**
220         Constructs a new Mask
221     */
222     this(Node parent = null) {
223         super(parent);
224     }
225 
226     /**
227         Constructs a new Mask
228     */
229     this(GUID guid, Node parent = null) {
230         super(guid, parent);
231     }
232 }
233 
234 mixin Register!(Mask, in_node_registry);