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 }