3#include "scene/GameObject.h"
5#include <glm/ext/matrix_transform.hpp>
7#include <glm/gtc/type_ptr.hpp>
9#include "graphics/Texture.h"
10#include "graphics/VertexLayout.h"
11#include "render/Material.h"
12#include "render/Mesh.h"
13#include "scene/components/AnimationComponent.h"
14#include "scene/components/MeshComponent.h"
15#define GLM_ENABLE_EXPERIMENTAL
16#include <glm/gtx/matrix_decompose.hpp>
21#define CGLTF_IMPLEMENTATION
27auto ReadScalar = [](cgltf_accessor *acc, cgltf_size index)
30 cgltf_accessor_read_float(acc, index, &res, 1);
34auto ReadVec3 = [](cgltf_accessor *acc, cgltf_size index)
37 cgltf_accessor_read_float(acc, index, glm::value_ptr(res), 3);
41auto ReadQuat = [](cgltf_accessor *acc, cgltf_size index)
43 float res[4] = {0.0f, 0.0f, 0.0f, 1.0f};
44 cgltf_accessor_read_float(acc, index, res, 4);
45 return glm::quat(res[3], res[0], res[1], res[2]);
48auto ReadTimes = [](cgltf_accessor *acc, std::vector<float> &outTimes)
50 outTimes.resize(acc->count);
51 for (cgltf_size i = 0; i < acc->count; ++i)
57auto ReadOutputVec3 = [](cgltf_accessor *acc, std::vector<glm::vec3> &outValues)
59 outValues.resize(acc->count);
60 for (cgltf_size i = 0; i < acc->count; ++i)
66auto ReadOutputQuat = [](cgltf_accessor *acc, std::vector<glm::quat> &outValues)
68 outValues.resize(acc->count);
69 for (cgltf_size i = 0; i < acc->count; ++i)
82 for (
auto &component : m_components)
84 component->Update(deltaTime);
87 for (
auto it = m_children.begin(); it != m_children.end();)
91 (*it)->Update(deltaTime);
95 it = m_children.erase(it);
104 LOG_ERROR(
"AddComponent called with nullptr on '%s'", m_name.c_str());
107 m_components.emplace_back(component);
110 LOG_INFO(
"Component added to '%s' (total=%zu)", m_name.c_str(), m_components.size());
146 return vec3(hom) / hom.w;
154 mat = translate(mat, m_position);
160 mat = mat * mat4_cast(m_rotation);
163 mat = scale(mat, m_scale);
184 mat4 invParentWorld = inverse(parentWorld);
185 vec4 localPos = invParentWorld *
vec4(pos, 1.0F);
198 quat invParentWorldRot = inverse(parentWorldRot);
199 quat newLocalRot = invParentWorldRot * rot;
218auto readFloats = [](
const cgltf_accessor *acc, cgltf_size i,
float *out,
int n)
220 std::fill(out, out + n, 0.0f);
221 return cgltf_accessor_read_float(acc, i, out, n) == 1;
224auto readIndex = [](
const cgltf_accessor *acc, cgltf_size i)
227 cgltf_bool ok = cgltf_accessor_read_uint(acc, i, &out, 1);
228 return ok ?
static_cast<u32>(out) : 0;
235 if (node->has_matrix)
237 auto mat = glm::make_mat4(node->matrix);
238 vec3 translation, scale, skew;
241 decompose(mat, scale, orientation, translation, skew, perspective);
243 object->SetPosition(translation);
244 object->SetRotation(orientation);
245 object->SetScale(scale);
248 if (node->has_translation)
250 object->SetPosition(
vec3(node->translation[0], node->translation[1], node->translation[2]));
252 if (node->has_rotation)
254 object->SetRotation(
quat(node->rotation[3], node->rotation[0], node->rotation[1], node->rotation[2]));
258 object->SetScale(
vec3(node->scale[0], node->scale[1], node->scale[2]));
264 for (cgltf_size pi = 0; pi < node->mesh->primitives_count; ++pi)
266 auto &primitive = node->mesh->primitives[pi];
267 if (primitive.type != cgltf_primitive_type_triangles)
273 cgltf_accessor *accessors[4] = {
nullptr,
nullptr,
nullptr};
275 for (cgltf_size ai = 0; ai < primitive.attributes_count; ++ai)
277 auto &attr = primitive.attributes[ai];
278 auto acc = attr.data;
285 element.
type = GL_FLOAT;
289 case cgltf_attribute_type_position: {
296 case cgltf_attribute_type_color: {
308 case cgltf_attribute_type_texcoord: {
319 case cgltf_attribute_type_normal: {
330 if (element.
size > 0)
334 vertexLayout.
elements.push_back(element);
345 std::vector<f32> vertices;
346 vertices.resize(vertexLayout.
stride /
sizeof(
f32) * vertexCount);
348 for (cgltf_size vi = 0; vi < vertexCount; ++vi)
350 for (
auto &elem : vertexLayout.
elements)
352 if (!accessors[elem.index])
356 auto index = (vi * vertexLayout.
stride + elem.offset) /
sizeof(
f32);
357 f32 *outData = &vertices[index];
358 readFloats(accessors[elem.index], vi, outData, elem.size);
362 std::shared_ptr<Mesh>
mesh;
363 if (primitive.indices)
365 auto indexCount = primitive.indices->count;
367 std::vector<u32> indices(indexCount);
368 for (cgltf_size i = 0; i < indexCount; ++i)
370 indices[i] =
readIndex(primitive.indices, i);
373 mesh = std::make_shared<Mesh>(vertexLayout, vertices, indices);
378 mesh = std::make_shared<Mesh>(vertexLayout, vertices);
381 auto mat = std::make_shared<Material>();
384 if (primitive.material)
386 auto gltfMat = primitive.material;
387 if (gltfMat->has_pbr_metallic_roughness)
389 auto pbr = gltfMat->pbr_metallic_roughness;
390 auto texture = pbr.base_color_texture.texture;
391 if (texture && texture->image)
393 if (texture->image->uri)
395 auto path = folder / std::string(texture->image->uri);
397 mat->SetParam(
"baseColorTexture", tex);
400 }
else if (gltfMat->has_pbr_specular_glossiness)
402 auto pbr = gltfMat->pbr_specular_glossiness;
403 auto texture = pbr.diffuse_texture.texture;
404 if (texture && texture->image)
406 if (texture->image->uri)
408 auto path = folder / std::string(texture->image->uri);
410 mat->SetParam(
"baseColorTexture", tex);
420 for (cgltf_size ci = 0; ci < node->children_count; ++ci)
429 if (contents.empty())
431 LOG_ERROR(
"LoadGLTF empty or missing file '%s'", path.c_str());
437 LOG_ERROR(
"LoadGLTF called with null scene (path='%s')", path.c_str());
441 cgltf_options options = {};
442 cgltf_data *data =
nullptr;
444 cgltf_result res = cgltf_parse(&options, contents.data(), contents.size(), &data);
445 if (res != cgltf_result_success)
447 LOG_ERROR(
"LoadGLTF cgltf_parse failed (code=%d) for '%s'",
static_cast<int>(res), path.c_str());
452 auto fullFolderPath = fullPath.remove_filename();
453 auto relativeFolderPath = std::filesystem::path(path).remove_filename();
455 res = cgltf_load_buffers(&options, data, fullFolderPath.string().c_str());
456 if (res != cgltf_result_success)
458 LOG_ERROR(
"LoadGLTF cgltf_load_buffers failed (code=%d) for '%s'",
static_cast<int>(res), path.c_str());
464 auto scene = &data->scenes[0];
466 for (cgltf_size i = 0; i < scene->nodes_count; ++i)
468 auto node = scene->nodes[i];
472 std::vector<std::shared_ptr<AnimationClip>> clips;
473 for (cgltf_size ai = 0; ai < data->animations_count; ++ai)
475 auto &anim = data->animations[ai];
477 auto clip = std::make_shared<AnimationClip>();
478 clip->name = anim.name ? anim.name :
"noname";
479 clip->duration = 0.0f;
481 std::unordered_map<cgltf_node *, size_t> trackIndexOf;
485 auto it = trackIndexOf.find(node);
486 if (it != trackIndexOf.end())
488 return clip->tracks[it->second];
493 clip->tracks.push_back(track);
494 size_t idx = clip->tracks.size() - 1;
495 trackIndexOf[node] = idx;
496 return clip->tracks[idx];
499 for (cgltf_size ci = 0; ci < anim.channels_count; ++ci)
501 auto &channel = anim.channels[ci];
502 auto sampler = channel.sampler;
504 if (!channel.target_node || !sampler || !sampler->input || !sampler->output)
509 std::vector<float> times;
512 auto &track = GetOrCreateTrack(channel.target_node);
514 switch (channel.target_path)
516 case cgltf_animation_path_type_translation: {
517 std::vector<glm::vec3> values;
519 track.positions.resize(times.size());
520 for (
size_t i = 0; i < times.size(); ++i)
522 track.positions[i].time = times[i];
523 track.positions[i].value = values[i];
527 case cgltf_animation_path_type_rotation: {
528 std::vector<glm::quat> values;
530 track.rotations.resize(times.size());
531 for (
size_t i = 0; i < times.size(); ++i)
533 track.rotations[i].time = times[i];
534 track.rotations[i].value = values[i];
538 case cgltf_animation_path_type_scale: {
539 std::vector<glm::vec3> values;
541 track.scales.resize(times.size());
542 for (
size_t i = 0; i < times.size(); ++i)
544 track.scales[i].time = times[i];
545 track.scales[i].value = values[i];
553 clip->duration = std::max(clip->duration, times.back());
556 clips.push_back(std::move(clip));
562 resultObject->AddComponent(animComp);
563 for (
auto &clip : clips)
565 animComp->RegisterClip(clip->name, clip);
586 if (m_scene ==
nullptr)
610 LOG_INFO(
"GameObject '%s' marked for destroy", m_name.c_str());
631 for (
auto &child : m_children)
633 if (
auto res = child->FindChildByName(name))
Console logging macros for all engine and game code.
#define LOG_INFO(fmt,...)
#define LOG_ERROR(fmt,...)
std::shared_ptr< Mesh > mesh
Drives transform animation on a hierarchy of GameObjects.
FileSystem & GetFileSystem()
Returns the file system helper for asset-relative path resolution.
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
TextureManager & GetTextureManager()
Returns the TextureManager that caches loaded textures by path.
std::string LoadAssetFileText(const std::string &relativePath)
Load a text file from the assets directory into a string.
std::filesystem::path GetAssetsFolder() const
Return the runtime assets directory.
Node in the scene graph: a named transform that owns components and children.
const vec3 & GetScale() const
void SetName(const str &name)
Sets the display name (used by FindChildByName).
bool IsAlive() const
Returns false after MarkForDestroy() is called.
virtual void Update(f32 deltaTime)
Update this object and all its components and children recursively.
const vec3 & GetPosition() const
void SetActive(bool active)
Show/hide this object (and stop Update calls).
vec3 GetWorldPosition() const
Extract the world-space position from GetWorldTransform().
void SetPosition(const vec3 &pos)
static GameObject * LoadGLTF(const std::string &path, Scene *scene)
Load a GLTF / GLB file and build a matching GameObject hierarchy.
const str & GetName() const
Returns the object's display name.
void MarkForDestroy()
Flag this object for removal at end of frame.
mat4 GetLocalTransform() const
Compute the local transform matrix (TRS order).
void SetWorldRotation(const quat &rot)
void SetRotation(const quat &rot)
void AddComponent(Component *component)
Attach a component to this object (takes ownership).
void SetScale(const vec3 &scale)
bool SetParent(GameObject *parent)
Re-parent this object in the scene hierarchy.
const quat & GetRotation() const
GameObject * GetParent()
Returns the parent node, or nullptr for root objects.
bool IsActive() const
Returns false if the object is hidden.
GameObject * FindChildByName(const std::string &name)
Depth-first search for a child with the given name.
Scene * GetScene()
Returns the Scene that owns this object.
mat4 GetWorldTransform() const
Compute the world transform by walking up the parent chain.
void SetWorldPosition(const vec3 &pos)
Makes a GameObject renderable by submitting its mesh each frame.
Container for the entire game object graph.
GameObject * CreateObject(const std::string &name, GameObject *parent=nullptr)
Create a plain GameObject owned by this scene.
bool SetParent(GameObject *obj, GameObject *parent)
Move a GameObject in the hierarchy.
std::shared_ptr< Texture > GetOrLoadTexture(const std::string &path)
Return a cached texture, loading it from disk on first request.
glm::mat4 mat4
4×4 column-major float matrix (model/view/projection).
glm::vec4 vec4
4-component float vector (e.g. RGBA colour, homogeneous coords).
glm::vec3 vec3
3-component float vector (e.g. world position, RGB colour, normals).
glm::quat quat
Unit quaternion for rotation (avoids gimbal lock).
std::string str
Convenience alias for std::string.
float f32
32-bit IEEE float — the standard GL scalar type.
uint32_t u32
Unsigned 32-bit integer — also the GL index type.
void ParseGLTFNode(cgltf_node *node, GameObject *parent, const std::filesystem::path &folder)
Describes one interleaved vertex attribute within a VBO.
GLuint index
Shader attribute location (layout(location = N)).
static constexpr int NormalIndex
layout(location = 3) vec3 aNormal
uint32_t offset
Byte offset from the start of one vertex.
GLuint type
GL data type constant (e.g. GL_FLOAT).
static constexpr int ColorIndex
layout(location = 1) vec3 aColor
static constexpr int UVIndex
layout(location = 2) vec2 aUV
GLuint size
Number of components (e.g. 3 for a vec3 position).
static constexpr int PositionIndex
Canonical attribute slot indices — must match the GLSL layout locations.
Complete description of how vertex data is packed in a VBO.
std::vector< VertexElement > elements
Ordered list of vertex attributes.
uint32_t stride
Total byte size of one vertex.