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