From bd57f345ed9dbed1d81683e48199626de2ea9044 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 27 Jun 2025 10:18:39 -0700 Subject: Restructure project --- src/scene/animation.c | 524 +++++++++++++++++++++++++++++++++++++++++++++ src/scene/animation_impl.h | 98 +++++++++ src/scene/camera.c | 37 ++++ src/scene/camera_impl.h | 12 ++ src/scene/light.c | 42 ++++ src/scene/light_impl.h | 25 +++ src/scene/material.c | 57 +++++ src/scene/material_impl.h | 16 ++ src/scene/mesh.c | 24 +++ src/scene/mesh_impl.h | 12 ++ src/scene/model.c | 45 ++++ src/scene/model_impl.h | 17 ++ src/scene/node.c | 409 +++++++++++++++++++++++++++++++++++ src/scene/node_impl.h | 40 ++++ src/scene/object.c | 83 +++++++ src/scene/object_impl.h | 26 +++ src/scene/scene.c | 25 +++ src/scene/scene_graph.h | 138 ++++++++++++ src/scene/scene_impl.h | 13 ++ src/scene/scene_memory.c | 149 +++++++++++++ src/scene/scene_memory.h | 39 ++++ src/scene/types.h | 24 +++ 22 files changed, 1855 insertions(+) create mode 100644 src/scene/animation.c create mode 100644 src/scene/animation_impl.h create mode 100644 src/scene/camera.c create mode 100644 src/scene/camera_impl.h create mode 100644 src/scene/light.c create mode 100644 src/scene/light_impl.h create mode 100644 src/scene/material.c create mode 100644 src/scene/material_impl.h create mode 100644 src/scene/mesh.c create mode 100644 src/scene/mesh_impl.h create mode 100644 src/scene/model.c create mode 100644 src/scene/model_impl.h create mode 100644 src/scene/node.c create mode 100644 src/scene/node_impl.h create mode 100644 src/scene/object.c create mode 100644 src/scene/object_impl.h create mode 100644 src/scene/scene.c create mode 100644 src/scene/scene_graph.h create mode 100644 src/scene/scene_impl.h create mode 100644 src/scene/scene_memory.c create mode 100644 src/scene/scene_memory.h create mode 100644 src/scene/types.h (limited to 'src/scene') 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 @@ +#include "animation_impl.h" + +#include "node_impl.h" +#include "scene_memory.h" + +#include + +// #include // Debugging. + +static const R PLAYBACK_UNINITIALIZED = -1; + +static joint_idx get_anima_root_joint_index(Anima* anima) { + assert(anima); + assert(anima->num_joints > 0); + assert(anima->num_joints < GFX_MAX_NUM_JOINTS); + return anima->num_joints - 1; +} + +static Joint* get_anima_root_joint(Anima* anima) { + assert(anima); + return &anima->joints[get_anima_root_joint_index(anima)]; +} + +static const Joint* get_anima_joint(const Anima* anima, joint_idx index) { + assert(anima); + assert(index < GFX_MAX_NUM_JOINTS); + assert(index != INDEX_NONE); + assert(index < anima->num_joints); + return &anima->joints[index]; +} + +static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) { + return (Joint*)get_anima_joint(anima, index); +} + +static const Joint* get_skeleton_joint( + const Anima* anima, const Skeleton* skeleton, joint_idx index) { + assert(anima); + assert(skeleton); + return get_anima_joint(anima, skeleton->joints[index]); +} + +static void set_joint_parent( + Anima* anima, joint_idx joint_index, joint_idx parent_index) { + assert(anima); + assert(joint_index != INDEX_NONE); + assert(joint_index != get_anima_root_joint_index(anima)); + assert(parent_index != INDEX_NONE); + + Joint* parent = get_anima_joint_mut(anima, parent_index); + + if (parent->child == INDEX_NONE) { + parent->child = joint_index; + } else { + // Find the last child in the chain of children. + Joint* child = get_anima_joint_mut(anima, parent->child); + while (child->next != INDEX_NONE) { + child = get_anima_joint_mut(anima, child->next); + } + // Wire up this joint as the last child's sibling. + child->next = joint_index; + } +} + +static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) { + assert(anima); + assert(desc); + assert(joint); + + // The joint matrix needs to be initialized so that meshes look right even if + // no animation is played. Initializing joint matrices to the identity makes + // meshes appear in their bind pose. + joint->child = INDEX_NONE; + joint->next = INDEX_NONE; + joint->transform = mat4_id(); + joint->inv_bind_matrix = desc->inv_bind_matrix; + joint->joint_matrix = mat4_id(); + joint->box = desc->box; +} + +static Skeleton* make_skeleton(const SkeletonDesc* desc) { + assert(desc); + assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); + + Skeleton* skeleton = mem_alloc_skeleton(); + skeleton->num_joints = desc->num_joints; + memcpy( + skeleton->joints, desc->joints, + desc->num_joints * sizeof(skeleton->joints[0])); + return skeleton; +} + +static Animation* make_animation(const AnimationDesc* desc) { + assert(desc); + assert(desc->num_channels < GFX_MAX_NUM_CHANNELS); + + Animation* animation = mem_alloc_animation(); + animation->name = desc->name; + animation->duration = 0; + animation->num_channels = desc->num_channels; + R start_time = 0; + R end_time = 0; + + for (size_t c = 0; c < desc->num_channels; ++c) { + const ChannelDesc* channel_desc = &desc->channels[c]; + Channel* channel = &animation->channels[c]; + + channel->target = channel_desc->target; + channel->type = channel_desc->type; + channel->interpolation = channel_desc->interpolation; + channel->num_keyframes = channel_desc->num_keyframes; + assert(channel_desc->num_keyframes < GFX_MAX_NUM_KEYFRAMES); + + for (size_t k = 0; k < channel_desc->num_keyframes; ++k) { + const KeyframeDesc* keyframe_desc = &channel_desc->keyframes[k]; + Keyframe* keyframe = &channel->keyframes[k]; + + keyframe->time = keyframe_desc->time; + keyframe->translation = keyframe_desc->translation; + keyframe->rotation = keyframe_desc->rotation; + + start_time = keyframe->time < start_time ? keyframe->time : start_time; + end_time = keyframe->time > end_time ? keyframe->time : end_time; + } + } + + // LOGD("Animation start/end: %f / %f", start_time, end_time); + animation->duration = end_time - start_time; + assert(animation->duration >= 0); + return animation; +} + +Anima* gfx_make_anima(const AnimaDesc* desc) { + assert(desc); + assert(desc->num_joints > 0); + assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); + // All joints should have a parent except for the root. + for (size_t i = 0; i < desc->num_joints - 1; ++i) { + const joint_idx parent = desc->joints[i].parent; + assert(parent != INDEX_NONE); + assert(parent < desc->num_joints); + } + // The root should have no parent. + assert(desc->joints[desc->num_joints - 1].parent == INDEX_NONE); + + Anima* anima = mem_alloc_anima(); + + // Wire the skeletons in the same order they are given in the descriptor. + Skeleton* last_skeleton = 0; + for (size_t i = 0; i < desc->num_skeletons; ++i) { + Skeleton* skeleton = make_skeleton(&desc->skeletons[i]); + const skeleton_idx skeleton_index = mem_get_skeleton_index(skeleton); + if (last_skeleton == 0) { + anima->skeleton = skeleton_index; + } else { + last_skeleton->next = skeleton_index; + } + last_skeleton = skeleton; + } + + // Wire the animations in the same order they are given in the descriptor. + Animation* last_animation = 0; + for (size_t i = 0; i < desc->num_animations; ++i) { + Animation* animation = make_animation(&desc->animations[i]); + const animation_idx animation_index = mem_get_animation_index(animation); + if (last_animation == 0) { + anima->animation = animation_index; + } else { + last_animation->next = animation_index; + } + last_animation = animation; + } + + // Create joints. + anima->num_joints = desc->num_joints; + // Initialize all joints. + // Child and sibling pointers must be initialized before wiring up the + // hierarchy. + for (size_t i = 0; i < desc->num_joints; ++i) { + Joint* joint = get_anima_joint_mut(anima, i); + make_joint(anima, &desc->joints[i], joint); + } + // Wire up joints to their parents. -1 to skip the root. + for (size_t i = 0; i < desc->num_joints - 1; ++i) { + set_joint_parent(anima, i, desc->joints[i].parent); + } + + return anima; +} + +void gfx_destroy_anima(Anima** anima) { + assert(anima); + + if (*anima) { + for (skeleton_idx i = (*anima)->skeleton; i.val != 0;) { + Skeleton* skeleton = mem_get_skeleton(i); + i = skeleton->next; + mem_free_skeleton(&skeleton); + } + + for (animation_idx i = (*anima)->animation; i.val != 0;) { + Animation* animation = mem_get_animation(i); + i = animation->next; + mem_free_animation(&animation); + } + + if ((*anima)->parent.val) { + gfx_del_node((*anima)->parent); + } + + mem_free_anima(anima); + } +} + +static Animation* find_animation(animation_idx index, const char* name) { + assert(name); + + while (index.val != 0) { + Animation* animation = mem_get_animation(index); + if (sstring_eq_cstr(animation->name, name)) { + // LOGD( + // "Found animation at index %u, %s - %s", index.val, + // sstring_cstr(&animation->name), name); + // LOGD("Animation has duration %f", animation->duration); + return animation; + } + index = animation->next; + } + + return 0; +} + +bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) { + assert(anima); + assert(settings); + + // TODO: Should we animate at t=0 here to kickstart the animation? Otherwise + // the client is forced to call gfx_update_animation() to do this. + Animation* animation = find_animation(anima->animation, settings->name); + if (!animation) { + return false; + } + // Playback initialized on first call to update(). + AnimationState* state = &anima->state; + state->start_time = PLAYBACK_UNINITIALIZED; + state->animation = mem_get_animation_index(animation); + state->loop = settings->loop; + return true; +} + +static void gfx_set_joint_position(Joint* joint, vec3 position) { + assert(joint); + mat4_set_v3(&joint->transform, position); +} + +static void gfx_set_joint_rotation(Joint* joint, quat rotation) { + assert(joint); + mat4_set_3x3(&joint->transform, mat4_from_quat(rotation)); +} + +static void find_keyframes(const Channel* channel, R t, int* prev, int* next) { + assert(channel); + assert(prev); + assert(next); + + *prev = -1; + *next = 0; + while (((*next + 1) < (int)channel->num_keyframes) && + (t >= channel->keyframes[*next + 1].time)) { + (*prev)++; + (*next)++; + } +} + +static R normalize_time(R a, R b, R t) { + assert(a <= t); + assert(t <= b); + return (t - a) / (b - a); +} + +static quat interpolate_rotation( + const Channel* channel, int prev, int next, R t) { + assert(channel); + + if (next == 0) { + // Animation has not started at this point in time yet. + return channel->keyframes[next].rotation; + } else { + switch (channel->interpolation) { + case StepInterpolation: + return channel->keyframes[prev].rotation; + case LinearInterpolation: { + const R normalized_t = normalize_time( + channel->keyframes[prev].time, channel->keyframes[next].time, t); + return qnormalize(qslerp( + channel->keyframes[prev].rotation, channel->keyframes[next].rotation, + normalized_t)); + break; + } + case CubicSplineInterpolation: + assert(false); // TODO + return qmake(0, 0, 0, 0); + default: + assert(false); + return qmake(0, 0, 0, 0); + } + } +} + +static vec3 interpolate_translation( + const Channel* channel, int prev, int next, R t) { + assert(channel); + + if (next == 0) { + // Animation has not started at this point in time yet. + return channel->keyframes[next].translation; + } else { + switch (channel->interpolation) { + case StepInterpolation: + return channel->keyframes[prev].translation; + case LinearInterpolation: { + const R normalized_t = normalize_time( + channel->keyframes[prev].time, channel->keyframes[next].time, t); + return vec3_lerp( + channel->keyframes[prev].translation, + channel->keyframes[next].translation, normalized_t); + break; + } + case CubicSplineInterpolation: + assert(false); // TODO + return vec3_make(0, 0, 0); + default: + assert(false); + return vec3_make(0, 0, 0); + } + } +} + +static void animate_channel(Anima* anima, const Channel* channel, R t) { + assert(anima); + assert(channel); + assert(channel->target < anima->num_joints); + + int prev, next; + find_keyframes(channel, t, &prev, &next); + + // Note that not all channels extend to the duration of an animation; some + // channels may stop animating their targets earlier. Clamp the animation time + // to the channel's end keyframe to make the rest of the math (normalize_time) + // work. + t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t; + + Joint* target = get_anima_joint_mut(anima, channel->target); + + switch (channel->type) { + case RotationChannel: { + const quat rotation = interpolate_rotation(channel, prev, next, t); + gfx_set_joint_rotation(target, rotation); + break; + } + case TranslationChannel: { + const vec3 translation = interpolate_translation(channel, prev, next, t); + gfx_set_joint_position(target, translation); + break; + } + // Not yet supported. + case ScaleChannel: + case WeightsChannel: + default: + // TODO: Add back the assertion or add support for scaling. + // assert(false); + break; + } +} + +static void compute_joint_matrices_rec( + Anima* anima, Joint* joint, const mat4* parent_global_joint_transform, + const mat4* root_inv_global_transform) { + assert(anima); + assert(joint); + assert(parent_global_joint_transform); + assert(root_inv_global_transform); + + const mat4 global_joint_transform = + mat4_mul(*parent_global_joint_transform, joint->transform); + + // Compute this joint's matrix. + joint->joint_matrix = mat4_mul( + *root_inv_global_transform, + mat4_mul(global_joint_transform, joint->inv_bind_matrix)); + + // Recursively compute the joint matrices for this joint's siblings. + if (joint->next != INDEX_NONE) { + Joint* sibling = get_anima_joint_mut(anima, joint->next); + + compute_joint_matrices_rec( + anima, sibling, parent_global_joint_transform, + root_inv_global_transform); + } + + // Recursively compute the joint matrices for this joint's children. + if (joint->child != INDEX_NONE) { + Joint* child = get_anima_joint_mut(anima, joint->child); + + compute_joint_matrices_rec( + anima, child, &global_joint_transform, root_inv_global_transform); + } +} + +void gfx_update_animation(Anima* anima, R t) { + assert(anima); + + AnimationState* state = &anima->state; + if (state->animation.val == 0) { + return; // No active animation. + } + const Animation* animation = mem_get_animation(state->animation); + assert(animation); + + // On a call to play(), the start time is set to -1 to signal that the + // animation playback has not yet been initialized. + if (state->start_time == PLAYBACK_UNINITIALIZED) { + state->start_time = t; + } + // Locate the current time point inside the animation's timeline. + assert(t >= state->start_time); + assert(animation->duration >= 0.0); + const R local_time = t - state->start_time; + const R animation_time = state->loop + ? rmod(local_time, animation->duration) + : clamp(local_time, 0.0, animation->duration); + + // LOGD( + // "animation_time = %f, animation duration: %f", animation_time, + // animation->duration); + + // Play through the animation to transform skeleton nodes. + for (size_t i = 0; i < animation->num_channels; ++i) { + const Channel* channel = &animation->channels[i]; + animate_channel(anima, channel, animation_time); + } + + // Compute joint matrices after having transformed the skeletons. + // + // The anima's parent node is the common ancestor of all skeletons, and its + // transform maps the skeletons from object space to world space. This is the + // transform used as the "global transform" in the joint matrix equations. + // + // Joint matrix calculation begins by descending from the anima's root joint, + // which we have constructed to be the common root of all skeletons. + // + // This procedure touches every joint exactly once. + SceneNode* root_node = mem_get_node(anima->parent); + // LOGD("Root: %u, child: %u", anima->parent.val, root->child.val); + const mat4 root_global_transform = gfx_get_node_global_transform(root_node); + const mat4 root_inv_global_transform = mat4_inverse(root_global_transform); + + Joint* root_joint = get_anima_root_joint(anima); + compute_joint_matrices_rec( + anima, root_joint, &root_global_transform, &root_inv_global_transform); +} + +const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) { + assert(anima); + + skeleton_idx skeleton_index = anima->skeleton; + const Skeleton* skeleton = mem_get_skeleton(skeleton_index); + + for (size_t j = 1; j < i; ++j) { + skeleton_index = skeleton->next; + mem_get_skeleton(skeleton_index); + } + + return skeleton; +} + +size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) { + assert(skeleton); + return skeleton->num_joints; +} + +bool gfx_joint_has_box( + const Anima* anima, const Skeleton* skeleton, size_t joint_index) { + assert(anima); + assert(skeleton); + assert(joint_index < skeleton->num_joints); + + const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); + return !aabb3_is_empty(joint->box); +} + +Box gfx_get_joint_box( + const Anima* anima, const Skeleton* skeleton, size_t joint_index) { + assert(anima); + assert(skeleton); + + const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); + + // Transform the box to anima space. + // Note that joint matrices do not usually have a translation since joints + // mostly just rotate with respect to their parent. + const vec3 pmin = joint->box.min; + const vec3 pmax = joint->box.max; + return (Box){ + .vertices = { + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1), + } + }; +} 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 @@ +#pragma once + +#include +#include + +#include "types.h" + +#include +#include +#include +#include +#include + +#include +#include + +typedef struct Buffer Buffer; + +// Currently ignoring scale in skinning and animation. +// +// TODO: Simultaneous animation of disjoint animations. + +/// Skeleton joint. +/// Joints are mutable and store the transform and joint matrices that result +/// from animation, aside from the inverse bind matrix. +typedef struct Joint { + joint_idx child; /// First child Joint; index into Anima's joints. + joint_idx next; /// Next sibling Joint; index into Anima's joints. + mat4 transform; /// Local transform relative to parent. + mat4 inv_bind_matrix; /// Transforms the mesh into the joint's local space. + mat4 joint_matrix; /// inv(global) * global joint transform * inv(bind). + aabb3 box; /// Bounding box of vertices affected by joint. +} Joint; + +/// Animation skeleton. +typedef struct Skeleton { + skeleton_idx next; + size_t num_joints; + joint_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. +} Skeleton; + +/// A keyframe of animation. +typedef struct Keyframe { + R time; /// Start time in [0, end animation time] + union { + vec3 translation; + quat rotation; + }; +} Keyframe; + +/// Animation channel. +typedef struct Channel { + joint_idx target; /// Index into Anima's joints array. + ChannelType type; + AnimationInterpolation interpolation; + size_t num_keyframes; + Keyframe keyframes[GFX_MAX_NUM_KEYFRAMES]; +} Channel; + +/// A skeletal animation. +typedef struct Animation { + animation_idx next; + sstring name; + R duration; + size_t num_channels; + Channel channels[GFX_MAX_NUM_CHANNELS]; +} Animation; + +/// Animation state. +/// +/// This represents the current state of an animation. +typedef struct AnimationState { + R start_time; // Time when the current animation started playing. -1 means the + // animation playback has not yet been initialized. + animation_idx animation; // Current animation. 0 = no animation. + bool loop; +} AnimationState; + +/// Animation object. +/// +/// This is the top-level animation object that encapsulates everything +/// necessary for animation. +/// +/// For lack of a better name, this is called an Anima. It is short and the +/// Latin root of animation. +/// +/// The last joint of the joints array at index 'num_joints - 1' is the root of +/// all skeletons; specifically, the root of all joints that otherwise would +/// have no parent (a skeleton need not have its own root and can be a set of +/// disjoint node hierarchies). +typedef struct Anima { + node_idx parent; /// Parent SceneNode. + skeleton_idx skeleton; /// Index of first skeleton. + animation_idx animation; /// Index of first animation. + AnimationState state; /// Current animation state. + size_t num_joints; /// Number of actual joints in the array. + Joint joints[GFX_MAX_NUM_JOINTS]; /// Shared by all skeletons. +} 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 @@ +#include "camera_impl.h" + +#include "node_impl.h" +#include "scene_memory.h" + +#include + +SceneCamera* gfx_make_camera() { + SceneCamera* camera = mem_alloc_camera(); + + camera->camera = camera_perspective( + /*fovy=*/90.0 * TO_RAD, /*aspect=*/16.0 / 9.0, + /*near=*/0.1, /*far=*/1000); + + return camera; +} + +void gfx_destroy_camera(SceneCamera** camera) { + assert(camera); + if (*camera) { + if ((*camera)->parent.val) { + gfx_del_node((*camera)->parent); + } + mem_free_camera(camera); + } +} + +void gfx_set_camera_camera(SceneCamera* scene_camera, Camera* camera) { + assert(scene_camera); + assert(camera); + scene_camera->camera = *camera; +} + +Camera* gfx_get_camera_camera(SceneCamera* camera) { + assert(camera); + return &camera->camera; +} 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 @@ +#pragma once + +#include + +#include "types.h" + +#include + +typedef struct SceneCamera { + Camera camera; + node_idx parent; // Parent SceneNode. +} 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 @@ +#include "light_impl.h" + +#include "node_impl.h" +#include "scene_memory.h" + +#include + +static void make_environment_light( + Light* light, const EnvironmentLightDesc* desc) { + assert(light); + assert(desc); + light->type = EnvironmentLightType; + light->environment.environment_map = desc->environment_map; +} + +Light* gfx_make_light(const LightDesc* desc) { + assert(desc); + + Light* light = mem_alloc_light(); + + switch (desc->type) { + case EnvironmentLightType: + make_environment_light(light, &desc->light.environment); + break; + default: + log_error("Unhandled light type"); + gfx_destroy_light(&light); + return 0; + } + + return light; +} + +void gfx_destroy_light(Light** light) { + assert(light); + if (*light) { + if ((*light)->parent.val) { + gfx_del_node((*light)->parent); + } + mem_free_light(light); + } +} 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 @@ +#pragma once + +#include + +#include "types.h" + +typedef struct Texture Texture; + +/// An environment light. +typedef struct EnvironmentLight { + const Texture* environment_map; + const Texture* irradiance_map; // Renderer implementation. + const Texture* prefiltered_environment_map; // Renderer implementation. + int max_reflection_lod; // Mandatory when prefiltered_environment_map is + // given. +} EnvironmentLight; + +/// A scene light. +typedef struct Light { + LightType type; + union { + EnvironmentLight environment; + }; + node_idx parent; // Parent SceneNode. +} 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 @@ +#include "material_impl.h" + +#include "scene_memory.h" + +#include + +static void material_make(Material* material, const MaterialDesc* desc) { + assert(material); + assert(desc); + assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + material->num_uniforms = desc->num_uniforms; + for (int i = 0; i < desc->num_uniforms; ++i) { + material->uniforms[i] = desc->uniforms[i]; + } +} + +Material* gfx_make_material(const MaterialDesc* desc) { + assert(desc); + Material* material = mem_alloc_material(); + material_make(material, desc); + return material; +} + +void gfx_destroy_material(Material** material) { mem_free_material(material); } + +static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) { + switch (uniform->type) { + case UniformTexture: + gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture); + break; + case UniformMat4: + gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.mat4); + break; + case UniformVec3: + gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.vec3); + break; + case UniformVec4: + gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.vec4); + break; + case UniformFloat: + gfx_set_float_uniform(prog, uniform->name.str, uniform->value.scalar); + break; + case UniformMat4Array: + gfx_set_mat4_array_uniform( + prog, uniform->name.str, uniform->value.array.values, + uniform->value.array.count); + break; + } +} + +void material_activate(ShaderProgram* shader, const Material* material) { + assert(material); + for (int i = 0; i < material->num_uniforms; ++i) { + const ShaderUniform* uniform = &material->uniforms[i]; + set_uniform(shader, uniform); + } +} 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 @@ +#pragma once + +#include + +typedef struct ShaderProgram ShaderProgram; + +typedef struct Material { + ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL]; + int num_uniforms; +} Material; + +/// Activate the material. +/// +/// This activates the material's shader and configures the shader uniforms that +/// are specific to the material. +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 @@ +#include "mesh_impl.h" + +#include "scene_memory.h" + +#include + +static void mesh_make(Mesh* mesh, const MeshDesc* desc) { + assert(mesh); + assert(desc); + assert(desc->geometry); + assert(desc->material); + assert(desc->shader); + mesh->geometry = desc->geometry; + mesh->material = desc->material; + mesh->shader = desc->shader; +} + +Mesh* gfx_make_mesh(const MeshDesc* desc) { + Mesh* mesh = mem_alloc_mesh(); + mesh_make(mesh, desc); + return mesh; +} + +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 @@ +#pragma once + +#include + +typedef struct Mesh { + const Geometry* geometry; + const Material* material; + ShaderProgram* shader; +} Mesh; + +// TODO: a mesh_render() that takes a transform, applies the material and the +// 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 @@ +#include "model_impl.h" + +#include + +#include "scene_memory.h" + +#include + +Model* gfx_make_model(const SceneNode* root) { + assert(root); + + Model* model = mem_alloc_model(); + model->root = mem_get_node_index(root); + return model; +} + +void gfx_del_model(Model** model) { + assert(model); + + if (*model) { + SceneNode* root = mem_get_node((*model)->root); + gfx_destroy_node(&root); + *model = 0; + } +} + +Anima* gfx_get_model_anima(Model* model) { + assert(model); + + SceneNode* root = mem_get_node(model->root); + if (gfx_get_node_type(root) == AnimaNode) { + return gfx_get_node_anima_mut(root); + } else { + return 0; + } +} + +const SceneNode* gfx_get_model_root(const Model* model) { + assert(model); + return mem_get_node(model->root); +} + +SceneNode* gfx_get_model_root_mut(Model* model) { + return (SceneNode*)gfx_get_model_root(model); +} 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 @@ +#pragma once + +#include + +#include "scene_memory.h" + +/// Model. +typedef struct Model { + node_idx root; + node_idx parent; // Parent SceneNode. +} Model; + +/// Create a new model. +Model* gfx_make_model(const SceneNode* root); + +/// Destroy the model. +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 @@ +#include "node_impl.h" + +#include "animation_impl.h" +#include "camera_impl.h" +#include "light_impl.h" +#include "model_impl.h" +#include "object_impl.h" +#include "scene_graph.h" +#include "scene_memory.h" + +#include "gfx_assert.h" + +#include +#include + +static void scene_node_make(SceneNode* node) { + assert(node); + node->type = LogicalNode; + node->transform = mat4_id(); +} + +SceneNode* gfx_make_node() { + SceneNode* node = mem_alloc_node(); + scene_node_make(node); + return node; +} + +SceneNode* gfx_make_anima_node(Anima* anima) { + assert(anima); + SceneNode* node = gfx_make_node(); + node->type = AnimaNode; + node->anima = mem_get_anima_index(anima); + anima->parent = mem_get_node_index(node); + return node; +} + +SceneNode* gfx_make_camera_node(SceneCamera* camera) { + assert(camera); + SceneNode* node = gfx_make_node(); + node->type = CameraNode; + node->camera = mem_get_camera_index(camera); + camera->parent = mem_get_node_index(node); + return node; +} + +SceneNode* gfx_make_light_node(Light* light) { + assert(light); + SceneNode* node = gfx_make_node(); + node->type = LightNode; + node->light = mem_get_light_index(light); + light->parent = mem_get_node_index(node); + return node; +} + +SceneNode* gfx_make_model_node(Model* model) { + assert(model); + SceneNode* node = gfx_make_node(); + node->type = ModelNode; + node->model = mem_get_model_index(model); + model->parent = mem_get_node_index(node); + return node; +} + +SceneNode* gfx_make_object_node(SceneObject* object) { + assert(object); + SceneNode* node = gfx_make_node(); + node->type = ObjectNode; + node->object = mem_get_object_index(object); + object->parent = mem_get_node_index(node); + return node; +} + +/// Frees the node's resource. +static void free_node_resource(SceneNode* node) { + assert(node); + + // Set the resource's parent node back to 0 to avoid a recursive call into + // gfx_del_node(). + switch (node->type) { + case AnimaNode: { + Anima* anima = mem_get_anima(node->anima); + anima->parent.val = 0; + gfx_destroy_anima(&anima); + return; + } + case CameraNode: { + SceneCamera* camera = mem_get_camera(node->camera); + camera->parent.val = 0; + gfx_destroy_camera(&camera); + return; + } + case LightNode: { + Light* light = mem_get_light(node->light); + light->parent.val = 0; + gfx_destroy_light(&light); + return; + } + case ModelNode: { + return; // Model data is owned by the asset cache. + } + case ObjectNode: { + SceneObject* object = mem_get_object(node->object); + object->parent.val = 0; + gfx_destroy_object(&object); + return; + } + case LogicalNode: + return; // Logical nodes have no resource. + } + FAIL("unhandled node type"); +} + +void gfx_construct_anima_node(SceneNode* node, Anima* anima) { + assert(node); + assert(anima); + free_node_resource(node); + node->type = AnimaNode; + node->anima = mem_get_anima_index(anima); + anima->parent = mem_get_node_index(node); +} + +void gfx_construct_camera_node(SceneNode* node, SceneCamera* camera) { + assert(node); + assert(camera); + free_node_resource(node); + node->type = CameraNode; + node->camera = mem_get_camera_index(camera); + camera->parent = mem_get_node_index(node); +} + +// TODO: Add a common helper function between each gfx_make_xyz_node() and +// gfx_construct_xyz_node() pair. +void gfx_construct_light_node(SceneNode* node, Light* light) { + assert(node); + assert(light); + free_node_resource(node); + node->type = LightNode; + node->light = mem_get_light_index(light); + light->parent = mem_get_node_index(node); +} + +void gfx_construct_model_node(SceneNode* node, Model* model) { + assert(node); + assert(model); + free_node_resource(node); + node->type = ModelNode; + node->model = mem_get_model_index(model); + model->parent = mem_get_node_index(node); +} + +void gfx_construct_object_node(SceneNode* node, SceneObject* object) { + assert(node); + assert(object); + free_node_resource(node); + node->type = ObjectNode; + node->object = mem_get_object_index(object); + object->parent = mem_get_node_index(node); +} + +static void destroy_node_rec(SceneNode* node) { + assert(node); + + // First child. + if (node->child.val) { + destroy_node_rec(mem_get_node(node->child)); + } + + // Right sibling. + if (node->next.val) { + destroy_node_rec(mem_get_node(node->next)); + } + + free_node_resource(node); + mem_free_node(&node); +} + +void gfx_destroy_node(SceneNode** node) { + assert(node); + if (*node) { + // Since the node and the whole hierarchy under it gets destroyed, there is + // no need to individually detach every node from its hierarchy. We can + // simply detach the given node and then destroy it and its sub-hierarchy. + TREE_REMOVE(*node); + destroy_node_rec(*node); + *node = 0; + } +} + +// TODO: Think more about ownership of nodes and resources. Should this function +// even exist? +void gfx_del_node(node_idx index) { + assert(index.val); + SceneNode* node = mem_get_node(index); + assert(node); + // TODO: Should destroy children recursively? + TREE_REMOVE(node); + mem_free_node(&node); +} + +NodeType gfx_get_node_type(const SceneNode* node) { + assert(node); + return node->type; +} + +#define NODE_GET(node, field, expected_type) \ + { \ + assert(node); \ + assert(node->type == expected_type); \ + return mem_get_##field(node->field); \ + } + +const Anima* gfx_get_node_anima(const SceneNode* node) { + NODE_GET(node, anima, AnimaNode); +} + +Anima* gfx_get_node_anima_mut(SceneNode* node) { + NODE_GET(node, anima, AnimaNode); +} + +const SceneCamera* gfx_get_node_camera(const SceneNode* node) { + NODE_GET(node, camera, CameraNode); +} + +SceneCamera* gfx_get_node_camera_mut(SceneNode* node) { + NODE_GET(node, camera, CameraNode); +} + +const Light* gfx_get_node_light(const SceneNode* node) { + NODE_GET(node, light, LightNode); +} + +Light* gfx_get_node_light_mut(SceneNode* node) { + NODE_GET(node, light, LightNode); +} + +const Model* gfx_get_node_model(const SceneNode* node) { + NODE_GET(node, model, ModelNode); +} + +Model* gfx_get_node_model_mut(SceneNode* node) { + NODE_GET(node, model, ModelNode); +} + +const SceneObject* gfx_get_node_object(const SceneNode* node) { + NODE_GET(node, object, ObjectNode); +} + +SceneObject* gfx_get_node_object_mut(SceneNode* node) { + NODE_GET(node, object, ObjectNode); +} + +const SceneNode* gfx_get_node_parent(const SceneNode* node) { + assert(node); + return mem_get_node(node->parent); +} + +SceneNode* gfx_get_node_parent_mut(SceneNode* node) { + assert(node); + return mem_get_node(node->parent); +} + +const SceneNode* gfx_get_node_child(const SceneNode* node) { + assert(node); + if (node->child.val) { + return mem_get_node(node->child); + } else { + return 0; + } +} + +SceneNode* gfx_get_node_child_mut(SceneNode* node) { + return (SceneNode*)gfx_get_node_child(node); +} + +const SceneNode* gfx_get_node_sibling(const SceneNode* node) { + assert(node); + if (node->next.val) { + return mem_get_node(node->next); + } else { + return 0; + } +} + +SceneNode* gfx_get_node_sibling_mut(SceneNode* node) { + return (SceneNode*)gfx_get_node_sibling(node); +} + +mat4 gfx_get_node_transform(const SceneNode* node) { + assert(node); + return node->transform; +} + +mat4 gfx_get_node_global_transform(const SceneNode* node) { + assert(node); + mat4 transform = node->transform; + node_idx parent_index = node->parent; + while (parent_index.val != 0) { + const SceneNode* parent = mem_get_node(parent_index); + transform = mat4_mul(parent->transform, transform); + parent_index = parent->parent; + } + return transform; +} + +void gfx_set_node_parent(SceneNode* child, SceneNode* parent_node) { + assert(child); + // Parent can be null. + SET_PARENT(child, parent_node); +} + +void gfx_set_node_transform(SceneNode* node, const mat4* transform) { + assert(node); + assert(transform); + node->transform = *transform; +} + +void gfx_set_node_position(SceneNode* node, const vec3* position) { + assert(node); + assert(position); + mat4_set_v3(&node->transform, *position); +} + +void gfx_set_node_rotation(SceneNode* node, const quat* rotation) { + assert(node); + assert(rotation); + mat4_set_3x3(&node->transform, mat4_from_quat(*rotation)); +} + +void gfx_set_node_rotation_mat(SceneNode* node, const mat4* rotation) { + assert(node); + assert(rotation); + mat4_set_3x3(&node->transform, *rotation); +} + +static const char* get_node_type_str(NodeType type) { + switch (type) { + case LogicalNode: + return "LogicalNode"; + case AnimaNode: + return "AnimaNode"; + case CameraNode: + return "CameraNode"; + case LightNode: + return "LightNode"; + case ModelNode: + return "ModelNode"; + case ObjectNode: + return "ObjectNode"; + } + FAIL("Unhandled node type"); + return ""; +} + +static void log_node_hierarchy_rec(const SceneNode* node, const sstring* pad) { + assert(node); + assert(pad); + + LOGI( + "%s%s (%u)", sstring_cstr(pad), get_node_type_str(node->type), + mem_get_node_index(node).val); + + // Log the children. + if (node->child.val) { + const sstring new_pad = sstring_concat_cstr(*pad, " "); + log_node_hierarchy_rec(mem_get_node(node->child), &new_pad); + } + + // Then log the siblings. + if (node->next.val) { + log_node_hierarchy_rec(mem_get_node(node->next), pad); + } +} + +void gfx_log_node_hierarchy(const SceneNode* node) { + const sstring pad = sstring_make(""); + log_node_hierarchy_rec(node, &pad); +} + +static SceneNode* clone_scene_rec(const SceneNode* node) { + assert(node); + + SceneNode* copy = mem_alloc_node(); + *copy = *node; // Shallow clone of the node's resource. + + if (node->child.val) { + SceneNode* child = mem_get_node(node->child); + SceneNode* child_copy = clone_scene_rec(child); + copy->child = mem_get_node_index(child_copy); + child_copy->parent = mem_get_node_index(copy); + } + + if (node->next.val) { + SceneNode* next = mem_get_node(node->next); + SceneNode* next_copy = clone_scene_rec(next); + copy->next = mem_get_node_index(next_copy); + next_copy->prev = mem_get_node_index(copy); + } + + return copy; +} + +SceneNode* gfx_clone_scene_shallow(const SceneNode* node) { + assert(node); + // Must be a root node; not allowed to have siblings. + assert(!node->prev.val); + assert(!node->next.val); + + return clone_scene_rec(node); +} 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 @@ +#pragma once + +#include + +#include "types.h" + +#include +#include + +/// Scene node. +/// +/// The SceneNode owns its cameras, objects, lights and child nodes. These +/// together form a strict tree hierarchy and not a more general DAG. +typedef struct SceneNode { + NodeType type; + union { + anima_idx anima; + camera_idx camera; + light_idx light; + model_idx model; + object_idx object; + }; + mat4 transform; // Transformation for this node and its children. + node_idx parent; // Parent SceneNode. + node_idx child; // First child SceneNode. + node_idx next; // Next sibling SceneNode. + node_idx prev; // Previous sibling SceneNode. +} SceneNode; + +/// Recursively destroy a node given its index but without destroying the node +/// resources. +/// +/// The node and its children are removed from the scene graph. +/// +/// This function is for the library's internal use only. +void gfx_del_node(node_idx); + +/// Return a shallow clone of the scene rooted at the given node. +/// The given node must have no siblings (must be a root node). +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 @@ +#include "object_impl.h" + +#include + +#include "mesh_impl.h" +#include "node_impl.h" +#include "scene_memory.h" + +#include + +static aabb3 calc_object_aabb(const SceneObject* object) { + assert(object); + + bool first = true; + aabb3 box; + + mesh_link_idx ml = object->mesh_link; + while (ml.val) { + const MeshLink* mesh_link = mem_get_mesh_link(ml); + const mesh_idx mi = mesh_link->mesh; + if (mi.val) { + const Mesh* mesh = mem_get_mesh(mi); + const aabb3 mesh_box = gfx_get_geometry_aabb(mesh->geometry); + if (first) { + box = mesh_box; + first = false; + } else { + box = aabb3_sum(box, mesh_box); + } + } + ml = mesh_link->next; + } + + return box; +} + +static void add_object_mesh(SceneObject* object, Mesh* mesh) { + assert(object); + assert(mesh); + + MeshLink* link = mem_alloc_mesh_link(); + link->mesh = mem_get_mesh_index(mesh); + link->next = object->mesh_link; + object->mesh_link = mem_get_mesh_link_index(link); +} + +SceneObject* gfx_make_object(const ObjectDesc* desc) { + assert(desc); + + SceneObject* object = mem_alloc_object(); + for (size_t i = 0; i < desc->num_meshes; ++i) { + add_object_mesh(object, desc->meshes[i]); + } + object->box = calc_object_aabb(object); + return object; +} + +void gfx_destroy_object(SceneObject** object) { + assert(object); + + if (*object) { + if ((*object)->parent.val) { + gfx_del_node((*object)->parent); + } + mem_free_object(object); + } +} + +void gfx_set_object_skeleton(SceneObject* object, const Skeleton* skeleton) { + assert(object); + assert(skeleton); + object->skeleton = mem_get_skeleton_index(skeleton); +} + +const Skeleton* gfx_get_object_skeleton(const SceneObject* object) { + assert(object); + return (object->skeleton.val == 0) ? 0 : mem_get_skeleton(object->skeleton); +} + +aabb3 gfx_get_object_aabb(const SceneObject* object) { + assert(object); + return object->box; +} 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 @@ +#pragma once + +#include + +#include "types.h" + +#include + +typedef struct MeshLink { + mesh_idx mesh; + mesh_link_idx next; // Next MeshLink in the list. +} MeshLink; + +/// Scene object. +/// +/// A SceneObject does not own its Meshes, and they are instead shared for +/// re-use. The SceneObject consequently embeds a list of MeshLinks as opposed +/// to a list of Meshes. The MeshLinks define a list of Meshes, which can be +/// different for each SceneObject. Each SceneObject may then have a unique list +/// of Meshes, and the Meshes are re-used. +typedef struct SceneObject { + mesh_link_idx mesh_link; /// First MeshLink in the list. + skeleton_idx skeleton; /// 0 for static objects. + node_idx parent; /// Parent SceneNode. + aabb3 box; +} 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 @@ +#include "scene_impl.h" + +#include "node_impl.h" +#include "scene_memory.h" + +#include + +Scene* gfx_make_scene(void) { + Scene* scene = mem_alloc_scene(); + scene->root = gfx_make_node(); + return scene; +} + +void gfx_destroy_scene(Scene** scene) { + assert(scene); + if (*scene) { + gfx_destroy_node(&(*scene)->root); + mem_free_scene(scene); + } +} + +SceneNode* gfx_get_scene_root(Scene* scene) { + assert(scene); + return scene->root; +} 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 @@ +/// Functions for list manipulation. +#pragma once + +#include "scene_memory.h" + +// NOTE: SceneMemory guarantees that index 0 can be regarded as an invalid +// index. + +#define MEM_GET(INDEX) \ + _Generic((INDEX), camera_idx \ + : mem_get_camera, material_idx \ + : mem_get_material, mesh_idx \ + : mem_get_mesh, mesh_link_idx \ + : mem_get_mesh_link, node_idx \ + : mem_get_node, object_idx \ + : mem_get_object, scene_idx \ + : mem_get_scene)(INDEX) + +#define MEM_GET_INDEX(ITEM) \ + _Generic((ITEM), SceneCamera * \ + : mem_get_camera_index, Material * \ + : mem_get_material_index, Mesh * \ + : mem_get_mesh_index, MeshLink * \ + : mem_get_mesh_link_index, SceneNode * \ + : mem_get_node_index, SceneObject * \ + : mem_get_object_index, Scene * \ + : mem_get_scene_index)(ITEM) + +/// Assert the list node invariant. +/// +/// - A node does not point to itself. +#define ASSERT_LIST_NODE_INVARIANT(ITEM) \ + { \ + const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \ + assert((ITEM)->prev.val != item_idx); \ + assert((ITEM)->next.val != item_idx); \ + } + +/// Assert the tree node invariant. +/// +/// - A node does not point to itself. +/// - The node's left and right siblings cannot be equal, unless both are 0. +/// - The node's left/right sibling cannot be its child, unless both are 0. +/// - The node's parent cannot be the node's child or sibling, unless it's 0. +/// - If the node has a parent and the node is the leftmost sibling, then the +/// parent's child is the node. +#define ASSERT_TREE_NODE_INVARIANT(ITEM) \ + { \ + const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \ + assert((ITEM)->prev.val != item_idx); \ + assert((ITEM)->next.val != item_idx); \ + if ((ITEM)->prev.val) { \ + assert((ITEM)->prev.val != (ITEM)->next.val); \ + } \ + if ((ITEM)->child.val) { \ + assert((ITEM)->child.val != (ITEM)->prev.val); \ + assert((ITEM)->child.val != (ITEM)->next.val); \ + } \ + assert((ITEM)->parent.val != item_idx); \ + if ((ITEM)->parent.val && !(ITEM)->prev.val) { \ + assert((ITEM)->parent.val != (ITEM)->prev.val); \ + assert((ITEM)->parent.val != (ITEM)->next.val); \ + const __typeof__(ITEM) item_parent = MEM_GET((ITEM)->parent); \ + assert(item_parent->child.val == item_idx); \ + } \ + } + +/// Prepend an item to a list. +/// Modify HEAD_INDEX to equal the index of the new head. +#define LIST_PREPEND(HEAD_INDEX, ITEM) \ + (ITEM)->next = HEAD_INDEX; \ + if (HEAD_INDEX.val) { \ + __typeof__(ITEM) old_head = MEM_GET(HEAD_INDEX); \ + old_head->prev = MEM_GET_INDEX(ITEM); \ + } \ + HEAD_INDEX = MEM_GET_INDEX(ITEM); \ + ASSERT_LIST_NODE_INVARIANT(ITEM); + +/// Disconnect an item from its siblings. +#define LIST_REMOVE(ITEM) \ + if ((ITEM)->prev.val) { \ + __typeof__(ITEM) prev_sibling = MEM_GET((ITEM)->prev); \ + prev_sibling->next = (ITEM)->next; \ + } \ + if ((ITEM)->next.val) { \ + __typeof__(ITEM) next_sibling = MEM_GET((ITEM)->next); \ + next_sibling->prev = (ITEM)->prev; \ + } \ + (ITEM)->prev.val = 0; \ + (ITEM)->next.val = 0; \ + ASSERT_LIST_NODE_INVARIANT(ITEM); + +/// Set the child's parent. +/// +/// The hierarchy is a strict tree hierarchy and a parent node points to its +/// first/leftmost child only. To add a new child, the new child becomes the +/// leftmost node in the list of siblings, the one that the parent then points +/// to. +/// +/// The child is also completely disconnected from its previous hierarchy. This +/// is because siblings in a hierarchy must all point to the same parent. +#define SET_PARENT(CHILD, PARENT) \ + assert(CHILD); \ + assert(CHILD != PARENT); \ + ASSERT_TREE_NODE_INVARIANT(CHILD); \ + ASSERT_TREE_NODE_INVARIANT(PARENT); \ + TREE_REMOVE(CHILD); /* Disconnect CHILD from its previous hierarchy. */ \ + if (PARENT) { \ + LIST_PREPEND((PARENT)->child, CHILD); \ + (CHILD)->parent = MEM_GET_INDEX(PARENT); \ + } else { \ + (CHILD)->parent.val = 0; \ + } \ + ASSERT_TREE_NODE_INVARIANT(CHILD); \ + if (PARENT) { \ + ASSERT_TREE_NODE_INVARIANT(PARENT); \ + } + +/// Remove an item from its hierarchy. +/// +/// The item is disconnected from its parents and siblings. The hierarchy rooted +/// under the item remains intact. +#define TREE_REMOVE(ITEM) \ + assert(ITEM); \ + if ((ITEM)->parent.val) { \ + /* The parent points only to its first/leftmost child. If this item is */ \ + /* the leftmost sibling, then we need to rewire the parent to point to */ \ + /* the next sibling to keep the parent connected to its children. */ \ + __typeof__(ITEM) parent = MEM_GET((ITEM)->parent); \ + const __typeof__(ITEM) parent_child = MEM_GET(parent->child); \ + if (parent_child == ITEM) { \ + assert((ITEM)->prev.val == 0); \ + parent->child = (ITEM)->next; \ + } \ + } \ + (ITEM)->parent.val = 0; \ + LIST_REMOVE(ITEM); /* Disconnect ITEM from its siblings. */ \ + 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 @@ +#pragma once + +#include + +#include "types.h" + +typedef struct SceneNode SceneNode; + +typedef struct Scene { + SceneNode* root; + scene_idx next; + scene_idx prev; +} 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 @@ +#include "scene_memory.h" + +#include + +#include "animation_impl.h" +#include "camera_impl.h" +#include "light_impl.h" +#include "material_impl.h" +#include "mesh_impl.h" +#include "model_impl.h" +#include "node_impl.h" +#include "object_impl.h" +#include "scene_impl.h" + +#include + +DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS) +DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS) +DEF_MEMPOOL(camera_pool, SceneCamera, GFX_MAX_NUM_CAMERAS) +DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS) +DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS) +DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES) +DEF_MEMPOOL(mesh_link_pool, MeshLink, GFX_MAX_NUM_MESH_LINKS) +DEF_MEMPOOL(model_pool, Model, GFX_MAX_NUM_MODELS) +DEF_MEMPOOL(node_pool, SceneNode, GFX_MAX_NUM_NODES) +DEF_MEMPOOL(object_pool, SceneObject, GFX_MAX_NUM_OBJECTS) +DEF_MEMPOOL(scene_pool, Scene, GFX_MAX_NUM_SCENES) +DEF_MEMPOOL(skeleton_pool, Skeleton, GFX_MAX_NUM_SKELETONS) + +/// Scene memory. +/// +/// Holds memory pools for every type of scene object. +typedef struct SceneMemory { + anima_pool animas; + animation_pool animations; + camera_pool cameras; + light_pool lights; + material_pool materials; + mesh_pool meshs; // Purposeful typo to make the PLURAL() macro work. + mesh_link_pool mesh_links; + model_pool models; + node_pool nodes; + object_pool objects; + scene_pool scenes; + skeleton_pool skeletons; +} SceneMemory; + +static SceneMemory mem; + +#define ALLOC_DUMMY(POOL) \ + { \ + const void* object = mempool_alloc(POOL); \ + assert(mempool_get_block_index(POOL, object) == 0); \ + } + +#define PLURAL(name) name##s +#define MEM_FIELD(name) mem.PLURAL(name) + +void scene_mem_init() { + mempool_make(&mem.animas); + mempool_make(&mem.animations); + mempool_make(&mem.cameras); + mempool_make(&mem.lights); + mempool_make(&mem.materials); + mempool_make(&mem.meshs); + mempool_make(&mem.mesh_links); + mempool_make(&mem.models); + mempool_make(&mem.nodes); + mempool_make(&mem.objects); + mempool_make(&mem.scenes); + mempool_make(&mem.skeletons); + + // Allocate dummy objects at index 0 to guarantee that no objects allocated by + // the caller map to index 0. + ALLOC_DUMMY(&mem.animas); + ALLOC_DUMMY(&mem.animations); + ALLOC_DUMMY(&mem.cameras); + ALLOC_DUMMY(&mem.lights); + ALLOC_DUMMY(&mem.materials); + ALLOC_DUMMY(&mem.meshs); + ALLOC_DUMMY(&mem.mesh_links); + ALLOC_DUMMY(&mem.models); + ALLOC_DUMMY(&mem.nodes); + ALLOC_DUMMY(&mem.objects); + ALLOC_DUMMY(&mem.scenes); + ALLOC_DUMMY(&mem.skeletons); +} + +void scene_mem_destroy() { + // NOTE: the dummy objects are not constructed, so the destruction code below + // always skips index 0. (I don't really like the conditional inside the loop, + // but this gets the job done without having to specialize the loop macro.) +#define DESTROY(name) \ + mempool_foreach(&MEM_FIELD(name), obj, { \ + if (i > 0) { \ + gfx_destroy_##name(&obj); \ + } \ + }) + + // Models contain scene elements. Destruction is handled by the remainder of + // scene destructionb elow. + // + // First destroy the scenes. This will recursively destroy the scene's nodes + // and their objects and avoid a double-free when we then destroy any stray + // scene elements. + DESTROY(scene); + // Then delete stray nodes. This will delete their children nodes and + // resource. + DESTROY(node); + // Destroy remaining scene elements. + DESTROY(anima); + // Animations are owned by animas and do not have a destructor. + DESTROY(camera); + DESTROY(light); + DESTROY(material); + DESTROY(mesh); + // Mesh links don't have a destructor. + DESTROY(object); + // Skeletons are owned by animas and do not have a destructor. +} + +#define DEF_MEMORY(name, type) \ + /* xyz* mem_alloc_xyz(); */ \ + type* mem_alloc_##name() { return mempool_alloc(&MEM_FIELD(name)); } \ + /* void mem_free_xyz(xyz**); */ \ + void mem_free_##name(type** obj) { mempool_free(&MEM_FIELD(name), obj); } \ + /* xyz* mem_get_xyz(xyz_idx); */ \ + type* mem_get_##name(NAMED_INDEX(name) index) { \ + assert(index.val != 0); /* 0 is the dummy allocation. */ \ + return mempool_get_block(&MEM_FIELD(name), index.val); \ + } \ + /* xyz_idx mem_get_xyz_index(const xyz*); */ \ + NAMED_INDEX(name) mem_get_##name##_index(const type* obj) { \ + return (NAMED_INDEX(name)){ \ + .val = mempool_get_block_index(&MEM_FIELD(name), obj)}; \ + } + +DEF_MEMORY(anima, Anima) +DEF_MEMORY(animation, Animation) +DEF_MEMORY(camera, SceneCamera) +DEF_MEMORY(light, Light) +DEF_MEMORY(material, Material) +DEF_MEMORY(mesh, Mesh) +DEF_MEMORY(mesh_link, MeshLink) +DEF_MEMORY(model, Model) +DEF_MEMORY(node, SceneNode) +DEF_MEMORY(object, SceneObject) +DEF_MEMORY(scene, Scene) +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 @@ +/// Memory management of scene objects. +#pragma once + +#include "types.h" + +/// Initialize scene memory. +/// +/// The scene memory guarantees that every object maps to an index different +/// than 0. This way, 0 can be used as a special index to denote "no value". +void scene_mem_init(); + +/// Destroy the scene memory and all allocated objects. +void scene_mem_destroy(); + +#define NAMED_INDEX(name) name##_idx + +#define DECL_MEMORY(name, type) \ + typedef struct type type; \ + /* xyz* mem_alloc_xyz() */ \ + type* mem_alloc_##name(); \ + /* mem_free_xyz(xyz**) */ \ + void mem_free_##name(type**); \ + /* xyz* mem_get_xyz(xyz_idx); */ \ + type* mem_get_##name(NAMED_INDEX(name)); \ + /* xyz_idx mem_get_xyz_index(const xyz*); */ \ + NAMED_INDEX(name) mem_get_##name##_index(const type*); + +DECL_MEMORY(anima, Anima) +DECL_MEMORY(animation, Animation) +DECL_MEMORY(camera, SceneCamera) +DECL_MEMORY(light, Light) +DECL_MEMORY(material, Material) +DECL_MEMORY(mesh, Mesh) +DECL_MEMORY(mesh_link, MeshLink) +DECL_MEMORY(model, Model) +DECL_MEMORY(node, SceneNode) +DECL_MEMORY(object, SceneObject) +DECL_MEMORY(scene, Scene) +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 @@ +/// Strongly-typed indices for every kind of scene node resource. +#pragma once + +#include + +typedef uint16_t gfx_idx; + +#define DEF_STRONG_INDEX(TYPE_NAME, IDX_TYPE) \ + typedef struct TYPE_NAME##_idx { \ + IDX_TYPE val; \ + } TYPE_NAME##_idx; + +DEF_STRONG_INDEX(anima, gfx_idx) +DEF_STRONG_INDEX(animation, gfx_idx) +DEF_STRONG_INDEX(camera, gfx_idx) +DEF_STRONG_INDEX(light, gfx_idx) +DEF_STRONG_INDEX(material, gfx_idx) +DEF_STRONG_INDEX(mesh, gfx_idx) +DEF_STRONG_INDEX(mesh_link, gfx_idx) +DEF_STRONG_INDEX(model, gfx_idx) +DEF_STRONG_INDEX(node, gfx_idx) +DEF_STRONG_INDEX(object, gfx_idx) +DEF_STRONG_INDEX(scene, gfx_idx) +DEF_STRONG_INDEX(skeleton, gfx_idx) -- cgit v1.2.3