Fix editor scene persistence and XC scene workflow
This commit is contained in:
@@ -8,15 +8,69 @@
|
|||||||
#include <imgui_impl_win32.h>
|
#include <imgui_impl_win32.h>
|
||||||
#include <imgui_impl_dx12.h>
|
#include <imgui_impl_dx12.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
#include <filesystem>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string WideToUtf8(const std::wstring& value) {
|
||||||
|
if (value.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||||
|
if (len <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result(len - 1, '\0');
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, &result[0], len, nullptr, nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring Utf8ToWide(const std::string& value) {
|
||||||
|
if (value.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
|
||||||
|
if (len <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring result(len - 1, L'\0');
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, &result[0], len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetExecutableDirectoryUtf8() {
|
||||||
|
wchar_t exePath[MAX_PATH];
|
||||||
|
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
||||||
|
|
||||||
|
std::wstring exeDirW(exePath);
|
||||||
|
const size_t pos = exeDirW.find_last_of(L"\\/");
|
||||||
|
if (pos != std::wstring::npos) {
|
||||||
|
exeDirW = exeDirW.substr(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WideToUtf8(exeDirW);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetExecutableLogPath(const char* fileName) {
|
||||||
|
return GetExecutableDirectoryUtf8() + "\\" + fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) {
|
static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) {
|
||||||
const char* logPath = "D:\\Xuanchi\\Main\\XCEngine\\editor\\bin\\Release\\crash.log";
|
const std::string logPath = GetExecutableLogPath("crash.log");
|
||||||
|
|
||||||
FILE* f = fopen(logPath, "a");
|
FILE* f = nullptr;
|
||||||
|
fopen_s(&f, logPath.c_str(), "a");
|
||||||
if (f) {
|
if (f) {
|
||||||
fprintf(f, "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n",
|
fprintf(f, "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n",
|
||||||
exceptionPointers->ExceptionRecord->ExceptionCode,
|
exceptionPointers->ExceptionRecord->ExceptionCode,
|
||||||
@@ -45,20 +99,7 @@ bool Application::Initialize(HWND hwnd) {
|
|||||||
|
|
||||||
// Redirect stderr to log file to capture ImGui errors
|
// Redirect stderr to log file to capture ImGui errors
|
||||||
{
|
{
|
||||||
wchar_t exePath[MAX_PATH];
|
const std::string stderrPath = GetExecutableLogPath("stderr.log");
|
||||||
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
|
||||||
std::wstring exeDirW(exePath);
|
|
||||||
size_t pos = exeDirW.find_last_of(L"\\/");
|
|
||||||
if (pos != std::wstring::npos) {
|
|
||||||
exeDirW = exeDirW.substr(0, pos);
|
|
||||||
}
|
|
||||||
std::string exeDir;
|
|
||||||
int len = WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
|
||||||
if (len > 0) {
|
|
||||||
exeDir.resize(len - 1);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
std::string stderrPath = exeDir + "\\stderr.log";
|
|
||||||
freopen(stderrPath.c_str(), "w", stderr);
|
freopen(stderrPath.c_str(), "w", stderr);
|
||||||
|
|
||||||
fprintf(stderr, "[TEST] stderr redirection test - this should appear in stderr.log\n");
|
fprintf(stderr, "[TEST] stderr redirection test - this should appear in stderr.log\n");
|
||||||
@@ -70,19 +111,7 @@ bool Application::Initialize(HWND hwnd) {
|
|||||||
Debug::Logger::Get().AddSink(std::make_unique<Debug::EditorConsoleSink>());
|
Debug::Logger::Get().AddSink(std::make_unique<Debug::EditorConsoleSink>());
|
||||||
|
|
||||||
// Get exe directory for log file path
|
// Get exe directory for log file path
|
||||||
wchar_t exePath[MAX_PATH];
|
const std::string exeDir = GetExecutableDirectoryUtf8();
|
||||||
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
|
||||||
std::wstring exeDirW(exePath);
|
|
||||||
size_t pos = exeDirW.find_last_of(L"\\/");
|
|
||||||
if (pos != std::wstring::npos) {
|
|
||||||
exeDirW = exeDirW.substr(0, pos);
|
|
||||||
}
|
|
||||||
std::string exeDir;
|
|
||||||
int len = WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
|
||||||
if (len > 0) {
|
|
||||||
exeDir.resize(len - 1);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
std::string logPath = exeDir + "\\editor.log";
|
std::string logPath = exeDir + "\\editor.log";
|
||||||
Debug::Logger::Get().AddSink(std::make_unique<Debug::FileLogSink>(logPath.c_str()));
|
Debug::Logger::Get().AddSink(std::make_unique<Debug::FileLogSink>(logPath.c_str()));
|
||||||
Debug::Logger::Get().Info(Debug::LogCategory::General, "Editor Application starting...");
|
Debug::Logger::Get().Info(Debug::LogCategory::General, "Editor Application starting...");
|
||||||
@@ -154,6 +183,7 @@ void Application::Render() {
|
|||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
m_layerStack.onImGuiRender();
|
m_layerStack.onImGuiRender();
|
||||||
|
UpdateWindowTitle();
|
||||||
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
|
||||||
@@ -198,6 +228,34 @@ void Application::Render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::UpdateWindowTitle() {
|
||||||
|
if (!m_hwnd || !m_editorContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& sceneManager = m_editorContext->GetSceneManager();
|
||||||
|
std::string sceneName = sceneManager.HasActiveScene() ? sceneManager.GetCurrentSceneName() : "No Scene";
|
||||||
|
if (sceneName.empty()) {
|
||||||
|
sceneName = "Untitled Scene";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sceneManager.IsSceneDirty()) {
|
||||||
|
sceneName += " *";
|
||||||
|
}
|
||||||
|
if (sceneManager.GetCurrentScenePath().empty()) {
|
||||||
|
sceneName += " (Unsaved)";
|
||||||
|
} else {
|
||||||
|
sceneName += " - ";
|
||||||
|
sceneName += std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::wstring title = Utf8ToWide(sceneName + " - XCVolumeRenderer - Unity Style Editor");
|
||||||
|
if (title != m_lastWindowTitle) {
|
||||||
|
SetWindowTextW(m_hwnd, title.c_str());
|
||||||
|
m_lastWindowTitle = title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::OnResize(int width, int height) {
|
void Application::OnResize(int width, int height) {
|
||||||
if (width <= 0 || height <= 0) return;
|
if (width <= 0 || height <= 0) return;
|
||||||
|
|
||||||
@@ -303,4 +361,4 @@ void Application::CleanupRenderTarget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <d3d12.h>
|
#include <d3d12.h>
|
||||||
#include <dxgi1_6.h>
|
#include <dxgi1_6.h>
|
||||||
@@ -23,6 +24,7 @@ public:
|
|||||||
void Shutdown();
|
void Shutdown();
|
||||||
void Render();
|
void Render();
|
||||||
void OnResize(int width, int height);
|
void OnResize(int width, int height);
|
||||||
|
HWND GetWindowHandle() const { return m_hwnd; }
|
||||||
|
|
||||||
IEditorContext& GetEditorContext() const { return *m_editorContext; }
|
IEditorContext& GetEditorContext() const { return *m_editorContext; }
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ private:
|
|||||||
bool CreateDevice();
|
bool CreateDevice();
|
||||||
bool CreateRenderTarget();
|
bool CreateRenderTarget();
|
||||||
void CleanupRenderTarget();
|
void CleanupRenderTarget();
|
||||||
|
void UpdateWindowTitle();
|
||||||
|
|
||||||
HWND m_hwnd = nullptr;
|
HWND m_hwnd = nullptr;
|
||||||
int m_width = 1280;
|
int m_width = 1280;
|
||||||
@@ -54,7 +57,8 @@ private:
|
|||||||
Core::LayerStack m_layerStack;
|
Core::LayerStack m_layerStack;
|
||||||
EditorLayer* m_editorLayer = nullptr;
|
EditorLayer* m_editorLayer = nullptr;
|
||||||
std::shared_ptr<IEditorContext> m_editorContext;
|
std::shared_ptr<IEditorContext> m_editorContext;
|
||||||
|
std::wstring m_lastWindowTitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
108
editor/src/ComponentEditors/CameraComponentEditor.h
Normal file
108
editor/src/ComponentEditors/CameraComponentEditor.h
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IComponentEditor.h"
|
||||||
|
#include "UI/UI.h"
|
||||||
|
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
class CameraComponentEditor : public IComponentEditor {
|
||||||
|
public:
|
||||||
|
const char* GetDisplayName() const override {
|
||||||
|
return "Camera";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanEdit(::XCEngine::Components::Component* component) const override {
|
||||||
|
return dynamic_cast<::XCEngine::Components::CameraComponent*>(component) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Render(::XCEngine::Components::Component* component) override {
|
||||||
|
auto* camera = dynamic_cast<::XCEngine::Components::CameraComponent*>(component);
|
||||||
|
if (!camera) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int projectionType = static_cast<int>(camera->GetProjectionType());
|
||||||
|
const char* projectionLabels[] = { "Perspective", "Orthographic" };
|
||||||
|
bool changed = false;
|
||||||
|
if (ImGui::Combo("Projection", &projectionType, projectionLabels, 2)) {
|
||||||
|
camera->SetProjectionType(static_cast<::XCEngine::Components::CameraProjectionType>(projectionType));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera->GetProjectionType() == ::XCEngine::Components::CameraProjectionType::Perspective) {
|
||||||
|
float fieldOfView = camera->GetFieldOfView();
|
||||||
|
if (UI::DrawSliderFloat("Field Of View", fieldOfView, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||||
|
camera->SetFieldOfView(fieldOfView);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
float orthographicSize = camera->GetOrthographicSize();
|
||||||
|
if (UI::DrawFloat("Orthographic Size", orthographicSize, 100.0f, 0.1f, 0.001f)) {
|
||||||
|
camera->SetOrthographicSize(orthographicSize);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float nearClip = camera->GetNearClipPlane();
|
||||||
|
if (UI::DrawFloat("Near Clip", nearClip, 100.0f, 0.01f, 0.001f)) {
|
||||||
|
camera->SetNearClipPlane(nearClip);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float farClip = camera->GetFarClipPlane();
|
||||||
|
if (UI::DrawFloat("Far Clip", farClip, 100.0f, 0.1f, nearClip + 0.001f)) {
|
||||||
|
camera->SetFarClipPlane(farClip);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float depth = camera->GetDepth();
|
||||||
|
if (UI::DrawFloat("Depth", depth, 100.0f, 0.1f)) {
|
||||||
|
camera->SetDepth(depth);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool primary = camera->IsPrimary();
|
||||||
|
if (UI::DrawBool("Primary", primary)) {
|
||||||
|
camera->SetPrimary(primary);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float clearColor[4] = {
|
||||||
|
camera->GetClearColor().r,
|
||||||
|
camera->GetClearColor().g,
|
||||||
|
camera->GetClearColor().b,
|
||||||
|
camera->GetClearColor().a
|
||||||
|
};
|
||||||
|
if (UI::DrawColor4("Clear Color", clearColor)) {
|
||||||
|
camera->SetClearColor(::XCEngine::Math::Color(clearColor[0], clearColor[1], clearColor[2], clearColor[3]));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
return gameObject && !gameObject->GetComponent<::XCEngine::Components::CameraComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
if (!gameObject) {
|
||||||
|
return "Invalid";
|
||||||
|
}
|
||||||
|
return gameObject->GetComponent<::XCEngine::Components::CameraComponent>() ? "Already Added" : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
return gameObject ? gameObject->AddComponent<::XCEngine::Components::CameraComponent>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanRemove(::XCEngine::Components::Component* component) const override {
|
||||||
|
return CanEdit(component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
35
editor/src/ComponentEditors/IComponentEditor.h
Normal file
35
editor/src/ComponentEditors/IComponentEditor.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Components/Component.h>
|
||||||
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
class IComponentEditor {
|
||||||
|
public:
|
||||||
|
virtual ~IComponentEditor() = default;
|
||||||
|
|
||||||
|
virtual const char* GetDisplayName() const = 0;
|
||||||
|
virtual bool CanEdit(::XCEngine::Components::Component* component) const = 0;
|
||||||
|
virtual bool Render(::XCEngine::Components::Component* component) = 0;
|
||||||
|
|
||||||
|
virtual bool ShowInAddComponentMenu() const { return true; }
|
||||||
|
virtual bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const { return false; }
|
||||||
|
virtual const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const {
|
||||||
|
(void)gameObject;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
virtual ::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const {
|
||||||
|
(void)gameObject;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool CanRemove(::XCEngine::Components::Component* component) const {
|
||||||
|
(void)component;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
98
editor/src/ComponentEditors/LightComponentEditor.h
Normal file
98
editor/src/ComponentEditors/LightComponentEditor.h
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IComponentEditor.h"
|
||||||
|
#include "UI/UI.h"
|
||||||
|
|
||||||
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
class LightComponentEditor : public IComponentEditor {
|
||||||
|
public:
|
||||||
|
const char* GetDisplayName() const override {
|
||||||
|
return "Light";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanEdit(::XCEngine::Components::Component* component) const override {
|
||||||
|
return dynamic_cast<::XCEngine::Components::LightComponent*>(component) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Render(::XCEngine::Components::Component* component) override {
|
||||||
|
auto* light = dynamic_cast<::XCEngine::Components::LightComponent*>(component);
|
||||||
|
if (!light) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lightType = static_cast<int>(light->GetLightType());
|
||||||
|
const char* lightTypeLabels[] = { "Directional", "Point", "Spot" };
|
||||||
|
bool changed = false;
|
||||||
|
if (ImGui::Combo("Type", &lightType, lightTypeLabels, 3)) {
|
||||||
|
light->SetLightType(static_cast<::XCEngine::Components::LightType>(lightType));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float color[4] = {
|
||||||
|
light->GetColor().r,
|
||||||
|
light->GetColor().g,
|
||||||
|
light->GetColor().b,
|
||||||
|
light->GetColor().a
|
||||||
|
};
|
||||||
|
if (UI::DrawColor4("Color", color)) {
|
||||||
|
light->SetColor(::XCEngine::Math::Color(color[0], color[1], color[2], color[3]));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float intensity = light->GetIntensity();
|
||||||
|
if (UI::DrawFloat("Intensity", intensity, 100.0f, 0.1f, 0.0f)) {
|
||||||
|
light->SetIntensity(intensity);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (light->GetLightType() != ::XCEngine::Components::LightType::Directional) {
|
||||||
|
float range = light->GetRange();
|
||||||
|
if (UI::DrawFloat("Range", range, 100.0f, 0.1f, 0.001f)) {
|
||||||
|
light->SetRange(range);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (light->GetLightType() == ::XCEngine::Components::LightType::Spot) {
|
||||||
|
float spotAngle = light->GetSpotAngle();
|
||||||
|
if (UI::DrawSliderFloat("Spot Angle", spotAngle, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||||
|
light->SetSpotAngle(spotAngle);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool castsShadows = light->GetCastsShadows();
|
||||||
|
if (UI::DrawBool("Cast Shadows", castsShadows)) {
|
||||||
|
light->SetCastsShadows(castsShadows);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
return gameObject && !gameObject->GetComponent<::XCEngine::Components::LightComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
if (!gameObject) {
|
||||||
|
return "Invalid";
|
||||||
|
}
|
||||||
|
return gameObject->GetComponent<::XCEngine::Components::LightComponent>() ? "Already Added" : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
return gameObject ? gameObject->AddComponent<::XCEngine::Components::LightComponent>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanRemove(::XCEngine::Components::Component* component) const override {
|
||||||
|
return CanEdit(component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
67
editor/src/ComponentEditors/TransformComponentEditor.h
Normal file
67
editor/src/ComponentEditors/TransformComponentEditor.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IComponentEditor.h"
|
||||||
|
#include "UI/UI.h"
|
||||||
|
|
||||||
|
#include <XCEngine/Components/TransformComponent.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
class TransformComponentEditor : public IComponentEditor {
|
||||||
|
public:
|
||||||
|
const char* GetDisplayName() const override {
|
||||||
|
return "Transform";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanEdit(::XCEngine::Components::Component* component) const override {
|
||||||
|
return dynamic_cast<::XCEngine::Components::TransformComponent*>(component) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Render(::XCEngine::Components::Component* component) override {
|
||||||
|
auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component);
|
||||||
|
if (!transform) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::Math::Vector3 position = transform->GetLocalPosition();
|
||||||
|
::XCEngine::Math::Vector3 rotation = transform->GetLocalEulerAngles();
|
||||||
|
::XCEngine::Math::Vector3 scale = transform->GetLocalScale();
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (UI::DrawVec3("Position", position, 0.0f, 80.0f, 0.1f)) {
|
||||||
|
transform->SetLocalPosition(position);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UI::DrawVec3("Rotation", rotation, 0.0f, 80.0f, 1.0f)) {
|
||||||
|
transform->SetLocalEulerAngles(rotation);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UI::DrawVec3("Scale", scale, 1.0f, 80.0f, 0.1f)) {
|
||||||
|
transform->SetLocalScale(scale);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
(void)gameObject;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const override {
|
||||||
|
(void)gameObject;
|
||||||
|
return "Built-in";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanRemove(::XCEngine::Components::Component* component) const override {
|
||||||
|
(void)component;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -3,14 +3,22 @@
|
|||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Debug {
|
namespace Debug {
|
||||||
|
|
||||||
|
EditorConsoleSink* EditorConsoleSink::s_instance = nullptr;
|
||||||
|
|
||||||
EditorConsoleSink* EditorConsoleSink::GetInstance() {
|
EditorConsoleSink* EditorConsoleSink::GetInstance() {
|
||||||
static EditorConsoleSink instance;
|
static EditorConsoleSink fallbackInstance;
|
||||||
return &instance;
|
return s_instance ? s_instance : &fallbackInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorConsoleSink::EditorConsoleSink() = default;
|
EditorConsoleSink::EditorConsoleSink() {
|
||||||
|
s_instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
EditorConsoleSink::~EditorConsoleSink() = default;
|
EditorConsoleSink::~EditorConsoleSink() {
|
||||||
|
if (s_instance == this) {
|
||||||
|
s_instance = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EditorConsoleSink::Log(const LogEntry& entry) {
|
void EditorConsoleSink::Log(const LogEntry& entry) {
|
||||||
std::lock_guard<std::mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
@@ -26,7 +34,8 @@ void EditorConsoleSink::Log(const LogEntry& entry) {
|
|||||||
void EditorConsoleSink::Flush() {
|
void EditorConsoleSink::Flush() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<LogEntry>& EditorConsoleSink::GetLogs() const {
|
std::vector<LogEntry> EditorConsoleSink::GetLogs() const {
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
return m_logs;
|
return m_logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,4 +49,4 @@ void EditorConsoleSink::SetCallback(std::function<void()> callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Debug
|
} // namespace Debug
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public:
|
|||||||
void Log(const LogEntry& entry) override;
|
void Log(const LogEntry& entry) override;
|
||||||
void Flush() override;
|
void Flush() override;
|
||||||
|
|
||||||
const std::vector<LogEntry>& GetLogs() const;
|
std::vector<LogEntry> GetLogs() const;
|
||||||
void Clear();
|
void Clear();
|
||||||
void SetCallback(std::function<void()> callback);
|
void SetCallback(std::function<void()> callback);
|
||||||
|
|
||||||
@@ -27,8 +27,9 @@ private:
|
|||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
std::vector<LogEntry> m_logs;
|
std::vector<LogEntry> m_logs;
|
||||||
std::function<void()> m_callback;
|
std::function<void()> m_callback;
|
||||||
|
static EditorConsoleSink* s_instance;
|
||||||
static constexpr size_t MAX_LOGS = 1000;
|
static constexpr size_t MAX_LOGS = 1000;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Debug
|
} // namespace Debug
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public:
|
|||||||
EditorContext()
|
EditorContext()
|
||||||
: m_eventBus(std::make_unique<EventBus>())
|
: m_eventBus(std::make_unique<EventBus>())
|
||||||
, m_selectionManager(std::make_unique<SelectionManager>(*m_eventBus))
|
, m_selectionManager(std::make_unique<SelectionManager>(*m_eventBus))
|
||||||
, m_sceneManager(std::make_unique<SceneManager>())
|
, m_sceneManager(std::make_unique<SceneManager>(m_eventBus.get()))
|
||||||
, m_projectManager(std::make_unique<ProjectManager>()) {
|
, m_projectManager(std::make_unique<ProjectManager>()) {
|
||||||
|
|
||||||
m_entityDeletedHandlerId = m_eventBus->Subscribe<EntityDeletedEvent>([this](const EntityDeletedEvent& event) {
|
m_entityDeletedHandlerId = m_eventBus->Subscribe<EntityDeletedEvent>([this](const EntityDeletedEvent& event) {
|
||||||
|
|||||||
@@ -6,24 +6,30 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
template<typename T>
|
class EventTypeRegistry {
|
||||||
struct EventTypeId {
|
public:
|
||||||
static uint32_t Get() {
|
static uint32_t NextId() {
|
||||||
static const uint32_t id = s_nextId++;
|
return s_nextId.fetch_add(1, std::memory_order_relaxed);
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static uint32_t s_nextId;
|
inline static std::atomic<uint32_t> s_nextId{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
uint32_t EventTypeId<T>::s_nextId = 0;
|
struct EventTypeId {
|
||||||
|
static uint32_t Get() {
|
||||||
|
static const uint32_t id = EventTypeRegistry::NextId();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class EventBus {
|
class EventBus {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -22,8 +22,18 @@ public:
|
|||||||
virtual ::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id) = 0;
|
virtual ::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id) = 0;
|
||||||
virtual void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent) = 0;
|
virtual void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent) = 0;
|
||||||
virtual bool HasClipboardData() const = 0;
|
virtual bool HasClipboardData() const = 0;
|
||||||
|
virtual void NewScene(const std::string& name = "Untitled Scene") = 0;
|
||||||
|
virtual bool LoadScene(const std::string& filePath) = 0;
|
||||||
|
virtual bool SaveScene() = 0;
|
||||||
|
virtual bool SaveSceneAs(const std::string& filePath) = 0;
|
||||||
|
virtual bool LoadStartupScene(const std::string& projectPath) = 0;
|
||||||
|
virtual bool HasActiveScene() const = 0;
|
||||||
|
virtual bool IsSceneDirty() const = 0;
|
||||||
|
virtual void MarkSceneDirty() = 0;
|
||||||
|
virtual const std::string& GetCurrentScenePath() const = 0;
|
||||||
|
virtual const std::string& GetCurrentSceneName() const = 0;
|
||||||
virtual void CreateDemoScene() = 0;
|
virtual void CreateDemoScene() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "panels/ProjectPanel.h"
|
#include "panels/ProjectPanel.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/EditorContext.h"
|
#include "Core/EditorContext.h"
|
||||||
|
#include <filesystem>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ void EditorLayer::onAttach() {
|
|||||||
m_consolePanel->SetContext(m_context.get());
|
m_consolePanel->SetContext(m_context.get());
|
||||||
m_projectPanel->SetContext(m_context.get());
|
m_projectPanel->SetContext(m_context.get());
|
||||||
|
|
||||||
|
m_projectPanel->Initialize(m_context->GetProjectPath());
|
||||||
|
m_context->GetSceneManager().LoadStartupScene(m_context->GetProjectPath());
|
||||||
|
m_context->GetProjectManager().RefreshCurrentFolder();
|
||||||
|
|
||||||
m_menuBar->OnAttach();
|
m_menuBar->OnAttach();
|
||||||
m_hierarchyPanel->OnAttach();
|
m_hierarchyPanel->OnAttach();
|
||||||
m_sceneViewPanel->OnAttach();
|
m_sceneViewPanel->OnAttach();
|
||||||
@@ -48,11 +53,18 @@ void EditorLayer::onAttach() {
|
|||||||
m_inspectorPanel->OnAttach();
|
m_inspectorPanel->OnAttach();
|
||||||
m_consolePanel->OnAttach();
|
m_consolePanel->OnAttach();
|
||||||
m_projectPanel->OnAttach();
|
m_projectPanel->OnAttach();
|
||||||
|
|
||||||
m_projectPanel->Initialize(m_context->GetProjectPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditorLayer::onDetach() {
|
void EditorLayer::onDetach() {
|
||||||
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
|
if (sceneManager.HasActiveScene() && sceneManager.IsSceneDirty()) {
|
||||||
|
if (!sceneManager.SaveScene()) {
|
||||||
|
const std::string fallbackPath =
|
||||||
|
(std::filesystem::path(m_context->GetProjectPath()) / "Assets" / "Scenes" / "Main.xc").string();
|
||||||
|
sceneManager.SaveSceneAs(fallbackPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_menuBar->OnDetach();
|
m_menuBar->OnDetach();
|
||||||
m_hierarchyPanel->OnDetach();
|
m_hierarchyPanel->OnDetach();
|
||||||
m_sceneViewPanel->OnDetach();
|
m_sceneViewPanel->OnDetach();
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
|||||||
std::ofstream((assetsPath / L"Textures" / L"Stone.png").wstring());
|
std::ofstream((assetsPath / L"Textures" / L"Stone.png").wstring());
|
||||||
std::ofstream((assetsPath / L"Models" / L"Character.fbx").wstring());
|
std::ofstream((assetsPath / L"Models" / L"Character.fbx").wstring());
|
||||||
std::ofstream((assetsPath / L"Scripts" / L"PlayerController.cs").wstring());
|
std::ofstream((assetsPath / L"Scripts" / L"PlayerController.cs").wstring());
|
||||||
std::ofstream((assetsPath / L"Scenes" / L"Main.unity").wstring());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||||
@@ -227,7 +226,7 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
|||||||
item->type = "Script";
|
item->type = "Script";
|
||||||
} else if (ext == L".mat") {
|
} else if (ext == L".mat") {
|
||||||
item->type = "Material";
|
item->type = "Material";
|
||||||
} else if (ext == L".unity" || ext == L".scene") {
|
} else if (ext == L".xc" || ext == L".unity" || ext == L".scene") {
|
||||||
item->type = "Scene";
|
item->type = "Scene";
|
||||||
} else if (ext == L".prefab") {
|
} else if (ext == L".prefab") {
|
||||||
item->type = "Prefab";
|
item->type = "Prefab";
|
||||||
@@ -240,4 +239,4 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,106 @@
|
|||||||
#include "SceneManager.h"
|
#include "SceneManager.h"
|
||||||
|
#include "Core/EventBus.h"
|
||||||
|
#include "Core/EditorEvents.h"
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
|
#include <XCEngine/Components/AudioSourceComponent.h>
|
||||||
|
#include <XCEngine/Components/AudioListenerComponent.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
SceneManager::SceneManager() = default;
|
namespace {
|
||||||
|
|
||||||
|
std::pair<std::string, std::string> SerializeComponent(const ::XCEngine::Components::Component* component) {
|
||||||
|
std::ostringstream payload;
|
||||||
|
component->Serialize(payload);
|
||||||
|
return { component->GetName(), payload.str() };
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::Components::Component* CreateComponentByName(
|
||||||
|
::XCEngine::Components::GameObject* gameObject,
|
||||||
|
const std::string& componentName) {
|
||||||
|
if (!gameObject) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (componentName == "Camera") {
|
||||||
|
return gameObject->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||||
|
}
|
||||||
|
if (componentName == "Light") {
|
||||||
|
return gameObject->AddComponent<::XCEngine::Components::LightComponent>();
|
||||||
|
}
|
||||||
|
if (componentName == "AudioSource") {
|
||||||
|
return gameObject->AddComponent<::XCEngine::Components::AudioSourceComponent>();
|
||||||
|
}
|
||||||
|
if (componentName == "AudioListener") {
|
||||||
|
return gameObject->AddComponent<::XCEngine::Components::AudioListenerComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SceneManager::SceneManager(EventBus* eventBus)
|
||||||
|
: m_eventBus(eventBus) {}
|
||||||
|
|
||||||
|
void SceneManager::SetSceneDirty(bool dirty) {
|
||||||
|
m_isSceneDirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::MarkSceneDirty() {
|
||||||
|
SetSceneDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* SceneManager::CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent) {
|
::XCEngine::Components::GameObject* SceneManager::CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent) {
|
||||||
if (!m_scene) {
|
if (!m_scene) {
|
||||||
m_scene = new ::XCEngine::Components::Scene("EditorScene");
|
m_scene = std::make_unique<::XCEngine::Components::Scene>("EditorScene");
|
||||||
}
|
}
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* entity = m_scene->CreateGameObject(name, parent);
|
::XCEngine::Components::GameObject* entity = m_scene->CreateGameObject(name, parent);
|
||||||
|
const auto entityId = entity->GetID();
|
||||||
if (parent == nullptr) {
|
SyncRootEntities();
|
||||||
m_rootEntities.push_back(entity);
|
SetSceneDirty(true);
|
||||||
|
|
||||||
|
OnEntityCreated.Invoke(entityId);
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(EntityCreatedEvent{ entityId });
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
}
|
}
|
||||||
|
|
||||||
OnEntityCreated.Invoke(entity->GetID());
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::DeleteEntity(::XCEngine::Components::GameObject::ID id) {
|
void SceneManager::DeleteEntity(::XCEngine::Components::GameObject::ID id) {
|
||||||
if (!m_scene) return;
|
if (!m_scene) return;
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||||
if (!entity) return;
|
if (!entity) return;
|
||||||
|
|
||||||
std::vector<::XCEngine::Components::GameObject*> children = entity->GetChildren();
|
std::vector<::XCEngine::Components::GameObject*> children = entity->GetChildren();
|
||||||
for (auto* child : children) {
|
for (auto* child : children) {
|
||||||
DeleteEntity(child->GetID());
|
DeleteEntity(child->GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity->GetParent() == nullptr) {
|
const auto entityId = entity->GetID();
|
||||||
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), entity), m_rootEntities.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
m_scene->DestroyGameObject(entity);
|
m_scene->DestroyGameObject(entity);
|
||||||
OnEntityDeleted.Invoke(entity->GetID());
|
SyncRootEntities();
|
||||||
|
SetSceneDirty(true);
|
||||||
|
|
||||||
|
OnEntityDeleted.Invoke(entityId);
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(EntityDeletedEvent{ entityId });
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity) {
|
SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity) {
|
||||||
@@ -50,6 +113,14 @@ SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::
|
|||||||
data.localScale = transform->GetLocalScale();
|
data.localScale = transform->GetLocalScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto components = entity->GetComponents<::XCEngine::Components::Component>();
|
||||||
|
for (auto* component : components) {
|
||||||
|
if (!component || component == entity->GetTransform()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
data.components.push_back(SerializeComponent(component));
|
||||||
|
}
|
||||||
|
|
||||||
for (auto* child : entity->GetChildren()) {
|
for (auto* child : entity->GetChildren()) {
|
||||||
data.children.push_back(CopyEntityRecursive(child));
|
data.children.push_back(CopyEntityRecursive(child));
|
||||||
}
|
}
|
||||||
@@ -60,7 +131,7 @@ SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::
|
|||||||
void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||||
if (!m_scene) return;
|
if (!m_scene) return;
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||||
if (!entity) return;
|
if (!entity) return;
|
||||||
|
|
||||||
m_clipboard = CopyEntityRecursive(entity);
|
m_clipboard = CopyEntityRecursive(entity);
|
||||||
@@ -69,7 +140,7 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
|||||||
::XCEngine::Components::GameObject::ID SceneManager::PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent) {
|
::XCEngine::Components::GameObject::ID SceneManager::PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent) {
|
||||||
::XCEngine::Components::GameObject* parentObj = nullptr;
|
::XCEngine::Components::GameObject* parentObj = nullptr;
|
||||||
if (parent != 0) {
|
if (parent != 0) {
|
||||||
parentObj = m_scene->Find(std::to_string(parent));
|
parentObj = m_scene->FindByID(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* newEntity = m_scene->CreateGameObject(data.name, parentObj);
|
::XCEngine::Components::GameObject* newEntity = m_scene->CreateGameObject(data.name, parentObj);
|
||||||
@@ -79,9 +150,14 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
|||||||
transform->SetLocalRotation(data.localRotation);
|
transform->SetLocalRotation(data.localRotation);
|
||||||
transform->SetLocalScale(data.localScale);
|
transform->SetLocalScale(data.localScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentObj == nullptr) {
|
for (const auto& componentData : data.components) {
|
||||||
m_rootEntities.push_back(newEntity);
|
if (auto* component = CreateComponentByName(newEntity, componentData.first)) {
|
||||||
|
if (!componentData.second.empty()) {
|
||||||
|
std::istringstream payloadStream(componentData.second);
|
||||||
|
component->Deserialize(payloadStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& childData : data.children) {
|
for (const auto& childData : data.children) {
|
||||||
@@ -93,13 +169,25 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
|||||||
|
|
||||||
::XCEngine::Components::GameObject::ID SceneManager::PasteEntity(::XCEngine::Components::GameObject::ID parent) {
|
::XCEngine::Components::GameObject::ID SceneManager::PasteEntity(::XCEngine::Components::GameObject::ID parent) {
|
||||||
if (!m_clipboard || !m_scene) return 0;
|
if (!m_clipboard || !m_scene) return 0;
|
||||||
return PasteEntityRecursive(*m_clipboard, parent);
|
|
||||||
|
const auto newEntityId = PasteEntityRecursive(*m_clipboard, parent);
|
||||||
|
SyncRootEntities();
|
||||||
|
|
||||||
|
OnEntityCreated.Invoke(newEntityId);
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(EntityCreatedEvent{ newEntityId });
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newEntityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
::XCEngine::Components::GameObject::ID SceneManager::DuplicateEntity(::XCEngine::Components::GameObject::ID id) {
|
::XCEngine::Components::GameObject::ID SceneManager::DuplicateEntity(::XCEngine::Components::GameObject::ID id) {
|
||||||
if (!m_scene) return 0;
|
if (!m_scene) return 0;
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||||
if (!entity) return 0;
|
if (!entity) return 0;
|
||||||
|
|
||||||
CopyEntity(id);
|
CopyEntity(id);
|
||||||
@@ -110,54 +198,210 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
|||||||
return PasteEntity(parentId);
|
return PasteEntity(parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SceneManager::NewScene(const std::string& name) {
|
||||||
|
m_scene = std::make_unique<::XCEngine::Components::Scene>(name);
|
||||||
|
m_rootEntities.clear();
|
||||||
|
m_clipboard.reset();
|
||||||
|
m_currentScenePath.clear();
|
||||||
|
m_currentSceneName = name;
|
||||||
|
SetSceneDirty(true);
|
||||||
|
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::LoadScene(const std::string& filePath) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
if (!fs::exists(filePath) || !fs::is_regular_file(filePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scene = std::make_unique<::XCEngine::Components::Scene>();
|
||||||
|
scene->Load(filePath);
|
||||||
|
|
||||||
|
m_scene = std::move(scene);
|
||||||
|
m_clipboard.reset();
|
||||||
|
SyncRootEntities();
|
||||||
|
m_currentScenePath = filePath;
|
||||||
|
m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene";
|
||||||
|
SetSceneDirty(false);
|
||||||
|
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::SaveScene() {
|
||||||
|
if (!m_scene) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_currentScenePath.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
fs::path savePath = fs::path(m_currentScenePath).replace_extension(".xc");
|
||||||
|
fs::create_directories(savePath.parent_path());
|
||||||
|
|
||||||
|
m_scene->Save(savePath.string());
|
||||||
|
m_currentScenePath = savePath.string();
|
||||||
|
SetSceneDirty(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::SaveSceneAs(const std::string& filePath) {
|
||||||
|
if (!m_scene || filePath.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
fs::path savePath = fs::path(filePath).replace_extension(".xc");
|
||||||
|
fs::create_directories(savePath.parent_path());
|
||||||
|
|
||||||
|
m_scene->Save(savePath.string());
|
||||||
|
m_currentScenePath = savePath.string();
|
||||||
|
m_currentSceneName = m_scene->GetName();
|
||||||
|
SetSceneDirty(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::LoadStartupScene(const std::string& projectPath) {
|
||||||
|
const std::string defaultScenePath = ResolveDefaultScenePath(projectPath);
|
||||||
|
if (IsSceneFileUsable(defaultScenePath) && LoadScene(defaultScenePath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDemoScene();
|
||||||
|
return SaveSceneAs(defaultScenePath);
|
||||||
|
}
|
||||||
|
|
||||||
void SceneManager::RenameEntity(::XCEngine::Components::GameObject::ID id, const std::string& newName) {
|
void SceneManager::RenameEntity(::XCEngine::Components::GameObject::ID id, const std::string& newName) {
|
||||||
if (!m_scene) return;
|
if (!m_scene) return;
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* obj = m_scene->Find(std::to_string(id));
|
::XCEngine::Components::GameObject* obj = m_scene->FindByID(id);
|
||||||
if (!obj) return;
|
if (!obj) return;
|
||||||
|
|
||||||
obj->SetName(newName);
|
obj->SetName(newName);
|
||||||
|
SetSceneDirty(true);
|
||||||
OnEntityChanged.Invoke(id);
|
OnEntityChanged.Invoke(id);
|
||||||
|
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(EntityChangedEvent{ id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParentId) {
|
void SceneManager::MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParentId) {
|
||||||
if (!m_scene) return;
|
if (!m_scene) return;
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* obj = m_scene->Find(std::to_string(id));
|
::XCEngine::Components::GameObject* obj = m_scene->FindByID(id);
|
||||||
if (!obj) return;
|
if (!obj) return;
|
||||||
|
const auto oldParentId = obj->GetParent() ? obj->GetParent()->GetID() : 0;
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* newParent = nullptr;
|
::XCEngine::Components::GameObject* newParent = nullptr;
|
||||||
if (newParentId != 0) {
|
if (newParentId != 0) {
|
||||||
newParent = m_scene->Find(std::to_string(newParentId));
|
newParent = m_scene->FindByID(newParentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
obj->SetParent(newParent);
|
obj->SetParent(newParent);
|
||||||
|
SyncRootEntities();
|
||||||
|
SetSceneDirty(true);
|
||||||
|
|
||||||
OnEntityChanged.Invoke(id);
|
OnEntityChanged.Invoke(id);
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(EntityParentChangedEvent{ id, oldParentId, newParentId });
|
||||||
|
m_eventBus->Publish(EntityChangedEvent{ id });
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::CreateDemoScene() {
|
void SceneManager::CreateDemoScene() {
|
||||||
if (m_scene) {
|
m_scene = std::make_unique<::XCEngine::Components::Scene>("Main Scene");
|
||||||
delete m_scene;
|
|
||||||
}
|
|
||||||
m_scene = new ::XCEngine::Components::Scene("DemoScene");
|
|
||||||
m_rootEntities.clear();
|
m_rootEntities.clear();
|
||||||
m_clipboard.reset();
|
m_clipboard.reset();
|
||||||
|
m_currentScenePath.clear();
|
||||||
|
m_currentSceneName = m_scene->GetName();
|
||||||
|
SetSceneDirty(true);
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* camera = CreateEntity("Main Camera", nullptr);
|
::XCEngine::Components::GameObject* camera = CreateEntity("Main Camera", nullptr);
|
||||||
camera->AddComponent<::XCEngine::Components::TransformComponent>();
|
camera->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* light = CreateEntity("Directional Light", nullptr);
|
::XCEngine::Components::GameObject* light = CreateEntity("Directional Light", nullptr);
|
||||||
|
light->AddComponent<::XCEngine::Components::LightComponent>();
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* cube = CreateEntity("Cube", nullptr);
|
SyncRootEntities();
|
||||||
cube->AddComponent<::XCEngine::Components::TransformComponent>();
|
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* sphere = CreateEntity("Sphere", nullptr);
|
|
||||||
sphere->AddComponent<::XCEngine::Components::TransformComponent>();
|
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* player = CreateEntity("Player", nullptr);
|
|
||||||
::XCEngine::Components::GameObject* weapon = CreateEntity("Weapon", player);
|
|
||||||
|
|
||||||
OnSceneChanged.Invoke();
|
OnSceneChanged.Invoke();
|
||||||
|
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::SyncRootEntities() {
|
||||||
|
if (!m_scene) {
|
||||||
|
m_rootEntities.clear();
|
||||||
|
m_currentSceneName = "Untitled Scene";
|
||||||
|
SetSceneDirty(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rootEntities = m_scene->GetRootGameObjects();
|
||||||
|
m_currentSceneName = m_scene->GetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SceneManager::GetScenesDirectory(const std::string& projectPath) {
|
||||||
|
return (std::filesystem::path(projectPath) / "Assets" / "Scenes").string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SceneManager::ResolveDefaultScenePath(const std::string& projectPath) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path scenesDir = GetScenesDirectory(projectPath);
|
||||||
|
const fs::path mainXC = scenesDir / "Main.xc";
|
||||||
|
if (fs::exists(mainXC)) {
|
||||||
|
return mainXC.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path mainScene = scenesDir / "Main.scene";
|
||||||
|
if (fs::exists(mainScene)) {
|
||||||
|
return mainScene.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path mainUnity = scenesDir / "Main.unity";
|
||||||
|
if (fs::exists(mainUnity)) {
|
||||||
|
return mainUnity.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs::exists(scenesDir)) {
|
||||||
|
for (const auto& entry : fs::directory_iterator(scenesDir)) {
|
||||||
|
if (!entry.is_regular_file()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path ext = entry.path().extension();
|
||||||
|
if (ext == ".xc" || ext == ".scene" || ext == ".unity") {
|
||||||
|
return entry.path().string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainXC.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::IsSceneFileUsable(const std::string& filePath) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
return !filePath.empty() &&
|
||||||
|
fs::exists(filePath) &&
|
||||||
|
fs::is_regular_file(filePath) &&
|
||||||
|
fs::file_size(filePath) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <XCEngine/Core/Event.h>
|
#include <XCEngine/Core/Event.h>
|
||||||
#include <XCEngine/Core/Math/Vector3.h>
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
@@ -18,9 +19,11 @@
|
|||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
|
class EventBus;
|
||||||
|
|
||||||
class SceneManager : public ISceneManager {
|
class SceneManager : public ISceneManager {
|
||||||
public:
|
public:
|
||||||
SceneManager();
|
explicit SceneManager(EventBus* eventBus = nullptr);
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent = nullptr);
|
::XCEngine::Components::GameObject* CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent = nullptr);
|
||||||
|
|
||||||
@@ -45,7 +48,17 @@ public:
|
|||||||
::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id);
|
::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id);
|
||||||
void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent);
|
void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent);
|
||||||
|
|
||||||
void CreateDemoScene();
|
void NewScene(const std::string& name = "Untitled Scene") override;
|
||||||
|
bool LoadScene(const std::string& filePath) override;
|
||||||
|
bool SaveScene() override;
|
||||||
|
bool SaveSceneAs(const std::string& filePath) override;
|
||||||
|
bool LoadStartupScene(const std::string& projectPath) override;
|
||||||
|
bool HasActiveScene() const override { return m_scene != nullptr; }
|
||||||
|
bool IsSceneDirty() const override { return m_isSceneDirty; }
|
||||||
|
void MarkSceneDirty() override;
|
||||||
|
const std::string& GetCurrentScenePath() const override { return m_currentScenePath; }
|
||||||
|
const std::string& GetCurrentSceneName() const override { return m_currentSceneName; }
|
||||||
|
void CreateDemoScene() override;
|
||||||
|
|
||||||
bool HasClipboardData() const { return m_clipboard.has_value(); }
|
bool HasClipboardData() const { return m_clipboard.has_value(); }
|
||||||
|
|
||||||
@@ -60,15 +73,25 @@ private:
|
|||||||
Math::Vector3 localPosition = Math::Vector3::Zero();
|
Math::Vector3 localPosition = Math::Vector3::Zero();
|
||||||
Math::Quaternion localRotation = Math::Quaternion::Identity();
|
Math::Quaternion localRotation = Math::Quaternion::Identity();
|
||||||
Math::Vector3 localScale = Math::Vector3::One();
|
Math::Vector3 localScale = Math::Vector3::One();
|
||||||
|
std::vector<std::pair<std::string, std::string>> components;
|
||||||
std::vector<ClipboardData> children;
|
std::vector<ClipboardData> children;
|
||||||
};
|
};
|
||||||
|
|
||||||
ClipboardData CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity);
|
ClipboardData CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity);
|
||||||
::XCEngine::Components::GameObject::ID PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent);
|
::XCEngine::Components::GameObject::ID PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent);
|
||||||
|
void SetSceneDirty(bool dirty);
|
||||||
|
void SyncRootEntities();
|
||||||
|
static std::string GetScenesDirectory(const std::string& projectPath);
|
||||||
|
static std::string ResolveDefaultScenePath(const std::string& projectPath);
|
||||||
|
static bool IsSceneFileUsable(const std::string& filePath);
|
||||||
|
|
||||||
::XCEngine::Components::Scene* m_scene = nullptr;
|
std::unique_ptr<::XCEngine::Components::Scene> m_scene;
|
||||||
std::vector<::XCEngine::Components::GameObject*> m_rootEntities;
|
std::vector<::XCEngine::Components::GameObject*> m_rootEntities;
|
||||||
std::optional<ClipboardData> m_clipboard;
|
std::optional<ClipboardData> m_clipboard;
|
||||||
|
EventBus* m_eventBus = nullptr;
|
||||||
|
std::string m_currentScenePath;
|
||||||
|
std::string m_currentSceneName = "Untitled Scene";
|
||||||
|
bool m_isSceneDirty = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
196
editor/src/Utils/SceneEditorUtils.h
Normal file
196
editor/src/Utils/SceneEditorUtils.h
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Core/IEditorContext.h"
|
||||||
|
#include "Core/IProjectManager.h"
|
||||||
|
#include "Core/ISceneManager.h"
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <commdlg.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace SceneEditorUtils {
|
||||||
|
|
||||||
|
inline std::wstring Utf8ToWide(const std::string& value) {
|
||||||
|
if (value.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const int length = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
|
||||||
|
if (length <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring result(length - 1, L'\0');
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, &result[0], length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string WideToUtf8(const std::wstring& value) {
|
||||||
|
if (value.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const int length = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||||
|
if (length <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result(length - 1, '\0');
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, &result[0], length, nullptr, nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string SanitizeSceneFileName(const std::string& value) {
|
||||||
|
std::string result = value.empty() ? "Untitled Scene" : value;
|
||||||
|
for (char& ch : result) {
|
||||||
|
switch (ch) {
|
||||||
|
case '\\':
|
||||||
|
case '/':
|
||||||
|
case ':':
|
||||||
|
case '*':
|
||||||
|
case '?':
|
||||||
|
case '"':
|
||||||
|
case '<':
|
||||||
|
case '>':
|
||||||
|
case '|':
|
||||||
|
ch = '_';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (static_cast<unsigned char>(ch) < 32u) {
|
||||||
|
ch = '_';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline HWND GetDialogOwnerWindow() {
|
||||||
|
HWND owner = GetActiveWindow();
|
||||||
|
if (!owner) {
|
||||||
|
owner = GetForegroundWindow();
|
||||||
|
}
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const wchar_t* GetSceneFilter() {
|
||||||
|
static const wchar_t filter[] =
|
||||||
|
L"XCEngine Scene (*.xc)\0*.xc\0Legacy Scene (*.scene;*.unity)\0*.scene;*.unity\0All Files (*.*)\0*.*\0\0";
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string OpenSceneFileDialog(const std::string& projectPath, const std::string& initialPath = {}) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path scenesDir = fs::path(projectPath) / "Assets" / "Scenes";
|
||||||
|
const std::wstring initialDir = Utf8ToWide(scenesDir.string());
|
||||||
|
|
||||||
|
std::array<wchar_t, 1024> fileBuffer{};
|
||||||
|
if (!initialPath.empty()) {
|
||||||
|
const std::wstring initialFile = Utf8ToWide(initialPath);
|
||||||
|
wcsncpy_s(fileBuffer.data(), fileBuffer.size(), initialFile.c_str(), _TRUNCATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
OPENFILENAMEW dialog{};
|
||||||
|
dialog.lStructSize = sizeof(dialog);
|
||||||
|
dialog.hwndOwner = GetDialogOwnerWindow();
|
||||||
|
dialog.lpstrFilter = GetSceneFilter();
|
||||||
|
dialog.lpstrFile = fileBuffer.data();
|
||||||
|
dialog.nMaxFile = static_cast<DWORD>(fileBuffer.size());
|
||||||
|
dialog.lpstrInitialDir = initialDir.empty() ? nullptr : initialDir.c_str();
|
||||||
|
dialog.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
|
||||||
|
|
||||||
|
if (!GetOpenFileNameW(&dialog)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return WideToUtf8(fileBuffer.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string SaveSceneFileDialog(
|
||||||
|
const std::string& projectPath,
|
||||||
|
const std::string& currentScenePath,
|
||||||
|
const std::string& currentSceneName) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path scenesDir = fs::path(projectPath) / "Assets" / "Scenes";
|
||||||
|
const fs::path suggestedPath = currentScenePath.empty()
|
||||||
|
? scenesDir / (SanitizeSceneFileName(currentSceneName) + ".xc")
|
||||||
|
: fs::path(currentScenePath).replace_extension(".xc");
|
||||||
|
|
||||||
|
std::array<wchar_t, 1024> fileBuffer{};
|
||||||
|
const std::wstring suggestedWide = Utf8ToWide(suggestedPath.string());
|
||||||
|
wcsncpy_s(fileBuffer.data(), fileBuffer.size(), suggestedWide.c_str(), _TRUNCATE);
|
||||||
|
|
||||||
|
const std::wstring initialDir = Utf8ToWide(suggestedPath.parent_path().string());
|
||||||
|
OPENFILENAMEW dialog{};
|
||||||
|
dialog.lStructSize = sizeof(dialog);
|
||||||
|
dialog.hwndOwner = GetDialogOwnerWindow();
|
||||||
|
dialog.lpstrFilter = GetSceneFilter();
|
||||||
|
dialog.lpstrFile = fileBuffer.data();
|
||||||
|
dialog.nMaxFile = static_cast<DWORD>(fileBuffer.size());
|
||||||
|
dialog.lpstrInitialDir = initialDir.empty() ? nullptr : initialDir.c_str();
|
||||||
|
dialog.lpstrDefExt = L"xc";
|
||||||
|
dialog.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
|
||||||
|
|
||||||
|
if (!GetSaveFileNameW(&dialog)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return WideToUtf8(fileBuffer.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SaveCurrentScene(IEditorContext& context) {
|
||||||
|
auto& sceneManager = context.GetSceneManager();
|
||||||
|
if (sceneManager.SaveScene()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filePath = SaveSceneFileDialog(
|
||||||
|
context.GetProjectPath(),
|
||||||
|
sceneManager.GetCurrentScenePath(),
|
||||||
|
sceneManager.GetCurrentSceneName());
|
||||||
|
if (filePath.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool saved = sceneManager.SaveSceneAs(filePath);
|
||||||
|
if (saved) {
|
||||||
|
context.GetProjectManager().RefreshCurrentFolder();
|
||||||
|
}
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ConfirmSceneSwitch(IEditorContext& context) {
|
||||||
|
auto& sceneManager = context.GetSceneManager();
|
||||||
|
if (!sceneManager.HasActiveScene() || !sceneManager.IsSceneDirty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int result = MessageBoxW(
|
||||||
|
GetDialogOwnerWindow(),
|
||||||
|
L"Save changes to the current scene before continuing?",
|
||||||
|
L"Unsaved Scene Changes",
|
||||||
|
MB_YESNOCANCEL | MB_ICONWARNING);
|
||||||
|
|
||||||
|
if (result == IDCANCEL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result == IDYES) {
|
||||||
|
return SaveCurrentScene(context);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace SceneEditorUtils
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -11,9 +11,11 @@ ConsolePanel::ConsolePanel() : Panel("Console") {
|
|||||||
|
|
||||||
void ConsolePanel::Render() {
|
void ConsolePanel::Render() {
|
||||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||||
|
|
||||||
|
auto* sink = Debug::EditorConsoleSink::GetInstance();
|
||||||
|
|
||||||
if (ImGui::Button("Clear")) {
|
if (ImGui::Button("Clear")) {
|
||||||
Debug::EditorConsoleSink::GetInstance()->Clear();
|
sink->Clear();
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ void ConsolePanel::Render() {
|
|||||||
|
|
||||||
ImGui::BeginChild("LogScroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
ImGui::BeginChild("LogScroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
|
||||||
const auto& logs = Debug::EditorConsoleSink::GetInstance()->GetLogs();
|
const auto logs = sink->GetLogs();
|
||||||
size_t logIndex = 0;
|
size_t logIndex = 0;
|
||||||
for (const auto& log : logs) {
|
for (const auto& log : logs) {
|
||||||
bool shouldShow = false;
|
bool shouldShow = false;
|
||||||
@@ -117,4 +119,4 @@ void ConsolePanel::Render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include "Core/EventBus.h"
|
#include "Core/EventBus.h"
|
||||||
#include <XCEngine/Core/Math/Vector3.h>
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
#include <XCEngine/Core/Math/Quaternion.h>
|
#include <XCEngine/Core/Math/Quaternion.h>
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -22,9 +24,6 @@ HierarchyPanel::~HierarchyPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HierarchyPanel::OnAttach() {
|
void HierarchyPanel::OnAttach() {
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
|
||||||
sceneManager.CreateDemoScene();
|
|
||||||
|
|
||||||
m_selectionHandlerId = m_context->GetEventBus().Subscribe<SelectionChangedEvent>(
|
m_selectionHandlerId = m_context->GetEventBus().Subscribe<SelectionChangedEvent>(
|
||||||
[this](const SelectionChangedEvent& event) {
|
[this](const SelectionChangedEvent& event) {
|
||||||
OnSelectionChanged(event);
|
OnSelectionChanged(event);
|
||||||
@@ -74,7 +73,16 @@ void HierarchyPanel::Render() {
|
|||||||
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
|
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
|
||||||
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
|
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
|
auto* srcTransform = sourceGameObject->GetTransform();
|
||||||
|
Math::Vector3 worldPos = srcTransform->GetPosition();
|
||||||
|
Math::Quaternion worldRot = srcTransform->GetRotation();
|
||||||
|
Math::Vector3 worldScale = srcTransform->GetScale();
|
||||||
|
|
||||||
sceneManager.MoveEntity(sourceGameObject->GetID(), 0);
|
sceneManager.MoveEntity(sourceGameObject->GetID(), 0);
|
||||||
|
|
||||||
|
srcTransform->SetPosition(worldPos);
|
||||||
|
srcTransform->SetRotation(worldRot);
|
||||||
|
srcTransform->SetScale(worldScale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
@@ -195,7 +203,7 @@ void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameO
|
|||||||
|
|
||||||
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
|
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
|
||||||
if (ImGui::MenuItem("Detach from Parent")) {
|
if (ImGui::MenuItem("Detach from Parent")) {
|
||||||
gameObject->DetachFromParent();
|
sceneManager.MoveEntity(gameObject->GetID(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,12 +250,13 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent
|
|||||||
|
|
||||||
if (ImGui::MenuItem("Camera")) {
|
if (ImGui::MenuItem("Camera")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Camera", parent);
|
auto* newEntity = sceneManager.CreateEntity("Camera", parent);
|
||||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
newEntity->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Light")) {
|
if (ImGui::MenuItem("Light")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Light", parent);
|
auto* newEntity = sceneManager.CreateEntity("Light", parent);
|
||||||
|
newEntity->AddComponent<::XCEngine::Components::LightComponent>();
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,19 +264,16 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent
|
|||||||
|
|
||||||
if (ImGui::MenuItem("Cube")) {
|
if (ImGui::MenuItem("Cube")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Cube", parent);
|
auto* newEntity = sceneManager.CreateEntity("Cube", parent);
|
||||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Sphere")) {
|
if (ImGui::MenuItem("Sphere")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Sphere", parent);
|
auto* newEntity = sceneManager.CreateEntity("Sphere", parent);
|
||||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Plane")) {
|
if (ImGui::MenuItem("Plane")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Plane", parent);
|
auto* newEntity = sceneManager.CreateEntity("Plane", parent);
|
||||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
#include "InspectorPanel.h"
|
#include "InspectorPanel.h"
|
||||||
#include "Core/EditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "UI/UI.h"
|
#include "Core/ISelectionManager.h"
|
||||||
#include <XCEngine/Debug/Logger.h>
|
#include "Core/EventBus.h"
|
||||||
|
#include "Core/EditorEvents.h"
|
||||||
|
#include "ComponentEditors/CameraComponentEditor.h"
|
||||||
|
#include "ComponentEditors/IComponentEditor.h"
|
||||||
|
#include "ComponentEditors/LightComponentEditor.h"
|
||||||
|
#include "ComponentEditors/TransformComponentEditor.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -10,7 +15,7 @@ namespace XCEngine {
|
|||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
InspectorPanel::InspectorPanel() : Panel("Inspector") {
|
InspectorPanel::InspectorPanel() : Panel("Inspector") {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "InspectorPanel constructed");
|
RegisterDefaultComponentEditors();
|
||||||
}
|
}
|
||||||
|
|
||||||
InspectorPanel::~InspectorPanel() {
|
InspectorPanel::~InspectorPanel() {
|
||||||
@@ -23,8 +28,35 @@ void InspectorPanel::OnSelectionChanged(const SelectionChangedEvent& event) {
|
|||||||
m_selectedEntityId = event.primarySelection;
|
m_selectedEntityId = event.primarySelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InspectorPanel::RegisterDefaultComponentEditors() {
|
||||||
|
RegisterComponentEditor(std::make_unique<TransformComponentEditor>());
|
||||||
|
RegisterComponentEditor(std::make_unique<CameraComponentEditor>());
|
||||||
|
RegisterComponentEditor(std::make_unique<LightComponentEditor>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void InspectorPanel::RegisterComponentEditor(std::unique_ptr<IComponentEditor> editor) {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_componentEditors.push_back(std::move(editor));
|
||||||
|
}
|
||||||
|
|
||||||
|
IComponentEditor* InspectorPanel::GetEditorFor(::XCEngine::Components::Component* component) const {
|
||||||
|
if (!component) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& editor : m_componentEditors) {
|
||||||
|
if (editor && editor->CanEdit(component)) {
|
||||||
|
return editor.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void InspectorPanel::Render() {
|
void InspectorPanel::Render() {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "InspectorPanel::Render START");
|
|
||||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||||
|
|
||||||
if (!m_selectionHandlerId && m_context) {
|
if (!m_selectionHandlerId && m_context) {
|
||||||
@@ -51,21 +83,18 @@ void InspectorPanel::Render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "InspectorPanel::Render END");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameObject) {
|
void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameObject) {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "RenderGameObject START");
|
|
||||||
char nameBuffer[256];
|
char nameBuffer[256];
|
||||||
strcpy_s(nameBuffer, gameObject->GetName().c_str());
|
strcpy_s(nameBuffer, gameObject->GetName().c_str());
|
||||||
ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer));
|
ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer));
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
gameObject->SetName(nameBuffer);
|
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), nameBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Add Component")) {
|
if (ImGui::Button("Add Component")) {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "Add Component BUTTON CLICKED");
|
|
||||||
ImGui::OpenPopup("AddComponent");
|
ImGui::OpenPopup("AddComponent");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,53 +104,54 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
|||||||
for (auto* component : components) {
|
for (auto* component : components) {
|
||||||
RenderComponent(component, gameObject);
|
RenderComponent(component, gameObject);
|
||||||
}
|
}
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "RenderGameObject END");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
|
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "RenderAddComponentPopup called");
|
|
||||||
|
|
||||||
if (!gameObject) {
|
|
||||||
Debug::Logger::Get().Error(Debug::LogCategory::General, "ERROR: gameObject is nullptr!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ImGui::BeginPopup("AddComponent")) {
|
if (!ImGui::BeginPopup("AddComponent")) {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "BeginPopup returned false");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "BeginPopup succeeded");
|
|
||||||
|
|
||||||
ImGui::Text("Components");
|
ImGui::Text("Components");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
bool hasTransform = gameObject->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr;
|
bool drewAnyEntry = false;
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, hasTransform ? "Has Transform: yes" : "Has Transform: no");
|
for (const auto& editor : m_componentEditors) {
|
||||||
|
if (!editor || !editor->ShowInAddComponentMenu()) {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "About to check MenuItem condition");
|
continue;
|
||||||
if (ImGui::MenuItem("Transform", nullptr, false, !hasTransform)) {
|
}
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "MenuItem CLICKED! Before AddComponent");
|
|
||||||
|
drewAnyEntry = true;
|
||||||
auto* newComp = gameObject->AddComponent<::XCEngine::Components::TransformComponent>();
|
const bool canAdd = editor->CanAddTo(gameObject);
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, newComp ? "AddComponent SUCCEEDED" : "AddComponent FAILED");
|
std::string label = editor->GetDisplayName();
|
||||||
|
if (!canAdd) {
|
||||||
ImGui::CloseCurrentPopup();
|
const char* reason = editor->GetAddDisabledReason(gameObject);
|
||||||
} else {
|
if (reason && reason[0] != '\0') {
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "MenuItem not clicked (disabled or condition false)");
|
label += " (";
|
||||||
|
label += reason;
|
||||||
|
label += ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::MenuItem(label.c_str(), nullptr, false, canAdd)) {
|
||||||
|
if (editor->AddTo(gameObject)) {
|
||||||
|
m_context->GetSceneManager().MarkSceneDirty();
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
if (!drewAnyEntry) {
|
||||||
ImGui::TextDisabled("No more components available");
|
ImGui::TextDisabled("No registered component editors");
|
||||||
|
}
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "About to EndPopup - calling EndPopup");
|
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "Popup closed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) {
|
void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) {
|
||||||
if (!component) return;
|
if (!component) return;
|
||||||
|
|
||||||
|
IComponentEditor* editor = GetEditorFor(component);
|
||||||
|
|
||||||
const char* name = component->GetName().c_str();
|
const char* name = component->GetName().c_str();
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 2});
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 2});
|
||||||
@@ -138,8 +168,9 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
|
|||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
bool removeComponent = false;
|
bool removeComponent = false;
|
||||||
|
const bool canRemoveComponent = editor ? editor->CanRemove(component) : false;
|
||||||
if (ImGui::BeginPopupContextItem("ComponentSettings")) {
|
if (ImGui::BeginPopupContextItem("ComponentSettings")) {
|
||||||
if (ImGui::MenuItem("Remove Component")) {
|
if (ImGui::MenuItem("Remove Component", nullptr, false, canRemoveComponent)) {
|
||||||
removeComponent = true;
|
removeComponent = true;
|
||||||
}
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
@@ -151,22 +182,12 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
if (auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
|
if (editor) {
|
||||||
::XCEngine::Math::Vector3 position = transform->GetLocalPosition();
|
if (editor->Render(component)) {
|
||||||
::XCEngine::Math::Vector3 rotation = transform->GetLocalEulerAngles();
|
m_context->GetSceneManager().MarkSceneDirty();
|
||||||
::XCEngine::Math::Vector3 scale = transform->GetLocalScale();
|
|
||||||
|
|
||||||
if (UI::DrawVec3("Position", position, 0.0f, 80.0f, 0.1f)) {
|
|
||||||
transform->SetLocalPosition(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UI::DrawVec3("Rotation", rotation, 0.0f, 80.0f, 1.0f)) {
|
|
||||||
transform->SetLocalEulerAngles(rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UI::DrawVec3("Scale", scale, 1.0f, 80.0f, 0.1f)) {
|
|
||||||
transform->SetLocalScale(scale);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("No registered editor for this component");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
@@ -179,14 +200,9 @@ void InspectorPanel::RemoveComponentByType(::XCEngine::Components::Component* co
|
|||||||
if (dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
|
if (dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto components = gameObject->GetComponents<::XCEngine::Components::Component>();
|
gameObject->RemoveComponent(component);
|
||||||
for (auto* comp : components) {
|
m_context->GetSceneManager().MarkSceneDirty();
|
||||||
if (comp == component) {
|
|
||||||
gameObject->RemoveComponent<::XCEngine::Components::Component>();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Panel.h"
|
#include "Panel.h"
|
||||||
#include <XCEngine/Components/TransformComponent.h>
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
class Component;
|
||||||
|
class GameObject;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
|
class IComponentEditor;
|
||||||
|
|
||||||
class InspectorPanel : public Panel {
|
class InspectorPanel : public Panel {
|
||||||
public:
|
public:
|
||||||
InspectorPanel();
|
InspectorPanel();
|
||||||
@@ -14,6 +24,9 @@ public:
|
|||||||
void Render() override;
|
void Render() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void RegisterDefaultComponentEditors();
|
||||||
|
void RegisterComponentEditor(std::unique_ptr<IComponentEditor> editor);
|
||||||
|
IComponentEditor* GetEditorFor(::XCEngine::Components::Component* component) const;
|
||||||
void RenderGameObject(::XCEngine::Components::GameObject* gameObject);
|
void RenderGameObject(::XCEngine::Components::GameObject* gameObject);
|
||||||
void RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject);
|
void RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject);
|
||||||
void RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject);
|
void RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject);
|
||||||
@@ -22,6 +35,7 @@ private:
|
|||||||
|
|
||||||
uint64_t m_selectionHandlerId = 0;
|
uint64_t m_selectionHandlerId = 0;
|
||||||
uint64_t m_selectedEntityId = 0;
|
uint64_t m_selectedEntityId = 0;
|
||||||
|
std::vector<std::unique_ptr<IComponentEditor>> m_componentEditors;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
#include "MenuBar.h"
|
#include "MenuBar.h"
|
||||||
|
#include "Core/IEditorContext.h"
|
||||||
|
#include "Core/ISceneManager.h"
|
||||||
|
#include "Utils/SceneEditorUtils.h"
|
||||||
|
#include <filesystem>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -7,20 +11,103 @@ namespace Editor {
|
|||||||
MenuBar::MenuBar() : Panel("MenuBar") {}
|
MenuBar::MenuBar() : Panel("MenuBar") {}
|
||||||
|
|
||||||
void MenuBar::Render() {
|
void MenuBar::Render() {
|
||||||
|
HandleShortcuts();
|
||||||
|
|
||||||
if (ImGui::BeginMainMenuBar()) {
|
if (ImGui::BeginMainMenuBar()) {
|
||||||
ShowFileMenu();
|
ShowFileMenu();
|
||||||
ShowEditMenu();
|
ShowEditMenu();
|
||||||
ShowViewMenu();
|
ShowViewMenu();
|
||||||
ShowHelpMenu();
|
ShowHelpMenu();
|
||||||
|
RenderSceneStatus();
|
||||||
ImGui::EndMainMenuBar();
|
ImGui::EndMainMenuBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MenuBar::NewScene() {
|
||||||
|
if (!m_context || !SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_context->GetSceneManager().NewScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuBar::OpenScene() {
|
||||||
|
if (!m_context || !SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filePath = SceneEditorUtils::OpenSceneFileDialog(
|
||||||
|
m_context->GetProjectPath(),
|
||||||
|
m_context->GetSceneManager().GetCurrentScenePath());
|
||||||
|
if (!filePath.empty()) {
|
||||||
|
m_context->GetSceneManager().LoadScene(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuBar::SaveScene() {
|
||||||
|
if (!m_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SceneEditorUtils::SaveCurrentScene(*m_context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuBar::SaveSceneAs() {
|
||||||
|
if (!m_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filePath = SceneEditorUtils::SaveSceneFileDialog(
|
||||||
|
m_context->GetProjectPath(),
|
||||||
|
m_context->GetSceneManager().GetCurrentScenePath(),
|
||||||
|
m_context->GetSceneManager().GetCurrentSceneName());
|
||||||
|
if (!filePath.empty() && m_context->GetSceneManager().SaveSceneAs(filePath)) {
|
||||||
|
m_context->GetProjectManager().RefreshCurrentFolder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuBar::HandleShortcuts() {
|
||||||
|
if (!m_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
if (!io.KeyCtrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_N, false)) {
|
||||||
|
NewScene();
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_O, false)) {
|
||||||
|
OpenScene();
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_S, false)) {
|
||||||
|
if (io.KeyShift) {
|
||||||
|
SaveSceneAs();
|
||||||
|
} else {
|
||||||
|
SaveScene();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MenuBar::ShowFileMenu() {
|
void MenuBar::ShowFileMenu() {
|
||||||
if (ImGui::BeginMenu("File")) {
|
if (ImGui::BeginMenu("File")) {
|
||||||
if (ImGui::MenuItem("New Scene", "Ctrl+N")) {}
|
if (ImGui::MenuItem("New Scene", "Ctrl+N")) {
|
||||||
if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {}
|
NewScene();
|
||||||
if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {}
|
}
|
||||||
|
if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {
|
||||||
|
OpenScene();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {
|
||||||
|
SaveScene();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) {
|
||||||
|
SaveSceneAs();
|
||||||
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("Exit", "Alt+F4")) {}
|
if (ImGui::MenuItem("Exit", "Alt+F4")) {}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
@@ -53,5 +140,45 @@ void MenuBar::ShowHelpMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MenuBar::RenderSceneStatus() {
|
||||||
|
if (!m_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
|
std::string sceneLabel = sceneManager.HasActiveScene() ? sceneManager.GetCurrentSceneName() : "No Scene";
|
||||||
|
if (sceneLabel.empty()) {
|
||||||
|
sceneLabel = "Untitled Scene";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fileLabel = sceneManager.GetCurrentScenePath().empty()
|
||||||
|
? "Unsaved.xc"
|
||||||
|
: std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
|
||||||
|
|
||||||
|
const bool dirty = sceneManager.IsSceneDirty();
|
||||||
|
const std::string statusText = std::string("Scene: ") + fileLabel + (dirty ? " Modified" : " Saved");
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(statusText.c_str());
|
||||||
|
const float targetX = ImGui::GetWindowWidth() - textSize.x - 20.0f;
|
||||||
|
if (targetX > ImGui::GetCursorPosX()) {
|
||||||
|
ImGui::SetCursorPosX(targetX);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec4 accentColor = dirty
|
||||||
|
? ImVec4(0.94f, 0.68f, 0.20f, 1.0f)
|
||||||
|
: ImVec4(0.48f, 0.78f, 0.49f, 1.0f);
|
||||||
|
ImGui::TextColored(accentColor, "%s", statusText.c_str());
|
||||||
|
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::Text("Scene");
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Name: %s", sceneLabel.c_str());
|
||||||
|
ImGui::Text("File: %s", fileLabel.c_str());
|
||||||
|
ImGui::Text("State: %s", dirty ? "Modified" : "Saved");
|
||||||
|
ImGui::Text("Path: %s", sceneManager.GetCurrentScenePath().empty() ? "(not saved yet)" : sceneManager.GetCurrentScenePath().c_str());
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -11,6 +11,12 @@ public:
|
|||||||
void Render() override;
|
void Render() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void HandleShortcuts();
|
||||||
|
void NewScene();
|
||||||
|
void OpenScene();
|
||||||
|
void SaveScene();
|
||||||
|
void SaveSceneAs();
|
||||||
|
void RenderSceneStatus();
|
||||||
void ShowFileMenu();
|
void ShowFileMenu();
|
||||||
void ShowEditMenu();
|
void ShowEditMenu();
|
||||||
void ShowViewMenu();
|
void ShowViewMenu();
|
||||||
@@ -18,4 +24,4 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "ProjectPanel.h"
|
#include "ProjectPanel.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/IProjectManager.h"
|
#include "Core/IProjectManager.h"
|
||||||
|
#include "Core/ISceneManager.h"
|
||||||
#include "Core/AssetItem.h"
|
#include "Core/AssetItem.h"
|
||||||
|
#include "Utils/SceneEditorUtils.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ void ProjectPanel::Render() {
|
|||||||
|
|
||||||
auto& items = manager.GetCurrentItems();
|
auto& items = manager.GetCurrentItems();
|
||||||
std::string searchStr = m_searchBuffer;
|
std::string searchStr = m_searchBuffer;
|
||||||
int itemIndex = 0;
|
int displayedCount = 0;
|
||||||
|
|
||||||
for (int i = 0; i < (int)items.size(); i++) {
|
for (int i = 0; i < (int)items.size(); i++) {
|
||||||
if (!searchStr.empty()) {
|
if (!searchStr.empty()) {
|
||||||
@@ -92,11 +94,11 @@ void ProjectPanel::Render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemIndex > 0 && itemIndex % columns != 0) {
|
if (displayedCount > 0 && displayedCount % columns != 0) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
}
|
}
|
||||||
RenderAssetItem(items[i], itemIndex);
|
RenderAssetItem(items[i], i);
|
||||||
itemIndex++;
|
displayedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
@@ -108,9 +110,13 @@ void ProjectPanel::Render() {
|
|||||||
if (ImGui::BeginPopup("ItemContextMenu")) {
|
if (ImGui::BeginPopup("ItemContextMenu")) {
|
||||||
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
|
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
|
||||||
auto& item = items[m_contextMenuIndex];
|
auto& item = items[m_contextMenuIndex];
|
||||||
if (item->isFolder) {
|
if (item->isFolder || item->type == "Scene") {
|
||||||
if (ImGui::MenuItem("Open")) {
|
if (ImGui::MenuItem("Open")) {
|
||||||
manager.NavigateToFolder(item);
|
if (item->isFolder) {
|
||||||
|
manager.NavigateToFolder(item);
|
||||||
|
} else if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||||
|
m_context->GetSceneManager().LoadScene(item->fullPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
}
|
}
|
||||||
@@ -276,8 +282,14 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doubleClicked && item->isFolder) {
|
if (doubleClicked) {
|
||||||
manager.NavigateToFolder(item);
|
if (item->isFolder) {
|
||||||
|
manager.NavigateToFolder(item);
|
||||||
|
} else if (item->type == "Scene") {
|
||||||
|
if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||||
|
m_context->GetSceneManager().LoadScene(item->fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
@@ -297,4 +309,4 @@ bool ProjectPanel::HandleDrop(const AssetItemPtr& targetFolder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,9 +243,17 @@ add_library(XCEngine STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/Component.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/Component.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/TransformComponent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/TransformComponent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/GameObject.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/GameObject.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/CameraComponent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/LightComponent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/AudioSourceComponent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/AudioListenerComponent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/Component.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/Component.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/TransformComponent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/TransformComponent.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/GameObject.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/GameObject.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/CameraComponent.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/LightComponent.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/AudioSourceComponent.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/AudioListenerComponent.cpp
|
||||||
|
|
||||||
# Scene
|
# Scene
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h
|
||||||
|
|||||||
57
engine/include/XCEngine/Components/CameraComponent.h
Normal file
57
engine/include/XCEngine/Components/CameraComponent.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Components/Component.h>
|
||||||
|
#include <XCEngine/Core/Math/Color.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
enum class CameraProjectionType {
|
||||||
|
Perspective = 0,
|
||||||
|
Orthographic
|
||||||
|
};
|
||||||
|
|
||||||
|
class CameraComponent : public Component {
|
||||||
|
public:
|
||||||
|
std::string GetName() const override { return "Camera"; }
|
||||||
|
|
||||||
|
CameraProjectionType GetProjectionType() const { return m_projectionType; }
|
||||||
|
void SetProjectionType(CameraProjectionType type) { m_projectionType = type; }
|
||||||
|
|
||||||
|
float GetFieldOfView() const { return m_fieldOfView; }
|
||||||
|
void SetFieldOfView(float value);
|
||||||
|
|
||||||
|
float GetOrthographicSize() const { return m_orthographicSize; }
|
||||||
|
void SetOrthographicSize(float value);
|
||||||
|
|
||||||
|
float GetNearClipPlane() const { return m_nearClipPlane; }
|
||||||
|
void SetNearClipPlane(float value);
|
||||||
|
|
||||||
|
float GetFarClipPlane() const { return m_farClipPlane; }
|
||||||
|
void SetFarClipPlane(float value);
|
||||||
|
|
||||||
|
float GetDepth() const { return m_depth; }
|
||||||
|
void SetDepth(float value) { m_depth = value; }
|
||||||
|
|
||||||
|
bool IsPrimary() const { return m_primary; }
|
||||||
|
void SetPrimary(bool value) { m_primary = value; }
|
||||||
|
|
||||||
|
const Math::Color& GetClearColor() const { return m_clearColor; }
|
||||||
|
void SetClearColor(const Math::Color& value) { m_clearColor = value; }
|
||||||
|
|
||||||
|
void Serialize(std::ostream& os) const override;
|
||||||
|
void Deserialize(std::istream& is) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CameraProjectionType m_projectionType = CameraProjectionType::Perspective;
|
||||||
|
float m_fieldOfView = 60.0f;
|
||||||
|
float m_orthographicSize = 5.0f;
|
||||||
|
float m_nearClipPlane = 0.1f;
|
||||||
|
float m_farClipPlane = 1000.0f;
|
||||||
|
float m_depth = 0.0f;
|
||||||
|
bool m_primary = true;
|
||||||
|
Math::Color m_clearColor = Math::Color(0.192f, 0.302f, 0.475f, 1.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -5,10 +5,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <algorithm>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
@@ -38,6 +40,10 @@ public:
|
|||||||
|
|
||||||
template<typename T, typename... Args>
|
template<typename T, typename... Args>
|
||||||
T* AddComponent(Args&&... args) {
|
T* AddComponent(Args&&... args) {
|
||||||
|
if constexpr (std::is_base_of_v<TransformComponent, T>) {
|
||||||
|
return dynamic_cast<T*>(m_transform);
|
||||||
|
}
|
||||||
|
|
||||||
auto component = std::make_unique<T>(std::forward<Args>(args)...);
|
auto component = std::make_unique<T>(std::forward<Args>(args)...);
|
||||||
component->m_gameObject = this;
|
component->m_gameObject = this;
|
||||||
T* ptr = component.get();
|
T* ptr = component.get();
|
||||||
@@ -47,6 +53,10 @@ public:
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T* GetComponent() {
|
T* GetComponent() {
|
||||||
|
if (T* casted = dynamic_cast<T*>(m_transform)) {
|
||||||
|
return casted;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& comp : m_components) {
|
for (auto& comp : m_components) {
|
||||||
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
||||||
return casted;
|
return casted;
|
||||||
@@ -57,6 +67,10 @@ public:
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
const T* GetComponent() const {
|
const T* GetComponent() const {
|
||||||
|
if (const T* casted = dynamic_cast<const T*>(m_transform)) {
|
||||||
|
return casted;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& comp : m_components) {
|
for (auto& comp : m_components) {
|
||||||
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
||||||
return casted;
|
return casted;
|
||||||
@@ -68,6 +82,9 @@ public:
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
std::vector<T*> GetComponents() {
|
std::vector<T*> GetComponents() {
|
||||||
std::vector<T*> result;
|
std::vector<T*> result;
|
||||||
|
if (T* casted = dynamic_cast<T*>(m_transform)) {
|
||||||
|
result.push_back(casted);
|
||||||
|
}
|
||||||
for (auto& comp : m_components) {
|
for (auto& comp : m_components) {
|
||||||
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
||||||
result.push_back(casted);
|
result.push_back(casted);
|
||||||
@@ -79,6 +96,9 @@ public:
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
std::vector<const T*> GetComponents() const {
|
std::vector<const T*> GetComponents() const {
|
||||||
std::vector<const T*> result;
|
std::vector<const T*> result;
|
||||||
|
if (const T* casted = dynamic_cast<const T*>(m_transform)) {
|
||||||
|
result.push_back(casted);
|
||||||
|
}
|
||||||
for (auto& comp : m_components) {
|
for (auto& comp : m_components) {
|
||||||
if (const T* casted = dynamic_cast<const T*>(comp.get())) {
|
if (const T* casted = dynamic_cast<const T*>(comp.get())) {
|
||||||
result.push_back(casted);
|
result.push_back(casted);
|
||||||
@@ -89,6 +109,10 @@ public:
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void RemoveComponent() {
|
void RemoveComponent() {
|
||||||
|
if (dynamic_cast<T*>(m_transform)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it = m_components.begin(); it != m_components.end(); ++it) {
|
for (auto it = m_components.begin(); it != m_components.end(); ++it) {
|
||||||
if (T* casted = dynamic_cast<T*>(it->get())) {
|
if (T* casted = dynamic_cast<T*>(it->get())) {
|
||||||
m_components.erase(it);
|
m_components.erase(it);
|
||||||
@@ -97,6 +121,23 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RemoveComponent(Component* component) {
|
||||||
|
if (!component || component == m_transform) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = std::find_if(m_components.begin(), m_components.end(),
|
||||||
|
[component](const std::unique_ptr<Component>& entry) {
|
||||||
|
return entry.get() == component;
|
||||||
|
});
|
||||||
|
if (it == m_components.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_components.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T* GetComponentInChildren() {
|
T* GetComponentInChildren() {
|
||||||
T* comp = GetComponent<T>();
|
T* comp = GetComponent<T>();
|
||||||
@@ -187,4 +228,4 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Components
|
} // namespace Components
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
50
engine/include/XCEngine/Components/LightComponent.h
Normal file
50
engine/include/XCEngine/Components/LightComponent.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Components/Component.h>
|
||||||
|
#include <XCEngine/Core/Math/Color.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
enum class LightType {
|
||||||
|
Directional = 0,
|
||||||
|
Point,
|
||||||
|
Spot
|
||||||
|
};
|
||||||
|
|
||||||
|
class LightComponent : public Component {
|
||||||
|
public:
|
||||||
|
std::string GetName() const override { return "Light"; }
|
||||||
|
|
||||||
|
LightType GetLightType() const { return m_lightType; }
|
||||||
|
void SetLightType(LightType value) { m_lightType = value; }
|
||||||
|
|
||||||
|
const Math::Color& GetColor() const { return m_color; }
|
||||||
|
void SetColor(const Math::Color& value) { m_color = value; }
|
||||||
|
|
||||||
|
float GetIntensity() const { return m_intensity; }
|
||||||
|
void SetIntensity(float value);
|
||||||
|
|
||||||
|
float GetRange() const { return m_range; }
|
||||||
|
void SetRange(float value);
|
||||||
|
|
||||||
|
float GetSpotAngle() const { return m_spotAngle; }
|
||||||
|
void SetSpotAngle(float value);
|
||||||
|
|
||||||
|
bool GetCastsShadows() const { return m_castsShadows; }
|
||||||
|
void SetCastsShadows(bool value) { m_castsShadows = value; }
|
||||||
|
|
||||||
|
void Serialize(std::ostream& os) const override;
|
||||||
|
void Deserialize(std::istream& is) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
LightType m_lightType = LightType::Directional;
|
||||||
|
Math::Color m_color = Math::Color::White();
|
||||||
|
float m_intensity = 1.0f;
|
||||||
|
float m_range = 10.0f;
|
||||||
|
float m_spotAngle = 30.0f;
|
||||||
|
bool m_castsShadows = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
77
engine/src/Components/CameraComponent.cpp
Normal file
77
engine/src/Components/CameraComponent.cpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include "Components/CameraComponent.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
void CameraComponent::SetFieldOfView(float value) {
|
||||||
|
m_fieldOfView = std::clamp(value, 1.0f, 179.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraComponent::SetOrthographicSize(float value) {
|
||||||
|
m_orthographicSize = std::max(0.001f, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraComponent::SetNearClipPlane(float value) {
|
||||||
|
m_nearClipPlane = std::max(0.001f, value);
|
||||||
|
if (m_farClipPlane <= m_nearClipPlane) {
|
||||||
|
m_farClipPlane = m_nearClipPlane + 0.001f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraComponent::SetFarClipPlane(float value) {
|
||||||
|
m_farClipPlane = std::max(m_nearClipPlane + 0.001f, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraComponent::Serialize(std::ostream& os) const {
|
||||||
|
os << "projection=" << static_cast<int>(m_projectionType) << ";";
|
||||||
|
os << "fov=" << m_fieldOfView << ";";
|
||||||
|
os << "orthoSize=" << m_orthographicSize << ";";
|
||||||
|
os << "near=" << m_nearClipPlane << ";";
|
||||||
|
os << "far=" << m_farClipPlane << ";";
|
||||||
|
os << "depth=" << m_depth << ";";
|
||||||
|
os << "primary=" << (m_primary ? 1 : 0) << ";";
|
||||||
|
os << "clearColor=" << m_clearColor.r << "," << m_clearColor.g << "," << m_clearColor.b << "," << m_clearColor.a << ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraComponent::Deserialize(std::istream& is) {
|
||||||
|
std::string token;
|
||||||
|
while (std::getline(is, token, ';')) {
|
||||||
|
if (token.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t eqPos = token.find('=');
|
||||||
|
if (eqPos == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string key = token.substr(0, eqPos);
|
||||||
|
std::string value = token.substr(eqPos + 1);
|
||||||
|
|
||||||
|
if (key == "projection") {
|
||||||
|
m_projectionType = static_cast<CameraProjectionType>(std::stoi(value));
|
||||||
|
} else if (key == "fov") {
|
||||||
|
SetFieldOfView(std::stof(value));
|
||||||
|
} else if (key == "orthoSize") {
|
||||||
|
SetOrthographicSize(std::stof(value));
|
||||||
|
} else if (key == "near") {
|
||||||
|
SetNearClipPlane(std::stof(value));
|
||||||
|
} else if (key == "far") {
|
||||||
|
SetFarClipPlane(std::stof(value));
|
||||||
|
} else if (key == "depth") {
|
||||||
|
m_depth = std::stof(value);
|
||||||
|
} else if (key == "primary") {
|
||||||
|
m_primary = (std::stoi(value) != 0);
|
||||||
|
} else if (key == "clearColor") {
|
||||||
|
std::replace(value.begin(), value.end(), ',', ' ');
|
||||||
|
std::istringstream ss(value);
|
||||||
|
ss >> m_clearColor.r >> m_clearColor.g >> m_clearColor.b >> m_clearColor.a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -48,32 +48,30 @@ void GameObject::SetParent(GameObject* parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameObject::SetParent(GameObject* parent, bool worldPositionStays) {
|
void GameObject::SetParent(GameObject* parent, bool worldPositionStays) {
|
||||||
if (m_parent == parent) {
|
if (m_parent == parent || parent == this) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Math::Vector3 worldPos = worldPositionStays ? GetTransform()->GetPosition() : GetTransform()->GetLocalPosition();
|
|
||||||
Math::Quaternion worldRot = worldPositionStays ? GetTransform()->GetRotation() : GetTransform()->GetLocalRotation();
|
|
||||||
Math::Vector3 worldScale = worldPositionStays ? GetTransform()->GetScale() : GetTransform()->GetLocalScale();
|
|
||||||
|
|
||||||
if (m_parent) {
|
if (m_parent) {
|
||||||
auto& siblings = m_parent->m_children;
|
auto& siblings = m_parent->m_children;
|
||||||
siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end());
|
siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end());
|
||||||
|
} else if (m_scene) {
|
||||||
|
auto& roots = m_scene->m_rootGameObjects;
|
||||||
|
roots.erase(std::remove(roots.begin(), roots.end(), m_id), roots.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_parent = parent;
|
m_parent = parent;
|
||||||
|
|
||||||
if (m_parent) {
|
if (m_parent) {
|
||||||
m_parent->m_children.push_back(this);
|
m_parent->m_children.push_back(this);
|
||||||
|
} else if (m_scene) {
|
||||||
|
auto& roots = m_scene->m_rootGameObjects;
|
||||||
|
if (std::find(roots.begin(), roots.end(), m_id) == roots.end()) {
|
||||||
|
roots.push_back(m_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (worldPositionStays) {
|
GetTransform()->SetParent(parent ? parent->GetTransform() : nullptr, worldPositionStays);
|
||||||
GetTransform()->SetPosition(worldPos);
|
|
||||||
GetTransform()->SetRotation(worldRot);
|
|
||||||
GetTransform()->SetScale(worldScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
GetTransform()->SetDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GameObject* GameObject::GetChild(size_t index) const {
|
GameObject* GameObject::GetChild(size_t index) const {
|
||||||
@@ -88,32 +86,17 @@ std::vector<GameObject*> GameObject::GetChildren() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameObject::DetachChildren() {
|
void GameObject::DetachChildren() {
|
||||||
for (auto* child : m_children) {
|
auto children = m_children;
|
||||||
|
for (auto* child : children) {
|
||||||
if (child) {
|
if (child) {
|
||||||
child->m_parent = nullptr;
|
child->SetParent(nullptr, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_children.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameObject::DetachFromParent() {
|
void GameObject::DetachFromParent() {
|
||||||
if (m_parent) {
|
if (m_parent) {
|
||||||
Math::Vector3 worldPos = GetTransform()->GetPosition();
|
SetParent(nullptr, true);
|
||||||
Math::Quaternion worldRot = GetTransform()->GetRotation();
|
|
||||||
Math::Vector3 worldScale = GetTransform()->GetScale();
|
|
||||||
|
|
||||||
auto& siblings = m_parent->m_children;
|
|
||||||
siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end());
|
|
||||||
m_parent = nullptr;
|
|
||||||
|
|
||||||
if (m_scene) {
|
|
||||||
m_scene->m_rootGameObjects.push_back(m_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
GetTransform()->SetPosition(worldPos);
|
|
||||||
GetTransform()->SetRotation(worldRot);
|
|
||||||
GetTransform()->SetScale(worldScale);
|
|
||||||
GetTransform()->SetDirty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,4 +243,4 @@ void GameObject::Deserialize(std::istream& is) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Components
|
} // namespace Components
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
64
engine/src/Components/LightComponent.cpp
Normal file
64
engine/src/Components/LightComponent.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include "Components/LightComponent.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
void LightComponent::SetIntensity(float value) {
|
||||||
|
m_intensity = std::max(0.0f, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::SetRange(float value) {
|
||||||
|
m_range = std::max(0.001f, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::SetSpotAngle(float value) {
|
||||||
|
m_spotAngle = std::clamp(value, 1.0f, 179.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::Serialize(std::ostream& os) const {
|
||||||
|
os << "type=" << static_cast<int>(m_lightType) << ";";
|
||||||
|
os << "color=" << m_color.r << "," << m_color.g << "," << m_color.b << "," << m_color.a << ";";
|
||||||
|
os << "intensity=" << m_intensity << ";";
|
||||||
|
os << "range=" << m_range << ";";
|
||||||
|
os << "spotAngle=" << m_spotAngle << ";";
|
||||||
|
os << "shadows=" << (m_castsShadows ? 1 : 0) << ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::Deserialize(std::istream& is) {
|
||||||
|
std::string token;
|
||||||
|
while (std::getline(is, token, ';')) {
|
||||||
|
if (token.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t eqPos = token.find('=');
|
||||||
|
if (eqPos == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string key = token.substr(0, eqPos);
|
||||||
|
std::string value = token.substr(eqPos + 1);
|
||||||
|
|
||||||
|
if (key == "type") {
|
||||||
|
m_lightType = static_cast<LightType>(std::stoi(value));
|
||||||
|
} else if (key == "color") {
|
||||||
|
std::replace(value.begin(), value.end(), ',', ' ');
|
||||||
|
std::istringstream ss(value);
|
||||||
|
ss >> m_color.r >> m_color.g >> m_color.b >> m_color.a;
|
||||||
|
} else if (key == "intensity") {
|
||||||
|
SetIntensity(std::stof(value));
|
||||||
|
} else if (key == "range") {
|
||||||
|
SetRange(std::stof(value));
|
||||||
|
} else if (key == "spotAngle") {
|
||||||
|
SetSpotAngle(std::stof(value));
|
||||||
|
} else if (key == "shadows") {
|
||||||
|
m_castsShadows = (std::stoi(value) != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -1,10 +1,126 @@
|
|||||||
#include "Scene/Scene.h"
|
#include "Scene/Scene.h"
|
||||||
#include "Components/GameObject.h"
|
#include "Components/GameObject.h"
|
||||||
|
#include "Components/TransformComponent.h"
|
||||||
|
#include "Components/CameraComponent.h"
|
||||||
|
#include "Components/LightComponent.h"
|
||||||
|
#include "Components/AudioSourceComponent.h"
|
||||||
|
#include "Components/AudioListenerComponent.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct PendingComponentData {
|
||||||
|
std::string type;
|
||||||
|
std::string payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PendingGameObjectData {
|
||||||
|
GameObject::ID id = GameObject::INVALID_ID;
|
||||||
|
std::string name = "GameObject";
|
||||||
|
bool active = true;
|
||||||
|
GameObject::ID parentId = GameObject::INVALID_ID;
|
||||||
|
std::string transformPayload;
|
||||||
|
std::vector<PendingComponentData> components;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string EscapeString(const std::string& value) {
|
||||||
|
std::string escaped;
|
||||||
|
escaped.reserve(value.size());
|
||||||
|
for (char ch : value) {
|
||||||
|
if (ch == '\\' || ch == '\n' || ch == '\r') {
|
||||||
|
escaped.push_back('\\');
|
||||||
|
if (ch == '\n') {
|
||||||
|
escaped.push_back('n');
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
escaped.push_back('r');
|
||||||
|
} else {
|
||||||
|
escaped.push_back(ch);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
escaped.push_back(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UnescapeString(const std::string& value) {
|
||||||
|
std::string unescaped;
|
||||||
|
unescaped.reserve(value.size());
|
||||||
|
for (size_t i = 0; i < value.size(); ++i) {
|
||||||
|
if (value[i] == '\\' && i + 1 < value.size()) {
|
||||||
|
++i;
|
||||||
|
switch (value[i]) {
|
||||||
|
case 'n': unescaped.push_back('\n'); break;
|
||||||
|
case 'r': unescaped.push_back('\r'); break;
|
||||||
|
default: unescaped.push_back(value[i]); break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unescaped.push_back(value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unescaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component* CreateComponentByType(GameObject* gameObject, const std::string& type) {
|
||||||
|
if (!gameObject) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "Camera") {
|
||||||
|
return gameObject->AddComponent<CameraComponent>();
|
||||||
|
}
|
||||||
|
if (type == "Light") {
|
||||||
|
return gameObject->AddComponent<LightComponent>();
|
||||||
|
}
|
||||||
|
if (type == "AudioSource") {
|
||||||
|
return gameObject->AddComponent<AudioSourceComponent>();
|
||||||
|
}
|
||||||
|
if (type == "AudioListener") {
|
||||||
|
return gameObject->AddComponent<AudioListenerComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerializeGameObjectRecursive(std::ostream& os, GameObject* gameObject) {
|
||||||
|
if (!gameObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
os << "gameobject_begin\n";
|
||||||
|
os << "id=" << gameObject->GetID() << "\n";
|
||||||
|
os << "name=" << EscapeString(gameObject->GetName()) << "\n";
|
||||||
|
os << "active=" << (gameObject->IsActive() ? 1 : 0) << "\n";
|
||||||
|
os << "parent=" << (gameObject->GetParent() ? gameObject->GetParent()->GetID() : GameObject::INVALID_ID) << "\n";
|
||||||
|
os << "transform=";
|
||||||
|
gameObject->GetTransform()->Serialize(os);
|
||||||
|
os << "\n";
|
||||||
|
|
||||||
|
auto components = gameObject->GetComponents<Component>();
|
||||||
|
for (Component* component : components) {
|
||||||
|
if (!component || component == gameObject->GetTransform()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
os << "component=" << component->GetName() << ";";
|
||||||
|
component->Serialize(os);
|
||||||
|
os << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
os << "gameobject_end\n";
|
||||||
|
|
||||||
|
for (GameObject* child : gameObject->GetChildren()) {
|
||||||
|
SerializeGameObjectRecursive(os, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Scene::Scene()
|
Scene::Scene()
|
||||||
: m_name("Untitled") {
|
: m_name("Untitled") {
|
||||||
}
|
}
|
||||||
@@ -24,14 +140,13 @@ GameObject* Scene::CreateGameObject(const std::string& name, GameObject* parent)
|
|||||||
GameObject::GetGlobalRegistry()[ptr->m_id] = ptr;
|
GameObject::GetGlobalRegistry()[ptr->m_id] = ptr;
|
||||||
m_gameObjectIDs.insert(ptr->m_id);
|
m_gameObjectIDs.insert(ptr->m_id);
|
||||||
m_gameObjects.emplace(ptr->m_id, std::move(gameObject));
|
m_gameObjects.emplace(ptr->m_id, std::move(gameObject));
|
||||||
|
ptr->m_scene = this;
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
ptr->SetParent(parent);
|
ptr->SetParent(parent);
|
||||||
} else {
|
} else {
|
||||||
m_rootGameObjects.push_back(ptr->m_id);
|
m_rootGameObjects.push_back(ptr->m_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr->m_scene = this;
|
|
||||||
ptr->Awake();
|
ptr->Awake();
|
||||||
|
|
||||||
m_onGameObjectCreated.Invoke(ptr);
|
m_onGameObjectCreated.Invoke(ptr);
|
||||||
@@ -159,26 +274,118 @@ void Scene::Load(const std::string& filePath) {
|
|||||||
m_rootGameObjects.clear();
|
m_rootGameObjects.clear();
|
||||||
m_gameObjectIDs.clear();
|
m_gameObjectIDs.clear();
|
||||||
|
|
||||||
|
std::vector<PendingGameObjectData> pendingObjects;
|
||||||
std::string line;
|
std::string line;
|
||||||
|
PendingGameObjectData* currentObject = nullptr;
|
||||||
|
GameObject::ID maxId = 0;
|
||||||
|
|
||||||
while (std::getline(file, line)) {
|
while (std::getline(file, line)) {
|
||||||
if (line.empty() || line[0] == '#') continue;
|
if (line.empty() || line[0] == '#') continue;
|
||||||
|
|
||||||
std::istringstream iss(line);
|
if (line == "gameobject_begin") {
|
||||||
std::string token;
|
pendingObjects.emplace_back();
|
||||||
std::getline(iss, token, '=');
|
currentObject = &pendingObjects.back();
|
||||||
|
continue;
|
||||||
if (token == "scene") {
|
|
||||||
std::getline(iss, m_name, ';');
|
|
||||||
} else if (token == "gameobject") {
|
|
||||||
auto go = std::make_unique<GameObject>();
|
|
||||||
go->Deserialize(iss);
|
|
||||||
go->m_scene = this;
|
|
||||||
GameObject* ptr = go.get();
|
|
||||||
GameObject::GetGlobalRegistry()[ptr->m_id] = ptr;
|
|
||||||
m_gameObjectIDs.insert(ptr->m_id);
|
|
||||||
m_rootGameObjects.push_back(ptr->m_id);
|
|
||||||
m_gameObjects.emplace(ptr->m_id, std::move(go));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (line == "gameobject_end") {
|
||||||
|
currentObject = nullptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t eqPos = line.find('=');
|
||||||
|
if (eqPos == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string key = line.substr(0, eqPos);
|
||||||
|
const std::string value = line.substr(eqPos + 1);
|
||||||
|
|
||||||
|
if (!currentObject) {
|
||||||
|
if (key == "scene") {
|
||||||
|
m_name = UnescapeString(value);
|
||||||
|
} else if (key == "active") {
|
||||||
|
m_active = (value == "1");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == "id") {
|
||||||
|
currentObject->id = static_cast<GameObject::ID>(std::stoull(value));
|
||||||
|
maxId = std::max(maxId, currentObject->id);
|
||||||
|
} else if (key == "name") {
|
||||||
|
currentObject->name = UnescapeString(value);
|
||||||
|
} else if (key == "active") {
|
||||||
|
currentObject->active = (value == "1");
|
||||||
|
} else if (key == "parent") {
|
||||||
|
currentObject->parentId = static_cast<GameObject::ID>(std::stoull(value));
|
||||||
|
} else if (key == "transform") {
|
||||||
|
currentObject->transformPayload = value;
|
||||||
|
} else if (key == "component") {
|
||||||
|
const size_t typeEnd = value.find(';');
|
||||||
|
PendingComponentData componentData;
|
||||||
|
if (typeEnd == std::string::npos) {
|
||||||
|
componentData.type = value;
|
||||||
|
} else {
|
||||||
|
componentData.type = value.substr(0, typeEnd);
|
||||||
|
componentData.payload = value.substr(typeEnd + 1);
|
||||||
|
}
|
||||||
|
currentObject->components.push_back(std::move(componentData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<GameObject::ID, GameObject*> createdObjects;
|
||||||
|
createdObjects.reserve(pendingObjects.size());
|
||||||
|
|
||||||
|
for (const PendingGameObjectData& pending : pendingObjects) {
|
||||||
|
auto go = std::make_unique<GameObject>(pending.name);
|
||||||
|
go->m_id = pending.id;
|
||||||
|
go->m_activeSelf = pending.active;
|
||||||
|
go->m_scene = this;
|
||||||
|
|
||||||
|
if (!pending.transformPayload.empty()) {
|
||||||
|
std::istringstream transformStream(pending.transformPayload);
|
||||||
|
go->m_transform->Deserialize(transformStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const PendingComponentData& componentData : pending.components) {
|
||||||
|
if (Component* component = CreateComponentByType(go.get(), componentData.type)) {
|
||||||
|
if (!componentData.payload.empty()) {
|
||||||
|
std::istringstream componentStream(componentData.payload);
|
||||||
|
component->Deserialize(componentStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject* ptr = go.get();
|
||||||
|
GameObject::GetGlobalRegistry()[ptr->m_id] = ptr;
|
||||||
|
m_gameObjectIDs.insert(ptr->m_id);
|
||||||
|
createdObjects[ptr->m_id] = ptr;
|
||||||
|
m_gameObjects.emplace(ptr->m_id, std::move(go));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rootGameObjects.clear();
|
||||||
|
for (const PendingGameObjectData& pending : pendingObjects) {
|
||||||
|
auto it = createdObjects.find(pending.id);
|
||||||
|
if (it == createdObjects.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject* gameObject = it->second;
|
||||||
|
if (pending.parentId == GameObject::INVALID_ID) {
|
||||||
|
m_rootGameObjects.push_back(gameObject->GetID());
|
||||||
|
} else {
|
||||||
|
auto parentIt = createdObjects.find(pending.parentId);
|
||||||
|
if (parentIt != createdObjects.end()) {
|
||||||
|
gameObject->SetParent(parentIt->second, false);
|
||||||
|
} else {
|
||||||
|
m_rootGameObjects.push_back(gameObject->GetID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxId != GameObject::INVALID_ID && GameObject::s_nextID <= maxId) {
|
||||||
|
GameObject::s_nextID = maxId + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,15 +396,14 @@ void Scene::Save(const std::string& filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
file << "# XCEngine Scene File\n";
|
file << "# XCEngine Scene File\n";
|
||||||
file << "scene=" << m_name << ";\n";
|
file << "scene=" << EscapeString(m_name) << "\n";
|
||||||
file << "active=" << (m_active ? "1" : "0") << ";\n\n";
|
file << "active=" << (m_active ? "1" : "0") << "\n\n";
|
||||||
|
|
||||||
for (auto* go : GetRootGameObjects()) {
|
for (auto* go : GetRootGameObjects()) {
|
||||||
file << "gameobject=";
|
SerializeGameObjectRecursive(file, go);
|
||||||
go->Serialize(file);
|
|
||||||
file << "\n";
|
file << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Components
|
} // namespace Components
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ set(COMPONENTS_TEST_SOURCES
|
|||||||
test_component.cpp
|
test_component.cpp
|
||||||
test_transform_component.cpp
|
test_transform_component.cpp
|
||||||
test_game_object.cpp
|
test_game_object.cpp
|
||||||
|
test_camera_light_component.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(components_tests ${COMPONENTS_TEST_SOURCES})
|
add_executable(components_tests ${COMPONENTS_TEST_SOURCES})
|
||||||
@@ -27,4 +28,4 @@ target_include_directories(components_tests PRIVATE
|
|||||||
)
|
)
|
||||||
|
|
||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
gtest_discover_tests(components_tests)
|
gtest_discover_tests(components_tests)
|
||||||
|
|||||||
57
tests/Components/test_camera_light_component.cpp
Normal file
57
tests/Components/test_camera_light_component.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
|
|
||||||
|
using namespace XCEngine::Components;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(CameraComponent_Test, DefaultValues) {
|
||||||
|
CameraComponent camera;
|
||||||
|
|
||||||
|
EXPECT_EQ(camera.GetProjectionType(), CameraProjectionType::Perspective);
|
||||||
|
EXPECT_FLOAT_EQ(camera.GetFieldOfView(), 60.0f);
|
||||||
|
EXPECT_FLOAT_EQ(camera.GetOrthographicSize(), 5.0f);
|
||||||
|
EXPECT_FLOAT_EQ(camera.GetNearClipPlane(), 0.1f);
|
||||||
|
EXPECT_FLOAT_EQ(camera.GetFarClipPlane(), 1000.0f);
|
||||||
|
EXPECT_TRUE(camera.IsPrimary());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CameraComponent_Test, SetterClamping) {
|
||||||
|
CameraComponent camera;
|
||||||
|
|
||||||
|
camera.SetFieldOfView(500.0f);
|
||||||
|
camera.SetOrthographicSize(-1.0f);
|
||||||
|
camera.SetNearClipPlane(-10.0f);
|
||||||
|
camera.SetFarClipPlane(0.0f);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(camera.GetFieldOfView(), 179.0f);
|
||||||
|
EXPECT_FLOAT_EQ(camera.GetOrthographicSize(), 0.001f);
|
||||||
|
EXPECT_FLOAT_EQ(camera.GetNearClipPlane(), 0.001f);
|
||||||
|
EXPECT_GT(camera.GetFarClipPlane(), camera.GetNearClipPlane());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LightComponent_Test, DefaultValues) {
|
||||||
|
LightComponent light;
|
||||||
|
|
||||||
|
EXPECT_EQ(light.GetLightType(), LightType::Directional);
|
||||||
|
EXPECT_FLOAT_EQ(light.GetIntensity(), 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(light.GetRange(), 10.0f);
|
||||||
|
EXPECT_FLOAT_EQ(light.GetSpotAngle(), 30.0f);
|
||||||
|
EXPECT_FALSE(light.GetCastsShadows());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LightComponent_Test, SetterClamping) {
|
||||||
|
LightComponent light;
|
||||||
|
|
||||||
|
light.SetIntensity(-3.0f);
|
||||||
|
light.SetRange(-1.0f);
|
||||||
|
light.SetSpotAngle(500.0f);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(light.GetIntensity(), 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(light.GetRange(), 0.001f);
|
||||||
|
EXPECT_FLOAT_EQ(light.GetSpotAngle(), 179.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@@ -142,7 +142,7 @@ TEST(GameObject_Test, SetParent_WithoutWorldPosition) {
|
|||||||
child.SetParent(&parent, false);
|
child.SetParent(&parent, false);
|
||||||
|
|
||||||
Vector3 childWorldPos = child.GetTransform()->GetPosition();
|
Vector3 childWorldPos = child.GetTransform()->GetPosition();
|
||||||
EXPECT_NEAR(childWorldPos.x, 2.0f, 0.001f);
|
EXPECT_NEAR(childWorldPos.x, 3.0f, 0.001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GameObject_Test, GetChild_ValidIndex) {
|
TEST(GameObject_Test, GetChild_ValidIndex) {
|
||||||
@@ -275,4 +275,4 @@ TEST(GameObject_Test, GetChildCount) {
|
|||||||
EXPECT_EQ(parent.GetChildCount(), 2u);
|
EXPECT_EQ(parent.GetChildCount(), 2u);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <XCEngine/Scene/Scene.h>
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
#include <XCEngine/Components/GameObject.h>
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
#include <XCEngine/Components/TransformComponent.h>
|
#include <XCEngine/Components/TransformComponent.h>
|
||||||
#include <XCEngine/Core/Math/Vector3.h>
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
@@ -36,6 +39,10 @@ protected:
|
|||||||
testScene = std::make_unique<Scene>("TestScene");
|
testScene = std::make_unique<Scene>("TestScene");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetTempScenePath(const char* fileName) const {
|
||||||
|
return std::filesystem::temp_directory_path() / fileName;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<Scene> testScene;
|
std::unique_ptr<Scene> testScene;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,30 +229,128 @@ TEST_F(SceneTest, Save_And_Load) {
|
|||||||
GameObject* go = testScene->CreateGameObject("SavedObject");
|
GameObject* go = testScene->CreateGameObject("SavedObject");
|
||||||
go->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
go->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||||
go->SetActive(true);
|
go->SetActive(true);
|
||||||
|
|
||||||
testScene->Save("test_scene.scene");
|
const std::filesystem::path scenePath = GetTempScenePath("test_scene.xc");
|
||||||
|
testScene->Save(scenePath.string());
|
||||||
|
|
||||||
Scene loadedScene;
|
Scene loadedScene;
|
||||||
loadedScene.Load("test_scene.scene");
|
loadedScene.Load(scenePath.string());
|
||||||
|
|
||||||
GameObject* loadedGo = loadedScene.Find("SavedObject");
|
GameObject* loadedGo = loadedScene.Find("SavedObject");
|
||||||
EXPECT_NE(loadedGo, nullptr);
|
EXPECT_NE(loadedGo, nullptr);
|
||||||
EXPECT_EQ(loadedGo->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
EXPECT_EQ(loadedGo->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||||
|
|
||||||
|
std::filesystem::remove(scenePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SceneTest, Save_ContainsGameObjectData) {
|
TEST_F(SceneTest, Save_ContainsGameObjectData) {
|
||||||
testScene->CreateGameObject("Player");
|
testScene->CreateGameObject("Player");
|
||||||
testScene->CreateGameObject("Enemy");
|
testScene->CreateGameObject("Enemy");
|
||||||
|
|
||||||
testScene->Save("test_scene_multi.scene");
|
const std::filesystem::path scenePath = GetTempScenePath("test_scene_multi.xc");
|
||||||
|
testScene->Save(scenePath.string());
|
||||||
std::ifstream file("test_scene_multi.scene");
|
|
||||||
|
std::ifstream file(scenePath);
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
buffer << file.rdbuf();
|
buffer << file.rdbuf();
|
||||||
std::string content = buffer.str();
|
std::string content = buffer.str();
|
||||||
|
file.close();
|
||||||
|
|
||||||
EXPECT_TRUE(content.find("Player") != std::string::npos);
|
EXPECT_TRUE(content.find("Player") != std::string::npos);
|
||||||
EXPECT_TRUE(content.find("Enemy") != std::string::npos);
|
EXPECT_TRUE(content.find("Enemy") != std::string::npos);
|
||||||
|
|
||||||
|
std::filesystem::remove(scenePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
TEST_F(SceneTest, Save_And_Load_PreservesHierarchyAndComponents) {
|
||||||
|
testScene->SetName("Serialized Scene");
|
||||||
|
testScene->SetActive(false);
|
||||||
|
|
||||||
|
GameObject* parent = testScene->CreateGameObject("Rig Root");
|
||||||
|
parent->GetTransform()->SetLocalPosition(Vector3(5.0f, 0.0f, 0.0f));
|
||||||
|
parent->SetActive(false);
|
||||||
|
|
||||||
|
auto* light = parent->AddComponent<LightComponent>();
|
||||||
|
light->SetLightType(LightType::Spot);
|
||||||
|
light->SetIntensity(3.5f);
|
||||||
|
light->SetRange(12.0f);
|
||||||
|
light->SetSpotAngle(45.0f);
|
||||||
|
light->SetCastsShadows(true);
|
||||||
|
|
||||||
|
GameObject* child = testScene->CreateGameObject("Main Camera", parent);
|
||||||
|
child->GetTransform()->SetLocalPosition(Vector3(1.0f, 2.0f, 3.0f));
|
||||||
|
child->GetTransform()->SetLocalScale(Vector3(2.0f, 2.0f, 2.0f));
|
||||||
|
|
||||||
|
auto* camera = child->AddComponent<CameraComponent>();
|
||||||
|
camera->SetProjectionType(CameraProjectionType::Orthographic);
|
||||||
|
camera->SetOrthographicSize(7.5f);
|
||||||
|
camera->SetNearClipPlane(0.5f);
|
||||||
|
camera->SetFarClipPlane(250.0f);
|
||||||
|
camera->SetDepth(2.0f);
|
||||||
|
camera->SetPrimary(false);
|
||||||
|
|
||||||
|
const std::filesystem::path scenePath = GetTempScenePath("test_scene_hierarchy_components.xc");
|
||||||
|
testScene->Save(scenePath.string());
|
||||||
|
|
||||||
|
Scene loadedScene;
|
||||||
|
loadedScene.Load(scenePath.string());
|
||||||
|
|
||||||
|
EXPECT_EQ(loadedScene.GetName(), "Serialized Scene");
|
||||||
|
EXPECT_FALSE(loadedScene.IsActive());
|
||||||
|
|
||||||
|
GameObject* loadedParent = loadedScene.Find("Rig Root");
|
||||||
|
GameObject* loadedChild = loadedScene.Find("Main Camera");
|
||||||
|
ASSERT_NE(loadedParent, nullptr);
|
||||||
|
ASSERT_NE(loadedChild, nullptr);
|
||||||
|
|
||||||
|
EXPECT_EQ(loadedChild->GetParent(), loadedParent);
|
||||||
|
EXPECT_EQ(loadedScene.GetRootGameObjects().size(), 1u);
|
||||||
|
EXPECT_FALSE(loadedParent->IsActive());
|
||||||
|
EXPECT_EQ(loadedParent->GetTransform()->GetLocalPosition(), Vector3(5.0f, 0.0f, 0.0f));
|
||||||
|
EXPECT_EQ(loadedChild->GetTransform()->GetLocalPosition(), Vector3(1.0f, 2.0f, 3.0f));
|
||||||
|
EXPECT_EQ(loadedChild->GetTransform()->GetPosition(), Vector3(6.0f, 2.0f, 3.0f));
|
||||||
|
|
||||||
|
auto* loadedLight = loadedParent->GetComponent<LightComponent>();
|
||||||
|
ASSERT_NE(loadedLight, nullptr);
|
||||||
|
EXPECT_EQ(loadedLight->GetLightType(), LightType::Spot);
|
||||||
|
EXPECT_FLOAT_EQ(loadedLight->GetIntensity(), 3.5f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedLight->GetRange(), 12.0f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedLight->GetSpotAngle(), 45.0f);
|
||||||
|
EXPECT_TRUE(loadedLight->GetCastsShadows());
|
||||||
|
|
||||||
|
auto* loadedCamera = loadedChild->GetComponent<CameraComponent>();
|
||||||
|
ASSERT_NE(loadedCamera, nullptr);
|
||||||
|
EXPECT_EQ(loadedCamera->GetProjectionType(), CameraProjectionType::Orthographic);
|
||||||
|
EXPECT_FLOAT_EQ(loadedCamera->GetOrthographicSize(), 7.5f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedCamera->GetNearClipPlane(), 0.5f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedCamera->GetFarClipPlane(), 250.0f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedCamera->GetDepth(), 2.0f);
|
||||||
|
EXPECT_FALSE(loadedCamera->IsPrimary());
|
||||||
|
|
||||||
|
std::filesystem::remove(scenePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SceneTest, Save_ContainsHierarchyAndComponentEntries) {
|
||||||
|
GameObject* parent = testScene->CreateGameObject("Parent");
|
||||||
|
GameObject* child = testScene->CreateGameObject("Child", parent);
|
||||||
|
child->AddComponent<CameraComponent>();
|
||||||
|
parent->AddComponent<LightComponent>();
|
||||||
|
|
||||||
|
const std::filesystem::path scenePath = GetTempScenePath("test_scene_format.xc");
|
||||||
|
testScene->Save(scenePath.string());
|
||||||
|
|
||||||
|
std::ifstream file(scenePath);
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
const std::string content = buffer.str();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
EXPECT_TRUE(content.find("gameobject_begin") != std::string::npos);
|
||||||
|
EXPECT_TRUE(content.find("parent=" + std::to_string(parent->GetID())) != std::string::npos);
|
||||||
|
EXPECT_TRUE(content.find("component=Camera;") != std::string::npos);
|
||||||
|
EXPECT_TRUE(content.find("component=Light;") != std::string::npos);
|
||||||
|
|
||||||
|
std::filesystem::remove(scenePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user