Water with floating box scene

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
phillip
Posts: 16
Joined: Thu Nov 05, 2009 9:04 pm

Water with floating box scene

Post by phillip »

Hello All. Here is a simple scene that I was working on to simulate a box floating on water. I know this can be done much better than I did it. So please feel free to update the code and share.

Please change this below lines of code below to the path off your own water and box textures.
water->setMaterialTexture(0, driver->getTexture("water.jpg"));
cube->setMaterialTexture(0, driver->getTexture("wood.jpeg"));

Code: Select all

#include <irrlicht.h>
#include <cmath>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

int main()
{
    // Create device
    IrrlichtDevice *device = createDevice(EDT_OPENGL, dimension2d<u32>(800, 600), 16, false, false, false, 0);

    if (!device)
        return 1;

    device->setWindowCaption(L"Irrlicht Engine - Water with floating box scene ");

    IVideoDriver *driver = device->getVideoDriver();
    ISceneManager *smgr = device->getSceneManager();
    IGUIEnvironment *guienv = device->getGUIEnvironment();

    // Add camera
    smgr->addCameraSceneNode(0, vector3df(0, 100, -200), vector3df(0, 0, 0));

    // Add light
    smgr->addLightSceneNode(0, vector3df(50, 50, 50), SColorf(1.0f, 1.0f, 1.0f), 500.0f);

    // Create custom water surface mesh
    IMesh* waterMesh = smgr->addHillPlaneMesh("myHill",
        dimension2d<f32>(20, 20), dimension2d<u32>(40, 40), 0, 0.0f, dimension2d<f32>(1.0f, 1.0f));
    ISceneNode* water = smgr->addWaterSurfaceSceneNode(waterMesh, 3.0f, 600.0f, 30.0f);
    water->setMaterialFlag(EMF_LIGHTING, false);
    water->setMaterialTexture(0, driver->getTexture("water.jpg"));
    water->setMaterialType(EMT_TRANSPARENT_ADD_COLOR);

    // Add rectangular cube
    ISceneNode *cube = smgr->addCubeSceneNode(40.0f);
    if (cube)
    {
        cube->setPosition(vector3df(0, 5.0f, 0)); // Initially half submerged
        cube->setMaterialTexture(0, driver->getTexture("wood.jpeg"));
        cube->setMaterialFlag(EMF_LIGHTING, false);
    }

    // Physics variables
    f32 cubeMass = 1.0f; // mass of the cube
    f32 gravity = -9.81f; // gravity constant
    f32 cubeVelocity = 0.0f; // initial velocity of the cube
    f32 damping = 0.99f; // damping factor to simulate resistance

    u32 then = device->getTimer()->getTime();

    // Main loop
    while (device->run())
    {

         // Render the scene
        driver->beginScene(true, true, SColor(255, 100, 101, 140));
        smgr->drawAll();
        guienv->drawAll();


        const u32 now = device->getTimer()->getTime();
        const f32 deltaTime = (f32)(now - then) / 1000.f; // Time in seconds
        then = now;

        // Get the cube's current position
        vector3df cubePos = cube->getPosition();

        // Calculate the wave height at the cube's position
        f32 waveHeight = 3.0f * sinf(cubePos.X * 0.05f + now / 1000.0f) * cosf(cubePos.Z * 0.05f + now / 1000.0f);

        // Calculate the wave height at slightly different positions to determine slope
        f32 waveHeightX1 = 3.0f * sinf((cubePos.X + 1) * 0.05f + now / 1000.0f) * cosf(cubePos.Z * 0.05f + now / 1000.0f);
        f32 waveHeightX2 = 3.0f * sinf((cubePos.X - 1) * 0.05f + now / 1000.0f) * cosf(cubePos.Z * 0.05f + now / 1000.0f);
        f32 waveHeightZ1 = 3.0f * sinf(cubePos.X * 0.05f + now / 1000.0f) * cosf((cubePos.Z + 1) * 0.05f + now / 1000.0f);
        f32 waveHeightZ2 = 3.0f * sinf(cubePos.X * 0.05f + now / 1000.0f) * cosf((cubePos.Z - 1) * 0.05f + now / 1000.0f);

        // Calculate the slopes in the X and Z directions
        f32 slopeX = (waveHeightX1 - waveHeightX2) / 2.0f;
        f32 slopeZ = (waveHeightZ1 - waveHeightZ2) / 2.0f;

        // Calculate the rotation angles based on the slopes
        f32 tiltAngleX = atan(slopeZ) * 180.0f / 3.14159265f; // Rotate around the X-axis
        f32 tiltAngleZ = atan(slopeX) * 180.0f / 3.14159265f; // Rotate around the Z-axis

        // Apply the rotations to the cube
        cube->setRotation(vector3df(tiltAngleX, 0, tiltAngleZ));

        // Calculate the buoyancy force based on the wave height
        f32 submergedVolume = (cubePos.Y < waveHeight) ? (waveHeight - cubePos.Y) * 40.0f * 40.0f : 0.0f;
        f32 buoyancy = submergedVolume * 10.0f / cubeMass;

        // Update the velocity with gravity and buoyancy
        cubeVelocity += (gravity + buoyancy) * deltaTime;
        cubeVelocity *= damping; // apply damping

        // Update the cube's position
        cubePos.Y += cubeVelocity * deltaTime;
        cube->setPosition(cubePos);


        driver->endScene();
    }

    device->drop();
    return 0;
}
Noiecity
Posts: 163
Joined: Wed Aug 23, 2023 7:22 pm
Contact:

