### Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

### Author Topic: collision detection  (Read 28241 times)

#### rvk

• Hero Member
• Posts: 5894
##### Re: collision detection
« Reply #60 on: October 14, 2017, 05:55:17 pm »
fwiw: when the shapes are more or less 'lined-up' then they wiggle, but that was to be expected with the 5 degree angle approach. Could that be solved by checking the direction of the head, and ignore further calculation if the x coord line up in case going up/down (and Y for left/right) ?
If the timer is set fast enough the degrees shift can be 1 degree. Then there is no wiggleing.

But somehow I still don't think I got the case of direction correct:
Code: Pascal  [Select][+][-]
1.     case direction of
2.       1: a := sign(0 - a) * 1 + a;
3.       2: a := sign(180 - a) * 1 + a;
4.       3: a := sign(90 - a) * 1 + a;
5.       4: a := sign(-90 - a) * 1 + a;
6.     end;

Actually, direction is the wrong variable to take here. It should not be the direction of the main ball but the direction of the ball it's following. For the second ball that's the main ball but the third ball needs to follow the second ball and that one can go in another direction as the main ball (if you know what I mean).

So when the second ball is moved (at the bottom of the for-loop) you need to calculate in what direction it is heading and the next ball needs to use that direction to get the point to connect to.

#### Handoko

• Hero Member
• Posts: 5091
• My goal: build my own game engine using Lazarus
##### Re: collision detection
« Reply #61 on: October 14, 2017, 10:02:18 pm »
Let me try.

Here is my StickyBalls.pas. Not very good because all the balls sticking rigidly. To make the sticking flexible, we need to add inverse kinematics calculation. But because it will the add complexity a lot, so I did not do it. Anyone interest to know more about inverse kinematic, read here:
https://en.wikipedia.org/wiki/Inverse_kinematics

To make the ball movement more realistic, I added simple calculation for inertia effect but only to the red ball. For better performance, it did not use TShape but draw on the canvas directly. When running the program, press [Esc] for more information.

To avoid confusion, the red ball uses different identifier names than the followers:
Position --- Pos
Velocity --- Vel

Also the code uses Cartesian coordinate system instead of the inverse Y windowing system.

