From 4b6f773c0c1426e6ad177d2401770750817f26e7 Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Sat, 25 Jul 2026 21:46:01 +0200 Subject: [PATCH] feat? add basich ah scene loading --- destrum/CMakeLists.txt | 3 + destrum/include/destrum/Components/Animator.h | 4 + .../Components/MeshRendererComponent.h | 5 + .../destrum/Components/Physics/BoxCollider.h | 5 + .../destrum/Components/Physics/Rigidbody.h | 5 + .../Components/Physics/SphereCollider.h | 4 + .../include/destrum/ObjectModel/Component.h | 33 ++- .../include/destrum/ObjectModel/GameObject.h | 90 ++++--- .../include/destrum/ObjectModel/ObjectId.h | 10 + destrum/include/destrum/Scene/Scene.h | 6 + .../destrum/Serialization/ComponentFactory.h | 37 +++ .../destrum/Serialization/ComponentRegistry.h | 6 + .../destrum/Serialization/SceneSerializer.h | 14 ++ destrum/src/ObjectModel/Component.cpp | 13 +- destrum/src/ObjectModel/GameObject.cpp | 58 +++-- destrum/src/Scene/Scene.cpp | 24 +- .../src/Serialization/ComponentRegistry.cpp | 56 +++++ destrum/src/Serialization/SceneSerializer.cpp | 223 ++++++++++++++++++ 18 files changed, 523 insertions(+), 73 deletions(-) create mode 100644 destrum/include/destrum/ObjectModel/ObjectId.h create mode 100644 destrum/include/destrum/Serialization/ComponentFactory.h create mode 100644 destrum/include/destrum/Serialization/ComponentRegistry.h create mode 100644 destrum/include/destrum/Serialization/SceneSerializer.h create mode 100644 destrum/src/Serialization/ComponentRegistry.cpp create mode 100644 destrum/src/Serialization/SceneSerializer.cpp diff --git a/destrum/CMakeLists.txt b/destrum/CMakeLists.txt index 1d12389..7eb0716 100644 --- a/destrum/CMakeLists.txt +++ b/destrum/CMakeLists.txt @@ -53,6 +53,9 @@ set(SRC_FILES "src/FS/AssetFS.cpp" "src/FS/Manifest.cpp" + "src/Serialization/SceneSerializer.cpp" + "src/Serialization/ComponentRegistry.cpp" + "src/Util/DeltaTime.cpp" "src/Physics/PhysicsWorld.cpp" diff --git a/destrum/include/destrum/Components/Animator.h b/destrum/include/destrum/Components/Animator.h index 068e656..b4a9417 100644 --- a/destrum/include/destrum/Components/Animator.h +++ b/destrum/include/destrum/Components/Animator.h @@ -19,6 +19,10 @@ public: explicit Animator(GameObject& parent); void Update() override; + std::string GetTypeName() const override + { + return "Animator"; + } void ImGuiInspector() override; void addClip(std::shared_ptr clip); diff --git a/destrum/include/destrum/Components/MeshRendererComponent.h b/destrum/include/destrum/Components/MeshRendererComponent.h index 0890988..91a6f49 100644 --- a/destrum/include/destrum/Components/MeshRendererComponent.h +++ b/destrum/include/destrum/Components/MeshRendererComponent.h @@ -18,6 +18,11 @@ public: void SetMaterialID(MaterialID id) { materialID = id; } MaterialID GetMaterialID() const { return materialID; } + std::string GetTypeName() const override + { + return "MeshRendererComponent"; + } + private: MeshID meshID{NULL_MESH_ID}; MaterialID materialID{NULL_MATERIAL_ID}; diff --git a/destrum/include/destrum/Components/Physics/BoxCollider.h b/destrum/include/destrum/Components/Physics/BoxCollider.h index 8f724ea..a5e1588 100644 --- a/destrum/include/destrum/Components/Physics/BoxCollider.h +++ b/destrum/include/destrum/Components/Physics/BoxCollider.h @@ -11,6 +11,11 @@ public: void Update() override {} + std::string GetTypeName() const override + { + return "BoxCollider"; + } + [[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override { return PhysicsShapeDesc::Box(m_HalfExtents, m_CenterOffset, m_IsTrigger); diff --git a/destrum/include/destrum/Components/Physics/Rigidbody.h b/destrum/include/destrum/Components/Physics/Rigidbody.h index 136a004..8babe8c 100644 --- a/destrum/include/destrum/Components/Physics/Rigidbody.h +++ b/destrum/include/destrum/Components/Physics/Rigidbody.h @@ -16,6 +16,11 @@ public: void Update() override {} + std::string GetTypeName() const override + { + return "RigidBody"; + } + void AttachPhysicsBody(PhysicsWorld* world, PhysicsBodyHandle body); void DetachPhysicsBody(); diff --git a/destrum/include/destrum/Components/Physics/SphereCollider.h b/destrum/include/destrum/Components/Physics/SphereCollider.h index cf511e5..72124d9 100644 --- a/destrum/include/destrum/Components/Physics/SphereCollider.h +++ b/destrum/include/destrum/Components/Physics/SphereCollider.h @@ -11,6 +11,10 @@ public: , m_Radius(radius) {} void Update() override {} + std::string GetTypeName() const override + { + return "SphereCollider"; + } [[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override { return PhysicsShapeDesc::Sphere(m_Radius, m_CenterOffset, m_IsTrigger); diff --git a/destrum/include/destrum/ObjectModel/Component.h b/destrum/include/destrum/ObjectModel/Component.h index a5928b2..9507a5c 100644 --- a/destrum/include/destrum/ObjectModel/Component.h +++ b/destrum/include/destrum/ObjectModel/Component.h @@ -1,14 +1,21 @@ #ifndef COMPONENT_H #define COMPONENT_H +#include +#include + +#include + #include #include +#include class GameObject; class Transform; -class Component: public Object { +class Component : public Object { public: + using ObjectMap = std::unordered_map; ~Component() override = default; @@ -18,30 +25,44 @@ public: Component& operator=(Component&& other) noexcept = delete; [[nodiscard]] bool isEnabled() const { return m_IsEnabled; } - [[nodiscard]] GameObject *GetGameObject() const { return m_ParentGameObjectPtr; } + [[nodiscard]] GameObject* GetGameObject() const { return m_ParentGameObjectPtr; } [[nodiscard]] Transform& GetTransform() const; void Destroy() override; + virtual void Start(); virtual void SetEnabled(bool enabled); + virtual void Update() = 0; virtual void LateUpdate(); virtual void FixedUpdate(); - virtual void ImGuiInspector(); //Specifically for the inspector - virtual void ImGuiRender(); + virtual void ImGuiInspector(); + virtual void ImGuiRender(); virtual void Render(const RenderContext& ctx); + // Serialization. + virtual std::string GetTypeName() const = 0; + + virtual nlohmann::json Serialize() const { + return {}; + } + + virtual void Deserialize(const nlohmann::json& data) { + } + + virtual void ResolveReferences(const ObjectMap& objects) { + } bool HasStarted{false}; + protected: explicit Component(GameObject& pParent, const std::string& name = "Component"); private: GameObject* m_ParentGameObjectPtr{}; bool m_IsEnabled{true}; - }; -#endif //COMPONENT_H +#endif // COMPONENT_H \ No newline at end of file diff --git a/destrum/include/destrum/ObjectModel/GameObject.h b/destrum/include/destrum/ObjectModel/GameObject.h index 025382f..09dc2a8 100644 --- a/destrum/include/destrum/ObjectModel/GameObject.h +++ b/destrum/include/destrum/ObjectModel/GameObject.h @@ -1,14 +1,17 @@ #pragma once + #include #include +#include #include #include +#include #include class Scene; -class GameObject final: public Object { +class GameObject final : public Object { public: friend class Scene; @@ -31,14 +34,28 @@ public: SetActiveDirty(); } - [[nodiscard]] std::vector>& GetComponents() { return m_Components; } + [[nodiscard]] bool IsActive() const { return m_Active; } + + [[nodiscard]] ObjectId GetId() const { return m_Id; } + + // Use this only while loading a scene. + void SetIdForDeserialization(ObjectId id); + + [[nodiscard]] Scene* GetScene() const { return m_Scene; } + + [[nodiscard]] std::vector>& GetComponents() { + return m_Components; + } + + [[nodiscard]] const std::vector>& GetComponents() const { + return m_Components; + } [[nodiscard]] Transform& GetTransform() { return m_TransformPtr; } + [[nodiscard]] const Transform& GetTransform() const { return m_TransformPtr; } [[nodiscard]] bool IsActiveInHierarchy(); - [[nodiscard]] bool IsActive() const { return m_Active; } - explicit GameObject(const std::string& name = "GameObject"); ~GameObject() override; @@ -57,20 +74,30 @@ public: return static_cast(addedComponent.get()); } - template - [[nodiscard]] Component *GetComponent() { - for (const auto& component: m_Components) { - if (auto casted = dynamic_cast(component.get())) { + template + [[nodiscard]] TComponent* GetComponent() { + for (const auto& component : m_Components) { + if (auto casted = dynamic_cast(component.get())) { return casted; } } return nullptr; } - template - Component *DestroyComponent() { - for (const auto& component: m_Components) { - if (auto casted = dynamic_cast(component.get())) { + template + [[nodiscard]] const TComponent* GetComponent() const { + for (const auto& component : m_Components) { + if (auto casted = dynamic_cast(component.get())) { + return casted; + } + } + return nullptr; + } + + template + TComponent* DestroyComponent() { + for (const auto& component : m_Components) { + if (auto casted = dynamic_cast(component.get())) { casted->Destroy(); return casted; } @@ -78,40 +105,50 @@ public: return nullptr; } - template - [[nodiscard]] bool HasComponent() { - for (const auto& component: m_Components) { - if (auto casted = dynamic_cast(component.get())) { + template + [[nodiscard]] bool HasComponent() const { + for (const auto& component : m_Components) { + if (dynamic_cast(component.get())) { return true; } } return false; } - template - Component *GetComponentInChildren() { - return GetComponentInChildrenRecursive(&GetTransform()); + template + TComponent* GetComponentInChildren() { + return GetComponentInChildrenRecursive(&GetTransform()); } private: - template - static Component *GetComponentInChildrenRecursive(Transform* transform) { + template + static TComponent* GetComponentInChildrenRecursive(Transform* transform) { if (!transform) return nullptr; + GameObject* owner = transform->GetOwner(); if (owner) { - if (Component* comp = owner->GetComponent()) { + if (TComponent* comp = owner->GetComponent()) { return comp; } } - for (Transform* child: transform->GetChildren()) { - if (Component* found = GetComponentInChildrenRecursive(child)) { + + for (Transform* child : transform->GetChildren()) { + if (TComponent* found = GetComponentInChildrenRecursive(child)) { return found; } } + return nullptr; } + void SetScene(Scene* scene) { m_Scene = scene; } + void SetActiveDirty(); + void UpdateActiveState(); + + inline static ObjectId s_NextId{1}; + + ObjectId m_Id{InvalidObjectId}; bool m_Active{true}; Scene* m_Scene{}; @@ -119,6 +156,5 @@ private: std::vector> m_Components{}; bool m_ActiveDirty{true}; - bool m_ActiveInHierarchy{true}; //Derived - void UpdateActiveState(); -}; + bool m_ActiveInHierarchy{true}; +}; \ No newline at end of file diff --git a/destrum/include/destrum/ObjectModel/ObjectId.h b/destrum/include/destrum/ObjectModel/ObjectId.h new file mode 100644 index 0000000..ad1be1d --- /dev/null +++ b/destrum/include/destrum/ObjectModel/ObjectId.h @@ -0,0 +1,10 @@ +#ifndef DESTRUM_OBJECTID_H +#define DESTRUM_OBJECTID_H + +#include + +using ObjectId = std::uint64_t; + +inline constexpr ObjectId InvalidObjectId = 0; + +#endif //DESTRUM_OBJECTID_H diff --git a/destrum/include/destrum/Scene/Scene.h b/destrum/include/destrum/Scene/Scene.h index d548165..02f1b61 100644 --- a/destrum/include/destrum/Scene/Scene.h +++ b/destrum/include/destrum/Scene/Scene.h @@ -35,6 +35,12 @@ public: [[nodiscard]] bool IsBeingUnloaded() const { return m_BeingUnloaded; } + void CommitPendingAdditions(); + + [[nodiscard]] const std::vector>& GetObjects() const { + return m_objects; + } + void SetRegisterBindings(std::function registerBindings) { m_registerBindings = std::move(registerBindings); } diff --git a/destrum/include/destrum/Serialization/ComponentFactory.h b/destrum/include/destrum/Serialization/ComponentFactory.h new file mode 100644 index 0000000..73dd4c8 --- /dev/null +++ b/destrum/include/destrum/Serialization/ComponentFactory.h @@ -0,0 +1,37 @@ +#ifndef DESTRUM_COMPONENTFACTORY_H +#define DESTRUM_COMPONENTFACTORY_H + +#include +#include +#include +#include + +#include +#include + +class ComponentFactory final { +public: + using CreateFn = std::function; + + static void Register(const std::string& typeName, CreateFn createFn) { + Registry()[typeName] = std::move(createFn); + } + + static Component* Create(const std::string& typeName, GameObject& owner) { + const auto it = Registry().find(typeName); + + if (it == Registry().end()) { + return nullptr; + } + + return it->second(owner); + } + +private: + static std::unordered_map& Registry() { + static std::unordered_map registry; + return registry; + } +}; + +#endif //DESTRUM_COMPONENTFACTORY_H diff --git a/destrum/include/destrum/Serialization/ComponentRegistry.h b/destrum/include/destrum/Serialization/ComponentRegistry.h new file mode 100644 index 0000000..7a6f87c --- /dev/null +++ b/destrum/include/destrum/Serialization/ComponentRegistry.h @@ -0,0 +1,6 @@ +#ifndef DESTRUM_COMPONENTREGISTRY_H +#define DESTRUM_COMPONENTREGISTRY_H + +void RegisterEngineComponents(); + +#endif //DESTRUM_COMPONENTREGISTRY_H diff --git a/destrum/include/destrum/Serialization/SceneSerializer.h b/destrum/include/destrum/Serialization/SceneSerializer.h new file mode 100644 index 0000000..31b86a7 --- /dev/null +++ b/destrum/include/destrum/Serialization/SceneSerializer.h @@ -0,0 +1,14 @@ +#ifndef DESTRUM_SCENESERIALIZER_H +#define DESTRUM_SCENESERIALIZER_H + +#include + +class Scene; + +class SceneSerializer final { +public: + static bool Save(Scene& scene, const std::filesystem::path& path); + static bool Load(Scene& scene, const std::filesystem::path& path); +}; + +#endif //DESTRUM_SCENESERIALIZER_H diff --git a/destrum/src/ObjectModel/Component.cpp b/destrum/src/ObjectModel/Component.cpp index f2e6c3c..f7aebaf 100644 --- a/destrum/src/ObjectModel/Component.cpp +++ b/destrum/src/ObjectModel/Component.cpp @@ -2,13 +2,10 @@ #include #include -// #include "imgui.h" -Component::Component(GameObject& pParent, const std::string& name): Object(name), m_ParentGameObjectPtr(&pParent) { - // if (m_ParentGameObjectPtr == nullptr) { - // //TODO: Change pParent ot be a reference - // throw std::runtime_error("Component made with no GameObject??"); - // } +Component::Component(GameObject& pParent, const std::string& name) + : Object(name), + m_ParentGameObjectPtr(&pParent) { } Transform& Component::GetTransform() const { @@ -16,7 +13,6 @@ Transform& Component::GetTransform() const { } void Component::Destroy() { - // const bool isBeingDestroyed = GetIsBeingDestroyed(); Object::Destroy(); } @@ -40,5 +36,4 @@ void Component::ImGuiRender() { } void Component::Render(const RenderContext& ctx) { - -} +} \ No newline at end of file diff --git a/destrum/src/ObjectModel/GameObject.cpp b/destrum/src/ObjectModel/GameObject.cpp index 9a8abc6..0646cd4 100644 --- a/destrum/src/ObjectModel/GameObject.cpp +++ b/destrum/src/ObjectModel/GameObject.cpp @@ -1,18 +1,30 @@ #include #include - #include #include "spdlog/spdlog.h" -// #include "Managers/ResourceManager.h" - GameObject::~GameObject() { // spdlog::debug("GameObject destroyed: {}", GetName()); } +GameObject::GameObject(const std::string& name) + : Object(name), + m_Id(s_NextId++) { +} + +void GameObject::SetIdForDeserialization(ObjectId id) { + m_Id = id; + + // Prevent newly created objects from reusing loaded IDs. + if (id >= s_NextId) { + s_NextId = id + 1; + } +} + void GameObject::SetActiveDirty() { m_ActiveDirty = true; + for (const Transform* child : m_TransformPtr.GetChildren()) { child->GetOwner()->SetActiveDirty(); } @@ -21,7 +33,7 @@ void GameObject::SetActiveDirty() { void GameObject::UpdateActiveState() { const auto* parentPtr = m_TransformPtr.GetParent(); - if(parentPtr == nullptr) { + if (parentPtr == nullptr) { m_ActiveInHierarchy = m_Active; } else { m_ActiveInHierarchy = m_Active && parentPtr->GetOwner()->IsActiveInHierarchy(); @@ -38,49 +50,51 @@ bool GameObject::IsActiveInHierarchy() { return m_ActiveInHierarchy; } -GameObject::GameObject(const std::string& name): Object(name) { -} - void GameObject::Update() { - for (const auto& component: m_Components) { + for (const auto& component : m_Components) { + if (!component->isEnabled()) { + continue; + } + if (!component->HasStarted) { component->Start(); component->HasStarted = true; } + component->Update(); } } void GameObject::LateUpdate() { - for (const auto& component: m_Components) { - component->LateUpdate(); + for (const auto& component : m_Components) { + if (component->isEnabled()) { + component->LateUpdate(); + } } } void GameObject::FixedUpdate() { - for (const auto& component: m_Components) { - component->FixedUpdate(); + for (const auto& component : m_Components) { + if (component->isEnabled()) { + component->FixedUpdate(); + } } } void GameObject::Render(const RenderContext& ctx) const { - for (const auto& component: m_Components) { - component->Render(ctx); + for (const auto& component : m_Components) { + if (component->isEnabled()) { + component->Render(ctx); + } } } -// void GameObject::ImGuiRender() { -// for (const auto& component: m_Components) { -// component->ImGuiRender(); -// } -// } - void GameObject::Destroy() { Object::Destroy(); m_TransformPtr.SetParent(nullptr); - for (const auto& component: m_Components) { + for (const auto& component : m_Components) { component->Destroy(); } @@ -93,4 +107,4 @@ void GameObject::CleanupComponents() { std::erase_if(m_Components, [](const std::unique_ptr& component) { return component->IsBeingDestroyed(); }); -} +} \ No newline at end of file diff --git a/destrum/src/Scene/Scene.cpp b/destrum/src/Scene/Scene.cpp index cf206db..f81b756 100644 --- a/destrum/src/Scene/Scene.cpp +++ b/destrum/src/Scene/Scene.cpp @@ -32,7 +32,7 @@ GameObject* Scene::CreateGameObject(std::string name) GameObject* rawPtr = obj.get(); - // obj->SetScene(this); + obj->SetScene(this); m_pendingAdditions.emplace_back(std::move(obj)); return rawPtr; @@ -61,15 +61,9 @@ void Scene::Load() { } void Scene::Update() { - if (!m_pendingAdditions.empty()) { - for (auto& obj : m_pendingAdditions) { - m_objects.emplace_back(std::move(obj)); - } - m_pendingAdditions.clear(); - } + CommitPendingAdditions(); - - for (const auto& object: m_objects) { + for (const auto& object : m_objects) { if (object->IsActiveInHierarchy()) { object->Update(); } @@ -155,3 +149,15 @@ void Scene::DestroyGameObjects() { assert(m_BeingUnloaded && "Scene is being cleared but not unloaded? Weird"); } } + +void Scene::CommitPendingAdditions() { + if (m_pendingAdditions.empty()) { + return; + } + + for (auto& obj : m_pendingAdditions) { + m_objects.emplace_back(std::move(obj)); + } + + m_pendingAdditions.clear(); +} \ No newline at end of file diff --git a/destrum/src/Serialization/ComponentRegistry.cpp b/destrum/src/Serialization/ComponentRegistry.cpp new file mode 100644 index 0000000..dee115a --- /dev/null +++ b/destrum/src/Serialization/ComponentRegistry.cpp @@ -0,0 +1,56 @@ +// destrum/Serialization/ComponentRegistry.cpp + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +void RegisterEngineComponents() +{ + static bool registered = false; + if (registered) { + return; + } + + registered = true; + + ComponentFactory::Register("MeshRenderer", [](GameObject& owner) { + return owner.AddComponent(); + }); + + // ComponentFactory::Register("Rotator", [](GameObject& owner) { + // return owner.AddComponent(); + // }); + + // ComponentFactory::Register("Spinner", [](GameObject& owner) { + // return owner.AddComponent(); + // }); + + // ComponentFactory::Register("OrbitAndSpin", [](GameObject& owner) { + // return owner.AddComponent(); + // }); + + ComponentFactory::Register("Animator", [](GameObject& owner) { + return owner.AddComponent(); + }); + + ComponentFactory::Register("Rigidbody", [](GameObject& owner) { + return owner.AddComponent(); + }); + + ComponentFactory::Register("BoxCollider", [](GameObject& owner) { + return owner.AddComponent(); + }); + + ComponentFactory::Register("SphereCollider", [](GameObject& owner) { + return owner.AddComponent(); + }); +} \ No newline at end of file diff --git a/destrum/src/Serialization/SceneSerializer.cpp b/destrum/src/Serialization/SceneSerializer.cpp new file mode 100644 index 0000000..6d4a5a8 --- /dev/null +++ b/destrum/src/Serialization/SceneSerializer.cpp @@ -0,0 +1,223 @@ +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) { + scene.CommitPendingAdditions(); + + json root; + + root["version"] = 1; + root["name"] = scene.GetName(); + root["objects"] = json::array(); + + for (const auto& objectPtr : scene.GetObjects()) { + if (!objectPtr) { + continue; + } + + const GameObject& object = *objectPtr; + + json objectJson; + objectJson["id"] = object.GetId(); + objectJson["name"] = object.GetName(); + objectJson["active"] = object.IsActive(); + + // Transform should be saved separately because in your engine it is not a component. + // Replace this with your actual Transform getters. + objectJson["transform"] = { + // Example: + // { "position", { object.GetTransform().GetLocalPosition().x, + // object.GetTransform().GetLocalPosition().y, + // object.GetTransform().GetLocalPosition().z } }, + // { "rotation", { ... } }, + // { "scale", { ... } } + }; + + const Transform* parent = object.GetTransform().GetParent(); + objectJson["parent"] = parent + ? parent->GetOwner()->GetId() + : InvalidObjectId; + + objectJson["components"] = json::array(); + + for (const auto& componentPtr : object.GetComponents()) { + if (!componentPtr) { + continue; + } + + const Component& component = *componentPtr; + + json componentJson; + componentJson["type"] = component.GetTypeName(); + componentJson["enabled"] = component.isEnabled(); + componentJson["data"] = component.Serialize(); + + objectJson["components"].push_back(componentJson); + } + + root["objects"].push_back(objectJson); + } + + std::ofstream file(path); + if (!file.is_open()) { + std::cerr << "Failed to open scene file for writing: " << path << '\n'; + return false; + } + + file << root.dump(4); + return true; +} + +bool SceneSerializer::Load(Scene& scene, const std::filesystem::path& path) { + std::ifstream file(path); + if (!file.is_open()) { + std::cerr << "Failed to open scene file for reading: " << path << '\n'; + return false; + } + + json root; + + try { + file >> root; + } catch (const std::exception& e) { + std::cerr << "Failed to parse scene file: " << e.what() << '\n'; + return false; + } + + scene.RemoveAll(); + + std::unordered_map idMap; + std::vector objectJsonList; + + try { + const auto& objectsJson = root.at("objects"); + + // Pass 1: + // Create all GameObjects first. + for (const auto& objectJson : objectsJson) { + const auto id = objectJson.at("id").get(); + const std::string name = objectJson.value("name", "GameObject"); + const bool active = objectJson.value("active", true); + + GameObject* object = scene.CreateGameObject(name); + object->SetIdForDeserialization(id); + object->SetActive(active); + + idMap[id] = object; + objectJsonList.push_back(&objectJson); + } + + scene.CommitPendingAdditions(); + + // Pass 2: + // Restore transforms and parent-child hierarchy. + for (const json* objectJson : objectJsonList) { + const auto id = objectJson->at("id").get(); + + GameObject* object = idMap.at(id); + + // Replace this with your actual Transform deserialization. + if (objectJson->contains("transform")) { + const json& transformJson = objectJson->at("transform"); + + auto position = transformJson.at("position"); + object->GetTransform().SetLocalPosition({ + position[0].get(), + position[1].get(), + position[2].get() + }); + + auto rotation = transformJson.at("rotation"); + object->GetTransform().SetLocalRotation({ + rotation[0].get(), + rotation[1].get(), + rotation[2].get() + }); + + auto scale = transformJson.at("scale"); + object->GetTransform().SetLocalScale({ + scale[0].get(), + scale[1].get(), + scale[2].get() + }); + } + + const ObjectId parentId = objectJson->value("parent", InvalidObjectId); + + if (parentId != InvalidObjectId) { + auto parentIt = idMap.find(parentId); + + if (parentIt != idMap.end()) { + object->GetTransform().SetParent(&parentIt->second->GetTransform()); + } else { + std::cerr << "Parent not found while loading object " << id << '\n'; + } + } + } + + // Pass 3: + // Create and deserialize components. + for (const json* objectJson : objectJsonList) { + const auto id = objectJson->at("id").get(); + GameObject* object = idMap.at(id); + + if (!objectJson->contains("components")) { + continue; + } + + for (const auto& componentJson : objectJson->at("components")) { + const std::string type = componentJson.at("type").get(); + + Component* component = ComponentFactory::Create(type, *object); + + if (!component) { + std::cerr << "Unknown component type while loading scene: " << type << '\n'; + continue; + } + + component->SetEnabled(componentJson.value("enabled", true)); + + if (componentJson.contains("data")) { + component->Deserialize(componentJson.at("data")); + } + } + } + + // Pass 4: + // Resolve object references now that all objects and components exist. + for (const auto& objectPtr : scene.GetObjects()) { + if (!objectPtr) { + continue; + } + + for (const auto& componentPtr : objectPtr->GetComponents()) { + if (!componentPtr) { + continue; + } + + componentPtr->ResolveReferences(idMap); + } + } + + } catch (const std::exception& e) { + std::cerr << "Failed to load scene: " << e.what() << '\n'; + scene.RemoveAll(); + return false; + } + + return true; +} \ No newline at end of file