318 lines
8.6 KiB
C++
318 lines
8.6 KiB
C++
|
/*
|
||
|
Copyright 2020 KoroLion (github.com/KoroLion)
|
||
|
*/
|
||
|
#include <iostream>
|
||
|
#include <sstream>
|
||
|
#include <cmath>
|
||
|
#include <algorithm>
|
||
|
|
||
|
#include <iomanip>
|
||
|
#include <vector>
|
||
|
#include <string>
|
||
|
#include <sstream>
|
||
|
|
||
|
#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 -90
|
||
|
// endconf
|
||
|
|
||
|
template <typename T>
|
||
|
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<Object*> 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;
|
||
|
}
|