Buoyancy
This commit is contained in:
@@ -15,6 +15,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/EditorUISystem.cpp
|
||||
systems/SceneSerializer.cpp
|
||||
systems/PhysicsSystem.cpp
|
||||
systems/BuoyancySystem.cpp
|
||||
systems/LightSystem.cpp
|
||||
systems/CameraSystem.cpp
|
||||
systems/LodSystem.cpp
|
||||
@@ -59,6 +60,7 @@ set(EDITSCENE_SOURCES
|
||||
ui/FurnitureTemplateEditor.cpp
|
||||
ui/StartupMenuEditor.cpp
|
||||
ui/PlayerControllerEditor.cpp
|
||||
ui/BuoyancyInfoEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/LightModule.cpp
|
||||
components/CameraModule.cpp
|
||||
@@ -77,6 +79,9 @@ set(EDITSCENE_SOURCES
|
||||
components/CellGrid.cpp
|
||||
components/StartupMenuModule.cpp
|
||||
components/PlayerControllerModule.cpp
|
||||
components/BuoyancyInfoModule.cpp
|
||||
components/WaterPhysicsModule.cpp
|
||||
components/WaterPlaneModule.cpp
|
||||
camera/EditorCamera.cpp
|
||||
gizmo/Gizmo.cpp
|
||||
physics/physics.cpp
|
||||
@@ -90,6 +95,9 @@ set(EDITSCENE_HEADERS
|
||||
components/Relationship.hpp
|
||||
components/PhysicsCollider.hpp
|
||||
components/RigidBody.hpp
|
||||
components/BuoyancyInfo.hpp
|
||||
components/WaterPhysics.hpp
|
||||
components/WaterPlane.hpp
|
||||
components/Light.hpp
|
||||
components/Camera.hpp
|
||||
components/Lod.hpp
|
||||
@@ -121,6 +129,7 @@ set(EDITSCENE_HEADERS
|
||||
systems/StaticGeometrySystem.hpp
|
||||
systems/SceneSerializer.hpp
|
||||
systems/PhysicsSystem.hpp
|
||||
systems/BuoyancySystem.hpp
|
||||
systems/LightSystem.hpp
|
||||
systems/CameraSystem.hpp
|
||||
systems/LodSystem.hpp
|
||||
@@ -156,6 +165,7 @@ set(EDITSCENE_HEADERS
|
||||
ui/FurnitureTemplateEditor.hpp
|
||||
ui/StartupMenuEditor.hpp
|
||||
ui/PlayerControllerEditor.hpp
|
||||
ui/BuoyancyInfoEditor.hpp
|
||||
camera/EditorCamera.hpp
|
||||
gizmo/Gizmo.hpp
|
||||
physics/physics.h
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "EditorApp.hpp"
|
||||
#include "systems/EditorUISystem.hpp"
|
||||
#include "systems/PhysicsSystem.hpp"
|
||||
#include "systems/BuoyancySystem.hpp"
|
||||
#include "systems/LightSystem.hpp"
|
||||
#include "systems/CameraSystem.hpp"
|
||||
#include "systems/LodSystem.hpp"
|
||||
@@ -25,6 +26,9 @@
|
||||
#include "components/PhysicsCollider.hpp"
|
||||
#include "components/RigidBody.hpp"
|
||||
#include "components/GeneratedPhysicsTag.hpp"
|
||||
#include "components/BuoyancyInfo.hpp"
|
||||
#include "components/WaterPhysics.hpp"
|
||||
#include "components/WaterPlane.hpp"
|
||||
#include "components/Light.hpp"
|
||||
#include "components/Camera.hpp"
|
||||
#include "components/Lod.hpp"
|
||||
@@ -219,6 +223,22 @@ void EditorApp::setup()
|
||||
m_physicsSystem->initialize();
|
||||
m_uiSystem->setPhysicsSystem(m_physicsSystem.get());
|
||||
|
||||
// Setup buoyancy system (requires physics system)
|
||||
// Get the physics wrapper from the physics system
|
||||
m_buoyancySystem = std::make_unique<BuoyancySystem>(
|
||||
m_world, m_physicsSystem->getPhysicsWrapper());
|
||||
m_buoyancySystem->initialize();
|
||||
|
||||
// Apply debug setting if it was set before system creation
|
||||
if (m_debugBuoyancy) {
|
||||
m_buoyancySystem->setDebugEnabled(true);
|
||||
}
|
||||
|
||||
// Set buoyancy system in UI system for configuration
|
||||
if (m_uiSystem) {
|
||||
m_uiSystem->setBuoyancySystem(m_buoyancySystem.get());
|
||||
}
|
||||
|
||||
// Setup light system
|
||||
m_lightSystem = std::make_unique<EditorLightSystem>(m_world,
|
||||
m_sceneMgr);
|
||||
@@ -355,6 +375,28 @@ void EditorApp::setGameMode(GameMode mode)
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::setDebugBuoyancy(bool enabled)
|
||||
{
|
||||
m_debugBuoyancy = enabled;
|
||||
|
||||
// Apply debug setting to buoyancy system if it exists
|
||||
if (m_buoyancySystem) {
|
||||
m_buoyancySystem->setDebugEnabled(enabled);
|
||||
}
|
||||
|
||||
// Log the debug mode change only if OGRE is initialized
|
||||
// (setDebugBuoyancy may be called before OGRE setup)
|
||||
try {
|
||||
if (Ogre::LogManager::getSingletonPtr()) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Buoyancy debug mode: " +
|
||||
Ogre::StringConverter::toString(enabled));
|
||||
}
|
||||
} catch (...) {
|
||||
// Ignore if OGRE not initialized yet
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::setGamePlayState(GamePlayState state)
|
||||
{
|
||||
m_gamePlayState = state;
|
||||
@@ -413,6 +455,9 @@ void EditorApp::setupECS()
|
||||
m_world.component<PhysicsColliderComponent>();
|
||||
m_world.component<GeneratedPhysicsTag>();
|
||||
m_world.component<RigidBodyComponent>();
|
||||
m_world.component<BuoyancyInfo>();
|
||||
m_world.component<WaterPhysics>();
|
||||
m_world.component<WaterPlane>();
|
||||
|
||||
// Register light and camera components
|
||||
m_world.component<LightComponent>();
|
||||
@@ -456,6 +501,20 @@ void EditorApp::setupECS()
|
||||
void EditorApp::createDefaultEntities()
|
||||
{
|
||||
// Start with empty scene - user creates entities as needed
|
||||
|
||||
// Create global WaterPhysics component for buoyancy system
|
||||
flecs::entity waterPhysicsEntity = m_world.entity("WaterPhysics");
|
||||
WaterPhysics waterPhysics;
|
||||
waterPhysics.enabled = true;
|
||||
waterPhysics.waterSurfaceY =
|
||||
1.0f; // Raise water surface to 1.0m above ground
|
||||
waterPhysics.waterDensity = 1000.0f; // kg/m³
|
||||
waterPhysics.gravity = 9.81f; // m/s²
|
||||
waterPhysics.defaultBuoyancy = 1.0f;
|
||||
waterPhysics.defaultLinearDrag = 0.1f;
|
||||
waterPhysics.defaultAngularDrag = 0.05f;
|
||||
waterPhysics.defaultSubmergedThreshold = 0.3f;
|
||||
waterPhysicsEntity.set<WaterPhysics>(waterPhysics);
|
||||
}
|
||||
|
||||
void EditorApp::setupLights()
|
||||
@@ -620,6 +679,16 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
m_physicsSystem->update(evt.timeSinceLastFrame);
|
||||
}
|
||||
|
||||
/* --- Buoyancy system (after physics) --- */
|
||||
if (m_buoyancySystem) {
|
||||
// Update camera position for water detection area
|
||||
if (m_camera) {
|
||||
Ogre::Vector3 cameraPos = m_camera->getPosition();
|
||||
m_buoyancySystem->setCameraPosition(cameraPos);
|
||||
}
|
||||
m_buoyancySystem->update(evt.timeSinceLastFrame);
|
||||
}
|
||||
|
||||
/* --- Rendering support systems --- */
|
||||
if (m_lightSystem) {
|
||||
m_lightSystem->update();
|
||||
|
||||
@@ -28,6 +28,7 @@ class CellGridSystem;
|
||||
class RoomLayoutSystem;
|
||||
class StartupMenuSystem;
|
||||
class PlayerControllerSystem;
|
||||
class BuoyancySystem;
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
@@ -127,6 +128,13 @@ public:
|
||||
{
|
||||
return m_gameMode;
|
||||
}
|
||||
|
||||
// Debug buoyancy
|
||||
void setDebugBuoyancy(bool enabled);
|
||||
bool getDebugBuoyancy() const
|
||||
{
|
||||
return m_debugBuoyancy;
|
||||
}
|
||||
GamePlayState getGamePlayState() const
|
||||
{
|
||||
return m_gamePlayState;
|
||||
@@ -187,6 +195,7 @@ private:
|
||||
std::unique_ptr<EditorCamera> m_camera;
|
||||
std::unique_ptr<ImGuiRenderListener> m_imguiListener;
|
||||
std::unique_ptr<EditorPhysicsSystem> m_physicsSystem;
|
||||
std::unique_ptr<BuoyancySystem> m_buoyancySystem;
|
||||
std::unique_ptr<EditorLightSystem> m_lightSystem;
|
||||
std::unique_ptr<EditorCameraSystem> m_cameraSystem;
|
||||
std::unique_ptr<EditorLodSystem> m_lodSystem;
|
||||
@@ -210,6 +219,7 @@ private:
|
||||
GamePlayState m_gamePlayState = GamePlayState::Menu;
|
||||
GameInputState m_gameInput;
|
||||
bool m_setupComplete = false;
|
||||
bool m_debugBuoyancy = false;
|
||||
|
||||
// Editor visualization nodes
|
||||
Ogre::SceneNode *m_gridNode = nullptr;
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
class EditorCamera {
|
||||
public:
|
||||
EditorCamera(Ogre::SceneManager *sceneMgr,
|
||||
Ogre::RenderWindow *window);
|
||||
EditorCamera(Ogre::SceneManager *sceneMgr, Ogre::RenderWindow *window);
|
||||
~EditorCamera();
|
||||
|
||||
/**
|
||||
@@ -35,7 +34,10 @@ public:
|
||||
/**
|
||||
* Get the camera
|
||||
*/
|
||||
Ogre::Camera *getCamera() const { return m_camera; }
|
||||
Ogre::Camera *getCamera() const
|
||||
{
|
||||
return m_camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus camera on a point
|
||||
@@ -47,6 +49,14 @@ public:
|
||||
*/
|
||||
void setPosition(const Ogre::Vector3 &pos);
|
||||
|
||||
/**
|
||||
* Get camera position
|
||||
*/
|
||||
Ogre::Vector3 getPosition() const
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ray from mouse position
|
||||
*/
|
||||
@@ -55,7 +65,10 @@ public:
|
||||
/**
|
||||
* Check if in FPS mode
|
||||
*/
|
||||
bool isFPSMode() const { return m_fpsMode; }
|
||||
bool isFPSMode() const
|
||||
{
|
||||
return m_fpsMode;
|
||||
}
|
||||
|
||||
private:
|
||||
void updateCameraPosition();
|
||||
@@ -65,7 +78,7 @@ private:
|
||||
Ogre::Camera *m_camera;
|
||||
Ogre::SceneNode *m_cameraNode;
|
||||
Ogre::SceneNode *m_targetNode;
|
||||
|
||||
|
||||
// Use OgreBites::CameraMan for proper camera control
|
||||
std::unique_ptr<OgreBites::CameraMan> m_cameraMan;
|
||||
|
||||
|
||||
46
src/features/editScene/components/BuoyancyInfo.hpp
Normal file
46
src/features/editScene/components/BuoyancyInfo.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef EDITSCENE_BUOYANCYINFO_HPP
|
||||
#define EDITSCENE_BUOYANCYINFO_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* BuoyancyInfo component
|
||||
* Provides per-entity buoyancy settings for water physics
|
||||
* If an entity has this component, it will use these settings
|
||||
* Otherwise, default settings from the buoyancy system will be used
|
||||
*/
|
||||
struct BuoyancyInfo {
|
||||
// Enable/disable buoyancy for this entity
|
||||
bool enabled = true;
|
||||
|
||||
// Buoyancy strength (0 = no buoyancy, 1 = neutral buoyancy, >1 = floats)
|
||||
float buoyancy = 1.0f;
|
||||
|
||||
// Linear drag when submerged (0 = no drag, 1 = full drag)
|
||||
float linearDrag = 0.1f;
|
||||
|
||||
// Angular drag when submerged (0 = no drag, 1 = full drag)
|
||||
float angularDrag = 0.05f;
|
||||
|
||||
// Water surface Y level for this entity (world space)
|
||||
// If not set (0), uses global water level from buoyancy system
|
||||
float waterSurfaceY = 0.0f;
|
||||
|
||||
// Submergedness threshold (0-1) - how much of the body must be submerged
|
||||
// before buoyancy is applied (0 = any contact, 1 = fully submerged)
|
||||
float submergedThreshold = 0.3f;
|
||||
|
||||
// Use custom water surface level (if false, uses global water level)
|
||||
bool useCustomWaterLevel = false;
|
||||
|
||||
// Mark component as dirty (needs update)
|
||||
bool dirty = true;
|
||||
|
||||
void markDirty()
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_BUOYANCYINFO_HPP
|
||||
23
src/features/editScene/components/BuoyancyInfoModule.cpp
Normal file
23
src/features/editScene/components/BuoyancyInfoModule.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "BuoyancyInfo.hpp"
|
||||
#include "Transform.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/BuoyancyInfoEditor.hpp"
|
||||
|
||||
// Register BuoyancyInfo component
|
||||
REGISTER_COMPONENT("Buoyancy Info", BuoyancyInfo, BuoyancyInfoEditor)
|
||||
{
|
||||
registry.registerComponent<BuoyancyInfo>(
|
||||
"Buoyancy Info", std::make_unique<BuoyancyInfoEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<BuoyancyInfo>()) {
|
||||
e.set<BuoyancyInfo>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<BuoyancyInfo>()) {
|
||||
e.remove<BuoyancyInfo>();
|
||||
}
|
||||
});
|
||||
}
|
||||
43
src/features/editScene/components/WaterPhysics.hpp
Normal file
43
src/features/editScene/components/WaterPhysics.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef WATER_PHYSICS_HPP
|
||||
#define WATER_PHYSICS_HPP
|
||||
|
||||
#include <Ogre.h>
|
||||
|
||||
struct WaterPhysics {
|
||||
// Water surface Y level (world space)
|
||||
float waterSurfaceY = -0.1f;
|
||||
|
||||
// Default buoyancy parameters (used when entity has no BuoyancyInfo)
|
||||
float defaultBuoyancy = 1.0f; // 1.0 = neutral buoyancy
|
||||
float defaultLinearDrag = 0.1f;
|
||||
float defaultAngularDrag = 0.05f;
|
||||
float defaultSubmergedThreshold =
|
||||
0.1f; // Minimum submerged fraction to apply buoyancy
|
||||
|
||||
// Water density (kg/m³)
|
||||
float waterDensity = 1000.0f;
|
||||
|
||||
// Gravity acceleration (m/s²)
|
||||
float gravity = 9.81f;
|
||||
|
||||
// Enable/disable water physics globally
|
||||
bool enabled = true;
|
||||
|
||||
WaterPhysics() = default;
|
||||
|
||||
WaterPhysics(float surfaceY, float buoyancy, float linearDrag,
|
||||
float angularDrag, float submergedThreshold, float density,
|
||||
float grav, bool enable)
|
||||
: waterSurfaceY(surfaceY)
|
||||
, defaultBuoyancy(buoyancy)
|
||||
, defaultLinearDrag(linearDrag)
|
||||
, defaultAngularDrag(angularDrag)
|
||||
, defaultSubmergedThreshold(submergedThreshold)
|
||||
, waterDensity(density)
|
||||
, gravity(grav)
|
||||
, enabled(enable)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif // WATER_PHYSICS_HPP
|
||||
22
src/features/editScene/components/WaterPhysicsModule.cpp
Normal file
22
src/features/editScene/components/WaterPhysicsModule.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "WaterPhysics.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/WaterPhysicsEditor.hpp"
|
||||
|
||||
// Register WaterPhysics component
|
||||
REGISTER_COMPONENT("Water Physics", WaterPhysics, WaterPhysicsEditor)
|
||||
{
|
||||
registry.registerComponent<WaterPhysics>(
|
||||
"Water Physics", std::make_unique<WaterPhysicsEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<WaterPhysics>()) {
|
||||
e.set<WaterPhysics>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<WaterPhysics>()) {
|
||||
e.remove<WaterPhysics>();
|
||||
}
|
||||
});
|
||||
}
|
||||
63
src/features/editScene/components/WaterPlane.hpp
Normal file
63
src/features/editScene/components/WaterPlane.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef EDITSCENE_WATERPLANE_HPP
|
||||
#define EDITSCENE_WATERPLANE_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* WaterPlane component
|
||||
* Provides visual representation of water surface for buoyancy system
|
||||
* Creates a plane mesh at the water surface Y level for visualization
|
||||
*/
|
||||
struct WaterPlane {
|
||||
// Enable/disable water plane visualization
|
||||
bool enabled = true;
|
||||
|
||||
// Water surface Y level (world space) - should match WaterPhysics::waterSurfaceY
|
||||
float waterSurfaceY = -0.1f;
|
||||
|
||||
// Plane size (width and depth)
|
||||
float planeSize = 100.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
|
||||
bool autoUpdateFromWaterPhysics = true;
|
||||
|
||||
// Scene node for the water plane (managed by system)
|
||||
Ogre::SceneNode *sceneNode = nullptr;
|
||||
|
||||
// Manual object for the water plane (managed by system)
|
||||
Ogre::ManualObject *manualObject = nullptr;
|
||||
|
||||
// Mark component as dirty (needs update)
|
||||
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
|
||||
266
src/features/editScene/components/WaterPlaneModule.cpp
Normal file
266
src/features/editScene/components/WaterPlaneModule.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#include "WaterPlane.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
#include "../components/EditorMarker.hpp"
|
||||
#include "../components/EntityName.hpp"
|
||||
#include "../components/Transform.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)
|
||||
{
|
||||
registry.registerComponent<WaterPlane>(
|
||||
"Water Plane", "Water", std::make_unique<WaterPlaneEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<WaterPlane>()) {
|
||||
e.set<WaterPlane>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<WaterPlane>()) {
|
||||
e.remove<WaterPlane>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,14 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Parse command line arguments
|
||||
bool gameMode = false;
|
||||
bool debugBuoyancy = false;
|
||||
Ogre::String sceneFile;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
Ogre::String arg = argv[i];
|
||||
if (arg == "--game") {
|
||||
gameMode = true;
|
||||
} else if (arg == "--debug-buoyancy") {
|
||||
debugBuoyancy = true;
|
||||
} else if (arg.length() > 0 && arg[0] != '-') {
|
||||
sceneFile = arg;
|
||||
}
|
||||
@@ -23,14 +26,23 @@ int main(int argc, char *argv[])
|
||||
app.setGameMode(EditorApp::GameMode::Game);
|
||||
}
|
||||
|
||||
if (debugBuoyancy) {
|
||||
app.setDebugBuoyancy(true);
|
||||
}
|
||||
|
||||
app.initApp();
|
||||
|
||||
// Auto-load scene if provided as argument (editor mode only)
|
||||
if (!sceneFile.empty() && app.getGameMode() == EditorApp::GameMode::Editor) {
|
||||
std::cout << "Auto-loading scene: " << sceneFile << std::endl;
|
||||
SceneSerializer serializer(*app.getWorld(), app.getSceneManager());
|
||||
if (!sceneFile.empty() &&
|
||||
app.getGameMode() == EditorApp::GameMode::Editor) {
|
||||
std::cout << "Auto-loading scene: " << sceneFile
|
||||
<< std::endl;
|
||||
SceneSerializer serializer(*app.getWorld(),
|
||||
app.getSceneManager());
|
||||
if (!serializer.loadFromFile(sceneFile, nullptr)) {
|
||||
std::cerr << "Failed to load scene: " << serializer.getLastError() << std::endl;
|
||||
std::cerr << "Failed to load scene: "
|
||||
<< serializer.getLastError()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,10 +90,10 @@ public:
|
||||
Layers::MOVING; // Non moving only collides with moving
|
||||
case Layers::MOVING:
|
||||
return true; // Moving collides with everything
|
||||
case Layers::SENSORS:
|
||||
return inObject2 ==
|
||||
Layers::MOVING; // Non moving only collides with moving
|
||||
default:
|
||||
case Layers::SENSORS:
|
||||
return inObject2 ==
|
||||
Layers::MOVING; // Non moving only collides with moving
|
||||
default:
|
||||
JPH_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
@@ -110,8 +110,8 @@ public:
|
||||
mObjectToBroadPhase[Layers::NON_MOVING] =
|
||||
BroadPhaseLayers::NON_MOVING;
|
||||
mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
|
||||
mObjectToBroadPhase[Layers::SENSORS] = BroadPhaseLayers::MOVING;
|
||||
}
|
||||
mObjectToBroadPhase[Layers::SENSORS] = BroadPhaseLayers::MOVING;
|
||||
}
|
||||
|
||||
virtual uint GetNumBroadPhaseLayers() const override
|
||||
{
|
||||
@@ -269,13 +269,13 @@ public:
|
||||
void DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo,
|
||||
JPH::ColorArg inColor) override
|
||||
{
|
||||
JPH::Vec4 color = inColor.ToVec4();
|
||||
mLines.push_back(
|
||||
{ { (float)inFrom[0], (float)inFrom[1],
|
||||
(float)inFrom[2] },
|
||||
{ (float)inTo[0], (float)inTo[1], (float)inTo[2] },
|
||||
Ogre::ColourValue(color[0], color[1], color[2],
|
||||
color[3]) });
|
||||
JPH::Vec4 color = inColor.ToVec4();
|
||||
mLines.push_back(
|
||||
{ { (float)inFrom[0], (float)inFrom[1],
|
||||
(float)inFrom[2] },
|
||||
{ (float)inTo[0], (float)inTo[1], (float)inTo[2] },
|
||||
Ogre::ColourValue(color[0], color[1], color[2],
|
||||
color[3]) });
|
||||
}
|
||||
void DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2,
|
||||
JPH::RVec3Arg inV3, JPH::ColorArg inColor,
|
||||
@@ -422,14 +422,14 @@ DebugRenderer::DebugRenderer(Ogre::SceneManager *scnMgr,
|
||||
pass->setCullingMode(Ogre::CullingMode::CULL_NONE);
|
||||
pass->setVertexColourTracking(Ogre::TVC_AMBIENT);
|
||||
pass->setLightingEnabled(false);
|
||||
pass->setDepthWriteEnabled(false);
|
||||
pass->setDepthCheckEnabled(false);
|
||||
pass->setDepthWriteEnabled(false);
|
||||
pass->setDepthCheckEnabled(false);
|
||||
DebugRenderer::Initialize();
|
||||
scnMgr->getRootSceneNode()->attachObject(mObject);
|
||||
mLines.reserve(6000);
|
||||
mObject->estimateVertexCount(64000);
|
||||
mObject->estimateIndexCount(8000);
|
||||
mObject->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
|
||||
mObject->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
|
||||
}
|
||||
DebugRenderer::~DebugRenderer()
|
||||
{
|
||||
@@ -466,7 +466,7 @@ void CompoundShapeBuilder::addShape(JPH::ShapeRefC shape,
|
||||
const Ogre::Vector3 &position,
|
||||
const Ogre::Quaternion &rotation)
|
||||
{
|
||||
shapeSettings.AddShape(JoltPhysics::convert<JPH::Vec3>(position),
|
||||
shapeSettings.AddShape(JoltPhysics::convert<JPH::Vec3>(position),
|
||||
JoltPhysics::convert(rotation), shape.GetPtr());
|
||||
}
|
||||
JPH::ShapeRefC CompoundShapeBuilder::build()
|
||||
@@ -577,13 +577,13 @@ public:
|
||||
, job_system(JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers,
|
||||
std::thread::hardware_concurrency() - 1)
|
||||
, mDebugRenderer(new DebugRenderer(scnMgr, cameraNode))
|
||||
, object_vs_broadphase_layer_filter{}
|
||||
, object_vs_object_layer_filter{}
|
||||
, object_vs_broadphase_layer_filter{}
|
||||
, object_vs_object_layer_filter{}
|
||||
, debugDraw(false)
|
||||
{
|
||||
static int instanceCount = 0;
|
||||
OgreAssert(instanceCount == 0, "Bad initialisation");
|
||||
instanceCount++;
|
||||
static int instanceCount = 0;
|
||||
OgreAssert(instanceCount == 0, "Bad initialisation");
|
||||
instanceCount++;
|
||||
|
||||
// This is the max amount of rigid bodies that you can add to the physics system. If you try to add more you'll get an error.
|
||||
// Note: This value is low because this is a simple test. For a real project use something in the order of 65536.
|
||||
@@ -773,7 +773,7 @@ public:
|
||||
timeAccumulator -= fixedDeltaTime;
|
||||
}
|
||||
for (JPH::BodyID bID : bodies) {
|
||||
JPH::RVec3 p;
|
||||
JPH::RVec3 p;
|
||||
JPH::Quat q;
|
||||
if (id2node.find(bID) == id2node.end())
|
||||
continue;
|
||||
@@ -792,10 +792,12 @@ public:
|
||||
for (JPH::Character *ch : characters) {
|
||||
if (body_interface.IsAdded(ch->GetBodyID())) {
|
||||
ch->PostSimulation(0.1f);
|
||||
Ogre::SceneNode *node = id2node[ch->GetBodyID()];
|
||||
Ogre::SceneNode *node =
|
||||
id2node[ch->GetBodyID()];
|
||||
if (node)
|
||||
node->_setDerivedPosition(
|
||||
JoltPhysics::convert(ch->GetPosition()));
|
||||
JoltPhysics::convert(
|
||||
ch->GetPosition()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,7 +830,7 @@ public:
|
||||
}
|
||||
static JPH::ShapeRefC createBoxShape(float x, float y, float z)
|
||||
{
|
||||
return new JPH::BoxShape(JPH::Vec3(x, y, z));
|
||||
return new JPH::BoxShape(JPH::Vec3(x, y, z));
|
||||
}
|
||||
static JPH::ShapeRefC createCylinderShape(float halfHeight,
|
||||
float radius)
|
||||
@@ -836,7 +838,7 @@ public:
|
||||
return new JPH::CylinderShape(halfHeight, radius);
|
||||
}
|
||||
JPH::BodyCreationSettings createBodyCreationSettings(
|
||||
JPH::Shape *shape, JPH::RVec3 &position, JPH::Quat &rotation,
|
||||
JPH::Shape *shape, JPH::RVec3 &position, JPH::Quat &rotation,
|
||||
JPH::EMotionType motionType, JPH::ObjectLayer layer)
|
||||
{
|
||||
JPH::BodyCreationSettings body_settings(
|
||||
@@ -845,7 +847,7 @@ public:
|
||||
}
|
||||
JPH::BodyCreationSettings
|
||||
createBodyCreationSettings(JPH::ShapeSettings *shapeSettings,
|
||||
JPH::RVec3 &position, JPH::Quat &rotation,
|
||||
JPH::RVec3 &position, JPH::Quat &rotation,
|
||||
JPH::EMotionType motionType,
|
||||
JPH::ObjectLayer layer)
|
||||
{
|
||||
@@ -870,8 +872,8 @@ public:
|
||||
{
|
||||
JPH::BodyInterface &body_interface =
|
||||
physics_system.GetBodyInterface();
|
||||
body_interface.AddAngularImpulse(
|
||||
id, JoltPhysics::convert<JPH::Vec3>(impulse));
|
||||
body_interface.AddAngularImpulse(
|
||||
id, JoltPhysics::convert<JPH::Vec3>(impulse));
|
||||
}
|
||||
JPH::BodyID createBody(const JPH::BodyCreationSettings &settings,
|
||||
ActivationListener *listener = nullptr)
|
||||
@@ -917,14 +919,14 @@ public:
|
||||
msp.ScaleToMass(mass);
|
||||
bodySettings.mMassPropertiesOverride = msp;
|
||||
}
|
||||
JPH::BodyID id = createBody(bodySettings, listener);
|
||||
if (shape->GetType() == JPH::EShapeType::HeightField) {
|
||||
JPH::BodyInterface &body_interface =
|
||||
physics_system.GetBodyInterface();
|
||||
body_interface.SetFriction(id, 1.0f);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
JPH::BodyID id = createBody(bodySettings, listener);
|
||||
if (shape->GetType() == JPH::EShapeType::HeightField) {
|
||||
JPH::BodyInterface &body_interface =
|
||||
physics_system.GetBodyInterface();
|
||||
body_interface.SetFriction(id, 1.0f);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
JPH::BodyID createBody(const JPH::Shape *shape, float mass,
|
||||
Ogre::SceneNode *node, JPH::EMotionType motion,
|
||||
JPH::ObjectLayer layer,
|
||||
@@ -934,8 +936,8 @@ public:
|
||||
const Ogre::Quaternion &rotation =
|
||||
node->_getDerivedOrientation();
|
||||
std::cout << "body position: " << position << std::endl;
|
||||
JPH::BodyID id = createBody(shape, mass, position, rotation,
|
||||
motion, layer, listener);
|
||||
JPH::BodyID id = createBody(shape, mass, position, rotation,
|
||||
motion, layer, listener);
|
||||
id2node[id] = node;
|
||||
node2id[node] = id;
|
||||
return id;
|
||||
@@ -982,7 +984,7 @@ public:
|
||||
JPH::MutableCompoundShape *master =
|
||||
static_cast<JPH::MutableCompoundShape *>(
|
||||
compoundShape.GetPtr());
|
||||
master->AddShape(JoltPhysics::convert<JPH::Vec3>(position),
|
||||
master->AddShape(JoltPhysics::convert<JPH::Vec3>(position),
|
||||
JoltPhysics::convert(rotation),
|
||||
childShape.GetPtr());
|
||||
}
|
||||
@@ -1156,46 +1158,54 @@ public:
|
||||
for (j = 0; j < indices.size() / 3; j++)
|
||||
triangles[j] = { indices[j * 3 + 0], indices[j * 3 + 1],
|
||||
indices[j * 3 + 2] };
|
||||
|
||||
|
||||
// Validate we have enough data
|
||||
if (vertices.size() < 3) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ERROR: Mesh has too few vertices: " + mesh->getName() +
|
||||
" (" + Ogre::StringConverter::toString(vertices.size()) + ")");
|
||||
"ERROR: Mesh has too few vertices: " +
|
||||
mesh->getName() + " (" +
|
||||
Ogre::StringConverter::toString(
|
||||
vertices.size()) +
|
||||
")");
|
||||
return JPH::ShapeRefC();
|
||||
}
|
||||
if (triangles.size() < 1) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ERROR: Mesh has no triangles: " + mesh->getName());
|
||||
"ERROR: Mesh has no triangles: " +
|
||||
mesh->getName());
|
||||
return JPH::ShapeRefC();
|
||||
}
|
||||
|
||||
|
||||
JPH::MeshShapeSettings mesh_shape_settings(vertices, triangles);
|
||||
|
||||
|
||||
// Configure settings to be more permissive
|
||||
mesh_shape_settings.mPerTriangleUserData = false;
|
||||
|
||||
|
||||
// Log mesh stats for debugging
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Creating mesh shape: " + mesh->getName() +
|
||||
" - Vertices: " + Ogre::StringConverter::toString(vertices.size()) +
|
||||
" - Triangles: " + Ogre::StringConverter::toString(triangles.size()));
|
||||
|
||||
"Creating mesh shape: " + mesh->getName() +
|
||||
" - Vertices: " +
|
||||
Ogre::StringConverter::toString(vertices.size()) +
|
||||
" - Triangles: " +
|
||||
Ogre::StringConverter::toString(triangles.size()));
|
||||
|
||||
// Sanitize the mesh data to help with validation
|
||||
mesh_shape_settings.Sanitize();
|
||||
|
||||
|
||||
JPH::ShapeSettings::ShapeResult result =
|
||||
mesh_shape_settings.Create();
|
||||
|
||||
|
||||
if (!result.Get()) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ERROR: Failed to create mesh shape: " + mesh->getName() +
|
||||
"ERROR: Failed to create mesh shape: " +
|
||||
mesh->getName() +
|
||||
" - Error: " + result.GetError().c_str());
|
||||
return JPH::ShapeRefC();
|
||||
}
|
||||
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Successfully created mesh shape for: " + mesh->getName());
|
||||
"Successfully created mesh shape for: " +
|
||||
mesh->getName());
|
||||
return result.Get();
|
||||
}
|
||||
JPH::ShapeRefC createMeshShape(Ogre::String meshName)
|
||||
@@ -1206,7 +1216,7 @@ public:
|
||||
new Ogre::DefaultHardwareBufferManager;
|
||||
p = Ogre::DefaultHardwareBufferManager::getSingletonPtr();
|
||||
}
|
||||
|
||||
|
||||
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
|
||||
meshName, "General");
|
||||
if (mesh.get()) {
|
||||
@@ -1216,7 +1226,7 @@ public:
|
||||
}
|
||||
return createMeshShape(mesh);
|
||||
}
|
||||
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"ERROR: Could not load mesh for collider: " + meshName);
|
||||
return JPH::ShapeRefC();
|
||||
@@ -1361,9 +1371,9 @@ public:
|
||||
{
|
||||
int i;
|
||||
JPH::HeightFieldShapeSettings heightfieldSettings(
|
||||
samples, JoltPhysics::convert<JPH::Vec3>(offset),
|
||||
JoltPhysics::convert<JPH::Vec3>(scale),
|
||||
(uint32_t)sampleCount);
|
||||
samples, JoltPhysics::convert<JPH::Vec3>(offset),
|
||||
JoltPhysics::convert<JPH::Vec3>(scale),
|
||||
(uint32_t)sampleCount);
|
||||
for (i = 0; i < sampleCount; i++) {
|
||||
memcpy(heightfieldSettings.mHeightSamples.data() +
|
||||
sampleCount * i,
|
||||
@@ -1386,10 +1396,10 @@ public:
|
||||
"bad parameters");
|
||||
JPH::MutableCompoundShapeSettings settings;
|
||||
for (i = 0; i < shapes.size(); i++)
|
||||
settings.AddShape(
|
||||
JoltPhysics::convert<JPH::Vec3>(positions[i]),
|
||||
JoltPhysics::convert(rotations[i]),
|
||||
shapes[i].GetPtr());
|
||||
settings.AddShape(
|
||||
JoltPhysics::convert<JPH::Vec3>(positions[i]),
|
||||
JoltPhysics::convert(rotations[i]),
|
||||
shapes[i].GetPtr());
|
||||
JPH::ShapeSettings::ShapeResult result = settings.Create();
|
||||
OgreAssert(result.Get(), "Can not create compound shape");
|
||||
return result.Get();
|
||||
@@ -1405,10 +1415,10 @@ public:
|
||||
"bad parameters");
|
||||
JPH::StaticCompoundShapeSettings settings;
|
||||
for (i = 0; i < shapes.size(); i++)
|
||||
settings.AddShape(
|
||||
JoltPhysics::convert<JPH::Vec3>(positions[i]),
|
||||
JoltPhysics::convert(rotations[i]),
|
||||
shapes[i].GetPtr());
|
||||
settings.AddShape(
|
||||
JoltPhysics::convert<JPH::Vec3>(positions[i]),
|
||||
JoltPhysics::convert(rotations[i]),
|
||||
shapes[i].GetPtr());
|
||||
JPH::ShapeSettings::ShapeResult result = settings.Create();
|
||||
OgreAssert(result.Get(), "Can not create compound shape");
|
||||
return result.Get();
|
||||
@@ -1418,24 +1428,24 @@ public:
|
||||
JPH::ShapeRefC shape)
|
||||
{
|
||||
JPH::OffsetCenterOfMassShapeSettings settings(
|
||||
JoltPhysics::convert<JPH::Vec3>(offset),
|
||||
shape.GetPtr());
|
||||
JoltPhysics::convert<JPH::Vec3>(offset),
|
||||
shape.GetPtr());
|
||||
JPH::ShapeSettings::ShapeResult result = settings.Create();
|
||||
OgreAssert(result.Get(), "Can not create com offset shape");
|
||||
return result.Get();
|
||||
}
|
||||
JPH::ShapeRefC
|
||||
createRotatedTranslatedShape(const Ogre::Vector3 &offset,
|
||||
const Ogre::Quaternion rotation,
|
||||
JPH::ShapeRefC shape)
|
||||
{
|
||||
return JPH::RotatedTranslatedShapeSettings(
|
||||
JoltPhysics::convert<JPH::Vec3>(offset),
|
||||
JoltPhysics::convert(rotation), shape)
|
||||
.Create()
|
||||
.Get();
|
||||
}
|
||||
void applyBuoyancyImpulse(JPH::BodyID id,
|
||||
JPH::ShapeRefC
|
||||
createRotatedTranslatedShape(const Ogre::Vector3 &offset,
|
||||
const Ogre::Quaternion rotation,
|
||||
JPH::ShapeRefC shape)
|
||||
{
|
||||
return JPH::RotatedTranslatedShapeSettings(
|
||||
JoltPhysics::convert<JPH::Vec3>(offset),
|
||||
JoltPhysics::convert(rotation), shape)
|
||||
.Create()
|
||||
.Get();
|
||||
}
|
||||
void applyBuoyancyImpulse(JPH::BodyID id,
|
||||
const Ogre::Vector3 &surfacePosition,
|
||||
const Ogre::Vector3 &surfaceNormal,
|
||||
float buoyancy, float linearDrag,
|
||||
@@ -1446,12 +1456,12 @@ public:
|
||||
JPH::BodyLockWrite lock(physics_system.GetBodyLockInterface(),
|
||||
id);
|
||||
JPH::Body &body = lock.GetBody();
|
||||
body.ApplyBuoyancyImpulse(
|
||||
JoltPhysics::convert(surfacePosition),
|
||||
JoltPhysics::convert<JPH::Vec3>(surfaceNormal),
|
||||
buoyancy, linearDrag, angularDrag,
|
||||
JoltPhysics::convert<JPH::Vec3>(fluidVelocity),
|
||||
JoltPhysics::convert<JPH::Vec3>(gravity), dt);
|
||||
body.ApplyBuoyancyImpulse(
|
||||
JoltPhysics::convert(surfacePosition),
|
||||
JoltPhysics::convert<JPH::Vec3>(surfaceNormal),
|
||||
buoyancy, linearDrag, angularDrag,
|
||||
JoltPhysics::convert<JPH::Vec3>(fluidVelocity),
|
||||
JoltPhysics::convert<JPH::Vec3>(gravity), dt);
|
||||
}
|
||||
void applyBuoyancyImpulse(JPH::BodyID id,
|
||||
const Ogre::Vector3 &surfacePosition,
|
||||
@@ -1463,12 +1473,12 @@ public:
|
||||
JPH::BodyLockWrite lock(physics_system.GetBodyLockInterface(),
|
||||
id);
|
||||
JPH::Body &body = lock.GetBody();
|
||||
body.ApplyBuoyancyImpulse(
|
||||
JoltPhysics::convert(surfacePosition),
|
||||
JoltPhysics::convert<JPH::Vec3>(surfaceNormal),
|
||||
buoyancy, linearDrag, angularDrag,
|
||||
JoltPhysics::convert<JPH::Vec3>(fluidVelocity),
|
||||
physics_system.GetGravity(), dt);
|
||||
body.ApplyBuoyancyImpulse(
|
||||
JoltPhysics::convert(surfacePosition),
|
||||
JoltPhysics::convert<JPH::Vec3>(surfaceNormal),
|
||||
buoyancy, linearDrag, angularDrag,
|
||||
JoltPhysics::convert<JPH::Vec3>(fluidVelocity),
|
||||
physics_system.GetGravity(), dt);
|
||||
}
|
||||
bool isActive(JPH::BodyID id)
|
||||
{
|
||||
@@ -1491,7 +1501,7 @@ public:
|
||||
void getPositionAndRotation(JPH::BodyID id, Ogre::Vector3 &position,
|
||||
Ogre::Quaternion &rotation)
|
||||
{
|
||||
JPH::RVec3 _position;
|
||||
JPH::RVec3 _position;
|
||||
JPH::Quat _rotation;
|
||||
physics_system.GetBodyInterface().GetPositionAndRotation(
|
||||
id, _position, _rotation);
|
||||
@@ -1552,20 +1562,23 @@ public:
|
||||
}
|
||||
void setGravityFactor(JPH::BodyID id, float factor)
|
||||
{
|
||||
return physics_system.GetBodyInterface().SetGravityFactor(id,
|
||||
factor);
|
||||
return physics_system.GetBodyInterface().SetGravityFactor(
|
||||
id, factor);
|
||||
}
|
||||
void broadphaseQuery(float dt, const Ogre::Vector3 &position,
|
||||
std::set<JPH::BodyID> &inWater)
|
||||
{
|
||||
JPH::RVec3 surface_point = JoltPhysics::convert(
|
||||
JPH::RVec3 surface_point = JoltPhysics::convert(
|
||||
position + Ogre::Vector3(0, -0.1f, 0));
|
||||
|
||||
MyCollector collector(&physics_system, surface_point,
|
||||
JPH::Vec3::sAxisY(), dt);
|
||||
JPH::Vec3::sAxisY(), dt);
|
||||
// Apply buoyancy to all bodies that intersect with the water
|
||||
JPH::AABox water_box(-JPH::Vec3(1000, 1000, 1000),
|
||||
JPH::Vec3(1000, 0.1f, 1000));
|
||||
// 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));
|
||||
water_box.Translate(JPH::Vec3(surface_point));
|
||||
physics_system.GetBroadPhaseQuery().CollideAABox(
|
||||
water_box, collector,
|
||||
@@ -1584,36 +1597,43 @@ public:
|
||||
}
|
||||
}
|
||||
bool raycastQuery(Ogre::Vector3 startPoint, Ogre::Vector3 endPoint,
|
||||
Ogre::Vector3 &position, JPH::BodyID &id)
|
||||
Ogre::Vector3 &position, JPH::BodyID &id)
|
||||
{
|
||||
int i;
|
||||
Ogre::Vector3 direction = endPoint - startPoint;
|
||||
JPH::RRayCast ray{ JoltPhysics::convert(startPoint),
|
||||
JoltPhysics::convert<JPH::Vec3>(direction) };
|
||||
JoltPhysics::convert<JPH::Vec3>(direction) };
|
||||
JPH::RayCastResult hit;
|
||||
bool hadHit = physics_system.GetNarrowPhaseQuery().CastRay(
|
||||
ray, hit, {},
|
||||
JPH::SpecifiedObjectLayerFilter(Layers::NON_MOVING));
|
||||
if (hadHit) {
|
||||
if (hadHit) {
|
||||
position = JoltPhysics::convert(
|
||||
ray.GetPointOnRay(hit.mFraction));
|
||||
id = hit.mBodyID;
|
||||
}
|
||||
id = hit.mBodyID;
|
||||
}
|
||||
return hadHit;
|
||||
}
|
||||
bool bodyIsCharacter(JPH::BodyID id) const
|
||||
{
|
||||
return characterBodies.find(id) != characterBodies.end();
|
||||
}
|
||||
void destroyCharacter(std::shared_ptr<JPH::Character> ch)
|
||||
{
|
||||
characterBodies.erase(characterBodies.find(ch->GetBodyID()));
|
||||
characters.erase(ch.get());
|
||||
Ogre::SceneNode *node = id2node[ch->GetBodyID()];
|
||||
id2node.erase(ch->GetBodyID());
|
||||
node2id.erase(node);
|
||||
ch = nullptr;
|
||||
}
|
||||
bool bodyIsCharacter(JPH::BodyID id) const
|
||||
{
|
||||
return characterBodies.find(id) != characterBodies.end();
|
||||
}
|
||||
void destroyCharacter(std::shared_ptr<JPH::Character> ch)
|
||||
{
|
||||
characterBodies.erase(characterBodies.find(ch->GetBodyID()));
|
||||
characters.erase(ch.get());
|
||||
Ogre::SceneNode *node = id2node[ch->GetBodyID()];
|
||||
id2node.erase(ch->GetBodyID());
|
||||
node2id.erase(node);
|
||||
ch = nullptr;
|
||||
}
|
||||
Ogre::SceneNode *getSceneNodeFromBodyID(JPH::BodyID id) const
|
||||
{
|
||||
auto it = id2node.find(id);
|
||||
if (it != id2node.end())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
void physics()
|
||||
@@ -1631,20 +1651,20 @@ JoltPhysicsWrapper::JoltPhysicsWrapper(Ogre::SceneManager *scnMgr,
|
||||
// This needs to be done before any other Jolt function is called.
|
||||
JPH::RegisterDefaultAllocator();
|
||||
|
||||
// Install trace and assert callbacks
|
||||
// Install trace and assert callbacks
|
||||
JPH::Trace = TraceImpl;
|
||||
JPH_IF_ENABLE_ASSERTS(JPH::AssertFailed = AssertFailedImpl;)
|
||||
|
||||
// Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data.
|
||||
// It is not directly used in this example but still required.
|
||||
// Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data.
|
||||
// It is not directly used in this example but still required.
|
||||
JPH::Factory::sInstance = new JPH::Factory();
|
||||
// Register all physics types with the factory and install their collision handlers with the CollisionDispatch class.
|
||||
// If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function.
|
||||
// If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you.
|
||||
// Register all physics types with the factory and install their collision handlers with the CollisionDispatch class.
|
||||
// If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function.
|
||||
// If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you.
|
||||
JPH::RegisterTypes();
|
||||
|
||||
phys = std::make_unique<Physics>(scnMgr, cameraNode, nullptr,
|
||||
&contacts);
|
||||
phys = std::make_unique<Physics>(scnMgr, cameraNode, nullptr,
|
||||
&contacts);
|
||||
}
|
||||
|
||||
JoltPhysicsWrapper::~JoltPhysicsWrapper()
|
||||
@@ -1679,7 +1699,7 @@ JPH::ShapeRefC JoltPhysicsWrapper::createSphereShape(float radius)
|
||||
}
|
||||
|
||||
JPH::ShapeRefC JoltPhysicsWrapper::createCapsuleShape(float halfHeight,
|
||||
float radius)
|
||||
float radius)
|
||||
{
|
||||
return phys->createCapsuleShape(halfHeight, radius);
|
||||
}
|
||||
@@ -1739,14 +1759,14 @@ JPH::ShapeRefC
|
||||
JoltPhysicsWrapper::createOffsetCenterOfMassShape(const Ogre::Vector3 &offset,
|
||||
JPH::ShapeRefC shape)
|
||||
{
|
||||
return phys->createOffsetCenterOfMassShape(offset, shape);
|
||||
return phys->createOffsetCenterOfMassShape(offset, shape);
|
||||
}
|
||||
|
||||
JPH::ShapeRefC JoltPhysicsWrapper::createRotatedTranslatedShape(
|
||||
const Ogre::Vector3 &offset, const Ogre::Quaternion rotation,
|
||||
JPH::ShapeRefC shape)
|
||||
const Ogre::Vector3 &offset, const Ogre::Quaternion rotation,
|
||||
JPH::ShapeRefC shape)
|
||||
{
|
||||
return phys->createRotatedTranslatedShape(offset, rotation, shape);
|
||||
return phys->createRotatedTranslatedShape(offset, rotation, shape);
|
||||
}
|
||||
|
||||
JPH::BodyID
|
||||
@@ -1936,19 +1956,26 @@ void JoltPhysicsWrapper::removeContactListener(const JPH::BodyID &id)
|
||||
}
|
||||
bool JoltPhysicsWrapper::raycastQuery(Ogre::Vector3 startPoint,
|
||||
Ogre::Vector3 endPoint,
|
||||
Ogre::Vector3 &position, JPH::BodyID &id)
|
||||
Ogre::Vector3 &position, JPH::BodyID &id)
|
||||
{
|
||||
return phys->raycastQuery(startPoint, endPoint, position, id);
|
||||
return phys->raycastQuery(startPoint, endPoint, position, id);
|
||||
}
|
||||
|
||||
bool JoltPhysicsWrapper::bodyIsCharacter(JPH::BodyID id) const
|
||||
{
|
||||
return phys->bodyIsCharacter(id);
|
||||
return phys->bodyIsCharacter(id);
|
||||
}
|
||||
|
||||
void JoltPhysicsWrapper::destroyCharacter(std::shared_ptr<JPH::Character> ch)
|
||||
{
|
||||
phys->destroyCharacter(ch);
|
||||
phys->destroyCharacter(ch);
|
||||
}
|
||||
|
||||
Ogre::SceneNode *
|
||||
JoltPhysicsWrapper::getSceneNodeFromBodyID(JPH::BodyID id) const
|
||||
{
|
||||
return phys->getSceneNodeFromBodyID(id);
|
||||
}
|
||||
|
||||
template <>
|
||||
JoltPhysicsWrapper *Ogre::Singleton<JoltPhysicsWrapper>::msSingleton = 0;
|
||||
|
||||
@@ -45,14 +45,13 @@ static constexpr uint NUM_LAYERS(2);
|
||||
|
||||
namespace JoltPhysics
|
||||
{
|
||||
template<class T>
|
||||
Ogre::Vector3 convert(const T &vec)
|
||||
template <class T> Ogre::Vector3 convert(const T &vec)
|
||||
{
|
||||
return {vec[0], vec[1], vec[2]};
|
||||
return { vec[0], vec[1], vec[2] };
|
||||
}
|
||||
template<class T> T convert(const Ogre::Vector3 &vec)
|
||||
template <class T> T convert(const Ogre::Vector3 &vec)
|
||||
{
|
||||
return { vec.x, vec.y, vec.z };
|
||||
return { vec.x, vec.y, vec.z };
|
||||
}
|
||||
Ogre::Vector3 convert(const JPH::RVec3Arg &vec);
|
||||
JPH::RVec3 convert(const Ogre::Vector3 &vec);
|
||||
@@ -146,10 +145,11 @@ public:
|
||||
JPH::ShapeRefC
|
||||
createOffsetCenterOfMassShape(const Ogre::Vector3 &offset,
|
||||
JPH::ShapeRefC shape);
|
||||
JPH::ShapeRefC
|
||||
createRotatedTranslatedShape(const Ogre::Vector3 &offset, const Ogre::Quaternion rotation,
|
||||
JPH::ShapeRefC shape);
|
||||
JPH::BodyID createBody(const JPH::BodyCreationSettings &settings);
|
||||
JPH::ShapeRefC
|
||||
createRotatedTranslatedShape(const Ogre::Vector3 &offset,
|
||||
const Ogre::Quaternion rotation,
|
||||
JPH::ShapeRefC shape);
|
||||
JPH::BodyID createBody(const JPH::BodyCreationSettings &settings);
|
||||
JPH::BodyID createBody(const JPH::Shape *shape, float mass,
|
||||
const Ogre::Vector3 &position,
|
||||
const Ogre::Quaternion &rotation,
|
||||
@@ -226,8 +226,9 @@ public:
|
||||
listener);
|
||||
void removeContactListener(const JPH::BodyID &id);
|
||||
bool raycastQuery(Ogre::Vector3 startPoint, Ogre::Vector3 endPoint,
|
||||
Ogre::Vector3 &position, JPH::BodyID &id);
|
||||
bool bodyIsCharacter(JPH::BodyID id) const;
|
||||
void destroyCharacter(std::shared_ptr<JPH::Character> ch);
|
||||
Ogre::Vector3 &position, JPH::BodyID &id);
|
||||
bool bodyIsCharacter(JPH::BodyID id) const;
|
||||
void destroyCharacter(std::shared_ptr<JPH::Character> ch);
|
||||
Ogre::SceneNode *getSceneNodeFromBodyID(JPH::BodyID id) const;
|
||||
};
|
||||
#endif
|
||||
|
||||
328
src/features/editScene/systems/BuoyancySystem.cpp
Normal file
328
src/features/editScene/systems/BuoyancySystem.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
#include "BuoyancySystem.hpp"
|
||||
#include "../components/EditorMarker.hpp"
|
||||
#include "../components/EntityName.hpp"
|
||||
#include "../components/Character.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <set>
|
||||
|
||||
BuoyancySystem::BuoyancySystem(flecs::world &world, JoltPhysicsWrapper *physics)
|
||||
: m_world(world)
|
||||
, m_physics(physics)
|
||||
, m_buoyancyInfoQuery(world.query<BuoyancyInfo>())
|
||||
{
|
||||
// Create default WaterPhysics entity if none exists
|
||||
bool hasWaterPhysics = false;
|
||||
world.query<WaterPhysics>().each(
|
||||
[&](flecs::entity, WaterPhysics &) { hasWaterPhysics = true; });
|
||||
|
||||
if (!hasWaterPhysics) {
|
||||
flecs::entity waterEntity = world.entity("WaterPhysics");
|
||||
waterEntity.set<WaterPhysics>({});
|
||||
waterEntity.add<EditorMarkerComponent>();
|
||||
waterEntity.set<EntityNameComponent>(
|
||||
EntityNameComponent("Water Physics"));
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"BuoyancySystem: Created default WaterPhysics entity");
|
||||
}
|
||||
}
|
||||
|
||||
void BuoyancySystem::initialize()
|
||||
{
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Buoyancy system initialized");
|
||||
}
|
||||
|
||||
void BuoyancySystem::update(float deltaTime)
|
||||
{
|
||||
// Check if there's a WaterPhysics component in the world
|
||||
WaterPhysics *waterPhysics = nullptr;
|
||||
m_world.query<WaterPhysics>().each(
|
||||
[&](flecs::entity entity, WaterPhysics &wp) {
|
||||
waterPhysics = ℘
|
||||
});
|
||||
|
||||
if (!waterPhysics) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"BuoyancySystem: No WaterPhysics component found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!waterPhysics->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_physics || deltaTime <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Rebuild scene node -> entity cache each frame
|
||||
// This ensures we have up-to-date mappings even if entities are added/removed
|
||||
m_nodeToEntity.clear();
|
||||
m_world.query<TransformComponent>().each(
|
||||
[&](flecs::entity entity, TransformComponent &transform) {
|
||||
if (transform.node) {
|
||||
m_nodeToEntity[transform.node] = entity.id();
|
||||
}
|
||||
});
|
||||
|
||||
// Clear previous frame's water bodies
|
||||
m_bodiesInWater.clear();
|
||||
|
||||
// Perform broadphase query to find bodies in water
|
||||
// Use water surface position from WaterPhysics component, camera XZ position
|
||||
Ogre::Vector3 waterSurfacePos(m_cameraPosition.x,
|
||||
waterPhysics->waterSurfaceY,
|
||||
m_cameraPosition.z);
|
||||
m_physics->broadphaseQuery(deltaTime, waterSurfacePos, m_bodiesInWater);
|
||||
|
||||
// Debug logging if enabled
|
||||
if (m_debugEnabled) {
|
||||
debugLogWaterDetection(waterSurfacePos);
|
||||
}
|
||||
|
||||
// Build a set of entity IDs that have BuoyancyInfo for quick lookup
|
||||
std::set<flecs::entity_t> entitiesWithBuoyancyInfo;
|
||||
m_buoyancyInfoQuery.each(
|
||||
[&](flecs::entity entity, BuoyancyInfo &buoyancyInfo) {
|
||||
if (buoyancyInfo.enabled) {
|
||||
entitiesWithBuoyancyInfo.insert(entity.id());
|
||||
}
|
||||
});
|
||||
|
||||
// Apply buoyancy to all bodies found in water
|
||||
for (JPH::BodyID bodyID : m_bodiesInWater) {
|
||||
// Try to find the entity associated with this body
|
||||
Ogre::SceneNode *node =
|
||||
m_physics->getSceneNodeFromBodyID(bodyID);
|
||||
if (!node) {
|
||||
// No scene node found for this body - apply default buoyancy
|
||||
applyBuoyancyToBody(bodyID, deltaTime, nullptr,
|
||||
waterPhysics);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find entity from scene node
|
||||
auto it = m_nodeToEntity.find(node);
|
||||
if (it == m_nodeToEntity.end()) {
|
||||
// No entity found for this scene node - apply default buoyancy
|
||||
applyBuoyancyToBody(bodyID, deltaTime, nullptr,
|
||||
waterPhysics);
|
||||
continue;
|
||||
}
|
||||
|
||||
flecs::entity entity = m_world.entity(it->second);
|
||||
if (!entity.is_valid()) {
|
||||
applyBuoyancyToBody(bodyID, deltaTime, nullptr,
|
||||
waterPhysics);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Debug logging for buoyancy application
|
||||
if (m_debugEnabled) {
|
||||
Ogre::Vector3 bodyPos = m_physics->getPosition(bodyID);
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"BuoyancySystem: Applying buoyancy to body " +
|
||||
Ogre::StringConverter::toString(
|
||||
bodyID.GetIndex()) +
|
||||
" at position " +
|
||||
Ogre::StringConverter::toString(bodyPos) +
|
||||
" (character: " +
|
||||
Ogre::StringConverter::toString(
|
||||
entity.has<CharacterComponent>()) +
|
||||
")");
|
||||
}
|
||||
|
||||
// Check if entity has BuoyancyInfo component
|
||||
if (entitiesWithBuoyancyInfo.find(entity.id()) !=
|
||||
entitiesWithBuoyancyInfo.end()) {
|
||||
const BuoyancyInfo *buoyancyInfo =
|
||||
entity.try_get<BuoyancyInfo>();
|
||||
if (buoyancyInfo && buoyancyInfo->enabled) {
|
||||
applyBuoyancyToBody(bodyID, deltaTime,
|
||||
buoyancyInfo, waterPhysics);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Entity found but no BuoyancyInfo - apply default buoyancy
|
||||
applyBuoyancyToBody(bodyID, deltaTime, nullptr, waterPhysics);
|
||||
}
|
||||
}
|
||||
|
||||
void BuoyancySystem::applyBuoyancyToBody(JPH::BodyID bodyID, float deltaTime,
|
||||
const BuoyancyInfo *buoyancyInfo,
|
||||
const WaterPhysics *waterPhysics)
|
||||
{
|
||||
if (!m_physics || !m_physics->isActive(bodyID) || !waterPhysics) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get water surface parameters from WaterPhysics component
|
||||
float waterSurfaceY = waterPhysics->waterSurfaceY;
|
||||
float buoyancy = waterPhysics->defaultBuoyancy;
|
||||
float linearDrag = waterPhysics->defaultLinearDrag;
|
||||
float angularDrag = waterPhysics->defaultAngularDrag;
|
||||
float submergedThreshold = waterPhysics->defaultSubmergedThreshold;
|
||||
|
||||
// Override with per-entity settings if available
|
||||
if (buoyancyInfo) {
|
||||
if (buoyancyInfo->useCustomWaterLevel) {
|
||||
waterSurfaceY = buoyancyInfo->waterSurfaceY;
|
||||
}
|
||||
buoyancy = buoyancyInfo->buoyancy;
|
||||
linearDrag = buoyancyInfo->linearDrag;
|
||||
angularDrag = buoyancyInfo->angularDrag;
|
||||
submergedThreshold = buoyancyInfo->submergedThreshold;
|
||||
}
|
||||
|
||||
// Check if body is sufficiently submerged
|
||||
if (!isBodySubmerged(bodyID, waterSurfaceY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get body position
|
||||
Ogre::Vector3 bodyPos = m_physics->getPosition(bodyID);
|
||||
|
||||
// Calculate submerged depth (how far below water surface)
|
||||
float submergedDepth = waterSurfaceY - bodyPos.y;
|
||||
if (submergedDepth <= 0.0f) {
|
||||
return; // Not submerged
|
||||
}
|
||||
|
||||
// Simple approximation: use body's bounding sphere radius
|
||||
// In a more advanced implementation, we would calculate actual submerged volume
|
||||
Ogre::Vector3 surfacePosition(bodyPos.x, waterSurfaceY, bodyPos.z);
|
||||
Ogre::Vector3 surfaceNormal(0.0f, 1.0f, 0.0f); // Water surface faces up
|
||||
Ogre::Vector3 fluidVelocity(0.0f, 0.0f, 0.0f); // Still water for now
|
||||
Ogre::Vector3 gravity(0.0f, -waterPhysics->gravity, 0.0f);
|
||||
|
||||
// Apply buoyancy impulse
|
||||
m_physics->applyBuoyancyImpulse(bodyID, surfacePosition, surfaceNormal,
|
||||
buoyancy, linearDrag, angularDrag,
|
||||
fluidVelocity, gravity, deltaTime);
|
||||
}
|
||||
|
||||
bool BuoyancySystem::isBodySubmerged(JPH::BodyID bodyID,
|
||||
float waterSurfaceY) const
|
||||
{
|
||||
if (!m_physics) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get body position
|
||||
Ogre::Vector3 bodyPos = m_physics->getPosition(bodyID);
|
||||
|
||||
// Simple check: body is submerged if its Y position is below water surface
|
||||
// In a more complete implementation, we would check the actual submerged volume
|
||||
// or use Jolt's submerged shape query
|
||||
return bodyPos.y < waterSurfaceY;
|
||||
}
|
||||
|
||||
flecs::entity BuoyancySystem::findEntityFromSceneNode(Ogre::SceneNode *node)
|
||||
{
|
||||
auto it = m_nodeToEntity.find(node);
|
||||
if (it != m_nodeToEntity.end()) {
|
||||
return m_world.entity(it->second);
|
||||
}
|
||||
return flecs::entity();
|
||||
}
|
||||
|
||||
void BuoyancySystem::debugLogWaterDetection(const Ogre::Vector3 &waterSurfacePos)
|
||||
{
|
||||
static int frameCounter = 0;
|
||||
frameCounter++;
|
||||
|
||||
// Log every 60 frames (about 1 second at 60 FPS)
|
||||
if (frameCounter % 60 != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"=== Buoyancy System Debug Frame " +
|
||||
Ogre::StringConverter::toString(frameCounter) + " ===");
|
||||
|
||||
// Log water physics state
|
||||
WaterPhysics *waterPhysics = nullptr;
|
||||
m_world.query<WaterPhysics>().each(
|
||||
[&](flecs::entity entity, WaterPhysics &wp) {
|
||||
waterPhysics = ℘
|
||||
});
|
||||
|
||||
if (waterPhysics) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"WaterPhysics: surfaceY=" +
|
||||
Ogre::StringConverter::toString(
|
||||
waterPhysics->waterSurfaceY) +
|
||||
", enabled=" +
|
||||
Ogre::StringConverter::toString(waterPhysics->enabled) +
|
||||
", buoyancy=" +
|
||||
Ogre::StringConverter::toString(
|
||||
waterPhysics->defaultBuoyancy));
|
||||
} else {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"WaterPhysics: NOT FOUND");
|
||||
}
|
||||
|
||||
// Log camera position
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Camera position: " +
|
||||
Ogre::StringConverter::toString(m_cameraPosition));
|
||||
|
||||
// Log water surface position used for detection
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Water detection center: " +
|
||||
Ogre::StringConverter::toString(waterSurfacePos));
|
||||
|
||||
// Log all characters and their positions
|
||||
int characterCount = 0;
|
||||
m_world.query<CharacterComponent, TransformComponent>().each(
|
||||
[&](flecs::entity entity, CharacterComponent &cc,
|
||||
TransformComponent &transform) {
|
||||
characterCount++;
|
||||
Ogre::Vector3 worldPos =
|
||||
transform.node ?
|
||||
transform.node->_getDerivedPosition() :
|
||||
Ogre::Vector3::ZERO;
|
||||
|
||||
// Check if character is in water (simple Y position check)
|
||||
bool inWater =
|
||||
waterPhysics &&
|
||||
(worldPos.y < waterPhysics->waterSurfaceY);
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Character " +
|
||||
Ogre::StringConverter::toString(entity.id()) +
|
||||
": pos=" +
|
||||
Ogre::StringConverter::toString(worldPos) +
|
||||
", inWater=" +
|
||||
Ogre::StringConverter::toString(inWater) +
|
||||
", enabled=" +
|
||||
Ogre::StringConverter::toString(cc.enabled));
|
||||
});
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Total characters: " +
|
||||
Ogre::StringConverter::toString(characterCount));
|
||||
|
||||
// Log bodies detected in water by broadphase query
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Bodies in water (broadphase): " +
|
||||
Ogre::StringConverter::toString(m_bodiesInWater.size()));
|
||||
|
||||
for (JPH::BodyID bodyID : m_bodiesInWater) {
|
||||
Ogre::Vector3 bodyPos = m_physics->getPosition(bodyID);
|
||||
bool isChar = m_physics->bodyIsCharacter(bodyID);
|
||||
float gravityFactor = m_physics->getGravityFactor(bodyID);
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
" Body " +
|
||||
Ogre::StringConverter::toString(bodyID.GetIndex()) +
|
||||
": pos=" + Ogre::StringConverter::toString(bodyPos) +
|
||||
", isCharacter=" +
|
||||
Ogre::StringConverter::toString(isChar) +
|
||||
", gravityFactor=" +
|
||||
Ogre::StringConverter::toString(gravityFactor));
|
||||
}
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage("=== End Debug Frame ===");
|
||||
}
|
||||
98
src/features/editScene/systems/BuoyancySystem.hpp
Normal file
98
src/features/editScene/systems/BuoyancySystem.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#ifndef EDITSCENE_BUOYANCYSYSTEM_HPP
|
||||
#define EDITSCENE_BUOYANCYSYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "../physics/physics.h"
|
||||
#include "../components/BuoyancyInfo.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
|
||||
/**
|
||||
* Buoyancy system for water physics
|
||||
* Applies buoyancy forces to entities submerged in water
|
||||
* Uses broadphase queries to detect submerged bodies
|
||||
*
|
||||
* The system handles all dynamic physics bodies (rigid bodies, characters, etc.)
|
||||
* by mapping JPH::BodyID to flecs::entity via scene node lookup.
|
||||
* If an entity has a BuoyancyInfo component, its settings are used.
|
||||
* Otherwise, default settings from WaterPhysics component are applied.
|
||||
*/
|
||||
class BuoyancySystem {
|
||||
public:
|
||||
BuoyancySystem(flecs::world &world, JoltPhysicsWrapper *physics);
|
||||
~BuoyancySystem() = default;
|
||||
|
||||
// Initialize buoyancy system
|
||||
void initialize();
|
||||
|
||||
// Update buoyancy forces
|
||||
void update(float deltaTime);
|
||||
|
||||
// Enable/disable buoyancy system
|
||||
void setEnabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
bool isEnabled() const
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
// Enable/disable debug mode
|
||||
void setDebugEnabled(bool enabled)
|
||||
{
|
||||
m_debugEnabled = enabled;
|
||||
}
|
||||
bool isDebugEnabled() const
|
||||
{
|
||||
return m_debugEnabled;
|
||||
}
|
||||
|
||||
// Set camera position for water detection area
|
||||
void setCameraPosition(const Ogre::Vector3 &cameraPos)
|
||||
{
|
||||
m_cameraPosition = cameraPos;
|
||||
}
|
||||
|
||||
private:
|
||||
// Apply buoyancy to a specific body
|
||||
void applyBuoyancyToBody(JPH::BodyID bodyID, float deltaTime,
|
||||
const BuoyancyInfo *buoyancyInfo,
|
||||
const WaterPhysics *waterPhysics);
|
||||
|
||||
// Check if a body is submerged based on its position
|
||||
bool isBodySubmerged(JPH::BodyID bodyID, float waterSurfaceY) const;
|
||||
|
||||
// Find entity from scene node (cached lookup)
|
||||
flecs::entity findEntityFromSceneNode(Ogre::SceneNode *node);
|
||||
|
||||
// Debug logging
|
||||
void debugLogWaterDetection(const Ogre::Vector3 &waterSurfacePos);
|
||||
|
||||
flecs::world &m_world;
|
||||
JoltPhysicsWrapper *m_physics;
|
||||
|
||||
// System enabled state
|
||||
bool m_enabled = true;
|
||||
|
||||
// Debug mode
|
||||
bool m_debugEnabled = false;
|
||||
|
||||
// Camera position for water detection area
|
||||
Ogre::Vector3 m_cameraPosition = Ogre::Vector3::ZERO;
|
||||
|
||||
// Bodies currently in water (cached from broadphase query)
|
||||
std::set<JPH::BodyID> m_bodiesInWater;
|
||||
|
||||
// Cache for scene node -> entity mapping
|
||||
std::unordered_map<Ogre::SceneNode *, flecs::entity_t> m_nodeToEntity;
|
||||
|
||||
// Query for entities with BuoyancyInfo component
|
||||
flecs::query<BuoyancyInfo> m_buoyancyInfoQuery;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_BUOYANCYSYSTEM_HPP
|
||||
@@ -30,8 +30,8 @@ void CharacterSystem::initialize()
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
JPH::ShapeRefC CharacterSystem::createColliderShape(
|
||||
PhysicsColliderComponent &collider)
|
||||
JPH::ShapeRefC
|
||||
CharacterSystem::createColliderShape(PhysicsColliderComponent &collider)
|
||||
{
|
||||
if (!collider.shapeDirty && collider.shape)
|
||||
return collider.shape;
|
||||
@@ -55,10 +55,12 @@ JPH::ShapeRefC CharacterSystem::createColliderShape(
|
||||
case PhysicsColliderComponent::ShapeType::Mesh: {
|
||||
if (!collider.meshName.empty()) {
|
||||
try {
|
||||
result = m_physics->createMeshShape(collider.meshName);
|
||||
result = m_physics->createMeshShape(
|
||||
collider.meshName);
|
||||
} catch (...) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Failed to create mesh shape: " + collider.meshName);
|
||||
"Failed to create mesh shape: " +
|
||||
collider.meshName);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -79,8 +81,8 @@ JPH::ShapeRefC CharacterSystem::createColliderShape(
|
||||
}
|
||||
|
||||
if (!result)
|
||||
result = m_physics->createBoxShape(Ogre::Vector3(0.1f, 0.1f,
|
||||
0.1f));
|
||||
result = m_physics->createBoxShape(
|
||||
Ogre::Vector3(0.1f, 0.1f, 0.1f));
|
||||
|
||||
if (collider.offset != Ogre::Vector3::ZERO ||
|
||||
collider.rotationOffset != Ogre::Quaternion::IDENTITY)
|
||||
@@ -93,7 +95,7 @@ JPH::ShapeRefC CharacterSystem::createColliderShape(
|
||||
}
|
||||
|
||||
JPH::ShapeRefC CharacterSystem::buildCharacterShape(flecs::entity e,
|
||||
CharacterComponent &cc)
|
||||
CharacterComponent &cc)
|
||||
{
|
||||
std::vector<JPH::ShapeRefC> shapes;
|
||||
std::vector<Ogre::Vector3> positions;
|
||||
@@ -101,13 +103,12 @@ JPH::ShapeRefC CharacterSystem::buildCharacterShape(flecs::entity e,
|
||||
|
||||
/* Main capsule */
|
||||
float halfHeight = cc.getHalfHeight();
|
||||
JPH::ShapeRefC capsule = m_physics->createCapsuleShape(halfHeight,
|
||||
cc.radius);
|
||||
JPH::ShapeRefC capsule =
|
||||
m_physics->createCapsuleShape(halfHeight, cc.radius);
|
||||
/* Shift capsule so its bottom sits at y = 0 */
|
||||
Ogre::Vector3 capsulePos(0.0f, halfHeight + cc.radius, 0.0f);
|
||||
capsule = m_physics->createRotatedTranslatedShape(
|
||||
capsulePos + cc.offset, Ogre::Quaternion::IDENTITY,
|
||||
capsule);
|
||||
capsulePos + cc.offset, Ogre::Quaternion::IDENTITY, capsule);
|
||||
shapes.push_back(capsule);
|
||||
positions.push_back(Ogre::Vector3::ZERO);
|
||||
rotations.push_back(Ogre::Quaternion::IDENTITY);
|
||||
@@ -135,8 +136,7 @@ JPH::ShapeRefC CharacterSystem::buildCharacterShape(flecs::entity e,
|
||||
rotations);
|
||||
}
|
||||
|
||||
void CharacterSystem::setupEntity(flecs::entity e,
|
||||
CharacterComponent &cc)
|
||||
void CharacterSystem::setupEntity(flecs::entity e, CharacterComponent &cc)
|
||||
{
|
||||
if (!m_physics)
|
||||
return;
|
||||
@@ -153,14 +153,15 @@ void CharacterSystem::setupEntity(flecs::entity e,
|
||||
if (!shape)
|
||||
return;
|
||||
|
||||
JPH::CharacterBase *base = m_physics->createCharacter(
|
||||
transform.node, shape);
|
||||
JPH::CharacterBase *base =
|
||||
m_physics->createCharacter(transform.node, shape);
|
||||
if (!base)
|
||||
return;
|
||||
|
||||
auto *ch = static_cast<JPH::Character *>(base);
|
||||
ch->AddToPhysicsSystem();
|
||||
m_physics->setGravityFactor(ch->GetBodyID(), 0.0f);
|
||||
// Don't modify gravity factor - let buoyancy handle floating/sinking
|
||||
// m_physics->setGravityFactor(ch->GetBodyID(), 0.0f);
|
||||
cc.hasFloor = false;
|
||||
std::cout << "CharacterSystem::setupEntity: entity=" << e.id()
|
||||
<< " nodePos=" << transform.node->_getDerivedPosition()
|
||||
@@ -196,8 +197,7 @@ void CharacterSystem::update(float deltaTime)
|
||||
return;
|
||||
|
||||
m_world.query<CharacterComponent, TransformComponent>().each(
|
||||
[this, deltaTime](flecs::entity e,
|
||||
CharacterComponent &cc,
|
||||
[this, deltaTime](flecs::entity e, CharacterComponent &cc,
|
||||
TransformComponent &transform) {
|
||||
if (!cc.enabled) {
|
||||
teardownEntity(e);
|
||||
@@ -237,7 +237,8 @@ void CharacterSystem::update(float deltaTime)
|
||||
JoltPhysics::convert<JPH::Vec3>(
|
||||
cc.linearVelocity));
|
||||
if (cc.linearVelocity.squaredLength() > 0.0001f) {
|
||||
std::cout << "CharacterSystem::update: entity=" << e.id()
|
||||
std::cout << "CharacterSystem::update: entity="
|
||||
<< e.id()
|
||||
<< " vel=" << cc.linearVelocity
|
||||
<< std::endl;
|
||||
}
|
||||
@@ -247,25 +248,28 @@ void CharacterSystem::update(float deltaTime)
|
||||
Ogre::Vector3 rayStart = charPos;
|
||||
Ogre::Vector3 rayEnd =
|
||||
rayStart +
|
||||
Ogre::Vector3(0.0f, -cc.floorCheckDistance, 0.0f);
|
||||
Ogre::Vector3(0.0f,
|
||||
-cc.floorCheckDistance,
|
||||
0.0f);
|
||||
Ogre::Vector3 hitPos;
|
||||
JPH::BodyID hitBody;
|
||||
if (m_physics->raycastQuery(rayStart, rayEnd,
|
||||
hitPos, hitBody)) {
|
||||
cc.hasFloor = true;
|
||||
m_physics->setGravityFactor(
|
||||
state.character->GetBodyID(), 1.0f);
|
||||
std::cout << "CharacterSystem::floor detected: entity="
|
||||
<< e.id()
|
||||
<< " dist="
|
||||
<< (charPos - hitPos).length()
|
||||
<< std::endl;
|
||||
// Don't modify gravity factor - let buoyancy handle floating/sinking
|
||||
// m_physics->setGravityFactor(
|
||||
// state.character->GetBodyID(),
|
||||
// 1.0f);
|
||||
std::cout
|
||||
<< "CharacterSystem::floor detected: entity="
|
||||
<< e.id() << " dist="
|
||||
<< (charPos - hitPos).length()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sync rotation from scene node */
|
||||
state.character->SetRotation(
|
||||
JoltPhysics::convert(
|
||||
state.sceneNode->_getDerivedOrientation()));
|
||||
state.character->SetRotation(JoltPhysics::convert(
|
||||
state.sceneNode->_getDerivedOrientation()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../components/EditorMarker.hpp"
|
||||
#include "../components/PhysicsCollider.hpp"
|
||||
#include "../components/RigidBody.hpp"
|
||||
#include "../components/BuoyancyInfo.hpp"
|
||||
#include "../components/Light.hpp"
|
||||
#include "../components/Camera.hpp"
|
||||
#include "../components/Lod.hpp"
|
||||
@@ -28,6 +29,7 @@
|
||||
#include "../ui/RigidBodyEditor.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "PhysicsSystem.hpp"
|
||||
#include "BuoyancySystem.hpp"
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
@@ -35,7 +37,7 @@
|
||||
|
||||
EditorUISystem::EditorUISystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr,
|
||||
Ogre::RenderWindow* renderWindow)
|
||||
Ogre::RenderWindow *renderWindow)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_renderWindow(renderWindow)
|
||||
@@ -65,7 +67,8 @@ bool EditorUISystem::onMousePressed(const Ogre::Ray &mouseRay)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EditorUISystem::onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDelta)
|
||||
bool EditorUISystem::onMouseMoved(const Ogre::Ray &mouseRay,
|
||||
const Ogre::Vector2 &mouseDelta)
|
||||
{
|
||||
if (m_gizmo) {
|
||||
return m_gizmo->onMouseMoved(mouseRay, mouseDelta);
|
||||
@@ -156,7 +159,8 @@ void EditorUISystem::registerComponentEditors()
|
||||
});
|
||||
|
||||
// Register PhysicsCollider component
|
||||
auto colliderEditor = std::make_unique<PhysicsColliderEditor>(m_sceneMgr);
|
||||
auto colliderEditor =
|
||||
std::make_unique<PhysicsColliderEditor>(m_sceneMgr);
|
||||
m_componentRegistry.registerComponent<PhysicsColliderComponent>(
|
||||
"Physics Collider", "Physics", std::move(colliderEditor),
|
||||
// Adder
|
||||
@@ -171,7 +175,7 @@ void EditorUISystem::registerComponentEditors()
|
||||
e.remove<PhysicsColliderComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Register modular components (Light, Camera, etc.)
|
||||
registerModularComponents();
|
||||
}
|
||||
@@ -179,7 +183,8 @@ void EditorUISystem::registerComponentEditors()
|
||||
void EditorUISystem::registerModularComponents()
|
||||
{
|
||||
// This calls all component modules registered via REGISTER_COMPONENT macro
|
||||
ComponentRegistration::registerAll(m_componentRegistry, m_sceneMgr, m_physicsSystem, m_renderWindow);
|
||||
ComponentRegistration::registerAll(m_componentRegistry, m_sceneMgr,
|
||||
m_physicsSystem, m_renderWindow);
|
||||
}
|
||||
|
||||
void EditorUISystem::update(float deltaTime)
|
||||
@@ -194,17 +199,17 @@ void EditorUISystem::update(float deltaTime)
|
||||
// Note: NewFrame() is called by ImGuiRenderListener::preViewportUpdate
|
||||
renderHierarchyWindow();
|
||||
renderPropertyWindow();
|
||||
|
||||
|
||||
// Update gizmo position to match selected entity
|
||||
if (m_gizmo && m_gizmo->isAttached()) {
|
||||
m_gizmo->update();
|
||||
}
|
||||
|
||||
|
||||
// Render file dialog if open
|
||||
if (m_showFileDialog) {
|
||||
renderFileDialog();
|
||||
}
|
||||
|
||||
|
||||
// Render FPS overlay
|
||||
renderFPSOverlay(deltaTime);
|
||||
}
|
||||
@@ -212,7 +217,7 @@ void EditorUISystem::update(float deltaTime)
|
||||
void EditorUISystem::setSelectedEntity(flecs::entity entity)
|
||||
{
|
||||
m_selectedEntity = entity;
|
||||
|
||||
|
||||
// Update gizmo attachment
|
||||
if (m_gizmo) {
|
||||
if (entity.is_alive() && entity.has<TransformComponent>()) {
|
||||
@@ -242,15 +247,17 @@ void EditorUISystem::renderHierarchyWindow()
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
// File menu
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Save Scene...", "Ctrl+S")) {
|
||||
if (ImGui::MenuItem("Save Scene...",
|
||||
"Ctrl+S")) {
|
||||
showFileDialog(true);
|
||||
}
|
||||
if (ImGui::MenuItem("Load Scene...", "Ctrl+O")) {
|
||||
if (ImGui::MenuItem("Load Scene...",
|
||||
"Ctrl+O")) {
|
||||
showFileDialog(false);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
||||
// Entity menu
|
||||
if (ImGui::BeginMenu("Entity")) {
|
||||
if (ImGui::MenuItem("New Entity", "Ctrl+N")) {
|
||||
@@ -276,27 +283,107 @@ void EditorUISystem::renderHierarchyWindow()
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
||||
// Settings menu
|
||||
if (ImGui::BeginMenu("Settings")) {
|
||||
if (ImGui::Checkbox("Parent SceneNodes", &m_parentSceneNodes)) {
|
||||
if (ImGui::Checkbox("Parent SceneNodes",
|
||||
&m_parentSceneNodes)) {
|
||||
// Toggle applied immediately to new entities
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("When enabled, child entities' SceneNodes are parented to their parent's SceneNode, inheriting transforms. When disabled, SceneNodes are created at root level.");
|
||||
ImGui::SetTooltip(
|
||||
"When enabled, child entities' SceneNodes are parented to their parent's SceneNode, inheriting transforms. When disabled, SceneNodes are created at root level.");
|
||||
}
|
||||
|
||||
|
||||
// Physics debug draw toggle
|
||||
if (m_physicsSystem) {
|
||||
bool debugDraw = m_physicsSystem->isDebugDrawEnabled();
|
||||
if (ImGui::Checkbox("Physics Debug Draw", &debugDraw)) {
|
||||
m_physicsSystem->setDebugDraw(debugDraw);
|
||||
bool debugDraw =
|
||||
m_physicsSystem
|
||||
->isDebugDrawEnabled();
|
||||
if (ImGui::Checkbox("Physics Debug Draw",
|
||||
&debugDraw)) {
|
||||
m_physicsSystem->setDebugDraw(
|
||||
debugDraw);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Toggle physics collision shape visualization");
|
||||
ImGui::SetTooltip(
|
||||
"Show physics collision shapes and constraints");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Water Physics");
|
||||
ImGui::Separator();
|
||||
|
||||
// Find and edit WaterPhysics component
|
||||
WaterPhysics *waterPhysics = nullptr;
|
||||
m_world.query<WaterPhysics>().each(
|
||||
[&](flecs::entity entity,
|
||||
WaterPhysics &wp) {
|
||||
waterPhysics = ℘
|
||||
});
|
||||
|
||||
if (waterPhysics) {
|
||||
if (ImGui::Checkbox(
|
||||
"Water Physics Enabled",
|
||||
&waterPhysics->enabled)) {
|
||||
}
|
||||
if (ImGui::SliderFloat(
|
||||
"Water Surface Y",
|
||||
&waterPhysics->waterSurfaceY,
|
||||
-10.0f, 10.0f, "%.2f")) {
|
||||
}
|
||||
if (ImGui::SliderFloat(
|
||||
"Water Density (kg/m³)",
|
||||
&waterPhysics->waterDensity,
|
||||
500.0f, 2000.0f, "%.0f")) {
|
||||
}
|
||||
if (ImGui::SliderFloat(
|
||||
"Gravity (m/s²)",
|
||||
&waterPhysics->gravity,
|
||||
0.0f, 20.0f, "%.2f")) {
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text(
|
||||
"Default Buoyancy Parameters");
|
||||
|
||||
if (ImGui::SliderFloat(
|
||||
"Default Buoyancy",
|
||||
&waterPhysics
|
||||
->defaultBuoyancy,
|
||||
0.0f, 5.0f, "%.2f")) {
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Default buoyancy density multiplier (1.0 = neutral buoyancy)");
|
||||
}
|
||||
|
||||
if (ImGui::SliderFloat(
|
||||
"Default Linear Drag",
|
||||
&waterPhysics
|
||||
->defaultLinearDrag,
|
||||
0.0f, 1.0f, "%.3f")) {
|
||||
}
|
||||
if (ImGui::SliderFloat(
|
||||
"Default Angular Drag",
|
||||
&waterPhysics
|
||||
->defaultAngularDrag,
|
||||
0.0f, 1.0f, "%.3f")) {
|
||||
}
|
||||
if (ImGui::SliderFloat(
|
||||
"Submerged Threshold",
|
||||
&waterPhysics
|
||||
->defaultSubmergedThreshold,
|
||||
0.0f, 1.0f, "%.2f")) {
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled(
|
||||
"No WaterPhysics component found");
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
@@ -327,7 +414,8 @@ void EditorUISystem::renderHierarchyWindow()
|
||||
// Just checking if we can iterate children
|
||||
});
|
||||
// Alternative: use parent() function
|
||||
if (entity.parent().is_valid() && entity.parent() != 0) {
|
||||
if (entity.parent().is_valid() &&
|
||||
entity.parent() != 0) {
|
||||
isRoot = false;
|
||||
}
|
||||
if (isRoot) {
|
||||
@@ -376,7 +464,8 @@ void EditorUISystem::renderEntityNode(flecs::entity entity, int depth)
|
||||
|
||||
// Leaf nodes (no children) have bullet style
|
||||
if (!hasChildren) {
|
||||
flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
||||
flags |= ImGuiTreeNodeFlags_Leaf |
|
||||
ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
||||
}
|
||||
|
||||
// Highlight selected
|
||||
@@ -534,14 +623,16 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
|
||||
// Dynamically render all components the entity has
|
||||
int componentCount = 0;
|
||||
|
||||
|
||||
// Render Transform first if present (it's the base component)
|
||||
if (entity.has<TransformComponent>()) {
|
||||
auto &transform = entity.get_mut<TransformComponent>();
|
||||
if (m_componentRegistry.render<TransformComponent>(entity, transform)) {
|
||||
if (m_componentRegistry.render<TransformComponent>(entity,
|
||||
transform)) {
|
||||
// Transform changed - mark StaticGeometryMember dirty if present
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
entity.get_mut<StaticGeometryMemberComponent>().markDirty();
|
||||
entity.get_mut<StaticGeometryMemberComponent>()
|
||||
.markDirty();
|
||||
}
|
||||
}
|
||||
componentCount++;
|
||||
@@ -550,7 +641,8 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
// Render Renderable
|
||||
if (entity.has<RenderableComponent>()) {
|
||||
auto &renderable = entity.get_mut<RenderableComponent>();
|
||||
m_componentRegistry.render<RenderableComponent>(entity, renderable);
|
||||
m_componentRegistry.render<RenderableComponent>(entity,
|
||||
renderable);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
@@ -571,66 +663,81 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
// Render RigidBody
|
||||
if (entity.has<RigidBodyComponent>()) {
|
||||
auto &rigidBody = entity.get_mut<RigidBodyComponent>();
|
||||
m_componentRegistry.render<RigidBodyComponent>(entity, rigidBody);
|
||||
m_componentRegistry.render<RigidBodyComponent>(entity,
|
||||
rigidBody);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render PhysicsCollider
|
||||
if (entity.has<PhysicsColliderComponent>()) {
|
||||
auto &collider = entity.get_mut<PhysicsColliderComponent>();
|
||||
m_componentRegistry.render<PhysicsColliderComponent>(entity, collider);
|
||||
m_componentRegistry.render<PhysicsColliderComponent>(entity,
|
||||
collider);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render BuoyancyInfo if present
|
||||
if (entity.has<BuoyancyInfo>()) {
|
||||
auto &buoyancy = entity.get_mut<BuoyancyInfo>();
|
||||
m_componentRegistry.render<BuoyancyInfo>(entity, buoyancy);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render LOD Settings if present
|
||||
if (entity.has<LodSettingsComponent>()) {
|
||||
auto &lodSettings = entity.get_mut<LodSettingsComponent>();
|
||||
m_componentRegistry.render<LodSettingsComponent>(entity, lodSettings);
|
||||
m_componentRegistry.render<LodSettingsComponent>(entity,
|
||||
lodSettings);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render LOD if present
|
||||
if (entity.has<LodComponent>()) {
|
||||
auto &lod = entity.get_mut<LodComponent>();
|
||||
m_componentRegistry.render<LodComponent>(entity, lod);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render StaticGeometry Region if present
|
||||
if (entity.has<StaticGeometryComponent>()) {
|
||||
auto ®ion = entity.get_mut<StaticGeometryComponent>();
|
||||
m_componentRegistry.render<StaticGeometryComponent>(entity, region);
|
||||
m_componentRegistry.render<StaticGeometryComponent>(entity,
|
||||
region);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render StaticGeometry Member if present
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
auto &member = entity.get_mut<StaticGeometryMemberComponent>();
|
||||
m_componentRegistry.render<StaticGeometryMemberComponent>(entity, member);
|
||||
m_componentRegistry.render<StaticGeometryMemberComponent>(
|
||||
entity, member);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render ProceduralTexture if present
|
||||
if (entity.has<ProceduralTextureComponent>()) {
|
||||
auto &texture = entity.get_mut<ProceduralTextureComponent>();
|
||||
m_componentRegistry.render<ProceduralTextureComponent>(entity, texture);
|
||||
m_componentRegistry.render<ProceduralTextureComponent>(entity,
|
||||
texture);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render ProceduralMaterial if present
|
||||
if (entity.has<ProceduralMaterialComponent>()) {
|
||||
auto &material = entity.get_mut<ProceduralMaterialComponent>();
|
||||
m_componentRegistry.render<ProceduralMaterialComponent>(entity, material);
|
||||
m_componentRegistry.render<ProceduralMaterialComponent>(
|
||||
entity, material);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render Primitive if present
|
||||
if (entity.has<PrimitiveComponent>()) {
|
||||
auto &primitive = entity.get_mut<PrimitiveComponent>();
|
||||
m_componentRegistry.render<PrimitiveComponent>(entity, primitive);
|
||||
m_componentRegistry.render<PrimitiveComponent>(entity,
|
||||
primitive);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render TriangleBuffer if present
|
||||
if (entity.has<TriangleBufferComponent>()) {
|
||||
auto &tb = entity.get_mut<TriangleBufferComponent>();
|
||||
@@ -669,7 +776,8 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
// Render PlayerController if present
|
||||
if (entity.has<PlayerControllerComponent>()) {
|
||||
auto &pc = entity.get_mut<PlayerControllerComponent>();
|
||||
m_componentRegistry.render<PlayerControllerComponent>(entity, pc);
|
||||
m_componentRegistry.render<PlayerControllerComponent>(entity,
|
||||
pc);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
@@ -679,35 +787,35 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
m_componentRegistry.render<CellGridComponent>(entity, grid);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render Lot if present
|
||||
if (entity.has<LotComponent>()) {
|
||||
auto &lot = entity.get_mut<LotComponent>();
|
||||
m_componentRegistry.render<LotComponent>(entity, lot);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render District if present
|
||||
if (entity.has<DistrictComponent>()) {
|
||||
auto &district = entity.get_mut<DistrictComponent>();
|
||||
m_componentRegistry.render<DistrictComponent>(entity, district);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render Town if present
|
||||
if (entity.has<TownComponent>()) {
|
||||
auto &town = entity.get_mut<TownComponent>();
|
||||
m_componentRegistry.render<TownComponent>(entity, town);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render Roof if present
|
||||
if (entity.has<RoofComponent>()) {
|
||||
auto &roof = entity.get_mut<RoofComponent>();
|
||||
m_componentRegistry.render<RoofComponent>(entity, roof);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render Room if present
|
||||
if (entity.has<RoomComponent>()) {
|
||||
auto &room = entity.get_mut<RoomComponent>();
|
||||
@@ -716,24 +824,22 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Render ClearArea if present
|
||||
if (entity.has<ClearAreaComponent>()) {
|
||||
auto &clearArea = entity.get_mut<ClearAreaComponent>();
|
||||
if (m_componentRegistry.render<ClearAreaComponent>(entity, clearArea)) {
|
||||
if (m_componentRegistry.render<ClearAreaComponent>(entity,
|
||||
clearArea)) {
|
||||
clearArea.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
// Render FurnitureTemplate if present
|
||||
if (entity.has<FurnitureTemplateComponent>()) {
|
||||
auto &furniture = entity.get_mut<FurnitureTemplateComponent>();
|
||||
m_componentRegistry.render<FurnitureTemplateComponent>(entity, furniture);
|
||||
m_componentRegistry.render<FurnitureTemplateComponent>(
|
||||
entity, furniture);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
@@ -755,28 +861,46 @@ void EditorUISystem::renderAddComponentMenu(flecs::entity entity)
|
||||
// Group components by their group name
|
||||
auto groups = m_componentRegistry.getGroups();
|
||||
bool hasAny = false;
|
||||
|
||||
for (const auto& group : groups) {
|
||||
|
||||
for (const auto &group : groups) {
|
||||
// Count components in this group that the entity doesn't have
|
||||
int availableCount = 0;
|
||||
m_componentRegistry.forEachInGroup(group.c_str(), [&](const std::type_index& type, const ComponentRegistry::ComponentInfo& info) {
|
||||
if (!m_componentRegistry.entityHasComponent(entity, type)) {
|
||||
availableCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (availableCount == 0) continue;
|
||||
hasAny = true;
|
||||
|
||||
// Create a submenu for each group
|
||||
if (ImGui::BeginMenu(group.c_str())) {
|
||||
m_componentRegistry.forEachInGroup(group.c_str(), [&](const std::type_index& type, const ComponentRegistry::ComponentInfo& info) {
|
||||
if (!m_componentRegistry.entityHasComponent(entity, type)) {
|
||||
if (ImGui::MenuItem(info.name)) {
|
||||
m_componentRegistry.addComponentByType(entity, type);
|
||||
}
|
||||
m_componentRegistry.forEachInGroup(
|
||||
group.c_str(),
|
||||
[&](const std::type_index &type,
|
||||
const ComponentRegistry::ComponentInfo
|
||||
&info) {
|
||||
if (!m_componentRegistry
|
||||
.entityHasComponent(
|
||||
entity, type)) {
|
||||
availableCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (availableCount == 0)
|
||||
continue;
|
||||
hasAny = true;
|
||||
|
||||
// Create a submenu for each group
|
||||
if (ImGui::BeginMenu(group.c_str())) {
|
||||
m_componentRegistry.forEachInGroup(
|
||||
group.c_str(),
|
||||
[&](const std::type_index &type,
|
||||
const ComponentRegistry::ComponentInfo
|
||||
&info) {
|
||||
if (!m_componentRegistry
|
||||
.entityHasComponent(
|
||||
entity,
|
||||
type)) {
|
||||
if (ImGui::MenuItem(
|
||||
info.name)) {
|
||||
m_componentRegistry
|
||||
.addComponentByType(
|
||||
entity,
|
||||
type);
|
||||
}
|
||||
}
|
||||
});
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
@@ -797,14 +921,19 @@ void EditorUISystem::renderRemoveComponentMenu(flecs::entity entity)
|
||||
|
||||
// Dynamically list all registered components that the entity has
|
||||
bool hasAny = false;
|
||||
m_componentRegistry.forEach([&](const std::type_index& type, const ComponentRegistry::ComponentInfo& info) {
|
||||
if (m_componentRegistry.entityHasComponent(entity, type)) {
|
||||
hasAny = true;
|
||||
if (ImGui::MenuItem(info.name)) {
|
||||
m_componentRegistry.removeComponentByType(entity, type);
|
||||
m_componentRegistry.forEach(
|
||||
[&](const std::type_index &type,
|
||||
const ComponentRegistry::ComponentInfo &info) {
|
||||
if (m_componentRegistry.entityHasComponent(
|
||||
entity, type)) {
|
||||
hasAny = true;
|
||||
if (ImGui::MenuItem(info.name)) {
|
||||
m_componentRegistry
|
||||
.removeComponentByType(
|
||||
entity, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!hasAny) {
|
||||
ImGui::TextDisabled("No components to remove");
|
||||
@@ -845,7 +974,7 @@ void EditorUISystem::createChildEntity(flecs::entity parent)
|
||||
// Create transform
|
||||
TransformComponent transform;
|
||||
auto &parentTransform = parent.get_mut<TransformComponent>();
|
||||
|
||||
|
||||
// SceneNode parenting depends on setting
|
||||
if (m_parentSceneNodes) {
|
||||
// Child SceneNode inherits parent's transform
|
||||
@@ -853,10 +982,11 @@ void EditorUISystem::createChildEntity(flecs::entity parent)
|
||||
transform.position = Ogre::Vector3::ZERO;
|
||||
} else {
|
||||
// Child SceneNode at root level, position relative to parent
|
||||
transform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
transform.node =
|
||||
m_sceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
transform.position = parentTransform.position;
|
||||
}
|
||||
|
||||
|
||||
transform.rotation = Ogre::Quaternion::IDENTITY;
|
||||
transform.scale = Ogre::Vector3::UNIT_SCALE;
|
||||
entity.set<TransformComponent>(transform);
|
||||
@@ -940,7 +1070,7 @@ void EditorUISystem::duplicateEntity(flecs::entity entity)
|
||||
// Find parent entity and node
|
||||
flecs::entity parent = entity.parent();
|
||||
Ogre::SceneNode *parentNode = m_sceneMgr->getRootSceneNode();
|
||||
|
||||
|
||||
if (parent.is_valid() && parent != 0 &&
|
||||
parent.has<TransformComponent>()) {
|
||||
parentNode = parent.get<TransformComponent>().node;
|
||||
@@ -950,13 +1080,16 @@ void EditorUISystem::duplicateEntity(flecs::entity entity)
|
||||
if (m_parentSceneNodes && parent.is_valid() && parent != 0) {
|
||||
// Create as child of parent's SceneNode
|
||||
newTransform.node = parentNode->createChildSceneNode();
|
||||
newTransform.position = oldTransform.position + Ogre::Vector3(1, 0, 0);
|
||||
newTransform.position =
|
||||
oldTransform.position + Ogre::Vector3(1, 0, 0);
|
||||
} else {
|
||||
// Create at root level
|
||||
newTransform.node = m_sceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
newTransform.position = oldTransform.position + Ogre::Vector3(1, 0, 0);
|
||||
newTransform.node = m_sceneMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
newTransform.position =
|
||||
oldTransform.position + Ogre::Vector3(1, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
newTransform.rotation = oldTransform.rotation;
|
||||
newTransform.scale = oldTransform.scale;
|
||||
newTransform.applyToNode();
|
||||
@@ -998,9 +1131,8 @@ EditorUISystem::getEntityChildren(flecs::entity parent)
|
||||
return children;
|
||||
|
||||
// Use flecs::ChildOf relationship to get children
|
||||
parent.children([&](flecs::entity child) {
|
||||
children.push_back(child);
|
||||
});
|
||||
parent.children(
|
||||
[&](flecs::entity child) { children.push_back(child); });
|
||||
|
||||
return children;
|
||||
}
|
||||
@@ -1020,33 +1152,36 @@ bool EditorUISystem::isDescendantOf(flecs::entity potentialChild,
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorUISystem::saveScene(const std::string& filepath)
|
||||
void EditorUISystem::saveScene(const std::string &filepath)
|
||||
{
|
||||
if (!m_serializer) return;
|
||||
|
||||
if (!m_serializer)
|
||||
return;
|
||||
|
||||
if (m_serializer->saveToFile(filepath)) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Scene saved to: " + filepath);
|
||||
Ogre::LogManager::getSingleton().logMessage("Scene saved to: " +
|
||||
filepath);
|
||||
} else {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Failed to save scene: " + m_serializer->getLastError());
|
||||
"Failed to save scene: " +
|
||||
m_serializer->getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorUISystem::loadScene(const std::string& filepath)
|
||||
void EditorUISystem::loadScene(const std::string &filepath)
|
||||
{
|
||||
if (!m_serializer) return;
|
||||
|
||||
if (!m_serializer)
|
||||
return;
|
||||
|
||||
if (m_serializer->loadFromFile(filepath, this)) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Scene loaded from: " + filepath);
|
||||
} else {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Failed to load scene: " + m_serializer->getLastError());
|
||||
"Failed to load scene: " +
|
||||
m_serializer->getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// File Dialog Implementation
|
||||
|
||||
void EditorUISystem::showFileDialog(bool isSave)
|
||||
@@ -1054,15 +1189,16 @@ void EditorUISystem::showFileDialog(bool isSave)
|
||||
m_showFileDialog = true;
|
||||
m_fileDialogIsSave = isSave;
|
||||
m_refreshDirectory = true;
|
||||
|
||||
|
||||
// Initialize current path if empty
|
||||
if (m_currentPath.empty()) {
|
||||
m_currentPath = std::filesystem::current_path().string();
|
||||
}
|
||||
|
||||
|
||||
// Set default filename for save dialog
|
||||
if (isSave && strlen(m_filenameBuffer) == 0) {
|
||||
std::strncpy(m_filenameBuffer, "scene.json", sizeof(m_filenameBuffer) - 1);
|
||||
std::strncpy(m_filenameBuffer, "scene.json",
|
||||
sizeof(m_filenameBuffer) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,36 +1209,41 @@ void EditorUISystem::closeFileDialog()
|
||||
|
||||
void EditorUISystem::renderFileDialog()
|
||||
{
|
||||
const char* title = m_fileDialogIsSave ? "Save Scene" : "Load Scene";
|
||||
|
||||
const char *title = m_fileDialogIsSave ? "Save Scene" : "Load Scene";
|
||||
|
||||
// Center the dialog
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing,
|
||||
ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
|
||||
|
||||
|
||||
if (!ImGui::Begin(title, &m_showFileDialog)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Current path display
|
||||
ImGui::Text("Location: %s", m_currentPath.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Refresh directory contents if needed
|
||||
if (m_refreshDirectory) {
|
||||
m_directoryContents.clear();
|
||||
try {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(m_currentPath)) {
|
||||
m_directoryContents.push_back(entry.path().filename().string());
|
||||
for (const auto &entry :
|
||||
std::filesystem::directory_iterator(
|
||||
m_currentPath)) {
|
||||
m_directoryContents.push_back(
|
||||
entry.path().filename().string());
|
||||
}
|
||||
std::sort(m_directoryContents.begin(), m_directoryContents.end());
|
||||
std::sort(m_directoryContents.begin(),
|
||||
m_directoryContents.end());
|
||||
} catch (...) {
|
||||
// Ignore errors (e.g., permission denied)
|
||||
}
|
||||
m_refreshDirectory = false;
|
||||
}
|
||||
|
||||
|
||||
// Parent directory button
|
||||
if (ImGui::Button("..")) {
|
||||
std::filesystem::path p(m_currentPath);
|
||||
@@ -1113,23 +1254,24 @@ void EditorUISystem::renderFileDialog()
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("(Parent Directory)");
|
||||
|
||||
|
||||
// File list
|
||||
ImGui::BeginChild("FileList", ImVec2(0, -60), true);
|
||||
|
||||
for (const auto& name : m_directoryContents) {
|
||||
std::filesystem::path fullPath = std::filesystem::path(m_currentPath) / name;
|
||||
|
||||
for (const auto &name : m_directoryContents) {
|
||||
std::filesystem::path fullPath =
|
||||
std::filesystem::path(m_currentPath) / name;
|
||||
bool isDirectory = std::filesystem::is_directory(fullPath);
|
||||
|
||||
|
||||
// Skip non-json files in load mode (but show directories)
|
||||
if (!m_fileDialogIsSave && !isDirectory) {
|
||||
if (fullPath.extension() != ".json") {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string label = isDirectory ? "[D] " + name : name;
|
||||
|
||||
|
||||
if (ImGui::Selectable(label.c_str(), m_selectedFile == name)) {
|
||||
if (isDirectory) {
|
||||
m_currentPath = fullPath.string();
|
||||
@@ -1137,10 +1279,11 @@ void EditorUISystem::renderFileDialog()
|
||||
m_selectedFile.clear();
|
||||
} else {
|
||||
m_selectedFile = name;
|
||||
std::strncpy(m_filenameBuffer, name.c_str(), sizeof(m_filenameBuffer) - 1);
|
||||
std::strncpy(m_filenameBuffer, name.c_str(),
|
||||
sizeof(m_filenameBuffer) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Double-click to enter directory or select file
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
if (isDirectory) {
|
||||
@@ -1150,26 +1293,32 @@ void EditorUISystem::renderFileDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
|
||||
// Filename input
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Filename:");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("##filename", m_filenameBuffer, sizeof(m_filenameBuffer));
|
||||
|
||||
ImGui::InputText("##filename", m_filenameBuffer,
|
||||
sizeof(m_filenameBuffer));
|
||||
|
||||
// Buttons
|
||||
if (ImGui::Button(m_fileDialogIsSave ? "Save" : "Load", ImVec2(120, 0))) {
|
||||
if (ImGui::Button(m_fileDialogIsSave ? "Save" : "Load",
|
||||
ImVec2(120, 0))) {
|
||||
std::string filename(m_filenameBuffer);
|
||||
if (!filename.empty()) {
|
||||
// Add .json extension if missing in save mode
|
||||
if (m_fileDialogIsSave && filename.find('.') == std::string::npos) {
|
||||
if (m_fileDialogIsSave &&
|
||||
filename.find('.') == std::string::npos) {
|
||||
filename += ".json";
|
||||
}
|
||||
|
||||
std::string fullPath = (std::filesystem::path(m_currentPath) / filename).string();
|
||||
|
||||
|
||||
std::string fullPath =
|
||||
(std::filesystem::path(m_currentPath) /
|
||||
filename)
|
||||
.string();
|
||||
|
||||
if (m_fileDialogIsSave) {
|
||||
saveScene(fullPath);
|
||||
} else {
|
||||
@@ -1178,72 +1327,74 @@ void EditorUISystem::renderFileDialog()
|
||||
closeFileDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||
closeFileDialog();
|
||||
}
|
||||
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
void EditorUISystem::renderFPSOverlay(float deltaTime)
|
||||
{
|
||||
// Toggle FPS overlay with F12 key
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F12)) {
|
||||
m_showFPSOverlay = !m_showFPSOverlay;
|
||||
}
|
||||
|
||||
|
||||
if (!m_showFPSOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Update FPS calculation
|
||||
m_fpsTimeAccumulator += deltaTime;
|
||||
m_fpsFrameCount++;
|
||||
|
||||
// Note: Batch count is set by ImGuiRenderListener::postViewportUpdate
|
||||
|
||||
// Note: Batch count is set by ImGuiRenderListener::postViewportUpdate
|
||||
// after each frame render (stats are updated at end of frame)
|
||||
|
||||
|
||||
// Update FPS every 0.5 seconds for smoother display
|
||||
if (m_fpsTimeAccumulator >= 0.5f) {
|
||||
m_currentFPS = m_fpsFrameCount / m_fpsTimeAccumulator;
|
||||
m_fpsTimeAccumulator = 0.0f;
|
||||
m_fpsFrameCount = 0;
|
||||
}
|
||||
|
||||
|
||||
// Set up transparent window style
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav;
|
||||
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav;
|
||||
|
||||
// Position in top-right corner with padding
|
||||
ImVec2 windowPos = ImVec2(ImGui::GetIO().DisplaySize.x - 10.0f, 10.0f);
|
||||
ImVec2 windowPivot = ImVec2(1.0f, 0.0f); // Top-right pivot
|
||||
|
||||
|
||||
ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, windowPivot);
|
||||
ImGui::SetNextWindowBgAlpha(0.35f); // Semi-transparent background
|
||||
|
||||
|
||||
if (ImGui::Begin("FPS Overlay", nullptr, windowFlags)) {
|
||||
// FPS text color based on performance
|
||||
ImVec4 fpsColor;
|
||||
if (m_currentFPS >= 60.0f) {
|
||||
fpsColor = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); // Green for 60+ FPS
|
||||
fpsColor = ImVec4(0.0f, 1.0f, 0.0f,
|
||||
1.0f); // Green for 60+ FPS
|
||||
} else if (m_currentFPS >= 30.0f) {
|
||||
fpsColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f); // Yellow for 30-60 FPS
|
||||
fpsColor = ImVec4(1.0f, 1.0f, 0.0f,
|
||||
1.0f); // Yellow for 30-60 FPS
|
||||
} else {
|
||||
fpsColor = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Red for <30 FPS
|
||||
fpsColor = ImVec4(1.0f, 0.0f, 0.0f,
|
||||
1.0f); // Red for <30 FPS
|
||||
}
|
||||
|
||||
|
||||
ImGui::TextColored(fpsColor, "%.1f FPS", m_currentFPS);
|
||||
ImGui::Text("Batches: %d", m_lastBatchCount);
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
ImVec4 hintColor = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
ImGui::TextColored(hintColor, "F12 to toggle");
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
|
||||
// Forward declarations
|
||||
class EditorPhysicsSystem;
|
||||
class BuoyancySystem;
|
||||
|
||||
namespace Ogre {
|
||||
class RenderWindow;
|
||||
namespace Ogre
|
||||
{
|
||||
class RenderWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,7 +30,7 @@ namespace Ogre {
|
||||
class EditorUISystem {
|
||||
public:
|
||||
EditorUISystem(flecs::world &world, Ogre::SceneManager *sceneMgr,
|
||||
Ogre::RenderWindow* renderWindow = nullptr);
|
||||
Ogre::RenderWindow *renderWindow = nullptr);
|
||||
~EditorUISystem();
|
||||
|
||||
/**
|
||||
@@ -36,7 +38,7 @@ public:
|
||||
* Call this once per frame
|
||||
*/
|
||||
void update(float deltaTime);
|
||||
|
||||
|
||||
/**
|
||||
* Render FPS and batch count overlay
|
||||
*/
|
||||
@@ -50,12 +52,16 @@ public:
|
||||
/**
|
||||
* Add existing entity to cache (for entities created before UI)
|
||||
*/
|
||||
void addEntity(flecs::entity entity) { m_allEntities.push_back(entity); }
|
||||
void addEntity(flecs::entity entity)
|
||||
{
|
||||
m_allEntities.push_back(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear entity cache and deselect
|
||||
*/
|
||||
void clearEntityCache() {
|
||||
void clearEntityCache()
|
||||
{
|
||||
m_allEntities.clear();
|
||||
setSelectedEntity(flecs::entity::null());
|
||||
}
|
||||
@@ -73,14 +79,18 @@ public:
|
||||
* Returns true if gizmo handled the event
|
||||
*/
|
||||
bool onMousePressed(const Ogre::Ray &mouseRay);
|
||||
bool onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDelta);
|
||||
bool onMouseMoved(const Ogre::Ray &mouseRay,
|
||||
const Ogre::Vector2 &mouseDelta);
|
||||
bool onMouseReleased();
|
||||
|
||||
/**
|
||||
* Get the gizmo for external interaction
|
||||
*/
|
||||
Gizmo *getGizmo() const { return m_gizmo.get(); }
|
||||
|
||||
Gizmo *getGizmo() const
|
||||
{
|
||||
return m_gizmo.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown UI system - must be called before SceneManager destruction
|
||||
*/
|
||||
@@ -89,35 +99,56 @@ public:
|
||||
/**
|
||||
* Get/set SceneNode parenting mode
|
||||
*/
|
||||
bool getParentSceneNodes() const { return m_parentSceneNodes; }
|
||||
void setParentSceneNodes(bool value) { m_parentSceneNodes = value; }
|
||||
bool getParentSceneNodes() const
|
||||
{
|
||||
return m_parentSceneNodes;
|
||||
}
|
||||
void setParentSceneNodes(bool value)
|
||||
{
|
||||
m_parentSceneNodes = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set physics system for debug toggle
|
||||
*/
|
||||
void setPhysicsSystem(EditorPhysicsSystem* physics) { m_physicsSystem = physics; }
|
||||
void setPhysicsSystem(EditorPhysicsSystem *physics)
|
||||
{
|
||||
m_physicsSystem = physics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set buoyancy system for configuration
|
||||
*/
|
||||
void setBuoyancySystem(BuoyancySystem *buoyancy)
|
||||
{
|
||||
m_buoyancySystem = buoyancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable editor UI rendering
|
||||
*/
|
||||
void setEditorUIEnabled(bool enabled) { m_editorUIEnabled = enabled; }
|
||||
|
||||
void setEditorUIEnabled(bool enabled)
|
||||
{
|
||||
m_editorUIEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set last frame batch count (called from render listener)
|
||||
*/
|
||||
void setLastBatchCount(int count) { m_lastBatchCount = count; }
|
||||
|
||||
|
||||
void setLastBatchCount(int count)
|
||||
{
|
||||
m_lastBatchCount = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save scene to file
|
||||
*/
|
||||
void saveScene(const std::string& filepath);
|
||||
void saveScene(const std::string &filepath);
|
||||
|
||||
/**
|
||||
* Load scene from file
|
||||
*/
|
||||
void loadScene(const std::string& filepath);
|
||||
void loadScene(const std::string &filepath);
|
||||
|
||||
/**
|
||||
* Show file dialog for save/load
|
||||
@@ -166,11 +197,12 @@ private:
|
||||
std::unique_ptr<SceneSerializer> m_serializer;
|
||||
|
||||
// Settings
|
||||
bool m_parentSceneNodes = true; // Whether child entities inherit parent's SceneNode
|
||||
|
||||
bool m_parentSceneNodes =
|
||||
true; // Whether child entities inherit parent's SceneNode
|
||||
|
||||
// FPS overlay settings
|
||||
bool m_showFPSOverlay = true; // Show FPS counter by default
|
||||
|
||||
bool m_showFPSOverlay = true; // Show FPS counter by default
|
||||
|
||||
// FPS tracking
|
||||
float m_fpsTimeAccumulator = 0.0f;
|
||||
int m_fpsFrameCount = 0;
|
||||
@@ -178,13 +210,16 @@ private:
|
||||
int m_lastBatchCount = 0;
|
||||
|
||||
// Physics system reference (for debug toggle)
|
||||
EditorPhysicsSystem* m_physicsSystem = nullptr;
|
||||
EditorPhysicsSystem *m_physicsSystem = nullptr;
|
||||
|
||||
// Buoyancy system reference (for configuration)
|
||||
BuoyancySystem *m_buoyancySystem = nullptr;
|
||||
|
||||
// Editor UI enabled flag
|
||||
bool m_editorUIEnabled = true;
|
||||
|
||||
// Render window reference (for viewport access)
|
||||
Ogre::RenderWindow* m_renderWindow = nullptr;
|
||||
Ogre::RenderWindow *m_renderWindow = nullptr;
|
||||
|
||||
// File dialog state
|
||||
bool m_showFileDialog = false;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
class EditorPhysicsSystem {
|
||||
public:
|
||||
EditorPhysicsSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
EditorPhysicsSystem(flecs::world &world, Ogre::SceneManager *sceneMgr);
|
||||
~EditorPhysicsSystem();
|
||||
|
||||
// Initialize physics world
|
||||
@@ -26,33 +26,46 @@ public:
|
||||
|
||||
// Process component changes and create/update bodies
|
||||
void syncBodies();
|
||||
|
||||
|
||||
// Update SceneNodes from physics for dynamic bodies
|
||||
void updateDynamicBodies();
|
||||
|
||||
// Enable/disable debug drawing
|
||||
void setDebugDraw(bool enable);
|
||||
bool isDebugDrawEnabled() const { return m_debugDraw; }
|
||||
|
||||
bool isInitialized() const { return m_initialized; }
|
||||
bool isDebugDrawEnabled() const
|
||||
{
|
||||
return m_debugDraw;
|
||||
}
|
||||
|
||||
bool isInitialized() const
|
||||
{
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
// Remove a rigid body (public for external cleanup)
|
||||
void removeRigidBody(RigidBodyComponent& rigidBody);
|
||||
void removeRigidBody(RigidBodyComponent &rigidBody);
|
||||
|
||||
// Get physics wrapper for other systems (e.g., buoyancy)
|
||||
JoltPhysicsWrapper *getPhysicsWrapper() const
|
||||
{
|
||||
return m_physics.get();
|
||||
}
|
||||
|
||||
private:
|
||||
// Create a shape from collider component
|
||||
JPH::ShapeRefC createShape(PhysicsColliderComponent& collider);
|
||||
JPH::ShapeRefC createShape(PhysicsColliderComponent &collider);
|
||||
|
||||
// Build compound shape from multiple colliders
|
||||
JPH::ShapeRefC buildCompoundShape(flecs::entity rigidBodyEntity);
|
||||
|
||||
// Create or update a rigid body
|
||||
void updateRigidBody(flecs::entity entity, RigidBodyComponent& rigidBody,
|
||||
class TransformComponent& transform);
|
||||
void updateRigidBody(flecs::entity entity,
|
||||
RigidBodyComponent &rigidBody,
|
||||
class TransformComponent &transform);
|
||||
|
||||
flecs::world &m_world;
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
std::unique_ptr<JoltPhysicsWrapper> m_physics;
|
||||
bool m_initialized = false;
|
||||
bool m_debugDraw = false;
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "../components/PlayerController.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include "../components/GeneratedPhysicsTag.hpp"
|
||||
#include "../components/BuoyancyInfo.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
#include "EditorUISystem.hpp"
|
||||
#include <random>
|
||||
#include <fstream>
|
||||
@@ -243,6 +245,15 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
json["clearArea"] = serializeClearArea(entity);
|
||||
}
|
||||
|
||||
// Buoyancy components
|
||||
if (entity.has<BuoyancyInfo>()) {
|
||||
json["buoyancyInfo"] = serializeBuoyancyInfo(entity);
|
||||
}
|
||||
|
||||
if (entity.has<WaterPhysics>()) {
|
||||
json["waterPhysics"] = serializeWaterPhysics(entity);
|
||||
}
|
||||
|
||||
// Serialize children
|
||||
json["children"] = nlohmann::json::array();
|
||||
entity.children([&](flecs::entity child) {
|
||||
@@ -391,6 +402,15 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
|
||||
deserializeClearArea(entity, json["clearArea"]);
|
||||
}
|
||||
|
||||
// Buoyancy components
|
||||
if (json.contains("buoyancyInfo")) {
|
||||
deserializeBuoyancyInfo(entity, json["buoyancyInfo"]);
|
||||
}
|
||||
|
||||
if (json.contains("waterPhysics")) {
|
||||
deserializeWaterPhysics(entity, json["waterPhysics"]);
|
||||
}
|
||||
|
||||
// Add to UI system if provided
|
||||
if (uiSystem) {
|
||||
uiSystem->addEntity(entity);
|
||||
@@ -2204,3 +2224,74 @@ void SceneSerializer::deserializePlayerController(flecs::entity entity,
|
||||
pc.runState = json.value("runState", pc.runState);
|
||||
entity.set<PlayerControllerComponent>(pc);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Buoyancy Component Serialization
|
||||
// ============================================================================
|
||||
|
||||
nlohmann::json SceneSerializer::serializeBuoyancyInfo(flecs::entity entity)
|
||||
{
|
||||
auto &buoyancy = entity.get<BuoyancyInfo>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["enabled"] = buoyancy.enabled;
|
||||
json["buoyancy"] = buoyancy.buoyancy;
|
||||
json["linearDrag"] = buoyancy.linearDrag;
|
||||
json["angularDrag"] = buoyancy.angularDrag;
|
||||
json["waterSurfaceY"] = buoyancy.waterSurfaceY;
|
||||
json["useCustomWaterLevel"] = buoyancy.useCustomWaterLevel;
|
||||
json["submergedThreshold"] = buoyancy.submergedThreshold;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializeWaterPhysics(flecs::entity entity)
|
||||
{
|
||||
auto &water = entity.get<WaterPhysics>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["enabled"] = water.enabled;
|
||||
json["waterSurfaceY"] = water.waterSurfaceY;
|
||||
json["waterDensity"] = water.waterDensity;
|
||||
json["gravity"] = water.gravity;
|
||||
json["defaultBuoyancy"] = water.defaultBuoyancy;
|
||||
json["defaultLinearDrag"] = water.defaultLinearDrag;
|
||||
json["defaultAngularDrag"] = water.defaultAngularDrag;
|
||||
json["defaultSubmergedThreshold"] = water.defaultSubmergedThreshold;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeBuoyancyInfo(flecs::entity entity,
|
||||
const nlohmann::json &json)
|
||||
{
|
||||
BuoyancyInfo buoyancy;
|
||||
|
||||
buoyancy.enabled = json.value("enabled", true);
|
||||
buoyancy.buoyancy = json.value("buoyancy", 1.0f);
|
||||
buoyancy.linearDrag = json.value("linearDrag", 0.1f);
|
||||
buoyancy.angularDrag = json.value("angularDrag", 0.05f);
|
||||
buoyancy.waterSurfaceY = json.value("waterSurfaceY", -0.1f);
|
||||
buoyancy.useCustomWaterLevel = json.value("useCustomWaterLevel", false);
|
||||
buoyancy.submergedThreshold = json.value("submergedThreshold", 0.3f);
|
||||
|
||||
entity.set<BuoyancyInfo>(buoyancy);
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeWaterPhysics(flecs::entity entity,
|
||||
const nlohmann::json &json)
|
||||
{
|
||||
WaterPhysics water;
|
||||
|
||||
water.enabled = json.value("enabled", true);
|
||||
water.waterSurfaceY = json.value("waterSurfaceY", -0.1f);
|
||||
water.waterDensity = json.value("waterDensity", 1000.0f);
|
||||
water.gravity = json.value("gravity", 9.81f);
|
||||
water.defaultBuoyancy = json.value("defaultBuoyancy", 1.0f);
|
||||
water.defaultLinearDrag = json.value("defaultLinearDrag", 0.1f);
|
||||
water.defaultAngularDrag = json.value("defaultAngularDrag", 0.05f);
|
||||
water.defaultSubmergedThreshold =
|
||||
json.value("defaultSubmergedThreshold", 0.3f);
|
||||
|
||||
entity.set<WaterPhysics>(water);
|
||||
}
|
||||
|
||||
@@ -16,28 +16,33 @@ class EditorUISystem;
|
||||
*/
|
||||
class SceneSerializer {
|
||||
public:
|
||||
SceneSerializer(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
SceneSerializer(flecs::world &world, Ogre::SceneManager *sceneMgr);
|
||||
|
||||
/**
|
||||
* Save scene to JSON file
|
||||
*/
|
||||
bool saveToFile(const std::string& filepath);
|
||||
bool saveToFile(const std::string &filepath);
|
||||
|
||||
/**
|
||||
* Load scene from JSON file
|
||||
*/
|
||||
bool loadFromFile(const std::string& filepath, EditorUISystem* uiSystem = nullptr);
|
||||
bool loadFromFile(const std::string &filepath,
|
||||
EditorUISystem *uiSystem = nullptr);
|
||||
|
||||
/**
|
||||
* Get last error message
|
||||
*/
|
||||
const std::string& getLastError() const { return m_lastError; }
|
||||
const std::string &getLastError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
private:
|
||||
// Serialization helpers
|
||||
nlohmann::json serializeEntity(flecs::entity entity);
|
||||
void deserializeEntity(const nlohmann::json& json, flecs::entity parent, EditorUISystem* uiSystem);
|
||||
|
||||
void deserializeEntity(const nlohmann::json &json, flecs::entity parent,
|
||||
EditorUISystem *uiSystem);
|
||||
|
||||
// Component serialization
|
||||
nlohmann::json serializeTransform(flecs::entity entity);
|
||||
nlohmann::json serializeRenderable(flecs::entity entity);
|
||||
@@ -59,7 +64,7 @@ private:
|
||||
nlohmann::json serializeAnimationTree(flecs::entity entity);
|
||||
nlohmann::json serializeStartupMenu(flecs::entity entity);
|
||||
nlohmann::json serializePlayerController(flecs::entity entity);
|
||||
|
||||
|
||||
// CellGrid/Town component serialization
|
||||
nlohmann::json serializeCellGrid(flecs::entity entity);
|
||||
nlohmann::json serializeTown(flecs::entity entity);
|
||||
@@ -69,43 +74,74 @@ private:
|
||||
nlohmann::json serializeRoof(flecs::entity entity);
|
||||
nlohmann::json serializeFurnitureTemplate(flecs::entity entity);
|
||||
nlohmann::json serializeClearArea(flecs::entity entity);
|
||||
|
||||
// Component deserialization
|
||||
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
|
||||
void deserializeRenderable(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeEntityName(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeRigidBody(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeCollider(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeLight(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeCamera(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeLodSettings(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeLod(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStaticGeometry(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStaticGeometryMember(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeProceduralTexture(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeProceduralMaterial(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializePrimitive(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeTriangleBuffer(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeCharacter(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeCharacterSlots(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeAnimationTree(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStartupMenu(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializePlayerController(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
// CellGrid/Town component deserialization
|
||||
void deserializeCellGrid(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeTown(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeDistrict(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeLot(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeRoom(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeRoof(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeFurnitureTemplate(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeClearArea(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
// Component deserialization
|
||||
void deserializeTransform(flecs::entity entity,
|
||||
const nlohmann::json &json,
|
||||
flecs::entity parentEntity);
|
||||
void deserializeRenderable(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeEntityName(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeRigidBody(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeCollider(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeLight(flecs::entity entity, const nlohmann::json &json);
|
||||
void deserializeCamera(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeLodSettings(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeLod(flecs::entity entity, const nlohmann::json &json);
|
||||
void deserializeStaticGeometry(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeStaticGeometryMember(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeProceduralTexture(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeProceduralMaterial(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializePrimitive(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeTriangleBuffer(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeCharacter(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeCharacterSlots(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeAnimationTree(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeStartupMenu(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializePlayerController(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
|
||||
// CellGrid/Town component deserialization
|
||||
void deserializeCellGrid(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeTown(flecs::entity entity, const nlohmann::json &json);
|
||||
void deserializeDistrict(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeLot(flecs::entity entity, const nlohmann::json &json);
|
||||
void deserializeRoom(flecs::entity entity, const nlohmann::json &json);
|
||||
void deserializeRoof(flecs::entity entity, const nlohmann::json &json);
|
||||
void deserializeFurnitureTemplate(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeClearArea(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
|
||||
// Buoyancy component serialization
|
||||
nlohmann::json serializeBuoyancyInfo(flecs::entity entity);
|
||||
nlohmann::json serializeWaterPhysics(flecs::entity entity);
|
||||
void deserializeBuoyancyInfo(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeWaterPhysics(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
|
||||
flecs::world &m_world;
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
std::string m_lastError;
|
||||
|
||||
|
||||
// Track entity ID mapping for parent/child relationships
|
||||
std::unordered_map<uint64_t, flecs::entity> m_entityMap;
|
||||
};
|
||||
|
||||
103
src/features/editScene/ui/BuoyancyInfoEditor.cpp
Normal file
103
src/features/editScene/ui/BuoyancyInfoEditor.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "BuoyancyInfoEditor.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
bool BuoyancyInfoEditor::renderComponent(flecs::entity entity,
|
||||
BuoyancyInfo &buoyancyInfo)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Buoyancy Info",
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Enabled checkbox
|
||||
if (ImGui::Checkbox("Enabled", &buoyancyInfo.enabled)) {
|
||||
buoyancyInfo.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Enable or disable buoyancy for this entity");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Buoyancy strength
|
||||
if (ImGui::DragFloat("Buoyancy Strength",
|
||||
&buoyancyInfo.buoyancy, 0.01f, 0.0f,
|
||||
5.0f)) {
|
||||
buoyancyInfo.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"0 = no buoyancy, 1 = neutral buoyancy, >1 = floats");
|
||||
}
|
||||
|
||||
// Linear drag
|
||||
if (ImGui::DragFloat("Linear Drag", &buoyancyInfo.linearDrag,
|
||||
0.01f, 0.0f, 1.0f)) {
|
||||
buoyancyInfo.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Drag force applied to linear movement when submerged (0-1)");
|
||||
}
|
||||
|
||||
// Angular drag
|
||||
if (ImGui::DragFloat("Angular Drag", &buoyancyInfo.angularDrag,
|
||||
0.01f, 0.0f, 1.0f)) {
|
||||
buoyancyInfo.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Drag force applied to rotation when submerged (0-1)");
|
||||
}
|
||||
|
||||
// Submerged threshold
|
||||
if (ImGui::DragFloat("Submerged Threshold",
|
||||
&buoyancyInfo.submergedThreshold, 0.01f,
|
||||
0.0f, 1.0f)) {
|
||||
buoyancyInfo.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"How much of the body must be submerged before buoyancy applies (0-1)");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Custom water level
|
||||
if (ImGui::Checkbox("Use Custom Water Level",
|
||||
&buoyancyInfo.useCustomWaterLevel)) {
|
||||
buoyancyInfo.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Use custom water surface level for this entity instead of global");
|
||||
}
|
||||
|
||||
if (buoyancyInfo.useCustomWaterLevel) {
|
||||
if (ImGui::DragFloat("Water Surface Y",
|
||||
&buoyancyInfo.waterSurfaceY, 0.1f,
|
||||
-100.0f, 100.0f)) {
|
||||
buoyancyInfo.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Water surface Y coordinate in world space");
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("Using global water level");
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
21
src/features/editScene/ui/BuoyancyInfoEditor.hpp
Normal file
21
src/features/editScene/ui/BuoyancyInfoEditor.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef EDITSCENE_BUOYANCYINFOEDITOR_HPP
|
||||
#define EDITSCENE_BUOYANCYINFOEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/BuoyancyInfo.hpp"
|
||||
|
||||
/**
|
||||
* Editor for BuoyancyInfo component
|
||||
*/
|
||||
class BuoyancyInfoEditor : public ComponentEditor<BuoyancyInfo> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity,
|
||||
BuoyancyInfo &buoyancyInfo) override;
|
||||
const char *getName() const override
|
||||
{
|
||||
return "Buoyancy Info";
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_BUOYANCYINFOEDITOR_HPP
|
||||
110
src/features/editScene/ui/WaterPhysicsEditor.hpp
Normal file
110
src/features/editScene/ui/WaterPhysicsEditor.hpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#ifndef WATER_PHYSICS_EDITOR_HPP
|
||||
#define WATER_PHYSICS_EDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/WaterPhysics.hpp"
|
||||
|
||||
/**
|
||||
* Editor for WaterPhysics component
|
||||
*/
|
||||
class WaterPhysicsEditor : public ComponentEditor<WaterPhysics> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity,
|
||||
WaterPhysics &component) override
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
ImGui::Text("Water Physics Settings");
|
||||
ImGui::Separator();
|
||||
|
||||
// Enabled toggle
|
||||
if (ImGui::Checkbox("Enabled", &component.enabled)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Water surface Y level
|
||||
if (ImGui::SliderFloat("Water Surface Y",
|
||||
&component.waterSurfaceY, -10.0f, 10.0f,
|
||||
"%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Water density
|
||||
if (ImGui::SliderFloat("Water Density (kg/m³)",
|
||||
&component.waterDensity, 500.0f, 2000.0f,
|
||||
"%.0f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Gravity
|
||||
if (ImGui::SliderFloat("Gravity (m/s²)", &component.gravity,
|
||||
0.0f, 20.0f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Default Buoyancy Parameters");
|
||||
|
||||
// Default buoyancy
|
||||
if (ImGui::SliderFloat("Default Buoyancy",
|
||||
&component.defaultBuoyancy, 0.0f, 5.0f,
|
||||
"%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Default buoyancy density multiplier (1.0 = neutral buoyancy)");
|
||||
}
|
||||
|
||||
// Default linear drag
|
||||
if (ImGui::SliderFloat("Default Linear Drag",
|
||||
&component.defaultLinearDrag, 0.0f, 1.0f,
|
||||
"%.3f")) {
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Default linear drag coefficient for objects in water");
|
||||
}
|
||||
|
||||
// Default angular drag
|
||||
if (ImGui::SliderFloat("Default Angular Drag",
|
||||
&component.defaultAngularDrag, 0.0f,
|
||||
1.0f, "%.3f")) {
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Default angular drag coefficient for objects in water");
|
||||
}
|
||||
|
||||
// Default submerged threshold
|
||||
if (ImGui::SliderFloat("Submerged Threshold",
|
||||
&component.defaultSubmergedThreshold,
|
||||
0.0f, 1.0f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Minimum submerged fraction to apply buoyancy (0.0 = any contact, 1.0 = fully submerged)");
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
const char *getName() const override
|
||||
{
|
||||
return "Water Physics";
|
||||
}
|
||||
};
|
||||
|
||||
#endif // WATER_PHYSICS_EDITOR_HPP
|
||||
101
src/features/editScene/ui/WaterPlaneEditor.hpp
Normal file
101
src/features/editScene/ui/WaterPlaneEditor.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef WATER_PLANE_EDITOR_HPP
|
||||
#define WATER_PLANE_EDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/WaterPlane.hpp"
|
||||
|
||||
/**
|
||||
* Editor for WaterPlane component
|
||||
*/
|
||||
class WaterPlaneEditor : public ComponentEditor<WaterPlane> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity,
|
||||
WaterPlane &component) override
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
ImGui::Text("Water Plane Visualization");
|
||||
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,
|
||||
10.0f, "%.2f")) {
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
} else {
|
||||
ImGui::Text(
|
||||
"Water Surface Y: %.2f (from Water Physics)",
|
||||
component.waterSurfaceY);
|
||||
}
|
||||
|
||||
// Plane size
|
||||
if (ImGui::SliderFloat("Plane Size", &component.planeSize,
|
||||
10.0f, 500.0f, "%.0f")) {
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Visual Appearance");
|
||||
|
||||
// Color picker
|
||||
float color[4] = { component.color.r, component.color.g,
|
||||
component.color.b, component.color.a };
|
||||
if (ImGui::ColorEdit4("Color", color,
|
||||
ImGuiColorEditFlags_AlphaBar)) {
|
||||
component.color = 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;
|
||||
changed = true;
|
||||
component.markDirty();
|
||||
}
|
||||
|
||||
// Material name (read-only for now)
|
||||
ImGui::Text("Material: %s", component.materialName.c_str());
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
const char *getName() const override
|
||||
{
|
||||
return "Water Plane";
|
||||
}
|
||||
};
|
||||
|
||||
#endif // WATER_PLANE_EDITOR_HPP
|
||||
Reference in New Issue
Block a user