From 8068d0a816b3efd17ebb0dcf468c6d333e3577d3 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 4 Feb 2023 14:36:02 -0800 Subject: Add support for skeletal animation. --- gfx/CMakeLists.txt | 1 + gfx/doc/extern/Scene Graph - CSE 167.pdf | Bin 0 -> 890801 bytes gfx/doc/scene.png | Bin 45239 -> 59119 bytes gfx/doc/scene.txt | 37 +- gfx/include/gfx/render_backend.h | 57 ++- gfx/include/gfx/scene/README.md | 50 ++- gfx/include/gfx/scene/animation.h | 127 ++++++ gfx/include/gfx/scene/node.h | 59 ++- gfx/include/gfx/scene/object.h | 6 +- gfx/include/gfx/sizes.h | 18 + gfx/include/gfx/util/scene.h | 5 +- gfx/shaders/cook_torrance.vert | 38 +- gfx/src/render/constants.h | 8 +- gfx/src/render/geometry.c | 222 +++++----- gfx/src/render/geometry.h | 10 +- gfx/src/render/render_backend.c | 48 +-- gfx/src/render/render_backend_impl.h | 12 +- gfx/src/render/shader_program.c | 98 +++-- gfx/src/renderer/renderer.c | 34 ++ gfx/src/scene/animation.c | 403 ++++++++++++++++++ gfx/src/scene/animation_impl.h | 95 +++++ gfx/src/scene/material.c | 5 + gfx/src/scene/node.c | 193 ++++++++- gfx/src/scene/node_impl.h | 11 +- gfx/src/scene/object.c | 14 +- gfx/src/scene/object_impl.h | 7 +- gfx/src/scene/scene_memory.c | 79 +++- gfx/src/scene/scene_memory.h | 53 +++ gfx/src/scene/types.h | 29 +- gfx/src/util/scene.c | 692 ++++++++++++++++++++++++++----- gltfview/src/game.c | 41 +- gltfview/src/game.h | 11 +- 32 files changed, 2070 insertions(+), 393 deletions(-) create mode 100644 gfx/doc/extern/Scene Graph - CSE 167.pdf create mode 100644 gfx/include/gfx/scene/animation.h create mode 100644 gfx/src/scene/animation.c create mode 100644 gfx/src/scene/animation_impl.h diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt index e91450b..d60a59f 100644 --- a/gfx/CMakeLists.txt +++ b/gfx/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(gfx src/render/shader.c src/render/texture.c src/renderer/renderer.c + src/scene/animation.c src/scene/camera.c src/scene/light.c src/scene/material.c diff --git a/gfx/doc/extern/Scene Graph - CSE 167.pdf b/gfx/doc/extern/Scene Graph - CSE 167.pdf new file mode 100644 index 0000000..5fbbb10 Binary files /dev/null and b/gfx/doc/extern/Scene Graph - CSE 167.pdf differ diff --git a/gfx/doc/scene.png b/gfx/doc/scene.png index 017e91b..85d2447 100644 Binary files a/gfx/doc/scene.png and b/gfx/doc/scene.png differ diff --git a/gfx/doc/scene.txt b/gfx/doc/scene.txt index dc22927..a771488 100644 --- a/gfx/doc/scene.txt +++ b/gfx/doc/scene.txt @@ -4,7 +4,6 @@ class Scene { } -Scene *-- Camera Scene *-- Node class Camera { @@ -15,11 +14,41 @@ class Node { + transform } +Node *-- AnimationObject Node *-- Object -Node o-- Light : "affected by" -Node o-- Camera +Node *-- Light +Node *-- Camera Node *-- Node +class AnimationObject { + +} + +AnimationObject *-- Animation +AnimationObject --> AnimationState +AnimationObject --> Skeleton + +class AnimationState { + + time + + current_animation + + pose +} + +class Animation { + + name +} + +Animation *-- Keyframe + +class Keyframe { + + time + + transforms +} + +class Skeleton { + + joints +} + class Object { + transform } @@ -33,6 +62,7 @@ class Mesh { Mesh --> BoundingVolume Mesh --> Geometry Mesh --> Material +Mesh --> Shader class Geometry { + positions @@ -49,7 +79,6 @@ class Material { + shader params } -Material --> Shader Material o-- Texture class Shader { diff --git a/gfx/include/gfx/render_backend.h b/gfx/include/gfx/render_backend.h index 3f2dbd0..167cf8a 100644 --- a/gfx/include/gfx/render_backend.h +++ b/gfx/include/gfx/render_backend.h @@ -48,6 +48,9 @@ typedef enum PrimitiveType { size_t stride_bytes; \ } NAME; +/// A buffer view for untyped data. +MAKE_BUFFER_VIEW(BufferView, void) + /// A buffer view for 2D vectors. MAKE_BUFFER_VIEW(BufferView2d, vec2) @@ -57,16 +60,37 @@ MAKE_BUFFER_VIEW(BufferView3d, vec3) /// A buffer view for 4D vectors. MAKE_BUFFER_VIEW(BufferView4d, vec4) +/// A buffer view for floats. +MAKE_BUFFER_VIEW(BufferViewFloat, float) + +/// A buffer view for 8-bit unsigned integers. +MAKE_BUFFER_VIEW(BufferViewU8, uint8_t) + +/// A buffer view for 16-bit unsigned integers. +MAKE_BUFFER_VIEW(BufferViewU16, uint16_t) + /// A buffer view for vertex indices. MAKE_BUFFER_VIEW(BufferViewIdx, uint16_t) /// Describes a piece of geometry. +/// +/// Currently we support only 16-bit vertex indices. Might have to change this +/// to support a larger variety of 3D models. typedef struct GeometryDesc { - BufferView2d positions2d; - BufferView3d positions3d; - BufferView3d normals; - BufferView4d tangents; - BufferView2d texcoords; + BufferView2d positions2d; + BufferView3d positions3d; + BufferView3d normals; + BufferView4d tangents; + BufferView2d texcoords; + struct { + BufferViewU8 u8; + BufferViewU16 u16; + } joints; // uvec4. + struct { + BufferViewFloat floats; + BufferViewU8 u8; + BufferViewU16 u16; + } weights; // vec4 or uvec4. BufferViewIdx indices; VertexCount num_verts; size_t num_indices; @@ -102,10 +126,14 @@ typedef enum { UniformMat4, UniformTexture, UniformVec3, - UniformVec4 + UniformVec4, + UniformMat4Array } UniformType; /// Shader uniform. +/// +/// For uniform arrays, the client must ensure that the array is still valid by +/// the time the uniform data is passed to the GPU. typedef struct ShaderUniform { sstring name; UniformType type; @@ -115,6 +143,12 @@ typedef struct ShaderUniform { vec3 vec3; vec4 vec4; float scalar; + struct { + size_t count; + union { + const mat4* values; + }; + } array; } value; } ShaderUniform; @@ -236,6 +270,12 @@ Buffer* gfx_make_buffer3d(RenderBackend*, const vec3* verts, size_t count); /// Create a buffer from 4D vertices. Buffer* gfx_make_buffer4d(RenderBackend*, const vec4* verts, size_t count); +/// Create a buffer from 8-bit unsigned integers. +Buffer* gfx_make_bufferu8(RenderBackend*, const uint8_t* vals, size_t count); + +/// Create a buffer from 16-bit unsigned integers. +Buffer* gfx_make_bufferu16(RenderBackend*, const uint16_t* vals, size_t count); + /// Destroy the buffer. void gfx_destroy_buffer(RenderBackend*, Buffer**); @@ -353,3 +393,8 @@ void gfx_set_vec4_uniform(ShaderProgram*, const char* name, vec4); /// Set the float uniform. /// Has no effect if the shader does not contain the given uniform. void gfx_set_float_uniform(ShaderProgram*, const char* name, float value); + +/// Set the matrix array uniform. +/// Has no effect if the shader does not contain the given uniform. +void gfx_set_mat4_array_uniform( + ShaderProgram*, const char* name, const mat4*, size_t count); diff --git a/gfx/include/gfx/scene/README.md b/gfx/include/gfx/scene/README.md index 1910abe..c8cdadb 100644 --- a/gfx/include/gfx/scene/README.md +++ b/gfx/include/gfx/scene/README.md @@ -10,7 +10,7 @@ A scene graph implementation that includes: - Object - Scene -## Hierarchy and parenting +## Hierarchy and Parenting Scene graphs typically expose functions on nodes to add/remove objects, cameras, lights, etc. This implementation forces the hierarchy to be a strict tree and @@ -25,3 +25,51 @@ glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/s > acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. That > is, no node may be a direct descendant of more than one node. This restriction > is meant to simplify implementation and facilitate conformance.* + +## Instancing + +Two use cases for instancing seem to be: + +1. Creating N identical clones, but each with a unique transform. (Ex: N +animated characters animated in unison but located in different locations.) +2. Creating N copies of a sub-tree, each now being their own unique tree. (Ex: +The same N animated characters, but each of them now being animated separately.) + +Some scene graphs +([Panda3D](https://docs.panda3d.org/1.10/python/programming/scene-graph/instancing)) +allow two or more nodes to point to the same child, or, in other words, a node +to have multiple parents. This turns the scene graph into a DAG and adds a +number of complications for us: + +1. Shared ownership of children. We would now need some sort of ref counting or +deferred GC to delete nodes and their subtrees. +2. Nodes no longer have a unique parent. +3. Given a node, we can no longer determine its location (which parent link do +you follow?), or any attribute that is derived from its parent(s). + +In our case, we stick to strict tree hierarchies. + +Use case (1), N identical clones with unique transforms, is not a problem for +us. This is because the bulk of the data -- geometry buffers, etc. -- is stored +in the render backend anyway. So creating a full copy of the node does not +present a significant overhead since we need a unique transform for each of the +clones anyway. + +Use case (2) does present a bit more overhead and we currently do not handle it. +This could be handled in the future by special-casing a node such as +`InstanceNode` that has one child subtree and N transforms (or other +attributes), one for each unique instance of that child subtree. + +Therefore, to visit the use cases again: + +1. N character clones animated in unison in different locations -> future + `InstanceNode`. +2. N unique character copies animated on their own -> copy the character subtree + (N unique skeletons; shared mesh data and textures stored in the render + backend.) + +## Reading + +[Panda3D Scene Graph](https://docs.panda3d.org/1.10/python/programming/scene-graph/index) + +[Pixar's USD](https://graphics.pixar.com/usd/release/intro.html) diff --git a/gfx/include/gfx/scene/animation.h b/gfx/include/gfx/scene/animation.h new file mode 100644 index 0000000..ce5d73d --- /dev/null +++ b/gfx/include/gfx/scene/animation.h @@ -0,0 +1,127 @@ +#pragma once + +#include "node.h" +#include "object.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef struct Buffer Buffer; +typedef struct SceneNode SceneNode; + +typedef struct Anima Anima; +typedef struct Joint Joint; +// TODO: Remove this when removing gfx_get_anima_skeleton(). +typedef struct Skeleton Skeleton; + +/// Joint descriptor. +typedef struct JointDesc { + mat4 inv_bind_matrix; +} JointDesc; + +/// Skeleton descriptor. +/// +/// The last element of the joints array is the root of the hierarchy. For +/// flexibility (glTF), the root need not be the immediate parent of the +/// top-level joints of the skeleton, but rather, intermediate non-joint nodes +/// are allowed between the root and the skeleton. +typedef struct SkeletonDesc { + size_t num_joints; // Number of joints and matrices. + const SceneNode* joints[GFX_MAX_NUM_JOINTS]; +} SkeletonDesc; + +/// Animation interpolation mode. +typedef enum AnimationInterpolation { + StepInterpolation, + LinearInterpolation, + CubicSplineInterpolation +} AnimationInterpolation; + +/// The kind of transformation applied by a Channel. +typedef enum ChannelType { + RotationChannel, + ScaleChannel, + TranslationChannel, + WeightsChannel +} ChannelType; + +/// Animation keyframe descriptor. +/// +/// The arrays should have as many entries as 'num_joints' in the SkeletonDesc. +typedef struct KeyframeDesc { + R time; // Start time in [0, end animation time] + union { + vec3 translation; + quat rotation; + }; +} KeyframeDesc; + +/// Animation channel descriptor. +typedef struct ChannelDesc { + SceneNode* target; + ChannelType type; + AnimationInterpolation interpolation; + size_t num_keyframes; + KeyframeDesc keyframes[GFX_MAX_NUM_KEYFRAMES]; +} ChannelDesc; + +/// Animation descriptor. +typedef struct AnimationDesc { + // TODO: Replace the name with a hash for a smaller footprint and faster + // comparisons. + sstring name; // Animation name. Required for playback. + size_t num_channels; // Number of channels. + ChannelDesc channels[GFX_MAX_NUM_CHANNELS]; +} AnimationDesc; + +/// Anima object descriptor. +typedef struct AnimaDesc { + size_t num_skeletons; + size_t num_animations; + SkeletonDesc skeletons[GFX_MAX_NUM_SKELETONS]; + AnimationDesc animations[GFX_MAX_NUM_ANIMATIONS]; +} AnimaDesc; + +/// Animation play settings. +typedef struct AnimationPlaySettings { + const char* name; // Animation name. + bool loop; // Whether to loop the animation or just play once. + // TODO: Add animation speed. +} AnimationPlaySettings; + +/// Create a joint. +Joint* gfx_make_joint(const JointDesc*); + +/// Destroy the joint. +void gfx_destroy_joint(Joint**); + +/// Create an anima object. +/// +/// The anima owns its skeletons and animations. +Anima* gfx_make_anima(const AnimaDesc*); + +/// Destroy the anima. +void gfx_destroy_anima(Anima**); + +/// Play an animation (sets the current animation). +bool gfx_play_animation(Anima*, const AnimationPlaySettings*); + +/// Update the current animation. +void gfx_update_animation(Anima*, R t); + +/// Stop the current animation. +void gfx_stop_animation(Anima*); + +// TODO: Remove this, it's ugly. Things would be much simpler if scene nodes +// are allocated in arrays. Then our descs can take indices instead of pointers, +// and locating a node is simply a matter of indexing the array. The same is +// true for skeletons here and other objects. +const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i); diff --git a/gfx/include/gfx/scene/node.h b/gfx/include/gfx/scene/node.h index 292a91f..ab6ca12 100644 --- a/gfx/include/gfx/scene/node.h +++ b/gfx/include/gfx/scene/node.h @@ -1,11 +1,28 @@ #pragma once +#include "animation.h" #include +typedef struct Anima Anima; +typedef struct Joint Joint; typedef struct Light Light; typedef struct SceneCamera SceneCamera; typedef struct SceneObject SceneObject; +/// Scene node type. +typedef enum NodeType { + LogicalNode, + AnimaNode, + CameraNode, + JointNode, + LightNode, + ObjectNode +} NodeType; + +/// A node in the scene graph. +/// +/// Scene nodes take ownership of the object they are associated with (Camera, +/// Light, SceneObject, etc), as well as of child nodes. typedef struct SceneNode SceneNode; /// Create a new scene node. @@ -14,15 +31,36 @@ typedef struct SceneNode SceneNode; /// as a logical and spatial construct. SceneNode* gfx_make_node(); +/// Create an anima node. +SceneNode* gfx_make_anima_node(Anima*); + /// Create a new camera node. SceneNode* gfx_make_camera_node(SceneCamera*); +/// Create a joint node. +SceneNode* gfx_make_joint_node(Joint*); + /// Create a new light node. SceneNode* gfx_make_light_node(Light*); /// Create a new object node. SceneNode* gfx_make_object_node(SceneObject*); +/// Make the node an anima node. +void gfx_construct_anima_node(SceneNode*, Anima*); + +/// Make the node a camera node. +void gfx_construct_camera_node(SceneNode*, SceneCamera*); + +/// Make the node a joint node. +void gfx_construct_joint_node(SceneNode*, Joint*); + +/// Make the node a light node. +void gfx_construct_light_node(SceneNode*, Light*); + +/// Make the node an object node. +void gfx_construct_object_node(SceneNode*, SceneObject*); + /// Recursively destroy the scene node and its children. /// /// The scene node and its children are removed from the scene graph. @@ -30,19 +68,36 @@ SceneNode* gfx_make_object_node(SceneObject*); /// Node resources -- cameras, lights, objects, etc. -- are also destroyed. void gfx_destroy_node(SceneNode**); +/// Return the node's type. +NodeType gfx_get_node_type(const SceneNode*); + +/// Get the node's anima. +/// +/// The node must be of type AnimaNode. +Anima* gfx_get_node_anima(const SceneNode*); + /// Set the node's parent. /// /// Pass in null to unwire from the existing parent, if one exists. void gfx_set_node_parent(SceneNode*, SceneNode* parent); -/// Set the node's transform. +/// Set the node's (local) transform. void gfx_set_node_transform(SceneNode*, const mat4* transform); /// Set the node's position. void gfx_set_node_position(SceneNode*, const vec3* position); /// Set the node's rotation. -void gfx_set_node_rotation(SceneNode*, const mat4* rotation); +void gfx_set_node_rotation(SceneNode*, const quat* rotation); + +/// Set the node's rotation. +void gfx_set_node_rotation_mat(SceneNode*, const mat4* rotation); + +/// Get the node's (local) transform. +mat4 gfx_get_node_transform(const SceneNode*); + +/// Get the node's global transform. +mat4 gfx_get_node_global_transform(const SceneNode*); /// Log the node's hierarchy. void gfx_log_node_hierarchy(const SceneNode*); diff --git a/gfx/include/gfx/scene/object.h b/gfx/include/gfx/scene/object.h index 69a2231..59372c5 100644 --- a/gfx/include/gfx/scene/object.h +++ b/gfx/include/gfx/scene/object.h @@ -2,8 +2,9 @@ #include -typedef struct Mesh Mesh; +typedef struct Mesh Mesh; typedef struct SceneNode SceneNode; +typedef struct Skeleton Skeleton; typedef struct SceneObject SceneObject; @@ -24,3 +25,6 @@ void gfx_add_object_mesh(SceneObject*, Mesh*); /// Remove a mesh from the object. void gfx_remove_object_mesh(SceneObject*, Mesh*); + +/// Set the object's skeleton. +void gfx_set_object_skeleton(SceneObject*, const Skeleton*); diff --git a/gfx/include/gfx/sizes.h b/gfx/include/gfx/sizes.h index 2ed25b4..17e7c7d 100644 --- a/gfx/include/gfx/sizes.h +++ b/gfx/include/gfx/sizes.h @@ -18,6 +18,24 @@ /// Maximum number of mesh links. #define GFX_MAX_NUM_MESH_LINKS 1024 +/// Maximum number of joints per skeleton. +#define GFX_MAX_NUM_JOINTS 96 + +/// Maximum number of keyframes per channel. +#define GFX_MAX_NUM_KEYFRAMES 32 + +/// Maximum number of channels per animation. +#define GFX_MAX_NUM_CHANNELS 128 + +/// Maximum number of skeletons. +#define GFX_MAX_NUM_SKELETONS 128 + +/// Maximum number of animations. +#define GFX_MAX_NUM_ANIMATIONS 128 + +/// Maximum number of animas. +#define GFX_MAX_NUM_ANIMAS 128 + /// Maximum number of nodes per scene. #define GFX_MAX_NUM_NODES 1024 diff --git a/gfx/include/gfx/util/scene.h b/gfx/include/gfx/util/scene.h index 0aad3d3..fa9304b 100644 --- a/gfx/include/gfx/util/scene.h +++ b/gfx/include/gfx/util/scene.h @@ -24,11 +24,12 @@ typedef struct LoadSceneCmd { /// Load a scene. /// -/// |root_node| is the node under which scene elements are loaded. +/// Return a top-level node under which scene elements are rooted. |root_node| +/// is made the parent of this top-level node. /// /// |shader| is an optional shader program assigned to the loaded scene objects. /// If no shader is given, a Cook-Torrance shader based on the object's /// characteristics (presence of normals, tangents, etc) is assigned. /// /// Currently only supports the GLTF format. -bool gfx_load_scene(Gfx*, SceneNode* root_node, const LoadSceneCmd*); +SceneNode* gfx_load_scene(Gfx*, SceneNode* root_node, const LoadSceneCmd*); diff --git a/gfx/shaders/cook_torrance.vert b/gfx/shaders/cook_torrance.vert index 6425565..697bb0c 100644 --- a/gfx/shaders/cook_torrance.vert +++ b/gfx/shaders/cook_torrance.vert @@ -1,7 +1,23 @@ precision highp float; uniform mat4 ModelMatrix; -uniform mat4 MVP; +uniform mat4 Modelview; +uniform mat4 Projection; +//uniform mat4 MVP; +#ifdef HAS_JOINTS +// The client should pass in an appropriate value for MAX_JOINTS. +// #define MAX_JOINTS 96 +// +// matnxm -- n columns and m rows, different convention from math. +// We don't need the last row of [0, 0, 0, 1], so drop it to pack the matrices +// as tightly as possible. +// 256 joints * 4x4 matrix * 4 bytes/float = 16.0KB +// 256 joints * 4x3 matrix * 4 bytes/float = 12.0KB +// 96 joints * 4x4 matrix * 4 bytes/float = 6.0KB +// 96 joints * 4x3 matrix * 4 bytes/float = 4.5KB +//uniform mat4x3 Joints[MAX_JOINTS]; +uniform mat4 JointMatrices[MAX_JOINTS]; // Use 4x4 for now to keep it simple. +#endif layout (location = 0) in vec3 vPosition; #ifdef HAS_NORMALS @@ -9,10 +25,14 @@ layout (location = 1) in vec3 vNormal; #endif #ifdef HAS_TANGENTS layout (location = 2) in vec4 vTangent; -#endif // HAS_TANGENTS +#endif #ifdef HAS_TEXCOORDS layout (location = 3) in vec2 vTexcoord; #endif +#ifdef HAS_JOINTS +layout (location = 4) in uvec4 vJoint; +layout (location = 5) in vec4 vWeight; +#endif // World-space position and normal. out vec3 Position; @@ -28,7 +48,16 @@ out vec2 Texcoord; void main() { - Position = vec3(ModelMatrix * vec4(vPosition, 1.0)); +#ifdef HAS_JOINTS + mat4 skinMatrix = + vWeight.x * JointMatrices[vJoint.x] + + vWeight.y * JointMatrices[vJoint.y] + + vWeight.z * JointMatrices[vJoint.z] + + vWeight.w * JointMatrices[vJoint.w]; + Position = vec3(Modelview * skinMatrix * vec4(vPosition, 1.0)); +#else + Position = vec3(Modelview * vec4(vPosition, 1.0)); +#endif #ifdef HAS_NORMALS Normal = mat3(ModelMatrix) * vNormal; //Normal = normalize(ModelMatrix * vec4(vNormal, 0.0)).xyz; @@ -39,5 +68,6 @@ void main() #ifdef HAS_TEXCOORDS Texcoord = vTexcoord; #endif - gl_Position = MVP * vec4(vPosition, 1.0); + gl_Position = Projection * vec4(Position, 1.0); + //gl_Position = MVP * vec4(vPosition, 1.0); } diff --git a/gfx/src/render/constants.h b/gfx/src/render/constants.h index b98b0ac..a6a3b94 100644 --- a/gfx/src/render/constants.h +++ b/gfx/src/render/constants.h @@ -1,7 +1,9 @@ #pragma once // Shaders vertex attribute locations must match the channels here. -#define GFX_POSITION_CHANNEL 0 -#define GFX_NORMAL_CHANNEL 1 -#define GFX_TANGENT_CHANNEL 2 +#define GFX_POSITION_CHANNEL 0 +#define GFX_NORMAL_CHANNEL 1 +#define GFX_TANGENT_CHANNEL 2 #define GFX_TEXCOORDS_CHANNEL 3 +#define GFX_JOINTS_CHANNEL 4 +#define GFX_WEIGHTS_CHANNEL 5 diff --git a/gfx/src/render/geometry.c b/gfx/src/render/geometry.c index d5b5a95..0df6efb 100644 --- a/gfx/src/render/geometry.c +++ b/gfx/src/render/geometry.c @@ -22,136 +22,154 @@ static GLenum primitive_type_to_gl(PrimitiveType type) { } } -static const Buffer* get_or_make_buffer2d(RenderBackend* render_backend, - const BufferView2d* view) { +/// Create a buffer for the view. +/// +/// If the view already has a buffer, return the buffer. Otherwise create a +/// buffer from the view's data. +static const Buffer* get_or_make_buffer( + RenderBackend* render_backend, const BufferView* view) { if (view->buffer) { return view->buffer; } else { - return gfx_make_buffer2d(render_backend, view->data, - view->size_bytes / sizeof(vec2)); - } -} - -static const Buffer* get_or_make_buffer3d(RenderBackend* render_backend, - const BufferView3d* view) { - if (view->buffer) { - return view->buffer; - } else { - return gfx_make_buffer3d(render_backend, view->data, - view->size_bytes / sizeof(vec3)); + return gfx_make_buffer(render_backend, view->data, view->size_bytes); } } -static const Buffer* get_or_make_buffer4d(RenderBackend* render_backend, - const BufferView4d* view) { - if (view->buffer) { - return view->buffer; - } else { - return gfx_make_buffer4d(render_backend, view->data, - view->size_bytes / sizeof(vec4)); +/// Create a buffer for the view, then configure it in the VAO. +static bool configure_buffer( + RenderBackend* render_backend, const GeometryDesc* desc, + const BufferView* view, size_t num_components, size_t component_size_bytes, + GLenum component_type, GLboolean normalized, GLuint channel, + const Buffer** buffer) { + assert(render_backend); + assert(desc); + assert(view); + assert(buffer); + assert( + desc->num_verts <= + view->size_bytes / (num_components * component_size_bytes)); + *buffer = get_or_make_buffer(render_backend, view); + if (!(*buffer)) { + return false; } -} - -static const Buffer* get_or_make_buffer_idx(RenderBackend* render_backend, - const BufferViewIdx* view) { - if (view->buffer) { - return view->buffer; + assert(view->size_bytes <= (*buffer)->size_bytes); + glBindBuffer(GL_ARRAY_BUFFER, (*buffer)->vbo); + glEnableVertexAttribArray(channel); + if ((component_type == GL_FLOAT) || normalized) { + glVertexAttribPointer( + channel, num_components, component_type, normalized, view->stride_bytes, + (const void*)view->offset_bytes); } else { - return gfx_make_buffer(render_backend, view->data, view->size_bytes); + assert(!normalized); + assert( + (component_type == GL_BYTE) || (component_type == GL_UNSIGNED_BYTE) || + (component_type == GL_SHORT) || (component_type == GL_UNSIGNED_SHORT) || + (component_type == GL_INT) || component_type == GL_UNSIGNED_INT); + glVertexAttribIPointer( + channel, num_components, component_type, view->stride_bytes, + (const void*)view->offset_bytes); } + return true; } -bool gfx_init_geometry(Geometry* geometry, RenderBackend* render_backend, - const GeometryDesc* desc) { +bool gfx_init_geometry( + Geometry* geometry, RenderBackend* render_backend, + const GeometryDesc* desc) { assert(geometry); assert(render_backend); assert(desc); - assert(view_is_populated(desc->positions3d) || - view_is_populated(desc->positions2d)); + assert( + view_is_populated(desc->positions3d) || + view_is_populated(desc->positions2d)); assert(desc->num_verts > 0); - geometry->mode = primitive_type_to_gl(desc->type); - geometry->num_verts = desc->num_verts; - geometry->num_indices = desc->num_indices; + geometry->mode = primitive_type_to_gl(desc->type); + geometry->num_verts = desc->num_verts; + geometry->num_indices = desc->num_indices; geometry->indices_offset_bytes = desc->indices.offset_bytes; glGenVertexArrays(1, &geometry->vao); glBindVertexArray(geometry->vao); + bool result = true; + if (view_is_populated(desc->positions3d)) { - assert(desc->num_verts <= desc->positions3d.size_bytes / sizeof(vec3)); - geometry->positions = - get_or_make_buffer3d(render_backend, &desc->positions3d); - if (!geometry->positions) { - gfx_del_geometry(geometry); - return false; - } - assert(desc->positions3d.size_bytes <= geometry->positions->size_bytes); - glBindBuffer(GL_ARRAY_BUFFER, geometry->positions->vbo); - glEnableVertexAttribArray(GFX_POSITION_CHANNEL); - glVertexAttribPointer(GFX_POSITION_CHANNEL, 3, GL_FLOAT, GL_FALSE, - desc->positions3d.stride_bytes, - (const void*)desc->positions3d.offset_bytes); + result = result && + configure_buffer( + render_backend, desc, (const BufferView*)&desc->positions3d, 3, + sizeof(float), GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL, + &geometry->positions); } else if (view_is_populated(desc->positions2d)) { - assert(desc->num_verts <= desc->positions2d.size_bytes / sizeof(vec2)); - geometry->positions = - get_or_make_buffer2d(render_backend, &desc->positions2d); - if (!geometry->positions) { - gfx_del_geometry(geometry); - return false; - } - assert(desc->positions2d.size_bytes <= geometry->positions->size_bytes); - glBindBuffer(GL_ARRAY_BUFFER, geometry->positions->vbo); - glEnableVertexAttribArray(GFX_POSITION_CHANNEL); - glVertexAttribPointer(GFX_POSITION_CHANNEL, 2, GL_FLOAT, GL_FALSE, - desc->positions2d.stride_bytes, - (const void*)desc->positions2d.offset_bytes); + result = result && + configure_buffer( + render_backend, desc, (const BufferView*)&desc->positions2d, 2, + sizeof(float), GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL, + &geometry->positions); } if (view_is_populated(desc->normals)) { - assert(desc->num_verts <= desc->normals.size_bytes / sizeof(vec3)); - geometry->normals = get_or_make_buffer3d(render_backend, &desc->normals); - if (!geometry->normals) { - gfx_del_geometry(geometry); - return false; - } - assert(desc->normals.size_bytes <= geometry->normals->size_bytes); - glBindBuffer(GL_ARRAY_BUFFER, geometry->normals->vbo); - glEnableVertexAttribArray(GFX_NORMAL_CHANNEL); - glVertexAttribPointer(GFX_NORMAL_CHANNEL, 3, GL_FLOAT, GL_FALSE, - desc->normals.stride_bytes, - (const void*)desc->normals.offset_bytes); + result = + result && configure_buffer( + render_backend, desc, (const BufferView*)&desc->normals, + 3, sizeof(float), GL_FLOAT, GL_FALSE, GFX_NORMAL_CHANNEL, + &geometry->normals); } if (view_is_populated(desc->tangents)) { - assert(desc->num_verts <= desc->tangents.size_bytes / sizeof(vec3)); - geometry->tangents = get_or_make_buffer4d(render_backend, &desc->tangents); - if (!geometry->tangents) { - gfx_del_geometry(geometry); - return false; - } - assert(desc->tangents.size_bytes <= geometry->tangents->size_bytes); - glBindBuffer(GL_ARRAY_BUFFER, geometry->tangents->vbo); - glEnableVertexAttribArray(GFX_TANGENT_CHANNEL); - glVertexAttribPointer(GFX_TANGENT_CHANNEL, 4, GL_FLOAT, GL_FALSE, - desc->tangents.stride_bytes, - (const void*)desc->tangents.offset_bytes); + result = + result && configure_buffer( + render_backend, desc, (const BufferView*)&desc->tangents, + 4, sizeof(float), GL_FLOAT, GL_FALSE, GFX_TANGENT_CHANNEL, + &geometry->tangents); } if (view_is_populated(desc->texcoords)) { - assert(desc->num_verts <= desc->texcoords.size_bytes / sizeof(vec2)); - geometry->texcoords = - get_or_make_buffer2d(render_backend, &desc->texcoords); - if (!geometry->texcoords) { - gfx_del_geometry(geometry); - return false; - } - assert(desc->texcoords.size_bytes <= geometry->texcoords->size_bytes); - glBindBuffer(GL_ARRAY_BUFFER, geometry->texcoords->vbo); - glEnableVertexAttribArray(GFX_TEXCOORDS_CHANNEL); - glVertexAttribPointer(GFX_TEXCOORDS_CHANNEL, 2, GL_FLOAT, GL_FALSE, - desc->texcoords.stride_bytes, - (const void*)desc->texcoords.offset_bytes); + result = + result && configure_buffer( + render_backend, desc, (const BufferView*)&desc->texcoords, + 2, sizeof(float), GL_FLOAT, GL_FALSE, + GFX_TEXCOORDS_CHANNEL, &geometry->texcoords); + } + + if (view_is_populated(desc->joints.u8)) { + result = + result && configure_buffer( + render_backend, desc, (const BufferView*)&desc->joints.u8, + 4, sizeof(uint8_t), GL_UNSIGNED_BYTE, GL_FALSE, + GFX_JOINTS_CHANNEL, &geometry->joints); + } else if (view_is_populated(desc->joints.u16)) { + result = result && + configure_buffer( + render_backend, desc, (const BufferView*)&desc->joints.u16, 4, + sizeof(uint16_t), GL_UNSIGNED_SHORT, GL_FALSE, + GFX_JOINTS_CHANNEL, &geometry->joints); + } + + // If weights are given as unsigned integers, then they are normalized when + // read by the shader. + if (view_is_populated(desc->weights.u8)) { + result = result && + configure_buffer( + render_backend, desc, (const BufferView*)&desc->weights.u8, 4, + sizeof(uint8_t), GL_UNSIGNED_BYTE, GL_TRUE, + GFX_WEIGHTS_CHANNEL, &geometry->weights); + } else if (view_is_populated(desc->weights.u16)) { + result = result && + configure_buffer( + render_backend, desc, (const BufferView*)&desc->weights.u16, 4, + sizeof(uint16_t), GL_UNSIGNED_SHORT, GL_TRUE, + GFX_WEIGHTS_CHANNEL, &geometry->weights); + } else if (view_is_populated(desc->weights.floats)) { + result = result && + configure_buffer( + render_backend, desc, (const BufferView*)&desc->weights.floats, + 4, sizeof(float), GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL, + &geometry->weights); + } + + if (!result) { + gfx_del_geometry(geometry); + return false; } glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -159,7 +177,8 @@ bool gfx_init_geometry(Geometry* geometry, RenderBackend* render_backend, if (view_is_populated(desc->indices)) { assert(desc->num_indices > 0); assert(desc->num_indices <= desc->indices.size_bytes / sizeof(VertexIndex)); - geometry->indices = get_or_make_buffer_idx(render_backend, &desc->indices); + geometry->indices = + get_or_make_buffer(render_backend, (const BufferView*)&desc->indices); if (!geometry->indices) { gfx_del_geometry(geometry); return false; @@ -186,8 +205,9 @@ void gfx_render_geometry(const Geometry* geometry) { glBindVertexArray(geometry->vao); if (geometry->indices) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometry->indices->vbo); - glDrawElements(geometry->mode, geometry->num_indices, GL_UNSIGNED_SHORT, - (const void*)geometry->indices_offset_bytes); + glDrawElements( + geometry->mode, geometry->num_indices, GL_UNSIGNED_SHORT, + (const void*)geometry->indices_offset_bytes); } else { glDrawArrays(geometry->mode, 0, geometry->num_verts); } diff --git a/gfx/src/render/geometry.h b/gfx/src/render/geometry.h index a6f7e05..38e716b 100644 --- a/gfx/src/render/geometry.h +++ b/gfx/src/render/geometry.h @@ -13,11 +13,11 @@ /// the renderer assumes ownership of all rendering resources, which simplifies /// their management. typedef struct Geometry { - GLuint vao; - GLenum mode; + GLuint vao; + GLenum mode; VertexCount num_verts; - size_t num_indices; - size_t indices_offset_bytes; + size_t num_indices; + size_t indices_offset_bytes; // Buffer pointers are no longer needed once the VAO is created, but we store // them here so that we can keep track of what Geometry objects use what // Buffer objects. @@ -25,6 +25,8 @@ typedef struct Geometry { const Buffer* normals; const Buffer* tangents; const Buffer* texcoords; + const Buffer* joints; + const Buffer* weights; const Buffer* indices; } Geometry; diff --git a/gfx/src/render/render_backend.c b/gfx/src/render/render_backend.c index b3dc805..fed906b 100644 --- a/gfx/src/render/render_backend.c +++ b/gfx/src/render/render_backend.c @@ -112,44 +112,32 @@ Buffer* gfx_make_buffer( Buffer* gfx_make_buffer2d( RenderBackend* render_backend, const vec2* verts, size_t count) { - assert(render_backend); - Buffer* buffer = mempool_alloc(&render_backend->buffers); - if (!buffer) { - return 0; - } - if (!gfx_init_buffer_2d(buffer, verts, count)) { - mempool_free(&render_backend->buffers, &buffer); - return 0; - } - return buffer; + return gfx_make_buffer( + render_backend, (const void*)verts, count * sizeof(verts)); } Buffer* gfx_make_buffer3d( RenderBackend* render_backend, const vec3* verts, size_t count) { - assert(render_backend); - Buffer* buffer = mempool_alloc(&render_backend->buffers); - if (!buffer) { - return 0; - } - if (!gfx_init_buffer_3d(buffer, verts, count)) { - mempool_free(&render_backend->buffers, &buffer); - return 0; - } - return buffer; + return gfx_make_buffer( + render_backend, (const void*)verts, count * sizeof(verts)); } Buffer* gfx_make_buffer4d( RenderBackend* render_backend, const vec4* verts, size_t count) { - assert(render_backend); - Buffer* buffer = mempool_alloc(&render_backend->buffers); - if (!buffer) { - return 0; - } - if (!gfx_init_buffer_4d(buffer, verts, count)) { - mempool_free(&render_backend->buffers, &buffer); - return 0; - } - return buffer; + return gfx_make_buffer( + render_backend, (const void*)verts, count * sizeof(verts)); +} + +Buffer* gfx_make_bufferu8( + RenderBackend* render_backend, const uint8_t* vals, size_t count) { + return gfx_make_buffer( + render_backend, (const void*)vals, count * sizeof(uint8_t)); +} + +Buffer* gfx_make_bufferu16( + RenderBackend* render_backend, const uint16_t* vals, size_t count) { + return gfx_make_buffer( + render_backend, (const void*)vals, count * sizeof(uint16_t)); } void gfx_destroy_buffer(RenderBackend* render_backend, Buffer** buffer) { diff --git a/gfx/src/render/render_backend_impl.h b/gfx/src/render/render_backend_impl.h index 6d13367..9e552c2 100644 --- a/gfx/src/render/render_backend_impl.h +++ b/gfx/src/render/render_backend_impl.h @@ -29,13 +29,13 @@ typedef struct { typedef struct RenderBackend { Viewport viewport; // mempools for render-specific objects: textures, geometry, etc. - buffer_pool buffers; - framebuffer_pool framebuffers; - geometry_pool geometries; - renderbuffer_pool renderbuffers; - shader_pool shaders; + buffer_pool buffers; + framebuffer_pool framebuffers; + geometry_pool geometries; + renderbuffer_pool renderbuffers; + shader_pool shaders; shader_program_pool shader_programs; - texture_pool textures; + texture_pool textures; } RenderBackend; /// Create a new render backend. diff --git a/gfx/src/render/shader_program.c b/gfx/src/render/shader_program.c index 05cfd5a..d80ee4f 100644 --- a/gfx/src/render/shader_program.c +++ b/gfx/src/render/shader_program.c @@ -41,8 +41,8 @@ static GLuint create_program(GLuint vertex_shader, GLuint fragment_shader) { return prog; } -bool gfx_build_shader_program(ShaderProgram* prog, - const ShaderProgramDesc* desc) { +bool gfx_build_shader_program( + ShaderProgram* prog, const ShaderProgramDesc* desc) { assert(prog); assert(desc); prog->id = create_program(desc->vertex_shader->id, desc->fragment_shader->id); @@ -70,8 +70,8 @@ void gfx_deactivate_shader_program(const ShaderProgram* prog) { ASSERT_GL; } -static void set_texture_uniform(GLuint prog, const char* name, int texture_unit, - const Texture* texture) { +static void set_texture_uniform( + GLuint prog, const char* name, int texture_unit, const Texture* texture) { assert(prog != 0); assert(name); assert(texture); @@ -83,13 +83,14 @@ static void set_texture_uniform(GLuint prog, const char* name, int texture_unit, } } -static void set_mat4_uniform(GLuint prog, const char* name, const mat4* mat) { +static void set_mat4_uniform( + GLuint prog, const char* name, const mat4* mats, size_t count) { assert(prog != 0); assert(name); - assert(mat); + assert(mats); const GLint location = glGetUniformLocation(prog, name); if (location >= 0) { - glUniformMatrix4fv(location, 1, GL_FALSE, (const float*)mat); + glUniformMatrix4fv(location, count, GL_FALSE, (const float*)mats); } } @@ -127,12 +128,13 @@ void gfx_apply_uniforms(const ShaderProgram* prog) { const ShaderUniform* uniform = &prog->uniforms[i]; switch (uniform->type) { case UniformTexture: - set_texture_uniform(prog->id, uniform->name.str, next_texture_unit, - uniform->value.texture); + set_texture_uniform( + prog->id, uniform->name.str, next_texture_unit, + uniform->value.texture); next_texture_unit++; break; case UniformMat4: - set_mat4_uniform(prog->id, uniform->name.str, &uniform->value.mat4); + set_mat4_uniform(prog->id, uniform->name.str, &uniform->value.mat4, 1); break; case UniformVec3: set_vec3_uniform(prog->id, uniform->name.str, uniform->value.vec3); @@ -143,17 +145,19 @@ void gfx_apply_uniforms(const ShaderProgram* prog) { case UniformFloat: set_float_uniform(prog->id, uniform->name.str, uniform->value.scalar); break; + case UniformMat4Array: + set_mat4_uniform( + prog->id, uniform->name.str, uniform->value.array.values, + uniform->value.array.count); + break; } } } // Get the ShaderUniform object by name from the shader program if it already // exists, or allocate a new one otherwise. -// -// If there is no more space for a new uniform, this function logs an error and -// returns 0. -static ShaderUniform* get_or_allocate_uniform(ShaderProgram* prog, - const char* name) { +static ShaderUniform* get_or_allocate_uniform( + ShaderProgram* prog, const char* name) { assert(prog); assert(name); // First search for the uniform in the list. @@ -167,6 +171,7 @@ static ShaderUniform* get_or_allocate_uniform(ShaderProgram* prog, if (prog->num_uniforms == GFX_MAX_UNIFORMS_PER_SHADER) { LOGE("Exceeded the maximum number of uniforms per shader. Please increase " "this value."); + assert(false); return 0; } ShaderUniform* uniform = &prog->uniforms[prog->num_uniforms]; @@ -177,8 +182,8 @@ static ShaderUniform* get_or_allocate_uniform(ShaderProgram* prog, // The functions below save the value of a uniform in the shader program. If the // uniform does not even exist, then there is no need to store the value. -void gfx_set_texture_uniform(ShaderProgram* prog, const char* name, - const Texture* texture) { +void gfx_set_texture_uniform( + ShaderProgram* prog, const char* name, const Texture* texture) { assert(prog); assert(name); assert(texture); @@ -187,16 +192,14 @@ void gfx_set_texture_uniform(ShaderProgram* prog, const char* name, return; } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); - if (!uniform) { - return; - } - uniform->name = sstring_make(name); - uniform->type = UniformTexture; + assert(uniform); + uniform->name = sstring_make(name); + uniform->type = UniformTexture; uniform->value.texture = texture; } -void gfx_set_mat4_uniform(ShaderProgram* prog, const char* name, - const mat4* mat) { +void gfx_set_mat4_uniform( + ShaderProgram* prog, const char* name, const mat4* mat) { assert(prog); assert(name); assert(mat); @@ -205,11 +208,9 @@ void gfx_set_mat4_uniform(ShaderProgram* prog, const char* name, return; } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); - if (!uniform) { - return; - } - uniform->name = sstring_make(name); - uniform->type = UniformMat4; + assert(uniform); + uniform->name = sstring_make(name); + uniform->type = UniformMat4; uniform->value.mat4 = *mat; } @@ -221,11 +222,9 @@ void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) { return; } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); - if (!uniform) { - return; - } - uniform->name = sstring_make(name); - uniform->type = UniformVec3; + assert(uniform); + uniform->name = sstring_make(name); + uniform->type = UniformVec3; uniform->value.vec3 = value; } @@ -237,11 +236,9 @@ void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) { return; } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); - if (!uniform) { - return; - } - uniform->name = sstring_make(name); - uniform->type = UniformVec4; + assert(uniform); + uniform->name = sstring_make(name); + uniform->type = UniformVec4; uniform->value.vec4 = value; } @@ -255,10 +252,25 @@ void gfx_set_float_uniform(ShaderProgram* prog, const char* name, float value) { return; } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); - if (!uniform) { + assert(uniform); + uniform->name = sstring_make(name); + uniform->type = UniformFloat; + uniform->value.scalar = value; +} + +void gfx_set_mat4_array_uniform( + ShaderProgram* prog, const char* name, const mat4* mats, size_t count) { + assert(prog); + assert(name); + assert(mats); + const GLint location = glGetUniformLocation(prog->id, name); + if (location < 0) { return; } - uniform->name = sstring_make(name); - uniform->type = UniformFloat; - uniform->value.scalar = value; + ShaderUniform* uniform = get_or_allocate_uniform(prog, name); + assert(uniform); + uniform->name = sstring_make(name); + uniform->type = UniformMat4Array; + uniform->value.array.count = count; + uniform->value.array.values = mats; } diff --git a/gfx/src/renderer/renderer.c b/gfx/src/renderer/renderer.c index 8b101db..47f1344 100644 --- a/gfx/src/renderer/renderer.c +++ b/gfx/src/renderer/renderer.c @@ -119,8 +119,31 @@ typedef struct RenderState { Light* environment_light; const float fovy; const float aspect; + size_t num_joints; + mat4 joint_matrices[GFX_MAX_NUM_JOINTS]; } RenderState; +/// Load joint matrices into the render state. +static void load_skeleton(RenderState* state, skeleton_idx skeleton_index) { + assert(state); + assert(skeleton_index.val != 0); + + const Skeleton* skeleton = mem_get_skeleton(skeleton_index); + assert(skeleton); + assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS); + + for (size_t i = 0; i < skeleton->num_joints; ++i) { + const SceneNode* node = mem_get_node(skeleton->joints[i]); + assert(node); + assert(node->type == JointNode); + const Joint* joint = mem_get_joint(node->joint); + assert(joint); + state->joint_matrices[i] = joint->joint_matrix; + } + state->num_joints = skeleton->num_joints; +} + +/// Draw the scene recursively. static void draw_recursively( RenderState* state, mat4 parent_transform, const SceneNode* node) { assert(state); @@ -148,6 +171,10 @@ static void draw_recursively( const mat4 modelview = mat4_mul(*state->view_matrix, model_matrix); const mat4 mvp = mat4_mul(*state->projection, modelview); + if (object->skeleton.val) { + load_skeleton(state, object->skeleton); + } + for (mesh_link_idx mesh_link_index = object->mesh_link; mesh_link_index.val;) { const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index); @@ -170,6 +197,10 @@ static void draw_recursively( gfx_set_float_uniform(shader, "Fovy", state->fovy); gfx_set_float_uniform(shader, "Aspect", state->aspect); gfx_set_vec3_uniform(shader, "CameraPosition", state->camera->spatial.p); + if (state->num_joints > 0) { + gfx_set_mat4_array_uniform( + shader, "JointMatrices", state->joint_matrices, state->num_joints); + } // Apply lights. if (state->environment_light) { const EnvironmentLight* light = &state->environment_light->environment; @@ -192,6 +223,9 @@ static void draw_recursively( gfx_apply_uniforms(shader); gfx_render_geometry(mesh->geometry); } + + // Reset state for next object. + state->num_joints = 0; } // Render children recursively. diff --git a/gfx/src/scene/animation.c b/gfx/src/scene/animation.c new file mode 100644 index 0000000..91d3e08 --- /dev/null +++ b/gfx/src/scene/animation.c @@ -0,0 +1,403 @@ +#include "animation_impl.h" + +#include "node_impl.h" +#include "scene_memory.h" + +// #include // Debugging. + +static const R PLAYBACK_UNINITIALIZED = -1; + +Joint* gfx_make_joint(const JointDesc* desc) { + assert(desc); + + // 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* joint = mem_alloc_joint(); + assert(joint); + joint->inv_bind_matrix = desc->inv_bind_matrix; + joint->joint_matrix = mat4_id(); + return joint; +} + +void gfx_destroy_joint(Joint** joint) { + assert(joint); + assert(*joint); + + if ((*joint)->parent.val) { + gfx_del_node((*joint)->parent); + } + mem_free_joint(joint); +} + +static Skeleton* make_skeleton(const SkeletonDesc* desc) { + assert(desc); + assert(desc->num_joints < GFX_MAX_NUM_JOINTS); + + Skeleton* skeleton = mem_alloc_skeleton(); + assert(skeleton); + skeleton->num_joints = desc->num_joints; + for (size_t i = 0; i < desc->num_joints; ++i) { + skeleton->joints[i] = mem_get_node_index(desc->joints[i]); + } + return skeleton; +} + +static Animation* make_animation(const AnimationDesc* desc) { + assert(desc); + assert(desc->num_channels < GFX_MAX_NUM_CHANNELS); + + Animation* animation = mem_alloc_animation(); + assert(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 = mem_get_node_index(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); + + Anima* anima = mem_alloc_anima(); + if (!anima) { + return 0; + } + + // 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]); + // TODO: Here and everywhere else, I think it would simplify the code + // greatly to make mem_alloc_xyz() fail if the allocation fails. At that + // point the user should just bump their memory limits. + assert(skeleton); + 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]); + assert(animation); + 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; + } + + return anima; +} + +void gfx_destroy_anima(Anima** anima) { + assert(anima); + assert(*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); + assert(animation); + 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 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(const Channel* channel, R t) { + assert(channel); + assert(channel->target.val != 0); + + 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; + + SceneNode* target = mem_get_node(channel->target); + assert(target); + + switch (channel->type) { + case RotationChannel: { + const quat rotation = interpolate_rotation(channel, prev, next, t); + gfx_set_node_rotation(target, &rotation); + break; + } + case TranslationChannel: { + const vec3 translation = interpolate_translation(channel, prev, next, t); + gfx_set_node_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( + node_idx node_index, mat4 parent_global_joint_transform, + const mat4* root_inv_global_transform) { + if (node_index.val == 0) { + return; + } + + const SceneNode* node = mem_get_node(node_index); + assert(node); + const mat4 global_joint_transform = + mat4_mul(parent_global_joint_transform, node->transform); + + // For flexibility (glTF), we allow non-joint nodes between the root of the + // skeleton and its joint nodes. We check the node type as opposed to assert + // it. + if (node->type == JointNode) { + // Compute this node's joint matrix. + Joint* joint = mem_get_joint(node->joint); + assert(joint); + + 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 node's children. + node_idx child = node->child; + while (child.val != 0) { + compute_joint_matrices_rec( + child, global_joint_transform, root_inv_global_transform); + node = mem_get_node(child); + child = node->next; // Next sibling. + } +} + +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(channel, animation_time); + } + + // Compute joint matrices after having transformed the skeletons. + // + // Skeletons are not guaranteed to have a common parent, but are generally a + // set of disjoint trees (glTF). The anima's parent node, however, is + // guaranteed to be the common ancestor of all skeletons. + // + // Joint matrix calculation therefore begins by descending from the anima's + // node. This node's immediate children may not be joints, however, so we need + // to gracefully handle them and proceed recursively. + // + // Lack of a common parent aside, the procedure touches every joint exactly + // once (and potentially other non-joint intermediate nodes). + node_idx root_index = anima->parent; + SceneNode* root = mem_get_node(root_index); + assert(root); + // LOGD("Root: %u, child: %u", root_index.val, root->child.val); + const mat4 root_global_transform = gfx_get_node_global_transform(root); + const mat4 root_inv_global_transform = mat4_inverse(root_global_transform); + compute_joint_matrices_rec( + root->child, 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; +} diff --git a/gfx/src/scene/animation_impl.h b/gfx/src/scene/animation_impl.h new file mode 100644 index 0000000..ceebbbf --- /dev/null +++ b/gfx/src/scene/animation_impl.h @@ -0,0 +1,95 @@ +#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. + +typedef struct Joint { + node_idx parent; // Parent SceneNode. + mat4 inv_bind_matrix; + mat4 joint_matrix; +} Joint; + +/// Animation skeleton. +/// +/// The joints array maps joint indices to scene nodes. +/// +/// The last element of the joints array is the root of the hierarchy. Storing +/// the root of the hierarchy allows us to compute joint matrices only for scene +/// nodes that are actually part of a skeleton, since the root identifies the +/// skeleton's node hierarchy. +/// +/// Some model formats (glTF) may not actually specify a root. In that case, the +/// client must create one to form a strict tree hierarchy. +typedef struct Skeleton { + skeleton_idx next; + size_t num_joints; + node_idx joints[GFX_MAX_NUM_JOINTS]; // Last = root. +} 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 { + node_idx target; + 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. +typedef struct Anima { + AnimationState state; + skeleton_idx skeleton; + animation_idx animation; // Index of first animation. + node_idx parent; // Parent SceneNode. +} Anima; diff --git a/gfx/src/scene/material.c b/gfx/src/scene/material.c index e5856d0..6d6decb 100644 --- a/gfx/src/scene/material.c +++ b/gfx/src/scene/material.c @@ -43,6 +43,11 @@ static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) { 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; } } diff --git a/gfx/src/scene/node.c b/gfx/src/scene/node.c index 71e0e0b..760951b 100644 --- a/gfx/src/scene/node.c +++ b/gfx/src/scene/node.c @@ -26,6 +26,18 @@ SceneNode* gfx_make_node() { return node; } +SceneNode* gfx_make_anima_node(Anima* anima) { + assert(anima); + SceneNode* node = gfx_make_node(); + if (!node) { + return 0; + } + 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(); @@ -38,6 +50,18 @@ SceneNode* gfx_make_camera_node(SceneCamera* camera) { return node; } +SceneNode* gfx_make_joint_node(Joint* joint) { + assert(joint); + SceneNode* node = gfx_make_node(); + if (!node) { + return 0; + } + node->type = JointNode; + node->joint = mem_get_joint_index(joint); + joint->parent = mem_get_node_index(node); + return node; +} + SceneNode* gfx_make_light_node(Light* light) { assert(light); SceneNode* node = gfx_make_node(); @@ -62,6 +86,100 @@ SceneNode* gfx_make_object_node(SceneObject* object) { 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); + break; + } + case CameraNode: { + SceneCamera* camera = mem_get_camera(node->camera); + camera->parent.val = 0; + gfx_destroy_camera(&camera); + break; + } + case JointNode: { + Joint* joint = mem_get_joint(node->joint); + joint->parent.val = 0; + gfx_destroy_joint(&joint); + break; + } + case LightNode: { + Light* light = mem_get_light(node->light); + light->parent.val = 0; + gfx_destroy_light(&light); + break; + } + case ObjectNode: { + SceneObject* object = mem_get_object(node->object); + object->parent.val = 0; + gfx_destroy_object(&object); + break; + } + case LogicalNode: + break; // Logical nodes have no resource. + } +} + +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_joint_node(SceneNode* node, Joint* joint) { + assert(node); + assert(joint); + + free_node_resource(node); + node->type = JointNode; + node->joint = mem_get_joint_index(joint); + joint->parent = mem_get_node_index(node); +} + +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_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); @@ -75,21 +193,7 @@ static void destroy_node_rec(SceneNode* node) { destroy_node_rec(mem_get_node(node->next)); } - // Destroy the node's resource. - if (node->type == CameraNode) { - SceneCamera* camera = mem_get_camera(node->camera); - camera->parent.val = 0; // Avoid call into gfx_del_node(). - gfx_destroy_camera(&camera); - } else if (node->type == LightNode) { - Light* light = mem_get_light(node->light); - light->parent.val = 0; // Avoid call into gfx_del_node(). - gfx_destroy_light(&light); - } else if (node->type == ObjectNode) { - SceneObject* object = mem_get_object(node->object); - object->parent.val = 0; // Avoid call into gfx_del_node(). - gfx_destroy_object(&object); - } - + free_node_resource(node); mem_free_node(&node); } @@ -118,6 +222,17 @@ void gfx_del_node(node_idx index) { mem_free_node(&node); } +NodeType gfx_get_node_type(const SceneNode* node) { + assert(node); + return node->type; +} + +Anima* gfx_get_node_anima(const SceneNode* node) { + assert(node); + assert(node->type == AnimaNode); + return mem_get_anima(node->anima); +} + void gfx_set_node_parent(SceneNode* child, SceneNode* parent) { assert(child); // Parent can be null. @@ -136,17 +251,61 @@ void gfx_set_node_position(SceneNode* node, const vec3* position) { mat4_set_v3(&node->transform, *position); } -void gfx_set_node_rotation(SceneNode* node, const mat4* rotation) { +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); } +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; +} + +static const char* get_node_type_str(NodeType type) { + switch (type) { + case LogicalNode: + return "LogicalNode"; + case AnimaNode: + return "AnimaNode"; + case CameraNode: + return "CameraNode"; + case JointNode: + return "JointNode"; + case LightNode: + return "LightNode"; + case ObjectNode: + return "ObjectNode"; + } + assert(false); + return ""; +} + static void log_node_hierarchy_rec(const SceneNode* node, const sstring* pad) { assert(node); assert(pad); - LOGD("%sNode (%u)", sstring_cstring(pad), mem_get_node_index(node).val); + 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) { diff --git a/gfx/src/scene/node_impl.h b/gfx/src/scene/node_impl.h index b5c4af8..e8d546f 100644 --- a/gfx/src/scene/node_impl.h +++ b/gfx/src/scene/node_impl.h @@ -4,16 +4,9 @@ #include "types.h" +#include #include -/// Scene node type. -typedef enum NodeType { - LogicalNode, - CameraNode, - LightNode, - ObjectNode -} NodeType; - /// Scene node. /// /// The SceneNode owns its cameras, objects, lights and child nodes. These @@ -21,7 +14,9 @@ typedef enum NodeType { typedef struct SceneNode { NodeType type; union { + anima_idx anima; camera_idx camera; + joint_idx joint; light_idx light; object_idx object; }; diff --git a/gfx/src/scene/object.c b/gfx/src/scene/object.c index 7f23e92..47d2f25 100644 --- a/gfx/src/scene/object.c +++ b/gfx/src/scene/object.c @@ -40,8 +40,8 @@ void gfx_add_object_mesh(SceneObject* object, Mesh* mesh) { MeshLink* link = mem_alloc_mesh_link(); assert(link); - link->mesh = mem_get_mesh_index(mesh); - link->next = object->mesh_link; + link->mesh = mem_get_mesh_index(mesh); + link->next = object->mesh_link; object->mesh_link = mem_get_mesh_link_index(link); } @@ -49,7 +49,7 @@ void gfx_remove_object_mesh(SceneObject* object, Mesh* mesh) { assert(object); assert(mesh); - MeshLink* prev = 0; + MeshLink* prev = 0; const mesh_idx mesh_index = mem_get_mesh_index(mesh); // Find the MeshLink in the object that contains the given mesh. @@ -65,7 +65,13 @@ void gfx_remove_object_mesh(SceneObject* object, Mesh* mesh) { mem_free_mesh_link(&mesh_link); break; } - prev = mesh_link; + prev = mesh_link; mesh_link_index = mesh_link->next; } } + +void gfx_set_object_skeleton(SceneObject* object, const Skeleton* skeleton) { + assert(object); + assert(skeleton); + object->skeleton = mem_get_skeleton_index(skeleton); +} diff --git a/gfx/src/scene/object_impl.h b/gfx/src/scene/object_impl.h index bd3fdbc..45119db 100644 --- a/gfx/src/scene/object_impl.h +++ b/gfx/src/scene/object_impl.h @@ -7,7 +7,7 @@ #include typedef struct MeshLink { - mesh_idx mesh; + mesh_idx mesh; mesh_link_idx next; // Next MeshLink in the list. } MeshLink; @@ -19,7 +19,8 @@ typedef struct MeshLink { /// different for each SceneObject. Each SceneObject may then have a unique list /// of Meshes, and the Meshes are re-used. typedef struct SceneObject { - mat4 transform; + mat4 transform; mesh_link_idx mesh_link; // First MeshLink in the list. - node_idx parent; // Parent SceneNode. + skeleton_idx skeleton; + node_idx parent; // Parent SceneNode. } SceneObject; diff --git a/gfx/src/scene/scene_memory.c b/gfx/src/scene/scene_memory.c index 83ecd57..62e9401 100644 --- a/gfx/src/scene/scene_memory.c +++ b/gfx/src/scene/scene_memory.c @@ -2,6 +2,7 @@ #include +#include "animation_impl.h" #include "camera_impl.h" #include "light_impl.h" #include "material_impl.h" @@ -13,7 +14,10 @@ #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(joint_pool, Joint, GFX_MAX_NUM_JOINTS) 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) @@ -21,12 +25,16 @@ DEF_MEMPOOL(mesh_link_pool, MeshLink, GFX_MAX_NUM_MESH_LINKS) 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; + joint_pool joints; light_pool lights; material_pool materials; mesh_pool meshes; @@ -34,6 +42,7 @@ typedef struct SceneMemory { node_pool nodes; object_pool objects; scene_pool scenes; + skeleton_pool skeletons; } SceneMemory; static SceneMemory mem; @@ -42,7 +51,10 @@ static SceneMemory mem; assert(mempool_get_block_index(POOL, mempool_alloc(POOL)) == 0) void scene_mem_init() { + mempool_make(&mem.animas); + mempool_make(&mem.animations); mempool_make(&mem.cameras); + mempool_make(&mem.joints); mempool_make(&mem.lights); mempool_make(&mem.materials); mempool_make(&mem.meshes); @@ -50,10 +62,14 @@ void scene_mem_init() { 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.joints); ALLOC_DUMMY(&mem.lights); ALLOC_DUMMY(&mem.materials); ALLOC_DUMMY(&mem.meshes); @@ -61,6 +77,7 @@ void scene_mem_init() { ALLOC_DUMMY(&mem.nodes); ALLOC_DUMMY(&mem.objects); ALLOC_DUMMY(&mem.scenes); + ALLOC_DUMMY(&mem.skeletons); } void scene_mem_destroy() { @@ -76,12 +93,30 @@ void scene_mem_destroy() { scene_destroy(scene); } }); + // Then delete stray nodes. This will delete their children nodes and + // resource. + mempool_foreach(&mem.nodes, node, { + if (i > 0) { + gfx_destroy_node(&node); + } + }); // Destroy remaining scene elements. + mempool_foreach(&mem.animas, anima, { + if (i > 0) { + gfx_destroy_anima(&anima); + } + }); + // Animations are owned by animas and do not have a destructor. mempool_foreach(&mem.cameras, camera, { if (i > 0) { gfx_destroy_camera(&camera); } }); + mempool_foreach(&mem.joints, joint, { + if (i > 0) { + gfx_destroy_joint(&joint); + } + }); mempool_foreach(&mem.lights, light, { if (i > 0) { gfx_destroy_light(&light); @@ -98,20 +133,19 @@ void scene_mem_destroy() { } }); // Mesh links don't have a destructor. - mempool_foreach(&mem.nodes, node, { - if (i > 0) { - gfx_destroy_node(&node); - } - }); mempool_foreach(&mem.objects, object, { if (i > 0) { gfx_destroy_object(&object); } }); + // Skeletons are owned by animas and do not have a destructor. } // Memory allocation. +Anima* mem_alloc_anima() { return mempool_alloc(&mem.animas); } +Animation* mem_alloc_animation() { return mempool_alloc(&mem.animations); } SceneCamera* mem_alloc_camera() { return mempool_alloc(&mem.cameras); } +Joint* mem_alloc_joint() { return mempool_alloc(&mem.joints); } Light* mem_alloc_light() { return mempool_alloc(&mem.lights); } Material* mem_alloc_material() { return mempool_alloc(&mem.materials); } Mesh* mem_alloc_mesh() { return mempool_alloc(&mem.meshes); } @@ -119,11 +153,17 @@ MeshLink* mem_alloc_mesh_link() { return mempool_alloc(&mem.mesh_links); } SceneNode* mem_alloc_node() { return mempool_alloc(&mem.nodes); } SceneObject* mem_alloc_object() { return mempool_alloc(&mem.objects); } Scene* mem_alloc_scene() { return mempool_alloc(&mem.scenes); } +Skeleton* mem_alloc_skeleton() { return mempool_alloc(&mem.skeletons); } // Memory free. +void mem_free_anima(Anima** anima) { mempool_free(&mem.animas, anima); } +void mem_free_animation(Animation** animation) { + mempool_free(&mem.animations, animation); +} void mem_free_camera(SceneCamera** camera) { mempool_free(&mem.cameras, camera); } +void mem_free_joint(Joint** joint) { mempool_free(&mem.joints, joint); } void mem_free_light(Light** light) { mempool_free(&mem.lights, light); } void mem_free_material(Material** material) { mempool_free(&mem.materials, material); @@ -137,11 +177,23 @@ void mem_free_object(SceneObject** object) { mempool_free(&mem.objects, object); } void mem_free_scene(Scene** scene) { mempool_free(&mem.scenes, scene); } +void mem_free_skeleton(Skeleton** skeleton) { + mempool_free(&mem.skeletons, skeleton); +} // Query by index. +Anima* mem_get_anima(anima_idx index) { + return mempool_get_block(&mem.animas, index.val); +} +Animation* mem_get_animation(animation_idx index) { + return mempool_get_block(&mem.animations, index.val); +} SceneCamera* mem_get_camera(camera_idx index) { return mempool_get_block(&mem.cameras, index.val); } +Joint* mem_get_joint(joint_idx index) { + return mempool_get_block(&mem.joints, index.val); +} Light* mem_get_light(light_idx index) { return mempool_get_block(&mem.lights, index.val); } @@ -163,11 +215,24 @@ SceneObject* mem_get_object(object_idx index) { Scene* mem_get_scene(scene_idx index) { return mempool_get_block(&mem.scenes, index.val); } +Skeleton* mem_get_skeleton(skeleton_idx index) { + return mempool_get_block(&mem.skeletons, index.val); +} // Map object to index. +anima_idx mem_get_anima_index(const Anima* anima) { + return (anima_idx){.val = mempool_get_block_index(&mem.animas, anima)}; +} +animation_idx mem_get_animation_index(const Animation* animation) { + return (animation_idx){ + .val = mempool_get_block_index(&mem.animations, animation)}; +} camera_idx mem_get_camera_index(const SceneCamera* camera) { return (camera_idx){.val = mempool_get_block_index(&mem.cameras, camera)}; } +joint_idx mem_get_joint_index(const Joint* joint) { + return (joint_idx){.val = mempool_get_block_index(&mem.joints, joint)}; +} light_idx mem_get_light_index(const Light* light) { return (light_idx){.val = mempool_get_block_index(&mem.lights, light)}; } @@ -191,3 +256,7 @@ object_idx mem_get_object_index(const SceneObject* object) { scene_idx mem_get_scene_index(const Scene* scene) { return (scene_idx){.val = mempool_get_block_index(&mem.scenes, scene)}; } +skeleton_idx mem_get_skeleton_index(const Skeleton* skeleton) { + return (skeleton_idx){ + .val = mempool_get_block_index(&mem.skeletons, skeleton)}; +} diff --git a/gfx/src/scene/scene_memory.h b/gfx/src/scene/scene_memory.h index bd2c691..197e4ca 100644 --- a/gfx/src/scene/scene_memory.h +++ b/gfx/src/scene/scene_memory.h @@ -1,8 +1,12 @@ /// Memory management of scene objects. #pragma once +#include "animation_impl.h" #include "types.h" +typedef struct Anima Anima; +typedef struct Animation Animation; +typedef struct Joint Joint; typedef struct Light Light; typedef struct Material Material; typedef struct Mesh Mesh; @@ -11,6 +15,7 @@ typedef struct SceneCamera SceneCamera; typedef struct SceneNode SceneNode; typedef struct SceneObject SceneObject; typedef struct Scene Scene; +typedef struct Skeleton Skeleton; /// Initialize scene memory. /// @@ -25,9 +30,18 @@ void scene_mem_destroy(); /// Memory allocation. /// ---------------------------------------------------------------------------- +/// Allocate space for an anima. +Anima* mem_alloc_anima(); + +/// Allocate space for an animation. +Animation* mem_alloc_animation(); + /// Allocate space for a camera. SceneCamera* mem_alloc_camera(); +/// Allocate space for a joint. +Joint* mem_alloc_joint(); + /// Allocate space for a light. Light* mem_alloc_light(); @@ -49,9 +63,21 @@ SceneObject* mem_alloc_object(); /// Allocate space for a scene. Scene* mem_alloc_scene(); +/// Allocate space for a skeleton. +Skeleton* mem_alloc_skeleton(); + +/// Free the anima. +void mem_free_anima(Anima**); + +/// Free the animation. +void mem_free_animation(Animation**); + /// Free the camera. void mem_free_camera(SceneCamera**); +/// Free the joint. +void mem_free_joint(Joint**); + /// Free the light. void mem_free_light(Light**); @@ -73,13 +99,25 @@ void mem_free_object(SceneObject**); /// Free the scene. void mem_free_scene(Scene**); +/// Free the skeleton. +void mem_free_skeleton(Skeleton**); + /// ---------------------------------------------------------------------------- /// Query objects by index. /// ---------------------------------------------------------------------------- +/// Get an anima by index. +Anima* mem_get_anima(anima_idx); + +/// Get an animation by index. +Animation* mem_get_animation(animation_idx); + /// Get a camera by index. SceneCamera* mem_get_camera(camera_idx); +/// Get a joint by index. +Joint* mem_get_joint(joint_idx); + /// Get a light by index. Light* mem_get_light(light_idx); @@ -101,13 +139,25 @@ SceneObject* mem_get_object(object_idx); /// Get a scene by index. Scene* mem_get_scene(scene_idx); +/// Get a skeleton by index. +Skeleton* mem_get_skeleton(skeleton_idx); + /// ---------------------------------------------------------------------------- /// Map objects to indices. /// ---------------------------------------------------------------------------- +/// Get the anima's index. +anima_idx mem_get_anima_index(const Anima*); + +/// Get the animation's index. +animation_idx mem_get_animation_index(const Animation*); + /// Get the camera's index. camera_idx mem_get_camera_index(const SceneCamera*); +/// Get the joint's index. +joint_idx mem_get_joint_index(const Joint*); + /// Get the light's index. light_idx mem_get_light_index(const Light*); @@ -128,3 +178,6 @@ object_idx mem_get_object_index(const SceneObject*); /// Get the scene's index. scene_idx mem_get_scene_index(const Scene*); + +/// Get the skeleton's index. +skeleton_idx mem_get_skeleton_index(const Skeleton*); diff --git a/gfx/src/scene/types.h b/gfx/src/scene/types.h index 1c52991..cd87430 100644 --- a/gfx/src/scene/types.h +++ b/gfx/src/scene/types.h @@ -1,21 +1,24 @@ +/// Strongly-typed indices for every kind of scene node resource. #pragma once #include typedef uint16_t gfx_idx; -// Strongly-typed indices. - -#define DEF_STRONG_INDEX(TYPE_NAME) \ - typedef struct TYPE_NAME##_idx { \ - gfx_idx val; \ +#define DEF_STRONG_INDEX(TYPE_NAME, IDX_TYPE) \ + typedef struct TYPE_NAME##_idx { \ + IDX_TYPE val; \ } TYPE_NAME##_idx; -DEF_STRONG_INDEX(camera) -DEF_STRONG_INDEX(light) -DEF_STRONG_INDEX(material) -DEF_STRONG_INDEX(mesh) -DEF_STRONG_INDEX(mesh_link) -DEF_STRONG_INDEX(node) -DEF_STRONG_INDEX(object) -DEF_STRONG_INDEX(scene) +DEF_STRONG_INDEX(anima, gfx_idx) +DEF_STRONG_INDEX(animation, gfx_idx) +DEF_STRONG_INDEX(camera, gfx_idx) +DEF_STRONG_INDEX(joint, 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(node, gfx_idx) +DEF_STRONG_INDEX(object, gfx_idx) +DEF_STRONG_INDEX(scene, gfx_idx) +DEF_STRONG_INDEX(skeleton, gfx_idx) diff --git a/gfx/src/util/scene.c b/gfx/src/util/scene.c index 87d775c..49e661d 100644 --- a/gfx/src/util/scene.c +++ b/gfx/src/util/scene.c @@ -84,6 +84,7 @@ #include #include #include +#include #include #include #include @@ -97,6 +98,7 @@ #include #include #include +#include #include #include #include @@ -138,6 +140,8 @@ #define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" #define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" #define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" +#define DEFINE_HAS_JOINTS "HAS_JOINTS" +#define DEFINE_MAX_JOINTS "MAX_JOINTS" typedef enum TextureType { BaseColorTexture, @@ -156,6 +160,8 @@ typedef struct MeshPermutation { bool has_texcoords : 1; bool has_normals : 1; bool has_tangents : 1; + bool has_joints : 1; + bool has_weights : 1; // Textures. bool has_albedo_map : 1; bool has_metallic_roughness_map : 1; @@ -179,16 +185,22 @@ static size_t make_defines( defines[next].value = sstring_make(str_true); \ next++; \ } - check(has_texcoords, DEFINE_HAS_TEXCOORDS); check(has_normals, DEFINE_HAS_NORMALS); check(has_tangents, DEFINE_HAS_TANGENTS); + check(has_joints, DEFINE_HAS_JOINTS); check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP); check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP); check(has_normal_map, DEFINE_HAS_NORMAL_MAP); check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP); check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP); + if (perm.has_joints) { + defines[next].name = sstring_make(DEFINE_MAX_JOINTS); + defines[next].value = sstring_itoa(GFX_MAX_NUM_JOINTS); + next++; + } + return next; } @@ -197,11 +209,13 @@ static ShaderProgram* make_shader_permutation( RenderBackend* render_backend, MeshPermutation perm) { LOGD( "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " - "%d, tangents: %d, albedo map: %d, metallic-roughness map: %d, normal " + "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, " + "metallic-roughness map: " + "%d, normal " "map: %d, AO map: %d, emissive map: %d", - perm.has_texcoords, perm.has_normals, perm.has_tangents, - perm.has_albedo_map, perm.has_metallic_roughness_map, perm.has_normal_map, - perm.has_occlusion_map, perm.has_emissive_map); + perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints, + perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map, + perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map); ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; const size_t num_defines = make_defines(perm, defines); @@ -211,7 +225,7 @@ static ShaderProgram* make_shader_permutation( /// Map a texture type to the name of the shader uniform used to access the /// texture. -static const char* TextureUniformName(TextureType type) { +static const char* get_texture_uniform_name(TextureType type) { switch (type) { case BaseColorTexture: return UNIFORM_BASE_COLOR_TEXTURE; @@ -223,10 +237,9 @@ static const char* TextureUniformName(TextureType type) { return UNIFORM_AMBIENT_OCCLUSION_TEXTURE; case NormalMap: return UNIFORM_NORMAL_MAP; - default: - assert(false); - return 0; } + assert(false); + return 0; } /// Map a glTF primitive type to a gfx primitive type. @@ -238,13 +251,261 @@ static PrimitiveType from_gltf_primitive_type(cgltf_primitive_type type) { return TriangleFan; case cgltf_primitive_type_triangle_strip: return TriangleStrip; - default: - LOGE("Unsupported primitive type: %d", type); + // Not yet implemented. + case cgltf_primitive_type_lines: + case cgltf_primitive_type_line_loop: + case cgltf_primitive_type_line_strip: + case cgltf_primitive_type_points: + break; + } + LOGE("Unsupported primitive type: %d", type); + assert(false); + return 0; +} + +/// Map a glTF animation path type to its Gfx equivalent. +static ChannelType from_gltf_animation_path_type( + cgltf_animation_path_type type) { + switch (type) { + case cgltf_animation_path_type_translation: + return TranslationChannel; + case cgltf_animation_path_type_rotation: + return RotationChannel; + case cgltf_animation_path_type_scale: + return ScaleChannel; + case cgltf_animation_path_type_weights: + return WeightsChannel; + case cgltf_animation_path_type_invalid: assert(false); - return 0; + break; + } + assert(false); + return 0; +} + +/// Map a glTF interpolation to its Gfx equivalent. +static AnimationInterpolation from_gltf_interpolation_type( + cgltf_interpolation_type type) { + switch (type) { + case cgltf_interpolation_type_linear: + return LinearInterpolation; + case cgltf_interpolation_type_step: + return StepInterpolation; + case cgltf_interpolation_type_cubic_spline: + return CubicSplineInterpolation; } + assert(false); + return 0; } +/// Return the component's size in bytes. +static cgltf_size get_component_size(cgltf_component_type type) { + switch (type) { + case cgltf_component_type_r_8: + return 1; + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + return 2; + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + return 4; + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + assert(false); + break; + } + assert(false); + return 0; +} + +/// Read a float from the given data pointer and accessor. +/// +/// This function uses the normalization equations from the spec. See the +/// animation section: +/// +/// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations +static float read_float(const void* data, const cgltf_accessor* accessor) { + assert(data); + assert(accessor); + + switch (accessor->component_type) { + case cgltf_component_type_r_8: { + assert(accessor->normalized); + const int8_t c = *((int8_t*)data); + return max((float)c / 127.0, -1.0); + } + case cgltf_component_type_r_8u: { + assert(accessor->normalized); + const uint8_t c = *((uint8_t*)data); + return (float)c / 255.0; + } + case cgltf_component_type_r_16: { + assert(accessor->normalized); + const int16_t c = *((int16_t*)data); + return max((float)c / 32767.0, -1.0); + } + case cgltf_component_type_r_16u: { + assert(accessor->normalized); + const uint16_t c = *((uint16_t*)data); + return (float)c / 65535.0; + } + case cgltf_component_type_r_32u: { + assert(accessor->normalized); + const uint32_t c = *((uint32_t*)data); + return (float)c / 4294967295.0; + } + case cgltf_component_type_r_32f: { + const float c = *((float*)data); + return c; + } + case cgltf_component_type_invalid: + assert(false); + break; + } + assert(false); + return 0; +} + +/// Iterate over the vectors in an accessor. +#define ACCESSOR_FOREACH_VEC(dimensions, accessor, body) \ + { \ + assert((1 <= dimensions) && (dimensions <= 4)); \ + assert(!(dimensions == 1) || (accessor->type == cgltf_type_scalar)); \ + assert(!(dimensions == 2) || (accessor->type == cgltf_type_vec2)); \ + assert(!(dimensions == 3) || (accessor->type == cgltf_type_vec3)); \ + assert(!(dimensions == 4) || (accessor->type == cgltf_type_vec4)); \ + const cgltf_buffer_view* view = accessor->buffer_view; \ + const cgltf_buffer* buffer = view->buffer; \ + const cgltf_size offset = accessor->offset + view->offset; \ + const cgltf_size comp_size = get_component_size(accessor->component_type); \ + const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ + assert( \ + (offset + accessor->count * dimensions * comp_size) < buffer->size); \ + /* From the spec: */ \ + /* "Buffer views with other types of data MUST NOT not define */ \ + /* byteStride (unless such layout is explicitly enabled by an */ \ + /* extension)."*/ \ + assert(view->stride == 0); \ + assert(accessor->stride == dimensions * comp_size); \ + cgltf_float x = 0, y = 0, z = 0, w = 0; \ + /* Silence unused variable warnings. */ \ + (void)y; \ + (void)z; \ + (void)w; \ + /* The {component type} X {dimensions} combinations are a pain to handle. \ + For floats, we switch on type first and then lay out a loop for each \ + dimension to get a tight loop with a possibly inlined body. For other \ + types, we take the performance hit and perform checks and conversions \ + inside the loop for simplicity. */ \ + if (accessor->component_type == cgltf_component_type_r_32f) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + switch (dimensions) { \ + case 1: \ + assert(accessor->type == cgltf_type_scalar); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + x = *floats++; \ + body; \ + } \ + break; \ + case 2: \ + assert(accessor->type == cgltf_type_vec2); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + x = *floats++; \ + y = *floats++; \ + body; \ + } \ + break; \ + case 3: \ + assert(accessor->type == cgltf_type_vec3); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + x = *floats++; \ + y = *floats++; \ + z = *floats++; \ + body; \ + } \ + break; \ + case 4: \ + assert(accessor->type == cgltf_type_vec4); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + x = *floats++; \ + y = *floats++; \ + z = *floats++; \ + w = *floats++; \ + body; \ + } \ + break; \ + } \ + } else { \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + x = read_float(bytes, accessor); \ + bytes += accessor->stride; \ + if (dimensions > 1) { \ + y = read_float(bytes, accessor); \ + bytes += accessor->stride; \ + } \ + if (dimensions > 2) { \ + z = read_float(bytes, accessor); \ + bytes += accessor->stride; \ + } \ + if (dimensions > 3) { \ + w = read_float(bytes, accessor); \ + bytes += accessor->stride; \ + } \ + body; \ + } \ + } \ + } + +/// Iterate over the matrices in an accessor. +#define ACCESSOR_FOREACH_MAT(dimensions, accessor, body) \ + { \ + assert((2 <= dimensions) && (dimensions <= 4)); \ + assert(!(dimensions == 2) || (accessor->type == cgltf_type_mat2)); \ + assert(!(dimensions == 3) || (accessor->type == cgltf_type_mat3)); \ + assert(!(dimensions == 4) || (accessor->type == cgltf_type_mat4)); \ + const cgltf_buffer_view* view = accessor->buffer_view; \ + const cgltf_buffer* buffer = view->buffer; \ + const cgltf_size offset = accessor->offset + view->offset; \ + const cgltf_size comp_size = get_component_size(accessor->component_type); \ + const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ + assert( \ + (offset + accessor->count * dimensions * comp_size) < buffer->size); \ + /* From the spec: */ \ + /* "Buffer views with other types of data MUST NOT not define */ \ + /* byteStride (unless such layout is explicitly enabled by an */ \ + /* extension)."*/ \ + assert(view->stride == 0); \ + assert(accessor->stride == dimensions * dimensions * comp_size); \ + assert(accessor->component_type == cgltf_component_type_r_32f); \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + switch (dimensions) { \ + case 2: \ + assert(accessor->type == cgltf_type_mat2); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + body; \ + floats += 4; \ + } \ + break; \ + case 3: \ + assert(accessor->type == cgltf_type_mat3); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + body; \ + floats += 9; \ + } \ + break; \ + case 4: \ + assert(accessor->type == cgltf_type_mat4); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + body; \ + floats += 16; \ + } \ + break; \ + } \ + } + /// Return the total number of primitives in the scene. Each mesh may contain /// multiple primitives. /// @@ -261,8 +522,11 @@ static size_t get_total_primitives(const cgltf_data* data) { /// /// If buffer data is loaded from memory, set filepath = null. /// -/// Return an array of Buffers such that the index of each glTF buffer in -/// the original array matches the same Buffer in the resulting array. +/// Return an array of Buffers such that the index of each glTF buffer in the +/// original array matches the same Buffer in the resulting array. +/// +/// TODO: There is no need to load the inverse bind matrices buffer into the +/// GPU. Might need to lazily load buffers. static bool load_buffers( const cgltf_data* data, RenderBackend* render_backend, Buffer** buffers) { assert(data); @@ -411,21 +675,21 @@ static bool load_texture_and_uniform( LOGD( "Load texture: %s (mipmaps: %d, filtering: %d)", - mstring_cstring(&cmd->data.texture.filepath), cmd->mipmaps, + mstring_cstr(&cmd->data.texture.filepath), cmd->mipmaps, cmd->filtering); textures[texture_index] = gfx_load_texture(render_backend, cmd); if (!textures[texture_index]) { gfx_prepend_error( "Failed to load texture: %s", - mstring_cstring(&cmd->data.texture.filepath)); + mstring_cstr(&cmd->data.texture.filepath)); return false; } } assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc->uniforms[(*next_uniform)++] = (ShaderUniform){ - .name = sstring_make(TextureUniformName(texture_type)), + .name = sstring_make(get_texture_uniform_name(texture_type)), .type = UniformTexture, .value.texture = textures[texture_index]}; @@ -627,9 +891,12 @@ static bool load_meshes( assert(buffer_index < data->buffers_count); const Buffer* buffer = buffers[buffer_index]; - BufferView2d* buffer_view_2d = 0; - BufferView3d* buffer_view_3d = 0; - BufferView4d* buffer_view_4d = 0; + BufferView2d* buffer_view_2d = 0; + BufferView3d* buffer_view_3d = 0; + BufferView4d* buffer_view_4d = 0; + BufferViewFloat* buffer_view_float = 0; + BufferViewU8* buffer_view_u8 = 0; + BufferViewU16* buffer_view_u16 = 0; switch (attrib->type) { case cgltf_attribute_type_position: { @@ -665,29 +932,66 @@ static bool load_meshes( buffer_view_2d = &geometry_desc.texcoords; perm.has_texcoords = true; break; - default: - // Attribute ignored. + case cgltf_attribute_type_color: + // TODO: Add support for color. + break; + case cgltf_attribute_type_joints: + // Joints can be either u8 or u16. + switch (accessor->component_type) { + case cgltf_component_type_r_8u: + buffer_view_u8 = &geometry_desc.joints.u8; + break; + case cgltf_component_type_r_16u: + buffer_view_u16 = &geometry_desc.joints.u16; + break; + default: + assert(false); + return false; + } + perm.has_joints = true; + break; + case cgltf_attribute_type_weights: + // Weights can be either u8, u16, or float. + switch (accessor->component_type) { + case cgltf_component_type_r_8u: + buffer_view_u8 = &geometry_desc.weights.u8; + break; + case cgltf_component_type_r_16u: + buffer_view_u16 = &geometry_desc.weights.u16; + break; + case cgltf_component_type_r_32f: + buffer_view_float = &geometry_desc.weights.floats; + break; + default: + assert(false); + return false; + } + perm.has_weights = true; + break; + case cgltf_attribute_type_invalid: + assert(false); break; } - if (buffer_view_2d) { - buffer_view_2d->buffer = buffer; - buffer_view_2d->offset_bytes = offset; - buffer_view_2d->size_bytes = view->size; - buffer_view_2d->stride_bytes = view->stride; - } else if (buffer_view_3d) { - buffer_view_3d->buffer = buffer; - buffer_view_3d->offset_bytes = offset; - buffer_view_3d->size_bytes = view->size; - buffer_view_3d->stride_bytes = view->stride; - } else if (buffer_view_4d) { - buffer_view_4d->buffer = buffer; - buffer_view_4d->offset_bytes = offset; - buffer_view_4d->size_bytes = view->size; - buffer_view_4d->stride_bytes = view->stride; - } +#define CONFIGURE_BUFFER(buf) \ + if (buf) { \ + buf->buffer = buffer; \ + buf->offset_bytes = offset; \ + buf->size_bytes = view->size; \ + buf->stride_bytes = view->stride; \ + } + CONFIGURE_BUFFER(buffer_view_2d); + CONFIGURE_BUFFER(buffer_view_3d); + CONFIGURE_BUFFER(buffer_view_4d); + CONFIGURE_BUFFER(buffer_view_u8); + CONFIGURE_BUFFER(buffer_view_u16); + CONFIGURE_BUFFER(buffer_view_float); } // Vertex attributes. + assert( + (perm.has_joints && perm.has_weights) || + (!perm.has_joints && !perm.has_weights)); + // If the mesh primitive has no tangents, see if they were computed // separately. if (!geometry_desc.tangents.buffer) { @@ -712,23 +1016,23 @@ static bool load_meshes( (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + (geometry_desc.positions3d.size_bytes / sizeof(vec3)); +#define CHECK_COUNT(buffer_view, type, num_components) \ + if (geometry_desc.buffer_view.buffer) { \ + assert( \ + (geometry_desc.buffer_view.size_bytes / \ + (num_components * sizeof(type))) == geometry_desc.num_verts); \ + } + // Check that the number of vertices is consistent across all vertex // attributes. - if (geometry_desc.normals.buffer) { - assert( - (geometry_desc.normals.size_bytes / sizeof(vec3)) == - geometry_desc.num_verts); - } - if (geometry_desc.tangents.buffer) { - assert( - (geometry_desc.tangents.size_bytes / sizeof(vec4)) == - geometry_desc.num_verts); - } - if (geometry_desc.texcoords.buffer) { - assert( - (geometry_desc.texcoords.size_bytes / sizeof(vec2)) == - geometry_desc.num_verts); - } + CHECK_COUNT(normals, vec3, 1); + CHECK_COUNT(tangents, vec4, 1); + CHECK_COUNT(texcoords, vec2, 1); + CHECK_COUNT(joints.u8, uint8_t, 4); + CHECK_COUNT(joints.u16, uint16_t, 4); + CHECK_COUNT(weights.u8, uint8_t, 4); + CHECK_COUNT(weights.u16, uint16_t, 4); + CHECK_COUNT(weights.floats, float, 4); const cgltf_size material_index = prim->material - data->materials; assert(material_index < data->materials_count); @@ -774,13 +1078,117 @@ static bool load_meshes( return true; } +/// Load all skins (Gfx skeletons) from the glTF scene. +static void load_skins( + const cgltf_data* data, Buffer* const* buffers, SceneNode** nodes, + SkeletonDesc* descs) { + assert(data); + assert(buffers); + assert(nodes); + assert(descs); + + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices; + assert(matrices_accessor->count == skin->joints_count); + SkeletonDesc* desc = &descs[s]; + + *desc = (SkeletonDesc){.num_joints = skin->joints_count}; + + assert(skin->joints_count < GFX_MAX_NUM_JOINTS); + // for (cgltf_size j = 0; j < skin->joints_count; ++j) { + ACCESSOR_FOREACH_MAT(4, matrices_accessor, { + const mat4 inv_bind_matrix = mat4_from_array(floats); + + const cgltf_size node_index = skin->joints[i] - data->nodes; + assert(node_index < data->nodes_count); + SceneNode* node = nodes[node_index]; + + // Transform the node into a joint node. + const JointDesc joint_desc = + (JointDesc){.inv_bind_matrix = inv_bind_matrix}; + Joint* joint = gfx_make_joint(&joint_desc); + gfx_construct_joint_node(node, joint); + + desc->joints[i] = node; + }); + + // glTF may specify a "skeleton", which is the root of the skeleton's node + // hierarchy. TODO: We could use this root node to optimize the descend in + // joint matrix computation. The root node should be passed with the + // AnimaDesc. + // if (skin->skeleton) { + // cgltf_size root_index = skin->skeleton - data->nodes; + // assert(root_index <= data->nodes_count); + // root_node = nodes[root_index]; + // } + } +} + +/// Load all animations from the glTF scene. +static void load_animations( + const cgltf_data* data, SceneNode** nodes, AnimationDesc* descs) { + assert(data); + assert(nodes); + assert(descs); + + for (cgltf_size a = 0; a < data->animations_count; ++a) { + const cgltf_animation* animation = &data->animations[a]; + AnimationDesc* animation_desc = &descs[a]; + + *animation_desc = (AnimationDesc){ + .name = sstring_make(animation->name), + .num_channels = animation->channels_count}; + + assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS); + for (cgltf_size c = 0; c < animation->channels_count; ++c) { + const cgltf_animation_channel* channel = &animation->channels[c]; + ChannelDesc* channel_desc = &animation_desc->channels[c]; + const cgltf_animation_sampler* sampler = channel->sampler; + const size_t node_index = channel->target_node - data->nodes; + assert(node_index < data->nodes_count); + SceneNode* target = nodes[node_index]; + assert(target); + + *channel_desc = (ChannelDesc){ + .target = target, + .type = from_gltf_animation_path_type(channel->target_path), + .interpolation = from_gltf_interpolation_type(sampler->interpolation), + .num_keyframes = 0}; + + // Read time inputs. + ACCESSOR_FOREACH_VEC(1, sampler->input, { + channel_desc->keyframes[i].time = x; + channel_desc->num_keyframes++; + }); + + // Read transform outputs. + switch (channel->target_path) { + case cgltf_animation_path_type_translation: + ACCESSOR_FOREACH_VEC(3, sampler->output, { + channel_desc->keyframes[i].translation = vec3_make(x, y, z); + }); + break; + case cgltf_animation_path_type_rotation: + ACCESSOR_FOREACH_VEC(4, sampler->output, { + channel_desc->keyframes[i].rotation = qmake(x, y, z, w); + }); + break; + default: + // TODO: Handle other channel transformations. + break; + } + } + } +} + /// Load all nodes from the glTF scene. /// /// This function ignores the many scenes and default scene of the glTF spec /// and instead just loads all nodes into a single gfx Scene. static void load_nodes( const cgltf_data* data, SceneNode* root_node, SceneObject** objects, - SceneCamera** cameras, SceneNode** nodes) { + SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a // disjount union of strict trees: // @@ -796,12 +1204,11 @@ static void load_nodes( assert(root_node); assert(objects); assert(cameras); + assert(anima); assert(nodes); cgltf_size next_camera = 0; - // First pass: create all nodes with no hierarchy just yet. We cannot build - // the hierarchy if we do not have a node pointer to connect. for (cgltf_size n = 0; n < data->nodes_count; ++n) { const cgltf_node* node = &data->nodes[n]; @@ -810,7 +1217,15 @@ static void load_nodes( if (node->mesh) { const cgltf_size mesh_index = node->mesh - data->meshes; assert(mesh_index < data->meshes_count); - nodes[n] = gfx_make_object_node(objects[mesh_index]); + SceneObject* object = objects[mesh_index]; + + gfx_construct_object_node(nodes[n], object); + if (node->skin) { + const cgltf_size skin_index = node->skin - data->skins; + assert(skin_index < data->skins_count); + const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); + gfx_set_object_skeleton(object, skeleton); + } } else if (node->camera) { assert(next_camera < data->cameras_count); @@ -836,16 +1251,11 @@ static void load_nodes( } gfx_set_camera_camera(cameras[next_camera], &camera); - nodes[n] = gfx_make_camera_node(cameras[next_camera]); + gfx_construct_camera_node(nodes[n], cameras[next_camera]); ++next_camera; } else { // TODO: implementation for missing node types. - // Create a vanilla SceneNode for unhandled node types for the sake of - // being able to complete the hierarchy. Otherwise, wiring nodes in pass 2 - // fails. - LOGW("Unhandled node type, creating vanilla SceneNode for hierarchy " - "completeness"); - nodes[n] = gfx_make_node(); + // These nodes currently default to logical nodes. } assert(nodes[n]); @@ -876,22 +1286,14 @@ static void load_nodes( // given root node. if (!node->parent) { gfx_set_node_parent(nodes[n], root_node); + } else { + const cgltf_size parent_index = node->parent - data->nodes; + assert(parent_index < data->nodes_count); + SceneNode* parent = nodes[parent_index]; + assert(parent); + gfx_set_node_parent(nodes[n], parent); } } // SceneNode. - - // Second pass: wire nodes together to build the hierarchy. - for (cgltf_size n = 0; n < data->nodes_count; ++n) { - const cgltf_node* parent = &data->nodes[n]; - SceneNode* parent_scene_node = nodes[n]; - - for (cgltf_size c = 0; c < parent->children_count; ++c) { - const cgltf_size child_index = parent->children[c] - data->nodes; - assert(child_index < data->nodes_count); - SceneNode* child_scene_node = nodes[child_index]; - assert(child_scene_node); - gfx_set_node_parent(child_scene_node, parent_scene_node); - } - } } /// Load all scenes from the glTF file into the given gfx Scene. @@ -900,7 +1302,7 @@ static void load_nodes( /// /// This function ignores the many scenes and default scene of the glTF spec /// and instead just loads all scenes into a single gfx Scene. -static bool load_scene( +static SceneNode* load_scene( cgltf_data* data, Gfx* gfx, SceneNode* root_node, const char* filepath, ShaderProgram* shader, const cgltfTangentBuffer* cgltf_tangent_buffers, cgltf_size num_tangent_buffers) { @@ -915,16 +1317,23 @@ static bool load_scene( // an error is encountered, the helper functions can simply return and this // function cleans up any intermediate objects that had been created up until // the point of failure. + // + // Loading animation data: + // - Buffers with animation sampler data need to stay on the CPU, not + // uploaded to the GPU. We could try to implement GPU animation at a later + // stage. assert(data); assert(gfx); assert(root_node); + bool success = false; + RenderBackend* render_backend = gfx_get_render_backend(gfx); const size_t primitive_count = get_total_primitives(data); const mstring directory = mstring_dirname(mstring_make(filepath)); LOGD("Filepath: %s", filepath); - LOGD("Directory: %s", mstring_cstring(&directory)); + LOGD("Directory: %s", mstring_cstr(&directory)); Buffer** tangent_buffers = 0; Buffer** buffers = 0; @@ -933,9 +1342,12 @@ static bool load_scene( Material** materials = 0; Geometry** geometries = 0; Mesh** meshes = 0; + AnimaDesc* anima_desc = 0; SceneObject** scene_objects = 0; SceneCamera** scene_cameras = 0; SceneNode** scene_nodes = 0; + Anima* anima = 0; + SceneNode* anima_node = 0; tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*)); buffers = calloc(data->buffers_count, sizeof(Buffer*)); @@ -943,6 +1355,7 @@ static bool load_scene( materials = calloc(data->materials_count, sizeof(Material*)); geometries = calloc(primitive_count, sizeof(Geometry*)); meshes = calloc(primitive_count, sizeof(Mesh*)); + anima_desc = calloc(1, sizeof(AnimaDesc)); scene_objects = calloc(data->meshes_count, sizeof(SceneObject*)); scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**)); scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**)); @@ -972,7 +1385,7 @@ static bool load_scene( if (data->textures_count > 0) { load_textures_lazy( - data, render_backend, mstring_cstring(&directory), load_texture_cmds); + data, render_backend, mstring_cstr(&directory), load_texture_cmds); } if (!load_materials( @@ -987,23 +1400,49 @@ static bool load_scene( goto cleanup; } - load_nodes(data, root_node, scene_objects, scene_cameras, scene_nodes); + // Skins refer to nodes, and nodes may refer to skins. To break this circular + // dependency, glTF defines skins in terms of node indices. We could do the + // same if Gfx allowed allocating nodes contiguously in memory. For now, + // create the nodes up front and use the indices of the array to map to the + // node_idx. + for (cgltf_size i = 0; i < data->nodes_count; ++i) { + scene_nodes[i] = gfx_make_node(); + } - return true; + anima_node = gfx_make_node(); + load_skins(data, buffers, scene_nodes, anima_desc->skeletons); + load_animations(data, scene_nodes, anima_desc->animations); + anima_desc->num_skeletons = data->skins_count; + anima_desc->num_animations = data->animations_count; + anima = gfx_make_anima(anima_desc); + gfx_construct_anima_node(anima_node, anima); + gfx_set_node_parent(anima_node, root_node); + + // The anima node becomes the root of all scene nodes. + load_nodes( + data, anima_node, scene_objects, scene_cameras, anima, scene_nodes); + + success = anima_node != 0; cleanup: + // The arrays of resources are no longer needed. The resources themselves are + // destroyed only if this function fails. if (tangent_buffers) { - for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { - if (tangent_buffers[i]) { - gfx_destroy_buffer(render_backend, &tangent_buffers[i]); + if (!success) { + for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { + if (tangent_buffers[i]) { + gfx_destroy_buffer(render_backend, &tangent_buffers[i]); + } } } free(tangent_buffers); } if (buffers) { - for (cgltf_size i = 0; i < data->buffers_count; ++i) { - if (buffers[i]) { - gfx_destroy_buffer(render_backend, &buffers[i]); + if (!success) { + for (cgltf_size i = 0; i < data->buffers_count; ++i) { + if (buffers[i]) { + gfx_destroy_buffer(render_backend, &buffers[i]); + } } } free(buffers); @@ -1012,70 +1451,93 @@ cleanup: free(load_texture_cmds); } if (textures) { - for (cgltf_size i = 0; i < data->textures_count; ++i) { - if (textures[i]) { - gfx_destroy_texture(render_backend, &textures[i]); + if (!success) { + for (cgltf_size i = 0; i < data->textures_count; ++i) { + if (textures[i]) { + gfx_destroy_texture(render_backend, &textures[i]); + } } } free(textures); } if (materials) { - for (cgltf_size i = 0; i < data->materials_count; ++i) { - if (materials[i]) { - gfx_destroy_material(&materials[i]); + if (!success) { + for (cgltf_size i = 0; i < data->materials_count; ++i) { + if (materials[i]) { + gfx_destroy_material(&materials[i]); + } } } free(materials); } if (geometries) { - for (size_t i = 0; i < primitive_count; ++i) { - if (geometries[i]) { - gfx_destroy_geometry(render_backend, &geometries[i]); + if (!success) { + for (size_t i = 0; i < primitive_count; ++i) { + if (geometries[i]) { + gfx_destroy_geometry(render_backend, &geometries[i]); + } } } free(geometries); } if (meshes) { - for (size_t i = 0; i < primitive_count; ++i) { - if (meshes[i]) { - gfx_destroy_mesh(&meshes[i]); + if (!success) { + for (size_t i = 0; i < primitive_count; ++i) { + if (meshes[i]) { + gfx_destroy_mesh(&meshes[i]); + } } } free(meshes); } + if (anima_desc) { + free(anima_desc); + } if (scene_objects) { - for (cgltf_size i = 0; i < data->meshes_count; ++i) { - if (scene_objects[i]) { - gfx_destroy_object(&scene_objects[i]); + if (!success) { + for (cgltf_size i = 0; i < data->meshes_count; ++i) { + if (scene_objects[i]) { + gfx_destroy_object(&scene_objects[i]); + } } } free(scene_objects); } if (scene_cameras) { - for (cgltf_size i = 0; i < data->cameras_count; ++i) { - if (scene_cameras[i]) { - gfx_destroy_camera(&scene_cameras[i]); + if (!success) { + for (cgltf_size i = 0; i < data->cameras_count; ++i) { + if (scene_cameras[i]) { + gfx_destroy_camera(&scene_cameras[i]); + } } } free(scene_cameras); } if (scene_nodes) { - for (cgltf_size i = 0; i < data->nodes_count; ++i) { - if (scene_nodes[i]) { - gfx_destroy_node(&scene_nodes[i]); + if (!success) { + for (cgltf_size i = 0; i < data->nodes_count; ++i) { + if (scene_nodes[i]) { + gfx_destroy_node(&scene_nodes[i]); + } } } free(scene_nodes); } - return false; + if (!success && anima_node) { + gfx_destroy_node(&anima_node); // Node owns the anima. + } else if (!success && anima) { + gfx_destroy_anima(&anima); + } + return anima_node; } -bool gfx_load_scene(Gfx* gfx, SceneNode* root_node, const LoadSceneCmd* cmd) { +SceneNode* gfx_load_scene( + Gfx* gfx, SceneNode* root_node, const LoadSceneCmd* cmd) { assert(gfx); assert(root_node); assert(cmd); - bool success = false; + SceneNode* scene_node = 0; cgltf_options options = {0}; cgltf_data* data = NULL; @@ -1107,7 +1569,7 @@ bool gfx_load_scene(Gfx* gfx, SceneNode* root_node, const LoadSceneCmd* cmd) { cgltf_compute_tangents( &options, data, &tangent_buffers, &num_tangent_buffers); - success = load_scene( + scene_node = load_scene( data, gfx, root_node, cmd->filepath, cmd->shader, tangent_buffers, num_tangent_buffers); @@ -1118,5 +1580,5 @@ cleanup: if (tangent_buffers) { free(tangent_buffers); } - return success; + return scene_node; } diff --git a/gltfview/src/game.c b/gltfview/src/game.c index 612ec67..1db7cba 100644 --- a/gltfview/src/game.c +++ b/gltfview/src/game.c @@ -89,13 +89,13 @@ static SceneNode* load_skyquad(Game* game) { } /// Load the 3D scene. -static bool load_scene( +static SceneNode* load_scene( Game* game, const char* scene_filepath, const char* view_mode) { assert(game); game->camera = gfx_make_camera(); if (!game->camera) { - return false; + return 0; } Camera* camera = gfx_get_camera_camera(game->camera); // Sponza. @@ -105,7 +105,7 @@ static bool load_scene( SceneNode* sky_light_node = load_skyquad(game); if (!sky_light_node) { - return false; + return 0; } // TODO: Move the debug rendering to the renderer. @@ -114,16 +114,16 @@ static bool load_scene( // return false; // } - if (!gfx_load_scene( - game->gfx, sky_light_node, - &(LoadSceneCmd){ - .origin = SceneFromFile, .filepath = scene_filepath})) { - return false; + SceneNode* scene_node = gfx_load_scene( + game->gfx, sky_light_node, + &(LoadSceneCmd){.origin = SceneFromFile, .filepath = scene_filepath}); + if (!scene_node) { + return 0; } gfx_log_node_hierarchy(gfx_get_scene_root(game->scene)); - return true; + return scene_node; } /// Load a scene for debugging textures. @@ -207,13 +207,23 @@ bool game_new(Game* game, int argc, const char** argv) { goto cleanup; } - if (!load_scene(game, scene_filepath, view_mode)) { + game->root_node = load_scene(game, scene_filepath, view_mode); + if (!game->root_node) { goto cleanup; } /*if (!load_texture_debugger_scene(game)) { goto cleanup; }*/ + Anima* anima = gfx_get_node_anima(game->root_node); + + // const bool play_result = gfx_play_animation( + // anima, &(AnimationPlaySettings){.name = "Defiant stance", .loop = + // false}); + const bool play_result = gfx_play_animation( + anima, &(AnimationPlaySettings){.name = "Walk", .loop = true}); + assert(play_result); + return true; cleanup: @@ -227,12 +237,11 @@ cleanup: void game_end(Game* game) { gfx_destroy(&game->gfx); } void game_update(Game* game, double t, double dt) { - game->elapsed += dt; - while (game->elapsed >= 1.0) { - // LOGD("Tick"); - usleep(1000); - game->elapsed -= 1.0; - } + // LOGD("Tick"); + + Anima* anima = gfx_get_node_anima(game->root_node); + gfx_update_animation(anima, t); + // TODO: Compute bounding boxes to then find a good orbit point and radius // for each scene. const vec3 orbit_point = vec3_make(0, 2, 0); diff --git a/gltfview/src/game.h b/gltfview/src/game.h index 92c0885..4aeb5ea 100644 --- a/gltfview/src/game.h +++ b/gltfview/src/game.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -13,12 +14,12 @@ static const double game_dt = 1.0 / 60.0; /// Game state. typedef struct { - Gfx* gfx; + Gfx* gfx; RenderBackend* render_backend; - Renderer* renderer; - Scene* scene; - SceneCamera* camera; - double elapsed; + Renderer* renderer; + Scene* scene; + SceneCamera* camera; + SceneNode* root_node; } Game; bool game_new(Game*, int argc, const char** argv); -- cgit v1.2.3