Skybox and sun

This commit is contained in:
2026-04-23 00:38:20 +03:00
parent d55bf970e0
commit 4335a8cb05
22 changed files with 1460 additions and 9 deletions

View File

@@ -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

View File

@@ -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<EditorSunSystem>(m_world, m_sceneMgr);
m_skyboxSystem =
std::make_unique<EditorSkyboxSystem>(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<PlayerControllerComponent>();
m_world.component<InWater>();
// Register environment components
m_world.component<SunComponent>();
m_world.component<SkyboxComponent>();
// 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();
}

View File

@@ -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<ImGuiRenderListener> m_imguiListener;
std::unique_ptr<EditorPhysicsSystem> m_physicsSystem;
std::unique_ptr<BuoyancySystem> m_buoyancySystem;
std::unique_ptr<EditorSunSystem> m_sunSystem;
std::unique_ptr<EditorSkyboxSystem> m_skyboxSystem;
std::unique_ptr<EditorLightSystem> m_lightSystem;
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
std::unique_ptr<EditorLodSystem> m_lodSystem;

View File

@@ -0,0 +1,58 @@
#ifndef EDITSCENE_SKYBOX_HPP
#define EDITSCENE_SKYBOX_HPP
#pragma once
#include <Ogre.h>
/**
* 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

View File

@@ -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<SkyboxComponent>(
"Skybox", "Environment", std::make_unique<SkyboxEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<SkyboxComponent>()) {
e.set<SkyboxComponent>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<SkyboxComponent>()) {
e.remove<SkyboxComponent>();
}
});
}

View File

@@ -0,0 +1,70 @@
#ifndef EDITSCENE_SUN_HPP
#define EDITSCENE_SUN_HPP
#pragma once
#include <Ogre.h>
/**
* 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

View File

@@ -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<SunComponent>(
"Sun", "Environment", std::make_unique<SunEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<SunComponent>()) {
e.set<SunComponent>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<SunComponent>()) {
e.remove<SunComponent>();
}
});
}

View File

@@ -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,

View File

@@ -0,0 +1,78 @@
OGRE_NATIVE_GLSL_VERSION_DIRECTIVE
#include <OgreUnifiedShader.h>
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);
}

View File

@@ -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
{
}
}
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,16 @@
OGRE_NATIVE_GLSL_VERSION_DIRECTIVE
#include <OgreUnifiedShader.h>
OGRE_UNIFORMS(
uniform mat4 worldViewProj;
)
OUT(vec3 vWorldPos, TEXCOORD0)
MAIN_PARAMETERS
IN(vec4 vertex, POSITION)
MAIN_DECLARATION
{
gl_Position = worldViewProj * vertex;
vWorldPos = vertex.xyz;
}

View File

@@ -110,11 +110,16 @@ void BuoyancySystem::update(float deltaTime)
continue;
}
// Mark entity as in water
if (!entity.has<InWater>()) {
entity.add<InWater>();
// 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<InWater>()) {
entity.add<InWater>();
}
m_entitiesInWater.insert(entity.id());
}
m_entitiesInWater.insert(entity.id());
// Debug logging for buoyancy application
if (m_debugEnabled) {

View File

@@ -0,0 +1,228 @@
#include "EditorSkyboxSystem.hpp"
#include "../components/Skybox.hpp"
#include "../components/Sun.hpp"
#include <OgreLogManager.h>
#include <OgreManualObject.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreGpuProgramParams.h>
EditorSkyboxSystem::EditorSkyboxSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_query(world.query<SkyboxComponent>())
{
}
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<SunComponent>()) {
const SunComponent &sun = entity.get<SunComponent>();
{
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);
}

View File

@@ -0,0 +1,33 @@
#ifndef EDITSCENE_EDITOR_SKYBOX_SYSTEM_HPP
#define EDITSCENE_EDITOR_SKYBOX_SYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
/**
* 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<struct SkyboxComponent> m_query;
};
#endif // EDITSCENE_EDITOR_SKYBOX_SYSTEM_HPP

View File

@@ -0,0 +1,314 @@
#include "EditorSunSystem.hpp"
#include "../components/Sun.hpp"
#include "../components/Transform.hpp"
#include <OgreLogManager.h>
#include <OgreManualObject.h>
#include <cmath>
EditorSunSystem::EditorSunSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_query(world.query<SunComponent, TransformComponent>())
{
}
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<Vertex> verts;
std::vector<int> 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<int> 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();
}

View File

@@ -0,0 +1,32 @@
#ifndef EDITSCENE_EDITOR_SUN_SYSTEM_HPP
#define EDITSCENE_EDITOR_SUN_SYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
/**
* 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<struct SunComponent, struct TransformComponent> m_query;
};
#endif // EDITSCENE_EDITOR_SUN_SYSTEM_HPP

View File

@@ -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<SunComponent>()) {
auto &sun = entity.get_mut<SunComponent>();
if (m_componentRegistry.render<SunComponent>(entity, sun)) {
sun.markDirty();
}
componentCount++;
}
// Render Skybox if present
if (entity.has<SkyboxComponent>()) {
auto &sky = entity.get_mut<SkyboxComponent>();
if (m_componentRegistry.render<SkyboxComponent>(entity, sky)) {
sky.markDirty();
}
componentCount++;
}
// Render LOD Settings if present
if (entity.has<LodSettingsComponent>()) {
auto &lodSettings = entity.get_mut<LodSettingsComponent>();

View File

@@ -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 <random>
#include <fstream>
@@ -259,6 +261,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
json["waterPhysics"] = serializeWaterPhysics(entity);
}
if (entity.has<SunComponent>()) {
json["sun"] = serializeSun(entity);
}
if (entity.has<SkyboxComponent>()) {
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<WaterPhysics>(water);
}
nlohmann::json SceneSerializer::serializeSun(flecs::entity entity)
{
const SunComponent &sun = entity.get<SunComponent>();
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<SkyboxComponent>();
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<SunComponent>(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<SkyboxComponent>(sky);
}

View File

@@ -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;

View File

@@ -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<SkyboxComponent> {
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

View File

@@ -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<SunComponent> {
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