Added saving and loading

This commit is contained in:
Erris
2026-02-19 23:45:10 +01:00
parent e0396fedd1
commit a897d5c798
99 changed files with 889 additions and 9784 deletions

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.28)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
set(PROJECT_EXECUTABLE_NAME open_engine)
@@ -11,6 +11,8 @@ project(OpenEngine
add_definitions( -DOE_ENABLE_ASSERTS )
find_package(imgui REQUIRED)
find_package(EnTT REQUIRED)
find_package(yaml-cpp REQUIRED)
file(GLOB_RECURSE SRC_FILES "src/*.cpp")
add_library(${PROJECT_EXECUTABLE_NAME} STATIC
@@ -30,6 +32,7 @@ target_include_directories(${PROJECT_EXECUTABLE_NAME} PRIVATE
target_include_directories(${PROJECT_EXECUTABLE_NAME} PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"/home/erris/.conan2/p/b/imguic69fe98538919/p/include"
"vendor/nativefiledialog-extended/src/include"
)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
@@ -42,7 +45,10 @@ target_link_libraries(${PROJECT_EXECUTABLE_NAME} PUBLIC
glfw
glm
dl
EnTT::EnTT
X11
yaml-cpp::yaml-cpp
nfd
)
#target_link_directories(${PROJECT_EXECUTABLE_NAME} PRIVATE
@@ -52,3 +58,5 @@ target_link_libraries(${PROJECT_EXECUTABLE_NAME} PUBLIC
add_subdirectory(vendor/glad
./lib
)
add_subdirectory("vendor/nativefiledialog-extended")

View File

@@ -2,14 +2,18 @@
#define OPEN_ENGINE_HPP
#include "open_engine/orthographic_camera_controller.hpp"
#include "open_engine/scene/native_scriptable_entity.hpp"
#include "open_engine/imgui/imgui_layer.hpp"
#include "open_engine/events/key_event.hpp"
#include "open_engine/scene/components.hpp"
#include "open_engine/application.hpp"
#include "open_engine/ref_scope.hpp"
#include "open_engine/logging.hpp"
#include "open_engine/scene/native_scriptable_entity.hpp"
#include "open_engine/scene/scene_serializer.hpp"
#include "open_engine/scene/components.hpp"
#include "open_engine/scene/components.hpp"
#include "open_engine/core/file_dialogs.hpp"
#include "open_engine/core/time.hpp"
#include "open_engine/core.hpp"
@@ -23,7 +27,6 @@
#include "open_engine/renderer/renderer2d.hpp"
#include "open_engine/renderer/renderer.hpp"
#include "open_engine/renderer/texture.hpp"
#include "open_engine/scene/components.hpp"
#include "open_engine/renderer/buffer.hpp"
#include "open_engine/renderer/shader.hpp"
#include "open_engine/scene/entity.hpp"

View File

@@ -0,0 +1,16 @@
#ifndef FILE_DIALOGS_HPP
#define FILE_DIALOGS_HPP
// TODO: Un-Class this?
namespace OpenEngine {
class FileDialogs
{
public:
static std::string OpenFile(const char* filters);
static std::string SaveFile(const char* filters);
};
}
#endif // FILE_DIALOGS_HPP

View File

@@ -34,7 +34,6 @@ namespace OpenEngine {
std::memset(buffer, 0, sizeof(buffer));
std::strncpy(buffer, tag.c_str(), sizeof(buffer));
ImGui::Text("Name");
if (ImGui::InputText("##tagEditor", buffer, sizeof(buffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)
|| (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)))
tag = buffer;
@@ -68,7 +67,9 @@ namespace OpenEngine {
void OnImGuiRender(Entity& entity)
{
DrawVec3Control("Position", translation);
DrawVec3Control("Rotation", rotation);
glm::vec3 rotation_comp = glm::degrees(rotation);
DrawVec3Control("Rotation", rotation_comp);
rotation = glm::radians(rotation_comp);
DrawVec3Control("Scale", scale);
};
};

View File

@@ -5,6 +5,8 @@
#include "open_engine/scene/scene.hpp"
#include <cstdint>
#include <entt/entity/fwd.hpp>
#include <entt/entt.hpp>
#include <utility>
@@ -44,6 +46,7 @@ namespace OpenEngine {
};
operator bool() const { return handle != entt::null; };
operator entt::entity() const { return handle; };
operator uint32_t() const { return (uint32_t)handle; };
bool operator ==(const Entity& other) const {

View File

@@ -11,6 +11,8 @@ namespace OpenEngine {
virtual ~NativeScriptableEntity() = default;
template <typename T>
T& GetComponent() { return entity.GetComponents<T>(); };
template <typename T>
bool HasComponent() { return entity.HasComponent<T>(); };
protected:
virtual void OnCreate() {};

View File

@@ -15,6 +15,7 @@ namespace OpenEngine {
~Scene() = default;
Entity CreateEntity(const std::string& name = std::string());
void DeleteEntity(Entity entity);
void OnUpdate();
void OnViewportResize(uint32_t width, uint32_t height);
@@ -26,6 +27,7 @@ namespace OpenEngine {
uint32_t viewport_width = 0, viewport_height = 0;
friend class SceneSerializer;
friend class Entity;
};
}

View File

@@ -0,0 +1,25 @@
#ifndef SCENE_SERIALIZER_HPP
#define SCENE_SERIALIZER_HPP
#include "open_engine/ref_scope.hpp"
#include "open_engine/scene/scene.hpp"
namespace OpenEngine {
class SceneSerializer
{
public:
SceneSerializer(const Ref<Scene>& scene);
void Serialize(const std::string& file_path);
void SerializeRuntime(const std::string& file_path);
bool Deserialize(const std::string& file_path);
bool DeserializeRuntime(const std::string& file_path);
private:
Ref<Scene> context;
};
}
#endif // SCENE_SERIALIZER_HPP

View File

@@ -0,0 +1,74 @@
#include "logging.hpp"
#include "nfd.h"
#include <pch.hpp>
#include <core/file_dialogs.hpp>
#include <nfd.hpp>
namespace OpenEngine {
std::string FileDialogs::OpenFile(const char* filters)
{
NFD_Init();
std::string path;
nfdu8char_t *outPath;
nfdu8filteritem_t nfd_filters[1] = { { "Open Engine save file", "oes" } };
nfdopendialogu8args_t args = {0};
args.filterList = nfd_filters;
args.filterCount = 1;
args.defaultPath = ".";
nfdresult_t result = NFD_OpenDialogU8_With(&outPath, &args);
if (result == NFD_OKAY)
{
path = outPath;
NFD_FreePathU8(outPath);
NFD_Quit();
return path;
}
else if (result == NFD_CANCEL)
{
OE_CORE_TRACE("User canceled.");
NFD_Quit();
return std::string();
}
else
{
OE_CORE_ERROR("{}", NFD_GetError());
NFD_Quit();
return std::string();
}
}
// TODO: Debug Make work consistently
std::string FileDialogs::SaveFile(const char* filters)
{
NFD_Init();
std::string path;
nfdchar_t* savePath;
// prepare filters for the dialog
nfdfilteritem_t filterItem[1] = {{"Open Engine save file", "oes"}};
// show the dialog
nfdresult_t result = NFD_SaveDialog(&savePath, filterItem, 1, NULL, "untitled.oes");
if (result == NFD_OKAY) {
path = savePath;
NFD_FreePath(savePath);
NFD_Quit();
return path;
NFD_Quit();
} else if (result == NFD_CANCEL) {
puts("User pressed cancel.");
NFD_Quit();
return std::string();
} else {
printf("Error: %s\n", NFD_GetError());
NFD_Quit();
return std::string();
}
}
}

View File

@@ -274,6 +274,9 @@ namespace OpenEngine {
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
io.Fonts->AddFontFromFileTTF("assets/fonts/opensans.ttf", 18.0f);
io.FontDefault = io.Fonts->AddFontFromFileTTF("assets/fonts/opensans.ttf", 18.0f);
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {

View File

@@ -9,6 +9,9 @@ namespace OpenEngine {
float reset_value, float column_width,
const std::array<const char*, 3> labels)
{
ImGuiIO& io = ImGui::GetIO();
auto bold_font = io.Fonts->Fonts[0];
ImGui::PushID(label);
ImVec2 item_spacing = { 15.0f, 0.0f };
ImGui::Columns(2);
@@ -28,12 +31,14 @@ namespace OpenEngine {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 1.0f, 0.8f, 0.9f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.953f, 0.545f, 0.659f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.0f, 0.0f, 0.0f, 1.0f });
ImGui::PushFont(bold_font);
if (ImGui::Button(labels[0], button_size))
values.x = reset_value;
ImGui::PopFont();
ImGui::PopStyleColor(4);
ImGui::SameLine();
ImGui::DragFloat("##X", &values.x, 0.1f);
ImGui::DragFloat("##X", &values.x, 0.1f, 0.0f, 0.0f, "%.2f");
ImGui::PopItemWidth();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, item_spacing);
ImGui::SameLine();
@@ -42,13 +47,15 @@ namespace OpenEngine {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.9f, 1.0f, 0.9f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.650f, 0.890f, 0.631f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.0f, 0.0f, 0.0f, 1.0f });
ImGui::PushFont(bold_font);
if (ImGui::Button(labels[1], button_size))
values.y = reset_value;
ImGui::PopFont();
ImGui::PopStyleColor(4);
ImGui::PopStyleVar();
ImGui::SameLine();
ImGui::DragFloat("##Y", &values.y, 0.1f);
ImGui::DragFloat("##Y", &values.y, 0.1f, 0.0f, 0.0f, "%.2f");
ImGui::PopItemWidth();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, item_spacing);
ImGui::SameLine();
@@ -57,13 +64,15 @@ namespace OpenEngine {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.7f, 0.9f, 1.0f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.533f, 0.698f, 0.976f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.0f, 0.0f, 0.0f, 1.0f });
ImGui::PushFont(bold_font);
if (ImGui::Button(labels[2], button_size))
values.z = reset_value;
ImGui::PopFont();
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(1);
ImGui::SameLine();
ImGui::DragFloat("##Z", &values.z, 0.1f);
ImGui::DragFloat("##Z", &values.z, 0.1f, 0.0f, 0.0f, "%.2f");
ImGui::PopItemWidth();
ImGui::PopStyleVar();

View File

@@ -16,6 +16,11 @@ namespace OpenEngine {
return entity;
}
void Scene::DeleteEntity(Entity entity)
{
registry.destroy(entity);
}
void Scene::OnUpdate()
{

View File

@@ -0,0 +1,255 @@
#include <pch.hpp>
#include "core.hpp"
#include <scene/scene_serializer.hpp>
#include "scene/components.hpp"
#include "scene/entity.hpp"
#include <fstream>
#include <yaml-cpp/emittermanip.h>
#include <yaml-cpp/yaml.h>
namespace YAML {
template<>
struct convert<glm::vec3>
{
static Node encode(const glm::vec3& rhs)
{
Node node;
node.push_back(rhs.x);
node.push_back(rhs.y);
node.push_back(rhs.z);
return node;
}
static bool decode(const Node& node, glm::vec3& rhs)
{
if (!node.IsSequence() || node.size() != 3)
return false;
rhs.x = node[0].as<float>();
rhs.y = node[1].as<float>();
rhs.z = node[2].as<float>();
return true;
}
};
template<>
struct convert<glm::vec4>
{
static Node encode(const glm::vec4& rhs)
{
Node node;
node.push_back(rhs.x);
node.push_back(rhs.y);
node.push_back(rhs.z);
node.push_back(rhs.w);
return node;
}
static bool decode(const Node& node, glm::vec4& rhs)
{
if (!node.IsSequence() || node.size() != 4)
return false;
rhs.x = node[0].as<float>();
rhs.y = node[1].as<float>();
rhs.z = node[2].as<float>();
rhs.w = node[3].as<float>();
return true;
}
};
}
namespace OpenEngine {
YAML::Emitter& operator<<(YAML::Emitter& out, const glm::vec3& v)
{
out << YAML::Flow;
out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq;
return out;
}
YAML::Emitter& operator<<(YAML::Emitter& out, const glm::vec4& v)
{
out << YAML::Flow;
out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq;
return out;
}
SceneSerializer::SceneSerializer(const Ref<Scene>& scene)
: context(scene)
{
}
static void SerializeEntity(YAML::Emitter& out, Entity entity)
{
out << YAML::BeginMap;
out << YAML::Key << "Entity" << YAML::Value << "412741205"; // Needs random
if (entity.HasComponent<TagComponent>())
{
out << YAML::Key << "TagComponent";
out << YAML::BeginMap; // TagComponent
auto& tag = entity.GetComponents<TagComponent>().tag;
out << YAML::Key << "Tag" << YAML::Value << tag;
out << YAML::EndMap; // TagComponent
}
if (entity.HasComponent<TransformComponent>())
{
out << YAML::Key << "TransformComponent";
out << YAML::BeginMap; // TransformComponent
auto& tc = entity.GetComponents<TransformComponent>();
out << YAML::Key << "Translation" << YAML::Value << tc.translation;
out << YAML::Key << "Rotation" << YAML::Value << tc.rotation;
out << YAML::Key << "Scale" << YAML::Value << tc.scale;
out << YAML::EndMap; // TransformComponent
}
if (entity.HasComponent<CameraComponent>())
{
out << YAML::Key << "CameraComponent";
out << YAML::BeginMap; // CameraComponent
auto& cameraComponent = entity.GetComponents<CameraComponent>();
auto& camera = cameraComponent.camera;
out << YAML::Key << "Camera" << YAML::Value;
out << YAML::BeginMap; // Camera
out << YAML::Key << "ProjectionType" << YAML::Value << (int)camera.GetProjectionType();
out << YAML::Key << "PerspectiveFOV" << YAML::Value << camera.GetVerticalFov();
out << YAML::Key << "PerspectiveNear" << YAML::Value << camera.GetPerspectiveNearClip();
out << YAML::Key << "PerspectiveFar" << YAML::Value << camera.GetPerspectiveFarClip();
out << YAML::Key << "OrthographicSize" << YAML::Value << camera.GetOrthographicSize();
out << YAML::Key << "OrthographicNear" << YAML::Value << camera.GetOrthographicNearClip();
out << YAML::Key << "OrthographicFar" << YAML::Value << camera.GetOrthographicFarClip();
out << YAML::EndMap; // Camera
out << YAML::Key << "Primary" << YAML::Value << cameraComponent.primary;
out << YAML::Key << "FixedAspectRatio" << YAML::Value << cameraComponent.fixed_aspect_ratio;
out << YAML::EndMap; // CameraComponent
}
if (entity.HasComponent<SpriteRendererComponent>())
{
out << YAML::Key << "SpriteRendererComponent";
out << YAML::BeginMap; // SpriteRendererComponent
auto& spriteRendererComponent = entity.GetComponents<SpriteRendererComponent>();
out << YAML::Key << "Color" << YAML::Value << spriteRendererComponent.color;
out << YAML::EndMap; // SpriteRendererComponent
}
out << YAML::EndMap;
}
void SceneSerializer::Serialize(const std::string& file_path)
{
YAML::Emitter out;
out << YAML::BeginMap;
out << YAML::Key << "Scene" << YAML::Value << "N/A";
out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq;
for (auto entity_id : context->registry.view<entt::entity>()) {
Entity entity = { entity_id, context.get()};
if (!entity)
return;
SerializeEntity(out, entity);
}
out << YAML::EndSeq;
out << YAML::EndMap;
std::ofstream fout(file_path);
fout << out.c_str();
}
void SceneSerializer::SerializeRuntime(const std::string& file_path)
{
OE_CORE_ASSERT(false, "Not implemented yet");
}
bool SceneSerializer::Deserialize(const std::string& file_path)
{
std::ifstream stream(file_path);
std::stringstream strStream;
strStream << stream.rdbuf();
YAML::Node data = YAML::Load(strStream.str());
if (!data["Scene"])
return false;
std::string sceneName = data["Scene"].as<std::string>();
OE_CORE_TRACE("Deserializing scene '{0}'", sceneName);
auto entities = data["Entities"];
if (entities)
{
for (auto entity : entities)
{
uint64_t uuid = entity["Entity"].as<uint64_t>(); // TODO
std::string name;
auto tagComponent = entity["TagComponent"];
if (tagComponent)
name = tagComponent["Tag"].as<std::string>();
OE_CORE_TRACE("Deserialized entity with ID = {0}, name = {1}", uuid, name);
Entity deserializedEntity = context->CreateEntity(name);
auto transformComponent = entity["TransformComponent"];
if (transformComponent)
{
auto& tc = deserializedEntity.AddComponents<TransformComponent>();
tc.translation = transformComponent["Translation"].as<glm::vec3>();
tc.rotation = transformComponent["Rotation"].as<glm::vec3>();
tc.scale = transformComponent["Scale"].as<glm::vec3>();
}
auto cameraComponent = entity["CameraComponent"];
if (cameraComponent)
{
auto& cc = deserializedEntity.AddComponents<CameraComponent>();
auto camera_props = cameraComponent["Camera"];
cc.camera.SetProjectionType((SceneCamera::ProjectionType)camera_props["ProjectionType"].as<int>());
cc.camera.SetVerticalFov(camera_props["PerspectiveFOV"].as<float>());
cc.camera.SetPerspectiveNearClip(camera_props["PerspectiveNear"].as<float>());
cc.camera.SetPerspectiveFarClip(camera_props["PerspectiveFar"].as<float>());
cc.camera.SetOrthographicSize(camera_props["OrthographicSize"].as<float>());
cc.camera.SetOrthographicNearClip(camera_props["OrthographicNear"].as<float>());
cc.camera.SetOrthographicFarClip(camera_props["OrthographicFar"].as<float>());
cc.primary = cameraComponent["Primary"].as<bool>();
cc.fixed_aspect_ratio = cameraComponent["FixedAspectRatio"].as<bool>();
}
auto spriteRendererComponent = entity["SpriteRendererComponent"];
if (spriteRendererComponent)
{
auto& src = deserializedEntity.AddComponents<SpriteRendererComponent>();
src.color = spriteRendererComponent["Color"].as<glm::vec4>();
}
}
}
return true;
}
bool SceneSerializer::DeserializeRuntime(const std::string& file_path)
{
OE_CORE_ASSERT(false, "Not implemented yet");
return false;
}
}