Fixed buoyancy

This commit is contained in:
2026-04-22 18:57:33 +03:00
parent ca5b5b3052
commit 35f50f7f51
6 changed files with 37 additions and 135 deletions

View File

@@ -501,20 +501,6 @@ 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()
@@ -674,12 +660,7 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_characterSystem->update(evt.timeSinceLastFrame);
}
/* --- Main physics step --- */
if (m_physicsSystem) {
m_physicsSystem->update(evt.timeSinceLastFrame);
}
/* --- Buoyancy system (after physics) --- */
/* --- Buoyancy system (before physics so impulse is integrated) --- */
if (m_buoyancySystem) {
// Update camera position for water detection area
if (m_camera) {
@@ -689,6 +670,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_buoyancySystem->update(evt.timeSinceLastFrame);
}
/* --- Main physics step --- */
if (m_physicsSystem) {
m_physicsSystem->update(evt.timeSinceLastFrame);
}
/* --- Rendering support systems --- */
if (m_lightSystem) {
m_lightSystem->update();

View File

@@ -8,9 +8,9 @@ struct WaterPhysics {
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 defaultBuoyancy = 1.5f; // 1.0 = neutral, >1 floats (Jolt convention)
float defaultLinearDrag = 0.5f; // Jolt recommends ~0.5
float defaultAngularDrag = 0.01f; // Jolt recommends ~0.01
float defaultSubmergedThreshold =
0.1f; // Minimum submerged fraction to apply buoyancy

View File

@@ -1,6 +1,4 @@
#include "BuoyancySystem.hpp"
#include "../components/EditorMarker.hpp"
#include "../components/EntityName.hpp"
#include "../components/Character.hpp"
#include <OgreLogManager.h>
#include <set>
@@ -10,20 +8,6 @@ BuoyancySystem::BuoyancySystem(flecs::world &world, JoltPhysicsWrapper *physics)
, 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()
@@ -162,7 +146,6 @@ void BuoyancySystem::applyBuoyancyToBody(JPH::BodyID bodyID, float deltaTime,
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) {
@@ -172,25 +155,14 @@ void BuoyancySystem::applyBuoyancyToBody(JPH::BodyID bodyID, float deltaTime,
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
// Let Jolt handle submersion detection internally via ApplyBuoyancyImpulse.
// The previous center-point check prevented partial submersion from working,
// which caused characters standing in shallow water to receive no buoyancy.
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

View File

@@ -231,11 +231,20 @@ void CharacterSystem::update(float deltaTime)
JoltPhysics::convert(nodePos));
}
/* Apply velocity via Jolt linear velocity (matches
* original game code) */
state.character->SetLinearVelocity(
JoltPhysics::convert<JPH::Vec3>(
cc.linearVelocity));
/* Apply velocity via Jolt linear velocity.
* Preserve physics-driven Y velocity when no explicit
* vertical input is given so gravity/buoyancy/jumps
* are not overwritten every frame. */
JPH::Vec3 currentVel = state.character->GetLinearVelocity();
JPH::Vec3 desiredVel = JoltPhysics::convert<JPH::Vec3>(
cc.linearVelocity);
JPH::Vec3 finalVel;
finalVel.SetX(desiredVel.GetX());
finalVel.SetZ(desiredVel.GetZ());
finalVel.SetY(desiredVel.GetY() != 0.0f ?
desiredVel.GetY() :
currentVel.GetY());
state.character->SetLinearVelocity(finalVel);
if (cc.linearVelocity.squaredLength() > 0.0001f) {
std::cout << "CharacterSystem::update: entity="
<< e.id()

View File

@@ -7,6 +7,7 @@
#include "../components/PhysicsCollider.hpp"
#include "../components/RigidBody.hpp"
#include "../components/BuoyancyInfo.hpp"
#include "../components/WaterPhysics.hpp"
#include "../components/Light.hpp"
#include "../components/Camera.hpp"
#include "../components/Lod.hpp"
@@ -311,79 +312,6 @@ void EditorUISystem::renderHierarchyWindow()
}
}
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 = &wp;
});
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();
@@ -683,6 +611,13 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
componentCount++;
}
// Render WaterPhysics if present
if (entity.has<WaterPhysics>()) {
auto &wp = entity.get_mut<WaterPhysics>();
m_componentRegistry.render<WaterPhysics>(entity, wp);
componentCount++;
}
// Render LOD Settings if present
if (entity.has<LodSettingsComponent>()) {
auto &lodSettings = entity.get_mut<LodSettingsComponent>();

View File

@@ -2287,9 +2287,9 @@ void SceneSerializer::deserializeWaterPhysics(flecs::entity entity,
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.defaultBuoyancy = json.value("defaultBuoyancy", 1.5f);
water.defaultLinearDrag = json.value("defaultLinearDrag", 0.5f);
water.defaultAngularDrag = json.value("defaultAngularDrag", 0.01f);
water.defaultSubmergedThreshold =
json.value("defaultSubmergedThreshold", 0.3f);