-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
234 lines (191 loc) · 8 KB
/
Copy pathmain.cpp
File metadata and controls
234 lines (191 loc) · 8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <ostream>
#include <thread>
#include <chrono>
#include "Circle.hpp"
const float RADIUS = 40.0f; // pixels
const float TIME_STEP_60FPS = 1.0f / 60.0f; // seconds
const float TIME_STEP_120FPS = 1.0f / 120.0f; // seconds
const float ACCEL = 981.0f; // pixels / seconds^2
// given a circle c of radius r,
// draw a circle consisting of line connected points
void drawCircle(const Circle& c, SDL_Renderer* renderer);
int main(int argc, char *argv[])
{
// default 60fps with no flag
float time_step = TIME_STEP_60FPS;
for(int i = 0; i < argc; ++i)
{
if(strcmp(argv[i], "--fps=120") == 0)
{
time_step = TIME_STEP_120FPS;
}
}
// SDL structs
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Event event;
// attempt initialize SDL library subsystem video
if(!SDL_Init(SDL_INIT_VIDEO))
{
std::cout << "ERROR: " << SDL_GetError() << std::endl;
return 0;
}
// 1000 window width, 900 window height
if(!SDL_CreateWindowAndRenderer("Window", 1000, 800, SDL_WINDOW_RESIZABLE, &window, &renderer))
{
std::cout << "ERROR: " << SDL_GetError() << std::endl;
SDL_Quit();
return 0;
}
// init our circle
float x_velocity = 200.0f; // pixels / second
Circle circle1(500.0f - (4.0f * RADIUS), 100.0f, RADIUS, -x_velocity, -100.0f);
Circle circle2(500.0f, 400.0f, RADIUS, 1.7f * x_velocity, 0.0f);
// now make it for an arbitrary amount with a slider (pairs)
// AND RANDOMIZE STARTING LOCATIONS (not touching)
// next step is to add mass
Circle circles[2] { circle1, circle2 };
int height;
int width;
bool done = false;
// sim loop
while(!done)
{
// poll all events and handle quit
while(SDL_PollEvent(&event))
{
// types are enum, .type is uint32
if(event.type == SDL_EVENT_QUIT)
{
done = true;
}
}
SDL_GetWindowSize(window, &width, &height);
// render draws over whatever is already rendered
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
//SDL_RenderClear(renderer);
// now set render color for points
SDL_RenderClear(renderer);
//SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
SDL_SetRenderDrawColor(renderer, 0x27, 0xF5, 0x3C, 0xFF);
// TODO: must test all pairs with arbitrary number once full
// 2D Elastic Collisions without Trigonometry:
// https://imada.sdu.dk/~rolf/Edu/DM815/E10/2dcollisions.pdf
// distance components
float dist_x = circles[0].center.x - circles[1].center.x;
float dist_y = circles[0].center.y - circles[1].center.y;
float dist = std::sqrt(dist_x*dist_x + dist_y * dist_y);
// object collision
if(dist <= (2.1f * RADIUS))
{
// TODO: Fix these variable names lmaooooo
// components of unit normal vector between objects' centers
float norm_x = dist_x / dist;
float norm_y = dist_y / dist;
// components of unit tangent vector between the objects where the unit tangent is (-norm_y, norm_x)
// Orthogonal (orthonormal) to the unit normal vector
float tang_X = -norm_y;
float tang_Y = norm_x;
// project the velocity vectors onto the unit normal and
// unit tangent vectors by taking the dot product of the velocity vectors with the unit normal and
// unit tangent vectors
// Let v1_normal be the scalar (plain number, not a vector) velocity of object 1 in
// the normal direction. Let v1_tangent be the scalar velocity of object 1 in the tangential direction.
// Similarly, let v2_normal and v2_tangent be for object 2.
// EQUAL MASS
float v1_normal = norm_x * circles[0].velocity.x + norm_y * circles[0].velocity.y;
float v2_normal = norm_x * circles[1].velocity.x + norm_y * circles[1].velocity.y;
float v1_tangent = tang_X * circles[0].velocity.x + tang_Y * circles[0].velocity.y;
float v2_tangent = tang_X * circles[1].velocity.x + tang_Y * circles[1].velocity.y;
// update normal velocities after collision (follows new v1_normal` = v2_normal, new V1 normal = v2_normal dot unit normal
float new_v1_normal_X = v2_normal * norm_x;
float new_v1_normal_Y = v2_normal * norm_y;
float new_v2_normal_X = v1_normal * norm_x;
float new_v2_normal_Y = v1_normal * norm_y;
// updated tangent velocities after collision (no force in the tangential direction, same scalar after collision)
float new_v1_tangent_X = v1_tangent * tang_X;
float new_v1_tangent_Y = v1_tangent * tang_Y;
float new_v2_tangent_X = v2_tangent * tang_X;
float new_v2_tangent_Y = v2_tangent * tang_Y;
// now just add the new normal and tangent vectors together
circles[0].velocity.x = (new_v1_normal_X + new_v1_tangent_X);
circles[0].velocity.y = (new_v1_normal_Y + new_v1_tangent_Y);
circles[1].velocity.x = (new_v2_normal_X + new_v2_tangent_X);
circles[1].velocity.y = (new_v2_normal_Y + new_v2_tangent_Y);
}
// update state (euler method)
// time step fixed at 1/60 second
for(Circle& c : circles)
{
c.velocity.y += time_step * ACCEL;
c.center.y += time_step * c.velocity.y;
c.center.x += time_step * c.velocity.x;
// check collision bottom
if(c.center.y + RADIUS >= height - 0.55)
{
// fix position slightly to avoid being stuck at bottom
c.center.y = height - RADIUS - 0.55;
// velocity = -velocity to return up
c.velocity.y *= -0.95f;
}
if(c.center.x + RADIUS >= width)
{
c.center.x = width - RADIUS;
c.velocity.x *= -0.9f;
}
else if(c.center.x - RADIUS <= 0)
{
c.center.x = RADIUS;
c.velocity.x *= -0.9f;
}
// TODO: handle if they are just sitting on the bottom sliding
drawCircle(c, renderer);
// match frame rate
}
// TODO: add these options into a makefile with run commands
// // also handle better?
// like in a man / help layout
if(time_step == TIME_STEP_120FPS)
{
// ~8ms = 120fps (cap loop)
std::this_thread::sleep_for(std::chrono::milliseconds(8));
}
else
{
// ~16ms = 60fps (cap loop)
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
SDL_RenderPresent(renderer);
}
return 0;
}
void drawCircle(const Circle& c, SDL_Renderer* renderer)
{
//
// Determine N : number of points
// - Small angle approx for starting distance (pixels) between each point
// - sin(theta) = y/radius, where y is the vertical pixel height between points
// - and theta is approximately = 2 * PI / N (if each theta is identical)
//
// to find the current theta offset from 0, theta = 2.0f * M_PI * (current point # / total points #)
float y = 4.4;
// At most N = 255, ideally N <= ~ 64
std::uint8_t N = 2.0f * M_PI * RADIUS / y;
SDL_FPoint points[N + 1];
for(auto i = 0; i <= N; ++i)
{
// TODO: see if you can smooth out the ratios (i/N) to make circles smoother?
// --- LOG HOW MUCH THE THETAS DIFFER
points[i].x = c.center.x + RADIUS * std::cos(2.0f * M_PI * i / N);
points[i].y = c.center.y + RADIUS * std::sin(2.0f * M_PI * i / N);
}
// wrap the last point back to the first
points[N].x = points[0].x;
points[N].y = points[0].y;
SDL_RenderLines(renderer, points, N + 1);
}