Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
GameObject.cpp
Go to the documentation of this file.
1#include <memory>
2
3#include "scene/GameObject.h"
4
5#include <glm/ext/matrix_transform.hpp>
6#include <glm/glm.hpp>
7#include <glm/gtc/type_ptr.hpp>
8
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>
17
18#include "Engine.h"
19#include "Log.h"
20
21#define CGLTF_IMPLEMENTATION
22#include "cgltf.h"
23
24namespace mnd
25{
26
27auto ReadScalar = [](cgltf_accessor *acc, cgltf_size index)
28{
29 float res = 0.0f;
30 cgltf_accessor_read_float(acc, index, &res, 1);
31 return res;
32};
33
34auto ReadVec3 = [](cgltf_accessor *acc, cgltf_size index)
35{
36 glm::vec3 res;
37 cgltf_accessor_read_float(acc, index, glm::value_ptr(res), 3);
38 return res;
39};
40
41auto ReadQuat = [](cgltf_accessor *acc, cgltf_size index)
42{
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]);
46};
47
48auto ReadTimes = [](cgltf_accessor *acc, std::vector<float> &outTimes)
49{
50 outTimes.resize(acc->count);
51 for (cgltf_size i = 0; i < acc->count; ++i)
52 {
53 outTimes[i] = ReadScalar(acc, i);
54 }
55};
56
57auto ReadOutputVec3 = [](cgltf_accessor *acc, std::vector<glm::vec3> &outValues)
58{
59 outValues.resize(acc->count);
60 for (cgltf_size i = 0; i < acc->count; ++i)
61 {
62 outValues[i] = ReadVec3(acc, i);
63 }
64};
65
66auto ReadOutputQuat = [](cgltf_accessor *acc, std::vector<glm::quat> &outValues)
67{
68 outValues.resize(acc->count);
69 for (cgltf_size i = 0; i < acc->count; ++i)
70 {
71 outValues[i] = ReadQuat(acc, i);
72 }
73};
74
75void GameObject::Update(f32 deltaTime)
76{
77 if (!m_active)
78 {
79 return;
80 }
81
82 for (auto &component : m_components)
83 {
84 component->Update(deltaTime);
85 }
86
87 for (auto it = m_children.begin(); it != m_children.end();)
88 {
89 if ((*it)->IsAlive())
90 {
91 (*it)->Update(deltaTime);
92 ++it;
93 } else
94 {
95 it = m_children.erase(it);
96 }
97 }
98}
99
101{
102 if (!component)
103 {
104 LOG_ERROR("AddComponent called with nullptr on '%s'", m_name.c_str());
105 return;
106 }
107 m_components.emplace_back(component);
108 component->m_owner = this;
109 component->Init();
110 LOG_INFO("Component added to '%s' (total=%zu)", m_name.c_str(), m_components.size());
111}
112
114{
115 return m_position;
116}
117
119{
120 m_position = pos;
121}
122
124{
125 return m_rotation;
126}
127
129{
130 m_rotation = rot;
131}
132
134{
135 return m_scale;
136}
137
139{
140 m_scale = pos;
141}
142
144{
145 vec4 hom = GetWorldTransform() * vec4(0.0F, 0.0F, 0.0F, 1.0F);
146 return vec3(hom) / hom.w;
147}
148
150{
151 mat4 mat = mat4(1.0F);
152
153 // Translation
154 mat = translate(mat, m_position);
155
156 // Rotation
157 // mat = rotate(mat, m_rotation.x, vec3(1.0f, 0.0f, 0.0f)); // X Axis
158 // mat = rotate(mat, m_rotation.y, vec3(0.0f, 1.0f, 0.0f)); // Y Axis
159 // mat = rotate(mat, m_rotation.z, vec3(0.0f, 0.0f, 1.0f)); // Z Axis
160 mat = mat * mat4_cast(m_rotation);
161
162 // Scale
163 mat = scale(mat, m_scale);
164
165 return mat;
166}
167
169{
170 if (m_parent)
171 {
172 return m_parent->GetWorldTransform() * GetLocalTransform();
173 } else
174 {
175 return GetLocalTransform();
176 }
177}
178
180{
181 if (m_parent)
182 {
183 mat4 parentWorld = m_parent->GetWorldTransform();
184 mat4 invParentWorld = inverse(parentWorld);
185 vec4 localPos = invParentWorld * vec4(pos, 1.0F);
186 SetPosition(vec3(localPos) / localPos.w);
187 } else
188 {
189 SetPosition(pos);
190 }
191}
192
194{
195 if (m_parent)
196 {
197 quat parentWorldRot = m_parent->GetWorldRotation();
198 quat invParentWorldRot = inverse(parentWorldRot);
199 quat newLocalRot = invParentWorldRot * rot;
200 SetRotation(newLocalRot);
201 } else
202 {
203 SetRotation(rot);
204 }
205}
206
208{
209 if (m_parent)
210 {
211 return m_parent->GetWorldRotation() * m_rotation;
212 } else
213 {
214 return m_rotation;
215 }
216}
217
218auto readFloats = [](const cgltf_accessor *acc, cgltf_size i, float *out, int n)
219{
220 std::fill(out, out + n, 0.0f);
221 return cgltf_accessor_read_float(acc, i, out, n) == 1;
222};
223
224auto readIndex = [](const cgltf_accessor *acc, cgltf_size i)
225{
226 cgltf_uint out = 0;
227 cgltf_bool ok = cgltf_accessor_read_uint(acc, i, &out, 1);
228 return ok ? static_cast<u32>(out) : 0;
229};
230
231void ParseGLTFNode(cgltf_node *node, GameObject *parent, const std::filesystem::path &folder)
232{
233 auto object = parent->GetScene()->CreateObject(node->name, parent);
234
235 if (node->has_matrix)
236 {
237 auto mat = glm::make_mat4(node->matrix);
238 vec3 translation, scale, skew;
239 vec4 perspective;
240 quat orientation;
241 decompose(mat, scale, orientation, translation, skew, perspective);
242
243 object->SetPosition(translation);
244 object->SetRotation(orientation);
245 object->SetScale(scale);
246 } else
247 {
248 if (node->has_translation)
249 {
250 object->SetPosition(vec3(node->translation[0], node->translation[1], node->translation[2]));
251 }
252 if (node->has_rotation)
253 {
254 object->SetRotation(quat(node->rotation[3], node->rotation[0], node->rotation[1], node->rotation[2]));
255 }
256 if (node->has_scale)
257 {
258 object->SetScale(vec3(node->scale[0], node->scale[1], node->scale[2]));
259 }
260 }
261
262 if (node->mesh)
263 {
264 for (cgltf_size pi = 0; pi < node->mesh->primitives_count; ++pi)
265 {
266 auto &primitive = node->mesh->primitives[pi];
267 if (primitive.type != cgltf_primitive_type_triangles)
268 {
269 continue;
270 }
271
272 VertexLayout vertexLayout;
273 cgltf_accessor *accessors[4] = {nullptr, nullptr, nullptr};
274
275 for (cgltf_size ai = 0; ai < primitive.attributes_count; ++ai)
276 {
277 auto &attr = primitive.attributes[ai];
278 auto acc = attr.data;
279 if (!acc)
280 {
281 continue;
282 }
283
284 VertexElement element;
285 element.type = GL_FLOAT;
286
287 switch (attr.type)
288 {
289 case cgltf_attribute_type_position: {
290 accessors[VertexElement::PositionIndex] = acc;
291
293 element.size = 3;
294 }
295 break;
296 case cgltf_attribute_type_color: {
297 if (attr.index != 0)
298 {
299 continue;
300 }
301 accessors[VertexElement::ColorIndex] = acc;
302
304 element.size = 3;
305 }
306 break;
307
308 case cgltf_attribute_type_texcoord: {
309 if (attr.index != 0)
310 {
311 continue;
312 }
313 accessors[VertexElement::UVIndex] = acc;
314
316 element.size = 2;
317 }
318 break;
319 case cgltf_attribute_type_normal: {
320 accessors[VertexElement::NormalIndex] = acc;
321
323 element.size = 3;
324 }
325 break;
326 default:
327 continue;
328 }
329
330 if (element.size > 0)
331 {
332 element.offset = vertexLayout.stride;
333 vertexLayout.stride += element.size * sizeof(f32);
334 vertexLayout.elements.push_back(element);
335 }
336 }
337
338 if (!accessors[VertexElement::PositionIndex])
339 {
340 continue;
341 }
342
343 auto &vertexCount = accessors[VertexElement::PositionIndex]->count;
344
345 std::vector<f32> vertices;
346 vertices.resize(vertexLayout.stride / sizeof(f32) * vertexCount);
347
348 for (cgltf_size vi = 0; vi < vertexCount; ++vi)
349 {
350 for (auto &elem : vertexLayout.elements)
351 {
352 if (!accessors[elem.index])
353 {
354 continue;
355 }
356 auto index = (vi * vertexLayout.stride + elem.offset) / sizeof(f32);
357 f32 *outData = &vertices[index];
358 readFloats(accessors[elem.index], vi, outData, elem.size);
359 }
360 }
361
362 std::shared_ptr<Mesh> mesh;
363 if (primitive.indices)
364 {
365 auto indexCount = primitive.indices->count;
366
367 std::vector<u32> indices(indexCount);
368 for (cgltf_size i = 0; i < indexCount; ++i)
369 {
370 indices[i] = readIndex(primitive.indices, i);
371 }
372
373 mesh = std::make_shared<Mesh>(vertexLayout, vertices, indices);
374 }
375
376 else
377 {
378 mesh = std::make_shared<Mesh>(vertexLayout, vertices);
379 }
380
381 auto mat = std::make_shared<Material>();
382 mat->SetShaderProgram(Engine::GetInstance().GetGraphicsAPI().GetDefaultShaderProgram());
383
384 if (primitive.material)
385 {
386 auto gltfMat = primitive.material;
387 if (gltfMat->has_pbr_metallic_roughness)
388 {
389 auto pbr = gltfMat->pbr_metallic_roughness;
390 auto texture = pbr.base_color_texture.texture;
391 if (texture && texture->image)
392 {
393 if (texture->image->uri)
394 {
395 auto path = folder / std::string(texture->image->uri);
397 mat->SetParam("baseColorTexture", tex);
398 }
399 }
400 } else if (gltfMat->has_pbr_specular_glossiness)
401 {
402 auto pbr = gltfMat->pbr_specular_glossiness;
403 auto texture = pbr.diffuse_texture.texture;
404 if (texture && texture->image)
405 {
406 if (texture->image->uri)
407 {
408 auto path = folder / std::string(texture->image->uri);
410 mat->SetParam("baseColorTexture", tex);
411 }
412 }
413 }
414
415 object->AddComponent(new MeshComponent(mat, mesh));
416 }
417 }
418 }
419
420 for (cgltf_size ci = 0; ci < node->children_count; ++ci)
421 {
422 ParseGLTFNode(node->children[ci], object, folder);
423 }
424}
425
426GameObject *GameObject::LoadGLTF(const std::string &path, Scene *gameScene)
427{
428 auto contents = Engine::GetInstance().GetFileSystem().LoadAssetFileText(path);
429 if (contents.empty())
430 {
431 LOG_ERROR("LoadGLTF empty or missing file '%s'", path.c_str());
432 return nullptr;
433 }
434
435 if (!gameScene)
436 {
437 LOG_ERROR("LoadGLTF called with null scene (path='%s')", path.c_str());
438 return nullptr;
439 }
440
441 cgltf_options options = {};
442 cgltf_data *data = nullptr;
443
444 cgltf_result res = cgltf_parse(&options, contents.data(), contents.size(), &data);
445 if (res != cgltf_result_success)
446 {
447 LOG_ERROR("LoadGLTF cgltf_parse failed (code=%d) for '%s'", static_cast<int>(res), path.c_str());
448 return nullptr;
449 }
450
451 auto fullPath = Engine::GetInstance().GetFileSystem().GetAssetsFolder() / path;
452 auto fullFolderPath = fullPath.remove_filename();
453 auto relativeFolderPath = std::filesystem::path(path).remove_filename();
454
455 res = cgltf_load_buffers(&options, data, fullFolderPath.string().c_str());
456 if (res != cgltf_result_success)
457 {
458 LOG_ERROR("LoadGLTF cgltf_load_buffers failed (code=%d) for '%s'", static_cast<int>(res), path.c_str());
459 cgltf_free(data);
460 return nullptr;
461 }
462
463 auto resultObject = gameScene->CreateObject("Result");
464 auto scene = &data->scenes[0];
465
466 for (cgltf_size i = 0; i < scene->nodes_count; ++i)
467 {
468 auto node = scene->nodes[i];
469 ParseGLTFNode(node, resultObject, relativeFolderPath);
470 }
471
472 std::vector<std::shared_ptr<AnimationClip>> clips;
473 for (cgltf_size ai = 0; ai < data->animations_count; ++ai)
474 {
475 auto &anim = data->animations[ai];
476
477 auto clip = std::make_shared<AnimationClip>();
478 clip->name = anim.name ? anim.name : "noname";
479 clip->duration = 0.0f;
480
481 std::unordered_map<cgltf_node *, size_t> trackIndexOf;
482
483 auto GetOrCreateTrack = [&](cgltf_node *node) -> TransformTrack &
484 {
485 auto it = trackIndexOf.find(node);
486 if (it != trackIndexOf.end())
487 {
488 return clip->tracks[it->second];
489 }
490
491 TransformTrack track;
492 track.targetName = node->name;
493 clip->tracks.push_back(track);
494 size_t idx = clip->tracks.size() - 1;
495 trackIndexOf[node] = idx;
496 return clip->tracks[idx];
497 };
498
499 for (cgltf_size ci = 0; ci < anim.channels_count; ++ci)
500 {
501 auto &channel = anim.channels[ci];
502 auto sampler = channel.sampler;
503
504 if (!channel.target_node || !sampler || !sampler->input || !sampler->output)
505 {
506 continue;
507 }
508
509 std::vector<float> times;
510 ReadTimes(sampler->input, times);
511
512 auto &track = GetOrCreateTrack(channel.target_node);
513
514 switch (channel.target_path)
515 {
516 case cgltf_animation_path_type_translation: {
517 std::vector<glm::vec3> values;
518 ReadOutputVec3(sampler->output, values);
519 track.positions.resize(times.size());
520 for (size_t i = 0; i < times.size(); ++i)
521 {
522 track.positions[i].time = times[i];
523 track.positions[i].value = values[i];
524 }
525 }
526 break;
527 case cgltf_animation_path_type_rotation: {
528 std::vector<glm::quat> values;
529 ReadOutputQuat(sampler->output, values);
530 track.rotations.resize(times.size());
531 for (size_t i = 0; i < times.size(); ++i)
532 {
533 track.rotations[i].time = times[i];
534 track.rotations[i].value = values[i];
535 }
536 }
537 break;
538 case cgltf_animation_path_type_scale: {
539 std::vector<glm::vec3> values;
540 ReadOutputVec3(sampler->output, values);
541 track.scales.resize(times.size());
542 for (size_t i = 0; i < times.size(); ++i)
543 {
544 track.scales[i].time = times[i];
545 track.scales[i].value = values[i];
546 }
547 }
548 break;
549 default:
550 break;
551 }
552
553 clip->duration = std::max(clip->duration, times.back());
554 }
555
556 clips.push_back(std::move(clip));
557 }
558
559 if (!clips.empty())
560 {
561 auto animComp = new AnimationComponent();
562 resultObject->AddComponent(animComp);
563 for (auto &clip : clips)
564 {
565 animComp->RegisterClip(clip->name, clip);
566 }
567 }
568
569 cgltf_free(data);
570
571 return resultObject;
572}
573
575{
576 return m_name;
577}
578
579void GameObject::SetName(const str &name)
580{
581 m_name = name;
582}
583
585{
586 if (m_scene == nullptr)
587 {
588 return false;
589 }
590 return m_scene->SetParent(this, parent);
591}
592
594{
595 return m_scene;
596}
597
599{
600 return m_parent;
601}
602
604{
605 return m_isAlive;
606}
607
609{
610 LOG_INFO("GameObject '%s' marked for destroy", m_name.c_str());
611 m_isAlive = false;
612}
613
614void GameObject::SetActive(bool active)
615{
616 m_active = active;
617}
618
620{
621 return m_active;
622}
623
625{
626 if (m_name == name)
627 {
628 return this;
629 }
630
631 for (auto &child : m_children)
632 {
633 if (auto res = child->FindChildByName(name))
634 {
635 return res;
636 }
637 }
638
639 return nullptr;
640}
641
642} // namespace mnd
Console logging macros for all engine and game code.
#define LOG_INFO(fmt,...)
Definition Log.h:79
#define LOG_ERROR(fmt,...)
Definition Log.h:81
std::shared_ptr< Mesh > mesh
Drives transform animation on a hierarchy of GameObjects.
virtual void Init()
Definition Component.cpp:10
GameObject * m_owner
Definition Component.h:57
FileSystem & GetFileSystem()
Returns the file system helper for asset-relative path resolution.
Definition Engine.cpp:453
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
Definition Engine.cpp:59
TextureManager & GetTextureManager()
Returns the TextureManager that caches loaded textures by path.
Definition Engine.cpp:433
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.
Definition GameObject.h:91
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().
quat GetWorldRotation()
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.
Definition Scene.h:59
GameObject * CreateObject(const std::string &name, GameObject *parent=nullptr)
Create a plain GameObject owned by this scene.
Definition Scene.cpp:132
bool SetParent(GameObject *obj, GameObject *parent)
Move a GameObject in the hierarchy.
Definition Scene.cpp:339
std::shared_ptr< Texture > GetOrLoadTexture(const std::string &path)
Return a cached texture, loading it from disk on first request.
Definition Texture.cpp:58
glm::mat4 mat4
4×4 column-major float matrix (model/view/projection).
Definition Types.h:63
glm::vec4 vec4
4-component float vector (e.g. RGBA colour, homogeneous coords).
Definition Types.h:52
glm::vec3 vec3
3-component float vector (e.g. world position, RGB colour, normals).
Definition Types.h:51
glm::quat quat
Unit quaternion for rotation (avoids gimbal lock).
Definition Types.h:65
std::string str
Convenience alias for std::string.
Definition Types.h:45
float f32
32-bit IEEE float — the standard GL scalar type.
Definition Types.h:40
uint32_t u32
Unsigned 32-bit integer — also the GL index type.
Definition Types.h:32
auto ReadVec3
auto ReadTimes
auto ReadOutputQuat
auto ReadOutputVec3
auto ReadQuat
void ParseGLTFNode(cgltf_node *node, GameObject *parent, const std::filesystem::path &folder)
auto readFloats
auto readIndex
auto ReadScalar
Animation data for one GLTF node: position, rotation, and scale tracks.
std::string targetName
Name of the child GameObject to animate.
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.