1 /**
2     Inochi2D Animation Primitives
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.animation.animation;
14 import inochi2d.puppet;
15 import inochi2d.param;
16 import inochi2d.core;
17 import inochi2d.common;
18 import numath;
19 import numem.sorting;
20 import numem;
21 
22 /**
23     An animation
24 */
25 struct Animation {
26 public:
27 @nogc:
28 
29     /**
30         The timestep of each frame
31     */
32     float timestep = 0.0166;
33 
34     /**
35         Whether the animation is additive.
36 
37         Additive animations will not replace main animations, but add their data
38         on top of the running main animation
39     */
40     bool additive;
41 
42     /**
43         The weight of the animation
44 
45         This is only relevant for additive animations
46     */
47     float animationWeight;
48 
49     /**
50         All of the animation lanes in this animation
51     */
52     AnimationLane[] lanes;
53 
54     /**
55         Length in frames
56     */
57     int length;
58 
59     /**
60         Time where the lead-in ends
61     */
62     int leadIn = -1;
63 
64     /**
65         Time where the lead-out starts
66     */
67     int leadOut = -1;
68 
69     /**
70         Finalizes the animation
71     */
72     void finalize(Puppet puppet) {
73         foreach (ref lane; lanes)
74             lane.finalize(puppet);
75     }
76 
77     /**
78         Serialization function
79     */
80     void onSerialize(ref DataNode object, bool recursive = true) @nogc {
81         object["timestep"] = timestep;
82         object["additive"] = additive;
83         object["length"] = length;
84         object["leadIn"] = leadIn;
85         object["leadOut"] = leadOut;
86         object["animationWeight"] = animationWeight;
87 
88         object["lanes"] = DataNode.createArray();
89         foreach (ref AnimationLane lane; lanes) {
90             if (lane.paramRef.targetParam) {
91                 object["lanes"] ~= lane.serialize();
92             }
93         }
94     }
95 
96     /**
97         Deserialization function
98     */
99     void onDeserialize(ref DataNode object, ref ModelState state) @nogc {
100         object.tryGetRef(state, timestep, "timestep", timestep.init);
101         object.tryGetRef(state, additive, "additive", additive.init);
102         object.tryGetRef(state, animationWeight, "animationWeight", animationWeight.init);
103         object.tryGetRef(state, length, "length", length.init);
104         object.tryGetRef(state, leadIn, "leadIn", leadIn.init);
105         object.tryGetRef(state, leadOut, "leadOut", leadOut.init);
106         object.tryGetRef(state, lanes, "lanes", lanes.init);
107     }
108 }
109 
110 struct AnimationParameterRef {
111 
112     /**
113         A parameter to target
114     */
115     Parameter targetParam;
116 
117     /**
118         Target axis of the parameter
119     */
120     int targetAxis;
121 
122 }
123 
124 /**
125     Animation Lane
126 */
127 struct AnimationLane {
128 private:
129 @nogc:
130     GUID refguid;
131 
132 public:
133 
134     /**
135         Reference to parameter if any
136     */
137     AnimationParameterRef* paramRef;
138 
139     /**
140         Serialization function
141     */
142     void onSerialize(ref DataNode object, bool recursive = true) @nogc {
143         object["interpolation"] = cast(uint)interpolation;
144         object["keyframes"] = frames.serialize();
145         object["merge_mode"] = cast(uint)mergeMode;
146         if (paramRef) {
147             auto targetGuid = paramRef.targetParam.guid.toString;
148             object["guid"] = targetGuid[];
149             object["target"] = paramRef.targetAxis;
150         }
151     }
152 
153     /**
154         Deserialization function
155     */
156     void onDeserialize(ref DataNode object, ref ModelState state) @nogc {
157         this.paramRef = nogc_new!AnimationParameterRef(null, 0);
158         this.refguid = object.tryGetGUID(state, "uuid", "guid");
159 
160         object.tryGetRef(state, interpolation, "interpolation");
161         object.tryGetRef(state, paramRef.targetAxis, "target");
162         object.tryGetRef(state, frames, "keyframes");
163         object.tryGetRef(state, mergeMode, "merge_mode", mergeMode.init);
164     }
165 
166     /**
167         List of frames in the lane
168     */
169     Keyframe[] frames;
170 
171     /**
172         The interpolation between each frame in the lane
173     */
174     InterpolateMode interpolation;
175 
176     /**
177         Merging mode of the lane
178     */
179     ParameterMergeMode mergeMode = ParameterMergeMode.forced;
180 
181     /**
182         Gets the interpolated state of a frame of animation 
183         for this lane
184     */
185     float get(float frame, bool snapSubframes = false) {
186         if (frames.length > 0) {
187 
188             // If subframe snapping is turned on then we'll only run at the framerate
189             // of the animation, without any smooth interpolation on faster app rates.
190             if (snapSubframes)
191                 frame = floor(frame);
192 
193             // Fallback if there's only 1 frame
194             if (frames.length == 1)
195                 return frames[0].value;
196 
197             foreach (i; 0 .. frames.length) {
198                 if (frames[i].frame < frame)
199                     continue;
200 
201                 // Fallback to not try to index frame -1
202                 if (i == 0)
203                     return frames[0].value;
204 
205                 // Interpolation "time" 0->1
206                 // Note we use floats here in case you're running the
207                 // update step faster than the timestep of the animation
208                 // This way it won't look choppy
209                 float tonext = cast(float)frames[i].frame - frame;
210                 float ilen = (cast(float)frames[i].frame - cast(float)frames[i - 1].frame);
211                 float t = 1 - (tonext / ilen);
212 
213                 // Interpolation tension 0->1
214                 float tension = frames[i].tension;
215 
216                 switch (interpolation) {
217 
218                     // Nearest - Snap to the closest frame
219                 case InterpolateMode.nearest:
220                     return t > 0.5 ? frames[i].value : frames[i - 1].value;
221 
222                     // Stepped - Snap to the current active keyframe
223                 case InterpolateMode.stepped:
224                     return frames[i - 1].value;
225 
226                     // Linear - Linearly interpolate between frame A and B
227                 case InterpolateMode.linear:
228                     return lerp(frames[i - 1].value, frames[i].value, t);
229 
230                     // Cubic - Smoothly in a curve between frame A and B
231                 case InterpolateMode.cubic:
232                     float prev = frames[max(cast(ptrdiff_t)i - 2, 0)].value;
233                     float curr = frames[max(cast(ptrdiff_t)i - 1, 0)].value;
234                     float next1 = frames[min(cast(ptrdiff_t)i, frames.length - 1)].value;
235                     float next2 = frames[min(cast(ptrdiff_t)i + 1, frames.length - 1)].value;
236 
237                     // TODO: Switch formulae, catmullrom interpolation
238                     return cubic(prev, curr, next1, next2, t);
239 
240                     // Bezier - Allows the user to specify beziér curves.
241                 case InterpolateMode.quadratic:
242                     // TODO: Switch formulae, Beziér curve
243                     return lerp(frames[i - 1].value, frames[i].value, clamp(hermite(0, 2 * tension, 1, 2 * tension, t), 0, 1));
244 
245                 default:
246                     assert(0);
247                 }
248             }
249             return frames[$ - 1].value;
250         }
251 
252         // Fallback, no values.
253         // Ideally we won't even call this function
254         // if there's nothing to do.
255         return 0;
256     }
257 
258     void finalize(Puppet puppet) {
259         if (paramRef)
260             paramRef.targetParam = puppet.findParameter(refguid);
261     }
262 
263     /**
264         Updates the order of the keyframes
265     */
266     void updateFrames() {
267         nu_sort!((a, b) => a.frame < b.frame)(frames);
268     }
269 }
270 
271 /**
272     A keyframe
273 */
274 struct Keyframe {
275     /**
276         The frame at which this frame occurs
277     */
278     int frame;
279 
280     /**
281         The value of the parameter at the given frame
282     */
283     float value;
284 
285     /**
286         Interpolation tension for cubic/inout
287     */
288     float tension = 0.5;
289 
290     /**
291         Serialization function
292     */
293     void onSerialize(ref DataNode object, bool recursive = true) @nogc {
294         object["frame"] = frame;
295         object["value"] = value;
296         object["tension"] = tension;
297     }
298 
299     /**
300         Deserialization function
301     */
302     void onDeserialize(ref DataNode object, ref ModelState state) @nogc {
303         object.tryGetRef(state, frame, "frame");
304         object.tryGetRef(state, value, "value");
305         object.tryGetRef(state, tension, "tension");
306     }
307 }