Bookstore

Recent

Author Topic: collision detection  (Read 19657 times)

rvk

  • Hero Member
  • *****
  • Posts: 3943
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: 3434
  • 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
Size     --- Radius


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;
  49.     Radius:     Integer;
  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;
  141.             Radius     := 15;
  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);
  268.         R := Radius;
  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: 3943
Re: collision detection
« Reply #62 on: October 14, 2017, 11:11:33 pm »
My last try at stability with 16 balls :D

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: 3434
  • 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;
  38.     Distance := MasterRadius + Radius;
  39.     PosX := MasterX + cos(StickAngle)*Distance;
  40.     PosY := MasterY + sin(StickAngle)*Distance;
  41.   end;
  42. end;

@rvk
I like the dancing effect of your code.
« Last Edit: October 15, 2017, 08:02:39 am by Handoko »