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);