Unravel Engine C++ Reference
Loading...
Searching...
No Matches
model_system.cpp
Go to the documentation of this file.
1#include "model_system.h"
4
5#include <engine/ecs/ecs.h>
6#include <engine/events.h>
8
9#include <hpp/small_vector.hpp>
10#include <logging/logging.h>
11
12#define POOLSTL_STD_SUPPLEMENT 1
13#include <poolstl/poolstl.hpp>
14
15namespace unravel
16{
17
18template<typename T>
19using ik_vector = hpp::small_vector<T>;
20
21auto bones_collect(entt::handle end_effector, size_t num_bones_in_chain) -> ik_vector<transform_component*>
22{
23 bool skinned = end_effector.all_of<bone_component>();
24
26 transform_component* current = end_effector.try_get<transform_component>();
27 chain.push_back(current);
28
29 // Collect bones from the end effector upward.
30 while(current != nullptr && chain.size() < num_bones_in_chain + 1)
31 {
32 auto parent = current->get_parent();
33 if(parent)
34 {
35 current = parent.try_get<transform_component>();
36
37 if(skinned)
38 {
39 if(auto bone = parent.try_get<bone_component>())
40 {
41 if(bone->bone_index == 0)
42 {
43 break;
44 }
45
46 chain.push_back(current);
47 }
48 }
49 else
50 {
51 chain.push_back(current);
52 }
53 }
54 else
55 {
56 break;
57 }
58 }
59 // The chain was built from end effector upward; reverse it to have it from root to end effector.
60 std::reverse(chain.begin(), chain.end());
61 return chain;
62}
63
64// Computes the global position of the bone's "end" (tip).
65auto get_end_position(transform_component* comp) -> math::vec3
66{
67 return comp->get_position_global();
68}
69
70// Advanced CCD IK solver with unreachable target handling and non-linear weighting.
72 math::vec3 target,
73 float threshold = 0.001f,
74 int maxIterations = 10,
75 float damping_error_threshold = 0.5f,
76 float weight_exponent = 1.0f) -> bool
77{
78 transform_component* end_effector = chain.back();
79 const size_t chain_size = chain.size();
80
81 // ----- Unreachable Target Handling -----
82
83 // We approximate bone lengths as the distance from each bone to the end effector.
84 float max_reach = 0.f;
85 for(size_t i = 0; i < chain.size() - 1; ++i)
86 {
87 math::vec3 diff = chain[i + 1]->get_position_global() - chain[i]->get_position_global();
88 max_reach += math::length(diff);
89 }
90 // Clamp the target if it lies outside the reachable sphere.
91 math::vec3 base_position = chain.front()->get_position_global();
92 math::vec3 target_dir = target - base_position;
93 float target_dist = math::length(target_dir);
94 if(target_dist > max_reach)
95 {
96 target_dir = math::normalize(target_dir);
97 target = base_position + target_dir * (max_reach - 0.001f);
98 }
99 // ------------------------------------------
100
101 // Main CCD iteration loop.
102 for(int iter = 0; iter < maxIterations; ++iter)
103 {
104 // Traverse the chain from the bone before the end effector to the root.
105 for(int i = static_cast<int>(chain_size) - 2; i >= 0; --i)
106 {
107 transform_component* bone = chain[i];
108 math::vec3 bone_pos = bone->get_position_global();
109 math::vec3 current_end_pos = get_end_position(end_effector);
110
111 // Compute direction vectors from the current bone to the end effector and target.
112 math::vec3 to_end = current_end_pos - bone_pos;
113 math::vec3 to_target = target - bone_pos;
114
115 float len_to_end = math::length(to_end);
116 float len_to_target = math::length(to_target);
117
118 // Skip this bone if either vector is degenerate.
119 if(len_to_end < math::epsilon<float>() || len_to_target < math::epsilon<float>())
120 {
121 continue;
122 }
123
124 to_end = math::normalize(to_end);
125 to_target = math::normalize(to_target);
126
127 // Calculate the angle between the current direction and the target direction.
128 float cos_angle = math::clamp(math::dot(to_end, to_target), -1.0f, 1.0f);
129 float angle = math::acos(cos_angle);
130
131 // Skip tiny adjustments.
132 if(std::fabs(angle) < 1e-3f)
133 {
134 continue;
135 }
136
137 // Determine the rotation axis in global space.
138 math::vec3 rotation_axis = math::cross(to_end, to_target);
139 if(math::length(rotation_axis) < 1e-4f)
140 {
141 continue;
142 }
143 rotation_axis = math::normalize(rotation_axis);
144
145 // ----- Dynamic Damping -----
146 // Scale the rotation angle based on the current global error.
147 float global_error = math::length(target - current_end_pos);
148 float damping_factor = math::clamp(global_error / damping_error_threshold, 0.0f, 1.0f);
149 float damped_angle = angle * damping_factor;
150 // ---------------------------
151
152 // Compute the rotation quaternion (in global space) for the damped angle.
153 math::quat rotation_delta = math::angleAxis(damped_angle, rotation_axis);
154
155 // Convert the global rotation to the bone's local space.
156 auto parent = bone->get_parent();
157 transform_component* parent_trans = parent ? parent.try_get<transform_component>() : nullptr;
158 math::quat parent_global_rot =
159 (parent_trans) ? parent_trans->get_rotation_global() : math::identity<math::quat>();
160 math::quat local_rotation_delta = glm::inverse(parent_global_rot) * rotation_delta * parent_global_rot;
161
162 // ----- Non-Linear Weighting -----
163 // Bones closer to the end effector have more influence.
164 // Using a non-linear weighting function allows more control over influence falloff.
165 float t = float(i + 1) / float(chain_size); // Linear ratio [0,1]
166 float weight = std::pow(t, weight_exponent);
167 math::quat weighted_local_rotation_delta =
168 glm::slerp(math::identity<math::quat>(), local_rotation_delta, weight);
169 // ---------------------------------
170
171 // (Optional: Here you could enforce joint limits on the resulting rotation.)
172
173 // Update the bone's local rotation.
174 bone->set_rotation_local(math::normalize(weighted_local_rotation_delta * bone->get_rotation_local()));
175
176 // Check overall error after applying the rotation.
177 current_end_pos = get_end_position(end_effector);
178 float current_error = math::length(target - current_end_pos);
179 if(current_error < threshold)
180 {
181 return true; // Target reached.
182 }
183 }
184 }
185 return false; // Target not reached within the iteration limit.
186}
187
188// FABRIK IK Solver (Advanced)
189// Uses the chain’s rest configuration to compute per‐bone rest directions,
190// then iteratively updates joint positions and finally adjusts bone rotations
191// so that each bone’s tip aligns with its new child joint position.
192//
193// The chain is provided as a vector of transform_component pointers,
194// ordered from the root (index 0) to the end effector (last element).
195//
196// NOTE: This implementation assumes that, before IK, the bone positions
197// in the chain (obtained via get_position_global()) reflect the rest pose.
198// If your system stores a separate rest offset (or tip offset), you can substitute that.
200 const math::vec3& target,
201 float threshold = 0.001f,
202 int max_iterations = 10) -> bool
203{
204 const size_t n = chain.size();
205 if(n < 2)
206 return false; // Need at least two joints
207
208 // STEP 1: Capture the original (rest) joint positions.
209 ik_vector<math::vec3> orig_positions(n);
210 for(size_t i = 0; i < n; ++i)
211 {
212 orig_positions[i] = chain[i]->get_position_global();
213 }
214
215 // STEP 2: Initialize the working positions for IK.
216 ik_vector<math::vec3> positions = orig_positions;
217
218 // Compute bone lengths from the rest positions.
219 ik_vector<float> bone_lengths(n - 1, 0.f);
220 float total_length = 0.f;
221 for(size_t i = 0; i < n - 1; ++i)
222 {
223 bone_lengths[i] = math::length(orig_positions[i + 1] - orig_positions[i]);
224 total_length += bone_lengths[i];
225 }
226
227 // Store the root position.
228 const math::vec3 root_pos = positions[0];
229
230 // STEP 3: Handle unreachable target.
231 if(math::length(target - root_pos) > total_length)
232 {
233 // Target is unreachable: stretch the chain toward the target.
234 math::vec3 dir = math::normalize(target - root_pos);
235 for(size_t i = 0; i < n - 1; ++i)
236 {
237 positions[i + 1] = positions[i] + dir * bone_lengths[i];
238 }
239 }
240 else
241 {
242 // Target is reachable: perform iterative forward and backward passes.
243 for(int iter = 0; iter < max_iterations; ++iter)
244 {
245 // BACKWARD REACHING: Set the end effector to the target.
246 positions[n - 1] = target;
247 for(int i = static_cast<int>(n) - 2; i >= 0; --i)
248 {
249 float r = math::length(positions[i + 1] - positions[i]);
250 float lambda = bone_lengths[i] / r;
251 positions[i] = (1 - lambda) * positions[i + 1] + lambda * positions[i];
252 }
253
254 // FORWARD REACHING: Reset the root and move joints forward.
255 positions[0] = root_pos;
256 for(size_t i = 0; i < n - 1; ++i)
257 {
258 float r = math::length(positions[i + 1] - positions[i]);
259 float lambda = bone_lengths[i] / r;
260 positions[i + 1] = (1 - lambda) * positions[i] + lambda * positions[i + 1];
261 }
262
263 // Check if the end effector is within threshold of the target.
264 if(math::length(positions[n - 1] - target) < threshold)
265 {
266 break;
267 }
268 }
269 }
270
271 // STEP 4: Update bone rotations (mirroring CCDIK logic).
272 for(size_t i = 0; i < n - 1; ++i)
273 {
274 transform_component* bone = chain[i];
275 transform_component* child = chain[i + 1];
276
277 // Current direction: where the bone is pointing right now
278 math::vec3 current_pos = bone->get_position_global();
279 math::vec3 child_pos = child->get_position_global();
280 math::vec3 current_dir = math::normalize(child_pos - current_pos);
281
282 // Desired direction: where it *should* be pointing now after FABRIK
283 math::vec3 desired_dir = math::normalize(positions[i + 1] - positions[i]);
284
285 // If the vectors are too short or nearly aligned, skip
286 if(math::length(current_dir) < 1e-5f || math::length(desired_dir) < 1e-5f)
287 {
288 continue;
289 }
290
291 float dot = math::clamp(math::dot(current_dir, desired_dir), -1.f, 1.f);
292 if(dot > 0.9999f) // Almost aligned
293 {
294 continue;
295 }
296
297 math::vec3 axis = math::cross(current_dir, desired_dir);
298 if(math::length(axis) < 1e-5f)
299 {
300 continue;
301 }
302
303 axis = math::normalize(axis);
304 float angle = math::acos(dot);
305 math::quat rotation_delta = math::angleAxis(angle, axis);
306
307 // Convert rotation from world to local space
308 auto parent = bone->get_parent();
309 transform_component* parent_trans = parent ? parent.try_get<transform_component>() : nullptr;
310 math::quat parent_global_rot =
311 (parent_trans) ? parent_trans->get_rotation_global() : math::identity<math::quat>();
312
313 math::quat local_rotation_delta = glm::inverse(parent_global_rot) * rotation_delta * parent_global_rot;
314
315 // Apply the rotation delta to the local rotation
316 math::quat new_local = math::normalize(local_rotation_delta * bone->get_rotation_local());
317 bone->set_rotation_local(new_local);
318 }
319
320 // (Optionally update the end effector orientation if desired.)
321
322 return true;
323}
324
325// Helper functions to transform points and vectors in homogeneous coordinates.
326inline glm::vec3 transform_point(const glm::mat4& mat, const glm::vec3& point)
327{
328 return glm::vec3(mat * glm::vec4(point, 1.0f));
329}
330
331inline glm::vec3 transform_vector(const glm::mat4& mat, const glm::vec3& vec)
332{
333 return glm::vec3(mat * glm::vec4(vec, 0.0f));
334}
335
348 transform_component* mid_joint,
349 transform_component* end_joint,
350 const glm::vec3& target,
351 const glm::vec3& mid_axis, // desired bending axis (global)
352 const glm::vec3& pole_vector, // reference pole vector (global)
353 float twist_angle,
354 float weight,
355 float soften)
356{
357 // --- 1. Retrieve Global Transforms and Positions ---
358 glm::mat4 start_transform = start_joint->get_transform_global();
359 glm::mat4 mid_transform = mid_joint->get_transform_global();
360 glm::mat4 end_transform = end_joint->get_transform_global();
361
362 glm::vec3 start_pos = start_joint->get_position_global();
363 glm::vec3 mid_pos = mid_joint->get_position_global();
364 glm::vec3 end_pos = end_joint->get_position_global();
365
366 // --- 2. Compute Inverse Transforms ---
367 glm::mat4 inv_start = glm::inverse(start_transform);
368 glm::mat4 inv_mid = glm::inverse(mid_transform);
369
370 // --- 3. Set Up Constant Data ---
371
372 // Transform positions into mid joint space.
373 glm::vec3 start_ms = transform_point(inv_mid, start_pos);
374 glm::vec3 end_ms = transform_point(inv_mid, end_pos);
375 // In mid joint space, the mid joint is the origin.
376 glm::vec3 start_mid_ms = -start_ms; // vector from mid joint to start joint.
377 glm::vec3 mid_end_ms = end_ms; // vector from mid joint to end effector.
378
379 // Transform positions into start joint space.
380 glm::vec3 mid_ss = transform_point(inv_start, mid_pos);
381 glm::vec3 end_ss = transform_point(inv_start, end_pos);
382 glm::vec3 start_mid_ss = mid_ss; // start joint is at the origin.
383 glm::vec3 mid_end_ss = end_ss - mid_ss; // vector from mid joint to end effector.
384 glm::vec3 start_end_ss = end_ss; // vector from start joint to end effector.
385
386 float start_mid_ss_len2 = glm::dot(start_mid_ss, start_mid_ss);
387 float mid_end_ss_len2 = glm::dot(mid_end_ss, mid_end_ss);
388 float start_end_ss_len2 = glm::dot(start_end_ss, start_end_ss);
389
390 // --- 4. Soften the Target Position ---
391 // Transform target into start joint space.
392 glm::vec3 start_target_ss_orig = transform_point(inv_start, target);
393 float start_target_ss_orig_len = glm::length(start_target_ss_orig);
394 float start_target_ss_orig_len2 = start_target_ss_orig_len * start_target_ss_orig_len;
395
396 // Compute bone lengths in start joint space.
397 float l0 = std::sqrt(start_mid_ss_len2); // upper bone length
398 float l1 = std::sqrt(mid_end_ss_len2); // lower bone length
399 float chain_length = l0 + l1;
400 float bone_diff = fabs(l0 - l1);
401
402 // Compute softening parameters.
403 float da = chain_length * glm::clamp(soften, 0.0f, 1.0f);
404 float ds = chain_length - da;
405
406 glm::vec3 start_target_ss;
407 float start_target_ss_len2;
408 bool target_softened = false;
409 if(start_target_ss_orig_len > da && start_target_ss_orig_len > bone_diff && ds > 1e-4f)
410 {
411 float alpha = (start_target_ss_orig_len - da) / ds;
412 // Exponential-like blend: ratio = 1 - (3^4)/((alpha+3)^4)
413 float ratio = 1.0f - (std::pow(3.0f, 4.0f) / std::pow(alpha + 3.0f, 4.0f));
414 float new_target_len = da + ds - ds * ratio;
415 start_target_ss = glm::normalize(start_target_ss_orig) * new_target_len;
416 start_target_ss_len2 = new_target_len * new_target_len;
417 target_softened = true;
418 }
419 else
420 {
421 start_target_ss = start_target_ss_orig;
422 start_target_ss_len2 = start_target_ss_orig_len2;
423 }
424
425 // --- 5. Compute the Mid Joint (Knee) Correction ---
426 // Compute the "corrected" knee angle using the law of cosines.
427 float cos_corrected = (start_mid_ss_len2 + mid_end_ss_len2 - start_target_ss_len2) / (2.0f * l0 * l1);
428 cos_corrected = glm::clamp(cos_corrected, -1.0f, 1.0f);
429 float corrected_angle = std::acos(cos_corrected);
430
431 // Compute the "initial" knee angle from the original effector position.
432 float cos_initial = (start_mid_ss_len2 + mid_end_ss_len2 - start_end_ss_len2) / (2.0f * l0 * l1);
433 cos_initial = glm::clamp(cos_initial, -1.0f, 1.0f);
434 float initial_angle = std::acos(cos_initial);
435
436 // Adjust the sign of the initial angle based on the bending direction.
437 glm::vec3 mid_axis_ms = glm::normalize(glm::mat3(inv_mid) * mid_axis);
438 glm::vec3 bent_side_ref = glm::cross(start_mid_ms, mid_axis_ms);
439 if(glm::dot(bent_side_ref, mid_end_ms) < 0.0f)
440 {
441 initial_angle = -initial_angle;
442 }
443
444 float angle_delta = corrected_angle - initial_angle;
445 // Mid joint correction quaternion (rotation about the (global) mid_axis).
446 glm::quat mid_rot = glm::angleAxis(angle_delta, glm::normalize(mid_axis));
447
448 // --- 6. Compute the Start Joint Correction ---
449 // Predict the effector position given the mid correction.
450 glm::vec3 rotated_mid_end_ms = glm::rotate(mid_rot, mid_end_ms);
451 glm::vec3 rotated_mid_end_global = glm::mat3(mid_transform) * rotated_mid_end_ms;
452 glm::vec3 mid_end_ss_final = glm::mat3(inv_start) * rotated_mid_end_global;
453 glm::vec3 start_end_ss_final = start_mid_ss + mid_end_ss_final;
454
455 // Compute the rotation aligning the predicted effector direction to the softened target.
456 glm::quat end_to_target_rot_ss = glm::rotation(start_end_ss_final, start_target_ss);
457 glm::quat start_rot_ss = end_to_target_rot_ss;
458
459 // Compute the "rotate-plane" correction if the target direction is valid.
460 if(glm::length(start_target_ss) > 1e-4f)
461 {
462 // Transform the pole vector into start joint space.
463 glm::vec3 pole_ss = glm::normalize(glm::mat3(inv_start) * pole_vector);
464 // Compute the reference plane normal (cross of target and pole).
465 glm::vec3 ref_plane_normal_ss = glm::normalize(glm::cross(start_target_ss, pole_ss));
466 // Compute mid_axis in start joint space.
467 glm::vec3 mid_axis_ss = glm::normalize(glm::mat3(inv_start) * (glm::mat3(mid_transform) * mid_axis));
468 // Joint chain plane normal (rotated by end_to_target rotation).
469 glm::vec3 joint_plane_normal_ss = glm::rotate(end_to_target_rot_ss, mid_axis_ss);
470
471 float rotate_plane_cos_angle =
472 glm::dot(glm::normalize(ref_plane_normal_ss), glm::normalize(joint_plane_normal_ss));
473 rotate_plane_cos_angle = glm::clamp(rotate_plane_cos_angle, -1.0f, 1.0f);
474
475 // Rotation axis is along the softened target direction (flip if needed).
476 glm::vec3 rotate_plane_axis_ss = glm::normalize(start_target_ss);
477 if(glm::dot(joint_plane_normal_ss, pole_ss) < 0.0f)
478 {
479 rotate_plane_axis_ss = -rotate_plane_axis_ss;
480 }
481 glm::quat rotate_plane_ss = glm::angleAxis(std::acos(rotate_plane_cos_angle), rotate_plane_axis_ss);
482
483 // Apply twist if provided.
484 if(fabs(twist_angle) > 1e-5f)
485 {
486 glm::quat twist_ss = glm::angleAxis(twist_angle, glm::normalize(start_target_ss));
487 start_rot_ss = twist_ss * rotate_plane_ss * end_to_target_rot_ss;
488 }
489 else
490 {
491 start_rot_ss = rotate_plane_ss * end_to_target_rot_ss;
492 }
493 }
494
495 // --- 7. Weighting and Final Output ---
496 // Force the scalar (w) to be positive.
497 if(start_rot_ss.w < 0.0f)
498 start_rot_ss = -start_rot_ss;
499 if(mid_rot.w < 0.0f)
500 mid_rot = -mid_rot;
501
502 // Blend with identity if weight is less than 1.
503 if(weight < 1.0f)
504 {
505 start_rot_ss = glm::slerp(glm::identity<glm::quat>(), start_rot_ss, weight);
506 mid_rot = glm::slerp(glm::identity<glm::quat>(), mid_rot, weight);
507 }
508
509 // 'reached' is set when softening was applied and weight is full.
510 bool reached = target_softened && (weight >= 1.0f);
511
512 if(reached)
513 {
514 mid_joint->set_rotation_local(glm::normalize(mid_rot));
515 start_joint->set_rotation_local(glm::normalize(start_rot_ss));
516 }
517
518 return reached;
519}
520
522 transform_component* mid_joint,
523 transform_component* end_joint,
524 const math::vec3& target,
525 float weight = 1.0f,
526 float soften = 1.0f,
527 const math::vec3& pole = math::vec3(0, 0, 1),
528 float twist_angle = 0.f) -> bool
529{
530 math::vec3 mid_axis = mid_joint->get_z_axis_global();
531 return solve_two_bone_ik_impl(start_joint, mid_joint, end_joint, target, pole, pole, twist_angle, weight, soften);
532}
533
534//--------------------------------------
535// CCD IK Solver Implementation (Parent-Chain Version)
536//--------------------------------------
537/*
538 This overload of CCDIK takes an end effector and a maximum chain length.
539 It builds the IK chain by following parent upward until it reaches the specified count.
540*/
541auto ik_set_position_ccd(entt::handle end_effector,
542 const math::vec3& target,
543 size_t num_bones_in_chain,
544 float threshold,
545 int max_iterations) -> bool
546{
547 auto bones = bones_collect(end_effector, num_bones_in_chain);
548 return ccdik_advanced(bones, target, threshold, max_iterations);
549}
550
551auto ik_set_position_fabrik(entt::handle end_effector,
552 const math::vec3& target,
553 size_t num_bones_in_chain,
554 float threshold,
555 int max_iterations) -> bool
556{
557 auto bones = bones_collect(end_effector, num_bones_in_chain);
558 return fabrik(bones, target, threshold, max_iterations);
559}
560
561auto ik_set_position_two_bone(entt::handle end_effector,
562 const math::vec3& target,
563 const math::vec3& forward,
564 float weight,
565 float soften,
566 int max_iterations) -> bool
567{
568 auto bones = bones_collect(end_effector, 2);
569 if(bones.size() == 3)
570 {
571 auto root = bones[0];
572 auto joint = bones[1];
573 auto end = bones[2];
574
575 for(int i = 0; i < max_iterations; ++i)
576 {
577 if(solve_two_bone_ik_weighted(root, joint, end, target, weight, soften, forward))
578 {
579 return true;
580 }
581 }
582 }
583
584 return fabrik(bones, target, 0.001f, max_iterations);
585}
586
587auto ik_look_at_position(entt::handle end_effector, const math::vec3& target, float weight) -> bool
588{
589 auto bones = bones_collect(end_effector, 0);
590
591 auto bone = bones.front();
592
593 // 1) compute the desired “look at” rotation
594 math::vec3 eye = bone->get_position_global();
595 math::transform lookM = math::lookAt(eye, target, bone->get_y_axis_global());
596 lookM = math::inverse(lookM);
597 math::quat desired = lookM.get_rotation();
598
599 // 2) fetch current rotation
600 math::quat current = bone->get_rotation_global();
601
602 // 3) slerp toward desired by boneWeight
603 math::quat blended = math::slerp(current, desired, weight);
604
605 // 4) apply
606 bone->set_rotation_global(blended);
607
608 // bone->look_at(target, bone->get_y_axis_global());
609 return true;
610}
611
613{
614 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
615 auto& ev = ctx.get_cached<events>();
616
617 ev.on_play_begin.connect(sentinel_, 1000, this, &model_system::on_play_begin);
618
619 return true;
620}
621
623{
624 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
625
626 return true;
627}
628
630{
631 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
632
633 auto& ec = ctx.get_cached<ecs>();
634 auto& scn = ec.get_scene();
635
636 auto view = scn.registry->view<model_component>();
637
638 // this pass can create new entities so we cannot parallelize it
639 std::for_each(view.begin(),
640 view.end(),
641 [&](entt::entity entity)
642 {
643 auto& model_comp = view.get<model_component>(entity);
644 model_comp.init_armature(true);
645 });
646}
647
649{
650 APP_SCOPE_PERF("Model/System Update");
651
653
654 // this pass can create new entities so we cannot parallelize it
655 std::for_each(view.begin(),
656 view.end(),
657 [&](entt::entity entity)
658 {
659 auto& model_comp = view.get<model_component>(entity);
660 model_comp.init_armature(false);
661 });
662}
663
665{
666 APP_SCOPE_PERF("Model/Skinning");
668
669 // this code should be thread safe as each task works with a whole hierarchy and
670 // there is no interleaving between tasks.
671 std::for_each(std::execution::par,
672 view.begin(),
673 view.end(),
674 [&](entt::entity entity)
675 {
676 auto& transform_comp = view.get<transform_component>(entity);
677 auto& model_comp = view.get<model_component>(entity);
678
679 if(model_comp.was_used_last_frame())
680 {
681 model_comp.update_armature();
682 }
683
684 model_comp.update_world_bounds(transform_comp.get_transform_global());
685 });
686}
687
688void model_system::on_play_begin(hpp::span<const entt::handle> entities, delta_t dt)
689{
690 for(auto entity : entities)
691 {
692 if(auto model_comp = entity.try_get<model_component>())
693 {
694 model_comp->init_armature(false);
695
696 auto& transform_comp = entity.get<transform_component>();
697
698 model_comp->update_world_bounds(transform_comp.get_transform_global());
699 }
700 }
701}
702
703} // namespace unravel
General purpose transformation class designed to maintain each component of the transformation separa...
Definition transform.hpp:27
auto get_rotation() const noexcept -> const quat_t &
Get the rotation component.
Class that contains core data for meshes.
auto deinit(rtti::context &ctx) -> bool
void on_frame_before_render(scene &scn, delta_t dt)
void on_play_begin(rtti::context &ctx)
void on_frame_update(scene &scn, delta_t dt)
auto init(rtti::context &ctx) -> bool
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
auto get_rotation_local() const noexcept -> const math::quat &
Gets the local rotation.
auto get_position_global() const noexcept -> const math::vec3 &
TRANSLATION.
auto get_rotation_global() const noexcept -> const math::quat &
ROTATION.
auto get_z_axis_global() const noexcept -> math::vec3
Gets the global Z-axis.
auto get_transform_global() const noexcept -> const math::transform &
Gets the global transform.
void set_rotation_local(const math::quat &rotation) noexcept
Sets the local rotation.
auto get_parent() const noexcept -> entt::handle
RELATIONSHIP.
std::chrono::duration< float > delta_t
#define APPLOG_TRACE(...)
Definition logging.h:17
auto inverse(transform_t< T, Q > const &t) noexcept -> transform_t< T, Q >
auto ccdik_advanced(ik_vector< transform_component * > &chain, math::vec3 target, float threshold=0.001f, int maxIterations=10, float damping_error_threshold=0.5f, float weight_exponent=1.0f) -> bool
auto ik_set_position_ccd(entt::handle end_effector, const math::vec3 &target, size_t num_bones_in_chain, float threshold, int max_iterations) -> bool
bool solve_two_bone_ik_impl(transform_component *start_joint, transform_component *mid_joint, transform_component *end_joint, const glm::vec3 &target, const glm::vec3 &mid_axis, const glm::vec3 &pole_vector, float twist_angle, float weight, float soften)
Two-bone IK solver.
glm::vec3 transform_vector(const glm::mat4 &mat, const glm::vec3 &vec)
auto fabrik(ik_vector< transform_component * > &chain, const math::vec3 &target, float threshold=0.001f, int max_iterations=10) -> bool
auto ik_set_position_two_bone(entt::handle end_effector, const math::vec3 &target, const math::vec3 &forward, float weight, float soften, int max_iterations) -> bool
auto ik_set_position_fabrik(entt::handle end_effector, const math::vec3 &target, size_t num_bones_in_chain, float threshold, int max_iterations) -> bool
auto ik_look_at_position(entt::handle end_effector, const math::vec3 &target, float weight) -> bool
hpp::small_vector< T > ik_vector
auto solve_two_bone_ik_weighted(transform_component *start_joint, transform_component *mid_joint, transform_component *end_joint, const math::vec3 &target, float weight=1.0f, float soften=1.0f, const math::vec3 &pole=math::vec3(0, 0, 1), float twist_angle=0.f) -> bool
auto get_end_position(transform_component *comp) -> math::vec3
auto bones_collect(entt::handle end_effector, size_t num_bones_in_chain) -> ik_vector< transform_component * >
glm::vec3 transform_point(const glm::mat4 &mat, const glm::vec3 &point)
#define APP_SCOPE_PERF(name)
Create a scoped performance timer that only accepts string literals.
Definition profiler.h:160
entt::entity 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
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:21
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
Definition scene.h:117