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