1 /*
2     Inochi2D Welding
3 
4     Copyright © 2020-2025, Inochi2D Project
5     Distributed under the 2-Clause BSD License, see LICENSE file.
6     
7     Authors: Luna Nielsen
8 */
9 module inochi2d.effect.weld;
10 import inochi2d.effect;
11 import inochi2d.core.math.deform;
12 import inochi2d.nodes.visual.part;
13 import inochi2d.nodes;
14 import inochi2d.core;
15 import inochi2d.common;
16 import inp.format;
17 import numath;
18 import numem;
19 import nulib;
20 
21 /**
22     An effect that welds other meshes to this mesh, forcing
23     the welded meshes to be dragged towards paired vertices
24 */
25 @TypeId("WeldEffect", 0x0001)
26 class WeldEffect : MeshEffect {
27 private:
28 @nogc:
29 
30     /**
31         Target of the weld
32     */
33     IDeformable target_;
34     GUID targetGUID;
35 
36     /**
37         Vertices of the weld
38     */
39     WeldVertex[] vertices_;
40 
41 protected:
42 
43     /**
44         Serializes this MeshEffect to a DataNode.
45 
46         Params:
47             object =    The DataNode to serialize to.
48     */
49     override
50     void onSerialize(ref DataNode object) {
51         super.onSerialize(object);
52 
53         object["target"] = (cast(Node)target_).guid.toString()[0 .. $];
54         object["weights"] = DataNode.createArray();
55         foreach (vertex; vertices_) {
56             object["weights"] ~= DataNode(vertex.weight);
57         }
58     }
59 
60     /**
61         Deserializes this node from a DataNode.
62 
63         Params:
64             object =    The DataNode to deserialize from.
65             state =     The state of the deserializer.
66     */
67     override
68     void onDeserialize(ref DataNode object, ref ModelState state) {
69         targetGUID = object.tryGetGUID(state, "target", "target");
70         if ("weights" in object && object["weights"].isArray) {
71             vertices_ = nu_malloca!WeldVertex(object["weights"].length);
72             foreach (i, ref value; object["weights"].array) {
73                 vertices_[i].weight = value.tryCoerce!float(0.0);
74             }
75         }
76     }
77 
78     /**
79         Called when the Effect is to be finalized during loading.
80 
81         Params:
82             state =     The state of the deserializer.
83     */
84     override
85     void onFinalize(ref ModelState state) {
86         if (targetGUID != GUID.nil) {
87             this.target_ = cast(IDeformable)parent.puppet.find(targetGUID);
88             this.rebuildWeights();
89         }
90     }
91 
92     /**
93         Called after the full node update cycle.
94         
95         Params:
96             drawList =  The drawlist for the active scene.
97     */
98     override
99     void onApply(DrawList drawList) {
100         vec2[] targetMesh = target_.deformPoints;
101         vec2[] sourceMesh = parent.deformPoints;
102         foreach (i, ref WeldVertex weld; vertices_) {
103             vec2 pdelta = targetMesh[weld.index] + weld.delta;
104             vec2 p = lerp(sourceMesh[i], pdelta, weld.weight);
105             parent.deform(i, p, true);
106         }
107     }
108 
109     void rebuildWelds() {
110         if (vertices_)
111             nu_freea(vertices_);
112 
113         if (target_) {
114             vertices_ = nu_malloca!WeldVertex(parent.basePoints.length);
115             this.rebuildWeights();
116         }
117     }
118 
119 public:
120 
121     /**
122         Target of the weld
123     */
124     final @property IDeformable target() => target_;
125     final @property void target(IDeformable value) {
126         this.target_ = value;
127         this.rebuildWelds();
128     }
129 
130     /**
131         Constructs a new mesh effect.
132 
133         Params:
134             parent = The parent of the mesh effect.
135     */
136     this(Part parent) {
137         super(parent);
138     }
139 
140     /**
141         Rebuilds the weight deltas for the WeldEffect.
142     */
143     void rebuildWeights() {
144         foreach (i, ref WeldVertex vtx; vertices_) {
145 
146             // Rebuild target vertex
147             vec2 parentVtx = parent.basePoints[i];
148             vtx.index = cast(uint)findClosest(parentVtx, target.basePoints);
149 
150             // Rebuild delta
151             vec2 targetVtx = target.basePoints[vtx.index];
152             vtx.delta = targetVtx - parentVtx;
153         }
154     }
155 }
156 
157 /**
158     A welded vertex
159 */
160 struct WeldVertex {
161 
162     /**
163         Weld delta
164     */
165     vec2 delta = vec2(0, 0);
166 
167     /**
168         Index of the vertex to drag towards
169     */
170     uint index;
171 
172     /**
173         Weight of the given line segment.
174     */
175     float weight = 0;
176 }