Unravel Engine C++ Reference
Loading...
Searching...
No Matches
animation_system.cpp
Go to the documentation of this file.
1#include "animation_system.h"
7#include <engine/events.h>
10
11#include <engine/ecs/ecs.h>
12#include <glm/gtc/epsilon.hpp>
13#include <engine/engine.h>
16#include <logging/logging.h>
17
18#define POOLSTL_STD_SUPPLEMENT 1
19#include <poolstl/poolstl.hpp>
20
21namespace unravel
22{
23
24namespace
25{
26auto remap_root_motion_translation(const math::vec3& delta, const math::quat& correction) noexcept -> math::vec3
27{
28 if(math::all(math::epsilonEqual(correction,
29 math::identity<math::quat>(),
30 math::epsilon<float>())))
31 {
32 return delta;
33 }
34
35 return correction * delta;
36}
37
38auto remap_root_motion_rotation(const math::quat& delta, const math::quat& correction) noexcept -> math::quat
39{
40 if(math::all(math::epsilonEqual(correction,
41 math::identity<math::quat>(),
42 math::epsilon<float>())))
43 {
44 return delta;
45 }
46
47 return correction * delta * glm::conjugate(correction);
48}
49
50void on_play_begin_impl(animation_component& comp)
51{
52 if(comp.get_autoplay())
53 {
54 auto& player = comp.get_player();
55 player.blend_to(0, comp.get_animation());
56 player.play();
57 }
58}
59} // namespace
60
62{
63 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
64 auto& ev = ctx.get_cached<events>();
65
66 ev.on_play_begin.connect(sentinel_, 10, this, &animation_system::on_play_begin);
67 ev.on_play_end.connect(sentinel_, -10, this, &animation_system::on_play_end);
68 ev.on_pause.connect(sentinel_, 10, this, &animation_system::on_pause);
69 ev.on_resume.connect(sentinel_, -10, this, &animation_system::on_resume);
70 ev.on_skip_next_frame.connect(sentinel_, 10, this, &animation_system::on_skip_next_frame);
71
72 return true;
73}
74
76{
77 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
78
79 return true;
80}
81
82void animation_system::on_create_component(entt::registry& r, entt::entity e)
83{
84}
85
86void animation_system::on_destroy_component(entt::registry& r, entt::entity e)
87{
88}
89
91{
92 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
93
94 auto& ec = ctx.get_cached<ecs>();
95 auto& scn = ec.get_scene();
96
97 scn.registry->view<animation_component>().each(
98 [&](auto e, auto&& animation_comp)
99 {
100 on_play_begin_impl(animation_comp);
101 });
102}
103
104void animation_system::on_play_begin(hpp::span<const entt::handle> entities, delta_t dt)
105{
106 for(auto entity : entities)
107 {
108 if(auto animation_comp = entity.try_get<animation_component>())
109 {
110 on_play_begin_impl(*animation_comp);
111 }
112 }
113}
114
115void animation_system::on_play_end(rtti::context& ctx)
116{
117 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
118
119 auto& ec = ctx.get_cached<ecs>();
120 auto& scn = ec.get_scene();
121
122 scn.registry->view<animation_component>().each(
123 [&](auto e, auto&& animation_comp)
124 {
125 auto& player = animation_comp.get_player();
126 player.stop();
127 });
128}
129
130void animation_system::on_pause(rtti::context& ctx)
131{
132}
133
134void animation_system::on_resume(rtti::context& ctx)
135{
136}
137
138void animation_system::on_skip_next_frame(rtti::context& ctx)
139{
140 auto& ec = ctx.get_cached<ecs>();
141 auto& scn = ec.get_scene();
142
143 delta_t step(1.0f / 60.0f);
144 on_update(scn, step, true);
145}
146
147void animation_system::on_update(scene& scn, delta_t dt, bool force)
148{
149 APP_SCOPE_PERF("Animation/System Update");
150
151 static const std::string thread_name = "Animation/System Update Thread";
152 auto& ctx = engine::context();
153 auto& th = ctx.get_cached<threader>();
154 // Create a view for entities with transform_component and submesh_component
155 auto view = scn.registry->view<model_component, animation_component, transform_component>();
156
157 // this code should be thread safe as each task works with a whole hierarchy and
158 // there is no interleaving between tasks.
159 std::for_each(poolstl::par,//std::execution::par,
160 view.begin(),
161 view.end(),
162 [&](entt::entity entity)
163 {
164 APP_SCOPE_PERF_THREAD("Animation/Update Poses", "Pool Thread");
165
166 // This is needed as we call .get on the animations inside the player
167 tpp::this_thread::register_this_thread(thread_name, true);
168
169 auto& animation_comp = view.get<animation_component>(entity);
170 auto& model_comp = view.get<model_component>(entity);
171
172 bool should_update_poses = true;
173 if(animation_comp.get_culling_mode() == animation_component::culling_mode::renderer_based)
174 {
175 if(!model_comp.was_used_last_frame())
176 {
177 should_update_poses = false;
178 }
179 }
180
181 auto& player = animation_comp.get_player();
182
183 // Apply speed to delta time
184 auto speed = animation_comp.get_speed();
185 auto adjusted_dt = dt * speed;
186
187 bool updated = player.update_time(adjusted_dt, force);
188
189 if(updated && should_update_poses)
190 {
191 auto& transform_comp = view.get<transform_component>(entity);
192 // auto physics_comp_ptr = scn.registry->try_get<physics_component>(entity);
193
194 bool apply_root_motion = animation_comp.get_apply_root_motion();
195 auto retargeting_mode = animation_comp.get_retargeting_mode();
196 const auto facing_correction = model_comp.get_facing_adjustment_rotation();
197
198 player.update_poses(
199 model_comp.get_bind_pose(),
200 retargeting_mode,
201 [&](const animation_pose::node_desc& desc,
202 const math::transform& transform,
203 const animation_pose::root_motion_result& motion_result)
204 {
205 // Resolve armature entity based on retargeting mode
206 auto armature_index = desc.index;
207 if(retargeting_mode == animation_retargeting_mode::name_based)
208 {
209 // Name-based: use cached name lookup
210 int name_based_armature_index =
211 model_comp.get_armature_index_by_name_cached(desc.name);
212 if(name_based_armature_index >= 0 &&
213 name_based_armature_index != static_cast<int>(armature_index))
214 {
215 armature_index = name_based_armature_index;
216 }
217 }
218
219 auto armature = model_comp.get_armature_by_index(armature_index);
220 if(armature)
221 {
222 auto& armature_transform_comp = armature.template get<transform_component>();
223
224 bool processed_by_root_motion = false;
225
226 bool is_root_position_node = false;
227 bool is_root_rotation_node = false;
228 if(retargeting_mode == animation_retargeting_mode::name_based)
229 {
230 is_root_position_node = desc.name == motion_result.root_position_node_name;
231 is_root_rotation_node = desc.name == motion_result.root_rotation_node_name;
232 }
233 else
234 {
235 is_root_position_node = desc.index == motion_result.root_position_node_index;
236 is_root_rotation_node = desc.index == motion_result.root_position_node_index;
237 }
238
239 if(apply_root_motion && is_root_position_node)
240 {
241 armature_transform_comp.set_scale_local(transform.get_scale());
242
243 auto position_local = armature_transform_comp.get_position_local();
244 auto result_positon_local = math::mix(position_local,
245 transform.get_position(),
246 motion_result.bone_position_weights);
247 armature_transform_comp.set_position_local(result_positon_local);
248
249 math::vec3 delta_translation_logical = remap_root_motion_translation(
250 motion_result.root_transform_delta.get_translation(),
251 facing_correction);
252
253 // // Apply scaling if needed (for example, if BoneRoot’s scale differs
254 // significantly)
255 auto scale_global = armature_transform_comp.get_scale_global();
256 delta_translation_logical *= scale_global;
257
258 // Blend translation as needed:
259 auto result_move_local = math::mix(math::zero<math::vec3>(),
260 delta_translation_logical,
261 motion_result.root_position_weights);
262 // APPLOG_INFO("position_weights {}", motion_result.position_weights);
263 // if(physics_comp_ptr)
264 // {
265 // if(math::length2(result_move_local) > 0.0f)
266 // {
267 // auto global_delta =
268 // armature_transform_comp.get_transform_global().transform_normal(
269 // result_move_local);
270 // auto rm_velocity = global_delta / dt.count();
271 // physics_comp_ptr->set_velocity(rm_velocity);
272 // }
273 // }
274 // else
275 {
276 transform_comp.move_by_local(result_move_local);
277 }
278 processed_by_root_motion = true;
279 }
280
281 if(apply_root_motion && is_root_rotation_node)
282 {
283 armature_transform_comp.set_scale_local(transform.get_scale());
284
285 auto rotation_local = armature_transform_comp.get_rotation_local();
286 auto result_rotation_local = math::slerp(rotation_local,
287 transform.get_rotation(),
288 motion_result.bone_rotation_weight);
289 armature_transform_comp.set_rotation_local(result_rotation_local);
290
291 // --- Rotation ---
292 math::quat delta_rotation_logical = remap_root_motion_rotation(
293 motion_result.root_transform_delta.get_rotation(),
294 facing_correction);
295
296 // Optionally blend this delta toward identity:
297 auto result_rotate_local = math::slerp(math::identity<math::quat>(),
298 delta_rotation_logical,
299 motion_result.root_rotation_weight);
300 transform_comp.rotate_by_local(result_rotate_local);
301
302 processed_by_root_motion = true;
303 }
304
305 if(!processed_by_root_motion)
306 {
307 armature_transform_comp.set_transform_local(transform);
308 }
309
310 // if(desc.index == root_motion_entity_index)
311 // {
312 // model_comp.update_world_bounds(
313 // armature_transform_comp.get_transform_global());
314 // }
315 }
316 else
317 {
318 APPLOG_WARNING("Cannot find armature with index {}", desc.index);
319 }
320 });
321 }
322 });
323}
324
325void animation_system::on_frame_update(scene& scn, delta_t dt)
326{
327 on_update(scn, dt, false);
328}
329
330} // namespace unravel
static void on_create_component(entt::registry &r, entt::entity e)
Called when the component is created.
void on_play_begin(hpp::span< const entt::handle > entities, delta_t dt)
auto init(rtti::context &ctx) -> bool
auto deinit(rtti::context &ctx) -> bool
static void on_destroy_component(entt::registry &r, entt::entity e)
Called when the component is destroyed.
std::chrono::duration< float > delta_t
uint16_t view
#define APPLOG_TRACE(...)
Definition logging.h:17
const aiScene * scene
auto on_update(entt::registry &reg) -> decltype(auto)
Definition scene.h:44
#define APP_SCOPE_PERF(name_literal)
Create a scoped performance timer that records to the timeline profiler. Only accepts string literals...
Definition profiler.h:675
entt::handle entity
auto get_cached() -> T &
Definition context.hpp:49
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
auto get_scene() -> scene &
Gets the current scene.
Definition ecs.cpp:30
static auto context() -> rtti::context &
Definition engine.cpp:111
hpp::event< void(rtti::context &)> on_play_begin
Definition events.h:24
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:70