Skip to content

Instantly share code, notes, and snippets.

@cosinekitty
Last active March 9, 2025 13:52
Show Gist options
  • Save cosinekitty/e8f4f3cf34019bb873ad2d7f552d7fa6 to your computer and use it in GitHub Desktop.
Save cosinekitty/e8f4f3cf34019bb873ad2d7f552d7fa6 to your computer and use it in GitHub Desktop.

Revisions

  1. cosinekitty revised this gist Jul 8, 2023. 1 changed file with 109 additions and 0 deletions.
    109 changes: 109 additions & 0 deletions animate.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,109 @@
    #include <cstdio>
    #include <cmath>
    #include <string>
    #include "raylib.h"
    #include "waterpool.hpp"

    const int WIDTH = 160;
    const int HEIGHT = 160;
    using PoolType = Sapphire::WaterPool<WIDTH, HEIGHT>;

    const int PIXELS_PER_CELL = 4;


    struct RenderContext
    {
    const int screenWidth = WIDTH * PIXELS_PER_CELL;
    const int screenHeight = HEIGHT * PIXELS_PER_CELL;
    float zoom = 8000.0f; // pixels per meter
    float xCenter = 0.05f;
    float yCenter = 0.00f;

    int xScreen(float x) const
    {
    return (screenWidth/2) + static_cast<int>(round(zoom * (x - xCenter)));
    }

    int yScreen(float y) const
    {
    return (screenHeight/2) - static_cast<int>(round(zoom * (y - yCenter)));
    }

    int scale(float r) const
    {
    return static_cast<int>(round(zoom * r));
    }

    static Color cellColor(const Sapphire::WaterCell& cell)
    {
    using namespace Sapphire;

    if (cell.wet == 0.0f)
    return WHITE;

    float g = 128.0f * (cell.pos + 1.0f);
    if (g > 255.0f)
    g = 255.0f;
    else if (g < 0.0f)
    g = 0.0f;
    return CLITERAL(Color){0, static_cast<unsigned char>(g), 32, 255};
    }

    void draw(const PoolType& pool)
    {
    using namespace Sapphire;

    for (int i = 0; i < WIDTH; ++i)
    {
    for (int j = 0; j < HEIGHT; ++j)
    {
    const WaterCell& cell = pool.getCell(i, j);
    Color color = cellColor(cell);
    DrawRectangle(i*PIXELS_PER_CELL, j*PIXELS_PER_CELL, PIXELS_PER_CELL, PIXELS_PER_CELL, color);
    }
    }
    }
    };


    int main(int argc, const char *argv[])
    {
    using namespace Sapphire;

    float dt = 1.0f / 48000.0f;
    float halflife = 0.07f;
    float c = 2.0f; // speed of waves in meters/second
    float s = 0.001f; // grid spacing in meters
    float k = (c*c) / (s*s); // propagation constant [second^(-2)]
    PoolType pool;
    RenderContext render;

    for (int i = 0; i < 3; ++i)
    {
    for (int j = 0; j < 3; ++j)
    {
    int r = 1 + i*i + j*j;
    pool.getCell(i + WIDTH/5, j + HEIGHT/2).vel = +20000.0f / r;
    }
    }

    // Create reflective barriers.
    for (int i = 30; i+10 < WIDTH; ++i)
    {
    pool.getCell(i, HEIGHT/2-7).wet = 0.0f;
    pool.getCell(i-13, HEIGHT/2+17).wet = 0.0f;
    }

    InitWindow(render.screenWidth, render.screenHeight, "Water Simulation by Don Cross");
    SetTargetFPS(240);
    while (!WindowShouldClose())
    {
    BeginDrawing();
    ClearBackground(BLACK);
    render.draw(pool);
    EndDrawing();
    pool.update(dt, halflife, k);
    }
    CloseWindow();
    return 0;
    }
  2. cosinekitty created this gist Jul 8, 2023.
    99 changes: 99 additions & 0 deletions waterpool.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,99 @@
    /*
    waterpool.hpp - Don Cross - 2023-06-26
    Simulation of waves moving on the surface of water.
    Based on ideas from the video
    "How to write a Height-Field Water Simulator with 100 lines of code"
    by Matthias Müller / Ten Minute Physics:
    https://www.youtube.com/watch?v=hswBi5wcqAA
    */
    #ifndef __COSINEKITTY_WATERPOOL_HPP
    #define __COSINEKITTY_WATERPOOL_HPP

    #include <vector>
    #include <cmath>

    namespace Sapphire
    {
    struct WaterCell
    {
    float wet {1.0f};
    float pos {0.0f};
    float vel {0.0f};
    float acc {0.0f};
    };

    template <int WIDTH, int HEIGHT>
    class WaterPool
    {
    private:
    static_assert(WIDTH > 0, "Width must be a positive integer.");
    static_assert(HEIGHT > 0, "Height must be a positive integer.");
    static const int SIZE = WIDTH * HEIGHT;

    std::vector<WaterCell> cell { SIZE };

    static constexpr int index(int i, int j)
    {
    // Use border-wraparound logic.
    // Tolerate -1, but not arbitrarily negative integers.
    return ((i + WIDTH)%WIDTH) + WIDTH*((j + HEIGHT)%HEIGHT);
    }

    float acceleration(const WaterCell& h, int i, int j) const
    {
    const WaterCell& o = cell[index(i, j)];
    return o.wet*(o.pos - h.pos);
    }

    public:
    const WaterCell& getCell(int i, int j) const
    {
    return cell.at(index(i, j));
    }

    WaterCell& getCell(int i, int j)
    {
    return cell.at(index(i, j));
    }

    void update(float dt, float halflife, float k)
    {
    const float damp = pow(0.5, dt/halflife);

    // Calculate acceleration of each water cell.
    for (int i = 0; i < WIDTH; ++i)
    {
    for (int j = 0; j < HEIGHT; ++j)
    {
    WaterCell& h = cell[index(i, j)];
    if (h.wet > 0.0f)
    {
    h.acc = k * (
    acceleration(h, i, j+1) +
    acceleration(h, i, j-1) +
    acceleration(h, i-1, j) +
    acceleration(h, i+1, j)
    );
    }
    }
    }

    // Use accelerations to update position and velocity of each water cell.
    for (int i = 0; i < WIDTH; ++i)
    {
    for (int j = 0; j < HEIGHT; ++j)
    {
    WaterCell& h = cell[index(i, j)];
    if (h.wet > 0.0f)
    {
    h.vel = (damp * h.vel) + (dt * h.acc);
    h.pos += (dt * h.vel);
    }
    }
    }
    }
    };
    }

    #endif // __COSINEKITTY_WATERPOOL_HPP