1 /**
2     Inochi2D Part 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.part;
14 import inochi2d.nodes.visual;
15 import inochi2d.common;
16 import inochi2d.effect;
17 import inochi2d.nodes;
18 import inochi2d.core;
19 import numath;
20 import nulib.conv;
21 import nulib;
22 import numem;
23 
24 public import inochi2d.core.render.state;
25 public import inochi2d.core.mesh;
26 
27 enum NO_TEXTURE = uint.max;
28 enum TextureUsage : size_t {
29     Albedo,
30     Emissive,
31     Bumpmap,
32     COUNT
33 }
34 
35 struct PartVars {
36     vec3 tint;
37     vec3 screenTint;
38     private void[4] __dummy;
39     float opacity;
40     float emissionStrength;
41 }
42 
43 /**
44     Dynamic Mesh Part
45 */
46 @TypeId("Part", IN_MAKE_TAG!(1, 1))
47 class Part : Visual, IDeformable {
48 private:
49 @nogc:
50     Mesh mesh_;
51     DeformedMesh deformed_;
52     DeformedMesh base_;
53     weak_vector!MeshEffect effects_;
54 
55 protected:
56 
57     /**
58         The deformed mesh state of the part.
59     */
60     @property ref DeformedMesh deformedMesh() => deformed_;
61 
62     /**
63         The current active draw list slot for this
64         drawable.
65     */
66     DrawListAlloc* drawListSlot;
67 
68     /**
69         Serializes this node to a DataNode.
70 
71         Params:
72             object =    The DataNode to serialize to.
73     */
74     override
75     void onSerialize(ref DataNode object) {
76         super.onSerialize(object);
77 
78         MeshData data = MeshData(mesh_);
79         object["mesh"] = data.serialize();
80         object["textures"] = DataNode.createArray();
81         foreach (ref texture; textures) {
82             if (texture) {
83                 ptrdiff_t index = puppet.getTextureSlotIndexFor(texture);
84                 object["textures"].array ~= DataNode(index >= 0 ? index : NO_TEXTURE);
85             } else {
86                 object["textures"].array ~= DataNode(NO_TEXTURE);
87             }
88         }
89 
90         // Serialize attached effects.
91         if (effects_.length > 0) {
92             object["effects"] = DataNode.createArray();
93             foreach (effect; effects_) {
94 
95                 DataNode effectObj;
96                 effect.serialize(effectObj);
97                 object["effects"].array ~= effectObj;
98             }
99         }
100 
101         // Serialize basic data.
102         object["blend_mode"] = cast(uint)blendingMode;
103         object["tint"] = tint.serialize();
104         object["screenTint"] = screenTint.serialize();
105         object["emissionStrength"] = emissionStrength;
106         object["opacity"] = opacity;
107     }
108 
109     /**
110         Deserializes this node from a DataNode.
111 
112         Params:
113             object =    The DataNode to deserialize from.
114             state =     The state of the deserializer.
115     */
116     override
117     void onDeserialize(ref DataNode object, ref ModelState state) {
118         super.onDeserialize(object, state);
119 
120         auto meshData = object.tryGet!MeshData(state, "mesh");
121         this.deformed_ = nogc_new!DeformedMesh();
122         this.base_ = nogc_new!DeformedMesh();
123         this.mesh = Mesh.fromMeshData(meshData);
124 
125         // Textures
126         if ("textures" in object && object["textures"].isArray) {
127             foreach (i, ref DataNode element; object["textures"].array) {
128 
129                 uint textureId = element.tryGet!uint(state, NO_TEXTURE);
130                 if (textureId == NO_TEXTURE)
131                     continue;
132 
133                 // TODO: Abstract this to properly handle refcounts.
134                 this.textures[i] = puppet.textureCache.get(textureId);
135                 if (!this.textures[i]) {
136                     state.error(nstring(this.name[], ": failed to find texture ", .toString!ulong(textureId), "..."));
137                 }
138 
139                 if (this.textures[i])
140                     this.textures[i].retain();
141             }
142         }
143 
144         // Effects
145         if ("effects" in object && object["effects"].isArray) {
146             foreach (i, ref element; object["effects"].array) {
147                 if (MeshEffect effect = in_effect_registry.tryCreateFrom(element, this)) {
148                     effect.deserialize(element, state);
149                 }
150             }
151         }
152 
153         object.tryGetRef(state, opacity, "opacity", 1);
154         object.tryGetRef(state, tint.data, "tint");
155         object.tryGetRef(state, screenTint.data, "screenTint");
156         object.tryGetRef(state, emissionStrength, "emissionStrength");
157 
158         if ("blend_mode" in object && object["blend_mode"].isNumber)
159             blendingMode = cast(BlendMode)object.tryGet!uint(state, "blend_mode", blendingMode.normal);
160         else
161             blendingMode = object.tryGet!string(state, "blend_mode", "Normal").toBlendMode();
162     }
163 
164     /**
165         Called when the node is to finalize its deserialization from disk.
166 
167         Params:
168             state =     The state of the deserializer.
169     */
170     override
171     void onFinalize(ref ModelState state) {
172         super.onFinalize(state);
173         foreach (effect; effects_) {
174             effect.finalize(state);
175         }
176     }
177 
178     /**
179         Called during the early update phase of a new frame.
180         
181         Params:
182             drawList =  The drawlist for the active scene.
183     */
184     override
185     void onPreUpdate(DrawList drawList) {
186         super.onPreUpdate(drawList);
187         this.resetDeform();
188     }
189 
190     /**
191         Called during the update phase of a new frame.
192         
193         Params:
194             delta =     Time since the last frame.
195             drawList =  The drawlist for the active scene.
196     */
197     override
198     void onUpdate(float delta, DrawList drawList) {
199         super.onUpdate(delta, drawList);
200         deformed_.pushMatrix(this.deformMatrix);
201     }
202 
203     /**
204         Called during the late update phase of a new frame.
205         
206         Params:
207             drawList =  The drawlist for the active scene.
208     */
209     override
210     void onPostUpdate(DrawList drawList) {
211         super.onPostUpdate(drawList);
212 
213         this.drawListSlot = drawList.allocate(deformed_.vertices, deformed_.indices);
214 
215         // Apply mesh effects.
216         foreach (effect; effects_)
217             effect.apply(drawList);
218     }
219 
220     /**
221         Called when the node is to be redrawn.
222         
223         Params:
224             delta =     Time since the last frame.
225             drawList =  The drawlist for the active scene.
226     */
227     override
228     void onDraw(float delta, DrawList drawList) {
229         if (!this.enabled)
230             return;
231 
232         PartVars vars = PartVars(
233                 tint: tint * props.get!vec3(PROP_TINT_RGB),
234                 screenTint: screenTint * props.get!vec3(PROP_SCREEN_RGB),
235                 opacity: opacity * props.get!float(PROP_OPACITY),
236                 emissionStrength: emissionStrength * props.get!float(PROP_EMISSION_STRENGTH)
237         );
238 
239         drawList.setMesh(drawListSlot);
240         drawList.setVariables!PartVars(nid, vars);
241         drawList.setBlending(blendingMode);
242         drawList.setSources(textures);
243         drawList.next();
244     }
245 
246     /**
247         Called when the node should be drawn to a mask.
248         
249         Params:
250             delta =     Time since the last frame.
251             drawList =  The drawlist for the active scene.
252             mode =      The masking mode to draw with.
253     */
254     override
255     void onDrawMask(float delta, DrawList drawList, MaskingMode mode) {
256         drawList.setMesh(drawListSlot);
257         drawList.setSources(textures);
258         drawList.setMasking(mode);
259         drawList.next();
260     }
261 
262     /**
263         Called when the node is to define its properties.
264 
265         Call $(D propList.define) with a quark to do this.
266 
267         Params:
268             propList = The property list to populate.
269     */
270     override
271     void onDefineProperties(ref PropertyStore propList) {
272         super.onDefineProperties(propList);
273 
274         propList.define!float(PROP_SCREEN_R, 0);
275         propList.define!float(PROP_SCREEN_G, 0);
276         propList.define!float(PROP_SCREEN_B, 0);
277         propList.define!float(PROP_TINT_R, 1);
278         propList.define!float(PROP_TINT_G, 1);
279         propList.define!float(PROP_TINT_B, 1);
280         propList.define!float(PROP_OPACITY, 1);
281         propList.define!float(PROP_EMISSION_STRENGTH, 1);
282 
283         // Define combined overlays.
284         propList.defineOverlay!vec3(PROP_SCREEN_RGB, propList.offsetOf(PROP_SCREEN_R));
285         propList.defineOverlay!vec3(PROP_TINT_RGB, propList.offsetOf(PROP_TINT_R));
286     }
287 
288 public:
289 
290     /**
291         Whether the node can be used as a source of masking operations.
292     */
293     override @property bool isMasking() @nogc nothrow pure => true;
294 
295     /**
296         The mesh of the part..
297     */
298     final @property Mesh mesh() @nogc => mesh_;
299     final @property void mesh(Mesh value) @nogc {
300         if (value is mesh_)
301             return;
302 
303         if (mesh_)
304             mesh_.release();
305 
306         this.mesh_ = value.retained();
307         this.deformed_.parent = value;
308         this.base_.parent = value;
309     }
310 
311     /**
312         Mesh effects applied to the part.
313     */
314     final @property MeshEffect[] effects() => effects_;
315 
316     /**
317         The base matrix of the object before any parameters have been applied.
318     */
319     override @property Basis deformBaseMatrix() @nogc => baseMatrix;
320 
321     /**
322         World matrix of the deformable object.
323     */
324     override @property Basis deformMatrix() @nogc => matrix;
325 
326     /**
327         The base position of the deformable's points.
328     */
329     @property const(vec2)[] basePoints() => base_.points;
330 
331     /**
332         The points which may be deformed by the deformer.
333     */
334     override @property vec2[] deformPoints() => deformed_.points;
335 
336     /**
337         List of textures this part can use
338 
339         TODO: use more than texture 0
340     */
341     Texture[IN_MAX_ATTACHMENTS] textures;
342 
343     /**
344         Blending mode
345     */
346     BlendMode blendingMode = BlendMode.normal;
347 
348     /**
349         Opacity of the mesh
350     */
351     float opacity = 1;
352 
353     /**
354         Strength of emission
355     */
356     float emissionStrength = 1;
357 
358     /**
359         Multiplicative tint color
360     */
361     vec3 tint = vec3(1, 1, 1);
362 
363     /**
364         Screen tint color
365     */
366     vec3 screenTint = vec3(0, 0, 0);
367 
368     /// Destructor
369     ~this() {
370         mesh_.release();
371         nogc_delete(deformed_);
372         nogc_delete(base_);
373         foreach (texture; textures) {
374             if (texture)
375                 texture.release();
376         }
377     }
378 
379     /**
380         Constructs a new part
381     */
382     this(Node parent = null) {
383         super(parent);
384     }
385 
386     /**
387         Constructs a new part
388     */
389     this(MeshData data, Node parent = null) {
390         this(data, inNewGUID(), parent);
391     }
392 
393     /**
394         Constructs a new part
395     */
396     this(MeshData data, GUID guid, Node parent = null) {
397         super(guid, parent);
398 
399         this.deformed_ = nogc_new!DeformedMesh();
400         this.base_ = nogc_new!DeformedMesh();
401         this.mesh = Mesh.fromMeshData(data);
402     }
403 
404     /**
405         Constructs a new part
406     */
407     this(MeshData data, Texture[] textures, Node parent = null) {
408         this(data, textures, inNewGUID(), parent);
409     }
410 
411     /**
412         Constructs a new part
413     */
414     this(MeshData data, Texture[] textures, GUID guid, Node parent = null) {
415         this(data, guid, parent);
416         foreach (i; 0 .. TextureUsage.COUNT) {
417             if (i >= textures.length)
418                 break;
419             this.textures[i] = textures[i];
420         }
421     }
422 
423     /**
424         Resets the deformation for the IDeformable.
425     */
426     override
427     void resetDeform() {
428         deformed_.reset();
429 
430         base_.reset();
431         base_.pushMatrix(this.deformBaseMatrix);
432     }
433 
434     /**
435         Deforms the IDeformable.
436 
437         Params:
438             deformed =  The deformation delta.
439             absolute =  Whether the deformation is absolute,
440                         replacing the original deformation.
441     */
442     override
443     void deform(vec2[] deformed, bool absolute = false) {
444         deformed_.deform(deformed);
445     }
446 
447     /**
448         Deforms a single vertex in the IDeformable
449 
450         Params:
451             offset =    The offset into the point list to deform.
452             deform =    The deformation delta.
453             absolute =  Whether the deformation is absolute,
454                         replacing the original deformation.
455     */
456     override
457     void deform(size_t offset, vec2 deform, bool absolute = false) {
458         deformed_.deform(offset, deform);
459     }
460 
461     /**
462         Adds a mesh effect to the part.
463 
464         Param:
465             T =     The type of the mesh effect to add.
466             args =  The arguments to pass to the effect's constructor.
467     */
468     void addEffect(T, Args...)(Args args)
469     if (is(T : MeshEffect)) {
470         this.effects_ ~= nogc_new!T(this, args);
471     }
472 
473     /**
474         Adds a mesh effect to the part.
475 
476         Param:
477             effect = The mesh effect to add.
478     */
479     void addEffect(MeshEffect effect) {
480         if (this.effects_.find(effect) == -1)
481             this.effects_ ~= effect.retained();
482     }
483 
484     /**
485         Removes a given mesh effect from this part.
486 
487         Params:
488             effect = The effect to remove.
489     */
490     void removeEffect(MeshEffect effect) {
491         auto i = this.effects_.find(effect);
492         if (i >= 0) {
493             this.effects_.removeAt(i);
494             effect.release();
495         }
496     }
497 }
498 
499 mixin Register!(Part, in_node_registry);