Code: Pascal  [Select][+][-]
1. unit Unit1;
2.
3. {\$mode objfpc}{\$H+}
4.
5. interface
6.
7. uses
8.   Classes, Forms, Controls, Graphics, ExtCtrls, LCLType;
9.
10. type
11.
12.   { TForm1 }
13.
14.   TForm1 = class(TForm)
15.     Timer1: TTimer;
16.     procedure FormCreate(Sender: TObject);
17.     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
18.     procedure Timer1Timer(Sender: TObject);
19.   private
20.     procedure DrawGame;
21.   end;
22.
23. var
24.   Form1: TForm1;
25.
26. implementation
27.
28. type
29.
30.   TPlayer = record
31.     PositionX:  Single;
32.     PositionY:  Single;
33.     VelocityX:  Single;
34.     VelocityY:  Single;
35.     Inertia:    Single;
36.     MaxInertia: Integer;
37.     Size:       Integer;
38.   end;
39.
40.   TFollower = record
41.     MasterX:    Single;
42.     MasterY:    Single;
43.     MasterOldX: Single;
44.     MasterOldY: Single;
45.     VelX:       Single;
46.     VelY:       Single;
47.     PosX:       Single;
48.     PosY:       Single;
50.     isSticking: Boolean;
51.   end;
52.
53. var
54.   Player:         TPlayer;
55.   Followers:      array of TFollower;
56.   isPaused:       Boolean;
57.   isUsingInertia: Boolean;
58.
59. {\$R *.lfm}
60.
61. { TForm1 }
62.
63. procedure TForm1.FormCreate(Sender: TObject);
64. const
65.   FormHeight = 600;
66.   FormWidht  = 600;
67. begin
68.   // Prepare data
69.   with Player do begin
70.     PositionX  := FormWidht/2;
71.     PositionY  := FormHeight/2;
72.     VelocityX  := 0;
73.     VelocityY  := 0;
74.     Inertia    := 0;
75.     MaxInertia := 5;
76.     Size       := 30;
77.   end;
78.   SetLength(Followers, 0);
79.   isPaused       := False;
80.   isUsingInertia := True;
81.   // Prepare display
82.   Height := FormHeight;
83.   Width  := FormWidht;
84.   with Constraints do begin
85.     MaxHeight := FormHeight;
86.     MaxWidth  := FormWidht;
87.     MinHeight := FormHeight;
88.     MinWidth  := FormWidht;
89.   end;
90.   // Set to 60 FPS as close as possible
91.   Timer1.Interval := 17;
92. end;
93.
94. procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState
95.   );
96. var
97.   Index: Integer;
98. begin
99.   if (Key = VK_ESCAPE) then // Pause or Quit
100.     begin
101.       if isPaused then Halt;
102.       isPaused := True;
103.       Exit;
104.     end;
105.   if isPaused then // Resume
106.     begin
107.       isPaused := False;
108.       Exit;
109.     end;
110.   with Player do
111.     case Key of
112.       VK_R: FormCreate(Sender); // Reset
113.       VK_I: isUsingInertia := not (isUsingInertia); // Inertia effect
114.       VK_C: // Teleport to centre
115.         begin
116.           PositionX := Width/2;
117.           PositionY := Height/2;
118.         end;
119.       VK_A: // New follower
120.         begin
121.           Index := Length(Followers);
122.           if (Index >= 20) then Exit;
123.           SetLength(Followers, Index+1);
124.           with Followers[Index] do begin
125.             if (Index >= 1) then
126.               begin
127.                 MasterX := Followers[Index-1].PosX;
128.                 MasterY := Followers[Index-1].PosY;
129.               end
130.             else
131.               begin
132.                 MasterX := PositionX;
133.                 MasterY := PositionY;
134.               end;
135.             MasterOldX := 0;
136.             MasterOldY := 0;
137.             PosX       := Random(Width);
138.             PosY       := Random(Height);
139.             VelX       := 0;
140.             VelY       := 0;
142.             isSticking := False;
143.           end;
144.         end;
145.       VK_D: // Delete last follower
146.         begin
147.           Index := Length(Followers);
148.           if (Index <= 0) then Exit;
149.           Dec(Index);
150.           SetLength(Followers, Index);
151.         end;
152.       VK_UP: if (VelocityY <= 0) then    // Up
153.         begin
154.           VelocityY := 1;
155.           VelocityX := 0;
156.           Inertia   := 0;
157.         end;
158.       VK_DOWN: if (VelocityY >= 0) then  // Down
159.         begin
160.           VelocityY := -1;
161.           VelocityX := 0;
162.           Inertia   := 0;
163.         end;
164.       VK_LEFT: if (VelocityX >= 0) then  // Left
165.         begin
166.           VelocityX := -1;
167.           VelocityY := 0;
168.           Inertia   := 0;
169.         end;
170.       VK_RIGHT: if (VelocityX <= 0) then // Right
171.         begin
172.           VelocityX := 1;
173.           VelocityY := 0;
174.           Inertia   := 0;
175.         end;
176.     end;
177. end;
178.
179. procedure TForm1.Timer1Timer(Sender: TObject);
180. var
181.   InertiaInEffect: Single;
182.   Distance:        Single;
183.   i:               Integer;
184. begin
185.   if isPaused then with Canvas do
186.     begin
187.       Brush.Color := clWhite;
188.       Clear;
189.       TextOut(10,  10, '===== P A U S E D =====');
190.       TextOut(10,  30, 'Press [ESC] again to quit.');
191.       TextOut(10,  80, 'Keys available when playing:');
192.       TextOut(10, 100, '[r] - Reset');
193.       TextOut(10, 120, '[i] - Enable/disable inertia effect');
194.       TextOut(10, 140, '[c] - Teleport to centre');
195.       TextOut(10, 160, '[a] - New follower');
196.       TextOut(10, 180, '[d] - Delete last follower');
197.       TextOut(10, 200, '[arrows] - Move the red ball');
198.       Exit;
199.     end;
200.   // Move player
201.   with Player do begin
202.     if isUsingInertia then
203.       begin
204.         if (Inertia < MaxInertia) then Inertia := Inertia + 0.05;
205.         InertiaInEffect := Inertia;
206.       end
207.     else
208.       InertiaInEffect := 2;
209.     PositionX := PositionX + (VelocityX*InertiaInEffect);
210.     PositionY := PositionY + (VelocityY*InertiaInEffect);
211.   end;
212.   // Move followers
213.   for i := 0 to High(Followers) do
214.     with Followers[i] do begin
215.       if (i > 0) then
216.         begin
217.           MasterX := Followers[i-1].PosX;
218.           MasterY := Followers[i-1].PosY;
219.         end
220.       else
221.         begin
222.           MasterX := Player.PositionX;
223.           MasterY := Player.PositionY;
224.         end;
225.       if isSticking then
226.         begin
227.           VelX := (MasterX-MasterOldX);
228.           VelY := (MasterY-MasterOldY);
229.         end
230.       else
231.         begin
232.           VelX := (MasterX-PosX) / 20;
233.           VelY := (MasterY-PosY) / 20;
234.         end;
235.       PosX := PosX + VelX;
236.       PosY := PosY + VelY;
237.       MasterOldX := MasterX;
238.       MasterOldY := MasterY;
239.       if not(isSticking) then
240.         begin
241.           Distance := sqrt(sqr(MasterX-PosX)+sqr(MasterY-PosY)) -Player.Size/2 -Radius;
242.           if (Distance <= 1) then isSticking := True;
243.         end;
244.     end;
245.   // Show them
246.   DrawGame;
247. end;
248.
249. procedure TForm1.DrawGame;
250. var
251.   X, Y, R, i: Integer;
252. begin
253.   with Player, Canvas do begin
254.     X := Round(PositionX);
255.     Y := Round(PositionY);
256.     R := Size div 2;
257.     Brush.Color := clWhite;
258.     Clear;
259.     // Show player
260.     Brush.Color := clRed;
261.     Ellipse(Rect(X-R, Height-Y-R, X+R, Height-Y+R));
262.     // Show followers
263.     Brush.Color := clYellow;
264.     for i := 0 to High(Followers) do
265.       with Followers[i] do begin
266.         X := Round(PosX);
267.         Y := Round(PosY);
269.         Ellipse(Rect(X-R, Height-Y-R, X+R, Height-Y+R));
270.       end;
271.   end;
272. end;
273.
274. end.

