Unravel Engine C++ Reference
Loading...
Searching...
No Matches
animation_player.cpp
Go to the documentation of this file.
1#include "animation_player.h"
2#include <hpp/utility/overload.hpp>
3namespace unravel
4{
5
6namespace
7{
15template<typename T>
16auto interpolate(const std::vector<animation_channel::key<T>>& keys, animation_player::seconds_t time) -> T
17{
18 if(keys.empty())
19 {
20 return {}; // Return default value if there are no keys
21 }
22
23 // Do binary search for keyframe
24 int high = (int)keys.size(), low = -1;
25 while(high - low > 1)
26 {
27 int probe = (high + low) / 2;
28 if(keys[probe].time < time)
29 {
30 low = probe;
31 }
32 else
33 {
34 high = probe;
35 }
36 }
37
38 if(low == -1)
39 {
40 // Before first key, return first key
41 return keys.front().value;
42 }
43
44 if(high == (int)keys.size())
45 {
46 // Beyond last key, return last key
47 return keys.back().value;
48 }
49
50 const auto& key1 = keys[low];
51 const auto& key2 = keys[low + 1];
52
53 // Compute the interpolation factor (0.0 to 1.0)
54 float factor = (time.count() - key1.time.count()) / (key2.time.count() - key1.time.count());
55
56 // Perform the interpolation
57 if constexpr(std::is_same_v<T, math::vec3>)
58 {
59 return math::lerp(key1.value, key2.value, factor);
60 }
61 else if constexpr(std::is_same_v<T, math::quat>)
62 {
63 return math::slerp(key1.value, key2.value, factor);
64 }
65
66 return {};
67}
68
69} // namespace
70auto animation_player::get_layer(size_t index) -> animation_layer&
71{
72 if(index >= layers_.size())
73 {
74 layers_.resize(index + 1);
75 }
76 return layers_[index];
77}
78void animation_player::clear(size_t layer_idx)
79{
80 auto& layer = get_layer(layer_idx);
81 layer = {};
82}
83
84void animation_player::blend_to(size_t layer_idx,
86 seconds_t duration,
87 bool loop,
88 bool phase_sync,
89 const blend_easing_t& easing)
90{
91 auto& layer = get_layer(layer_idx);
92 if(!clip)
93 {
94 if(layer.current_state.state.clip)
95 {
96 layer.current_state = {};
97 }
98 }
99
100 layer.target_state.state.loop = loop;
101
102 if(layer.target_state.state.clip == clip)
103 {
104 return;
105 }
106
107 if(layer.current_state.state.clip == clip)
108 {
109 return;
110 }
111
112 layer.target_state.state.clip = clip;
113 auto phase = phase_sync ? layer.current_state.state.get_progress() : 0.0f;
114 layer.target_state.state.set_progress(phase);
115
116
117 if(duration > clip.get()->duration)
118 {
119 duration = clip.get()->duration;
120 }
121
122 // Set blending parameters
123 layer.blending_state.state = blend_over_time{duration};
124 layer.blending_state.easing = easing;
125}
126
127void animation_player::set_blend_space(size_t layer_idx, const std::shared_ptr<blend_space_def>& blend_space, bool loop)
128{
129 auto& layer = get_layer(layer_idx);
130
131 layer.current_state.state.loop = loop;
132
133 if(layer.current_state.state.blend_space == blend_space)
134 {
135 return;
136 }
137
138 layer.current_state.state.blend_space = blend_space;
139 layer.current_state.state.elapsed = seconds_t(0);
140 // Clear target state if any
141 layer.target_state = {};
142 layer.blending_state = {};
143}
144
145void animation_player::set_blend_space_parameters(size_t layer_idx, const std::vector<float>& params)
146{
147 auto& layer = get_layer(layer_idx);
148 layer.current_state.parameters = params;
149}
150
152{
153 if(playing_)
154 {
155 return false;
156 }
157 playing_ = true;
158 paused_ = false;
159
160 return true;
161}
162
164{
165 paused_ = true;
166}
167
169{
170 paused_ = false;
171}
172
174{
175 playing_ = false;
176 paused_ = false;
177
178 for(auto& layer : layers_)
179 {
180 layer.current_state.state.elapsed = seconds_t(0);
181 layer.target_state.state.elapsed = seconds_t(0);
182 }
183}
184
185auto animation_player::update_time(seconds_t delta_time, bool force) -> bool
186{
187 if(!is_playing())
188 {
189 return false;
190 }
191
192 bool any_valid = false;
193 for(auto& layer : layers_)
194 {
195 any_valid |= layer.current_state.is_valid();
196 any_valid |= layer.target_state.is_valid();
197
198 if(any_valid)
199 {
200 break;
201 }
202 }
203
204 if(!any_valid)
205 {
206 return false;
207 }
208
209 // Update times
210 if(playing_ && !paused_)
211 {
212 for(auto& layer : layers_)
213 {
214 update_state(delta_time, layer.current_state.state);
215
216 update_state(delta_time, layer.target_state.state);
217
218 // update overtime parameters
219 hpp::visit(hpp::overload(
220 [&](blend_over_time& state)
221 {
222 state.elapsed += delta_time;
223 },
224 [](auto& state)
225 {
226
227 }),
228 layer.blending_state.state);
229 }
230
231 return true;
232 }
233 return false;
234}
235
236void animation_player::update_poses(const animation_pose& ref_pose, const update_callback_t& set_transform_callback)
237{
238 if(layers_.empty())
239 {
240 return;
241 }
242
243 for(auto& layer : layers_)
244 {
245 // Update current layer
246 update_pose(layer.current_state);
247
248 // Update target layer
249 if(update_pose(layer.target_state))
250 {
251 // Compute blend factor
252 float blend_progress = get_blend_progress(layer);
253 float blend_factor = compute_blend_factor(layer, blend_progress);
254
255 // Blend poses
256 blend_poses(layer.current_state.pose, layer.target_state.pose, blend_factor, layer.blend_pose);
257
258 // Check if blending is finished
259 if(blend_progress >= 1.0f)
260 {
261 // Switch to target animation or blend space
262 layer.current_state = layer.target_state;
263 layer.target_state = {};
264 layer.blending_state = {};
265 }
266 }
267 }
268
269 if(layers_.size() == 1)
270 {
271 auto final_pose = layers_.front().get_final_pose();
272
273 // Apply the final pose using the callback
274 for(const auto& node : final_pose->nodes)
275 {
276 set_transform_callback(node.desc, node.transform, final_pose->motion_result);
277 }
278 }
279 else
280 {
281 animation_pose final_pose{};
282 blend_poses_additive(*layers_[0].get_final_pose(), *layers_[1].get_final_pose(), ref_pose, 1.0f, final_pose);
283
284 for(size_t i = 2; i < layers_.size(); ++i)
285 {
286 animation_pose next_final_pose{};
287 blend_poses_additive(final_pose, *layers_[i].get_final_pose(), ref_pose, 1.0f, next_final_pose);
288
289 final_pose = next_final_pose;
290 }
291
292 // Apply the final pose using the callback
293 for(const auto& node : final_pose.nodes)
294 {
295 set_transform_callback(node.desc, node.transform, final_pose.motion_result);
296 }
297 }
298}
299
300auto animation_player::update_pose(animation_layer_state& layer) -> bool
301{
302 auto& state = layer.state;
303 auto& pose = layer.pose;
304 auto& parameters = layer.parameters;
305
306 if(state.blend_space)
307 {
308 // Compute blending weights based on current parameters (e.g., speed and direction)
309 state.blend_space->compute_blend(parameters, state.blend_clips);
310
311 // Sample animations and blend poses
312 state.blend_poses.resize(state.blend_clips.size());
313 for(size_t i = 0; i < state.blend_clips.size(); ++i)
314 {
315 const auto& clip_weight_pair = state.blend_clips[i];
316 sample_animation(clip_weight_pair.first.get().get(), state.elapsed, state.blend_poses[i]);
317 }
318
319 // Blend all poses based on their weights
320 pose.nodes.clear();
321 if(!state.blend_poses.empty())
322 {
323 // Initialize with the first pose
324 pose = state.blend_poses[0];
325 float total_weight = state.blend_clips[0].second;
326
327 for(size_t i = 1; i < state.blend_poses.size(); ++i)
328 {
329 blend_poses(pose,
330 state.blend_poses[i],
331 state.blend_clips[i].second / (total_weight + state.blend_clips[i].second),
332 pose);
333 total_weight += state.blend_clips[i].second;
334 }
335 }
336 return true;
337 }
338
339 if(state.clip)
340 {
341 sample_animation(state.clip.get().get(), state.elapsed, pose);
342 return true;
343 }
344
345 return false;
346}
347
348void animation_player::update_state(seconds_t delta_time, animation_state& state)
349{
350 if(state.clip)
351 {
352 state.elapsed += delta_time;
353 auto target_anim = state.clip.get();
354 if(target_anim)
355 {
356 if(state.elapsed > target_anim->duration)
357 {
358 if(state.loop)
359 {
360 state.elapsed = seconds_t(std::fmod(state.elapsed.count(), target_anim->duration.count()));
361 }
362 else
363 {
364 state.elapsed = target_anim->duration;
365 }
366 }
367 }
368
369 }
370}
371
372auto animation_player::get_blend_progress(const animation_layer& layer) const -> float
373{
374 return hpp::visit(hpp::overload(
375 [](const hpp::monostate& state)
376 {
377 return 0.0f;
378 },
379 [](const auto& state)
380 {
381 return state.get_progress();
382 }),
383 layer.blending_state.state);
384}
385
386auto animation_player::compute_blend_factor(const animation_layer& layer, float normalized_blend_time) noexcept -> float
387{
388 float blend_factor = 0.0f;
389
390 // Apply the easing function
391 blend_factor = layer.blending_state.easing(normalized_blend_time);
392
393 // Check if blending is complete
394 if(normalized_blend_time >= 1.0f)
395 {
396 // Blending complete
397 blend_factor = 1.0f;
398 }
399
400 return blend_factor;
401}
402
403void animation_player::sample_animation(const animation_clip* anim_clip,
404 seconds_t time,
405 animation_pose& pose) const noexcept
406{
407 if(!anim_clip)
408 {
409 return;
410 }
411 pose.nodes.clear();
412 pose.nodes.reserve(anim_clip->channels.size());
413
414 for(const auto& channel : anim_clip->channels)
415 {
416 math::vec3 position = interpolate(channel.position_keys, time);
417 math::quat rotation = interpolate(channel.rotation_keys, time);
418 math::vec3 scaling = interpolate(channel.scaling_keys, time);
419
420 auto& node = pose.nodes.emplace_back();
421 node.desc.index = channel.node_index;
422 // node.desc.name = channel.node_name;
423 node.transform.set_position(position);
424 node.transform.set_rotation(rotation);
425 node.transform.set_scale(scaling);
426
427 bool processed = false;
428
429 if(int(node.desc.index) == anim_clip->root_motion.position_node_index)
430 {
431 pose.motion_result.root_position_node_index = anim_clip->root_motion.position_node_index;
432
433 const auto& clip_start_pos = channel.position_keys.front().value;
434 const auto& clip_end_pos = channel.position_keys.back().value;
435
436 pose.motion_result.root_position_weights = {1.0f, 1.0f, 1.0f};
437 pose.motion_result.bone_position_weights = {0.0f, 0.0f, 0.0f};
438
439 if(pose.motion_state.root_position_time == seconds_t(0))
440 {
441 pose.motion_state.root_position_time = time;
442 pose.motion_state.root_position_at_time = clip_start_pos;
443 }
444
445 auto delta_position = position - pose.motion_state.root_position_at_time;
446
447 if(time < pose.motion_state.root_position_time)
448 {
449 auto loop_pos_offset = clip_end_pos - clip_start_pos;
450 delta_position = delta_position + loop_pos_offset;
451 }
452
453
454
455 if(anim_clip->root_motion.keep_position_y)
456 {
457 pose.motion_result.root_position_weights.y = 0.0f;
458 pose.motion_result.bone_position_weights.y = 1.0f;
459
460 }
461
462 if(anim_clip->root_motion.keep_position_xz)
463 {
464 pose.motion_result.root_position_weights.x = 0.0f;
465 pose.motion_result.root_position_weights.z = 0.0f;
466
467 pose.motion_result.bone_position_weights.x = 1.0f;
468 pose.motion_result.bone_position_weights.z = 1.0f;
469 }
470
471 if(anim_clip->root_motion.keep_in_place)
472 {
473 pose.motion_result.root_position_weights.y = 0.0f;
474 pose.motion_result.root_position_weights.x = 0.0f;
475 pose.motion_result.root_position_weights.z = 0.0f;
476
477 pose.motion_result.bone_position_weights.y = 1.0f;
478 pose.motion_result.bone_position_weights.x = 0.0f;
479 pose.motion_result.bone_position_weights.z = 0.0f;
480 }
481
482 pose.motion_state.root_position_time = time;
483 pose.motion_state.root_position_at_time = position;
484 pose.motion_result.root_transform_delta.set_position(delta_position);
485 }
486
487 if(int(node.desc.index) == anim_clip->root_motion.rotation_node_index)
488 {
489 pose.motion_result.root_rotation_node_index = anim_clip->root_motion.rotation_node_index;
490
491 const auto& clip_start_rotation = channel.rotation_keys.front().value;
492 const auto& clip_end_rotation = channel.rotation_keys.back().value;
493
494 pose.motion_result.root_rotation_weight = {1.0f};
495 pose.motion_result.bone_rotation_weight = {0.0f};
496
497 if(pose.motion_state.root_rotation_time == seconds_t(0))
498 {
499 pose.motion_state.root_rotation_time = time;
500
501 pose.motion_state.root_rotation_at_time = clip_start_rotation;
502 }
503
504 auto delta_rotation = rotation * glm::inverse(pose.motion_state.root_rotation_at_time);
505
506 if(time < pose.motion_state.root_rotation_time)
507 {
508 auto loop_rotation_offset = clip_end_rotation * glm::inverse(clip_start_rotation);
509 delta_rotation = loop_rotation_offset * delta_rotation;
510 }
511
512 if(anim_clip->root_motion.keep_rotation)
513 {
514 pose.motion_result.root_rotation_weight = 0.0f;
515 pose.motion_result.bone_rotation_weight = 1.0f;
516
517 }
518
519 if(anim_clip->root_motion.keep_in_place)
520 {
521 pose.motion_result.root_rotation_weight = 0.0f;
522 pose.motion_result.bone_rotation_weight = 1.0f;
523 }
524
525 pose.motion_state.root_rotation_time = time;
526 pose.motion_state.root_rotation_at_time = rotation;
527 pose.motion_result.root_transform_delta.set_rotation(delta_rotation);
528 }
529 }
530}
531
533{
534 return playing_ && !paused_;
535}
536
537auto animation_player::is_paused() const -> bool
538{
539 return paused_;
540}
541
542} // namespace unravel
auto play() -> bool
Starts or resumes the animation playback.
void pause()
Pauses the animation playback.
animation_clip::seconds_t seconds_t
void clear(size_t layer_idx)
void set_blend_space(size_t layer_idx, const std::shared_ptr< blend_space_def > &blend_space, bool loop=true)
void stop()
Stops the animation playback and resets the time.
std::function< void(const animation_pose::node_desc &desc, const math::transform &abs, const animation_pose::root_motion_result &motion_result)> update_callback_t
auto is_playing() const -> bool
Returns whether the animation is currently playing.
void blend_to(size_t layer_idx, const asset_handle< animation_clip > &clip, seconds_t duration=seconds_t(0.3), bool loop=true, bool phase_sync=false, const blend_easing_t &easing=math::linearInterpolation< float >)
Blends to the animation over the specified time with the specified easing.
void set_blend_space_parameters(size_t layer_idx, const std::vector< float > &params)
void resume()
Resumes the animation playback.
auto update_time(seconds_t delta_time, bool force=false) -> bool
Updates the animation player, advancing the animation time and applying transformations.
void update_poses(const animation_pose &ref_pose, const update_callback_t &set_transform_callback)
auto is_paused() const -> bool
Returns whether the animation is currently paused.
std::function< float(float)> blend_easing_t
void blend_poses(const animation_pose &pose1, const animation_pose &pose2, float factor, animation_pose &result_pose)
void blend_poses_additive(const animation_pose &base, const animation_pose &additive, const animation_pose &ref_pose, float weight, animation_pose &result)
Represents a handle to an asset, providing access and management functions.
auto get(bool wait=true) const -> std::shared_ptr< T >
Gets the shared pointer to the asset.
animation_clip::seconds_t elapsed