Gitea
3 years ago
commit
3ba95ff850
13 changed files with 465 additions and 0 deletions
@ -0,0 +1,5 @@ |
|||||||
|
all: |
||||||
|
g++ -lSDL2 -O2 -Wall -Werror src/*.cpp -o cloth_sim
|
||||||
|
|
||||||
|
clean: |
||||||
|
rm -f cloth_sim
|
@ -0,0 +1,95 @@ |
|||||||
|
#include "app.hpp" |
||||||
|
#include "cloth.hpp" |
||||||
|
#include "window.hpp" |
||||||
|
#include <SDL2/SDL_events.h> |
||||||
|
#include <SDL2/SDL_keycode.h> |
||||||
|
#include <SDL2/SDL_render.h> |
||||||
|
#include <SDL2/SDL_timer.h> |
||||||
|
#include <SDL2/SDL_video.h> |
||||||
|
|
||||||
|
App* app_init(App_config conf) { |
||||||
|
App* app = new App; |
||||||
|
|
||||||
|
if (conf.window_dimensions.x <= 0) { |
||||||
|
conf.window_dimensions.x = 500; |
||||||
|
} |
||||||
|
if (conf.window_dimensions.y <= 0) { |
||||||
|
conf.window_dimensions.y = 500; |
||||||
|
} |
||||||
|
app->conf = conf; |
||||||
|
|
||||||
|
app->window = new_window(conf.win_name, conf.window_dimensions); |
||||||
|
app->cloth = new_cloth(conf.cloth_startpos, conf.cloth_dimensions, conf.cloth_spacing); |
||||||
|
|
||||||
|
if (app->window == NULL || app->cloth == NULL) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
return app;
|
||||||
|
} |
||||||
|
|
||||||
|
void app_handle_input(App* app) { |
||||||
|
SDL_Event event; |
||||||
|
while (SDL_PollEvent(&event)) { |
||||||
|
|
||||||
|
switch (event.type) { |
||||||
|
case SDL_QUIT: |
||||||
|
app->is_running = false; |
||||||
|
break; |
||||||
|
|
||||||
|
case SDL_KEYDOWN: |
||||||
|
switch (event.key.keysym.sym) { |
||||||
|
case SDLK_ESCAPE: |
||||||
|
app->is_running = false; |
||||||
|
break; |
||||||
|
|
||||||
|
case SDLK_q: |
||||||
|
app->is_running = false; |
||||||
|
break; |
||||||
|
|
||||||
|
case SDLK_SPACE: |
||||||
|
toggle_pause(app); |
||||||
|
break;
|
||||||
|
}
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void app_render(App* app) {
|
||||||
|
fill_screen(app->window, app->conf.background_color); |
||||||
|
draw_cloth( |
||||||
|
app->window, app->cloth, |
||||||
|
app->conf.point_color, app->conf.connection_color, app->conf.point_selected_color); |
||||||
|
SDL_RenderPresent(app->window->renderer); |
||||||
|
} |
||||||
|
|
||||||
|
void app_update(App* app) { |
||||||
|
if (app->paused) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
compute_cloth_forces(app->cloth, app->conf.gravity); |
||||||
|
|
||||||
|
app->conf.gravity = Vec2f{app->conf.gravity.x-0.0005f, app->conf.gravity.y-0.001f}; |
||||||
|
|
||||||
|
int updated_window_w; |
||||||
|
int updated_window_h; |
||||||
|
SDL_GetWindowSize(app->window->sdl_win, &updated_window_w, &updated_window_h); |
||||||
|
app->window->dimensions = Vec2{(unsigned int) updated_window_w, (unsigned int) updated_window_h}; |
||||||
|
} |
||||||
|
|
||||||
|
void destroy_app(App* app) { |
||||||
|
destroy_cloth(app->cloth); |
||||||
|
destroy_window(app->window); |
||||||
|
app->cloth = NULL; |
||||||
|
app->window = NULL; |
||||||
|
app->is_running = false; |
||||||
|
} |
||||||
|
|
||||||
|
void toggle_pause(App* app) { |
||||||
|
if (app->paused) { |
||||||
|
app->paused = false; |
||||||
|
} else { |
||||||
|
app->paused = true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "window.hpp" |
||||||
|
#include "cloth.hpp" |
||||||
|
#include <SDL2/SDL_render.h> |
||||||
|
|
||||||
|
struct App_config { |
||||||
|
const char* win_name; |
||||||
|
Vec2 window_dimensions; |
||||||
|
Vec2 cloth_startpos; |
||||||
|
Vec2 cloth_dimensions; |
||||||
|
unsigned int cloth_spacing; |
||||||
|
Vec2f gravity; |
||||||
|
RGB background_color; |
||||||
|
RGB point_color; |
||||||
|
RGB point_selected_color; |
||||||
|
RGB connection_color; |
||||||
|
}; |
||||||
|
|
||||||
|
struct App { |
||||||
|
bool is_running; |
||||||
|
bool paused; |
||||||
|
App_config conf; |
||||||
|
Window* window; |
||||||
|
Cloth* cloth; |
||||||
|
}; |
||||||
|
|
||||||
|
App* app_init(App_config conf); |
||||||
|
|
||||||
|
void app_update(App* app); |
||||||
|
void app_handle_input(App* app); |
||||||
|
void app_render(App* app); |
||||||
|
void destroy_app(App* app); |
||||||
|
void toggle_pause(App* app); |
@ -0,0 +1,61 @@ |
|||||||
|
#include "cloth.hpp" |
||||||
|
#include "connection.hpp" |
||||||
|
#include <cstddef> |
||||||
|
#include <cstdio> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
Cloth* new_cloth(Vec2 startpos, Vec2 dimensions, unsigned int spacing) { |
||||||
|
Cloth* new_cloth = new Cloth; |
||||||
|
new_cloth->startpos = startpos; |
||||||
|
new_cloth->dimensions = dimensions; |
||||||
|
new_cloth->points = std::vector<Point*>(); |
||||||
|
new_cloth->connections = std::vector<Connection*>(); |
||||||
|
|
||||||
|
for (unsigned int y = 0; y < dimensions.y; y += spacing) { |
||||||
|
for (unsigned int x = 0; x < dimensions.x; x += spacing) { |
||||||
|
Point* new_point = new Point{static_cast<float>(startpos.x + x), static_cast<float>(startpos.y + y), startpos.x + x, startpos.y + y, false}; |
||||||
|
new_cloth->points.push_back(new_point); |
||||||
|
|
||||||
|
if (x > 0) { |
||||||
|
Point* p0_left = new_cloth->points[new_cloth->points.size() - 2]; |
||||||
|
Connection* new_conn = new Connection{*p0_left, *new_point}; |
||||||
|
new_cloth->connections.push_back(new_conn); |
||||||
|
} |
||||||
|
|
||||||
|
if (y != 0) { |
||||||
|
Point* p0_up = new_cloth->points[new_cloth->points.size()-1 - dimensions.x/spacing]; |
||||||
|
Connection* new_conn = new Connection{*p0_up, *new_point}; |
||||||
|
new_cloth->connections.push_back(new_conn); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new_cloth; |
||||||
|
} |
||||||
|
|
||||||
|
void destroy_cloth(Cloth* cloth) { |
||||||
|
if (cloth == NULL) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < cloth->points.size(); i++) { |
||||||
|
delete cloth->points[i]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < cloth->connections.size(); i++) { |
||||||
|
delete cloth->connections[i]; |
||||||
|
} |
||||||
|
|
||||||
|
cloth->points.clear(); |
||||||
|
cloth->connections.clear(); |
||||||
|
|
||||||
|
delete cloth; |
||||||
|
} |
||||||
|
|
||||||
|
void compute_cloth_forces(Cloth* cloth, Vec2f gravity_vec) { |
||||||
|
for (Point* p : cloth->points) { |
||||||
|
p->x += gravity_vec.x; |
||||||
|
p->y += gravity_vec.y;
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "point.hpp" |
||||||
|
#include "connection.hpp" |
||||||
|
#include "vec.hpp" |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
struct Cloth { |
||||||
|
Vec2 startpos; |
||||||
|
Vec2 dimensions; |
||||||
|
std::vector<Point*> points; |
||||||
|
std::vector<Connection*> connections; |
||||||
|
}; |
||||||
|
|
||||||
|
Cloth* new_cloth(Vec2 startpos, Vec2 dimensions, unsigned int spacing); |
||||||
|
void destroy_cloth(Cloth* cloth); |
||||||
|
void compute_cloth_forces(Cloth* cloth, Vec2f gravity_vec); |
@ -0,0 +1,7 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
struct RGB { |
||||||
|
unsigned char r; |
||||||
|
unsigned char g; |
||||||
|
unsigned char b; |
||||||
|
}; |
@ -0,0 +1,8 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "point.hpp" |
||||||
|
|
||||||
|
struct Connection { |
||||||
|
Point& p0; |
||||||
|
Point& p1; |
||||||
|
}; |
@ -0,0 +1,97 @@ |
|||||||
|
#include "app.hpp" |
||||||
|
#include <SDL2/SDL_error.h> |
||||||
|
#include <cstdlib> |
||||||
|
#include <ostream> |
||||||
|
#include <string> |
||||||
|
#include <thread> |
||||||
|
#include <SDL2/SDL_timer.h> |
||||||
|
|
||||||
|
const char* HELP_ARG = "--help"; |
||||||
|
const char* HELP_ARG_LITTLE = "-h"; |
||||||
|
const char* WINDOW_NAME_ARG = "--window_name"; |
||||||
|
const char* WINDOW_NAME_ARG_LITTLE = "-wn"; |
||||||
|
const char* FRAMERATE_ARG = "--framerate"; |
||||||
|
const char* FRAMERATE_ARG_LITTLE = "-fr"; |
||||||
|
|
||||||
|
void print_help() { |
||||||
|
printf("cloth_sim\n"); |
||||||
|
|
||||||
|
printf("\nARGS\n"); |
||||||
|
printf("%s or %s --> print this message and exit\n", HELP_ARG, HELP_ARG_LITTLE); |
||||||
|
printf("%s (string) or %s (string) --> set a custom window name\n", WINDOW_NAME_ARG, WINDOW_NAME_ARG_LITTLE); |
||||||
|
printf("%s (unsigned integer) or %s (unsigned integer) --> set framerate\n", FRAMERATE_ARG, FRAMERATE_ARG_LITTLE); |
||||||
|
|
||||||
|
printf("\nKEYS\n"); |
||||||
|
printf("ESC or q --> exit\n"); |
||||||
|
printf("SPACE --> pause\n"); |
||||||
|
} |
||||||
|
|
||||||
|
int main(const int argc, char** argv) { |
||||||
|
std::string window_name = "cloth simulation"; |
||||||
|
unsigned int FRAMERATE = 60; |
||||||
|
const unsigned int FRAME_TIME = 1000.0f / FRAMERATE; |
||||||
|
|
||||||
|
unsigned int i = 0; |
||||||
|
while (i < (unsigned int) argc) { |
||||||
|
if (strcmp(argv[i], HELP_ARG) == 0 || strcmp(argv[i], HELP_ARG_LITTLE) == 0) { |
||||||
|
print_help(); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
if (strcmp(argv[i], WINDOW_NAME_ARG) == 0 || strcmp(argv[i], WINDOW_NAME_ARG_LITTLE) == 0) { |
||||||
|
if (i == (unsigned int) (argc-1)) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
i++; |
||||||
|
window_name = std::string(argv[i]); |
||||||
|
} |
||||||
|
|
||||||
|
if (strcmp(argv[i], FRAMERATE_ARG) == 0 || strcmp(argv[i], FRAMERATE_ARG_LITTLE) == 0) { |
||||||
|
if (i == (unsigned int) (argc-1)) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
i++; |
||||||
|
FRAMERATE = std::stoi(argv[i]); |
||||||
|
if (FRAMERATE == 0) { |
||||||
|
FRAMERATE = 60; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
i++; |
||||||
|
} |
||||||
|
|
||||||
|
App_config config = { |
||||||
|
.win_name = window_name.c_str(), |
||||||
|
.window_dimensions = Vec2{500, 500}, |
||||||
|
.cloth_startpos = Vec2{50, 50}, |
||||||
|
.cloth_dimensions = Vec2{400, 400}, |
||||||
|
.cloth_spacing = 20, |
||||||
|
.gravity = Vec2f{0.3f, 0.8f}, |
||||||
|
.background_color = RGB{80, 100, 255}, |
||||||
|
.point_color = RGB{10, 10, 10}, |
||||||
|
.point_selected_color = RGB{200, 40, 12}, |
||||||
|
.connection_color = RGB{100, 100, 100}, |
||||||
|
}; |
||||||
|
App* app = app_init(config); |
||||||
|
|
||||||
|
app->is_running = true; |
||||||
|
unsigned int frame_start_time, total_frame_time; |
||||||
|
while (app->is_running) { |
||||||
|
frame_start_time = SDL_GetTicks(); |
||||||
|
|
||||||
|
app_handle_input(app); |
||||||
|
app_update(app); |
||||||
|
app_render(app); |
||||||
|
|
||||||
|
total_frame_time = SDL_GetTicks() - frame_start_time; |
||||||
|
if (total_frame_time < FRAME_TIME) { |
||||||
|
SDL_Delay(FRAME_TIME - total_frame_time); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
destroy_app(app); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
// struct Point {
|
||||||
|
// unsigned int x;
|
||||||
|
// unsigned int y;
|
||||||
|
// unsigned int prev_x;
|
||||||
|
// unsigned int prev_y;
|
||||||
|
// unsigned int mass;
|
||||||
|
// bool selected;
|
||||||
|
// };
|
||||||
|
|
||||||
|
struct Point { |
||||||
|
float x; |
||||||
|
float y; |
||||||
|
unsigned int prev_x; |
||||||
|
unsigned int prev_y; |
||||||
|
unsigned int mass; |
||||||
|
bool selected; |
||||||
|
}; |
@ -0,0 +1,11 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
struct Vec2 { |
||||||
|
unsigned int x; |
||||||
|
unsigned int y; |
||||||
|
}; |
||||||
|
|
||||||
|
struct Vec2f { |
||||||
|
float x; |
||||||
|
float y; |
||||||
|
}; |
@ -0,0 +1,90 @@ |
|||||||
|
#include "window.hpp" |
||||||
|
#include <SDL2/SDL.h> |
||||||
|
#include <SDL2/SDL_error.h> |
||||||
|
#include <SDL2/SDL_rect.h> |
||||||
|
#include <SDL2/SDL_render.h> |
||||||
|
#include <SDL2/SDL_video.h> |
||||||
|
#include <cstdio> |
||||||
|
#include <stdexcept> |
||||||
|
|
||||||
|
Window* new_window(const char* window_name, Vec2 dimensions) { |
||||||
|
Window* new_win = new Window; |
||||||
|
new_win->dimensions = dimensions; |
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) { |
||||||
|
printf("[ERROR] Window initialization error: %s\n", SDL_GetError()); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
new_win->sdl_win = SDL_CreateWindow( |
||||||
|
window_name, |
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, |
||||||
|
dimensions.x, dimensions.y, |
||||||
|
SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE); |
||||||
|
|
||||||
|
if (new_win->sdl_win == NULL) { |
||||||
|
printf("[ERROR] Could not create SDL window: %s\n", SDL_GetError()); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
new_win->renderer = SDL_CreateRenderer(new_win->sdl_win, -1, SDL_RENDERER_PRESENTVSYNC); |
||||||
|
|
||||||
|
return new_win; |
||||||
|
} |
||||||
|
|
||||||
|
void destroy_window(Window* window) { |
||||||
|
if (window == NULL) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (window->sdl_win != NULL) { |
||||||
|
SDL_DestroyWindow(window->sdl_win); |
||||||
|
window->sdl_win = NULL; |
||||||
|
SDL_Quit(); |
||||||
|
} |
||||||
|
|
||||||
|
if (window->renderer != NULL) { |
||||||
|
SDL_DestroyRenderer(window->renderer); |
||||||
|
window->renderer = NULL; |
||||||
|
} |
||||||
|
|
||||||
|
delete window; |
||||||
|
window = NULL; |
||||||
|
} |
||||||
|
|
||||||
|
void fill_screen(Window* window, RGB color) { |
||||||
|
if (window->renderer != NULL) { |
||||||
|
SDL_SetRenderDrawColor(window->renderer, color.r, color.g, color.b, 255); |
||||||
|
SDL_RenderClear(window->renderer); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void draw_point(Window* window, Point point, RGB color) { |
||||||
|
if (window->renderer != NULL) {
|
||||||
|
SDL_SetRenderDrawColor(window->renderer, color.r, color.g, color.b, 255); |
||||||
|
SDL_RenderDrawPoint(window->renderer, (int) point.x, (int) point.y); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void draw_connection(Window* window, Connection connection, RGB color) { |
||||||
|
if (window->renderer != NULL) { |
||||||
|
SDL_SetRenderDrawColor(window->renderer, color.r, color.g, color.b, 255); |
||||||
|
SDL_RenderDrawLine( |
||||||
|
window->renderer, |
||||||
|
(int) connection.p0.x, |
||||||
|
(int) connection.p0.y, |
||||||
|
(int) connection.p1.x, |
||||||
|
(int) connection.p1.y |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void draw_cloth(Window* window, Cloth* cloth, RGB point_color, RGB connection_color, RGB selected_p_color) { |
||||||
|
for (const Connection* conn : cloth->connections) { |
||||||
|
draw_connection(window, *conn, connection_color); |
||||||
|
} |
||||||
|
|
||||||
|
for (const Point* point : cloth->points) { |
||||||
|
draw_point(window, *point, point_color); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <SDL2/SDL.h> |
||||||
|
#include <SDL2/SDL_render.h> |
||||||
|
#include "cloth.hpp" |
||||||
|
#include "color.hpp" |
||||||
|
|
||||||
|
struct Window { |
||||||
|
Vec2 dimensions; |
||||||
|
SDL_Window* sdl_win; |
||||||
|
SDL_Renderer* renderer; |
||||||
|
}; |
||||||
|
|
||||||
|
Window* new_window(const char* window_name, Vec2 dimensions); |
||||||
|
void destroy_window(Window* window); |
||||||
|
void render_cloth(Window* window, Cloth* cloth); |
||||||
|
void fill_screen(Window* window, RGB color); |
||||||
|
void draw_point(Window* window, Point point); |
||||||
|
void draw_connection(Window* window, Connection connection); |
||||||
|
void draw_cloth(Window* window, Cloth* cloth, RGB point_color, RGB connection_color, RGB selected_p_color); |
Loading…
Reference in new issue