diff options
Diffstat (limited to 'src/animation.c')
| -rw-r--r-- | src/animation.c | 517 | 
1 files changed, 517 insertions, 0 deletions
| diff --git a/src/animation.c b/src/animation.c new file mode 100644 index 0000000..c52df73 --- /dev/null +++ b/src/animation.c | |||
| @@ -0,0 +1,517 @@ | |||
| 1 | #include "animation_impl.h" | ||
| 2 | |||
| 3 | #include "memory.h" | ||
| 4 | |||
| 5 | #include <string.h> | ||
| 6 | |||
| 7 | // #include <log/log.h> // Debugging. | ||
| 8 | |||
| 9 | static const R PLAYBACK_UNINITIALIZED = -1; | ||
| 10 | |||
| 11 | static joint_idx get_anima_root_joint_index(Anima* anima) { | ||
| 12 | assert(anima); | ||
| 13 | assert(anima->num_joints > 0); | ||
| 14 | assert(anima->num_joints < GFX_MAX_NUM_JOINTS); | ||
| 15 | return anima->num_joints - 1; | ||
| 16 | } | ||
| 17 | |||
| 18 | static Joint* get_anima_root_joint(Anima* anima) { | ||
| 19 | assert(anima); | ||
| 20 | return &anima->joints[get_anima_root_joint_index(anima)]; | ||
| 21 | } | ||
| 22 | |||
| 23 | static const Joint* get_anima_joint(const Anima* anima, joint_idx index) { | ||
| 24 | assert(anima); | ||
| 25 | assert(index < GFX_MAX_NUM_JOINTS); | ||
| 26 | assert(index != INDEX_NONE); | ||
| 27 | assert(index < anima->num_joints); | ||
| 28 | return &anima->joints[index]; | ||
| 29 | } | ||
| 30 | |||
| 31 | static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) { | ||
| 32 | return (Joint*)get_anima_joint(anima, index); | ||
| 33 | } | ||
| 34 | |||
| 35 | static const Joint* get_skeleton_joint( | ||
| 36 | const Anima* anima, const Skeleton* skeleton, joint_idx index) { | ||
| 37 | assert(anima); | ||
| 38 | assert(skeleton); | ||
| 39 | return get_anima_joint(anima, skeleton->joints[index]); | ||
| 40 | } | ||
| 41 | |||
| 42 | static void set_joint_parent( | ||
| 43 | Anima* anima, joint_idx joint_index, joint_idx parent_index) { | ||
| 44 | assert(anima); | ||
| 45 | assert(joint_index != INDEX_NONE); | ||
| 46 | assert(joint_index != get_anima_root_joint_index(anima)); | ||
| 47 | assert(parent_index != INDEX_NONE); | ||
| 48 | |||
| 49 | Joint* parent = get_anima_joint_mut(anima, parent_index); | ||
| 50 | |||
| 51 | if (parent->child == INDEX_NONE) { | ||
| 52 | parent->child = joint_index; | ||
| 53 | } else { | ||
| 54 | // Find the last child in the chain of children. | ||
| 55 | Joint* child = get_anima_joint_mut(anima, parent->child); | ||
| 56 | while (child->next != INDEX_NONE) { | ||
| 57 | child = get_anima_joint_mut(anima, child->next); | ||
| 58 | } | ||
| 59 | // Wire up this joint as the last child's sibling. | ||
| 60 | child->next = joint_index; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) { | ||
| 65 | assert(anima); | ||
| 66 | assert(desc); | ||
| 67 | assert(joint); | ||
| 68 | |||
| 69 | // The joint matrix needs to be initialized so that meshes look right even if | ||
| 70 | // no animation is played. Initializing joint matrices to the identity makes | ||
| 71 | // meshes appear in their bind pose. | ||
| 72 | joint->child = INDEX_NONE; | ||
| 73 | joint->next = INDEX_NONE; | ||
| 74 | joint->transform = mat4_id(); | ||
| 75 | joint->inv_bind_matrix = desc->inv_bind_matrix; | ||
| 76 | joint->joint_matrix = mat4_id(); | ||
| 77 | joint->box = desc->box; | ||
| 78 | } | ||
| 79 | |||
| 80 | static Skeleton* make_skeleton(const SkeletonDesc* desc) { | ||
| 81 | assert(desc); | ||
| 82 | assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); | ||
| 83 | |||
| 84 | Skeleton* skeleton = mem_alloc_skeleton(); | ||
| 85 | skeleton->num_joints = desc->num_joints; | ||
| 86 | memcpy( | ||
| 87 | skeleton->joints, desc->joints, | ||
| 88 | desc->num_joints * sizeof(skeleton->joints[0])); | ||
| 89 | return skeleton; | ||
| 90 | } | ||
| 91 | |||
| 92 | static Animation* make_animation(const AnimationDesc* desc) { | ||
| 93 | assert(desc); | ||
| 94 | assert(desc->num_channels < GFX_MAX_NUM_CHANNELS); | ||
| 95 | |||
| 96 | Animation* animation = mem_alloc_animation(); | ||
| 97 | animation->name = desc->name; | ||
| 98 | animation->duration = 0; | ||
| 99 | animation->num_channels = desc->num_channels; | ||
| 100 | R start_time = 0; | ||
| 101 | R end_time = 0; | ||
| 102 | |||
| 103 | for (size_t c = 0; c < desc->num_channels; ++c) { | ||
| 104 | const ChannelDesc* channel_desc = &desc->channels[c]; | ||
| 105 | Channel* channel = &animation->channels[c]; | ||
| 106 | |||
| 107 | channel->target = channel_desc->target; | ||
| 108 | channel->type = channel_desc->type; | ||
| 109 | channel->interpolation = channel_desc->interpolation; | ||
| 110 | channel->num_keyframes = channel_desc->num_keyframes; | ||
| 111 | assert(channel_desc->num_keyframes < GFX_MAX_NUM_KEYFRAMES); | ||
| 112 | |||
| 113 | for (size_t k = 0; k < channel_desc->num_keyframes; ++k) { | ||
| 114 | const KeyframeDesc* keyframe_desc = &channel_desc->keyframes[k]; | ||
| 115 | Keyframe* keyframe = &channel->keyframes[k]; | ||
| 116 | |||
| 117 | keyframe->time = keyframe_desc->time; | ||
| 118 | keyframe->translation = keyframe_desc->translation; | ||
| 119 | keyframe->rotation = keyframe_desc->rotation; | ||
| 120 | |||
| 121 | start_time = keyframe->time < start_time ? keyframe->time : start_time; | ||
| 122 | end_time = keyframe->time > end_time ? keyframe->time : end_time; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | // LOGD("Animation start/end: %f / %f", start_time, end_time); | ||
| 127 | animation->duration = end_time - start_time; | ||
| 128 | assert(animation->duration >= 0); | ||
| 129 | return animation; | ||
| 130 | } | ||
| 131 | |||
| 132 | Anima* gfx_make_anima(const AnimaDesc* desc) { | ||
| 133 | assert(desc); | ||
| 134 | assert(desc->num_joints > 0); | ||
| 135 | assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); | ||
| 136 | // All joints should have a parent except for the root. | ||
| 137 | for (size_t i = 0; i < desc->num_joints - 1; ++i) { | ||
| 138 | const joint_idx parent = desc->joints[i].parent; | ||
| 139 | assert(parent != INDEX_NONE); | ||
| 140 | assert(parent < desc->num_joints); | ||
| 141 | } | ||
| 142 | // The root should have no parent. | ||
| 143 | assert(desc->joints[desc->num_joints - 1].parent == INDEX_NONE); | ||
| 144 | |||
| 145 | Anima* anima = mem_alloc_anima(); | ||
| 146 | |||
| 147 | // Wire the skeletons in the same order they are given in the descriptor. | ||
| 148 | Skeleton* last_skeleton = 0; | ||
| 149 | for (size_t i = 0; i < desc->num_skeletons; ++i) { | ||
| 150 | Skeleton* skeleton = make_skeleton(&desc->skeletons[i]); | ||
| 151 | const skeleton_idx skeleton_index = mem_get_skeleton_index(skeleton); | ||
| 152 | if (last_skeleton == 0) { | ||
| 153 | anima->skeleton = skeleton_index; | ||
| 154 | } else { | ||
| 155 | last_skeleton->next = skeleton_index; | ||
| 156 | } | ||
| 157 | last_skeleton = skeleton; | ||
| 158 | } | ||
| 159 | |||
| 160 | // Wire the animations in the same order they are given in the descriptor. | ||
| 161 | Animation* last_animation = 0; | ||
| 162 | for (size_t i = 0; i < desc->num_animations; ++i) { | ||
| 163 | Animation* animation = make_animation(&desc->animations[i]); | ||
| 164 | const animation_idx animation_index = mem_get_animation_index(animation); | ||
| 165 | if (last_animation == 0) { | ||
| 166 | anima->animation = animation_index; | ||
| 167 | } else { | ||
| 168 | last_animation->next = animation_index; | ||
| 169 | } | ||
| 170 | last_animation = animation; | ||
| 171 | } | ||
| 172 | |||
| 173 | // Create joints. | ||
| 174 | anima->num_joints = desc->num_joints; | ||
| 175 | // Initialize all joints. | ||
| 176 | // Child and sibling pointers must be initialized before wiring up the | ||
| 177 | // hierarchy. | ||
| 178 | for (size_t i = 0; i < desc->num_joints; ++i) { | ||
| 179 | Joint* joint = get_anima_joint_mut(anima, i); | ||
| 180 | make_joint(anima, &desc->joints[i], joint); | ||
| 181 | } | ||
| 182 | // Wire up joints to their parents. -1 to skip the root. | ||
| 183 | for (size_t i = 0; i < desc->num_joints - 1; ++i) { | ||
| 184 | set_joint_parent(anima, i, desc->joints[i].parent); | ||
| 185 | } | ||
| 186 | |||
| 187 | return anima; | ||
| 188 | } | ||
| 189 | |||
| 190 | void gfx_destroy_anima(Anima** anima) { | ||
| 191 | assert(anima); | ||
| 192 | |||
| 193 | if (*anima) { | ||
| 194 | for (skeleton_idx i = (*anima)->skeleton; i.val != 0;) { | ||
| 195 | Skeleton* skeleton = mem_get_skeleton(i); | ||
| 196 | i = skeleton->next; | ||
| 197 | mem_free_skeleton(&skeleton); | ||
| 198 | } | ||
| 199 | |||
| 200 | for (animation_idx i = (*anima)->animation; i.val != 0;) { | ||
| 201 | Animation* animation = mem_get_animation(i); | ||
| 202 | i = animation->next; | ||
| 203 | mem_free_animation(&animation); | ||
| 204 | } | ||
| 205 | |||
| 206 | mem_free_anima(anima); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | static Animation* find_animation(animation_idx index, const char* name) { | ||
| 211 | assert(name); | ||
| 212 | |||
| 213 | while (index.val != 0) { | ||
| 214 | Animation* animation = mem_get_animation(index); | ||
| 215 | if (sstring_eq_cstr(animation->name, name)) { | ||
| 216 | // LOGD( | ||
| 217 | // "Found animation at index %u, %s - %s", index.val, | ||
| 218 | // sstring_cstr(&animation->name), name); | ||
| 219 | // LOGD("Animation has duration %f", animation->duration); | ||
| 220 | return animation; | ||
| 221 | } | ||
| 222 | index = animation->next; | ||
| 223 | } | ||
| 224 | |||
| 225 | return 0; | ||
| 226 | } | ||
| 227 | |||
| 228 | bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) { | ||
| 229 | assert(anima); | ||
| 230 | assert(settings); | ||
| 231 | |||
| 232 | // TODO: Should we animate at t=0 here to kickstart the animation? Otherwise | ||
| 233 | // the client is forced to call gfx_update_animation() to do this. | ||
| 234 | Animation* animation = find_animation(anima->animation, settings->name); | ||
| 235 | if (!animation) { | ||
| 236 | return false; | ||
| 237 | } | ||
| 238 | // Playback initialized on first call to update(). | ||
| 239 | AnimationState* state = &anima->state; | ||
| 240 | state->start_time = PLAYBACK_UNINITIALIZED; | ||
| 241 | state->animation = mem_get_animation_index(animation); | ||
| 242 | state->loop = settings->loop; | ||
| 243 | return true; | ||
| 244 | } | ||
| 245 | |||
| 246 | static void gfx_set_joint_position(Joint* joint, vec3 position) { | ||
| 247 | assert(joint); | ||
| 248 | mat4_set_v3(&joint->transform, position); | ||
| 249 | } | ||
| 250 | |||
| 251 | static void gfx_set_joint_rotation(Joint* joint, quat rotation) { | ||
| 252 | assert(joint); | ||
| 253 | mat4_set_3x3(&joint->transform, mat4_from_quat(rotation)); | ||
| 254 | } | ||
| 255 | |||
| 256 | static void find_keyframes(const Channel* channel, R t, int* prev, int* next) { | ||
| 257 | assert(channel); | ||
| 258 | assert(prev); | ||
| 259 | assert(next); | ||
| 260 | |||
| 261 | *prev = -1; | ||
| 262 | *next = 0; | ||
| 263 | while (((*next + 1) < (int)channel->num_keyframes) && | ||
| 264 | (t >= channel->keyframes[*next + 1].time)) { | ||
| 265 | (*prev)++; | ||
| 266 | (*next)++; | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | static R normalize_time(R a, R b, R t) { | ||
| 271 | assert(a <= t); | ||
| 272 | assert(t <= b); | ||
| 273 | return (t - a) / (b - a); | ||
| 274 | } | ||
| 275 | |||
| 276 | static quat interpolate_rotation( | ||
| 277 | const Channel* channel, int prev, int next, R t) { | ||
| 278 | assert(channel); | ||
| 279 | |||
| 280 | if (next == 0) { | ||
| 281 | // Animation has not started at this point in time yet. | ||
| 282 | return channel->keyframes[next].rotation; | ||
| 283 | } else { | ||
| 284 | switch (channel->interpolation) { | ||
| 285 | case StepInterpolation: | ||
| 286 | return channel->keyframes[prev].rotation; | ||
| 287 | case LinearInterpolation: { | ||
| 288 | const R normalized_t = normalize_time( | ||
| 289 | channel->keyframes[prev].time, channel->keyframes[next].time, t); | ||
| 290 | return qnormalize(qslerp( | ||
| 291 | channel->keyframes[prev].rotation, channel->keyframes[next].rotation, | ||
| 292 | normalized_t)); | ||
| 293 | break; | ||
| 294 | } | ||
| 295 | case CubicSplineInterpolation: | ||
| 296 | assert(false); // TODO | ||
| 297 | return qmake(0, 0, 0, 0); | ||
| 298 | default: | ||
| 299 | assert(false); | ||
| 300 | return qmake(0, 0, 0, 0); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | static vec3 interpolate_translation( | ||
| 306 | const Channel* channel, int prev, int next, R t) { | ||
| 307 | assert(channel); | ||
| 308 | |||
| 309 | if (next == 0) { | ||
| 310 | // Animation has not started at this point in time yet. | ||
| 311 | return channel->keyframes[next].translation; | ||
| 312 | } else { | ||
| 313 | switch (channel->interpolation) { | ||
| 314 | case StepInterpolation: | ||
| 315 | return channel->keyframes[prev].translation; | ||
| 316 | case LinearInterpolation: { | ||
| 317 | const R normalized_t = normalize_time( | ||
| 318 | channel->keyframes[prev].time, channel->keyframes[next].time, t); | ||
| 319 | return vec3_lerp( | ||
| 320 | channel->keyframes[prev].translation, | ||
| 321 | channel->keyframes[next].translation, normalized_t); | ||
| 322 | break; | ||
| 323 | } | ||
| 324 | case CubicSplineInterpolation: | ||
| 325 | assert(false); // TODO | ||
| 326 | return vec3_make(0, 0, 0); | ||
| 327 | default: | ||
| 328 | assert(false); | ||
| 329 | return vec3_make(0, 0, 0); | ||
| 330 | } | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | static void animate_channel(Anima* anima, const Channel* channel, R t) { | ||
| 335 | assert(anima); | ||
| 336 | assert(channel); | ||
| 337 | assert(channel->target < anima->num_joints); | ||
| 338 | |||
| 339 | int prev, next; | ||
| 340 | find_keyframes(channel, t, &prev, &next); | ||
| 341 | |||
| 342 | // Note that not all channels extend to the duration of an animation; some | ||
| 343 | // channels may stop animating their targets earlier. Clamp the animation time | ||
| 344 | // to the channel's end keyframe to make the rest of the math (normalize_time) | ||
| 345 | // work. | ||
| 346 | t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t; | ||
| 347 | |||
| 348 | Joint* target = get_anima_joint_mut(anima, channel->target); | ||
| 349 | |||
| 350 | switch (channel->type) { | ||
| 351 | case RotationChannel: { | ||
| 352 | const quat rotation = interpolate_rotation(channel, prev, next, t); | ||
| 353 | gfx_set_joint_rotation(target, rotation); | ||
| 354 | break; | ||
| 355 | } | ||
| 356 | case TranslationChannel: { | ||
| 357 | const vec3 translation = interpolate_translation(channel, prev, next, t); | ||
| 358 | gfx_set_joint_position(target, translation); | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | // Not yet supported. | ||
| 362 | case ScaleChannel: | ||
| 363 | case WeightsChannel: | ||
| 364 | default: | ||
| 365 | // TODO: Add back the assertion or add support for scaling. | ||
| 366 | // assert(false); | ||
| 367 | break; | ||
| 368 | } | ||
| 369 | } | ||
| 370 | |||
| 371 | static void compute_joint_matrices_rec( | ||
| 372 | Anima* anima, Joint* joint, const mat4* parent_global_joint_transform, | ||
| 373 | const mat4* root_inv_global_transform) { | ||
| 374 | assert(anima); | ||
| 375 | assert(joint); | ||
| 376 | assert(parent_global_joint_transform); | ||
| 377 | assert(root_inv_global_transform); | ||
| 378 | |||
| 379 | const mat4 global_joint_transform = | ||
| 380 | mat4_mul(*parent_global_joint_transform, joint->transform); | ||
| 381 | |||
| 382 | // Compute this joint's matrix. | ||
| 383 | joint->joint_matrix = mat4_mul( | ||
| 384 | *root_inv_global_transform, | ||
| 385 | mat4_mul(global_joint_transform, joint->inv_bind_matrix)); | ||
| 386 | |||
| 387 | // Recursively compute the joint matrices for this joint's siblings. | ||
| 388 | if (joint->next != INDEX_NONE) { | ||
| 389 | Joint* sibling = get_anima_joint_mut(anima, joint->next); | ||
| 390 | |||
| 391 | compute_joint_matrices_rec( | ||
| 392 | anima, sibling, parent_global_joint_transform, | ||
| 393 | root_inv_global_transform); | ||
| 394 | } | ||
| 395 | |||
| 396 | // Recursively compute the joint matrices for this joint's children. | ||
| 397 | if (joint->child != INDEX_NONE) { | ||
| 398 | Joint* child = get_anima_joint_mut(anima, joint->child); | ||
| 399 | |||
| 400 | compute_joint_matrices_rec( | ||
| 401 | anima, child, &global_joint_transform, root_inv_global_transform); | ||
| 402 | } | ||
| 403 | } | ||
| 404 | |||
| 405 | void gfx_update_animation(Anima* anima, R t) { | ||
| 406 | assert(anima); | ||
| 407 | |||
| 408 | AnimationState* state = &anima->state; | ||
| 409 | if (state->animation.val == 0) { | ||
| 410 | return; // No active animation. | ||
| 411 | } | ||
| 412 | const Animation* animation = mem_get_animation(state->animation); | ||
| 413 | assert(animation); | ||
| 414 | |||
| 415 | // On a call to play(), the start time is set to -1 to signal that the | ||
| 416 | // animation playback has not yet been initialized. | ||
| 417 | if (state->start_time == PLAYBACK_UNINITIALIZED) { | ||
| 418 | state->start_time = t; | ||
| 419 | } | ||
| 420 | // Locate the current time point inside the animation's timeline. | ||
| 421 | assert(t >= state->start_time); | ||
| 422 | assert(animation->duration >= 0.0); | ||
| 423 | const R local_time = t - state->start_time; | ||
| 424 | const R animation_time = state->loop | ||
| 425 | ? rmod(local_time, animation->duration) | ||
| 426 | : clamp(local_time, 0.0, animation->duration); | ||
| 427 | |||
| 428 | // LOGD( | ||
| 429 | // "animation_time = %f, animation duration: %f", animation_time, | ||
| 430 | // animation->duration); | ||
| 431 | |||
| 432 | // Play through the animation to transform skeleton nodes. | ||
| 433 | for (size_t i = 0; i < animation->num_channels; ++i) { | ||
| 434 | const Channel* channel = &animation->channels[i]; | ||
| 435 | animate_channel(anima, channel, animation_time); | ||
| 436 | } | ||
| 437 | |||
| 438 | // Compute joint matrices after having transformed the skeletons. | ||
| 439 | // | ||
| 440 | // The anima's parent node is the common ancestor of all skeletons, and its | ||
| 441 | // transform maps the skeletons from object space to world space. This is the | ||
| 442 | // transform used as the "global transform" in the joint matrix equations. | ||
| 443 | // | ||
| 444 | // Joint matrix calculation begins by descending from the anima's root joint, | ||
| 445 | // which we have constructed to be the common root of all skeletons. | ||
| 446 | // | ||
| 447 | // This procedure touches every joint exactly once. | ||
| 448 | const mat4 root_global_transform = mat4_id(); | ||
| 449 | const mat4 root_inv_global_transform = mat4_id(); | ||
| 450 | |||
| 451 | Joint* root_joint = get_anima_root_joint(anima); | ||
| 452 | compute_joint_matrices_rec( | ||
| 453 | anima, root_joint, &root_global_transform, &root_inv_global_transform); | ||
| 454 | } | ||
| 455 | |||
| 456 | const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) { | ||
| 457 | assert(anima); | ||
| 458 | |||
| 459 | skeleton_idx skeleton_index = anima->skeleton; | ||
| 460 | const Skeleton* skeleton = mem_get_skeleton(skeleton_index); | ||
| 461 | |||
| 462 | for (size_t j = 1; j < i; ++j) { | ||
| 463 | skeleton_index = skeleton->next; | ||
| 464 | mem_get_skeleton(skeleton_index); | ||
| 465 | } | ||
| 466 | |||
| 467 | return skeleton; | ||
| 468 | } | ||
| 469 | |||
| 470 | size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) { | ||
| 471 | assert(skeleton); | ||
| 472 | return skeleton->num_joints; | ||
| 473 | } | ||
| 474 | |||
| 475 | bool gfx_joint_has_box( | ||
| 476 | const Anima* anima, const Skeleton* skeleton, size_t joint_index) { | ||
| 477 | assert(anima); | ||
| 478 | assert(skeleton); | ||
| 479 | assert(joint_index < skeleton->num_joints); | ||
| 480 | |||
| 481 | const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); | ||
| 482 | return !aabb3_is_empty(joint->box); | ||
| 483 | } | ||
| 484 | |||
| 485 | Box gfx_get_joint_box( | ||
| 486 | const Anima* anima, const Skeleton* skeleton, size_t joint_index) { | ||
| 487 | assert(anima); | ||
| 488 | assert(skeleton); | ||
| 489 | |||
| 490 | const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); | ||
| 491 | |||
| 492 | // Transform the box to anima space. | ||
| 493 | // Note that joint matrices do not usually have a translation since joints | ||
| 494 | // mostly just rotate with respect to their parent. | ||
| 495 | const vec3 pmin = joint->box.min; | ||
| 496 | const vec3 pmax = joint->box.max; | ||
| 497 | return (Box){ | ||
| 498 | .vertices = { | ||
| 499 | mat4_mul_vec3( | ||
| 500 | joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1), | ||
| 501 | mat4_mul_vec3( | ||
| 502 | joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1), | ||
| 503 | mat4_mul_vec3( | ||
| 504 | joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1), | ||
| 505 | mat4_mul_vec3( | ||
| 506 | joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1), | ||
| 507 | mat4_mul_vec3( | ||
| 508 | joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1), | ||
| 509 | mat4_mul_vec3( | ||
| 510 | joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1), | ||
| 511 | mat4_mul_vec3( | ||
| 512 | joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1), | ||
| 513 | mat4_mul_vec3( | ||
| 514 | joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1), | ||
| 515 | } | ||
| 516 | }; | ||
| 517 | } | ||
