Files
ogre-prototype/buoyancy_analysis.md
T
2026-05-25 04:19:03 +03:00

8.1 KiB

Buoyancy System Analysis

Problem: Characters are not affected by buoyancy

After analyzing the code in src/features/editScene, I've identified several potential issues:

1. Character Gravity Factor Issue

Root Cause: Characters have gravity factor set to 0.0f by default.

In CharacterSystem.cpp line 163:

m_physics->setGravityFactor(ch->GetBodyID(), 0.0f);

This means characters won't sink into water naturally. The BuoyancySystem tries to handle this by setting gravity factor to 1.0f when characters are in water (line 127 in BuoyancySystem.cpp), but there may be timing or detection issues.

2. Broadphase Query Area Settings

The broadphaseQuery function in physics.cpp uses these settings:

JPH::AABox water_box(-JPH::Vec3(1000, 1000, 1000),
                     JPH::Vec3(1000, 0.1f, 1000));
water_box.Translate(JPH::Vec3(surface_point));

Where surface_point = position + Ogre::Vector3(0, -0.1f, 0) (position is the water surface Y level).

Dimensions:

  • X: -1000 to 1000 (2000 units wide, centered at surface_point.x)
  • Y: -1000 to 0.1f (1000.1 units tall, but centered 0.1 units BELOW water surface)
  • Z: -1000 to 1000 (2000 units deep, centered at surface_point.z)

Issue: The water box extends 1000 units BELOW the surface point, but only 0.1 units ABOVE it. Since surface_point is 0.1 units below the actual water surface, the box effectively covers:

  • From 1000.1 units below water surface
  • To 0.0 units at water surface (not above it)

This means bodies need to be at or below the water surface to be detected.

3. Character Detection in Broadphase

Characters are JPH::Character objects, not regular dynamic bodies. The broadphase query filters for:

  • BroadPhaseLayers::MOVING layer
  • Layers::MOVING object layer

Characters should be in these layers, but there may be issues with how character bodies are registered in the broadphase.

4. Debugging Approach

4.1 Enable Debug Logging

Modify BuoyancySystem.cpp to add debug logging:

// In update() method, after broadphaseQuery call:
Ogre::LogManager::getSingleton().logMessage(
    "BuoyancySystem: Found " + Ogre::StringConverter::toString(m_bodiesInWater.size()) + 
    " bodies in water");

// In the loop applying buoyancy:
for (JPH::BodyID bodyID : m_bodiesInWater) {
    Ogre::SceneNode *node = m_physics->getSceneNodeFromBodyID(bodyID);
    if (node) {
        Ogre::LogManager::getSingleton().logMessage(
            "Body " + Ogre::StringConverter::toString(bodyID.GetIndex()) + 
            " at position: " + Ogre::StringConverter::toString(m_physics->getPosition(bodyID)));
    }
    
    // Check if it's a character
    if (m_physics->bodyIsCharacter(bodyID)) {
        Ogre::LogManager::getSingleton().logMessage(
            "Body " + Ogre::StringConverter::toString(bodyID.GetIndex()) + " is a character");
    }
}

4.2 Visual Debugging - Draw Water Box

Add debug rendering to visualize the water detection area:

