Water plane
This commit is contained in:
@@ -18,6 +18,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/BuoyancySystem.cpp
|
||||
systems/EditorSunSystem.cpp
|
||||
systems/EditorSkyboxSystem.cpp
|
||||
systems/EditorWaterPlaneSystem.cpp
|
||||
systems/LightSystem.cpp
|
||||
systems/CameraSystem.cpp
|
||||
systems/LodSystem.cpp
|
||||
@@ -140,6 +141,7 @@ set(EDITSCENE_HEADERS
|
||||
systems/BuoyancySystem.hpp
|
||||
systems/EditorSunSystem.hpp
|
||||
systems/EditorSkyboxSystem.hpp
|
||||
systems/EditorWaterPlaneSystem.hpp
|
||||
systems/LightSystem.hpp
|
||||
systems/CameraSystem.hpp
|
||||
systems/LodSystem.hpp
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "systems/BuoyancySystem.hpp"
|
||||
#include "systems/EditorSunSystem.hpp"
|
||||
#include "systems/EditorSkyboxSystem.hpp"
|
||||
#include "systems/EditorWaterPlaneSystem.hpp"
|
||||
#include "systems/LightSystem.hpp"
|
||||
#include "systems/CameraSystem.hpp"
|
||||
#include "systems/LodSystem.hpp"
|
||||
@@ -238,6 +239,9 @@ void EditorApp::setup()
|
||||
m_sunSystem = std::make_unique<EditorSunSystem>(m_world, m_sceneMgr);
|
||||
m_skyboxSystem =
|
||||
std::make_unique<EditorSkyboxSystem>(m_world, m_sceneMgr);
|
||||
m_waterPlaneSystem =
|
||||
std::make_unique<EditorWaterPlaneSystem>(m_world,
|
||||
m_sceneMgr);
|
||||
|
||||
// Apply debug setting if it was set before system creation
|
||||
if (m_debugBuoyancy) {
|
||||
@@ -705,6 +709,14 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
cam = m_camera->getCamera();
|
||||
m_skyboxSystem->update(cam);
|
||||
}
|
||||
if (m_waterPlaneSystem) {
|
||||
Ogre::Camera *cam = nullptr;
|
||||
if (m_sceneMgr->hasCamera("PlayerCamera"))
|
||||
cam = m_sceneMgr->getCamera("PlayerCamera");
|
||||
if (!cam && m_camera)
|
||||
cam = m_camera->getCamera();
|
||||
m_waterPlaneSystem->update(evt.timeSinceLastFrame, cam);
|
||||
}
|
||||
if (m_lightSystem) {
|
||||
m_lightSystem->update();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class PlayerControllerSystem;
|
||||
class BuoyancySystem;
|
||||
class EditorSunSystem;
|
||||
class EditorSkyboxSystem;
|
||||
class EditorWaterPlaneSystem;
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
@@ -200,6 +201,7 @@ private:
|
||||
std::unique_ptr<BuoyancySystem> m_buoyancySystem;
|
||||
std::unique_ptr<EditorSunSystem> m_sunSystem;
|
||||
std::unique_ptr<EditorSkyboxSystem> m_skyboxSystem;
|
||||
std::unique_ptr<EditorWaterPlaneSystem> m_waterPlaneSystem;
|
||||
std::unique_ptr<EditorLightSystem> m_lightSystem;
|
||||
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
|
||||
std::unique_ptr<EditorLodSystem> m_lodSystem;
|
||||
|
||||
@@ -6,58 +6,68 @@
|
||||
|
||||
/**
|
||||
* WaterPlane component
|
||||
* Provides visual representation of water surface for buoyancy system
|
||||
* Creates a plane mesh at the water surface Y level for visualization
|
||||
* Visual water surface with reflection/refraction
|
||||
* for the editScene editor. Lightweight and OpenGL ES 2.0 compatible.
|
||||
*/
|
||||
struct WaterPlane {
|
||||
// Enable/disable water plane visualization
|
||||
// Enable/disable water
|
||||
bool enabled = true;
|
||||
|
||||
// Water surface Y level (world space) - should match WaterPhysics::waterSurfaceY
|
||||
// Water surface Y level (world space)
|
||||
float waterSurfaceY = -0.1f;
|
||||
|
||||
// Plane size (width and depth)
|
||||
float planeSize = 100.0f;
|
||||
float planeSize = 500.0f;
|
||||
|
||||
// Material name for water plane
|
||||
Ogre::String materialName = "BaseWhiteNoLighting";
|
||||
|
||||
// Opacity (0.0 = transparent, 1.0 = opaque)
|
||||
float opacity = 0.3f;
|
||||
|
||||
// Color tint
|
||||
Ogre::ColourValue color = Ogre::ColourValue(0.2f, 0.4f, 0.8f, 0.3f);
|
||||
|
||||
// Whether to update automatically from WaterPhysics component
|
||||
// Whether to update waterSurfaceY from WaterPhysics component
|
||||
bool autoUpdateFromWaterPhysics = true;
|
||||
|
||||
// Scene node for the water plane (managed by system)
|
||||
Ogre::SceneNode *sceneNode = nullptr;
|
||||
// Visual settings
|
||||
Ogre::ColourValue waterColor = Ogre::ColourValue(0.0f, 0.3f, 0.5f,
|
||||
0.8f);
|
||||
float reflectivity = 0.5f;
|
||||
float waveSpeed = 1.0f;
|
||||
float waveScale = 0.02f;
|
||||
float tiling = 0.012f;
|
||||
|
||||
// Manual object for the water plane (managed by system)
|
||||
// Render texture size (power of two recommended)
|
||||
int renderTextureSize = 512;
|
||||
|
||||
// Runtime objects (managed by EditorWaterPlaneSystem)
|
||||
Ogre::SceneNode *sceneNode = nullptr;
|
||||
Ogre::ManualObject *manualObject = nullptr;
|
||||
|
||||
// Mark component as dirty (needs update)
|
||||
// Render-to-texture resources
|
||||
Ogre::TexturePtr renderTexture;
|
||||
Ogre::Camera *reflectionCamera = nullptr;
|
||||
Ogre::Camera *refractionCamera = nullptr;
|
||||
Ogre::Viewport *reflectionViewport = nullptr;
|
||||
Ogre::Viewport *refractionViewport = nullptr;
|
||||
Ogre::RenderTarget *renderTarget = nullptr;
|
||||
|
||||
// Clip planes
|
||||
Ogre::Plane reflectionPlane;
|
||||
Ogre::Plane reflectionClipPlane;
|
||||
Ogre::Plane refractionClipPlane;
|
||||
|
||||
// Camera following state
|
||||
Ogre::Vector3 lastCameraPos = Ogre::Vector3::ZERO;
|
||||
float positionUpdateTimer = 0.0f;
|
||||
static constexpr float POSITION_UPDATE_INTERVAL = 0.3f;
|
||||
|
||||
// Which viewport to update this frame (0=reflection, 1=refraction)
|
||||
int updateViewportIndex = 0;
|
||||
|
||||
// Time accumulator for shader
|
||||
float shaderTime = 0.0f;
|
||||
|
||||
// Dirty flag
|
||||
bool dirty = true;
|
||||
|
||||
void markDirty()
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
WaterPlane() = default;
|
||||
|
||||
WaterPlane(float surfaceY, float size, const Ogre::String &material,
|
||||
float opac, const Ogre::ColourValue &col, bool autoUpdate)
|
||||
: waterSurfaceY(surfaceY)
|
||||
, planeSize(size)
|
||||
, materialName(material)
|
||||
, opacity(opac)
|
||||
, color(col)
|
||||
, autoUpdateFromWaterPhysics(autoUpdate)
|
||||
, dirty(true)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_WATERPLANE_HPP
|
||||
#endif // EDITSCENE_WATERPLANE_HPP
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
#include "WaterPlane.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
#include "../components/EditorMarker.hpp"
|
||||
#include "../components/EntityName.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "Transform.hpp"
|
||||
#include "EditorMarker.hpp"
|
||||
#include "EntityName.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/WaterPlaneEditor.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <OgreManualObject.h>
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreTechnique.h>
|
||||
#include <OgrePass.h>
|
||||
|
||||
// Register WaterPlane component
|
||||
REGISTER_COMPONENT("Water Plane", WaterPlane, WaterPlaneEditor)
|
||||
REGISTER_COMPONENT_GROUP("Water Plane", "Water", WaterPlane, WaterPlaneEditor)
|
||||
{
|
||||
registry.registerComponent<WaterPlane>(
|
||||
"Water Plane", "Water", std::make_unique<WaterPlaneEditor>(),
|
||||
"Water Plane", "Water",
|
||||
std::make_unique<WaterPlaneEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<WaterPlane>()) {
|
||||
@@ -31,236 +23,3 @@ REGISTER_COMPONENT("Water Plane", WaterPlane, WaterPlaneEditor)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* WaterPlaneModule
|
||||
* Manages WaterPlane components and creates visual water surface representations
|
||||
*/
|
||||
class WaterPlaneModule {
|
||||
public:
|
||||
WaterPlaneModule(flecs::world &world, Ogre::SceneManager *sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
{
|
||||
// Register WaterPlane component
|
||||
world.component<WaterPlane>();
|
||||
|
||||
// Create system for updating water planes
|
||||
world.system<WaterPlane>("UpdateWaterPlanes")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](flecs::entity entity,
|
||||
WaterPlane &waterPlane) {
|
||||
updateWaterPlane(entity, waterPlane);
|
||||
});
|
||||
|
||||
// Create system for cleaning up water planes when entities are destroyed
|
||||
world.observer<WaterPlane>("CleanupWaterPlanes")
|
||||
.event(flecs::OnRemove)
|
||||
.each([this](flecs::entity entity,
|
||||
WaterPlane &waterPlane) {
|
||||
cleanupWaterPlane(waterPlane);
|
||||
});
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"WaterPlaneModule initialized");
|
||||
}
|
||||
|
||||
~WaterPlaneModule()
|
||||
{
|
||||
// Clean up any remaining water planes
|
||||
m_world.each<WaterPlane>(
|
||||
[this](flecs::entity entity, WaterPlane &waterPlane) {
|
||||
cleanupWaterPlane(waterPlane);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void updateWaterPlane(flecs::entity entity, WaterPlane &waterPlane)
|
||||
{
|
||||
// Skip if not enabled
|
||||
if (!waterPlane.enabled) {
|
||||
if (waterPlane.sceneNode) {
|
||||
waterPlane.sceneNode->setVisible(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-update water surface Y from WaterPhysics if enabled
|
||||
if (waterPlane.autoUpdateFromWaterPhysics) {
|
||||
// Find WaterPhysics component in the world
|
||||
m_world.query<WaterPhysics>().each(
|
||||
[&](flecs::entity wpEntity,
|
||||
WaterPhysics &waterPhysics) {
|
||||
if (waterPhysics.waterSurfaceY !=
|
||||
waterPlane.waterSurfaceY) {
|
||||
waterPlane.waterSurfaceY =
|
||||
waterPhysics
|
||||
.waterSurfaceY;
|
||||
waterPlane.markDirty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create or update water plane visualization
|
||||
if (waterPlane.dirty || !waterPlane.sceneNode) {
|
||||
createOrUpdateWaterPlane(entity, waterPlane);
|
||||
waterPlane.dirty = false;
|
||||
}
|
||||
|
||||
// Ensure visibility
|
||||
if (waterPlane.sceneNode) {
|
||||
waterPlane.sceneNode->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
void createOrUpdateWaterPlane(flecs::entity entity,
|
||||
WaterPlane &waterPlane)
|
||||
{
|
||||
// Clean up existing water plane
|
||||
cleanupWaterPlane(waterPlane);
|
||||
|
||||
// Create scene node
|
||||
Ogre::String nodeName =
|
||||
"WaterPlaneNode_" +
|
||||
Ogre::StringConverter::toString(entity.id());
|
||||
waterPlane.sceneNode =
|
||||
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
|
||||
nodeName);
|
||||
|
||||
// Create manual object
|
||||
Ogre::String objName =
|
||||
"WaterPlaneObject_" +
|
||||
Ogre::StringConverter::toString(entity.id());
|
||||
waterPlane.manualObject =
|
||||
m_sceneMgr->createManualObject(objName);
|
||||
|
||||
// Create or get material for water plane
|
||||
Ogre::String materialName =
|
||||
"WaterPlaneMaterial_" +
|
||||
Ogre::StringConverter::toString(entity.id());
|
||||
Ogre::MaterialPtr material =
|
||||
Ogre::MaterialManager::getSingleton()
|
||||
.createOrRetrieve(
|
||||
materialName,
|
||||
Ogre::ResourceGroupManager::
|
||||
DEFAULT_RESOURCE_GROUP_NAME)
|
||||
.first.staticCast<Ogre::Material>();
|
||||
|
||||
// Configure material for transparent water
|
||||
material->setReceiveShadows(false);
|
||||
material->getTechnique(0)->getPass(0)->setDiffuse(
|
||||
waterPlane.color);
|
||||
material->getTechnique(0)->getPass(0)->setAmbient(
|
||||
waterPlane.color * 0.5f);
|
||||
material->getTechnique(0)->getPass(0)->setSelfIllumination(
|
||||
waterPlane.color * 0.2f);
|
||||
material->getTechnique(0)->getPass(0)->setSceneBlending(
|
||||
Ogre::SBT_TRANSPARENT_ALPHA);
|
||||
material->getTechnique(0)->getPass(0)->setDepthWriteEnabled(
|
||||
false);
|
||||
material->getTechnique(0)->getPass(0)->setLightingEnabled(true);
|
||||
|
||||
// Create water plane geometry
|
||||
float halfSize = waterPlane.planeSize * 0.5f;
|
||||
waterPlane.manualObject->begin(
|
||||
materialName, Ogre::RenderOperation::OT_TRIANGLE_LIST);
|
||||
|
||||
// Create a simple plane (2 triangles)
|
||||
// Vertex 0: (-halfSize, waterSurfaceY, -halfSize)
|
||||
waterPlane.manualObject->position(
|
||||
-halfSize, waterPlane.waterSurfaceY, -halfSize);
|
||||
waterPlane.manualObject->normal(0.0f, 1.0f, 0.0f);
|
||||
waterPlane.manualObject->textureCoord(0.0f, 0.0f);
|
||||
|
||||
// Vertex 1: (halfSize, waterSurfaceY, -halfSize)
|
||||
waterPlane.manualObject->position(
|
||||
halfSize, waterPlane.waterSurfaceY, -halfSize);
|
||||
waterPlane.manualObject->normal(0.0f, 1.0f, 0.0f);
|
||||
waterPlane.manualObject->textureCoord(1.0f, 0.0f);
|
||||
|
||||
// Vertex 2: (halfSize, waterSurfaceY, halfSize)
|
||||
waterPlane.manualObject->position(
|
||||
halfSize, waterPlane.waterSurfaceY, halfSize);
|
||||
waterPlane.manualObject->normal(0.0f, 1.0f, 0.0f);
|
||||
waterPlane.manualObject->textureCoord(1.0f, 1.0f);
|
||||
|
||||
// Vertex 3: (-halfSize, waterSurfaceY, halfSize)
|
||||
waterPlane.manualObject->position(
|
||||
-halfSize, waterPlane.waterSurfaceY, halfSize);
|
||||
waterPlane.manualObject->normal(0.0f, 1.0f, 0.0f);
|
||||
waterPlane.manualObject->textureCoord(0.0f, 1.0f);
|
||||
|
||||
// First triangle
|
||||
waterPlane.manualObject->triangle(0, 1, 2);
|
||||
|
||||
// Second triangle
|
||||
waterPlane.manualObject->triangle(0, 2, 3);
|
||||
|
||||
waterPlane.manualObject->end();
|
||||
|
||||
// Attach to scene node
|
||||
waterPlane.sceneNode->attachObject(waterPlane.manualObject);
|
||||
|
||||
// Log creation
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Created water plane at Y=" +
|
||||
Ogre::StringConverter::toString(
|
||||
waterPlane.waterSurfaceY) +
|
||||
", size=" +
|
||||
Ogre::StringConverter::toString(waterPlane.planeSize));
|
||||
}
|
||||
|
||||
void cleanupWaterPlane(WaterPlane &waterPlane)
|
||||
{
|
||||
if (waterPlane.manualObject) {
|
||||
if (waterPlane.sceneNode) {
|
||||
waterPlane.sceneNode->detachObject(
|
||||
waterPlane.manualObject);
|
||||
}
|
||||
m_sceneMgr->destroyManualObject(
|
||||
waterPlane.manualObject);
|
||||
waterPlane.manualObject = nullptr;
|
||||
}
|
||||
|
||||
if (waterPlane.sceneNode) {
|
||||
m_sceneMgr->destroySceneNode(waterPlane.sceneNode);
|
||||
waterPlane.sceneNode = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
flecs::world &m_world;
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
};
|
||||
|
||||
// Function to initialize WaterPlaneModule
|
||||
void initializeWaterPlaneModule(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr)
|
||||
{
|
||||
static WaterPlaneModule *module = nullptr;
|
||||
if (!module) {
|
||||
module = new WaterPlaneModule(world, sceneMgr);
|
||||
|
||||
// Create default WaterPlane entity if none exists
|
||||
bool hasWaterPlane = false;
|
||||
world.query<WaterPlane>().each(
|
||||
[&](flecs::entity, WaterPlane &) {
|
||||
hasWaterPlane = true;
|
||||
});
|
||||
|
||||
if (!hasWaterPlane) {
|
||||
flecs::entity waterPlaneEntity =
|
||||
world.entity("WaterPlane");
|
||||
waterPlaneEntity.set<WaterPlane>({});
|
||||
waterPlaneEntity.add<EditorMarkerComponent>();
|
||||
waterPlaneEntity.set<EntityNameComponent>(
|
||||
EntityNameComponent("Water Plane"));
|
||||
|
||||
// Add Transform component for potential future use
|
||||
waterPlaneEntity.set<TransformComponent>(
|
||||
TransformComponent());
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Created default WaterPlane entity");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
OGRE_NATIVE_GLSL_VERSION_DIRECTIVE
|
||||
#include <OgreUnifiedShader.h>
|
||||
|
||||
SAMPLER2D(reflectRefractMap, 0);
|
||||
SAMPLER2D(noiseMap, 1);
|
||||
|
||||
OGRE_UNIFORMS(
|
||||
uniform float time;
|
||||
uniform float waveScale;
|
||||
uniform vec4 waterColor;
|
||||
uniform float reflectivity;
|
||||
)
|
||||
|
||||
IN(vec4 vClipSpace, TEXCOORD0)
|
||||
IN(vec2 vUV, TEXCOORD1)
|
||||
|
||||
MAIN_DECLARATION
|
||||
{
|
||||
// Normalized device coordinates
|
||||
vec2 ndc = vClipSpace.xy / vClipSpace.w;
|
||||
ndc = ndc * 0.5 + 0.5;
|
||||
|
||||
// Noise-based distortion at 3 scales
|
||||
vec2 distortion1 = (texture2D(noiseMap, vUV + vec2(time * 0.01, time * 0.008)).rg * 2.0 - 1.0) * waveScale;
|
||||
vec2 distortion2 = (texture2D(noiseMap, vUV * 2.0 + vec2(-time * 0.006, time * 0.012)).rg * 2.0 - 1.0) * waveScale * 0.5;
|
||||
vec2 distortion3 = (texture2D(noiseMap, vUV * 4.0 + vec2(time * 0.004, -time * 0.005)).rg * 2.0 - 1.0) * waveScale * 0.25;
|
||||
vec2 totalDistortion = distortion1 + distortion2 + distortion3;
|
||||
|
||||
// Reflection samples from left half of texture
|
||||
vec2 reflectionUV = vec2(ndc.x, 1.0 - ndc.y) * 0.5 + totalDistortion;
|
||||
// Refraction samples from right half of texture
|
||||
vec2 refractionUV = vec2(ndc.x, ndc.y) * 0.5 + vec2(0.5, 0.0) + totalDistortion;
|
||||
|
||||
// Clamp to avoid sampling outside viewport
|
||||
reflectionUV = clamp(reflectionUV, vec2(0.001, 0.001), vec2(0.499, 0.999));
|
||||
refractionUV = clamp(refractionUV, vec2(0.501, 0.001), vec2(0.999, 0.999));
|
||||
|
||||
vec4 reflectionColour = texture2D(reflectRefractMap, reflectionUV);
|
||||
vec4 refractionColour = texture2D(reflectRefractMap, refractionUV);
|
||||
|
||||
// Mix reflection and refraction
|
||||
vec4 water = mix(refractionColour, reflectionColour, reflectivity);
|
||||
|
||||
// Apply water color tint
|
||||
gl_FragColor = water * waterColor;
|
||||
gl_FragColor.a = waterColor.a;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
material WaterPlane/Dynamic
|
||||
{
|
||||
technique
|
||||
{
|
||||
pass
|
||||
{
|
||||
lighting off
|
||||
depth_write off
|
||||
scene_blend alpha_blend
|
||||
cull_hardware none
|
||||
|
||||
vertex_program_ref WaterPlaneVP
|
||||
{
|
||||
param_named tiling float 0.012
|
||||
}
|
||||
|
||||
fragment_program_ref WaterPlaneFP
|
||||
{
|
||||
}
|
||||
|
||||
texture_unit
|
||||
{
|
||||
texture ReflectionRefractionTexture
|
||||
tex_address_mode mirror
|
||||
filtering linear linear linear
|
||||
}
|
||||
|
||||
texture_unit
|
||||
{
|
||||
texture waves2.png
|
||||
tex_address_mode wrap
|
||||
filtering linear linear linear
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
vertex_program WaterPlaneVP glsl glsles glslang hlsl
|
||||
{
|
||||
source water_plane.vert
|
||||
default_params
|
||||
{
|
||||
param_named_auto worldViewProj worldviewproj_matrix
|
||||
}
|
||||
}
|
||||
|
||||
fragment_program WaterPlaneFP glsl glsles glslang hlsl
|
||||
{
|
||||
source water_plane.frag
|
||||
default_params
|
||||
{
|
||||
param_named time float 0.0
|
||||
param_named waveScale float 0.02
|
||||
param_named waterColor float4 0.0 0.3 0.5 0.8
|
||||
param_named reflectivity float 0.5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
OGRE_NATIVE_GLSL_VERSION_DIRECTIVE
|
||||
#include <OgreUnifiedShader.h>
|
||||
|
||||
OGRE_UNIFORMS(
|
||||
uniform mat4 worldViewProj;
|
||||
uniform float tiling;
|
||||
)
|
||||
|
||||
OUT(vec4 vClipSpace, TEXCOORD0)
|
||||
OUT(vec2 vUV, TEXCOORD1)
|
||||
|
||||
MAIN_PARAMETERS
|
||||
IN(vec4 vertex, POSITION)
|
||||
IN(vec2 uv0, TEXCOORD0)
|
||||
MAIN_DECLARATION
|
||||
{
|
||||
vec4 worldPos = mul(worldViewProj, vec4(vertex.xyz, 1.0));
|
||||
gl_Position = worldPos;
|
||||
vClipSpace = worldPos;
|
||||
vUV = uv0 * tiling;
|
||||
}
|
||||
BIN
src/features/editScene/resources/textures/waves2.png
Normal file
BIN
src/features/editScene/resources/textures/waves2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
@@ -8,6 +8,7 @@
|
||||
#include "../components/RigidBody.hpp"
|
||||
#include "../components/BuoyancyInfo.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
#include "../components/WaterPlane.hpp"
|
||||
#include "../components/Sun.hpp"
|
||||
#include "../components/Skybox.hpp"
|
||||
#include "../components/Light.hpp"
|
||||
@@ -639,6 +640,15 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render WaterPlane if present
|
||||
if (entity.has<WaterPlane>()) {
|
||||
auto &wp = entity.get_mut<WaterPlane>();
|
||||
if (m_componentRegistry.render<WaterPlane>(entity, wp)) {
|
||||
wp.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render LOD Settings if present
|
||||
if (entity.has<LodSettingsComponent>()) {
|
||||
auto &lodSettings = entity.get_mut<LodSettingsComponent>();
|
||||
|
||||
407
src/features/editScene/systems/EditorWaterPlaneSystem.cpp
Normal file
407
src/features/editScene/systems/EditorWaterPlaneSystem.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
#include "EditorWaterPlaneSystem.hpp"
|
||||
#include "../components/WaterPlane.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <OgreManualObject.h>
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreTechnique.h>
|
||||
#include <OgrePass.h>
|
||||
#include <OgreGpuProgramParams.h>
|
||||
|
||||
static const uint32_t WATER_MASK = 0xF00;
|
||||
|
||||
void EditorWaterPlaneSystem::WaterRenderTargetListener::preRenderTargetUpdate(
|
||||
const Ogre::RenderTargetEvent &evt)
|
||||
{
|
||||
if (waterMesh)
|
||||
waterMesh->setVisible(false);
|
||||
}
|
||||
|
||||
void EditorWaterPlaneSystem::WaterRenderTargetListener::postRenderTargetUpdate(
|
||||
const Ogre::RenderTargetEvent &evt)
|
||||
{
|
||||
if (waterMesh)
|
||||
waterMesh->setVisible(true);
|
||||
}
|
||||
|
||||
EditorWaterPlaneSystem::EditorWaterPlaneSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_query(world.query<WaterPlane, TransformComponent>())
|
||||
{
|
||||
}
|
||||
|
||||
EditorWaterPlaneSystem::~EditorWaterPlaneSystem() = default;
|
||||
|
||||
void EditorWaterPlaneSystem::update(float deltaTime, Ogre::Camera *camera)
|
||||
{
|
||||
m_query.each([&](flecs::entity entity, WaterPlane &wp,
|
||||
TransformComponent &transform) {
|
||||
if (!wp.enabled) {
|
||||
if (wp.sceneNode)
|
||||
wp.sceneNode->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wp.dirty || !wp.manualObject) {
|
||||
rebuildWaterPlane(entity, wp, transform);
|
||||
wp.dirty = false;
|
||||
}
|
||||
|
||||
if (!wp.sceneNode || !wp.manualObject)
|
||||
return;
|
||||
|
||||
// Auto-sync Y from WaterPhysics
|
||||
if (wp.autoUpdateFromWaterPhysics) {
|
||||
m_world.query<WaterPhysics>().each(
|
||||
[&](flecs::entity, WaterPhysics &physics) {
|
||||
if (physics.waterSurfaceY != wp.waterSurfaceY) {
|
||||
wp.waterSurfaceY = physics.waterSurfaceY;
|
||||
wp.sceneNode->setPosition(
|
||||
wp.sceneNode->getPosition().x,
|
||||
wp.waterSurfaceY,
|
||||
wp.sceneNode->getPosition().z);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update shader time and params
|
||||
updateShaderParams(wp, deltaTime);
|
||||
|
||||
// Update cameras
|
||||
if (camera)
|
||||
updateCameras(wp, camera);
|
||||
|
||||
// Follow camera on XZ plane
|
||||
if (camera)
|
||||
updatePosition(wp, deltaTime, camera);
|
||||
|
||||
// Alternate viewport updates
|
||||
if (wp.renderTarget) {
|
||||
if (wp.updateViewportIndex == 0 && wp.reflectionViewport)
|
||||
wp.reflectionViewport->update();
|
||||
else if (wp.updateViewportIndex == 1 && wp.refractionViewport)
|
||||
wp.refractionViewport->update();
|
||||
wp.updateViewportIndex = 1 - wp.updateViewportIndex;
|
||||
}
|
||||
|
||||
wp.sceneNode->setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorWaterPlaneSystem::rebuildWaterPlane(
|
||||
flecs::entity entity, WaterPlane &wp,
|
||||
TransformComponent &transform)
|
||||
{
|
||||
cleanupWaterPlane(wp);
|
||||
|
||||
Ogre::String baseName = "WaterPlane_" +
|
||||
Ogre::StringConverter::toString(entity.id());
|
||||
|
||||
// Create scene node
|
||||
if (transform.node) {
|
||||
wp.sceneNode =
|
||||
transform.node->createChildSceneNode(baseName + "Node");
|
||||
} else {
|
||||
wp.sceneNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode(
|
||||
baseName + "Node");
|
||||
}
|
||||
wp.sceneNode->setPosition(0.0f, wp.waterSurfaceY, 0.0f);
|
||||
|
||||
// Create plane mesh
|
||||
createPlaneMesh(wp);
|
||||
|
||||
// Create render texture
|
||||
Ogre::String texName = baseName + "_RTT";
|
||||
wp.renderTexture = Ogre::TextureManager::getSingleton().createManual(
|
||||
texName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, wp.renderTextureSize, wp.renderTextureSize, 0,
|
||||
Ogre::PF_R8G8B8A8, Ogre::TU_RENDERTARGET);
|
||||
|
||||
wp.renderTarget = wp.renderTexture->getBuffer()->getRenderTarget();
|
||||
wp.renderTarget->setAutoUpdated(false);
|
||||
|
||||
// Set up clip planes
|
||||
wp.reflectionPlane =
|
||||
Ogre::Plane(Ogre::Vector3(0.0f, 1.0f, 0.0f), wp.waterSurfaceY);
|
||||
wp.reflectionClipPlane = Ogre::Plane(Ogre::Vector3(0.0f, 1.0f, 0.0f),
|
||||
wp.waterSurfaceY - 0.1f);
|
||||
wp.refractionClipPlane = Ogre::Plane(
|
||||
Ogre::Vector3(0.0f, -1.0f, 0.0f), -(wp.waterSurfaceY + 0.1f));
|
||||
|
||||
// Set listener to hide water during RTT
|
||||
m_listener.waterMesh = wp.manualObject;
|
||||
wp.renderTarget->addListener(&m_listener);
|
||||
|
||||
// Create cameras with their own nodes
|
||||
wp.reflectionCamera = m_sceneMgr->createCamera(baseName + "ReflectionCam");
|
||||
wp.refractionCamera = m_sceneMgr->createCamera(baseName + "RefractionCam");
|
||||
|
||||
Ogre::SceneNode *reflNode =
|
||||
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
|
||||
baseName + "ReflectionNode");
|
||||
reflNode->attachObject(wp.reflectionCamera);
|
||||
|
||||
Ogre::SceneNode *refrNode =
|
||||
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
|
||||
baseName + "RefractionNode");
|
||||
refrNode->attachObject(wp.refractionCamera);
|
||||
|
||||
// Reflection viewport (left half)
|
||||
wp.reflectionViewport = wp.renderTarget->addViewport(
|
||||
wp.reflectionCamera, 0, 0.0f, 0.0f, 0.5f, 1.0f);
|
||||
wp.reflectionViewport->setClearEveryFrame(true);
|
||||
wp.reflectionViewport->setBackgroundColour(
|
||||
Ogre::ColourValue(0.0f, 0.0f, 0.1f, 1.0f));
|
||||
wp.reflectionViewport->setOverlaysEnabled(false);
|
||||
wp.reflectionViewport->setSkiesEnabled(true);
|
||||
wp.reflectionViewport->setAutoUpdated(false);
|
||||
wp.reflectionViewport->setVisibilityMask(~WATER_MASK);
|
||||
|
||||
// Refraction viewport (right half)
|
||||
wp.refractionViewport = wp.renderTarget->addViewport(
|
||||
wp.refractionCamera, 1, 0.5f, 0.0f, 0.5f, 1.0f);
|
||||
wp.refractionViewport->setClearEveryFrame(true);
|
||||
wp.refractionViewport->setBackgroundColour(
|
||||
Ogre::ColourValue(0.0f, 0.1f, 0.2f, 1.0f));
|
||||
wp.refractionViewport->setOverlaysEnabled(false);
|
||||
wp.refractionViewport->setSkiesEnabled(false);
|
||||
wp.refractionViewport->setAutoUpdated(false);
|
||||
wp.refractionViewport->setVisibilityMask(~WATER_MASK);
|
||||
|
||||
// Apply material
|
||||
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(
|
||||
"WaterPlane/Dynamic",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
if (!mat) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"WaterPlaneSystem: Warning - WaterPlane/Dynamic material not found, using placeholder");
|
||||
}
|
||||
wp.manualObject->setMaterialName(0, "WaterPlane/Dynamic");
|
||||
|
||||
// Set visibility mask so cameras can hide water
|
||||
wp.manualObject->setVisibilityFlags(WATER_MASK);
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"WaterPlaneSystem: Created water plane for entity " +
|
||||
Ogre::StringConverter::toString(entity.id()) +
|
||||
", size=" + Ogre::StringConverter::toString(wp.planeSize) +
|
||||
", Y=" + Ogre::StringConverter::toString(wp.waterSurfaceY));
|
||||
}
|
||||
|
||||
void EditorWaterPlaneSystem::cleanupWaterPlane(WaterPlane &wp)
|
||||
{
|
||||
if (wp.renderTarget) {
|
||||
wp.renderTarget->removeListener(&m_listener);
|
||||
wp.renderTarget->removeAllViewports();
|
||||
wp.renderTarget = nullptr;
|
||||
}
|
||||
wp.reflectionViewport = nullptr;
|
||||
wp.refractionViewport = nullptr;
|
||||
|
||||
if (wp.renderTexture) {
|
||||
Ogre::TextureManager::getSingleton().remove(wp.renderTexture);
|
||||
wp.renderTexture.reset();
|
||||
}
|
||||
|
||||
if (wp.reflectionCamera) {
|
||||
Ogre::SceneNode *node = wp.reflectionCamera->getParentSceneNode();
|
||||
if (node) {
|
||||
node->detachObject(wp.reflectionCamera);
|
||||
m_sceneMgr->destroySceneNode(node);
|
||||
}
|
||||
m_sceneMgr->destroyCamera(wp.reflectionCamera);
|
||||
wp.reflectionCamera = nullptr;
|
||||
}
|
||||
if (wp.refractionCamera) {
|
||||
Ogre::SceneNode *node = wp.refractionCamera->getParentSceneNode();
|
||||
if (node) {
|
||||
node->detachObject(wp.refractionCamera);
|
||||
m_sceneMgr->destroySceneNode(node);
|
||||
}
|
||||
m_sceneMgr->destroyCamera(wp.refractionCamera);
|
||||
wp.refractionCamera = nullptr;
|
||||
}
|
||||
|
||||
if (wp.manualObject) {
|
||||
if (wp.sceneNode)
|
||||
wp.sceneNode->detachObject(wp.manualObject);
|
||||
m_sceneMgr->destroyManualObject(wp.manualObject);
|
||||
wp.manualObject = nullptr;
|
||||
}
|
||||
if (wp.sceneNode) {
|
||||
if (wp.sceneNode->getParentSceneNode()) {
|
||||
wp.sceneNode->getParentSceneNode()->removeChild(
|
||||
wp.sceneNode);
|
||||
}
|
||||
m_sceneMgr->destroySceneNode(wp.sceneNode);
|
||||
wp.sceneNode = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorWaterPlaneSystem::createPlaneMesh(WaterPlane &wp)
|
||||
{
|
||||
Ogre::String objName = "WaterPlaneMesh_" +
|
||||
Ogre::StringConverter::toString(
|
||||
(size_t)wp.sceneNode);
|
||||
wp.manualObject = m_sceneMgr->createManualObject(objName);
|
||||
|
||||
int segments = 20;
|
||||
float halfSize = wp.planeSize * 0.5f;
|
||||
float step = wp.planeSize / (float)segments;
|
||||
|
||||
wp.manualObject->begin("WaterPlane/Dynamic",
|
||||
Ogre::RenderOperation::OT_TRIANGLE_LIST);
|
||||
|
||||
for (int z = 0; z <= segments; z++) {
|
||||
for (int x = 0; x <= segments; x++) {
|
||||
float px = -halfSize + x * step;
|
||||
float pz = -halfSize + z * step;
|
||||
float u = (float)x / (float)segments;
|
||||
float v = (float)z / (float)segments;
|
||||
wp.manualObject->position(px, 0.0f, pz);
|
||||
wp.manualObject->normal(0.0f, 1.0f, 0.0f);
|
||||
wp.manualObject->textureCoord(u, v);
|
||||
}
|
||||
}
|
||||
|
||||
for (int z = 0; z < segments; z++) {
|
||||
for (int x = 0; x < segments; x++) {
|
||||
int i0 = z * (segments + 1) + x;
|
||||
int i1 = i0 + 1;
|
||||
int i2 = i0 + (segments + 1);
|
||||
int i3 = i2 + 1;
|
||||
wp.manualObject->triangle(i0, i2, i1);
|
||||
wp.manualObject->triangle(i1, i2, i3);
|
||||
}
|
||||
}
|
||||
|
||||
wp.manualObject->end();
|
||||
wp.sceneNode->attachObject(wp.manualObject);
|
||||
}
|
||||
|
||||
Ogre::SceneNode *EditorWaterPlaneSystem::getReflectionNode(WaterPlane &wp)
|
||||
{
|
||||
if (!wp.reflectionCamera)
|
||||
return nullptr;
|
||||
Ogre::SceneNode *node = wp.reflectionCamera->getParentSceneNode();
|
||||
return node;
|
||||
}
|
||||
|
||||
Ogre::SceneNode *EditorWaterPlaneSystem::getRefractionNode(WaterPlane &wp)
|
||||
{
|
||||
if (!wp.refractionCamera)
|
||||
return nullptr;
|
||||
Ogre::SceneNode *node = wp.refractionCamera->getParentSceneNode();
|
||||
return node;
|
||||
}
|
||||
|
||||
void EditorWaterPlaneSystem::updateCameras(WaterPlane &wp,
|
||||
Ogre::Camera *camera)
|
||||
{
|
||||
if (!camera)
|
||||
return;
|
||||
|
||||
Ogre::SceneNode *camNode = camera->getParentSceneNode();
|
||||
Ogre::Vector3 camPos = camNode ? camNode->_getDerivedPosition()
|
||||
: Ogre::Vector3::ZERO;
|
||||
Ogre::Quaternion camOrient = camNode
|
||||
? camNode->_getDerivedOrientation()
|
||||
: Ogre::Quaternion::IDENTITY;
|
||||
|
||||
// Reflection camera: mirrored below water plane
|
||||
if (wp.reflectionCamera) {
|
||||
wp.reflectionCamera->setAspectRatio(
|
||||
camera->getAspectRatio());
|
||||
wp.reflectionCamera->setNearClipDistance(
|
||||
camera->getNearClipDistance());
|
||||
wp.reflectionCamera->setFarClipDistance(
|
||||
camera->getFarClipDistance());
|
||||
Ogre::SceneNode *node = getReflectionNode(wp);
|
||||
if (node) {
|
||||
node->setPosition(camPos);
|
||||
node->setOrientation(camOrient);
|
||||
}
|
||||
wp.reflectionCamera->enableReflection(wp.reflectionPlane);
|
||||
wp.reflectionCamera->enableCustomNearClipPlane(
|
||||
wp.reflectionClipPlane);
|
||||
}
|
||||
|
||||
// Refraction camera: same position/orientation, clipped above water
|
||||
if (wp.refractionCamera) {
|
||||
wp.refractionCamera->setAspectRatio(
|
||||
camera->getAspectRatio());
|
||||
wp.refractionCamera->setNearClipDistance(
|
||||
camera->getNearClipDistance());
|
||||
wp.refractionCamera->setFarClipDistance(
|
||||
camera->getFarClipDistance());
|
||||
Ogre::SceneNode *node = getRefractionNode(wp);
|
||||
if (node) {
|
||||
node->setPosition(camPos);
|
||||
node->setOrientation(camOrient);
|
||||
}
|
||||
wp.refractionCamera->enableCustomNearClipPlane(
|
||||
wp.refractionClipPlane);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorWaterPlaneSystem::updatePosition(WaterPlane &wp, float deltaTime,
|
||||
Ogre::Camera *camera)
|
||||
{
|
||||
if (!camera)
|
||||
return;
|
||||
|
||||
wp.positionUpdateTimer += deltaTime;
|
||||
if (wp.positionUpdateTimer < WaterPlane::POSITION_UPDATE_INTERVAL)
|
||||
return;
|
||||
wp.positionUpdateTimer = 0.0f;
|
||||
|
||||
Ogre::SceneNode *camNode = camera->getParentSceneNode();
|
||||
Ogre::Vector3 camPos = camNode ? camNode->_getDerivedPosition()
|
||||
: Ogre::Vector3::ZERO;
|
||||
|
||||
Ogre::Vector3 waterPos = wp.sceneNode->getPosition();
|
||||
Ogre::Vector3 targetPos = camPos;
|
||||
targetPos.y = wp.waterSurfaceY;
|
||||
|
||||
Ogre::Vector3 d = targetPos - waterPos;
|
||||
d.y = 0.0f; // Only move on XZ
|
||||
|
||||
if (d.squaredLength() < 100.0f * 100.0f)
|
||||
wp.sceneNode->translate(d * 3.0f * deltaTime);
|
||||
else
|
||||
wp.sceneNode->translate(d);
|
||||
}
|
||||
|
||||
void EditorWaterPlaneSystem::updateShaderParams(WaterPlane &wp,
|
||||
float deltaTime)
|
||||
{
|
||||
wp.shaderTime += deltaTime * wp.waveSpeed;
|
||||
|
||||
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(
|
||||
"WaterPlane/Dynamic",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
if (!mat)
|
||||
return;
|
||||
|
||||
Ogre::Pass *pass = mat->getTechnique(0)->getPass(0);
|
||||
Ogre::GpuProgramParametersSharedPtr fpParams =
|
||||
pass->getFragmentProgramParameters();
|
||||
if (!fpParams)
|
||||
return;
|
||||
|
||||
fpParams->setNamedConstant("time", wp.shaderTime);
|
||||
fpParams->setNamedConstant("waveScale", wp.waveScale);
|
||||
fpParams->setNamedConstant(
|
||||
"waterColor",
|
||||
Ogre::Vector4(wp.waterColor.r, wp.waterColor.g,
|
||||
wp.waterColor.b, wp.waterColor.a));
|
||||
fpParams->setNamedConstant("reflectivity", wp.reflectivity);
|
||||
|
||||
Ogre::GpuProgramParametersSharedPtr vpParams =
|
||||
pass->getVertexProgramParameters();
|
||||
if (vpParams)
|
||||
vpParams->setNamedConstant("tiling", wp.tiling);
|
||||
}
|
||||
49
src/features/editScene/systems/EditorWaterPlaneSystem.hpp
Normal file
49
src/features/editScene/systems/EditorWaterPlaneSystem.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef EDITSCENE_EDITOR_WATERPLANE_SYSTEM_HPP
|
||||
#define EDITSCENE_EDITOR_WATERPLANE_SYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* WaterPlane system - manages visual water surface with
|
||||
* reflection/refraction render-to-texture.
|
||||
*/
|
||||
class EditorWaterPlaneSystem {
|
||||
public:
|
||||
EditorWaterPlaneSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr);
|
||||
~EditorWaterPlaneSystem();
|
||||
|
||||
void update(float deltaTime, Ogre::Camera *camera);
|
||||
|
||||
private:
|
||||
void rebuildWaterPlane(flecs::entity entity, struct WaterPlane &wp,
|
||||
struct TransformComponent &transform);
|
||||
void cleanupWaterPlane(WaterPlane &wp);
|
||||
void createPlaneMesh(WaterPlane &wp);
|
||||
void updateCameras(WaterPlane &wp, Ogre::Camera *camera);
|
||||
void updatePosition(WaterPlane &wp, float deltaTime,
|
||||
Ogre::Camera *camera);
|
||||
void updateShaderParams(WaterPlane &wp, float deltaTime);
|
||||
Ogre::SceneNode *getReflectionNode(WaterPlane &wp);
|
||||
Ogre::SceneNode *getRefractionNode(WaterPlane &wp);
|
||||
|
||||
// Render target listener - hides water during RTT update
|
||||
class WaterRenderTargetListener :
|
||||
public Ogre::RenderTargetListener {
|
||||
public:
|
||||
Ogre::ManualObject *waterMesh = nullptr;
|
||||
void preRenderTargetUpdate(
|
||||
const Ogre::RenderTargetEvent &evt) override;
|
||||
void postRenderTargetUpdate(
|
||||
const Ogre::RenderTargetEvent &evt) override;
|
||||
};
|
||||
|
||||
flecs::world &m_world;
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
flecs::query<struct WaterPlane, struct TransformComponent> m_query;
|
||||
WaterRenderTargetListener m_listener;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_EDITOR_WATERPLANE_SYSTEM_HPP
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "../components/GeneratedPhysicsTag.hpp"
|
||||
#include "../components/BuoyancyInfo.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
#include "../components/WaterPlane.hpp"
|
||||
#include "../components/Sun.hpp"
|
||||
#include "../components/Skybox.hpp"
|
||||
#include "EditorUISystem.hpp"
|
||||
@@ -261,6 +262,10 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
json["waterPhysics"] = serializeWaterPhysics(entity);
|
||||
}
|
||||
|
||||
if (entity.has<WaterPlane>()) {
|
||||
json["waterPlane"] = serializeWaterPlane(entity);
|
||||
}
|
||||
|
||||
if (entity.has<SunComponent>()) {
|
||||
json["sun"] = serializeSun(entity);
|
||||
}
|
||||
@@ -430,6 +435,10 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
|
||||
deserializeWaterPhysics(entity, json["waterPhysics"]);
|
||||
}
|
||||
|
||||
if (json.contains("waterPlane")) {
|
||||
deserializeWaterPlane(entity, json["waterPlane"]);
|
||||
}
|
||||
|
||||
if (json.contains("sun")) {
|
||||
deserializeSun(entity, json["sun"]);
|
||||
}
|
||||
@@ -2518,3 +2527,49 @@ void SceneSerializer::deserializeSkybox(flecs::entity entity,
|
||||
|
||||
entity.set<SkyboxComponent>(sky);
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json SceneSerializer::serializeWaterPlane(flecs::entity entity)
|
||||
{
|
||||
const WaterPlane &wp = entity.get<WaterPlane>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["enabled"] = wp.enabled;
|
||||
json["waterSurfaceY"] = wp.waterSurfaceY;
|
||||
json["planeSize"] = wp.planeSize;
|
||||
json["autoUpdateFromWaterPhysics"] = wp.autoUpdateFromWaterPhysics;
|
||||
json["waterColor"] = { wp.waterColor.r, wp.waterColor.g,
|
||||
wp.waterColor.b, wp.waterColor.a };
|
||||
json["reflectivity"] = wp.reflectivity;
|
||||
json["waveSpeed"] = wp.waveSpeed;
|
||||
json["waveScale"] = wp.waveScale;
|
||||
json["tiling"] = wp.tiling;
|
||||
json["renderTextureSize"] = wp.renderTextureSize;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeWaterPlane(flecs::entity entity,
|
||||
const nlohmann::json &json)
|
||||
{
|
||||
WaterPlane wp;
|
||||
|
||||
wp.enabled = json.value("enabled", true);
|
||||
wp.waterSurfaceY = json.value("waterSurfaceY", -0.1f);
|
||||
wp.planeSize = json.value("planeSize", 500.0f);
|
||||
wp.autoUpdateFromWaterPhysics =
|
||||
json.value("autoUpdateFromWaterPhysics", true);
|
||||
if (json.contains("waterColor") && json["waterColor"].is_array() &&
|
||||
json["waterColor"].size() >= 4) {
|
||||
wp.waterColor = Ogre::ColourValue(
|
||||
json["waterColor"][0], json["waterColor"][1],
|
||||
json["waterColor"][2], json["waterColor"][3]);
|
||||
}
|
||||
wp.reflectivity = json.value("reflectivity", 0.5f);
|
||||
wp.waveSpeed = json.value("waveSpeed", 1.0f);
|
||||
wp.waveScale = json.value("waveScale", 0.02f);
|
||||
wp.tiling = json.value("tiling", 0.012f);
|
||||
wp.renderTextureSize = json.value("renderTextureSize", 512);
|
||||
|
||||
entity.set<WaterPlane>(wp);
|
||||
}
|
||||
|
||||
@@ -141,6 +141,11 @@ private:
|
||||
void deserializeWaterPhysics(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
|
||||
// WaterPlane serialization
|
||||
nlohmann::json serializeWaterPlane(flecs::entity entity);
|
||||
void deserializeWaterPlane(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
|
||||
// Sun / Skybox serialization
|
||||
nlohmann::json serializeSun(flecs::entity entity);
|
||||
nlohmann::json serializeSkybox(flecs::entity entity);
|
||||
|
||||
@@ -15,22 +15,20 @@ public:
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
ImGui::Text("Water Plane Visualization");
|
||||
ImGui::PushID("WaterPlane");
|
||||
ImGui::Text("Water Surface");
|
||||
ImGui::Separator();
|
||||
|
||||
// Enabled toggle
|
||||
if (ImGui::Checkbox("Enabled", &component.enabled)) {
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
|
||||
// Auto-update from WaterPhysics
|
||||
if (ImGui::Checkbox("Auto-update from Water Physics",
|
||||
&component.autoUpdateFromWaterPhysics)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Water surface Y level (editable if not auto-updating)
|
||||
if (!component.autoUpdateFromWaterPhysics) {
|
||||
if (ImGui::SliderFloat("Water Surface Y",
|
||||
&component.waterSurfaceY, -10.0f,
|
||||
@@ -44,50 +42,57 @@ public:
|
||||
component.waterSurfaceY);
|
||||
}
|
||||
|
||||
// Plane size
|
||||
if (ImGui::SliderFloat("Plane Size", &component.planeSize,
|
||||
10.0f, 500.0f, "%.0f")) {
|
||||
100.0f, 2000.0f, "%.0f")) {
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
|
||||
if (ImGui::SliderInt("RTT Size", &component.renderTextureSize,
|
||||
128, 1024)) {
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Visual Appearance");
|
||||
ImGui::Text("Appearance");
|
||||
|
||||
// Color picker
|
||||
float color[4] = { component.color.r, component.color.g,
|
||||
component.color.b, component.color.a };
|
||||
if (ImGui::ColorEdit4("Color", color,
|
||||
float color[4] = { component.waterColor.r, component.waterColor.g,
|
||||
component.waterColor.b, component.waterColor.a };
|
||||
if (ImGui::ColorEdit4("Water Color", color,
|
||||
ImGuiColorEditFlags_AlphaBar)) {
|
||||
component.color = Ogre::ColourValue(color[0], color[1],
|
||||
color[2], color[3]);
|
||||
component.waterColor = Ogre::ColourValue(
|
||||
color[0], color[1], color[2], color[3]);
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
|
||||
// Opacity
|
||||
if (ImGui::SliderFloat("Opacity", &component.opacity, 0.0f,
|
||||
1.0f, "%.2f")) {
|
||||
component.color.a = component.opacity;
|
||||
if (ImGui::SliderFloat("Reflectivity", &component.reflectivity,
|
||||
0.0f, 1.0f, "%.2f")) {
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
|
||||
// Material name (read-only for now)
|
||||
ImGui::Text("Material: %s", component.materialName.c_str());
|
||||
if (ImGui::SliderFloat("Wave Speed", &component.waveSpeed, 0.0f,
|
||||
5.0f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (ImGui::SliderFloat("Wave Scale", &component.waveScale, 0.0f,
|
||||
0.2f, "%.3f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (ImGui::SliderFloat("Tiling", &component.tiling, 0.001f,
|
||||
0.1f, "%.3f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Force update button
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Force Update")) {
|
||||
component.markDirty();
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset to Defaults")) {
|
||||
component = WaterPlane();
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
}
|
||||
@@ -98,4 +103,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
#endif // WATER_PLANE_EDITOR_HPP
|
||||
#endif // WATER_PLANE_EDITOR_HPP
|
||||
|
||||
Reference in New Issue
Block a user