Skip to content

Instantly share code, notes, and snippets.

@cosinekitty
Last active April 16, 2024 19:42
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.
#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;
}
/*
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
Copy link

ghost commented Dec 5, 2023

How do I compile this?

@cosinekitty
Copy link
Author

How do I compile this?

Here is the bash script I used to build and run the program on Linux, using gcc 12.2.0:

#!/bin/bash
g++ -Wall -Werror -O3 -o animate animate.cpp -l raylib -l pthread -l dl || exit 1
./animate $1 || exit 1
exit 0

Copy link

ghost commented Dec 6, 2023

How do I compile this?

Here is the bash script I used to build and run the program on Linux, using gcc 12.2.0:

#!/bin/bash
g++ -Wall -Werror -O3 -o animate animate.cpp -l raylib -l pthread -l dl || exit 1
./animate $1 || exit 1
exit 0

Oh , thanks a lot
Works 👍
Thanks for making the video , learnt good things ❤️
Excited for more

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment