1 /*
2     Inochi2D Bone Group
3 
4     Copyright © 2020, Inochi2D Project
5     Distributed under the 2-Clause BSD License, see LICENSE file.
6     
7     Authors: Luna Nielsen
8 */
9 module inochi2d.core.nodes.pathdeform;
10 import inochi2d.fmt.serialize;
11 import inochi2d.core.nodes.part;
12 import inochi2d.core.nodes;
13 import inochi2d.core.dbg;
14 import inochi2d.core;
15 import inochi2d.math;
16 
17 package(inochi2d) {
18     void inInitPathDeform() {
19         inRegisterNodeType!PathDeform;
20     }
21 }
22 
23 /**
24     A node that deforms multiple nodes against a path.
25 */
26 @TypeId("PathDeform")
27 class PathDeform : Node {
28 private:
29     
30     // Joint Origins
31     vec2[] jointOrigins;
32 
33     // Computed joint matrices
34     mat3[] computedJoints;
35 
36 
37     void recomputeJoints() {
38         foreach(i; 0..joints.length) {
39             float startAngle;
40             float endAngle;
41             size_t next = i+1;
42             
43             // Special Case:
44             // We're at the end of the joints list
45             // There's nothing to "orient" ourselves against, so
46             // We'll just have a rotational value of 0
47             if (next >= joints.length) {
48                 startAngle = atan2(
49                     jointOrigins[i-1].y - jointOrigins[i].y, 
50                     jointOrigins[i-1].x - jointOrigins[i].x
51                 );
52                 
53                 endAngle = atan2(
54                     joints[i-1].y - joints[i].y, 
55                     joints[i-1].x - joints[i].x
56                 );
57             } else {
58 
59                 // Get the angles between our origin positions and our
60                 // Current joint positions to get the difference.
61                 // The difference between the root angle and the current
62                 // angle determines how much the point and path is rotated.
63                 startAngle = atan2(
64                     jointOrigins[i].y - jointOrigins[next].y, 
65                     jointOrigins[i].x - jointOrigins[next].x
66                 );
67 
68                 endAngle = atan2(
69                     joints[i].y - joints[next].y, 
70                     joints[i].x - joints[next].x
71                 );
72             }
73 
74 
75             // Apply our wonky math to our computed joint
76             computedJoints[i] = mat3.translation(vec3(joints[i], 0)) * mat3.zrotation(startAngle-endAngle);
77         }
78     }
79 
80     /**
81         Bindings queued for finalization
82     */
83     size_t[][][uint] queuedBindings;
84     
85 protected:
86 
87     override
88     string typeId() { return "PathDeform"; }
89 
90     /**
91         Allows serializing self data (with pretty serializer)
92     */
93     override
94     void serializeSelf(ref InochiSerializer serializer) {
95         super.serializeSelf(serializer);
96         serializer.putKey("joints");
97         auto state = serializer.arrayBegin();
98             foreach(joint; jointOrigins) {
99                 serializer.elemBegin;
100                 joint.serialize(serializer);
101             }
102         serializer.arrayEnd(state);
103 
104         serializer.putKey("bindings");
105         state = serializer.arrayBegin();
106             foreach(item, data; bindings) {
107                 serializer.elemBegin;
108                 auto obj = serializer.objectBegin();
109                     serializer.putKey("bound_to");
110                     serializer.putValue(item.uuid);
111                     serializer.putKey("bind_data");
112                     serializer.serializeValue(data);
113                 serializer.objectEnd(obj);
114             }
115         serializer.arrayEnd(state);
116     }
117 
118     /**
119         Allows serializing self data (with compact serializer)
120     */
121     override
122     void serializeSelf(ref InochiSerializerCompact serializer) {
123         super.serializeSelf(serializer);
124         serializer.putKey("joints");
125         auto state = serializer.arrayBegin();
126             foreach(joint; jointOrigins) {
127                 serializer.elemBegin;
128                 joint.serialize(serializer);
129             }
130         serializer.arrayEnd(state);
131 
132         if (bindings.length > 0) {
133             serializer.putKey("bindings");
134             state = serializer.arrayBegin();
135                 foreach(item, data; bindings) {
136 
137                     // (Safety measure) Skip null items
138                     if (item is null) continue;
139 
140                     serializer.elemBegin;
141                     auto obj = serializer.objectBegin();
142                         serializer.putKey("bound_to");
143                         serializer.putValue(item.uuid);
144                         
145                         serializer.putKey("bind_data");
146                         serializer.serializeValue(data);
147                     serializer.objectEnd(obj);
148                 }
149             serializer.arrayEnd(state);
150         }
151 
152         // TODO: serialize bindings
153     }
154 
155     override
156     SerdeException deserializeFromFghj(Fghj data) {
157         super.deserializeFromFghj(data);
158         
159         foreach(jointData; data["joints"].byElement) {
160             vec2 val;
161             val.deserialize(jointData);
162             
163             jointOrigins ~= val;
164         }
165         joints = jointOrigins.dup;
166         this.computedJoints = new mat3[joints.length];
167 
168         if (!data["bindings"].isEmpty) {
169             foreach(bindingData; data["bindings"].byElement) {
170                 uint uuid;
171                 size_t[][] qdata;
172                 bindingData["bound_to"].deserializeValue(uuid);
173                 bindingData["bind_data"].deserializeValue(qdata);
174                 queuedBindings[uuid] = qdata;
175             }
176         }
177 
178         // TODO: deserialize bindings
179         return null;
180     }
181 
182 public:
183 
184     /**
185         The current joint locations of the deformation
186     */
187     vec2[] joints;
188 
189     /**
190         The bindings from joints to verticies in multiple parts
191 
192         [Drawable] = Every drawable that is affected
193         [] = the entry of the joint
194         size_t[] = the entry of verticies in that part that should be affected.
195     */
196     size_t[][][Drawable] bindings;
197 
198     /**
199         Gets joint origins
200     */
201     vec2[] origins() {
202         return jointOrigins;
203     }
204 
205     /**
206         Constructs a new path deform
207     */
208     this(Node parent = null) {
209         super(parent);
210     }
211 
212     /**
213         Constructs a new path deform
214     */
215     this(vec2[] joints, Node parent = null) {
216         this.setJoints(joints);
217         super(parent);
218     }
219 
220     /**
221         Sets the joints for this path deform
222     */
223     void setJoints(vec2[] joints) {
224         this.jointOrigins = joints.dup;
225         this.joints = joints.dup;
226         this.computedJoints = new mat3[joints.length];
227     }
228 
229     /**
230         Adds a joint with the specified offset to the end of the joints list
231     */
232     void addJoint(vec2 joint) {
233         jointOrigins ~= jointOrigins[$-1] + joint;
234         joints ~= jointOrigins[$-1];
235         computedJoints.length++;
236     }
237 
238     /**
239         Sets the position of joint as its new origin
240     */
241     void setJointOriginFor(size_t index) {
242         if (index >= joints.length) return;
243         jointOrigins[index] = joints[index];
244     }
245 
246     /**
247         Updates the spline group.
248     */
249     override
250     void update() {
251         this.recomputeJoints();
252 
253         // Iterates over every part attached to this deform
254         // Then iterates over every joint that should affect that part
255         // Then appplies the deformation across that part's joints
256         foreach(Drawable part, size_t[][] entry; bindings) {
257             if (part is null) continue;
258             MeshData mesh = part.getMesh();
259             
260             foreach(jointEntry, vertList; entry) {
261                 mat3 joint = computedJoints[jointEntry];
262 
263                 // Deform vertices
264                 foreach(i; vertList) {
265                     part.vertices[i] = (joint * vec3(mesh.vertices[i], 0)).xy;
266                 }
267             }
268         }
269 
270         super.update();
271     }
272 
273     
274     void drawHandles() {
275         
276     }
277 
278     /**
279         Resets the positions of joints
280     */
281     void resetJoints() {
282         joints = jointOrigins;
283         computedJoints.length = joints.length;
284     }
285 
286     override
287     void finalize() {
288         super.finalize();
289         
290         // Finalize by moving the data over to the actual bindings
291         foreach(uuid, data; queuedBindings) {
292             bindings[puppet.find!Drawable(uuid)] = data.dup;
293         }
294 
295         // Clear this memory
296         destroy(queuedBindings);
297     }
298 }