edit:

To avoid precision lost (as suggested by User137), all the calculations use float.

Quote
But somehow I still don't think I got the case of direction correct:
Using direction is hard, I used velocity instead. The code became easier to understand.
« Last Edit: October 14, 2017, 10:38:49 pm by Handoko »

#### rvk

• Hero Member
• Posts: 5894
##### Re: collision detection
« Reply #62 on: October 14, 2017, 11:11:33 pm »
My last try at stability with 16 balls

I must say the balls all follow their previous ball quite nicely.

(I still think the direction of the previous ball should be taken but for now they "dance" good enough.)

#### Handoko

• Hero Member
• Posts: 5091
• My goal: build my own game engine using Lazarus
##### Re: collision detection
« Reply #63 on: October 15, 2017, 07:54:25 am »
After enough sleep and having my breakfast, now I added simple trigonometry calculation to solve the IK problem, not accurate but I think it is okay. To turn on/off the flexible mode use [space] when running the program.

Code: Pascal  [Select][+][-]
1. procedure SolveIK(var Follower: TFollower);
2. const
3.   Increment = pi/90; // ± 2 degree
4. var
5.   Distance: Single;
6. begin
7.   with Follower do begin
8.     while (StickAngle > 2*pi) do StickAngle := StickAngle - 2*pi;
9.     while (StickAngle < 0)    do StickAngle := StickAngle + 2*pi;
10.     if (VelX > abs(VelY)) then  // East direction
11.       begin
12.         if (StickAngle > 0) and (StickAngle < pi) then
13.           StickAngle := StickAngle + Increment
14.         else
15.           StickAngle := StickAngle - Increment;
16.       end;
17.     if (VelX < -abs(VelY)) then // West direction
18.       begin
19.         if (StickAngle > 0) and (StickAngle < pi) then
20.           StickAngle := StickAngle - Increment
21.         else
22.           StickAngle := StickAngle + Increment;
23.       end;
24.     if (VelY > abs(VelX)) then  // North direction
25.       begin
26.         if (StickAngle > 0.5*pi) and (StickAngle < 1.5*pi) then
27.           StickAngle := StickAngle + Increment
28.         else
29.           StickAngle := StickAngle - Increment;
30.       end;
31.     if (VelY < -abs(VelX)) then // South direction
32.       begin
33.         if (StickAngle > 0.5*pi) and (StickAngle < 1.5*pi) then
34.           StickAngle := StickAngle - Increment
35.         else
36.           StickAngle := StickAngle + Increment;
37.       end;