diff options
| author | 3gg <3gg@shellblade.net> | 2025-06-27 10:18:39 -0700 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-06-27 10:18:39 -0700 |
| commit | bd57f345ed9dbed1d81683e48199626de2ea9044 (patch) | |
| tree | 4221f2f2a7ad2244d2e93052bd68187ec91b8ea9 /src/scene | |
| parent | 9a82ce0083437a4f9f58108b2c23b957d2249ad8 (diff) | |
Restructure project
Diffstat (limited to 'src/scene')
| -rw-r--r-- | src/scene/animation.c | 524 | ||||
| -rw-r--r-- | src/scene/animation_impl.h | 98 | ||||
| -rw-r--r-- | src/scene/camera.c | 37 | ||||
| -rw-r--r-- | src/scene/camera_impl.h | 12 | ||||
| -rw-r--r-- | src/scene/light.c | 42 | ||||
| -rw-r--r-- | src/scene/light_impl.h | 25 | ||||
| -rw-r--r-- | src/scene/material.c | 57 | ||||
| -rw-r--r-- | src/scene/material_impl.h | 16 | ||||
| -rw-r--r-- | src/scene/mesh.c | 24 | ||||
| -rw-r--r-- | src/scene/mesh_impl.h | 12 | ||||
| -rw-r--r-- | src/scene/model.c | 45 | ||||
| -rw-r--r-- | src/scene/model_impl.h | 17 | ||||
| -rw-r--r-- | src/scene/node.c | 409 | ||||
| -rw-r--r-- | src/scene/node_impl.h | 40 | ||||
| -rw-r--r-- | src/scene/object.c | 83 | ||||
| -rw-r--r-- | src/scene/object_impl.h | 26 | ||||
| -rw-r--r-- | src/scene/scene.c | 25 | ||||
| -rw-r--r-- | src/scene/scene_graph.h | 138 | ||||
| -rw-r--r-- | src/scene/scene_impl.h | 13 | ||||
| -rw-r--r-- | src/scene/scene_memory.c | 149 | ||||
| -rw-r--r-- | src/scene/scene_memory.h | 39 | ||||
| -rw-r--r-- | src/scene/types.h | 24 |
22 files changed, 1855 insertions, 0 deletions
diff --git a/src/scene/animation.c b/src/scene/animation.c new file mode 100644 index 0000000..08d02ce --- /dev/null +++ b/src/scene/animation.c | |||
| @@ -0,0 +1,524 @@ | |||
| 1 | #include "animation_impl.h" | ||
| 2 | |||
| 3 | #include "node_impl.h" | ||
| 4 | #include "scene_memory.h" | ||
| 5 | |||
| 6 | #include <string.h> | ||
| 7 | |||
| 8 | // #include <log/log.h> // Debugging. | ||
| 9 | |||
| 10 | static const R PLAYBACK_UNINITIALIZED = -1; | ||
| 11 | |||
| 12 | static joint_idx get_anima_root_joint_index(Anima* anima) { | ||
| 13 | assert(anima); | ||
| 14 | assert(anima->num_joints > 0); | ||
| 15 | assert(anima->num_joints < GFX_MAX_NUM_JOINTS); | ||
| 16 | return anima->num_joints - 1; | ||
| 17 | } | ||
| 18 | |||
| 19 | static Joint* get_anima_root_joint(Anima* anima) { | ||
| 20 | assert(anima); | ||
| 21 | return &anima->joints[get_anima_root_joint_index(anima)]; | ||
| 22 | } | ||
| 23 | |||
| 24 | static const Joint* get_anima_joint(const Anima* anima, joint_idx index) { | ||
| 25 | assert(anima); | ||
| 26 | assert(index < GFX_MAX_NUM_JOINTS); | ||
| 27 | assert(index != INDEX_NONE); | ||
| 28 | assert(index < anima->num_joints); | ||
| 29 | return &anima->joints[index]; | ||
| 30 | } | ||
| 31 | |||
| 32 | static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) { | ||
| 33 | return (Joint*)get_anima_joint(anima, index); | ||
| 34 | } | ||
| 35 | |||
| 36 | static const Joint* get_skeleton_joint( | ||
| 37 | const Anima* anima, const Skeleton* skeleton, joint_idx index) { | ||
| 38 | assert(anima); | ||
| 39 | assert(skeleton); | ||
| 40 | return get_anima_joint(anima, skeleton->joints[index]); | ||
| 41 | } | ||
| 42 | |||
| 43 | static void set_joint_parent( | ||
| 44 | Anima* anima, joint_idx joint_index, joint_idx parent_index) { | ||
| 45 | assert(anima); | ||
| 46 | assert(joint_index != INDEX_NONE); | ||
| 47 | assert(joint_index != get_anima_root_joint_index(anima)); | ||
| 48 | assert(parent_index != INDEX_NONE); | ||
| 49 | |||
| 50 | Joint* parent = get_anima_joint_mut(anima, parent_index); | ||
| 51 | |||
| 52 | if (parent->child == INDEX_NONE) { | ||
| 53 | parent->child = joint_index; | ||
| 54 | } else { | ||
| 55 | // Find the last child in the chain of children. | ||
| 56 | Joint* child = get_anima_joint_mut(anima, parent->child); | ||
| 57 | while (child->next != INDEX_NONE) { | ||
| 58 | child = get_anima_joint_mut(anima, child->next); | ||
| 59 | } | ||
| 60 | // Wire up this joint as the last child's sibling. | ||
| 61 | child->next = joint_index; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) { | ||
| 66 | assert(anima); | ||
| 67 | assert(desc); | ||
| 68 | assert(joint); | ||
| 69 | |||
| 70 | // The joint matrix needs to be initialized so that meshes look right even if | ||
| 71 | // no animation is played. Initializing joint matrices to the identity makes | ||
| 72 | // meshes appear in their bind pose. | ||
| 73 | joint->child = INDEX_NONE; | ||
| 74 | joint->next = INDEX_NONE; | ||
| 75 | joint->transform = mat4_id(); | ||
| 76 | joint->inv_bind_matrix = desc->inv_bind_matrix; | ||
| 77 | joint->joint_matrix = mat4_id(); | ||
| 78 | joint->box = desc->box; | ||
| 79 | } | ||
| 80 | |||
| 81 | static Skeleton* make_skeleton(const SkeletonDesc* desc) { | ||
| 82 | assert(desc); | ||
| 83 | assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); | ||
| 84 | |||
| 85 | Skeleton* skeleton = mem_alloc_skeleton(); | ||
| 86 | skeleton->num_joints = desc->num_joints; | ||
| 87 | memcpy( | ||
| 88 | skeleton->joints, desc->joints, | ||
| 89 | desc->num_joints * sizeof(skeleton->joints[0])); | ||
| 90 | return skeleton; | ||
| 91 | } | ||
| 92 | |||
| 93 | static Animation* make_animation(const AnimationDesc* desc) { | ||
| 94 | assert(desc); | ||
| 95 | assert(desc->num_channels < GFX_MAX_NUM_CHANNELS); | ||
| 96 | |||
| 97 | Animation* animation = mem_alloc_animation(); | ||
| 98 | animation->name = desc->name; | ||
| 99 | animation->duration = 0; | ||
| 100 | animation->num_channels = desc->num_channels; | ||
| 101 | R start_time = 0; | ||
| 102 | R end_time = 0; | ||
| 103 | |||
| 104 | for (size_t c = 0; c < desc->num_channels; ++c) { | ||
| 105 | const ChannelDesc* channel_desc = &desc->channels[c]; | ||
| 106 | Channel* channel = &animation->channels[c]; | ||
| 107 | |||
| 108 | channel->target = channel_desc->target; | ||
| 109 | channel->type = channel_desc->type; | ||
| 110 | channel->interpolation = channel_desc->interpolation; | ||
| 111 | channel->num_keyframes = channel_desc->num_keyframes; | ||
| 112 | assert(channel_desc->num_keyframes < GFX_MAX_NUM_KEYFRAMES); | ||
| 113 | |||
| 114 | for (size_t k = 0; k < channel_desc->num_keyframes; ++k) { | ||
| 115 | const KeyframeDesc* keyframe_desc = &channel_desc->keyframes[k]; | ||
| 116 | Keyframe* keyframe = &channel->keyframes[k]; | ||
| 117 | |||
| 118 | keyframe->time = keyframe_desc->time; | ||
| 119 | keyframe->translation = keyframe_desc->translation; | ||
| 120 | keyframe->rotation = keyframe_desc->rotation; | ||
| 121 | |||
| 122 | start_time = keyframe->time < start_time ? keyframe->time : start_time; | ||
| 123 | end_time = keyframe->time > end_time ? keyframe->time : end_time; | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | // LOGD("Animation start/end: %f / %f", start_time, end_time); | ||
| 128 | animation->duration = end_time - start_time; | ||
| 129 | assert(animation->duration >= 0); | ||
| 130 | return animation; | ||
| 131 | } | ||
| 132 | |||
| 133 | Anima* gfx_make_anima(const AnimaDesc* desc) { | ||
| 134 | assert(desc); | ||
| 135 | assert(desc->num_joints > 0); | ||
| 136 | assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); | ||
| 137 | // All joints should have a parent except for the root. | ||
| 138 | for (size_t i = 0; i < desc->num_joints - 1; ++i) { | ||
| 139 | const joint_idx parent = desc->joints[i].parent; | ||
| 140 | assert(parent != INDEX_NONE); | ||
| 141 | assert(parent < desc->num_joints); | ||
| 142 | } | ||
| 143 | // The root should have no parent. | ||
| 144 | assert(desc->joints[desc->num_joints - 1].parent == INDEX_NONE); | ||
| 145 | |||
| 146 | Anima* anima = mem_alloc_anima(); | ||
| 147 | |||
| 148 | // Wire the skeletons in the same order they are given in the descriptor. | ||
| 149 | Skeleton* last_skeleton = 0; | ||
| 150 | for (size_t i = 0; i < desc->num_skeletons; ++i) { | ||
| 151 | Skeleton* skeleton = make_skeleton(&desc->skeletons[i]); | ||
| 152 | const skeleton_idx skeleton_index = mem_get_skeleton_index(skeleton); | ||
| 153 | if (last_skeleton == 0) { | ||
| 154 | anima->skeleton = skeleton_index; | ||
| 155 | } else { | ||
| 156 | last_skeleton->next = skeleton_index; | ||
| 157 | } | ||
| 158 | last_skeleton = skeleton; | ||
| 159 | } | ||
| 160 | |||
| 161 | // Wire the animations in the same order they are given in the descriptor. | ||
| 162 | Animation* last_animation = 0; | ||
| 163 | for (size_t i = 0; i < desc->num_animations; ++i) { | ||
| 164 | Animation* animation = make_animation(&desc->animations[i]); | ||
| 165 | const animation_idx animation_index = mem_get_animation_index(animation); | ||
| 166 | if (last_animation == 0) { | ||
| 167 | anima->animation = animation_index; | ||
| 168 | } else { | ||
| 169 | last_animation->next = animation_index; | ||
| 170 | } | ||
| 171 | last_animation = animation; | ||
| 172 | } | ||
| 173 | |||
| 174 | // Create joints. | ||
| 175 | anima->num_joints = desc->num_joints; | ||
| 176 | // Initialize all joints. | ||
| 177 | // Child and sibling pointers must be initialized before wiring up the | ||
| 178 | // hierarchy. | ||
| 179 | for (size_t i = 0; i < desc->num_joints; ++i) { | ||
| 180 | Joint* joint = get_anima_joint_mut(anima, i); | ||
| 181 | make_joint(anima, &desc->joints[i], joint); | ||
| 182 | } | ||
| 183 | // Wire up joints to their parents. -1 to skip the root. | ||
| 184 | for (size_t i = 0; i < desc->num_joints - 1; ++i) { | ||
| 185 | set_joint_parent(anima, i, desc->joints[i].parent); | ||
| 186 | } | ||
| 187 | |||
| 188 | return anima; | ||
| 189 | } | ||
| 190 | |||
| 191 | void gfx_destroy_anima(Anima** anima) { | ||
| 192 | assert(anima); | ||
| 193 | |||
| 194 | if (*anima) { | ||
| 195 | for (skeleton_idx i = (*anima)->skeleton; i.val != 0;) { | ||
| 196 | Skeleton* skeleton = mem_get_skeleton(i); | ||
| 197 | i = skeleton->next; | ||
| 198 | mem_free_skeleton(&skeleton); | ||
| 199 | } | ||
| 200 | |||
| 201 | for (animation_idx i = (*anima)->animation; i.val != 0;) { | ||
| 202 | Animation* animation = mem_get_animation(i); | ||
| 203 | i = animation->next; | ||
| 204 | mem_free_animation(&animation); | ||
| 205 | } | ||
| 206 | |||
| 207 | if ((*anima)->parent.val) { | ||
| 208 | gfx_del_node((*anima)->parent); | ||
| 209 | } | ||
| 210 | |||
| 211 | mem_free_anima(anima); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | static Animation* find_animation(animation_idx index, const char* name) { | ||
| 216 | assert(name); | ||
| 217 | |||
| 218 | while (index.val != 0) { | ||
| 219 | Animation* animation = mem_get_animation(index); | ||
| 220 | if (sstring_eq_cstr(animation->name, name)) { | ||
| 221 | // LOGD( | ||
| 222 | // "Found animation at index %u, %s - %s", index.val, | ||
| 223 | // sstring_cstr(&animation->name), name); | ||
| 224 | // LOGD("Animation has duration %f", animation->duration); | ||
| 225 | return animation; | ||
| 226 | } | ||
| 227 | index = animation->next; | ||
| 228 | } | ||
| 229 | |||
| 230 | return 0; | ||
| 231 | } | ||
| 232 | |||
| 233 | bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) { | ||
| 234 | assert(anima); | ||
| 235 | assert(settings); | ||
| 236 | |||
| 237 | // TODO: Should we animate at t=0 here to kickstart the animation? Otherwise | ||
| 238 | // the client is forced to call gfx_update_animation() to do this. | ||
| 239 | Animation* animation = find_animation(anima->animation, settings->name); | ||
| 240 | if (!animation) { | ||
| 241 | return false; | ||
| 242 | } | ||
| 243 | // Playback initialized on first call to update(). | ||
| 244 | AnimationState* state = &anima->state; | ||
| 245 | state->start_time = PLAYBACK_UNINITIALIZED; | ||
| 246 | state->animation = mem_get_animation_index(animation); | ||
| 247 | state->loop = settings->loop; | ||
| 248 | return true; | ||
| 249 | } | ||
| 250 | |||
| 251 | static void gfx_set_joint_position(Joint* joint, vec3 position) { | ||
| 252 | assert(joint); | ||
| 253 | mat4_set_v3(&joint->transform, position); | ||
| 254 | } | ||
| 255 | |||
| 256 | static void gfx_set_joint_rotation(Joint* joint, quat rotation) { | ||
| 257 | assert(joint); | ||
| 258 | mat4_set_3x3(&joint->transform, mat4_from_quat(rotation)); | ||
| 259 | } | ||
| 260 | |||
| 261 | static void find_keyframes(const Channel* channel, R t, int* prev, int* next) { | ||
| 262 | assert(channel); | ||
| 263 | assert(prev); | ||
| 264 | assert(next); | ||
| 265 | |||
| 266 | *prev = -1; | ||
| 267 | *next = 0; | ||
| 268 | while (((*next + 1) < (int)channel->num_keyframes) && | ||
| 269 | (t >= channel->keyframes[*next + 1].time)) { | ||
| 270 | (*prev)++; | ||
| 271 | (*next)++; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | static R normalize_time(R a, R b, R t) { | ||
| 276 | assert(a <= t); | ||
| 277 | assert(t <= b); | ||
| 278 | return (t - a) / (b - a); | ||
| 279 | } | ||
| 280 | |||
| 281 | static quat interpolate_rotation( | ||
| 282 | const Channel* channel, int prev, int next, R t) { | ||
| 283 | assert(channel); | ||
| 284 | |||
| 285 | if (next == 0) { | ||
| 286 | // Animation has not started at this point in time yet. | ||
| 287 | return channel->keyframes[next].rotation; | ||
| 288 | } else { | ||
| 289 | switch (channel->interpolation) { | ||
| 290 | case StepInterpolation: | ||
| 291 | return channel->keyframes[prev].rotation; | ||
| 292 | case LinearInterpolation: { | ||
| 293 | const R normalized_t = normalize_time( | ||
| 294 | channel->keyframes[prev].time, channel->keyframes[next].time, t); | ||
| 295 | return qnormalize(qslerp( | ||
| 296 | channel->keyframes[prev].rotation, channel->keyframes[next].rotation, | ||
| 297 | normalized_t)); | ||
| 298 | break; | ||
| 299 | } | ||
| 300 | case CubicSplineInterpolation: | ||
| 301 | assert(false); // TODO | ||
| 302 | return qmake(0, 0, 0, 0); | ||
| 303 | default: | ||
| 304 | assert(false); | ||
| 305 | return qmake(0, 0, 0, 0); | ||
| 306 | } | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | static vec3 interpolate_translation( | ||
| 311 | const Channel* channel, int prev, int next, R t) { | ||
| 312 | assert(channel); | ||
| 313 | |||
| 314 | if (next == 0) { | ||
| 315 | // Animation has not started at this point in time yet. | ||
| 316 | return channel->keyframes[next].translation; | ||
| 317 | } else { | ||
| 318 | switch (channel->interpolation) { | ||
| 319 | case StepInterpolation: | ||
| 320 | return channel->keyframes[prev].translation; | ||
| 321 | case LinearInterpolation: { | ||
| 322 | const R normalized_t = normalize_time( | ||
| 323 | channel->keyframes[prev].time, channel->keyframes[next].time, t); | ||
| 324 | return vec3_lerp( | ||
| 325 | channel->keyframes[prev].translation, | ||
| 326 | channel->keyframes[next].translation, normalized_t); | ||
| 327 | break; | ||
| 328 | } | ||
| 329 | case CubicSplineInterpolation: | ||
| 330 | assert(false); // TODO | ||
| 331 | return vec3_make(0, 0, 0); | ||
| 332 | default: | ||
| 333 | assert(false); | ||
| 334 | return vec3_make(0, 0, 0); | ||
| 335 | } | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | static void animate_channel(Anima* anima, const Channel* channel, R t) { | ||
| 340 | assert(anima); | ||
| 341 | assert(channel); | ||
| 342 | assert(channel->target < anima->num_joints); | ||
| 343 | |||
| 344 | int prev, next; | ||
| 345 | find_keyframes(channel, t, &prev, &next); | ||
| 346 | |||
| 347 | // Note that not all channels extend to the duration of an animation; some | ||
| 348 | // channels may stop animating their targets earlier. Clamp the animation time | ||
| 349 | // to the channel's end keyframe to make the rest of the math (normalize_time) | ||
| 350 | // work. | ||
| 351 | t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t; | ||
| 352 | |||
| 353 | Joint* target = get_anima_joint_mut(anima, channel->target); | ||
| 354 | |||
| 355 | switch (channel->type) { | ||
| 356 | case RotationChannel: { | ||
| 357 | const quat rotation = interpolate_rotation(channel, prev, next, t); | ||
| 358 | gfx_set_joint_rotation(target, rotation); | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | case TranslationChannel: { | ||
| 362 | const vec3 translation = interpolate_translation(channel, prev, next, t); | ||
| 363 | gfx_set_joint_position(target, translation); | ||
| 364 | break; | ||
| 365 | } | ||
| 366 | // Not yet supported. | ||
| 367 | case ScaleChannel: | ||
| 368 | case WeightsChannel: | ||
| 369 | default: | ||
| 370 | // TODO: Add back the assertion or add support for scaling. | ||
| 371 | // assert(false); | ||
| 372 | break; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | static void compute_joint_matrices_rec( | ||
| 377 | Anima* anima, Joint* joint, const mat4* parent_global_joint_transform, | ||
| 378 | const mat4* root_inv_global_transform) { | ||
| 379 | assert(anima); | ||
| 380 | assert(joint); | ||
| 381 | assert(parent_global_joint_transform); | ||
| 382 | assert(root_inv_global_transform); | ||
| 383 | |||
| 384 | const mat4 global_joint_transform = | ||
| 385 | mat4_mul(*parent_global_joint_transform, joint->transform); | ||
| 386 | |||
| 387 | // Compute this joint's matrix. | ||
| 388 | joint->joint_matrix = mat4_mul( | ||
| 389 | *root_inv_global_transform, | ||
| 390 | mat4_mul(global_joint_transform, joint->inv_bind_matrix)); | ||
| 391 | |||
| 392 | // Recursively compute the joint matrices for this joint's siblings. | ||
| 393 | if (joint->next != INDEX_NONE) { | ||
| 394 | Joint* sibling = get_anima_joint_mut(anima, joint->next); | ||
| 395 | |||
| 396 | compute_joint_matrices_rec( | ||
| 397 | anima, sibling, parent_global_joint_transform, | ||
| 398 | root_inv_global_transform); | ||
| 399 | } | ||
| 400 | |||
| 401 | // Recursively compute the joint matrices for this joint's children. | ||
| 402 | if (joint->child != INDEX_NONE) { | ||
| 403 | Joint* child = get_anima_joint_mut(anima, joint->child); | ||
| 404 | |||
| 405 | compute_joint_matrices_rec( | ||
| 406 | anima, child, &global_joint_transform, root_inv_global_transform); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | void gfx_update_animation(Anima* anima, R t) { | ||
| 411 | assert(anima); | ||
| 412 | |||
| 413 | AnimationState* state = &anima->state; | ||
| 414 | if (state->animation.val == 0) { | ||
| 415 | return; // No active animation. | ||
| 416 | } | ||
| 417 | const Animation* animation = mem_get_animation(state->animation); | ||
| 418 | assert(animation); | ||
| 419 | |||
| 420 | // On a call to play(), the start time is set to -1 to signal that the | ||
| 421 | // animation playback has not yet been initialized. | ||
| 422 | if (state->start_time == PLAYBACK_UNINITIALIZED) { | ||
| 423 | state->start_time = t; | ||
| 424 | } | ||
| 425 | // Locate the current time point inside the animation's timeline. | ||
| 426 | assert(t >= state->start_time); | ||
| 427 | assert(animation->duration >= 0.0); | ||
| 428 | const R local_time = t - state->start_time; | ||
| 429 | const R animation_time = state->loop | ||
| 430 | ? rmod(local_time, animation->duration) | ||
| 431 | : clamp(local_time, 0.0, animation->duration); | ||
| 432 | |||
| 433 | // LOGD( | ||
| 434 | // "animation_time = %f, animation duration: %f", animation_time, | ||
| 435 | // animation->duration); | ||
| 436 | |||
| 437 | // Play through the animation to transform skeleton nodes. | ||
| 438 | for (size_t i = 0; i < animation->num_channels; ++i) { | ||
| 439 | const Channel* channel = &animation->channels[i]; | ||
| 440 | animate_channel(anima, channel, animation_time); | ||
| 441 | } | ||
| 442 | |||
| 443 | // Compute joint matrices after having transformed the skeletons. | ||
| 444 | // | ||
| 445 | // The anima's parent node is the common ancestor of all skeletons, and its | ||
| 446 | // transform maps the skeletons from object space to world space. This is the | ||
| 447 | // transform used as the "global transform" in the joint matrix equations. | ||
| 448 | // | ||
| 449 | // Joint matrix calculation begins by descending from the anima's root joint, | ||
| 450 | // which we have constructed to be the common root of all skeletons. | ||
| 451 | // | ||
| 452 | // This procedure touches every joint exactly once. | ||
| 453 | SceneNode* root_node = mem_get_node(anima->parent); | ||
| 454 | // LOGD("Root: %u, child: %u", anima->parent.val, root->child.val); | ||
| 455 | const mat4 root_global_transform = gfx_get_node_global_transform(root_node); | ||
| 456 | const mat4 root_inv_global_transform = mat4_inverse(root_global_transform); | ||
| 457 | |||
| 458 | Joint* root_joint = get_anima_root_joint(anima); | ||
| 459 | compute_joint_matrices_rec( | ||
| 460 | anima, root_joint, &root_global_transform, &root_inv_global_transform); | ||
| 461 | } | ||
| 462 | |||
| 463 | const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) { | ||
| 464 | assert(anima); | ||
| 465 | |||
| 466 | skeleton_idx skeleton_index = anima->skeleton; | ||
| 467 | const Skeleton* skeleton = mem_get_skeleton(skeleton_index); | ||
| 468 | |||
| 469 | for (size_t j = 1; j < i; ++j) { | ||
| 470 | skeleton_index = skeleton->next; | ||
| 471 | mem_get_skeleton(skeleton_index); | ||
| 472 | } | ||
| 473 | |||
| 474 | return skeleton; | ||
| 475 | } | ||
| 476 | |||
| 477 | size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) { | ||
| 478 | assert(skeleton); | ||
| 479 | return skeleton->num_joints; | ||
| 480 | } | ||
| 481 | |||
| 482 | bool gfx_joint_has_box( | ||
| 483 | const Anima* anima, const Skeleton* skeleton, size_t joint_index) { | ||
| 484 | assert(anima); | ||
| 485 | assert(skeleton); | ||
| 486 | assert(joint_index < skeleton->num_joints); | ||
| 487 | |||
| 488 | const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); | ||
| 489 | return !aabb3_is_empty(joint->box); | ||
| 490 | } | ||
| 491 | |||
| 492 | Box gfx_get_joint_box( | ||
| 493 | const Anima* anima, const Skeleton* skeleton, size_t joint_index) { | ||
| 494 | assert(anima); | ||
| 495 | assert(skeleton); | ||
| 496 | |||
| 497 | const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); | ||
| 498 | |||
| 499 | // Transform the box to anima space. | ||
| 500 | // Note that joint matrices do not usually have a translation since joints | ||
| 501 | // mostly just rotate with respect to their parent. | ||
| 502 | const vec3 pmin = joint->box.min; | ||
| 503 | const vec3 pmax = joint->box.max; | ||
| 504 | return (Box){ | ||
| 505 | .vertices = { | ||
| 506 | mat4_mul_vec3( | ||
| 507 | joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1), | ||
| 508 | mat4_mul_vec3( | ||
| 509 | joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1), | ||
| 510 | mat4_mul_vec3( | ||
| 511 | joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1), | ||
| 512 | mat4_mul_vec3( | ||
| 513 | joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1), | ||
| 514 | mat4_mul_vec3( | ||
| 515 | joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1), | ||
| 516 | mat4_mul_vec3( | ||
| 517 | joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1), | ||
| 518 | mat4_mul_vec3( | ||
| 519 | joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1), | ||
| 520 | mat4_mul_vec3( | ||
| 521 | joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1), | ||
| 522 | } | ||
| 523 | }; | ||
| 524 | } | ||
diff --git a/src/scene/animation_impl.h b/src/scene/animation_impl.h new file mode 100644 index 0000000..4408158 --- /dev/null +++ b/src/scene/animation_impl.h | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/animation.h> | ||
| 4 | #include <gfx/sizes.h> | ||
| 5 | |||
| 6 | #include "types.h" | ||
| 7 | |||
| 8 | #include <cstring.h> | ||
| 9 | #include <math/defs.h> | ||
| 10 | #include <math/mat4.h> | ||
| 11 | #include <math/quat.h> | ||
| 12 | #include <math/vec3.h> | ||
| 13 | |||
| 14 | #include <stddef.h> | ||
| 15 | #include <stdint.h> | ||
| 16 | |||
| 17 | typedef struct Buffer Buffer; | ||
| 18 | |||
| 19 | // Currently ignoring scale in skinning and animation. | ||
| 20 | // | ||
| 21 | // TODO: Simultaneous animation of disjoint animations. | ||
| 22 | |||
| 23 | /// Skeleton joint. | ||
| 24 | /// Joints are mutable and store the transform and joint matrices that result | ||
| 25 | /// from animation, aside from the inverse bind matrix. | ||
| 26 | typedef struct Joint { | ||
| 27 | joint_idx child; /// First child Joint; index into Anima's joints. | ||
| 28 | joint_idx next; /// Next sibling Joint; index into Anima's joints. | ||
| 29 | mat4 transform; /// Local transform relative to parent. | ||
| 30 | mat4 inv_bind_matrix; /// Transforms the mesh into the joint's local space. | ||
| 31 | mat4 joint_matrix; /// inv(global) * global joint transform * inv(bind). | ||
| 32 | aabb3 box; /// Bounding box of vertices affected by joint. | ||
| 33 | } Joint; | ||
| 34 | |||
| 35 | /// Animation skeleton. | ||
| 36 | typedef struct Skeleton { | ||
| 37 | skeleton_idx next; | ||
| 38 | size_t num_joints; | ||
| 39 | joint_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. | ||
| 40 | } Skeleton; | ||
| 41 | |||
| 42 | /// A keyframe of animation. | ||
| 43 | typedef struct Keyframe { | ||
| 44 | R time; /// Start time in [0, end animation time] | ||
| 45 | union { | ||
| 46 | vec3 translation; | ||
| 47 | quat rotation; | ||
| 48 | }; | ||
| 49 | } Keyframe; | ||
| 50 | |||
| 51 | /// Animation channel. | ||
| 52 | typedef struct Channel { | ||
| 53 | joint_idx target; /// Index into Anima's joints array. | ||
| 54 | ChannelType type; | ||
| 55 | AnimationInterpolation interpolation; | ||
| 56 | size_t num_keyframes; | ||
| 57 | Keyframe keyframes[GFX_MAX_NUM_KEYFRAMES]; | ||
| 58 | } Channel; | ||
| 59 | |||
| 60 | /// A skeletal animation. | ||
| 61 | typedef struct Animation { | ||
| 62 | animation_idx next; | ||
| 63 | sstring name; | ||
| 64 | R duration; | ||
| 65 | size_t num_channels; | ||
| 66 | Channel channels[GFX_MAX_NUM_CHANNELS]; | ||
| 67 | } Animation; | ||
| 68 | |||
| 69 | /// Animation state. | ||
| 70 | /// | ||
| 71 | /// This represents the current state of an animation. | ||
| 72 | typedef struct AnimationState { | ||
| 73 | R start_time; // Time when the current animation started playing. -1 means the | ||
| 74 | // animation playback has not yet been initialized. | ||
| 75 | animation_idx animation; // Current animation. 0 = no animation. | ||
| 76 | bool loop; | ||
| 77 | } AnimationState; | ||
| 78 | |||
| 79 | /// Animation object. | ||
| 80 | /// | ||
| 81 | /// This is the top-level animation object that encapsulates everything | ||
| 82 | /// necessary for animation. | ||
| 83 | /// | ||
| 84 | /// For lack of a better name, this is called an Anima. It is short and the | ||
| 85 | /// Latin root of animation. | ||
| 86 | /// | ||
| 87 | /// The last joint of the joints array at index 'num_joints - 1' is the root of | ||
| 88 | /// all skeletons; specifically, the root of all joints that otherwise would | ||
| 89 | /// have no parent (a skeleton need not have its own root and can be a set of | ||
| 90 | /// disjoint node hierarchies). | ||
| 91 | typedef struct Anima { | ||
| 92 | node_idx parent; /// Parent SceneNode. | ||
| 93 | skeleton_idx skeleton; /// Index of first skeleton. | ||
| 94 | animation_idx animation; /// Index of first animation. | ||
| 95 | AnimationState state; /// Current animation state. | ||
| 96 | size_t num_joints; /// Number of actual joints in the array. | ||
| 97 | Joint joints[GFX_MAX_NUM_JOINTS]; /// Shared by all skeletons. | ||
| 98 | } Anima; | ||
diff --git a/src/scene/camera.c b/src/scene/camera.c new file mode 100644 index 0000000..be7d806 --- /dev/null +++ b/src/scene/camera.c | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | #include "camera_impl.h" | ||
| 2 | |||
| 3 | #include "node_impl.h" | ||
| 4 | #include "scene_memory.h" | ||
| 5 | |||
| 6 | #include <assert.h> | ||
| 7 | |||
| 8 | SceneCamera* gfx_make_camera() { | ||
| 9 | SceneCamera* camera = mem_alloc_camera(); | ||
| 10 | |||
| 11 | camera->camera = camera_perspective( | ||
| 12 | /*fovy=*/90.0 * TO_RAD, /*aspect=*/16.0 / 9.0, | ||
| 13 | /*near=*/0.1, /*far=*/1000); | ||
| 14 | |||
| 15 | return camera; | ||
| 16 | } | ||
| 17 | |||
| 18 | void gfx_destroy_camera(SceneCamera** camera) { | ||
| 19 | assert(camera); | ||
| 20 | if (*camera) { | ||
| 21 | if ((*camera)->parent.val) { | ||
| 22 | gfx_del_node((*camera)->parent); | ||
| 23 | } | ||
| 24 | mem_free_camera(camera); | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | void gfx_set_camera_camera(SceneCamera* scene_camera, Camera* camera) { | ||
| 29 | assert(scene_camera); | ||
| 30 | assert(camera); | ||
| 31 | scene_camera->camera = *camera; | ||
| 32 | } | ||
| 33 | |||
| 34 | Camera* gfx_get_camera_camera(SceneCamera* camera) { | ||
| 35 | assert(camera); | ||
| 36 | return &camera->camera; | ||
| 37 | } | ||
diff --git a/src/scene/camera_impl.h b/src/scene/camera_impl.h new file mode 100644 index 0000000..20c3890 --- /dev/null +++ b/src/scene/camera_impl.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/camera.h> | ||
| 4 | |||
| 5 | #include "types.h" | ||
| 6 | |||
| 7 | #include <math/camera.h> | ||
| 8 | |||
| 9 | typedef struct SceneCamera { | ||
| 10 | Camera camera; | ||
| 11 | node_idx parent; // Parent SceneNode. | ||
| 12 | } SceneCamera; | ||
diff --git a/src/scene/light.c b/src/scene/light.c new file mode 100644 index 0000000..adbec8d --- /dev/null +++ b/src/scene/light.c | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | #include "light_impl.h" | ||
| 2 | |||
| 3 | #include "node_impl.h" | ||
| 4 | #include "scene_memory.h" | ||
| 5 | |||
| 6 | #include <error.h> | ||
| 7 | |||
| 8 | static void make_environment_light( | ||
| 9 | Light* light, const EnvironmentLightDesc* desc) { | ||
| 10 | assert(light); | ||
| 11 | assert(desc); | ||
| 12 | light->type = EnvironmentLightType; | ||
| 13 | light->environment.environment_map = desc->environment_map; | ||
| 14 | } | ||
| 15 | |||
| 16 | Light* gfx_make_light(const LightDesc* desc) { | ||
| 17 | assert(desc); | ||
| 18 | |||
| 19 | Light* light = mem_alloc_light(); | ||
| 20 | |||
| 21 | switch (desc->type) { | ||
| 22 | case EnvironmentLightType: | ||
| 23 | make_environment_light(light, &desc->light.environment); | ||
| 24 | break; | ||
| 25 | default: | ||
| 26 | log_error("Unhandled light type"); | ||
| 27 | gfx_destroy_light(&light); | ||
| 28 | return 0; | ||
| 29 | } | ||
| 30 | |||
| 31 | return light; | ||
| 32 | } | ||
| 33 | |||
| 34 | void gfx_destroy_light(Light** light) { | ||
| 35 | assert(light); | ||
| 36 | if (*light) { | ||
| 37 | if ((*light)->parent.val) { | ||
| 38 | gfx_del_node((*light)->parent); | ||
| 39 | } | ||
| 40 | mem_free_light(light); | ||
| 41 | } | ||
| 42 | } | ||
diff --git a/src/scene/light_impl.h b/src/scene/light_impl.h new file mode 100644 index 0000000..1aa0bb4 --- /dev/null +++ b/src/scene/light_impl.h | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/light.h> | ||
| 4 | |||
| 5 | #include "types.h" | ||
| 6 | |||
| 7 | typedef struct Texture Texture; | ||
| 8 | |||
| 9 | /// An environment light. | ||
| 10 | typedef struct EnvironmentLight { | ||
| 11 | const Texture* environment_map; | ||
| 12 | const Texture* irradiance_map; // Renderer implementation. | ||
| 13 | const Texture* prefiltered_environment_map; // Renderer implementation. | ||
| 14 | int max_reflection_lod; // Mandatory when prefiltered_environment_map is | ||
| 15 | // given. | ||
| 16 | } EnvironmentLight; | ||
| 17 | |||
| 18 | /// A scene light. | ||
| 19 | typedef struct Light { | ||
| 20 | LightType type; | ||
| 21 | union { | ||
| 22 | EnvironmentLight environment; | ||
| 23 | }; | ||
| 24 | node_idx parent; // Parent SceneNode. | ||
| 25 | } Light; | ||
diff --git a/src/scene/material.c b/src/scene/material.c new file mode 100644 index 0000000..3248243 --- /dev/null +++ b/src/scene/material.c | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | #include "material_impl.h" | ||
| 2 | |||
| 3 | #include "scene_memory.h" | ||
| 4 | |||
| 5 | #include <gfx/core.h> | ||
| 6 | |||
| 7 | static void material_make(Material* material, const MaterialDesc* desc) { | ||
| 8 | assert(material); | ||
| 9 | assert(desc); | ||
| 10 | assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); | ||
| 11 | material->num_uniforms = desc->num_uniforms; | ||
| 12 | for (int i = 0; i < desc->num_uniforms; ++i) { | ||
| 13 | material->uniforms[i] = desc->uniforms[i]; | ||
| 14 | } | ||
| 15 | } | ||
| 16 | |||
| 17 | Material* gfx_make_material(const MaterialDesc* desc) { | ||
| 18 | assert(desc); | ||
| 19 | Material* material = mem_alloc_material(); | ||
| 20 | material_make(material, desc); | ||
| 21 | return material; | ||
| 22 | } | ||
| 23 | |||
| 24 | void gfx_destroy_material(Material** material) { mem_free_material(material); } | ||
| 25 | |||
| 26 | static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) { | ||
| 27 | switch (uniform->type) { | ||
| 28 | case UniformTexture: | ||
| 29 | gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture); | ||
| 30 | break; | ||
| 31 | case UniformMat4: | ||
| 32 | gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.mat4); | ||
| 33 | break; | ||
| 34 | case UniformVec3: | ||
| 35 | gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.vec3); | ||
| 36 | break; | ||
| 37 | case UniformVec4: | ||
| 38 | gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.vec4); | ||
| 39 | break; | ||
| 40 | case UniformFloat: | ||
| 41 | gfx_set_float_uniform(prog, uniform->name.str, uniform->value.scalar); | ||
| 42 | break; | ||
| 43 | case UniformMat4Array: | ||
| 44 | gfx_set_mat4_array_uniform( | ||
| 45 | prog, uniform->name.str, uniform->value.array.values, | ||
| 46 | uniform->value.array.count); | ||
| 47 | break; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | void material_activate(ShaderProgram* shader, const Material* material) { | ||
| 52 | assert(material); | ||
| 53 | for (int i = 0; i < material->num_uniforms; ++i) { | ||
| 54 | const ShaderUniform* uniform = &material->uniforms[i]; | ||
| 55 | set_uniform(shader, uniform); | ||
| 56 | } | ||
| 57 | } | ||
diff --git a/src/scene/material_impl.h b/src/scene/material_impl.h new file mode 100644 index 0000000..a6aa95b --- /dev/null +++ b/src/scene/material_impl.h | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/material.h> | ||
| 4 | |||
| 5 | typedef struct ShaderProgram ShaderProgram; | ||
| 6 | |||
| 7 | typedef struct Material { | ||
| 8 | ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL]; | ||
| 9 | int num_uniforms; | ||
| 10 | } Material; | ||
| 11 | |||
| 12 | /// Activate the material. | ||
| 13 | /// | ||
| 14 | /// This activates the material's shader and configures the shader uniforms that | ||
| 15 | /// are specific to the material. | ||
| 16 | void material_activate(ShaderProgram* shader, const Material* material); | ||
diff --git a/src/scene/mesh.c b/src/scene/mesh.c new file mode 100644 index 0000000..1a93bed --- /dev/null +++ b/src/scene/mesh.c | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | #include "mesh_impl.h" | ||
| 2 | |||
| 3 | #include "scene_memory.h" | ||
| 4 | |||
| 5 | #include <assert.h> | ||
| 6 | |||
| 7 | static void mesh_make(Mesh* mesh, const MeshDesc* desc) { | ||
| 8 | assert(mesh); | ||
| 9 | assert(desc); | ||
| 10 | assert(desc->geometry); | ||
| 11 | assert(desc->material); | ||
| 12 | assert(desc->shader); | ||
| 13 | mesh->geometry = desc->geometry; | ||
| 14 | mesh->material = desc->material; | ||
| 15 | mesh->shader = desc->shader; | ||
| 16 | } | ||
| 17 | |||
| 18 | Mesh* gfx_make_mesh(const MeshDesc* desc) { | ||
| 19 | Mesh* mesh = mem_alloc_mesh(); | ||
| 20 | mesh_make(mesh, desc); | ||
| 21 | return mesh; | ||
| 22 | } | ||
| 23 | |||
| 24 | void gfx_destroy_mesh(Mesh** mesh) { mem_free_mesh(mesh); } | ||
diff --git a/src/scene/mesh_impl.h b/src/scene/mesh_impl.h new file mode 100644 index 0000000..560b77e --- /dev/null +++ b/src/scene/mesh_impl.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/mesh.h> | ||
| 4 | |||
| 5 | typedef struct Mesh { | ||
| 6 | const Geometry* geometry; | ||
| 7 | const Material* material; | ||
| 8 | ShaderProgram* shader; | ||
| 9 | } Mesh; | ||
| 10 | |||
| 11 | // TODO: a mesh_render() that takes a transform, applies the material and the | ||
| 12 | // transform, and then renders the geometry. | ||
diff --git a/src/scene/model.c b/src/scene/model.c new file mode 100644 index 0000000..cc41a9a --- /dev/null +++ b/src/scene/model.c | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | #include "model_impl.h" | ||
| 2 | |||
| 3 | #include <gfx/scene/node.h> | ||
| 4 | |||
| 5 | #include "scene_memory.h" | ||
| 6 | |||
| 7 | #include <assert.h> | ||
| 8 | |||
| 9 | Model* gfx_make_model(const SceneNode* root) { | ||
| 10 | assert(root); | ||
| 11 | |||
| 12 | Model* model = mem_alloc_model(); | ||
| 13 | model->root = mem_get_node_index(root); | ||
| 14 | return model; | ||
| 15 | } | ||
| 16 | |||
| 17 | void gfx_del_model(Model** model) { | ||
| 18 | assert(model); | ||
| 19 | |||
| 20 | if (*model) { | ||
| 21 | SceneNode* root = mem_get_node((*model)->root); | ||
| 22 | gfx_destroy_node(&root); | ||
| 23 | *model = 0; | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | Anima* gfx_get_model_anima(Model* model) { | ||
| 28 | assert(model); | ||
| 29 | |||
| 30 | SceneNode* root = mem_get_node(model->root); | ||
| 31 | if (gfx_get_node_type(root) == AnimaNode) { | ||
| 32 | return gfx_get_node_anima_mut(root); | ||
| 33 | } else { | ||
| 34 | return 0; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | const SceneNode* gfx_get_model_root(const Model* model) { | ||
| 39 | assert(model); | ||
| 40 | return mem_get_node(model->root); | ||
| 41 | } | ||
| 42 | |||
| 43 | SceneNode* gfx_get_model_root_mut(Model* model) { | ||
| 44 | return (SceneNode*)gfx_get_model_root(model); | ||
| 45 | } | ||
diff --git a/src/scene/model_impl.h b/src/scene/model_impl.h new file mode 100644 index 0000000..a99d32c --- /dev/null +++ b/src/scene/model_impl.h | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/model.h> | ||
| 4 | |||
| 5 | #include "scene_memory.h" | ||
| 6 | |||
| 7 | /// Model. | ||
| 8 | typedef struct Model { | ||
| 9 | node_idx root; | ||
| 10 | node_idx parent; // Parent SceneNode. | ||
| 11 | } Model; | ||
| 12 | |||
| 13 | /// Create a new model. | ||
| 14 | Model* gfx_make_model(const SceneNode* root); | ||
| 15 | |||
| 16 | /// Destroy the model. | ||
| 17 | void gfx_del_model(Model**); | ||
diff --git a/src/scene/node.c b/src/scene/node.c new file mode 100644 index 0000000..67ce93c --- /dev/null +++ b/src/scene/node.c | |||
| @@ -0,0 +1,409 @@ | |||
| 1 | #include "node_impl.h" | ||
| 2 | |||
| 3 | #include "animation_impl.h" | ||
| 4 | #include "camera_impl.h" | ||
| 5 | #include "light_impl.h" | ||
| 6 | #include "model_impl.h" | ||
| 7 | #include "object_impl.h" | ||
| 8 | #include "scene_graph.h" | ||
| 9 | #include "scene_memory.h" | ||
| 10 | |||
| 11 | #include "gfx_assert.h" | ||
| 12 | |||
| 13 | #include <cstring.h> | ||
| 14 | #include <log/log.h> | ||
| 15 | |||
| 16 | static void scene_node_make(SceneNode* node) { | ||
| 17 | assert(node); | ||
| 18 | node->type = LogicalNode; | ||
| 19 | node->transform = mat4_id(); | ||
| 20 | } | ||
| 21 | |||
| 22 | SceneNode* gfx_make_node() { | ||
| 23 | SceneNode* node = mem_alloc_node(); | ||
| 24 | scene_node_make(node); | ||
| 25 | return node; | ||
| 26 | } | ||
| 27 | |||
| 28 | SceneNode* gfx_make_anima_node(Anima* anima) { | ||
| 29 | assert(anima); | ||
| 30 | SceneNode* node = gfx_make_node(); | ||
| 31 | node->type = AnimaNode; | ||
| 32 | node->anima = mem_get_anima_index(anima); | ||
| 33 | anima->parent = mem_get_node_index(node); | ||
| 34 | return node; | ||
| 35 | } | ||
| 36 | |||
| 37 | SceneNode* gfx_make_camera_node(SceneCamera* camera) { | ||
| 38 | assert(camera); | ||
| 39 | SceneNode* node = gfx_make_node(); | ||
| 40 | node->type = CameraNode; | ||
| 41 | node->camera = mem_get_camera_index(camera); | ||
| 42 | camera->parent = mem_get_node_index(node); | ||
| 43 | return node; | ||
| 44 | } | ||
| 45 | |||
| 46 | SceneNode* gfx_make_light_node(Light* light) { | ||
| 47 | assert(light); | ||
| 48 | SceneNode* node = gfx_make_node(); | ||
| 49 | node->type = LightNode; | ||
| 50 | node->light = mem_get_light_index(light); | ||
| 51 | light->parent = mem_get_node_index(node); | ||
| 52 | return node; | ||
| 53 | } | ||
| 54 | |||
| 55 | SceneNode* gfx_make_model_node(Model* model) { | ||
| 56 | assert(model); | ||
| 57 | SceneNode* node = gfx_make_node(); | ||
| 58 | node->type = ModelNode; | ||
| 59 | node->model = mem_get_model_index(model); | ||
| 60 | model->parent = mem_get_node_index(node); | ||
| 61 | return node; | ||
| 62 | } | ||
| 63 | |||
| 64 | SceneNode* gfx_make_object_node(SceneObject* object) { | ||
| 65 | assert(object); | ||
| 66 | SceneNode* node = gfx_make_node(); | ||
| 67 | node->type = ObjectNode; | ||
| 68 | node->object = mem_get_object_index(object); | ||
| 69 | object->parent = mem_get_node_index(node); | ||
| 70 | return node; | ||
| 71 | } | ||
| 72 | |||
| 73 | /// Frees the node's resource. | ||
| 74 | static void free_node_resource(SceneNode* node) { | ||
| 75 | assert(node); | ||
| 76 | |||
| 77 | // Set the resource's parent node back to 0 to avoid a recursive call into | ||
| 78 | // gfx_del_node(). | ||
| 79 | switch (node->type) { | ||
| 80 | case AnimaNode: { | ||
| 81 | Anima* anima = mem_get_anima(node->anima); | ||
| 82 | anima->parent.val = 0; | ||
| 83 | gfx_destroy_anima(&anima); | ||
| 84 | return; | ||
| 85 | } | ||
| 86 | case CameraNode: { | ||
| 87 | SceneCamera* camera = mem_get_camera(node->camera); | ||
| 88 | camera->parent.val = 0; | ||
| 89 | gfx_destroy_camera(&camera); | ||
| 90 | return; | ||
| 91 | } | ||
| 92 | case LightNode: { | ||
| 93 | Light* light = mem_get_light(node->light); | ||
| 94 | light->parent.val = 0; | ||
| 95 | gfx_destroy_light(&light); | ||
| 96 | return; | ||
| 97 | } | ||
| 98 | case ModelNode: { | ||
| 99 | return; // Model data is owned by the asset cache. | ||
| 100 | } | ||
| 101 | case ObjectNode: { | ||
| 102 | SceneObject* object = mem_get_object(node->object); | ||
| 103 | object->parent.val = 0; | ||
| 104 | gfx_destroy_object(&object); | ||
| 105 | return; | ||
| 106 | } | ||
| 107 | case LogicalNode: | ||
| 108 | return; // Logical nodes have no resource. | ||
| 109 | } | ||
| 110 | FAIL("unhandled node type"); | ||
| 111 | } | ||
| 112 | |||
| 113 | void gfx_construct_anima_node(SceneNode* node, Anima* anima) { | ||
| 114 | assert(node); | ||
| 115 | assert(anima); | ||
| 116 | free_node_resource(node); | ||
| 117 | node->type = AnimaNode; | ||
| 118 | node->anima = mem_get_anima_index(anima); | ||
| 119 | anima->parent = mem_get_node_index(node); | ||
| 120 | } | ||
| 121 | |||
| 122 | void gfx_construct_camera_node(SceneNode* node, SceneCamera* camera) { | ||
| 123 | assert(node); | ||
| 124 | assert(camera); | ||
| 125 | free_node_resource(node); | ||
| 126 | node->type = CameraNode; | ||
| 127 | node->camera = mem_get_camera_index(camera); | ||
| 128 | camera->parent = mem_get_node_index(node); | ||
| 129 | } | ||
| 130 | |||
| 131 | // TODO: Add a common helper function between each gfx_make_xyz_node() and | ||
| 132 | // gfx_construct_xyz_node() pair. | ||
| 133 | void gfx_construct_light_node(SceneNode* node, Light* light) { | ||
| 134 | assert(node); | ||
| 135 | assert(light); | ||
| 136 | free_node_resource(node); | ||
| 137 | node->type = LightNode; | ||
| 138 | node->light = mem_get_light_index(light); | ||
| 139 | light->parent = mem_get_node_index(node); | ||
| 140 | } | ||
| 141 | |||
| 142 | void gfx_construct_model_node(SceneNode* node, Model* model) { | ||
| 143 | assert(node); | ||
| 144 | assert(model); | ||
| 145 | free_node_resource(node); | ||
| 146 | node->type = ModelNode; | ||
| 147 | node->model = mem_get_model_index(model); | ||
| 148 | model->parent = mem_get_node_index(node); | ||
| 149 | } | ||
| 150 | |||
| 151 | void gfx_construct_object_node(SceneNode* node, SceneObject* object) { | ||
| 152 | assert(node); | ||
| 153 | assert(object); | ||
| 154 | free_node_resource(node); | ||
| 155 | node->type = ObjectNode; | ||
| 156 | node->object = mem_get_object_index(object); | ||
| 157 | object->parent = mem_get_node_index(node); | ||
| 158 | } | ||
| 159 | |||
| 160 | static void destroy_node_rec(SceneNode* node) { | ||
| 161 | assert(node); | ||
| 162 | |||
| 163 | // First child. | ||
| 164 | if (node->child.val) { | ||
| 165 | destroy_node_rec(mem_get_node(node->child)); | ||
| 166 | } | ||
| 167 | |||
| 168 | // Right sibling. | ||
| 169 | if (node->next.val) { | ||
| 170 | destroy_node_rec(mem_get_node(node->next)); | ||
| 171 | } | ||
| 172 | |||
| 173 | free_node_resource(node); | ||
| 174 | mem_free_node(&node); | ||
| 175 | } | ||
| 176 | |||
| 177 | void gfx_destroy_node(SceneNode** node) { | ||
| 178 | assert(node); | ||
| 179 | if (*node) { | ||
| 180 | // Since the node and the whole hierarchy under it gets destroyed, there is | ||
| 181 | // no need to individually detach every node from its hierarchy. We can | ||
| 182 | // simply detach the given node and then destroy it and its sub-hierarchy. | ||
| 183 | TREE_REMOVE(*node); | ||
| 184 | destroy_node_rec(*node); | ||
| 185 | *node = 0; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | // TODO: Think more about ownership of nodes and resources. Should this function | ||
| 190 | // even exist? | ||
| 191 | void gfx_del_node(node_idx index) { | ||
| 192 | assert(index.val); | ||
| 193 | SceneNode* node = mem_get_node(index); | ||
| 194 | assert(node); | ||
| 195 | // TODO: Should destroy children recursively? | ||
| 196 | TREE_REMOVE(node); | ||
| 197 | mem_free_node(&node); | ||
| 198 | } | ||
| 199 | |||
| 200 | NodeType gfx_get_node_type(const SceneNode* node) { | ||
| 201 | assert(node); | ||
| 202 | return node->type; | ||
| 203 | } | ||
| 204 | |||
| 205 | #define NODE_GET(node, field, expected_type) \ | ||
| 206 | { \ | ||
| 207 | assert(node); \ | ||
| 208 | assert(node->type == expected_type); \ | ||
| 209 | return mem_get_##field(node->field); \ | ||
| 210 | } | ||
| 211 | |||
| 212 | const Anima* gfx_get_node_anima(const SceneNode* node) { | ||
| 213 | NODE_GET(node, anima, AnimaNode); | ||
| 214 | } | ||
| 215 | |||
| 216 | Anima* gfx_get_node_anima_mut(SceneNode* node) { | ||
| 217 | NODE_GET(node, anima, AnimaNode); | ||
| 218 | } | ||
| 219 | |||
| 220 | const SceneCamera* gfx_get_node_camera(const SceneNode* node) { | ||
| 221 | NODE_GET(node, camera, CameraNode); | ||
| 222 | } | ||
| 223 | |||
| 224 | SceneCamera* gfx_get_node_camera_mut(SceneNode* node) { | ||
| 225 | NODE_GET(node, camera, CameraNode); | ||
| 226 | } | ||
| 227 | |||
| 228 | const Light* gfx_get_node_light(const SceneNode* node) { | ||
| 229 | NODE_GET(node, light, LightNode); | ||
| 230 | } | ||
| 231 | |||
| 232 | Light* gfx_get_node_light_mut(SceneNode* node) { | ||
| 233 | NODE_GET(node, light, LightNode); | ||
| 234 | } | ||
| 235 | |||
| 236 | const Model* gfx_get_node_model(const SceneNode* node) { | ||
| 237 | NODE_GET(node, model, ModelNode); | ||
| 238 | } | ||
| 239 | |||
| 240 | Model* gfx_get_node_model_mut(SceneNode* node) { | ||
| 241 | NODE_GET(node, model, ModelNode); | ||
| 242 | } | ||
| 243 | |||
| 244 | const SceneObject* gfx_get_node_object(const SceneNode* node) { | ||
| 245 | NODE_GET(node, object, ObjectNode); | ||
| 246 | } | ||
| 247 | |||
| 248 | SceneObject* gfx_get_node_object_mut(SceneNode* node) { | ||
| 249 | NODE_GET(node, object, ObjectNode); | ||
| 250 | } | ||
| 251 | |||
| 252 | const SceneNode* gfx_get_node_parent(const SceneNode* node) { | ||
| 253 | assert(node); | ||
| 254 | return mem_get_node(node->parent); | ||
| 255 | } | ||
| 256 | |||
| 257 | SceneNode* gfx_get_node_parent_mut(SceneNode* node) { | ||
| 258 | assert(node); | ||
| 259 | return mem_get_node(node->parent); | ||
| 260 | } | ||
| 261 | |||
| 262 | const SceneNode* gfx_get_node_child(const SceneNode* node) { | ||
| 263 | assert(node); | ||
| 264 | if (node->child.val) { | ||
| 265 | return mem_get_node(node->child); | ||
| 266 | } else { | ||
| 267 | return 0; | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | SceneNode* gfx_get_node_child_mut(SceneNode* node) { | ||
| 272 | return (SceneNode*)gfx_get_node_child(node); | ||
| 273 | } | ||
| 274 | |||
| 275 | const SceneNode* gfx_get_node_sibling(const SceneNode* node) { | ||
| 276 | assert(node); | ||
| 277 | if (node->next.val) { | ||
| 278 | return mem_get_node(node->next); | ||
| 279 | } else { | ||
| 280 | return 0; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | SceneNode* gfx_get_node_sibling_mut(SceneNode* node) { | ||
| 285 | return (SceneNode*)gfx_get_node_sibling(node); | ||
| 286 | } | ||
| 287 | |||
| 288 | mat4 gfx_get_node_transform(const SceneNode* node) { | ||
| 289 | assert(node); | ||
| 290 | return node->transform; | ||
| 291 | } | ||
| 292 | |||
| 293 | mat4 gfx_get_node_global_transform(const SceneNode* node) { | ||
| 294 | assert(node); | ||
| 295 | mat4 transform = node->transform; | ||
| 296 | node_idx parent_index = node->parent; | ||
| 297 | while (parent_index.val != 0) { | ||
| 298 | const SceneNode* parent = mem_get_node(parent_index); | ||
| 299 | transform = mat4_mul(parent->transform, transform); | ||
| 300 | parent_index = parent->parent; | ||
| 301 | } | ||
| 302 | return transform; | ||
| 303 | } | ||
| 304 | |||
| 305 | void gfx_set_node_parent(SceneNode* child, SceneNode* parent_node) { | ||
| 306 | assert(child); | ||
| 307 | // Parent can be null. | ||
| 308 | SET_PARENT(child, parent_node); | ||
| 309 | } | ||
| 310 | |||
| 311 | void gfx_set_node_transform(SceneNode* node, const mat4* transform) { | ||
| 312 | assert(node); | ||
| 313 | assert(transform); | ||
| 314 | node->transform = *transform; | ||
| 315 | } | ||
| 316 | |||
| 317 | void gfx_set_node_position(SceneNode* node, const vec3* position) { | ||
| 318 | assert(node); | ||
| 319 | assert(position); | ||
| 320 | mat4_set_v3(&node->transform, *position); | ||
| 321 | } | ||
| 322 | |||
| 323 | void gfx_set_node_rotation(SceneNode* node, const quat* rotation) { | ||
| 324 | assert(node); | ||
| 325 | assert(rotation); | ||
| 326 | mat4_set_3x3(&node->transform, mat4_from_quat(*rotation)); | ||
| 327 | } | ||
| 328 | |||
| 329 | void gfx_set_node_rotation_mat(SceneNode* node, const mat4* rotation) { | ||
| 330 | assert(node); | ||
| 331 | assert(rotation); | ||
| 332 | mat4_set_3x3(&node->transform, *rotation); | ||
| 333 | } | ||
| 334 | |||
| 335 | static const char* get_node_type_str(NodeType type) { | ||
| 336 | switch (type) { | ||
| 337 | case LogicalNode: | ||
| 338 | return "LogicalNode"; | ||
| 339 | case AnimaNode: | ||
| 340 | return "AnimaNode"; | ||
| 341 | case CameraNode: | ||
| 342 | return "CameraNode"; | ||
| 343 | case LightNode: | ||
| 344 | return "LightNode"; | ||
| 345 | case ModelNode: | ||
| 346 | return "ModelNode"; | ||
| 347 | case ObjectNode: | ||
| 348 | return "ObjectNode"; | ||
| 349 | } | ||
| 350 | FAIL("Unhandled node type"); | ||
| 351 | return ""; | ||
| 352 | } | ||
| 353 | |||
| 354 | static void log_node_hierarchy_rec(const SceneNode* node, const sstring* pad) { | ||
| 355 | assert(node); | ||
| 356 | assert(pad); | ||
| 357 | |||
| 358 | LOGI( | ||
| 359 | "%s%s (%u)", sstring_cstr(pad), get_node_type_str(node->type), | ||
| 360 | mem_get_node_index(node).val); | ||
| 361 | |||
| 362 | // Log the children. | ||
| 363 | if (node->child.val) { | ||
| 364 | const sstring new_pad = sstring_concat_cstr(*pad, " "); | ||
| 365 | log_node_hierarchy_rec(mem_get_node(node->child), &new_pad); | ||
| 366 | } | ||
| 367 | |||
| 368 | // Then log the siblings. | ||
| 369 | if (node->next.val) { | ||
| 370 | log_node_hierarchy_rec(mem_get_node(node->next), pad); | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 374 | void gfx_log_node_hierarchy(const SceneNode* node) { | ||
| 375 | const sstring pad = sstring_make(""); | ||
| 376 | log_node_hierarchy_rec(node, &pad); | ||
| 377 | } | ||
| 378 | |||
| 379 | static SceneNode* clone_scene_rec(const SceneNode* node) { | ||
| 380 | assert(node); | ||
| 381 | |||
| 382 | SceneNode* copy = mem_alloc_node(); | ||
| 383 | *copy = *node; // Shallow clone of the node's resource. | ||
| 384 | |||
| 385 | if (node->child.val) { | ||
| 386 | SceneNode* child = mem_get_node(node->child); | ||
| 387 | SceneNode* child_copy = clone_scene_rec(child); | ||
| 388 | copy->child = mem_get_node_index(child_copy); | ||
| 389 | child_copy->parent = mem_get_node_index(copy); | ||
| 390 | } | ||
| 391 | |||
| 392 | if (node->next.val) { | ||
| 393 | SceneNode* next = mem_get_node(node->next); | ||
| 394 | SceneNode* next_copy = clone_scene_rec(next); | ||
| 395 | copy->next = mem_get_node_index(next_copy); | ||
| 396 | next_copy->prev = mem_get_node_index(copy); | ||
| 397 | } | ||
| 398 | |||
| 399 | return copy; | ||
| 400 | } | ||
| 401 | |||
| 402 | SceneNode* gfx_clone_scene_shallow(const SceneNode* node) { | ||
| 403 | assert(node); | ||
| 404 | // Must be a root node; not allowed to have siblings. | ||
| 405 | assert(!node->prev.val); | ||
| 406 | assert(!node->next.val); | ||
| 407 | |||
| 408 | return clone_scene_rec(node); | ||
| 409 | } | ||
diff --git a/src/scene/node_impl.h b/src/scene/node_impl.h new file mode 100644 index 0000000..c79f252 --- /dev/null +++ b/src/scene/node_impl.h | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/node.h> | ||
| 4 | |||
| 5 | #include "types.h" | ||
| 6 | |||
| 7 | #include <cstring.h> | ||
| 8 | #include <math/mat4.h> | ||
| 9 | |||
| 10 | /// Scene node. | ||
| 11 | /// | ||
| 12 | /// The SceneNode owns its cameras, objects, lights and child nodes. These | ||
| 13 | /// together form a strict tree hierarchy and not a more general DAG. | ||
| 14 | typedef struct SceneNode { | ||
| 15 | NodeType type; | ||
| 16 | union { | ||
| 17 | anima_idx anima; | ||
| 18 | camera_idx camera; | ||
| 19 | light_idx light; | ||
| 20 | model_idx model; | ||
| 21 | object_idx object; | ||
| 22 | }; | ||
| 23 | mat4 transform; // Transformation for this node and its children. | ||
| 24 | node_idx parent; // Parent SceneNode. | ||
| 25 | node_idx child; // First child SceneNode. | ||
| 26 | node_idx next; // Next sibling SceneNode. | ||
| 27 | node_idx prev; // Previous sibling SceneNode. | ||
| 28 | } SceneNode; | ||
| 29 | |||
| 30 | /// Recursively destroy a node given its index but without destroying the node | ||
| 31 | /// resources. | ||
| 32 | /// | ||
| 33 | /// The node and its children are removed from the scene graph. | ||
| 34 | /// | ||
| 35 | /// This function is for the library's internal use only. | ||
| 36 | void gfx_del_node(node_idx); | ||
| 37 | |||
| 38 | /// Return a shallow clone of the scene rooted at the given node. | ||
| 39 | /// The given node must have no siblings (must be a root node). | ||
| 40 | SceneNode* gfx_clone_scene_shallow(const SceneNode*); | ||
diff --git a/src/scene/object.c b/src/scene/object.c new file mode 100644 index 0000000..e8e3ee6 --- /dev/null +++ b/src/scene/object.c | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | #include "object_impl.h" | ||
| 2 | |||
| 3 | #include <gfx/core.h> | ||
| 4 | |||
| 5 | #include "mesh_impl.h" | ||
| 6 | #include "node_impl.h" | ||
| 7 | #include "scene_memory.h" | ||
| 8 | |||
| 9 | #include <assert.h> | ||
| 10 | |||
| 11 | static aabb3 calc_object_aabb(const SceneObject* object) { | ||
| 12 | assert(object); | ||
| 13 | |||
| 14 | bool first = true; | ||
| 15 | aabb3 box; | ||
| 16 | |||
| 17 | mesh_link_idx ml = object->mesh_link; | ||
| 18 | while (ml.val) { | ||
| 19 | const MeshLink* mesh_link = mem_get_mesh_link(ml); | ||
| 20 | const mesh_idx mi = mesh_link->mesh; | ||
| 21 | if (mi.val) { | ||
| 22 | const Mesh* mesh = mem_get_mesh(mi); | ||
| 23 | const aabb3 mesh_box = gfx_get_geometry_aabb(mesh->geometry); | ||
| 24 | if (first) { | ||
| 25 | box = mesh_box; | ||
| 26 | first = false; | ||
| 27 | } else { | ||
| 28 | box = aabb3_sum(box, mesh_box); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | ml = mesh_link->next; | ||
| 32 | } | ||
| 33 | |||
| 34 | return box; | ||
| 35 | } | ||
| 36 | |||
| 37 | static void add_object_mesh(SceneObject* object, Mesh* mesh) { | ||
| 38 | assert(object); | ||
| 39 | assert(mesh); | ||
| 40 | |||
| 41 | MeshLink* link = mem_alloc_mesh_link(); | ||
| 42 | link->mesh = mem_get_mesh_index(mesh); | ||
| 43 | link->next = object->mesh_link; | ||
| 44 | object->mesh_link = mem_get_mesh_link_index(link); | ||
| 45 | } | ||
| 46 | |||
| 47 | SceneObject* gfx_make_object(const ObjectDesc* desc) { | ||
| 48 | assert(desc); | ||
| 49 | |||
| 50 | SceneObject* object = mem_alloc_object(); | ||
| 51 | for (size_t i = 0; i < desc->num_meshes; ++i) { | ||
| 52 | add_object_mesh(object, desc->meshes[i]); | ||
| 53 | } | ||
| 54 | object->box = calc_object_aabb(object); | ||
| 55 | return object; | ||
| 56 | } | ||
| 57 | |||
| 58 | void gfx_destroy_object(SceneObject** object) { | ||
| 59 | assert(object); | ||
| 60 | |||
| 61 | if (*object) { | ||
| 62 | if ((*object)->parent.val) { | ||
| 63 | gfx_del_node((*object)->parent); | ||
| 64 | } | ||
| 65 | mem_free_object(object); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | void gfx_set_object_skeleton(SceneObject* object, const Skeleton* skeleton) { | ||
| 70 | assert(object); | ||
| 71 | assert(skeleton); | ||
| 72 | object->skeleton = mem_get_skeleton_index(skeleton); | ||
| 73 | } | ||
| 74 | |||
| 75 | const Skeleton* gfx_get_object_skeleton(const SceneObject* object) { | ||
| 76 | assert(object); | ||
| 77 | return (object->skeleton.val == 0) ? 0 : mem_get_skeleton(object->skeleton); | ||
| 78 | } | ||
| 79 | |||
| 80 | aabb3 gfx_get_object_aabb(const SceneObject* object) { | ||
| 81 | assert(object); | ||
| 82 | return object->box; | ||
| 83 | } | ||
diff --git a/src/scene/object_impl.h b/src/scene/object_impl.h new file mode 100644 index 0000000..88f8e31 --- /dev/null +++ b/src/scene/object_impl.h | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/object.h> | ||
| 4 | |||
| 5 | #include "types.h" | ||
| 6 | |||
| 7 | #include <math/mat4.h> | ||
| 8 | |||
| 9 | typedef struct MeshLink { | ||
| 10 | mesh_idx mesh; | ||
| 11 | mesh_link_idx next; // Next MeshLink in the list. | ||
| 12 | } MeshLink; | ||
| 13 | |||
| 14 | /// Scene object. | ||
| 15 | /// | ||
| 16 | /// A SceneObject does not own its Meshes, and they are instead shared for | ||
| 17 | /// re-use. The SceneObject consequently embeds a list of MeshLinks as opposed | ||
| 18 | /// to a list of Meshes. The MeshLinks define a list of Meshes, which can be | ||
| 19 | /// different for each SceneObject. Each SceneObject may then have a unique list | ||
| 20 | /// of Meshes, and the Meshes are re-used. | ||
| 21 | typedef struct SceneObject { | ||
| 22 | mesh_link_idx mesh_link; /// First MeshLink in the list. | ||
| 23 | skeleton_idx skeleton; /// 0 for static objects. | ||
| 24 | node_idx parent; /// Parent SceneNode. | ||
| 25 | aabb3 box; | ||
| 26 | } SceneObject; | ||
diff --git a/src/scene/scene.c b/src/scene/scene.c new file mode 100644 index 0000000..54452dd --- /dev/null +++ b/src/scene/scene.c | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #include "scene_impl.h" | ||
| 2 | |||
| 3 | #include "node_impl.h" | ||
| 4 | #include "scene_memory.h" | ||
| 5 | |||
| 6 | #include <assert.h> | ||
| 7 | |||
| 8 | Scene* gfx_make_scene(void) { | ||
| 9 | Scene* scene = mem_alloc_scene(); | ||
| 10 | scene->root = gfx_make_node(); | ||
| 11 | return scene; | ||
| 12 | } | ||
| 13 | |||
| 14 | void gfx_destroy_scene(Scene** scene) { | ||
| 15 | assert(scene); | ||
| 16 | if (*scene) { | ||
| 17 | gfx_destroy_node(&(*scene)->root); | ||
| 18 | mem_free_scene(scene); | ||
| 19 | } | ||
| 20 | } | ||
| 21 | |||
| 22 | SceneNode* gfx_get_scene_root(Scene* scene) { | ||
| 23 | assert(scene); | ||
| 24 | return scene->root; | ||
| 25 | } | ||
diff --git a/src/scene/scene_graph.h b/src/scene/scene_graph.h new file mode 100644 index 0000000..a26f828 --- /dev/null +++ b/src/scene/scene_graph.h | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | /// Functions for list manipulation. | ||
| 2 | #pragma once | ||
| 3 | |||
| 4 | #include "scene_memory.h" | ||
| 5 | |||
| 6 | // NOTE: SceneMemory guarantees that index 0 can be regarded as an invalid | ||
| 7 | // index. | ||
| 8 | |||
| 9 | #define MEM_GET(INDEX) \ | ||
| 10 | _Generic((INDEX), camera_idx \ | ||
| 11 | : mem_get_camera, material_idx \ | ||
| 12 | : mem_get_material, mesh_idx \ | ||
| 13 | : mem_get_mesh, mesh_link_idx \ | ||
| 14 | : mem_get_mesh_link, node_idx \ | ||
| 15 | : mem_get_node, object_idx \ | ||
| 16 | : mem_get_object, scene_idx \ | ||
| 17 | : mem_get_scene)(INDEX) | ||
| 18 | |||
| 19 | #define MEM_GET_INDEX(ITEM) \ | ||
| 20 | _Generic((ITEM), SceneCamera * \ | ||
| 21 | : mem_get_camera_index, Material * \ | ||
| 22 | : mem_get_material_index, Mesh * \ | ||
| 23 | : mem_get_mesh_index, MeshLink * \ | ||
| 24 | : mem_get_mesh_link_index, SceneNode * \ | ||
| 25 | : mem_get_node_index, SceneObject * \ | ||
| 26 | : mem_get_object_index, Scene * \ | ||
| 27 | : mem_get_scene_index)(ITEM) | ||
| 28 | |||
| 29 | /// Assert the list node invariant. | ||
| 30 | /// | ||
| 31 | /// - A node does not point to itself. | ||
| 32 | #define ASSERT_LIST_NODE_INVARIANT(ITEM) \ | ||
| 33 | { \ | ||
| 34 | const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \ | ||
| 35 | assert((ITEM)->prev.val != item_idx); \ | ||
| 36 | assert((ITEM)->next.val != item_idx); \ | ||
| 37 | } | ||
| 38 | |||
| 39 | /// Assert the tree node invariant. | ||
| 40 | /// | ||
| 41 | /// - A node does not point to itself. | ||
| 42 | /// - The node's left and right siblings cannot be equal, unless both are 0. | ||
| 43 | /// - The node's left/right sibling cannot be its child, unless both are 0. | ||
| 44 | /// - The node's parent cannot be the node's child or sibling, unless it's 0. | ||
| 45 | /// - If the node has a parent and the node is the leftmost sibling, then the | ||
| 46 | /// parent's child is the node. | ||
| 47 | #define ASSERT_TREE_NODE_INVARIANT(ITEM) \ | ||
| 48 | { \ | ||
| 49 | const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \ | ||
| 50 | assert((ITEM)->prev.val != item_idx); \ | ||
| 51 | assert((ITEM)->next.val != item_idx); \ | ||
| 52 | if ((ITEM)->prev.val) { \ | ||
| 53 | assert((ITEM)->prev.val != (ITEM)->next.val); \ | ||
| 54 | } \ | ||
| 55 | if ((ITEM)->child.val) { \ | ||
| 56 | assert((ITEM)->child.val != (ITEM)->prev.val); \ | ||
| 57 | assert((ITEM)->child.val != (ITEM)->next.val); \ | ||
| 58 | } \ | ||
| 59 | assert((ITEM)->parent.val != item_idx); \ | ||
| 60 | if ((ITEM)->parent.val && !(ITEM)->prev.val) { \ | ||
| 61 | assert((ITEM)->parent.val != (ITEM)->prev.val); \ | ||
| 62 | assert((ITEM)->parent.val != (ITEM)->next.val); \ | ||
| 63 | const __typeof__(ITEM) item_parent = MEM_GET((ITEM)->parent); \ | ||
| 64 | assert(item_parent->child.val == item_idx); \ | ||
| 65 | } \ | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Prepend an item to a list. | ||
| 69 | /// Modify HEAD_INDEX to equal the index of the new head. | ||
| 70 | #define LIST_PREPEND(HEAD_INDEX, ITEM) \ | ||
| 71 | (ITEM)->next = HEAD_INDEX; \ | ||
| 72 | if (HEAD_INDEX.val) { \ | ||
| 73 | __typeof__(ITEM) old_head = MEM_GET(HEAD_INDEX); \ | ||
| 74 | old_head->prev = MEM_GET_INDEX(ITEM); \ | ||
| 75 | } \ | ||
| 76 | HEAD_INDEX = MEM_GET_INDEX(ITEM); \ | ||
| 77 | ASSERT_LIST_NODE_INVARIANT(ITEM); | ||
| 78 | |||
| 79 | /// Disconnect an item from its siblings. | ||
| 80 | #define LIST_REMOVE(ITEM) \ | ||
| 81 | if ((ITEM)->prev.val) { \ | ||
| 82 | __typeof__(ITEM) prev_sibling = MEM_GET((ITEM)->prev); \ | ||
| 83 | prev_sibling->next = (ITEM)->next; \ | ||
| 84 | } \ | ||
| 85 | if ((ITEM)->next.val) { \ | ||
| 86 | __typeof__(ITEM) next_sibling = MEM_GET((ITEM)->next); \ | ||
| 87 | next_sibling->prev = (ITEM)->prev; \ | ||
| 88 | } \ | ||
| 89 | (ITEM)->prev.val = 0; \ | ||
| 90 | (ITEM)->next.val = 0; \ | ||
| 91 | ASSERT_LIST_NODE_INVARIANT(ITEM); | ||
| 92 | |||
| 93 | /// Set the child's parent. | ||
| 94 | /// | ||
| 95 | /// The hierarchy is a strict tree hierarchy and a parent node points to its | ||
| 96 | /// first/leftmost child only. To add a new child, the new child becomes the | ||
| 97 | /// leftmost node in the list of siblings, the one that the parent then points | ||
| 98 | /// to. | ||
| 99 | /// | ||
| 100 | /// The child is also completely disconnected from its previous hierarchy. This | ||
| 101 | /// is because siblings in a hierarchy must all point to the same parent. | ||
| 102 | #define SET_PARENT(CHILD, PARENT) \ | ||
| 103 | assert(CHILD); \ | ||
| 104 | assert(CHILD != PARENT); \ | ||
| 105 | ASSERT_TREE_NODE_INVARIANT(CHILD); \ | ||
| 106 | ASSERT_TREE_NODE_INVARIANT(PARENT); \ | ||
| 107 | TREE_REMOVE(CHILD); /* Disconnect CHILD from its previous hierarchy. */ \ | ||
| 108 | if (PARENT) { \ | ||
| 109 | LIST_PREPEND((PARENT)->child, CHILD); \ | ||
| 110 | (CHILD)->parent = MEM_GET_INDEX(PARENT); \ | ||
| 111 | } else { \ | ||
| 112 | (CHILD)->parent.val = 0; \ | ||
| 113 | } \ | ||
| 114 | ASSERT_TREE_NODE_INVARIANT(CHILD); \ | ||
| 115 | if (PARENT) { \ | ||
| 116 | ASSERT_TREE_NODE_INVARIANT(PARENT); \ | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Remove an item from its hierarchy. | ||
| 120 | /// | ||
| 121 | /// The item is disconnected from its parents and siblings. The hierarchy rooted | ||
| 122 | /// under the item remains intact. | ||
| 123 | #define TREE_REMOVE(ITEM) \ | ||
| 124 | assert(ITEM); \ | ||
| 125 | if ((ITEM)->parent.val) { \ | ||
| 126 | /* The parent points only to its first/leftmost child. If this item is */ \ | ||
| 127 | /* the leftmost sibling, then we need to rewire the parent to point to */ \ | ||
| 128 | /* the next sibling to keep the parent connected to its children. */ \ | ||
| 129 | __typeof__(ITEM) parent = MEM_GET((ITEM)->parent); \ | ||
| 130 | const __typeof__(ITEM) parent_child = MEM_GET(parent->child); \ | ||
| 131 | if (parent_child == ITEM) { \ | ||
| 132 | assert((ITEM)->prev.val == 0); \ | ||
| 133 | parent->child = (ITEM)->next; \ | ||
| 134 | } \ | ||
| 135 | } \ | ||
| 136 | (ITEM)->parent.val = 0; \ | ||
| 137 | LIST_REMOVE(ITEM); /* Disconnect ITEM from its siblings. */ \ | ||
| 138 | ASSERT_TREE_NODE_INVARIANT(ITEM); | ||
diff --git a/src/scene/scene_impl.h b/src/scene/scene_impl.h new file mode 100644 index 0000000..992f620 --- /dev/null +++ b/src/scene/scene_impl.h | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <gfx/scene/scene.h> | ||
| 4 | |||
| 5 | #include "types.h" | ||
| 6 | |||
| 7 | typedef struct SceneNode SceneNode; | ||
| 8 | |||
| 9 | typedef struct Scene { | ||
| 10 | SceneNode* root; | ||
| 11 | scene_idx next; | ||
| 12 | scene_idx prev; | ||
| 13 | } Scene; | ||
diff --git a/src/scene/scene_memory.c b/src/scene/scene_memory.c new file mode 100644 index 0000000..85c27e7 --- /dev/null +++ b/src/scene/scene_memory.c | |||
| @@ -0,0 +1,149 @@ | |||
| 1 | #include "scene_memory.h" | ||
| 2 | |||
| 3 | #include <gfx/sizes.h> | ||
| 4 | |||
| 5 | #include "animation_impl.h" | ||
| 6 | #include "camera_impl.h" | ||
| 7 | #include "light_impl.h" | ||
| 8 | #include "material_impl.h" | ||
| 9 | #include "mesh_impl.h" | ||
| 10 | #include "model_impl.h" | ||
| 11 | #include "node_impl.h" | ||
| 12 | #include "object_impl.h" | ||
| 13 | #include "scene_impl.h" | ||
| 14 | |||
| 15 | #include <mempool.h> | ||
| 16 | |||
| 17 | DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS) | ||
| 18 | DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS) | ||
| 19 | DEF_MEMPOOL(camera_pool, SceneCamera, GFX_MAX_NUM_CAMERAS) | ||
| 20 | DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS) | ||
| 21 | DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS) | ||
| 22 | DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES) | ||
| 23 | DEF_MEMPOOL(mesh_link_pool, MeshLink, GFX_MAX_NUM_MESH_LINKS) | ||
| 24 | DEF_MEMPOOL(model_pool, Model, GFX_MAX_NUM_MODELS) | ||
| 25 | DEF_MEMPOOL(node_pool, SceneNode, GFX_MAX_NUM_NODES) | ||
| 26 | DEF_MEMPOOL(object_pool, SceneObject, GFX_MAX_NUM_OBJECTS) | ||
| 27 | DEF_MEMPOOL(scene_pool, Scene, GFX_MAX_NUM_SCENES) | ||
| 28 | DEF_MEMPOOL(skeleton_pool, Skeleton, GFX_MAX_NUM_SKELETONS) | ||
| 29 | |||
| 30 | /// Scene memory. | ||
| 31 | /// | ||
| 32 | /// Holds memory pools for every type of scene object. | ||
| 33 | typedef struct SceneMemory { | ||
| 34 | anima_pool animas; | ||
| 35 | animation_pool animations; | ||
| 36 | camera_pool cameras; | ||
| 37 | light_pool lights; | ||
| 38 | material_pool materials; | ||
| 39 | mesh_pool meshs; // Purposeful typo to make the PLURAL() macro work. | ||
| 40 | mesh_link_pool mesh_links; | ||
| 41 | model_pool models; | ||
| 42 | node_pool nodes; | ||
| 43 | object_pool objects; | ||
| 44 | scene_pool scenes; | ||
| 45 | skeleton_pool skeletons; | ||
| 46 | } SceneMemory; | ||
| 47 | |||
| 48 | static SceneMemory mem; | ||
| 49 | |||
| 50 | #define ALLOC_DUMMY(POOL) \ | ||
| 51 | { \ | ||
| 52 | const void* object = mempool_alloc(POOL); \ | ||
| 53 | assert(mempool_get_block_index(POOL, object) == 0); \ | ||
| 54 | } | ||
| 55 | |||
| 56 | #define PLURAL(name) name##s | ||
| 57 | #define MEM_FIELD(name) mem.PLURAL(name) | ||
| 58 | |||
| 59 | void scene_mem_init() { | ||
| 60 | mempool_make(&mem.animas); | ||
| 61 | mempool_make(&mem.animations); | ||
| 62 | mempool_make(&mem.cameras); | ||
| 63 | mempool_make(&mem.lights); | ||
| 64 | mempool_make(&mem.materials); | ||
| 65 | mempool_make(&mem.meshs); | ||
| 66 | mempool_make(&mem.mesh_links); | ||
| 67 | mempool_make(&mem.models); | ||
| 68 | mempool_make(&mem.nodes); | ||
| 69 | mempool_make(&mem.objects); | ||
| 70 | mempool_make(&mem.scenes); | ||
| 71 | mempool_make(&mem.skeletons); | ||
| 72 | |||
| 73 | // Allocate dummy objects at index 0 to guarantee that no objects allocated by | ||
| 74 | // the caller map to index 0. | ||
| 75 | ALLOC_DUMMY(&mem.animas); | ||
| 76 | ALLOC_DUMMY(&mem.animations); | ||
| 77 | ALLOC_DUMMY(&mem.cameras); | ||
| 78 | ALLOC_DUMMY(&mem.lights); | ||
| 79 | ALLOC_DUMMY(&mem.materials); | ||
| 80 | ALLOC_DUMMY(&mem.meshs); | ||
| 81 | ALLOC_DUMMY(&mem.mesh_links); | ||
| 82 | ALLOC_DUMMY(&mem.models); | ||
| 83 | ALLOC_DUMMY(&mem.nodes); | ||
| 84 | ALLOC_DUMMY(&mem.objects); | ||
| 85 | ALLOC_DUMMY(&mem.scenes); | ||
| 86 | ALLOC_DUMMY(&mem.skeletons); | ||
| 87 | } | ||
| 88 | |||
| 89 | void scene_mem_destroy() { | ||
| 90 | // NOTE: the dummy objects are not constructed, so the destruction code below | ||
| 91 | // always skips index 0. (I don't really like the conditional inside the loop, | ||
| 92 | // but this gets the job done without having to specialize the loop macro.) | ||
| 93 | #define DESTROY(name) \ | ||
| 94 | mempool_foreach(&MEM_FIELD(name), obj, { \ | ||
| 95 | if (i > 0) { \ | ||
| 96 | gfx_destroy_##name(&obj); \ | ||
| 97 | } \ | ||
| 98 | }) | ||
| 99 | |||
| 100 | // Models contain scene elements. Destruction is handled by the remainder of | ||
| 101 | // scene destructionb elow. | ||
| 102 | // | ||
| 103 | // First destroy the scenes. This will recursively destroy the scene's nodes | ||
| 104 | // and their objects and avoid a double-free when we then destroy any stray | ||
| 105 | // scene elements. | ||
| 106 | DESTROY(scene); | ||
| 107 | // Then delete stray nodes. This will delete their children nodes and | ||
| 108 | // resource. | ||
| 109 | DESTROY(node); | ||
| 110 | // Destroy remaining scene elements. | ||
| 111 | DESTROY(anima); | ||
| 112 | // Animations are owned by animas and do not have a destructor. | ||
| 113 | DESTROY(camera); | ||
| 114 | DESTROY(light); | ||
| 115 | DESTROY(material); | ||
| 116 | DESTROY(mesh); | ||
| 117 | // Mesh links don't have a destructor. | ||
| 118 | DESTROY(object); | ||
| 119 | // Skeletons are owned by animas and do not have a destructor. | ||
| 120 | } | ||
| 121 | |||
| 122 | #define DEF_MEMORY(name, type) \ | ||
| 123 | /* xyz* mem_alloc_xyz(); */ \ | ||
| 124 | type* mem_alloc_##name() { return mempool_alloc(&MEM_FIELD(name)); } \ | ||
| 125 | /* void mem_free_xyz(xyz**); */ \ | ||
| 126 | void mem_free_##name(type** obj) { mempool_free(&MEM_FIELD(name), obj); } \ | ||
| 127 | /* xyz* mem_get_xyz(xyz_idx); */ \ | ||
| 128 | type* mem_get_##name(NAMED_INDEX(name) index) { \ | ||
| 129 | assert(index.val != 0); /* 0 is the dummy allocation. */ \ | ||
| 130 | return mempool_get_block(&MEM_FIELD(name), index.val); \ | ||
| 131 | } \ | ||
| 132 | /* xyz_idx mem_get_xyz_index(const xyz*); */ \ | ||
| 133 | NAMED_INDEX(name) mem_get_##name##_index(const type* obj) { \ | ||
| 134 | return (NAMED_INDEX(name)){ \ | ||
| 135 | .val = mempool_get_block_index(&MEM_FIELD(name), obj)}; \ | ||
| 136 | } | ||
| 137 | |||
| 138 | DEF_MEMORY(anima, Anima) | ||
| 139 | DEF_MEMORY(animation, Animation) | ||
| 140 | DEF_MEMORY(camera, SceneCamera) | ||
| 141 | DEF_MEMORY(light, Light) | ||
| 142 | DEF_MEMORY(material, Material) | ||
| 143 | DEF_MEMORY(mesh, Mesh) | ||
| 144 | DEF_MEMORY(mesh_link, MeshLink) | ||
| 145 | DEF_MEMORY(model, Model) | ||
| 146 | DEF_MEMORY(node, SceneNode) | ||
| 147 | DEF_MEMORY(object, SceneObject) | ||
| 148 | DEF_MEMORY(scene, Scene) | ||
| 149 | DEF_MEMORY(skeleton, Skeleton) | ||
diff --git a/src/scene/scene_memory.h b/src/scene/scene_memory.h new file mode 100644 index 0000000..d175cba --- /dev/null +++ b/src/scene/scene_memory.h | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | /// Memory management of scene objects. | ||
| 2 | #pragma once | ||
| 3 | |||
| 4 | #include "types.h" | ||
| 5 | |||
| 6 | /// Initialize scene memory. | ||
| 7 | /// | ||
| 8 | /// The scene memory guarantees that every object maps to an index different | ||
| 9 | /// than 0. This way, 0 can be used as a special index to denote "no value". | ||
| 10 | void scene_mem_init(); | ||
| 11 | |||
| 12 | /// Destroy the scene memory and all allocated objects. | ||
| 13 | void scene_mem_destroy(); | ||
| 14 | |||
| 15 | #define NAMED_INDEX(name) name##_idx | ||
| 16 | |||
| 17 | #define DECL_MEMORY(name, type) \ | ||
| 18 | typedef struct type type; \ | ||
| 19 | /* xyz* mem_alloc_xyz() */ \ | ||
| 20 | type* mem_alloc_##name(); \ | ||
| 21 | /* mem_free_xyz(xyz**) */ \ | ||
| 22 | void mem_free_##name(type**); \ | ||
| 23 | /* xyz* mem_get_xyz(xyz_idx); */ \ | ||
| 24 | type* mem_get_##name(NAMED_INDEX(name)); \ | ||
| 25 | /* xyz_idx mem_get_xyz_index(const xyz*); */ \ | ||
| 26 | NAMED_INDEX(name) mem_get_##name##_index(const type*); | ||
| 27 | |||
| 28 | DECL_MEMORY(anima, Anima) | ||
| 29 | DECL_MEMORY(animation, Animation) | ||
| 30 | DECL_MEMORY(camera, SceneCamera) | ||
| 31 | DECL_MEMORY(light, Light) | ||
| 32 | DECL_MEMORY(material, Material) | ||
| 33 | DECL_MEMORY(mesh, Mesh) | ||
| 34 | DECL_MEMORY(mesh_link, MeshLink) | ||
| 35 | DECL_MEMORY(model, Model) | ||
| 36 | DECL_MEMORY(node, SceneNode) | ||
| 37 | DECL_MEMORY(object, SceneObject) | ||
| 38 | DECL_MEMORY(scene, Scene) | ||
| 39 | DECL_MEMORY(skeleton, Skeleton) | ||
diff --git a/src/scene/types.h b/src/scene/types.h new file mode 100644 index 0000000..d0ffc41 --- /dev/null +++ b/src/scene/types.h | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | /// Strongly-typed indices for every kind of scene node resource. | ||
| 2 | #pragma once | ||
| 3 | |||
| 4 | #include <stdint.h> | ||
| 5 | |||
| 6 | typedef uint16_t gfx_idx; | ||
| 7 | |||
| 8 | #define DEF_STRONG_INDEX(TYPE_NAME, IDX_TYPE) \ | ||
| 9 | typedef struct TYPE_NAME##_idx { \ | ||
| 10 | IDX_TYPE val; \ | ||
| 11 | } TYPE_NAME##_idx; | ||
| 12 | |||
| 13 | DEF_STRONG_INDEX(anima, gfx_idx) | ||
| 14 | DEF_STRONG_INDEX(animation, gfx_idx) | ||
| 15 | DEF_STRONG_INDEX(camera, gfx_idx) | ||
| 16 | DEF_STRONG_INDEX(light, gfx_idx) | ||
| 17 | DEF_STRONG_INDEX(material, gfx_idx) | ||
| 18 | DEF_STRONG_INDEX(mesh, gfx_idx) | ||
| 19 | DEF_STRONG_INDEX(mesh_link, gfx_idx) | ||
| 20 | DEF_STRONG_INDEX(model, gfx_idx) | ||
| 21 | DEF_STRONG_INDEX(node, gfx_idx) | ||
| 22 | DEF_STRONG_INDEX(object, gfx_idx) | ||
| 23 | DEF_STRONG_INDEX(scene, gfx_idx) | ||
| 24 | DEF_STRONG_INDEX(skeleton, gfx_idx) | ||
