Skip to content

Commit e021014

Browse files
authored
Added First Collision Video - Balls #1
1 parent d3111c6 commit e021014

1 file changed

Lines changed: 288 additions & 0 deletions

File tree

OneLoneCoder_Balls1.cpp

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
/*
2+
OneLoneCoder.com - Programming Balls! #1 Circle Vs Circle Collisions
3+
"..it's just balls bangin' together init..." - @Javidx9
4+
5+
Disclaimer
6+
~~~~~~~~~~
7+
I don't care what you use this for. It's intended to be educational, and perhaps
8+
to the oddly minded - a little bit of fun. Please hack this, change it and use it
9+
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
10+
bad that happens as a result of your actions. However, if good stuff happens, I
11+
would appreciate a shout out, or at least give the blog some publicity for me.
12+
Cheers!
13+
14+
Background
15+
~~~~~~~~~~
16+
Collision detection engines can get quite complicated. This program shows the interactions
17+
between circular objects of different sizes and masses. Use Left mouse button to select
18+
and drag a ball to examin static collisions, and use Right mouse button to apply velocity
19+
to the balls as if using a pool/snooker/billiards cue.
20+
21+
Author
22+
~~~~~~
23+
Twitter: @javidx9
24+
Blog: www.onelonecoder.com
25+
26+
Video:
27+
~~~~~~
28+
Part #1 https://youtu.be/LPzyNOHY3A4
29+
30+
Last Updated: 21/01/2017
31+
*/
32+
33+
#include <iostream>
34+
#include <string>
35+
using namespace std;
36+
37+
#include "olcConsoleGameEngine.h"
38+
39+
40+
struct sBall
41+
{
42+
float px, py;
43+
float vx, vy;
44+
float ax, ay;
45+
float radius;
46+
float mass;
47+
48+
int id;
49+
};
50+
51+
52+
class CirclePhysics : public olcConsoleGameEngine
53+
{
54+
public:
55+
CirclePhysics()
56+
{
57+
m_sAppName = L"Circle Physics";
58+
}
59+
60+
private:
61+
vector<pair<float, float>> modelCircle;
62+
vector<sBall> vecBalls;
63+
sBall *pSelectedBall = nullptr;
64+
65+
66+
// Adds a ball to the vector
67+
void AddBall(float x, float y, float r = 5.0f)
68+
{
69+
sBall b;
70+
b.px = x; b.py = y;
71+
b.vx = 0; b.vy = 0;
72+
b.ax = 0; b.ay = 0;
73+
b.radius = r;
74+
b.mass = r * 10.0f;
75+
76+
b.id = vecBalls.size();
77+
vecBalls.emplace_back(b);
78+
}
79+
80+
81+
public:
82+
bool OnUserCreate()
83+
{
84+
// Define Circle Model
85+
modelCircle.push_back({ 0.0f, 0.0f });
86+
int nPoints = 20;
87+
for (int i = 0; i < nPoints; i++)
88+
modelCircle.push_back({ cosf(i / (float)(nPoints - 1) * 2.0f * 3.14159f) , sinf(i / (float)(nPoints - 1) * 2.0f * 3.14159f) });
89+
90+
float fDefaultRad = 8.0f;
91+
//AddBall(ScreenWidth() * 0.25f, ScreenHeight() * 0.5f, fDefaultRad);
92+
//AddBall(ScreenWidth() * 0.75f, ScreenHeight() * 0.5f, fDefaultRad);
93+
94+
// Add 10 Random Balls
95+
for (int i = 0; i <10; i++)
96+
AddBall(rand() % ScreenWidth(), rand() % ScreenHeight(), rand() % 16 + 2);
97+
98+
99+
return true;
100+
}
101+
102+
bool OnUserUpdate(float fElapsedTime)
103+
{
104+
auto DoCirclesOverlap = [](float x1, float y1, float r1, float x2, float y2, float r2)
105+
{
106+
return fabs((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)) <= (r1 + r2)*(r1 + r2);
107+
};
108+
109+
auto IsPointInCircle = [](float x1, float y1, float r1, float px, float py)
110+
{
111+
return fabs((x1 - px)*(x1 - px) + (y1 - py)*(y1 - py)) < (r1 * r1);
112+
};
113+
114+
if (m_mouse[0].bPressed || m_mouse[1].bPressed)
115+
{
116+
pSelectedBall = nullptr;
117+
for (auto &ball : vecBalls)
118+
{
119+
if (IsPointInCircle(ball.px, ball.py, ball.radius, m_mousePosX, m_mousePosY))
120+
{
121+
pSelectedBall = &ball;
122+
break;
123+
}
124+
}
125+
}
126+
127+
if (m_mouse[0].bHeld)
128+
{
129+
if (pSelectedBall != nullptr)
130+
{
131+
pSelectedBall->px = m_mousePosX;
132+
pSelectedBall->py = m_mousePosY;
133+
}
134+
}
135+
136+
if (m_mouse[0].bReleased)
137+
{
138+
pSelectedBall = nullptr;
139+
}
140+
141+
if (m_mouse[1].bReleased)
142+
{
143+
if (pSelectedBall != nullptr)
144+
{
145+
// Apply velocity
146+
pSelectedBall->vx = 5.0f * ((pSelectedBall->px) - (float)m_mousePosX);
147+
pSelectedBall->vy = 5.0f * ((pSelectedBall->py) - (float)m_mousePosY);
148+
}
149+
150+
pSelectedBall = nullptr;
151+
}
152+
153+
154+
vector<pair<sBall*, sBall*>> vecCollidingPairs;
155+
156+
// Update Ball Positions
157+
for (auto &ball : vecBalls)
158+
{
159+
// Add Drag to emulate rolling friction
160+
ball.ax = -ball.vx * 0.8f;
161+
ball.ay = -ball.vy * 0.8f;
162+
163+
// Update ball physics
164+
ball.vx += ball.ax * fElapsedTime;
165+
ball.vy += ball.ay * fElapsedTime;
166+
ball.px += ball.vx * fElapsedTime;
167+
ball.py += ball.vy * fElapsedTime;
168+
169+
// Wrap the balls around screen
170+
if (ball.px < 0) ball.px += (float)ScreenWidth();
171+
if (ball.px >= ScreenWidth()) ball.px -= (float)ScreenWidth();
172+
if (ball.py < 0) ball.py += (float)ScreenHeight();
173+
if (ball.py >= ScreenHeight()) ball.py -= (float)ScreenHeight();
174+
175+
// Clamp velocity near zero
176+
if (fabs(ball.vx*ball.vx + ball.vy*ball.vy) < 0.01f)
177+
{
178+
ball.vx = 0;
179+
ball.vy = 0;
180+
}
181+
}
182+
183+
// Static collisions, i.e. overlap
184+
for (auto &ball : vecBalls)
185+
{
186+
for (auto &target : vecBalls)
187+
{
188+
if (ball.id != target.id)
189+
{
190+
if (DoCirclesOverlap(ball.px, ball.py, ball.radius, target.px, target.py, target.radius))
191+
{
192+
// Collision has occured
193+
vecCollidingPairs.push_back({ &ball, &target });
194+
195+
// Distance between ball centers
196+
float fDistance = sqrtf((ball.px - target.px)*(ball.px - target.px) + (ball.py - target.py)*(ball.py - target.py));
197+
198+
// Calculate displacement required
199+
float fOverlap = 0.5f * (fDistance - ball.radius - target.radius);
200+
201+
// Displace Current Ball away from collision
202+
ball.px -= fOverlap * (ball.px - target.px) / fDistance;
203+
ball.py -= fOverlap * (ball.py - target.py) / fDistance;
204+
205+
// Displace Target Ball away from collision
206+
target.px += fOverlap * (ball.px - target.px) / fDistance;
207+
target.py += fOverlap * (ball.py - target.py) / fDistance;
208+
}
209+
}
210+
}
211+
}
212+
213+
// Now work out dynamic collisions
214+
for (auto c : vecCollidingPairs)
215+
{
216+
sBall *b1 = c.first;
217+
sBall *b2 = c.second;
218+
219+
// Distance between balls
220+
float fDistance = sqrtf((b1->px - b2->px)*(b1->px - b2->px) + (b1->py - b2->py)*(b1->py - b2->py));
221+
222+
// Normal
223+
float nx = (b2->px - b1->px) / fDistance;
224+
float ny = (b2->py - b1->py) / fDistance;
225+
226+
// Tangent
227+
float tx = -ny;
228+
float ty = nx;
229+
230+
// Dot Product Tangent
231+
float dpTan1 = b1->vx * tx + b1->vy * ty;
232+
float dpTan2 = b2->vx * tx + b2->vy * ty;
233+
234+
// Dot Product Normal
235+
float dpNorm1 = b1->vx * nx + b1->vy * ny;
236+
float dpNorm2 = b2->vx * nx + b2->vy * ny;
237+
238+
// Conservation of momentum in 1D
239+
float m1 = (dpNorm1 * (b1->mass - b2->mass) + 2.0f * b2->mass * dpNorm2) / (b1->mass + b2->mass);
240+
float m2 = (dpNorm2 * (b2->mass - b1->mass) + 2.0f * b1->mass * dpNorm1) / (b1->mass + b2->mass);
241+
242+
// Update ball velocities
243+
b1->vx = tx * dpTan1 + nx * m1;
244+
b1->vy = ty * dpTan1 + ny * m1;
245+
b2->vx = tx * dpTan2 + nx * m2;
246+
b2->vy = ty * dpTan2 + ny * m2;
247+
248+
// Wikipedia Version - Maths is smarter but same
249+
//float kx = (b1->vx - b2->vx);
250+
//float ky = (b1->vy - b2->vy);
251+
//float p = 2.0 * (nx * kx + ny * ky) / (b1->mass + b2->mass);
252+
//b1->vx = b1->vx - p * b2->mass * nx;
253+
//b1->vy = b1->vy - p * b2->mass * ny;
254+
//b2->vx = b2->vx + p * b1->mass * nx;
255+
//b2->vy = b2->vy + p * b1->mass * ny;
256+
}
257+
258+
// Clear Screen
259+
Fill(0, 0, ScreenWidth(), ScreenHeight(), ' ');
260+
261+
// Draw Balls
262+
for (auto ball : vecBalls)
263+
DrawWireFrameModel(modelCircle, ball.px, ball.py, atan2f(ball.vy, ball.vx), ball.radius, FG_WHITE);
264+
265+
// Draw static collisions
266+
for (auto c : vecCollidingPairs)
267+
DrawLine(c.first->px, c.first->py, c.second->px, c.second->py, PIXEL_SOLID, FG_RED);
268+
269+
// Draw Cue
270+
if (pSelectedBall != nullptr)
271+
DrawLine(pSelectedBall->px, pSelectedBall->py, m_mousePosX, m_mousePosY, PIXEL_SOLID, FG_BLUE);
272+
273+
return true;
274+
}
275+
276+
};
277+
278+
279+
int main()
280+
{
281+
CirclePhysics game;
282+
if (game.ConstructConsole(160, 120, 8, 8))
283+
game.Start();
284+
else
285+
wcout << L"Could not construct console" << endl;
286+
287+
return 0;
288+
};

0 commit comments

Comments
 (0)