aboutsummaryrefslogtreecommitdiff
path: root/src/scene
diff options
context:
space:
mode:
Diffstat (limited to 'src/scene')
-rw-r--r--src/scene/animation.c524
-rw-r--r--src/scene/animation_impl.h98
-rw-r--r--src/scene/camera.c37
-rw-r--r--src/scene/camera_impl.h12
-rw-r--r--src/scene/light.c42
-rw-r--r--src/scene/light_impl.h25
-rw-r--r--src/scene/material.c57
-rw-r--r--src/scene/material_impl.h16
-rw-r--r--src/scene/mesh.c24
-rw-r--r--src/scene/mesh_impl.h12
-rw-r--r--src/scene/model.c45
-rw-r--r--src/scene/model_impl.h17
-rw-r--r--src/scene/node.c409
-rw-r--r--src/scene/node_impl.h40
-rw-r--r--src/scene/object.c83
-rw-r--r--src/scene/object_impl.h26
-rw-r--r--src/scene/scene.c25
-rw-r--r--src/scene/scene_graph.h138
-rw-r--r--src/scene/scene_impl.h13
-rw-r--r--src/scene/scene_memory.c149
-rw-r--r--src/scene/scene_memory.h39
-rw-r--r--src/scene/types.h24
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
10static const R PLAYBACK_UNINITIALIZED = -1;
11
12static 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
19static Joint* get_anima_root_joint(Anima* anima) {
20 assert(anima);
21 return &anima->joints[get_anima_root_joint_index(anima)];
22}
23
24static 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
32static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) {
33 return (Joint*)get_anima_joint(anima, index);
34}
35
36static 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
43static 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
65static 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
81static 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
93static 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
133Anima* 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
191void 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
215static 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
233bool 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
251static void gfx_set_joint_position(Joint* joint, vec3 position) {
252 assert(joint);
253 mat4_set_v3(&joint->transform, position);
254}
255
256static void gfx_set_joint_rotation(Joint* joint, quat rotation) {
257 assert(joint);
258 mat4_set_3x3(&joint->transform, mat4_from_quat(rotation));
259}
260
261static 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
275static 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
281static 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
310static 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
339static 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
376static 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
410void 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
463const 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
477size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) {
478 assert(skeleton);
479 return skeleton->num_joints;
480}
481
482bool 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
492Box 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
17typedef 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.
26typedef 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.
36typedef 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.
43typedef 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.
52typedef 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.
61typedef 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.
72typedef 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).
91typedef 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
8SceneCamera* 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
18void 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
28void gfx_set_camera_camera(SceneCamera* scene_camera, Camera* camera) {
29 assert(scene_camera);
30 assert(camera);
31 scene_camera->camera = *camera;
32}
33
34Camera* 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
9typedef 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
8static 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
16Light* 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
34void 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
7typedef struct Texture Texture;
8
9/// An environment light.
10typedef 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.
19typedef 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
7static 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
17Material* 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
24void gfx_destroy_material(Material** material) { mem_free_material(material); }
25
26static 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
51void 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
5typedef struct ShaderProgram ShaderProgram;
6
7typedef 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.
16void 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
7static 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
18Mesh* gfx_make_mesh(const MeshDesc* desc) {
19 Mesh* mesh = mem_alloc_mesh();
20 mesh_make(mesh, desc);
21 return mesh;
22}
23
24void 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
5typedef 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
9Model* 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
17void 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
27Anima* 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
38const SceneNode* gfx_get_model_root(const Model* model) {
39 assert(model);
40 return mem_get_node(model->root);
41}
42
43SceneNode* 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.
8typedef struct Model {
9 node_idx root;
10 node_idx parent; // Parent SceneNode.
11} Model;
12
13/// Create a new model.
14Model* gfx_make_model(const SceneNode* root);
15
16/// Destroy the model.
17void 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
16static void scene_node_make(SceneNode* node) {
17 assert(node);
18 node->type = LogicalNode;
19 node->transform = mat4_id();
20}
21
22SceneNode* gfx_make_node() {
23 SceneNode* node = mem_alloc_node();
24 scene_node_make(node);
25 return node;
26}
27
28SceneNode* 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
37SceneNode* 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
46SceneNode* 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
55SceneNode* 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
64SceneNode* 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.
74static 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
113void 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
122void 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.
133void 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
142void 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
151void 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
160static 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
177void 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?
191void 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
200NodeType 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
212const Anima* gfx_get_node_anima(const SceneNode* node) {
213 NODE_GET(node, anima, AnimaNode);
214}
215
216Anima* gfx_get_node_anima_mut(SceneNode* node) {
217 NODE_GET(node, anima, AnimaNode);
218}
219
220const SceneCamera* gfx_get_node_camera(const SceneNode* node) {
221 NODE_GET(node, camera, CameraNode);
222}
223
224SceneCamera* gfx_get_node_camera_mut(SceneNode* node) {
225 NODE_GET(node, camera, CameraNode);
226}
227
228const Light* gfx_get_node_light(const SceneNode* node) {
229 NODE_GET(node, light, LightNode);
230}
231
232Light* gfx_get_node_light_mut(SceneNode* node) {
233 NODE_GET(node, light, LightNode);
234}
235
236const Model* gfx_get_node_model(const SceneNode* node) {
237 NODE_GET(node, model, ModelNode);
238}
239
240Model* gfx_get_node_model_mut(SceneNode* node) {
241 NODE_GET(node, model, ModelNode);
242}
243
244const SceneObject* gfx_get_node_object(const SceneNode* node) {
245 NODE_GET(node, object, ObjectNode);
246}
247
248SceneObject* gfx_get_node_object_mut(SceneNode* node) {
249 NODE_GET(node, object, ObjectNode);
250}
251
252const SceneNode* gfx_get_node_parent(const SceneNode* node) {
253 assert(node);
254 return mem_get_node(node->parent);
255}
256
257SceneNode* gfx_get_node_parent_mut(SceneNode* node) {
258 assert(node);
259 return mem_get_node(node->parent);
260}
261
262const 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
271SceneNode* gfx_get_node_child_mut(SceneNode* node) {
272 return (SceneNode*)gfx_get_node_child(node);
273}
274
275const 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
284SceneNode* gfx_get_node_sibling_mut(SceneNode* node) {
285 return (SceneNode*)gfx_get_node_sibling(node);
286}
287
288mat4 gfx_get_node_transform(const SceneNode* node) {
289 assert(node);
290 return node->transform;
291}
292
293mat4 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
305void 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
311void gfx_set_node_transform(SceneNode* node, const mat4* transform) {
312 assert(node);
313 assert(transform);
314 node->transform = *transform;
315}
316
317void gfx_set_node_position(SceneNode* node, const vec3* position) {
318 assert(node);
319 assert(position);
320 mat4_set_v3(&node->transform, *position);
321}
322
323void 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
329void 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
335static 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
354static 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
374void gfx_log_node_hierarchy(const SceneNode* node) {
375 const sstring pad = sstring_make("");
376 log_node_hierarchy_rec(node, &pad);
377}
378
379static 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
402SceneNode* 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.
14typedef 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.
36void 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).
40SceneNode* 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
11static 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
37static 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
47SceneObject* 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
58void 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
69void 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
75const 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
80aabb3 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
9typedef 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.
21typedef 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
8Scene* gfx_make_scene(void) {
9 Scene* scene = mem_alloc_scene();
10 scene->root = gfx_make_node();
11 return scene;
12}
13
14void 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
22SceneNode* 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
7typedef struct SceneNode SceneNode;
8
9typedef 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
17DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS)
18DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS)
19DEF_MEMPOOL(camera_pool, SceneCamera, GFX_MAX_NUM_CAMERAS)
20DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS)
21DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS)
22DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES)
23DEF_MEMPOOL(mesh_link_pool, MeshLink, GFX_MAX_NUM_MESH_LINKS)
24DEF_MEMPOOL(model_pool, Model, GFX_MAX_NUM_MODELS)
25DEF_MEMPOOL(node_pool, SceneNode, GFX_MAX_NUM_NODES)
26DEF_MEMPOOL(object_pool, SceneObject, GFX_MAX_NUM_OBJECTS)
27DEF_MEMPOOL(scene_pool, Scene, GFX_MAX_NUM_SCENES)
28DEF_MEMPOOL(skeleton_pool, Skeleton, GFX_MAX_NUM_SKELETONS)
29
30/// Scene memory.
31///
32/// Holds memory pools for every type of scene object.
33typedef 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
48static 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
59void 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
89void 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
138DEF_MEMORY(anima, Anima)
139DEF_MEMORY(animation, Animation)
140DEF_MEMORY(camera, SceneCamera)
141DEF_MEMORY(light, Light)
142DEF_MEMORY(material, Material)
143DEF_MEMORY(mesh, Mesh)
144DEF_MEMORY(mesh_link, MeshLink)
145DEF_MEMORY(model, Model)
146DEF_MEMORY(node, SceneNode)
147DEF_MEMORY(object, SceneObject)
148DEF_MEMORY(scene, Scene)
149DEF_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".
10void scene_mem_init();
11
12/// Destroy the scene memory and all allocated objects.
13void 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
28DECL_MEMORY(anima, Anima)
29DECL_MEMORY(animation, Animation)
30DECL_MEMORY(camera, SceneCamera)
31DECL_MEMORY(light, Light)
32DECL_MEMORY(material, Material)
33DECL_MEMORY(mesh, Mesh)
34DECL_MEMORY(mesh_link, MeshLink)
35DECL_MEMORY(model, Model)
36DECL_MEMORY(node, SceneNode)
37DECL_MEMORY(object, SceneObject)
38DECL_MEMORY(scene, Scene)
39DECL_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
6typedef 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
13DEF_STRONG_INDEX(anima, gfx_idx)
14DEF_STRONG_INDEX(animation, gfx_idx)
15DEF_STRONG_INDEX(camera, gfx_idx)
16DEF_STRONG_INDEX(light, gfx_idx)
17DEF_STRONG_INDEX(material, gfx_idx)
18DEF_STRONG_INDEX(mesh, gfx_idx)
19DEF_STRONG_INDEX(mesh_link, gfx_idx)
20DEF_STRONG_INDEX(model, gfx_idx)
21DEF_STRONG_INDEX(node, gfx_idx)
22DEF_STRONG_INDEX(object, gfx_idx)
23DEF_STRONG_INDEX(scene, gfx_idx)
24DEF_STRONG_INDEX(skeleton, gfx_idx)