a simple particle physics sandbox in C++
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

286 lines
6.9 KiB

#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_pixels.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_scancode.h>
#include <SDL2/SDL_surface.h>
#include <SDL2/SDL_timer.h>
#include <SDL2/SDL_video.h>
#include <deque>
#include <iostream>
#include <string>
#include <vector>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#endif
#include "particles.h"
namespace pengine {
class GameWindow {
private:
std::string name;
public:
struct SDL_Window *window;
struct SDL_Renderer *renderer;
GameWindow(int width, int height, std::string name) {
SDL_Init(SDL_INIT_VIDEO);
this->name = name;
// SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas");
window = SDL_CreateWindow(name.c_str(), SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, width, height,
SDL_WINDOW_BORDERLESS);
renderer = SDL_CreateRenderer(
window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
}
~GameWindow() {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
};
enum MouseAction { NONE, LEFT, RIGHT };
class PaintTool {
public:
int particle_selected = 0;
int size = 1;
MouseAction mouse_state = NONE;
bool paused = false;
};
class Game {
GameWindow win;
Grid grid;
SDL_Texture *grid_texture;
SDL_Renderer *ren;
int internal_sz[2];
int window_sz[2];
struct su_pair {
int x;
int y;
} coord;
std::vector<Particle *> particles = std::vector<Particle *>();
PaintTool tool = PaintTool();
public:
Game(int x, int y, int gx, int gy)
: win{GameWindow{x, y, "sand"}}, grid{Grid{gx, gy}} {
ren = win.renderer;
/* texture for drawing particles into */
grid_texture = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STATIC, gx, gy);
internal_sz[0] = gx;
internal_sz[1] = gy;
window_sz[0] = x;
window_sz[1] = y;
particles.push_back(new Sand());
particles.push_back(new Water());
particles.push_back(new Steam());
particles.push_back(new Lava());
}
~Game() {
SDL_DestroyTexture(grid_texture);
for (auto p : particles) {
delete p;
}
}
void update() { grid.update(); }
void draw_tool() {
if (tool.size == 1) {
int i = coord.x;
int j = coord.y;
if (grid.inside_grid(i, j)) {
auto colour = PixelColour(grid.pixels[i + j * grid.xsize]);
colour.r = 255 - colour.r;
colour.g = 255 - colour.g;
colour.b = 255 - colour.b;
grid.pixels[i + j * grid.xsize] = colour.get_pixelcol();
}
return;
}
for (int i = coord.x - tool.size / 2; i < coord.x + tool.size / 2; i++) {
for (int j = coord.y - tool.size / 2; j < coord.y + tool.size / 2; j++) {
if (grid.inside_grid(i, j)) {
auto colour = PixelColour(grid.pixels[i + j * grid.xsize]);
colour.r = 255 - colour.r;
colour.g = 255 - colour.g;
colour.b = 255 - colour.b;
grid.pixels[i + j * grid.xsize] = colour.get_pixelcol();
}
}
}
}
void remove_particles() {
if (tool.size <= 1) {
if (grid.inside_grid(coord.x, coord.y)) {
grid.erase_particle_at(coord.x, coord.y);
}
return;
}
for (int i = coord.x - tool.size / 2; i < coord.x + tool.size / 2; i++) {
for (int j = coord.y - tool.size / 2; j < coord.y + tool.size / 2; j++) {
if (grid.inside_grid(i, j)) {
grid.erase_particle_at(i, j);
}
}
}
}
void add_particles() {
if (tool.size <= 1) {
if (grid.inside_grid(coord.x, coord.y)) {
grid.add_particle_at(coord.x, coord.y,
particles[tool.particle_selected]->id);
}
return;
}
for (int i = coord.x - tool.size / 2; i < coord.x + tool.size / 2; i++) {
for (int j = coord.y - tool.size / 2; j < coord.y + tool.size / 2; j++) {
if (grid.inside_grid(i, j)) {
grid.add_particle_at(i, j, particles[tool.particle_selected]->id);
}
}
}
}
void draw() {
SDL_SetRenderDrawColor(ren, 40, 40, 40, 255);
SDL_RenderClear(ren);
grid.update_texture();
draw_tool();
SDL_UpdateTexture(grid_texture, NULL, grid.pixels,
grid.xsize * sizeof(Uint32));
SDL_RenderCopy(ren, grid_texture, NULL, NULL);
SDL_RenderPresent(ren);
}
struct su_pair get_click_location_in_grid(int x, int y) {
struct su_pair p;
p.x = (internal_sz[0] * x / window_sz[0]) % internal_sz[0];
p.y = (internal_sz[1] * y / window_sz[1]) % internal_sz[1];
return p;
}
bool run() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
return true;
case SDL_WINDOWEVENT_RESIZED:
SDL_GetWindowSize(win.window, internal_sz, internal_sz + 1);
break;
case SDL_KEYDOWN:
if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
tool.paused = !tool.paused;
}
if (event.key.keysym.scancode == SDL_SCANCODE_P) {
tool.paused = !tool.paused;
}
if (event.key.keysym.scancode == SDL_SCANCODE_C) {
grid.clear();
}
if (event.key.keysym.scancode == SDL_SCANCODE_1) {
tool.particle_selected = 0;
};
if (event.key.keysym.scancode == SDL_SCANCODE_2) {
tool.particle_selected = 1;
};
if (event.key.keysym.scancode == SDL_SCANCODE_3) {
tool.particle_selected = 2;
};
if (event.key.keysym.scancode == SDL_SCANCODE_4) {
tool.particle_selected = 3;
};
break;
case SDL_MOUSEMOTION:
coord = get_click_location_in_grid(event.motion.x, event.motion.y);
break;
case SDL_KEYUP:
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == 1) {
tool.mouse_state = LEFT;
} else if (event.button.button == 3) {
tool.mouse_state = RIGHT;
}
break;
case SDL_MOUSEBUTTONUP:
tool.mouse_state = NONE;
break;
case SDL_MOUSEWHEEL:
tool.size += event.wheel.y;
break;
}
}
if (tool.mouse_state == LEFT) {
add_particles();
} else if (tool.mouse_state == RIGHT) {
remove_particles();
}
if (!tool.paused) {
update();
}
draw();
return false;
}
};
} // namespace pengine
pengine::Game *g_game;
void main_loop() { g_game->run(); }
int main(int argc, char **argv) {
const char *error = SDL_GetError();
if (error) {
std::cout << error;
}
bool quit = false;
int x = 500;
int y = 500;
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &x, &y);
#endif
pengine::Game game{pengine::Game(x, y, x, y)};
g_game = &game;
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(main_loop, 0, true);
#else
while (!quit) {
quit = game.run();
}
#endif
return 0;
}