From 4335a8cb058eff709b708c45d6f586394faa8781 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Thu, 23 Apr 2026 00:38:20 +0300 Subject: [PATCH] Skybox and sun --- src/features/editScene/CMakeLists.txt | 8 + src/features/editScene/EditorApp.cpp | 23 ++ src/features/editScene/EditorApp.hpp | 4 + src/features/editScene/components/Skybox.hpp | 58 ++++ .../editScene/components/SkyboxModule.cpp | 24 ++ src/features/editScene/components/Sun.hpp | 70 ++++ .../editScene/components/SunModule.cpp | 24 ++ src/features/editScene/physics/physics.cpp | 9 +- .../resources/materials/scripts/skybox.frag | 78 +++++ .../materials/scripts/skybox.material | 21 ++ .../materials/scripts/skybox.program | 27 ++ .../resources/materials/scripts/skybox.vert | 16 + .../editScene/systems/BuoyancySystem.cpp | 13 +- .../editScene/systems/EditorSkyboxSystem.cpp | 228 +++++++++++++ .../editScene/systems/EditorSkyboxSystem.hpp | 33 ++ .../editScene/systems/EditorSunSystem.cpp | 314 ++++++++++++++++++ .../editScene/systems/EditorSunSystem.hpp | 32 ++ .../editScene/systems/EditorUISystem.cpp | 20 ++ .../editScene/systems/SceneSerializer.cpp | 187 +++++++++++ .../editScene/systems/SceneSerializer.hpp | 6 + src/features/editScene/ui/SkyboxEditor.hpp | 127 +++++++ src/features/editScene/ui/SunEditor.hpp | 147 ++++++++ 22 files changed, 1460 insertions(+), 9 deletions(-) create mode 100644 src/features/editScene/components/Skybox.hpp create mode 100644 src/features/editScene/components/SkyboxModule.cpp create mode 100644 src/features/editScene/components/Sun.hpp create mode 100644 src/features/editScene/components/SunModule.cpp create mode 100644 src/features/editScene/resources/materials/scripts/skybox.frag create mode 100644 src/features/editScene/resources/materials/scripts/skybox.material create mode 100644 src/features/editScene/resources/materials/scripts/skybox.program create mode 100644 src/features/editScene/resources/materials/scripts/skybox.vert create mode 100644 src/features/editScene/systems/EditorSkyboxSystem.cpp create mode 100644 src/features/editScene/systems/EditorSkyboxSystem.hpp create mode 100644 src/features/editScene/systems/EditorSunSystem.cpp create mode 100644 src/features/editScene/systems/EditorSunSystem.hpp create mode 100644 src/features/editScene/ui/SkyboxEditor.hpp create mode 100644 src/features/editScene/ui/SunEditor.hpp diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index 2272870..1ed6247 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -16,6 +16,8 @@ set(EDITSCENE_SOURCES systems/SceneSerializer.cpp systems/PhysicsSystem.cpp systems/BuoyancySystem.cpp + systems/EditorSunSystem.cpp + systems/EditorSkyboxSystem.cpp systems/LightSystem.cpp systems/CameraSystem.cpp systems/LodSystem.cpp @@ -84,6 +86,8 @@ set(EDITSCENE_SOURCES components/BuoyancyInfoModule.cpp components/WaterPhysicsModule.cpp components/WaterPlaneModule.cpp + components/SunModule.cpp + components/SkyboxModule.cpp camera/EditorCamera.cpp gizmo/Gizmo.cpp physics/physics.cpp @@ -100,6 +104,8 @@ set(EDITSCENE_HEADERS components/BuoyancyInfo.hpp components/WaterPhysics.hpp components/WaterPlane.hpp + components/Sun.hpp + components/Skybox.hpp components/Light.hpp components/Camera.hpp components/Lod.hpp @@ -132,6 +138,8 @@ set(EDITSCENE_HEADERS systems/SceneSerializer.hpp systems/PhysicsSystem.hpp systems/BuoyancySystem.hpp + systems/EditorSunSystem.hpp + systems/EditorSkyboxSystem.hpp systems/LightSystem.hpp systems/CameraSystem.hpp systems/LodSystem.hpp diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 041aa00..dd3586b 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -3,6 +3,8 @@ #include "systems/EditorUISystem.hpp" #include "systems/PhysicsSystem.hpp" #include "systems/BuoyancySystem.hpp" +#include "systems/EditorSunSystem.hpp" +#include "systems/EditorSkyboxSystem.hpp" #include "systems/LightSystem.hpp" #include "systems/CameraSystem.hpp" #include "systems/LodSystem.hpp" @@ -30,6 +32,8 @@ #include "components/InWater.hpp" #include "components/WaterPhysics.hpp" #include "components/WaterPlane.hpp" +#include "components/Sun.hpp" +#include "components/Skybox.hpp" #include "components/Light.hpp" #include "components/Camera.hpp" #include "components/Lod.hpp" @@ -231,6 +235,10 @@ void EditorApp::setup() m_world, m_physicsSystem->getPhysicsWrapper()); m_buoyancySystem->initialize(); + m_sunSystem = std::make_unique(m_world, m_sceneMgr); + m_skyboxSystem = + std::make_unique(m_world, m_sceneMgr); + // Apply debug setting if it was set before system creation if (m_debugBuoyancy) { m_buoyancySystem->setDebugEnabled(true); @@ -500,6 +508,10 @@ void EditorApp::setupECS() m_world.component(); m_world.component(); + // Register environment components + m_world.component(); + m_world.component(); + // Register CellGrid/Town components CellGridModule::registerComponents(m_world); } @@ -682,6 +694,17 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt) } /* --- Rendering support systems --- */ + if (m_sunSystem) { + m_sunSystem->update(evt.timeSinceLastFrame); + } + if (m_skyboxSystem) { + Ogre::Camera *cam = nullptr; + if (m_sceneMgr->hasCamera("PlayerCamera")) + cam = m_sceneMgr->getCamera("PlayerCamera"); + if (!cam && m_camera) + cam = m_camera->getCamera(); + m_skyboxSystem->update(cam); + } if (m_lightSystem) { m_lightSystem->update(); } diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index f3f2cb4..b7f4398 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -29,6 +29,8 @@ class RoomLayoutSystem; class StartupMenuSystem; class PlayerControllerSystem; class BuoyancySystem; +class EditorSunSystem; +class EditorSkyboxSystem; class EditorApp; /** @@ -196,6 +198,8 @@ private: std::unique_ptr m_imguiListener; std::unique_ptr m_physicsSystem; std::unique_ptr m_buoyancySystem; + std::unique_ptr m_sunSystem; + std::unique_ptr m_skyboxSystem; std::unique_ptr m_lightSystem; std::unique_ptr m_cameraSystem; std::unique_ptr m_lodSystem; diff --git a/src/features/editScene/components/Skybox.hpp b/src/features/editScene/components/Skybox.hpp new file mode 100644 index 0000000..8ad1ef5 --- /dev/null +++ b/src/features/editScene/components/Skybox.hpp @@ -0,0 +1,58 @@ +#ifndef EDITSCENE_SKYBOX_HPP +#define EDITSCENE_SKYBOX_HPP +#pragma once + +#include + +/** + * Skybox component - procedural sky rendered as a large cube + * with a fragment shader that creates dynamic day/night/sunset + * sky gradients. + * + * Designed to work alongside SunComponent on the same entity. + * If no SunComponent is present, uses default noon lighting. + */ +struct SkyboxComponent { + // Enable/disable skybox + bool enabled = true; + + // Size of the skybox cube (default 500) + float size = 500.0f; + + // Day sky colors + Ogre::ColourValue dayTopColor = Ogre::ColourValue(0.2f, 0.5f, 1.0f); + Ogre::ColourValue dayBottomColor = Ogre::ColourValue(0.6f, 0.8f, 1.0f); + + // Night sky colors + Ogre::ColourValue nightTopColor = Ogre::ColourValue(0.0f, 0.0f, 0.05f); + Ogre::ColourValue nightBottomColor = Ogre::ColourValue(0.05f, 0.05f, 0.15f); + + // Horizon glow colors + Ogre::ColourValue sunriseColor = Ogre::ColourValue(1.0f, 0.5f, 0.2f); + Ogre::ColourValue sunsetColor = Ogre::ColourValue(1.0f, 0.3f, 0.1f); + + // Angular size of sun/moon discs in the sky shader (0.01 - 0.2) + float sunSize = 0.05f; + float moonSize = 0.03f; + + // Enable simple stars at night + bool starsEnabled = true; + + // Cloud coverage (0.0 = clear, 1.0 = overcast) + // Not yet implemented in shader but reserved for future + float cloudiness = 0.0f; + + // Runtime objects (managed by EditorSkyboxSystem) + Ogre::SceneNode *sceneNode = nullptr; + Ogre::ManualObject *manualObject = nullptr; + + // Dirty flag - triggers rebuild + bool dirty = true; + + void markDirty() + { + dirty = true; + } +}; + +#endif // EDITSCENE_SKYBOX_HPP diff --git a/src/features/editScene/components/SkyboxModule.cpp b/src/features/editScene/components/SkyboxModule.cpp new file mode 100644 index 0000000..f697422 --- /dev/null +++ b/src/features/editScene/components/SkyboxModule.cpp @@ -0,0 +1,24 @@ +#include "Skybox.hpp" +#include "Transform.hpp" +#include "EditorMarker.hpp" +#include "EntityName.hpp" +#include "../ui/ComponentRegistration.hpp" +#include "../ui/SkyboxEditor.hpp" + +REGISTER_COMPONENT_GROUP("Skybox", "Environment", SkyboxComponent, SkyboxEditor) +{ + registry.registerComponent( + "Skybox", "Environment", std::make_unique(), + // Adder + [](flecs::entity e) { + if (!e.has()) { + e.set({}); + } + }, + // Remover + [](flecs::entity e) { + if (e.has()) { + e.remove(); + } + }); +} diff --git a/src/features/editScene/components/Sun.hpp b/src/features/editScene/components/Sun.hpp new file mode 100644 index 0000000..fb7e046 --- /dev/null +++ b/src/features/editScene/components/Sun.hpp @@ -0,0 +1,70 @@ +#ifndef EDITSCENE_SUN_HPP +#define EDITSCENE_SUN_HPP +#pragma once + +#include + +/** + * Sun component - manages a directional light that rotates + * based on in-game time, with sun/moon visualization. + * + * Designed to work alongside SkyboxComponent on the same entity. + */ +struct SunComponent { + // Enable/disable sun system + bool enabled = true; + + // Time of day in hours (0.0 - 24.0) + float timeOfDay = 12.0f; + + // Game time speed: game-hours per real-second + // 0.01 = 1 game hour per 100 real seconds (slow) + // 0.1 = 1 game hour per 10 real seconds + // 1.0 = 1 game hour per 1 real second (fast) + float timeSpeed = 0.05f; + + // Sun color when at zenith (day) + Ogre::ColourValue sunColor = Ogre::ColourValue(1.0f, 0.95f, 0.8f); + + // Moon color when sun is below horizon (night) + Ogre::ColourValue moonColor = Ogre::ColourValue(0.3f, 0.3f, 0.5f); + + // Ambient light colors + Ogre::ColourValue ambientDay = Ogre::ColourValue(0.3f, 0.3f, 0.3f); + Ogre::ColourValue ambientNight = Ogre::ColourValue(0.05f, 0.05f, 0.15f); + Ogre::ColourValue ambientSunrise = Ogre::ColourValue(0.3f, 0.2f, 0.15f); + Ogre::ColourValue ambientSunset = Ogre::ColourValue(0.25f, 0.15f, 0.1f); + + // Sun / moon visualization spheres + bool showSunSphere = true; + bool showMoonSphere = true; + float sunSphereSize = 5.0f; + float moonSphereSize = 3.0f; + + // Orbit tilt (degrees) - tilts the sun path north/south + float orbitTilt = 15.0f; + + // Light intensity multiplier + float intensity = 1.0f; + + // Cast shadows + bool castShadows = true; + + // Runtime objects (managed by EditorSunSystem) + Ogre::Light *light = nullptr; + Ogre::SceneNode *lightNode = nullptr; + Ogre::SceneNode *sunSphereNode = nullptr; + Ogre::SceneNode *moonSphereNode = nullptr; + Ogre::ManualObject *sunSphere = nullptr; + Ogre::ManualObject *moonSphere = nullptr; + + // Dirty flag - triggers rebuild + bool dirty = true; + + void markDirty() + { + dirty = true; + } +}; + +#endif // EDITSCENE_SUN_HPP diff --git a/src/features/editScene/components/SunModule.cpp b/src/features/editScene/components/SunModule.cpp new file mode 100644 index 0000000..c1def45 --- /dev/null +++ b/src/features/editScene/components/SunModule.cpp @@ -0,0 +1,24 @@ +#include "Sun.hpp" +#include "Transform.hpp" +#include "EditorMarker.hpp" +#include "EntityName.hpp" +#include "../ui/ComponentRegistration.hpp" +#include "../ui/SunEditor.hpp" + +REGISTER_COMPONENT_GROUP("Sun", "Environment", SunComponent, SunEditor) +{ + registry.registerComponent( + "Sun", "Environment", std::make_unique(), + // Adder + [](flecs::entity e) { + if (!e.has()) { + e.set({}); + } + }, + // Remover + [](flecs::entity e) { + if (e.has()) { + e.remove(); + } + }); +} diff --git a/src/features/editScene/physics/physics.cpp b/src/features/editScene/physics/physics.cpp index 6364085..60dd254 100644 --- a/src/features/editScene/physics/physics.cpp +++ b/src/features/editScene/physics/physics.cpp @@ -1574,11 +1574,10 @@ public: MyCollector collector(&physics_system, surface_point, JPH::Vec3::sAxisY(), dt); // Apply buoyancy to all bodies that intersect with the water - // Create a symmetrical box around the surface point: - // 1000 units in X/Z, 1.0 unit above and below in Y - // This detects bodies slightly above and well below water surface - JPH::AABox water_box(-JPH::Vec3(1000, 1.0f, 1000), - JPH::Vec3(1000, 1000, 1000)); + // Detects bodies up to 0.1 units above the surface point + // and up to 1000 units below (deep underwater) + JPH::AABox water_box(-JPH::Vec3(1000, 1000, 1000), + JPH::Vec3(1000, 0.1f, 1000)); water_box.Translate(JPH::Vec3(surface_point)); physics_system.GetBroadPhaseQuery().CollideAABox( water_box, collector, diff --git a/src/features/editScene/resources/materials/scripts/skybox.frag b/src/features/editScene/resources/materials/scripts/skybox.frag new file mode 100644 index 0000000..4f63d6a --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/skybox.frag @@ -0,0 +1,78 @@ +OGRE_NATIVE_GLSL_VERSION_DIRECTIVE +#include + +OGRE_UNIFORMS( + uniform vec3 uDayTop; + uniform vec3 uDayBottom; + uniform vec3 uNightTop; + uniform vec3 uNightBottom; + uniform vec3 uSunriseColor; + uniform vec3 uSunsetColor; + uniform vec3 uSunDir; + uniform float uSunElev; + uniform float uSunSize; + uniform float uMoonSize; + uniform float uStarsEnabled; +) + +IN(vec3 vWorldPos, TEXCOORD0) + +// Simple pseudo-random function for stars +float hash(vec2 p) +{ + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); +} + +MAIN_DECLARATION +{ + vec3 dir = normalize(vWorldPos); + float y = dir.y; + + // Sky gradient factor based on elevation + float gradT = clamp(y * 0.5 + 0.5, 0.0, 1.0); + + // Day / night interpolation based on sun elevation + // uSunElev: -1.0 = midnight, 0.0 = horizon, 1.0 = noon + float dayFactor = smoothstep(-0.15, 0.25, uSunElev); + float nightFactor = 1.0 - smoothstep(-0.25, 0.15, uSunElev); + + vec3 daySky = mix(uDayBottom, uDayTop, gradT); + vec3 nightSky = mix(uNightBottom, uNightTop, gradT); + + vec3 skyColor = mix(nightSky, daySky, dayFactor); + + // Horizon glow for sunrise / sunset + float horizon = 1.0 - abs(y); + horizon = horizon * horizon; + + // Sunrise when sun is rising (uSunElev near 0, morning) + // Sunset when sun is setting (uSunElev near 0, evening) + // We approximate by checking if sun is near horizon + float nearHorizon = 1.0 - smoothstep(0.0, 0.3, abs(uSunElev)); + vec3 glowColor = mix(uSunriseColor, uSunsetColor, + smoothstep(-0.1, 0.1, uSunDir.x)); + skyColor += glowColor * nearHorizon * horizon * 0.8; + + // Sun disc + vec3 sunPos = -uSunDir; + float sunDot = max(0.0, dot(dir, sunPos)); + float sunDisc = pow(sunDot, 1.0 / uSunSize); + skyColor += vec3(1.0, 0.95, 0.8) * sunDisc * dayFactor; + + // Moon disc (opposite to sun) + vec3 moonPos = uSunDir; + float moonDot = max(0.0, dot(dir, moonPos)); + float moonDisc = pow(moonDot, 1.0 / uMoonSize); + skyColor += vec3(0.85, 0.85, 0.95) * moonDisc * nightFactor; + + // Simple stars + if (uStarsEnabled > 0.5) { + float starNoise = hash(floor(dir.xz * 80.0)); + float star = smoothstep(0.995, 1.0, starNoise); + // Only show stars at night and above horizon + float starMask = nightFactor * smoothstep(-0.2, 0.1, y); + skyColor += vec3(1.0, 1.0, 1.0) * star * starMask * 0.8; + } + + gl_FragColor = vec4(skyColor, 1.0); +} diff --git a/src/features/editScene/resources/materials/scripts/skybox.material b/src/features/editScene/resources/materials/scripts/skybox.material new file mode 100644 index 0000000..a27a4d9 --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/skybox.material @@ -0,0 +1,21 @@ +material Skybox/Dynamic +{ + technique + { + pass + { + lighting off + depth_write off + depth_check off + cull_hardware none + + vertex_program_ref SkyboxVP + { + } + + fragment_program_ref SkyboxFP + { + } + } + } +} diff --git a/src/features/editScene/resources/materials/scripts/skybox.program b/src/features/editScene/resources/materials/scripts/skybox.program new file mode 100644 index 0000000..effad36 --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/skybox.program @@ -0,0 +1,27 @@ +vertex_program SkyboxVP glsl glsles glslang hlsl +{ + source skybox.vert + default_params + { + param_named_auto worldViewProj worldviewproj_matrix + } +} + +fragment_program SkyboxFP glsl glsles glslang hlsl +{ + source skybox.frag + default_params + { + param_named uDayTop float3 0.2 0.5 1.0 + param_named uDayBottom float3 0.6 0.8 1.0 + param_named uNightTop float3 0.0 0.0 0.05 + param_named uNightBottom float3 0.05 0.05 0.15 + param_named uSunriseColor float3 1.0 0.5 0.2 + param_named uSunsetColor float3 1.0 0.3 0.1 + param_named uSunDir float3 0.0 -1.0 0.0 + param_named uSunElev float 1.0 + param_named uSunSize float 0.05 + param_named uMoonSize float 0.03 + param_named uStarsEnabled float 1.0 + } +} diff --git a/src/features/editScene/resources/materials/scripts/skybox.vert b/src/features/editScene/resources/materials/scripts/skybox.vert new file mode 100644 index 0000000..ce1cc5f --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/skybox.vert @@ -0,0 +1,16 @@ +OGRE_NATIVE_GLSL_VERSION_DIRECTIVE +#include + +OGRE_UNIFORMS( + uniform mat4 worldViewProj; +) + +OUT(vec3 vWorldPos, TEXCOORD0) + +MAIN_PARAMETERS +IN(vec4 vertex, POSITION) +MAIN_DECLARATION +{ + gl_Position = worldViewProj * vertex; + vWorldPos = vertex.xyz; +} diff --git a/src/features/editScene/systems/BuoyancySystem.cpp b/src/features/editScene/systems/BuoyancySystem.cpp index 6a34911..fb9b2a1 100644 --- a/src/features/editScene/systems/BuoyancySystem.cpp +++ b/src/features/editScene/systems/BuoyancySystem.cpp @@ -110,11 +110,16 @@ void BuoyancySystem::update(float deltaTime) continue; } - // Mark entity as in water - if (!entity.has()) { - entity.add(); + // Only mark as InWater if body center is below surface + // (for swim animation logic; buoyancy still applied to + // bodies partially submerged via broadphase) + Ogre::Vector3 bodyPos = m_physics->getPosition(bodyID); + if (bodyPos.y < waterPhysics->waterSurfaceY) { + if (!entity.has()) { + entity.add(); + } + m_entitiesInWater.insert(entity.id()); } - m_entitiesInWater.insert(entity.id()); // Debug logging for buoyancy application if (m_debugEnabled) { diff --git a/src/features/editScene/systems/EditorSkyboxSystem.cpp b/src/features/editScene/systems/EditorSkyboxSystem.cpp new file mode 100644 index 0000000..8ecd8b8 --- /dev/null +++ b/src/features/editScene/systems/EditorSkyboxSystem.cpp @@ -0,0 +1,228 @@ +#include "EditorSkyboxSystem.hpp" +#include "../components/Skybox.hpp" +#include "../components/Sun.hpp" +#include +#include +#include +#include +#include +#include + +EditorSkyboxSystem::EditorSkyboxSystem(flecs::world &world, + Ogre::SceneManager *sceneMgr) + : m_world(world) + , m_sceneMgr(sceneMgr) + , m_query(world.query()) +{ +} + +EditorSkyboxSystem::~EditorSkyboxSystem() = default; + +void EditorSkyboxSystem::update(Ogre::Camera *camera) +{ + m_query.each([&](flecs::entity entity, SkyboxComponent &skybox) { + if (!skybox.enabled) { + if (skybox.sceneNode) { + skybox.sceneNode->setVisible(false); + } + return; + } + + if (skybox.dirty || !skybox.manualObject) { + rebuildSkybox(entity, skybox); + skybox.dirty = false; + } + + if (!skybox.sceneNode || !camera) + return; + + // Follow camera position + skybox.sceneNode->setPosition(camera->getDerivedPosition()); + + // Get sun data from same entity or use defaults + float sunElev = 1.0f; + Ogre::Vector3 sunDir(0.0f, -1.0f, 0.0f); + + if (entity.has()) { + const SunComponent &sun = entity.get(); + { + float sunAngle = (sun.timeOfDay - 6.0f) / 24.0f * 2.0f * + M_PI; + sunElev = std::sin(sunAngle); + float tiltRad = sun.orbitTilt * M_PI / 180.0f; + sunDir.x = -std::cos(sunAngle); + sunDir.y = -sunElev; + sunDir.z = -std::sin(sunAngle) * std::sin(tiltRad); + sunDir.normalise(); + } + } + + updateShaderParams(skybox, sunElev, sunDir); + }); +} + +void EditorSkyboxSystem::rebuildSkybox(flecs::entity entity, + SkyboxComponent &skybox) +{ + cleanupSkybox(skybox); + + Ogre::String baseName = "Skybox_" + + Ogre::StringConverter::toString(entity.id()); + + // Create scene node at origin (will follow camera) + skybox.sceneNode = + m_sceneMgr->getRootSceneNode()->createChildSceneNode( + baseName + "Node"); + + // Create manual object cube + skybox.manualObject = + m_sceneMgr->createManualObject(baseName + "Mesh"); + createCube(skybox.manualObject, skybox.size); + + // Get material instance + Ogre::MaterialPtr material = + Ogre::MaterialManager::getSingleton() + .getByName("Skybox/Dynamic", + Ogre::ResourceGroupManager:: + DEFAULT_RESOURCE_GROUP_NAME); + + if (material) { + skybox.manualObject->setMaterialName(0, "Skybox/Dynamic"); + } + + skybox.manualObject->setRenderQueueGroup( + Ogre::RENDER_QUEUE_SKIES_EARLY); + + skybox.sceneNode->attachObject(skybox.manualObject); + + Ogre::LogManager::getSingleton().logMessage( + "SkyboxSystem: Created skybox for entity " + + Ogre::StringConverter::toString(entity.id()) + + ", size=" + Ogre::StringConverter::toString(skybox.size)); +} + +void EditorSkyboxSystem::cleanupSkybox(SkyboxComponent &skybox) +{ + if (skybox.manualObject) { + if (skybox.sceneNode) { + skybox.sceneNode->detachObject(skybox.manualObject); + } + m_sceneMgr->destroyManualObject(skybox.manualObject); + skybox.manualObject = nullptr; + } + if (skybox.sceneNode) { + m_sceneMgr->destroySceneNode(skybox.sceneNode); + skybox.sceneNode = nullptr; + } +} + +void EditorSkyboxSystem::createCube(Ogre::ManualObject *obj, float size) +{ + float h = size * 0.5f; + + obj->begin("Skybox/Dynamic", + Ogre::RenderOperation::OT_TRIANGLE_LIST); + + // Front face (+Z) + obj->position(-h, -h, h); + obj->position(h, -h, h); + obj->position(h, h, h); + obj->position(-h, h, h); + obj->triangle(0, 2, 1); + obj->triangle(0, 3, 2); + + // Back face (-Z) + obj->position(h, -h, -h); + obj->position(-h, -h, -h); + obj->position(-h, h, -h); + obj->position(h, h, -h); + obj->triangle(4, 6, 5); + obj->triangle(4, 7, 6); + + // Left face (-X) + obj->position(-h, -h, -h); + obj->position(-h, -h, h); + obj->position(-h, h, h); + obj->position(-h, h, -h); + obj->triangle(8, 10, 9); + obj->triangle(8, 11, 10); + + // Right face (+X) + obj->position(h, -h, h); + obj->position(h, -h, -h); + obj->position(h, h, -h); + obj->position(h, h, h); + obj->triangle(12, 14, 13); + obj->triangle(12, 15, 14); + + // Top face (+Y) + obj->position(-h, h, h); + obj->position(h, h, h); + obj->position(h, h, -h); + obj->position(-h, h, -h); + obj->triangle(16, 18, 17); + obj->triangle(16, 19, 18); + + // Bottom face (-Y) + obj->position(-h, -h, -h); + obj->position(h, -h, -h); + obj->position(h, -h, h); + obj->position(-h, -h, h); + obj->triangle(20, 22, 21); + obj->triangle(20, 23, 22); + + obj->end(); +} + +void EditorSkyboxSystem::updateShaderParams(SkyboxComponent &skybox, + float sunElev, + const Ogre::Vector3 &sunDir) +{ + if (!skybox.manualObject) + return; + + Ogre::MaterialPtr material = + Ogre::MaterialManager::getSingleton() + .getByName("Skybox/Dynamic", + Ogre::ResourceGroupManager:: + DEFAULT_RESOURCE_GROUP_NAME); + if (!material) + return; + + Ogre::Pass *pass = material->getTechnique(0)->getPass(0); + Ogre::GpuProgramParametersSharedPtr fpParams = + pass->getFragmentProgramParameters(); + if (!fpParams) + return; + + fpParams->setNamedConstant("uDayTop", + Ogre::Vector3(skybox.dayTopColor.r, + skybox.dayTopColor.g, + skybox.dayTopColor.b)); + fpParams->setNamedConstant("uDayBottom", + Ogre::Vector3(skybox.dayBottomColor.r, + skybox.dayBottomColor.g, + skybox.dayBottomColor.b)); + fpParams->setNamedConstant("uNightTop", + Ogre::Vector3(skybox.nightTopColor.r, + skybox.nightTopColor.g, + skybox.nightTopColor.b)); + fpParams->setNamedConstant("uNightBottom", + Ogre::Vector3(skybox.nightBottomColor.r, + skybox.nightBottomColor.g, + skybox.nightBottomColor.b)); + fpParams->setNamedConstant("uSunriseColor", + Ogre::Vector3(skybox.sunriseColor.r, + skybox.sunriseColor.g, + skybox.sunriseColor.b)); + fpParams->setNamedConstant("uSunsetColor", + Ogre::Vector3(skybox.sunsetColor.r, + skybox.sunsetColor.g, + skybox.sunsetColor.b)); + fpParams->setNamedConstant("uSunDir", sunDir); + fpParams->setNamedConstant("uSunElev", sunElev); + fpParams->setNamedConstant("uSunSize", skybox.sunSize); + fpParams->setNamedConstant("uMoonSize", skybox.moonSize); + fpParams->setNamedConstant("uStarsEnabled", + skybox.starsEnabled ? 1.0f : 0.0f); +} diff --git a/src/features/editScene/systems/EditorSkyboxSystem.hpp b/src/features/editScene/systems/EditorSkyboxSystem.hpp new file mode 100644 index 0000000..dda42e1 --- /dev/null +++ b/src/features/editScene/systems/EditorSkyboxSystem.hpp @@ -0,0 +1,33 @@ +#ifndef EDITSCENE_EDITOR_SKYBOX_SYSTEM_HPP +#define EDITSCENE_EDITOR_SKYBOX_SYSTEM_HPP +#pragma once + +#include +#include + +/** + * Skybox system - creates and updates a procedural skybox cube + * that follows the camera and renders dynamic sky colors. + */ +class EditorSkyboxSystem { +public: + EditorSkyboxSystem(flecs::world &world, Ogre::SceneManager *sceneMgr); + ~EditorSkyboxSystem(); + + // Update skybox position and shader parameters + void update(Ogre::Camera *camera); + +private: + void rebuildSkybox(flecs::entity entity, + struct SkyboxComponent &skybox); + void cleanupSkybox(SkyboxComponent &skybox); + void createCube(Ogre::ManualObject *obj, float size); + void updateShaderParams(SkyboxComponent &skybox, + float sunElev, const Ogre::Vector3 &sunDir); + + flecs::world &m_world; + Ogre::SceneManager *m_sceneMgr; + flecs::query m_query; +}; + +#endif // EDITSCENE_EDITOR_SKYBOX_SYSTEM_HPP diff --git a/src/features/editScene/systems/EditorSunSystem.cpp b/src/features/editScene/systems/EditorSunSystem.cpp new file mode 100644 index 0000000..17d484c --- /dev/null +++ b/src/features/editScene/systems/EditorSunSystem.cpp @@ -0,0 +1,314 @@ +#include "EditorSunSystem.hpp" +#include "../components/Sun.hpp" +#include "../components/Transform.hpp" +#include +#include +#include + +EditorSunSystem::EditorSunSystem(flecs::world &world, + Ogre::SceneManager *sceneMgr) + : m_world(world) + , m_sceneMgr(sceneMgr) + , m_query(world.query()) +{ +} + +EditorSunSystem::~EditorSunSystem() = default; + +void EditorSunSystem::update(float deltaTime) +{ + m_query.each([&](flecs::entity entity, SunComponent &sun, + TransformComponent &transform) { + if (!sun.enabled) { + if (sun.light) { + cleanupSun(sun); + } + return; + } + + // Rebuild if dirty or first time + if (sun.dirty || !sun.light) { + rebuildSun(entity, sun, transform); + sun.dirty = false; + } + + // Advance time + sun.timeOfDay += deltaTime * sun.timeSpeed; + if (sun.timeOfDay >= 24.0f) + sun.timeOfDay -= 24.0f; + if (sun.timeOfDay < 0.0f) + sun.timeOfDay += 24.0f; + + // Compute sun angle: + // 6:00 = 0 rad (horizon, east) + // 12:00 = PI/2 rad (zenith) + // 18:00 = PI rad (horizon, west) + // 24:00 = 3*PI/2 rad (nadir) + float sunAngle = (sun.timeOfDay - 6.0f) / 24.0f * 2.0f * M_PI; + float sunElev = std::sin(sunAngle); + + // Light direction (points TOWARD scene from sun) + float tiltRad = sun.orbitTilt * M_PI / 180.0f; + Ogre::Vector3 lightDir; + lightDir.x = -std::cos(sunAngle); + lightDir.y = -sunElev; + lightDir.z = -std::sin(sunAngle) * std::sin(tiltRad); + lightDir.normalise(); + + // Update light direction by orienting the light node + if (sun.lightNode) { + Ogre::Vector3 defaultDir(0, -1, 0); + Ogre::Quaternion rot = defaultDir.getRotationTo(lightDir); + sun.lightNode->setOrientation(rot); + } + + // Determine if sun is above horizon + bool isDay = sunElev > -0.1f; + + // Interpolate light color + Ogre::ColourValue lightColor; + float intensity = sun.intensity; + if (isDay) { + float t = std::clamp((sunElev + 0.1f) / 0.3f, 0.0f, 1.0f); + lightColor = Ogre::ColourValue::White * (1.0f - t) + + sun.sunColor * t; + // Dim at horizon + intensity *= std::clamp((sunElev + 0.1f) / 0.2f, 0.0f, 1.0f); + } else { + lightColor = sun.moonColor; + intensity *= 0.3f; + } + + if (sun.light) { + sun.light->setDiffuseColour(lightColor * intensity); + sun.light->setSpecularColour(lightColor * intensity * 0.5f); + } + + // Update ambient light + Ogre::ColourValue ambient; + if (sunElev > 0.2f) { + // Day + ambient = sun.ambientDay; + } else if (sunElev < -0.2f) { + // Night + ambient = sun.ambientNight; + } else if (sun.timeOfDay < 12.0f) { + // Sunrise transition + float t = std::clamp((sunElev + 0.2f) / 0.4f, 0.0f, 1.0f); + ambient = sun.ambientSunrise * (1.0f - t) + + sun.ambientDay * t; + } else { + // Sunset transition + float t = std::clamp((sunElev + 0.2f) / 0.4f, 0.0f, 1.0f); + ambient = sun.ambientSunset * (1.0f - t) + + sun.ambientDay * t; + } + m_sceneMgr->setAmbientLight(ambient); + + // Update sphere positions + float orbitRadius = 200.0f; + Ogre::Vector3 sunPos = -lightDir * orbitRadius; + Ogre::Vector3 moonPos = lightDir * orbitRadius; + + if (sun.sunSphereNode) { + sun.sunSphereNode->setPosition(sunPos); + sun.sunSphereNode->setVisible( + sun.showSunSphere && sunElev > -0.2f); + } + if (sun.moonSphereNode) { + sun.moonSphereNode->setPosition(moonPos); + sun.moonSphereNode->setVisible( + sun.showMoonSphere && sunElev < 0.2f); + } + }); +} + +void EditorSunSystem::rebuildSun(flecs::entity entity, SunComponent &sun, + TransformComponent &transform) +{ + cleanupSun(sun); + + // Create or get scene node from transform + if (!transform.node) { + Ogre::LogManager::getSingleton().logMessage( + "SunSystem: Entity has no transform node, skipping"); + return; + } + + // Create directional light + Ogre::String lightName = "SunLight_" + + Ogre::StringConverter::toString(entity.id()); + sun.light = m_sceneMgr->createLight(lightName); + sun.light->setType(Ogre::Light::LT_DIRECTIONAL); + sun.light->setDiffuseColour(sun.sunColor * sun.intensity); + sun.light->setSpecularColour( + Ogre::ColourValue(0.4f, 0.4f, 0.4f) * sun.intensity); + sun.light->setCastShadows(sun.castShadows); + + // Create a child node for the light (separate from entity node + // so we can orbit it independently) + sun.lightNode = transform.node->createChildSceneNode( + lightName + "Node"); + sun.lightNode->attachObject(sun.light); + + // Create sun sphere + if (sun.showSunSphere) { + Ogre::String sunName = "SunSphere_" + + Ogre::StringConverter::toString( + entity.id()); + sun.sunSphere = m_sceneMgr->createManualObject(sunName); + createSphere(sun.sunSphere, sun.sunSphereSize, + Ogre::ColourValue(1.0f, 0.95f, 0.8f)); + sun.sunSphereNode = + transform.node->createChildSceneNode(sunName + "Node"); + sun.sunSphereNode->attachObject(sun.sunSphere); + } + + // Create moon sphere + if (sun.showMoonSphere) { + Ogre::String moonName = "MoonSphere_" + + Ogre::StringConverter::toString( + entity.id()); + sun.moonSphere = m_sceneMgr->createManualObject(moonName); + createSphere(sun.moonSphere, sun.moonSphereSize, + Ogre::ColourValue(0.8f, 0.8f, 0.9f)); + sun.moonSphereNode = + transform.node->createChildSceneNode(moonName + "Node"); + sun.moonSphereNode->attachObject(sun.moonSphere); + } + + Ogre::LogManager::getSingleton().logMessage( + "SunSystem: Created sun for entity " + + Ogre::StringConverter::toString(entity.id())); +} + +void EditorSunSystem::cleanupSun(SunComponent &sun) +{ + if (sun.sunSphere) { + if (sun.sunSphereNode) { + sun.sunSphereNode->detachObject(sun.sunSphere); + } + m_sceneMgr->destroyManualObject(sun.sunSphere); + sun.sunSphere = nullptr; + } + if (sun.sunSphereNode) { + if (sun.sunSphereNode->getParentSceneNode()) { + sun.sunSphereNode->getParentSceneNode()->removeChild( + sun.sunSphereNode); + } + m_sceneMgr->destroySceneNode(sun.sunSphereNode); + sun.sunSphereNode = nullptr; + } + + if (sun.moonSphere) { + if (sun.moonSphereNode) { + sun.moonSphereNode->detachObject(sun.moonSphere); + } + m_sceneMgr->destroyManualObject(sun.moonSphere); + sun.moonSphere = nullptr; + } + if (sun.moonSphereNode) { + if (sun.moonSphereNode->getParentSceneNode()) { + sun.moonSphereNode->getParentSceneNode()->removeChild( + sun.moonSphereNode); + } + m_sceneMgr->destroySceneNode(sun.moonSphereNode); + sun.moonSphereNode = nullptr; + } + + if (sun.light) { + if (sun.lightNode) { + sun.lightNode->detachObject(sun.light); + } + m_sceneMgr->destroyLight(sun.light); + sun.light = nullptr; + } + if (sun.lightNode) { + if (sun.lightNode->getParentSceneNode()) { + sun.lightNode->getParentSceneNode()->removeChild( + sun.lightNode); + } + m_sceneMgr->destroySceneNode(sun.lightNode); + sun.lightNode = nullptr; + } +} + +void EditorSunSystem::createSphere(Ogre::ManualObject *obj, float radius, + const Ogre::ColourValue &color) +{ + // Simple icosphere approximation (octahedron + one subdivision) + // 8 faces, subdivided once = 32 faces + obj->begin("BaseWhiteNoLighting", + Ogre::RenderOperation::OT_TRIANGLE_LIST); + + struct Vertex { + Ogre::Vector3 p; + }; + std::vector verts; + std::vector indices; + + // Octahedron vertices + verts.push_back({ Ogre::Vector3(0, 1, 0) * radius }); + verts.push_back({ Ogre::Vector3(0, -1, 0) * radius }); + verts.push_back({ Ogre::Vector3(-1, 0, 0) * radius }); + verts.push_back({ Ogre::Vector3(1, 0, 0) * radius }); + verts.push_back({ Ogre::Vector3(0, 0, 1) * radius }); + verts.push_back({ Ogre::Vector3(0, 0, -1) * radius }); + + // Octahedron faces + int faces[] = { 0, 4, 3, 0, 3, 5, 0, 5, 2, 0, 2, 4, + 1, 3, 4, 1, 5, 3, 1, 2, 5, 1, 4, 2 }; + + for (int i = 0; i < 24; i += 3) { + indices.push_back(faces[i]); + indices.push_back(faces[i + 1]); + indices.push_back(faces[i + 2]); + } + + // Subdivide once + std::vector newIndices; + for (size_t i = 0; i < indices.size(); i += 3) { + Ogre::Vector3 a = verts[indices[i]].p; + Ogre::Vector3 b = verts[indices[i + 1]].p; + Ogre::Vector3 c = verts[indices[i + 2]].p; + + Ogre::Vector3 ab = (a + b).normalisedCopy() * radius; + Ogre::Vector3 bc = (b + c).normalisedCopy() * radius; + Ogre::Vector3 ca = (c + a).normalisedCopy() * radius; + + int ia = indices[i]; + int ib = indices[i + 1]; + int ic = indices[i + 2]; + int iab = (int)verts.size(); + verts.push_back({ ab }); + int ibc = (int)verts.size(); + verts.push_back({ bc }); + int ica = (int)verts.size(); + verts.push_back({ ca }); + + newIndices.push_back(ia); + newIndices.push_back(iab); + newIndices.push_back(ica); + newIndices.push_back(iab); + newIndices.push_back(ib); + newIndices.push_back(ibc); + newIndices.push_back(ica); + newIndices.push_back(ibc); + newIndices.push_back(ic); + newIndices.push_back(iab); + newIndices.push_back(ibc); + newIndices.push_back(ica); + } + indices = newIndices; + + // Write vertices + for (const auto &v : verts) { + obj->position(v.p); + obj->colour(color); + } + for (int idx : indices) { + obj->index(idx); + } + + obj->end(); +} diff --git a/src/features/editScene/systems/EditorSunSystem.hpp b/src/features/editScene/systems/EditorSunSystem.hpp new file mode 100644 index 0000000..54d1120 --- /dev/null +++ b/src/features/editScene/systems/EditorSunSystem.hpp @@ -0,0 +1,32 @@ +#ifndef EDITSCENE_EDITOR_SUN_SYSTEM_HPP +#define EDITSCENE_EDITOR_SUN_SYSTEM_HPP +#pragma once + +#include +#include + +/** + * Sun system - manages directional light and sun/moon spheres + * based on in-game time. + */ +class EditorSunSystem { +public: + EditorSunSystem(flecs::world &world, Ogre::SceneManager *sceneMgr); + ~EditorSunSystem(); + + // Update sun position, light, and colors + void update(float deltaTime); + +private: + void rebuildSun(flecs::entity entity, struct SunComponent &sun, + struct TransformComponent &transform); + void cleanupSun(SunComponent &sun); + void createSphere(Ogre::ManualObject *obj, float radius, + const Ogre::ColourValue &color); + + flecs::world &m_world; + Ogre::SceneManager *m_sceneMgr; + flecs::query m_query; +}; + +#endif // EDITSCENE_EDITOR_SUN_SYSTEM_HPP diff --git a/src/features/editScene/systems/EditorUISystem.cpp b/src/features/editScene/systems/EditorUISystem.cpp index c1180af..17dd20a 100644 --- a/src/features/editScene/systems/EditorUISystem.cpp +++ b/src/features/editScene/systems/EditorUISystem.cpp @@ -8,6 +8,8 @@ #include "../components/RigidBody.hpp" #include "../components/BuoyancyInfo.hpp" #include "../components/WaterPhysics.hpp" +#include "../components/Sun.hpp" +#include "../components/Skybox.hpp" #include "../components/Light.hpp" #include "../components/Camera.hpp" #include "../components/Lod.hpp" @@ -619,6 +621,24 @@ void EditorUISystem::renderComponentList(flecs::entity entity) componentCount++; } + // Render Sun if present + if (entity.has()) { + auto &sun = entity.get_mut(); + if (m_componentRegistry.render(entity, sun)) { + sun.markDirty(); + } + componentCount++; + } + + // Render Skybox if present + if (entity.has()) { + auto &sky = entity.get_mut(); + if (m_componentRegistry.render(entity, sky)) { + sky.markDirty(); + } + componentCount++; + } + // Render LOD Settings if present if (entity.has()) { auto &lodSettings = entity.get_mut(); diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index cf11996..a6a07d4 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -25,6 +25,8 @@ #include "../components/GeneratedPhysicsTag.hpp" #include "../components/BuoyancyInfo.hpp" #include "../components/WaterPhysics.hpp" +#include "../components/Sun.hpp" +#include "../components/Skybox.hpp" #include "EditorUISystem.hpp" #include #include @@ -259,6 +261,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity) json["waterPhysics"] = serializeWaterPhysics(entity); } + if (entity.has()) { + json["sun"] = serializeSun(entity); + } + + if (entity.has()) { + json["skybox"] = serializeSkybox(entity); + } + // Serialize children json["children"] = nlohmann::json::array(); entity.children([&](flecs::entity child) { @@ -420,6 +430,14 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json, deserializeWaterPhysics(entity, json["waterPhysics"]); } + if (json.contains("sun")) { + deserializeSun(entity, json["sun"]); + } + + if (json.contains("skybox")) { + deserializeSkybox(entity, json["skybox"]); + } + // Add to UI system if provided if (uiSystem) { uiSystem->addEntity(entity); @@ -2331,3 +2349,172 @@ void SceneSerializer::deserializeWaterPhysics(flecs::entity entity, entity.set(water); } + + +nlohmann::json SceneSerializer::serializeSun(flecs::entity entity) +{ + const SunComponent &sun = entity.get(); + nlohmann::json json; + + json["enabled"] = sun.enabled; + json["timeOfDay"] = sun.timeOfDay; + json["timeSpeed"] = sun.timeSpeed; + json["sunColor"] = { sun.sunColor.r, sun.sunColor.g, + sun.sunColor.b }; + json["moonColor"] = { sun.moonColor.r, sun.moonColor.g, + sun.moonColor.b }; + json["ambientDay"] = { sun.ambientDay.r, sun.ambientDay.g, + sun.ambientDay.b }; + json["ambientNight"] = { sun.ambientNight.r, sun.ambientNight.g, + sun.ambientNight.b }; + json["ambientSunrise"] = { sun.ambientSunrise.r, sun.ambientSunrise.g, + sun.ambientSunrise.b }; + json["ambientSunset"] = { sun.ambientSunset.r, sun.ambientSunset.g, + sun.ambientSunset.b }; + json["showSunSphere"] = sun.showSunSphere; + json["showMoonSphere"] = sun.showMoonSphere; + json["sunSphereSize"] = sun.sunSphereSize; + json["moonSphereSize"] = sun.moonSphereSize; + json["orbitTilt"] = sun.orbitTilt; + json["intensity"] = sun.intensity; + json["castShadows"] = sun.castShadows; + + return json; +} + +nlohmann::json SceneSerializer::serializeSkybox(flecs::entity entity) +{ + const SkyboxComponent &sky = entity.get(); + nlohmann::json json; + + json["enabled"] = sky.enabled; + json["size"] = sky.size; + json["dayTopColor"] = { sky.dayTopColor.r, sky.dayTopColor.g, + sky.dayTopColor.b }; + json["dayBottomColor"] = { sky.dayBottomColor.r, sky.dayBottomColor.g, + sky.dayBottomColor.b }; + json["nightTopColor"] = { sky.nightTopColor.r, sky.nightTopColor.g, + sky.nightTopColor.b }; + json["nightBottomColor"] = { sky.nightBottomColor.r, + sky.nightBottomColor.g, + sky.nightBottomColor.b }; + json["sunriseColor"] = { sky.sunriseColor.r, sky.sunriseColor.g, + sky.sunriseColor.b }; + json["sunsetColor"] = { sky.sunsetColor.r, sky.sunsetColor.g, + sky.sunsetColor.b }; + json["sunSize"] = sky.sunSize; + json["moonSize"] = sky.moonSize; + json["starsEnabled"] = sky.starsEnabled; + json["cloudiness"] = sky.cloudiness; + + return json; +} + +void SceneSerializer::deserializeSun(flecs::entity entity, + const nlohmann::json &json) +{ + SunComponent sun; + + sun.enabled = json.value("enabled", true); + sun.timeOfDay = json.value("timeOfDay", 12.0f); + sun.timeSpeed = json.value("timeSpeed", 0.05f); + if (json.contains("sunColor") && json["sunColor"].is_array() && + json["sunColor"].size() >= 3) { + sun.sunColor = Ogre::ColourValue(json["sunColor"][0], + json["sunColor"][1], + json["sunColor"][2]); + } + if (json.contains("moonColor") && json["moonColor"].is_array() && + json["moonColor"].size() >= 3) { + sun.moonColor = Ogre::ColourValue(json["moonColor"][0], + json["moonColor"][1], + json["moonColor"][2]); + } + if (json.contains("ambientDay") && json["ambientDay"].is_array() && + json["ambientDay"].size() >= 3) { + sun.ambientDay = Ogre::ColourValue(json["ambientDay"][0], + json["ambientDay"][1], + json["ambientDay"][2]); + } + if (json.contains("ambientNight") && json["ambientNight"].is_array() && + json["ambientNight"].size() >= 3) { + sun.ambientNight = Ogre::ColourValue(json["ambientNight"][0], + json["ambientNight"][1], + json["ambientNight"][2]); + } + if (json.contains("ambientSunrise") && + json["ambientSunrise"].is_array() && + json["ambientSunrise"].size() >= 3) { + sun.ambientSunrise = Ogre::ColourValue( + json["ambientSunrise"][0], json["ambientSunrise"][1], + json["ambientSunrise"][2]); + } + if (json.contains("ambientSunset") && json["ambientSunset"].is_array() && + json["ambientSunset"].size() >= 3) { + sun.ambientSunset = Ogre::ColourValue(json["ambientSunset"][0], + json["ambientSunset"][1], + json["ambientSunset"][2]); + } + sun.showSunSphere = json.value("showSunSphere", true); + sun.showMoonSphere = json.value("showMoonSphere", true); + sun.sunSphereSize = json.value("sunSphereSize", 5.0f); + sun.moonSphereSize = json.value("moonSphereSize", 3.0f); + sun.orbitTilt = json.value("orbitTilt", 15.0f); + sun.intensity = json.value("intensity", 1.0f); + sun.castShadows = json.value("castShadows", true); + + entity.set(sun); +} + +void SceneSerializer::deserializeSkybox(flecs::entity entity, + const nlohmann::json &json) +{ + SkyboxComponent sky; + + sky.enabled = json.value("enabled", true); + sky.size = json.value("size", 500.0f); + if (json.contains("dayTopColor") && json["dayTopColor"].is_array() && + json["dayTopColor"].size() >= 3) { + sky.dayTopColor = Ogre::ColourValue(json["dayTopColor"][0], + json["dayTopColor"][1], + json["dayTopColor"][2]); + } + if (json.contains("dayBottomColor") && + json["dayBottomColor"].is_array() && + json["dayBottomColor"].size() >= 3) { + sky.dayBottomColor = Ogre::ColourValue( + json["dayBottomColor"][0], json["dayBottomColor"][1], + json["dayBottomColor"][2]); + } + if (json.contains("nightTopColor") && json["nightTopColor"].is_array() && + json["nightTopColor"].size() >= 3) { + sky.nightTopColor = Ogre::ColourValue(json["nightTopColor"][0], + json["nightTopColor"][1], + json["nightTopColor"][2]); + } + if (json.contains("nightBottomColor") && + json["nightBottomColor"].is_array() && + json["nightBottomColor"].size() >= 3) { + sky.nightBottomColor = Ogre::ColourValue( + json["nightBottomColor"][0], json["nightBottomColor"][1], + json["nightBottomColor"][2]); + } + if (json.contains("sunriseColor") && json["sunriseColor"].is_array() && + json["sunriseColor"].size() >= 3) { + sky.sunriseColor = Ogre::ColourValue(json["sunriseColor"][0], + json["sunriseColor"][1], + json["sunriseColor"][2]); + } + if (json.contains("sunsetColor") && json["sunsetColor"].is_array() && + json["sunsetColor"].size() >= 3) { + sky.sunsetColor = Ogre::ColourValue(json["sunsetColor"][0], + json["sunsetColor"][1], + json["sunsetColor"][2]); + } + sky.sunSize = json.value("sunSize", 0.05f); + sky.moonSize = json.value("moonSize", 0.03f); + sky.starsEnabled = json.value("starsEnabled", true); + sky.cloudiness = json.value("cloudiness", 0.0f); + + entity.set(sky); +} diff --git a/src/features/editScene/systems/SceneSerializer.hpp b/src/features/editScene/systems/SceneSerializer.hpp index 0090ba2..1f2c48a 100644 --- a/src/features/editScene/systems/SceneSerializer.hpp +++ b/src/features/editScene/systems/SceneSerializer.hpp @@ -141,6 +141,12 @@ private: void deserializeWaterPhysics(flecs::entity entity, const nlohmann::json &json); + // Sun / Skybox serialization + nlohmann::json serializeSun(flecs::entity entity); + nlohmann::json serializeSkybox(flecs::entity entity); + void deserializeSun(flecs::entity entity, const nlohmann::json &json); + void deserializeSkybox(flecs::entity entity, const nlohmann::json &json); + flecs::world &m_world; Ogre::SceneManager *m_sceneMgr; std::string m_lastError; diff --git a/src/features/editScene/ui/SkyboxEditor.hpp b/src/features/editScene/ui/SkyboxEditor.hpp new file mode 100644 index 0000000..39bb86b --- /dev/null +++ b/src/features/editScene/ui/SkyboxEditor.hpp @@ -0,0 +1,127 @@ +#ifndef SKYBOX_EDITOR_HPP +#define SKYBOX_EDITOR_HPP +#pragma once + +#include "ComponentEditor.hpp" +#include "../components/Skybox.hpp" + +/** + * Editor for Skybox component + */ +class SkyboxEditor : public ComponentEditor { +public: + bool renderComponent(flecs::entity entity, + SkyboxComponent &component) override + { + bool changed = false; + + ImGui::Text("Procedural Skybox"); + ImGui::Separator(); + + if (ImGui::Checkbox("Enabled", &component.enabled)) { + changed = true; + component.markDirty(); + } + + if (ImGui::SliderFloat("Box Size", &component.size, 100.0f, 2000.0f, + "%.0f")) { + changed = true; + component.markDirty(); + } + + ImGui::Separator(); + ImGui::Text("Day Colors"); + + float dayTop[3] = { component.dayTopColor.r, component.dayTopColor.g, + component.dayTopColor.b }; + if (ImGui::ColorEdit3("Day Top", dayTop)) { + component.dayTopColor = Ogre::ColourValue(dayTop[0], dayTop[1], + dayTop[2]); + changed = true; + } + + float dayBot[3] = { component.dayBottomColor.r, + component.dayBottomColor.g, + component.dayBottomColor.b }; + if (ImGui::ColorEdit3("Day Bottom", dayBot)) { + component.dayBottomColor = Ogre::ColourValue( + dayBot[0], dayBot[1], dayBot[2]); + changed = true; + } + + ImGui::Separator(); + ImGui::Text("Night Colors"); + + float nightTop[3] = { component.nightTopColor.r, + component.nightTopColor.g, + component.nightTopColor.b }; + if (ImGui::ColorEdit3("Night Top", nightTop)) { + component.nightTopColor = Ogre::ColourValue( + nightTop[0], nightTop[1], nightTop[2]); + changed = true; + } + + float nightBot[3] = { component.nightBottomColor.r, + component.nightBottomColor.g, + component.nightBottomColor.b }; + if (ImGui::ColorEdit3("Night Bottom", nightBot)) { + component.nightBottomColor = Ogre::ColourValue( + nightBot[0], nightBot[1], nightBot[2]); + changed = true; + } + + ImGui::Separator(); + ImGui::Text("Horizon Glow"); + + float sunrise[3] = { component.sunriseColor.r, + component.sunriseColor.g, + component.sunriseColor.b }; + if (ImGui::ColorEdit3("Sunrise", sunrise)) { + component.sunriseColor = Ogre::ColourValue( + sunrise[0], sunrise[1], sunrise[2]); + changed = true; + } + + float sunset[3] = { component.sunsetColor.r, + component.sunsetColor.g, + component.sunsetColor.b }; + if (ImGui::ColorEdit3("Sunset", sunset)) { + component.sunsetColor = Ogre::ColourValue( + sunset[0], sunset[1], sunset[2]); + changed = true; + } + + ImGui::Separator(); + ImGui::Text("Celestial Bodies"); + + if (ImGui::SliderFloat("Sun Size", &component.sunSize, 0.01f, 0.2f, + "%.3f")) { + changed = true; + } + if (ImGui::SliderFloat("Moon Size", &component.moonSize, 0.01f, 0.2f, + "%.3f")) { + changed = true; + } + if (ImGui::Checkbox("Stars Enabled", &component.starsEnabled)) { + changed = true; + } + + ImGui::Separator(); + ImGui::PushID("Skybox"); + if (ImGui::Button("Reset to Defaults")) { + component = SkyboxComponent(); + changed = true; + component.markDirty(); + } + ImGui::PopID(); + + return changed; + } + + const char *getName() const override + { + return "Skybox"; + } +}; + +#endif // SKYBOX_EDITOR_HPP diff --git a/src/features/editScene/ui/SunEditor.hpp b/src/features/editScene/ui/SunEditor.hpp new file mode 100644 index 0000000..6bef498 --- /dev/null +++ b/src/features/editScene/ui/SunEditor.hpp @@ -0,0 +1,147 @@ +#ifndef SUN_EDITOR_HPP +#define SUN_EDITOR_HPP +#pragma once + +#include "ComponentEditor.hpp" +#include "../components/Sun.hpp" + +/** + * Editor for Sun component + */ +class SunEditor : public ComponentEditor { +public: + bool renderComponent(flecs::entity entity, + SunComponent &component) override + { + bool changed = false; + + ImGui::Text("Sun / Time of Day"); + ImGui::Separator(); + + if (ImGui::Checkbox("Enabled", &component.enabled)) { + changed = true; + component.markDirty(); + } + + if (ImGui::SliderFloat("Time of Day", &component.timeOfDay, 0.0f, + 24.0f, "%.2f h")) { + changed = true; + } + + if (ImGui::SliderFloat("Time Speed", + &component.timeSpeed, 0.0f, 2.0f, + "%.3f h/s")) { + changed = true; + } + + if (ImGui::SliderFloat("Intensity", &component.intensity, 0.0f, + 3.0f, "%.2f")) { + changed = true; + component.markDirty(); + } + + if (ImGui::SliderFloat("Orbit Tilt", &component.orbitTilt, -45.0f, + 45.0f, "%.1f deg")) { + changed = true; + } + + if (ImGui::Checkbox("Cast Shadows", &component.castShadows)) { + changed = true; + component.markDirty(); + } + + ImGui::Separator(); + ImGui::Text("Colors"); + + float sunColor[3] = { component.sunColor.r, component.sunColor.g, + component.sunColor.b }; + if (ImGui::ColorEdit3("Sun Color", sunColor)) { + component.sunColor = Ogre::ColourValue(sunColor[0], sunColor[1], + sunColor[2]); + changed = true; + } + + float moonColor[3] = { component.moonColor.r, component.moonColor.g, + component.moonColor.b }; + if (ImGui::ColorEdit3("Moon Color", moonColor)) { + component.moonColor = Ogre::ColourValue(moonColor[0], moonColor[1], + moonColor[2]); + changed = true; + } + + float ambDay[3] = { component.ambientDay.r, component.ambientDay.g, + component.ambientDay.b }; + if (ImGui::ColorEdit3("Ambient Day", ambDay)) { + component.ambientDay = Ogre::ColourValue(ambDay[0], ambDay[1], + ambDay[2]); + changed = true; + } + + float ambNight[3] = { component.ambientNight.r, + component.ambientNight.g, + component.ambientNight.b }; + if (ImGui::ColorEdit3("Ambient Night", ambNight)) { + component.ambientNight = Ogre::ColourValue(ambNight[0], ambNight[1], + ambNight[2]); + changed = true; + } + + float ambSunrise[3] = { component.ambientSunrise.r, + component.ambientSunrise.g, + component.ambientSunrise.b }; + if (ImGui::ColorEdit3("Ambient Sunrise", ambSunrise)) { + component.ambientSunrise = Ogre::ColourValue( + ambSunrise[0], ambSunrise[1], ambSunrise[2]); + changed = true; + } + + float ambSunset[3] = { component.ambientSunset.r, + component.ambientSunset.g, + component.ambientSunset.b }; + if (ImGui::ColorEdit3("Ambient Sunset", ambSunset)) { + component.ambientSunset = Ogre::ColourValue( + ambSunset[0], ambSunset[1], ambSunset[2]); + changed = true; + } + + ImGui::Separator(); + ImGui::Text("Visualization"); + + if (ImGui::Checkbox("Show Sun Sphere", &component.showSunSphere)) { + changed = true; + component.markDirty(); + } + if (ImGui::Checkbox("Show Moon Sphere", &component.showMoonSphere)) { + changed = true; + component.markDirty(); + } + if (ImGui::SliderFloat("Sun Sphere Size", &component.sunSphereSize, + 0.5f, 20.0f, "%.1f")) { + changed = true; + component.markDirty(); + } + if (ImGui::SliderFloat("Moon Sphere Size", &component.moonSphereSize, + 0.5f, 20.0f, "%.1f")) { + changed = true; + component.markDirty(); + } + + ImGui::Separator(); + ImGui::PushID("Sun"); + if (ImGui::Button("Reset to Defaults")) { + component = SunComponent(); + changed = true; + component.markDirty(); + } + ImGui::PopID(); + + return changed; + } + + const char *getName() const override + { + return "Sun"; + } +}; + +#endif // SUN_EDITOR_HPP