Re: Water with floating box scene

Post by Noiecity »

Great!

I tried to improve it in performance, but I got a lot of failures in the physics haha, but I still improved the performance (it consumes less CPU and can be faster without sleep, but the CPU consumption increases).

I leave the code as it is a nice experiment of multithreading and ECS, in c++ 17. As the graphics api not read the memory in multithread I had to read the information from the other thread as if it was a network packete like in online games.

To use it you only have to unzip the content of "single_include" into include folder(like include/entt/entt.hpp), from the entt repository(https://github.com/skypjack/entt/) and configure the project to use c++17 (right click in the project properties, in C/C++ go to Language and select in c++ language standard as ISO c++17).

Code: Select all

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

#include <irrlicht.h>
#include "exampleHelper.h"
#include <entt/entt.hpp>
#include <cmath>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <queue>
#include <utility>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

bool Multithread_enabled = true;

struct DeviceProp {
    int WidthResolution = 800;
    int HeightResolution = 600;
    int bitPerPixel = 16;
    bool fullScreen = false;
    bool stencilBuffer = false;
    bool vsync = false;
};

// fast sin and cos
inline f32 fast_sin(f32 x) {
    return 0.955f * sinf(x) + 0.045f * x;
}

inline f32 fast_cos(f32 x) {
    return 0.955f * cosf(x) + 0.045f;
}

// Components
struct Position {
    vector3df value;
};

struct Velocity {
    f32 value;
};

struct Rotation {
    vector3df value;
};

struct Mass {
    f32 value;
};

struct SceneNode {
    ISceneNode* node;
};

void updatePhysics(entt::registry& registry, f32 deltaTime, f32 gravity, f32 damping, f32 now) {
    auto view = registry.view<Position, Velocity, Mass>();
    for (auto entity : view) {
        auto& pos = view.get<Position>(entity);
        auto& vel = view.get<Velocity>(entity);
        auto& mass = view.get<Mass>(entity);

        // Calculate the wave height at the cube's position
        f32 waveHeight = 3.0f * fast_sin(pos.value.X * 0.05f + now / 1000.0f) * fast_cos(pos.value.Z * 0.05f + now / 1000.0f);

        // Calculate the wave height at slightly different positions to determine slope
        f32 waveHeightX1 = 3.0f * fast_sin((pos.value.X + 1) * 0.05f + now / 1000.0f) * fast_cos(pos.value.Z * 0.05f + now / 1000.0f);
        f32 waveHeightX2 = 3.0f * fast_sin((pos.value.X - 1) * 0.05f + now / 1000.0f) * fast_cos(pos.value.Z * 0.05f + now / 1000.0f);
        f32 waveHeightZ1 = 3.0f * fast_sin(pos.value.X * 0.05f + now / 1000.0f) * fast_cos((pos.value.Z + 1) * 0.05f + now / 1000.0f);
        f32 waveHeightZ2 = 3.0f * fast_sin(pos.value.X * 0.05f + now / 1000.0f) * fast_cos((pos.value.Z - 1) * 0.05f + now / 1000.0f);

        // Calculate the slopes in the X and Z directions
        f32 slopeX = (waveHeightX1 - waveHeightX2) / 2.0f;
        f32 slopeZ = (waveHeightZ1 - waveHeightZ2) / 2.0f;

        // Calculate the rotation angles based on the slopes
        f32 tiltAngleX = slopeZ * 57.2957795f; // Rotate around the X-axis (approximate atan)
        f32 tiltAngleZ = slopeX * 57.2957795f; // Rotate around the Z-axis (approximate atan)

        // Apply the rotations to the cube
        if (registry.all_of<Rotation>(entity)) {
            auto& rot = registry.get<Rotation>(entity);
            rot.value = vector3df(tiltAngleX, 0, tiltAngleZ);
        }

        // Calculate the buoyancy force based on the wave height
        f32 submergedVolume = (pos.value.Y < waveHeight) ? (waveHeight - pos.value.Y) * 40.0f * 40.0f : 0.0f;
        f32 buoyancy = submergedVolume * 10.0f / mass.value;

        // Update the velocity with gravity and buoyancy
        vel.value += (gravity + buoyancy) * deltaTime;
        vel.value *= damping; // apply damping

        // Update the cube's position
        pos.value.Y += vel.value * deltaTime;
    }
}

void updateSceneNodes(entt::registry& registry) {
    auto view = registry.view<Position, Rotation, SceneNode>();
    for (auto entity : view) {
        auto& pos = view.get<Position>(entity);
        auto& rot = view.get<Rotation>(entity);
        auto& node = view.get<SceneNode>(entity);

        node.node->setPosition(pos.value);
        node.node->setRotation(rot.value);
    }
}

struct PhysicsResult {
    std::unique_ptr<entt::registry> registry;
    u32 time;

    PhysicsResult(std::unique_ptr<entt::registry> reg, u32 t) : registry(std::move(reg)), time(t) {}
    PhysicsResult(PhysicsResult&& other) noexcept : registry(std::move(other.registry)), time(other.time) {}
    PhysicsResult& operator=(PhysicsResult&& other) noexcept {
        if (this != &other) {
            registry = std::move(other.registry);
            time = other.time;
        }
        return *this;
    }

    PhysicsResult(const PhysicsResult&) = delete;
    PhysicsResult& operator=(const PhysicsResult&) = delete;
};

int main()
{
    DeviceProp deviceProps;
    SIrrlichtCreationParameters params;
    params.DriverType = video::EDT_OPENGL;
    params.WindowSize = dimension2d<u32>(deviceProps.WidthResolution, deviceProps.HeightResolution);
    params.Bits = deviceProps.bitPerPixel;
    params.Fullscreen = deviceProps.fullScreen;
    params.Stencilbuffer = deviceProps.stencilBuffer;
    params.Vsync = deviceProps.vsync;
    IrrlichtDevice* device = createDeviceEx(params);

    if (!device)
        return 1;

    device->setWindowCaption(L"Irrlicht Engine - Water with floating box scene ");

    IVideoDriver* driver = device->getVideoDriver();
    ISceneManager* smgr = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();

    smgr->addCameraSceneNode(0, vector3df(0, 100, -200), vector3df(0, 0, 0));
    smgr->addLightSceneNode(0, vector3df(50, 50, 50), SColorf(1.0f, 1.0f, 1.0f), 500.0f);

    const io::path mediaPath = getExampleMediaPath();

    IMesh* waterMesh = smgr->addHillPlaneMesh("myHill",
        dimension2d<f32>(20, 20), dimension2d<u32>(40, 40), 0, 0.0f, dimension2d<f32>(1.0f, 1.0f));
    ISceneNode* water = smgr->addWaterSurfaceSceneNode(waterMesh, 3.0f, 600.0f, 30.0f);
    water->setMaterialFlag(EMF_LIGHTING, false);
    water->setMaterialTexture(0, driver->getTexture(mediaPath + "water.jpg"));
    water->setMaterialType(EMT_TRANSPARENT_ADD_COLOR);

    auto registry = std::make_unique<entt::registry>();

    auto cubeEntity = registry->create();
    registry->emplace<Position>(cubeEntity, vector3df(0, 5.0f, 0));
    registry->emplace<Velocity>(cubeEntity, 0.0f);
    registry->emplace<Mass>(cubeEntity, 1.0f);
    registry->emplace<Rotation>(cubeEntity, vector3df(0, 0, 0));
    registry->emplace<SceneNode>(cubeEntity, smgr->addCubeSceneNode(40.0f));

    if (registry->get<SceneNode>(cubeEntity).node) {
        registry->get<SceneNode>(cubeEntity).node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.jpg"));
        registry->get<SceneNode>(cubeEntity).node->setMaterialFlag(EMF_LIGHTING, false);
    }

    f32 gravity = -9.81f;
    f32 damping = 0.99f;
    u32 then = device->getTimer()->getTime();
    unsigned int numCores = 1;

    if (Multithread_enabled) {
        numCores = std::thread::hardware_concurrency();
    }
    

    if (numCores >= 2) {
        std::mutex mtx;
        std::condition_variable cv;
        std::queue<PhysicsResult> results;
        std::atomic<bool> done(false);

        std::thread physics([&]() {
            while (!done) {
                const u32 now = device->getTimer()->getTime();
                const f32 deltaTime = static_cast<f32>(now - then) / 1000.f;
                then = now;

                auto tempRegistry = std::make_unique<entt::registry>();
                {
                    std::lock_guard<std::mutex> lock(mtx);
                    auto view = registry->view<Position, Velocity, Mass, Rotation, SceneNode>();
                    for (auto entity : view) {
                        tempRegistry->emplace<Position>(entity, registry->get<Position>(entity));
                        tempRegistry->emplace<Velocity>(entity, registry->get<Velocity>(entity));
                        tempRegistry->emplace<Mass>(entity, registry->get<Mass>(entity));
                        tempRegistry->emplace<Rotation>(entity, registry->get<Rotation>(entity));
                        tempRegistry->emplace<SceneNode>(entity, registry->get<SceneNode>(entity));
                    }
                }

                updatePhysics(*tempRegistry, deltaTime, gravity, damping, now);

                {
                    std::lock_guard<std::mutex> lock(mtx);
                    results.emplace(std::move(tempRegistry), now);
                    cv.notify_one();
                }

                std::this_thread::sleep_for(std::chrono::milliseconds(16));
            }
            });

        while (device->run()) {
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait(lock, [&] { return !results.empty(); });

            PhysicsResult result = std::move(results.front());
            results.pop();
            lock.unlock();

            registry = std::move(result.registry);

            driver->beginScene(true, true, SColor(255, 100, 101, 140));
            smgr->drawAll();
            guienv->drawAll();

            updateSceneNodes(*registry);

            driver->endScene();

            std::this_thread::sleep_for(std::chrono::milliseconds(16));
        }

        done = true;
        physics.join();
    }
    else {
        while (device->run()) {
            const u32 now = device->getTimer()->getTime();
            const f32 deltaTime = static_cast<f32>(now - then) / 1000.f;
            then = now;

            updatePhysics(*registry, deltaTime, gravity, damping, now);

            driver->beginScene(true, true, SColor(255, 100, 101, 140));
            smgr->drawAll();
            guienv->drawAll();

            updateSceneNodes(*registry);

            driver->endScene();

            std::this_thread::sleep_for(std::chrono::milliseconds(16));
        }
    }

    device->drop();
    return 0;
}
**
If you are looking for people with whom to develop your game, even to try functionalities, I can help you, free. CC0 man.

Image
**
Post Reply