// In BuoyancySystem::update(), after broadphaseQuery:
void drawDebugWaterBox(const Ogre::Vector3& waterSurfacePos) {
    // Create a manual object to visualize the water box
    static Ogre::ManualObject* waterBoxDebug = nullptr;
    if (!waterBoxDebug) {
        waterBoxDebug = m_sceneManager->createManualObject("WaterBoxDebug");
        Ogre::SceneNode* debugNode = m_sceneManager->getRootSceneNode()->createChildSceneNode();
        debugNode->attachObject(waterBoxDebug);
    }
    
    waterBoxDebug->clear();
    waterBoxDebug->begin("BaseWhiteNoLighting", Ogre::RenderOperation::OT_LINE_LIST);
    
    // Water box dimensions (matching broadphaseQuery)
    float halfSize = 1000.0f;
    float top = waterSurfacePos.y - 0.1f + 0.1f; // surface_point.y + 0.1f
    float bottom = waterSurfacePos.y - 0.1f - 1000.0f; // surface_point.y - 1000.0f
    
    // Draw box edges
    Ogre::Vector3 corners[8] = {
        {waterSurfacePos.x - halfSize, bottom, waterSurfacePos.z - halfSize},
        {waterSurfacePos.x + halfSize, bottom, waterSurfacePos.z - halfSize},
        {waterSurfacePos.x + halfSize, bottom, waterSurfacePos.z + halfSize},
        {waterSurfacePos.x - halfSize, bottom, waterSurfacePos.z + halfSize},
        {waterSurfacePos.x - halfSize, top, waterSurfacePos.z - halfSize},
        {waterSurfacePos.x + halfSize, top, waterSurfacePos.z - halfSize},
        {waterSurfacePos.x + halfSize, top, waterSurfacePos.z + halfSize},
        {waterSurfacePos.x - halfSize, top, waterSurfacePos.z + halfSize}
    };
    
    // Bottom square
    for (int i = 0; i < 4; i++) {
        waterBoxDebug->position(corners[i]);
        waterBoxDebug->position(corners[(i+1)%4]);
    }
    
    // Top square
    for (int i = 4; i < 8; i++) {
        waterBoxDebug->position(corners[i]);
        waterBoxDebug->position(corners[4 + (i-3)%4]);
    }
    
    // Vertical edges
    for (int i = 0; i < 4; i++) {
        waterBoxDebug->position(corners[i]);
        waterBoxDebug->position(corners[i+4]);
    }
    
    waterBoxDebug->end();
}

4.3 Check Character Position Relative to Water

Add a debug function to check character positions:

void debugCharacterPositions() {
    m_world.query<CharacterComponent, TransformComponent>().each(
        [&](flecs::entity entity, CharacterComponent &cc, TransformComponent &transform) {
            if (transform.node) {
                Ogre::Vector3 worldPos = transform.node->_getDerivedPosition();
                Ogre::LogManager::getSingleton().logMessage(
                    "Character entity " + Ogre::StringConverter::toString(entity.id()) +
                    " at Y: " + Ogre::StringConverter::toString(worldPos.y));
                
                // Check if character has physics body
                auto it = m_states.find(entity.id());
                if (it != m_states.end() && it->second.character) {
                    JPH::BodyID bodyID = it->second.character->GetBodyID();
                    Ogre::Vector3 bodyPos = m_physics->getPosition(bodyID);
                    Ogre::LogManager::getSingleton().logMessage(
                        "Character body at Y: " + Ogre::StringConverter::toString(bodyPos.y) +
                        ", gravity factor: " + Ogre::StringConverter::toString(m_physics->getGravityFactor(bodyID)));
                }
            }
        });
}

5.1 Adjust Water Box Parameters

The current water box may be too shallow (only 0.1 units at the top). Consider adjusting:

// In broadphaseQuery function:
JPH::AABox water_box(-JPH::Vec3(1000, 1.0f, 1000),  // Increased from 0.1f to 1.0f
                     JPH::Vec3(1000, 1000, 1000));  // Symmetrical above/below

This creates a 2-unit tall detection area centered on the surface point.

5.2 Fix Character Gravity Handling

Modify BuoyancySystem::update() to better handle character gravity:

// Current issue: characters with gravity factor 0 won't sink into water
// Even if buoyancy is applied, they need gravity to sink first

// Potential fix: Always give characters some minimal gravity when near water
// or modify CharacterSystem to not set gravity factor to 0

5.3 Verify Character Body Registration

Ensure character bodies are properly registered in the physics system and included in broadphase queries. Check that:

  1. Characters are added to the physics system (ch->AddToPhysicsSystem())
  2. They are in the MOVING broadphase layer
  3. Their body IDs are valid for queries

6. Testing Procedure

  1. Enable debug logging as shown above
  2. Place a character in water (Y position below water surface)
  3. Check console output for:
    • Number of bodies detected in water
    • Character body positions
    • Gravity factor changes
  4. Use visual debug to see water box
  5. Adjust water surface Y in WaterPhysics component to ensure it's above character position

7. Water Physics Settings

Default WaterPhysics component has:

  • waterSurfaceY = -0.1f (slightly below origin)
  • defaultBuoyancy = 1.0f (neutral buoyancy)
  • enabled = true

Make sure:

  1. WaterPhysics entity exists (BuoyancySystem creates one if missing)
  2. waterSurfaceY is above character positions for testing
  3. Water physics is enabled (enabled = true)