/* Copyright 2020 KoroLion (github.com/KoroLion) */ #include #include #include #include #include #include #include #include #include "SDL.h" #include "SDL_TTF.h" const int WIN_WIDTH = 1280; const int WIN_HEIGHT = 800; const char* WIN_TITLE = "Fall Simulator"; const int FPS = 60; #define PI 3.1415 // configurate here #define HEIGHT 200 #define SPEED 100 #define ANGLE 60 // endconf template float sqr(T a) { return a * a; } std::string floatToStr(float v, int p) { std::stringstream stream; stream << std::fixed << std::setprecision(p) << v; std::string s = stream.str(); return s; } void renderText(SDL_Renderer *renderer, const char *s, float x, float y, float size) { SDL_Texture *texture; TTF_Font *font = TTF_OpenFont("FreeSans.ttf", size); SDL_Surface *surface; SDL_Color textColor = {128, 128, 128, 0}; surface = TTF_RenderText_Solid(font, s, textColor); texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_Rect rect{(int)round(x), (int)round(y), surface->w, surface->h}; SDL_RenderCopy(renderer, texture, NULL, &rect); SDL_FreeSurface(surface); } class Object { protected: float x, y; int width, height; public: Object(float x, float y, int width, int height): x(x), y(y), width(width), height(height) {}; float getX() const { return x; } float getY() const { return y; } void setX(float x) { this->x = x; } void setY(float y) { this->y = y; } virtual void update(int fps) {}; virtual void render(SDL_Renderer *renderer) { SDL_SetRenderDrawColor(renderer, 64, 80, 64, 0); SDL_Rect r{(int)x, (int)y, width, height}; SDL_RenderFillRect(renderer, &r); }; }; class PhysObject: public Object { protected: float speedX, speedY, aX, aY; float m = 1; public: PhysObject(): Object(0, 0, 1, 1), speedX(0), speedY(0), aX(0), aY(0) {} PhysObject(float x, float y, float speed, float angle): Object(x, y, 15, 15) { speedX = speed * cos(angle * PI / 180); speedY = speed * sin(angle * PI / 180); aX = 0; aY = 9.8; } float getSpeedX() const { return speedX; } float getSpeedY() const { return speedY; } float getWidth() const { return width; } float getHeight() const { return height; } float getSpeed() { return sqrt(sqr(speedX) + sqr(speedY)); } float getEKin() { return sqr(getSpeed()) * m / 2.0; } float getEPot(float height) { return m * aY * height; } void setSpeedX(float speedX) { this->speedX = speedX; } void setSpeedY(float speedY) { this->speedY = speedY; } void update(int fps) { x += speedX / fps; y -= speedY / fps; speedX += aX / fps; speedY -= aY / fps; // y axes is reversed } void render(SDL_Renderer *renderer) { SDL_SetRenderDrawColor(renderer, 32, 200, 32, 0); SDL_Rect r{(int)x, (int)y, width, height}; SDL_RenderFillRect(renderer, &r); } }; class Simulation { private: int width, height, floorHeight = 10; float t = 0; PhysObject *physObject; std::vector objects; void renderFloor(SDL_Renderer *renderer) { SDL_SetRenderDrawColor(renderer, 128, 128, 128, 0); SDL_Rect r{0, height - floorHeight, width, floorHeight}; SDL_RenderFillRect(renderer, &r); } float yToHeight(float y, int objHeight) { return height - y - floorHeight - objHeight; } void renderInfo(SDL_Renderer *renderer) { float h = yToHeight(physObject->getY(), physObject->getHeight()); std::string st = floatToStr(t, 2); std::string sh = floatToStr(h, 2); std::string sv = floatToStr(physObject->getSpeed(), 2); renderText(renderer, ("t = " + st + " s").c_str(), 10, 10, 16); renderText(renderer, ("h = " + sh + " m").c_str(), 10, 30, 16); renderText(renderer, ("V = " + sv + " m/s").c_str(), 10, 50, 16); } void renderEInfoAndGraphs(SDL_Renderer *renderer, int x, int y) { float h = yToHeight(physObject->getY(), physObject->getHeight()); float ep = physObject->getEPot(h); float ek = physObject->getEKin(); float e = ep + ek; std::string sek = floatToStr(ek, 2); std::string sep = floatToStr(ep, 2); std::string se = floatToStr(e, 2); const int maxWidth = 300; SDL_Rect r{x, 200, maxWidth, 30}; renderText(renderer, ("Ep = " + sek + " J").c_str(), x, y, 16); SDL_SetRenderDrawColor(renderer, 64, 64, 64, 0); r.y = y + 20; r.w = maxWidth; SDL_RenderFillRect(renderer, &r); r.w = maxWidth * (ep / e); SDL_SetRenderDrawColor(renderer, 32, 128, 32, 0); SDL_RenderFillRect(renderer, &r); renderText(renderer, ("Ek = " + sek + " J").c_str(), x, y + 70, 16); SDL_SetRenderDrawColor(renderer, 64, 64, 64, 0); r.y = y + 90; r.w = maxWidth; SDL_RenderFillRect(renderer, &r); r.w = maxWidth * (ek / e); SDL_SetRenderDrawColor(renderer, 32, 128, 128, 0); SDL_RenderFillRect(renderer, &r); renderText(renderer, ("E = " + se + " J").c_str(), x, y + 140, 16); } public: Simulation(int width, int height): width(width), height(height) { float h = HEIGHT; physObject = new PhysObject(50, 0, SPEED, ANGLE); physObject->setY(height - h - floorHeight - physObject->getWidth()); objects.push_back(physObject); } ~Simulation() { std::for_each( objects.begin(), objects.end(), [](Object *obj) { delete obj; } ); } void update(int fps) { t += 1.0 / fps; std::for_each( objects.begin(), objects.end(), [fps](Object* obj) { obj->update(60); } ); if (physObject->getSpeed() > 0.1) { float x = physObject->getX() + physObject->getWidth() / 2; float y = physObject->getY() + physObject->getHeight() / 2; objects.push_back(new Object(x, y, 5, 5)); } float collisionY = height - floorHeight - physObject->getHeight(); if (physObject->getY() > collisionY) { physObject->setY(collisionY); physObject->setSpeedY(floor(abs(physObject->getSpeedY() / 2))); } } void render(SDL_Renderer *renderer) { std::for_each( objects.rbegin(), objects.rend(), [renderer](Object* obj) { obj->render(renderer); } ); renderFloor(renderer); renderEInfoAndGraphs(renderer, 150, 10); renderInfo(renderer); } }; class App { private: int width, height, fps; bool isRunning; SDL_Window *window; SDL_Renderer *renderer; Simulation *matPlot; void handleEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { bool esc_or_quit = event.type == SDL_QUIT || event.key.keysym.sym == SDLK_ESCAPE; bool win_close = event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE; if (esc_or_quit || win_close) { isRunning = false; } } } void handleKeyboard() { const Uint8 *keys = SDL_GetKeyboardState(0); } public: App(const char* title, int width, int height, int initX, int initY, int fps): width(width), height(height), fps(fps), isRunning(true) { SDL_Init(SDL_INIT_VIDEO); TTF_Init(); window = SDL_CreateWindow(title, initX, initY, width, height, SDL_WINDOW_OPENGL); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); matPlot = new Simulation(width, height); } ~App() { delete matPlot; TTF_Quit(); SDL_Quit(); } void start() { while (isRunning) { handleEvents(); handleKeyboard(); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); matPlot->update(fps); matPlot->render(renderer); SDL_RenderPresent(renderer); SDL_Delay(1000 / fps); } } }; int main(int argc, char **argv) { std::stringstream ss; ss << WIN_TITLE << " (H = " << HEIGHT << " m, V = " << SPEED << " m/s, angle = " << ANGLE << " deg)"; App app(ss.str().c_str(), WIN_WIDTH, WIN_HEIGHT, 100, 100, FPS); app.start(); return 0; }