Delphi custom animation - collision detection

后端 未结 4 376
半阙折子戏
半阙折子戏 2021-01-12 00:35

I\'m working with custom drawing / 2D animation and I\'m trying to figure out how to detect when the moving object collides with a wall in the map. User holds arrow keys on

相关标签:
4条回答
  • 2021-01-12 01:02

    this unit found on the web (can't remember where, no author mentioned, perhaps someone can provide a link) would give you the ability of calculating collisions and reflection angles.

    unit Vector;
    
    interface
    
    type
      TPoint = record
        X, Y: Double;
      end;
    
      TVector = record
        X, Y: Double;
      end;
    
      TLine = record
        P1, P2: TPoint;
      end;
    
    function Dist(P1, P2: TPoint): Double; overload;
    function ScalarProd(P1, P2: TVector): Double;
    function ScalarMult(P: TVector; V: Double): TVector;
    function Subtract(V1, V2: TVector): TVector; overload;
    function Subtract(V1, V2: TPoint): TVector; overload;
    function MinDistPoint(Point: TPoint; Line: TLine): TPoint;
    function Mirror(W, V: TVector): TVector;
    function Dist(Point: TPoint; Line: TLine): Double; overload;
    
    implementation
    
    function Dist(P1, P2: TPoint): Double; overload;
    begin
      Result := Sqrt(Sqr(P1.X - P2.X) + Sqr(P1.Y - P2.Y));
    end;
    
    function ScalarProd(P1, P2: TVector): Double;
    begin
      Result := P1.X * P2.X + P1.Y * P2.Y;
    end;
    
    function ScalarMult(P: TVector; V: Double): TVector;
    begin
      Result.X := P.X * V;
      Result.Y := P.Y * V;
    end;
    
    function Subtract(V1, V2: TVector): TVector; overload;
    begin
      Result.X := V2.X - V1.X;
      Result.Y := V2.Y - V1.Y;
    end;
    
    function Subtract(V1, V2: TPoint): TVector; overload;
    begin
      Result.X := V2.X - V1.X;
      Result.Y := V2.Y - V1.Y;
    end;
    
    function MinDistPoint(Point: TPoint; Line: TLine): TPoint;
    var
      U: Double;
      P: TPoint;
    begin
      U := ((Point.X - Line.P1.X) * (Line.P2.X - Line.P1.X) +
            (Point.Y - Line.P1.Y) * (Line.P2.Y - Line.P1.Y)) /
        (Sqr(Line.P1.X - Line.P2.X) + Sqr(Line.P1.Y - Line.P2.Y));
      if U <= 0 then
        Exit(Line.P1);
      if U >= 1 then
        Exit(Line.P2);
      P.X := Line.P1.X + U * (Line.P2.X - Line.P1.X);
      P.Y := Line.P1.Y + U * (Line.P2.Y - Line.P1.Y);
      Exit(P);
    end;
    
    function Mirror(W, V: TVector): TVector;
    begin
      Result := Subtract(ScalarMult(V, 2*ScalarProd(v,w)/ScalarProd(v,v)), W);
    end;
    
    function Dist(Point: TPoint; Line: TLine): Double; overload;
    begin
      Result := Dist(Point, MinDistPoint(Point, Line));
    end;
    
    end.
    

    An example implementation would be

    unit BSP;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, Vector, ExtCtrls;
    
    type
      TForm2 = class(TForm)
        Timer1: TTimer;
        procedure FormPaint(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure Timer1Timer(Sender: TObject);
      private
        { Private-Deklarationen }
        FLines: array of TLine;
        FP: TPoint;
        FV: TVector;
        FBallRadius: Integer;
        FBallTopLeft: Windows.TPoint;
      public
        { Public-Deklarationen }
      end;
    
    var
      Form2: TForm2;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm2.FormCreate(Sender: TObject);
    const
      N = 5;
    
    var
      I: Integer;
    begin
      Randomize;
    
      SetLength(FLines, 4 + N);
      FBallRadius := 15;
      // Walls
      FLines[0].P1.X := 0;
      FLines[0].P1.Y := 0;
      FLines[0].P2.X := Width - 1;
      FLines[0].P2.Y := 0;
    
      FLines[1].P1.X := Width - 1;
      FLines[1].P1.Y := 0;
      FLines[1].P2.X := Width - 1;
      FLines[1].P2.Y := Height - 1;
    
      FLines[2].P1.X := Width - 1;
      FLines[2].P1.Y := Height - 1;
      FLines[2].P2.X := 0;
      FLines[2].P2.Y := Height - 1;
    
      FLines[3].P1.X := 0;
      FLines[3].P1.Y := 0;
      FLines[3].P2.X := 0;
      FLines[3].P2.Y := Height - 1;
      for I := 0 to N - 1 do
      begin
        FLines[I + 4].P1.X := 50 + Random(Width - 100);
        FLines[I + 4].P1.Y := 50 + Random(Height - 100);
        FLines[(I + 1) mod N + 4].P2 := FLines[I + 4].P1;
      end;
    
      FP.X := 50;
      FP.Y := 50;
    
      FV.X := 10;
      FV.Y := 10;
    end;
    
    procedure TForm2.FormPaint(Sender: TObject);
    const
      Iterations = 100;
    var
      I, MinIndex, J: Integer;
      MinDist, DP, DH: Double;
      MP: TPoint;
      H: TPoint;
    begin
    
    
      for I := 0 to Length(FLines) - 1 do
      begin
        Canvas.MoveTo(Round(FLines[I].P1.X), Round(FLines[I].P1.Y));
        Canvas.LineTo(Round(FLines[I].P2.X), Round(FLines[I].P2.Y));
      end;
    
      for I := 0 to Iterations do
      begin
        H := FP;
        FP.X := FP.X + FV.X / Iterations;
        FP.Y := FP.Y + FV.Y / Iterations;
        MinDist := Infinite;
        MinIndex := -1;
        for J := 0 to Length(FLines) - 1 do
        begin
          DP := Dist(FP, FLines[J]);
          DH := Dist(H, FLines[J]);
          if (DP < MinDist) and (DP < DH) then
          begin
            MinDist := DP;
            MinIndex := J;
          end;
        end;
    
        if MinIndex >= 0 then
          if Sqr(MinDist) < 2*Sqr(FBallRadius * 0.7 / 2)
             then
          begin
            MP := MinDistPoint(FP, FLines[MinIndex]);
            FV := Mirror(FV, Subtract(MP, FP));
          end;
      end;
    
      FBallTopLeft.X := Round(FP.X - FBallRadius);
      FBallTopLeft.Y := Round(FP.Y - FBallRadius);
      Canvas.Brush.Color := clBlue;
      Canvas.Ellipse(FBallTopLeft.X, FBallTopLeft.Y,
        FBallTopLeft.X + FBallRadius * 2, FBallTopLeft.Y + FBallRadius * 2);
    
    end;
    
    procedure TForm2.Timer1Timer(Sender: TObject);
    begin
      invalidate;
    end;
    
    end.
    
    0 讨论(0)
  • 2021-01-12 01:17

    If not doing it yourself is OK, you could use ready made library for this task. Box2D has Delphi version here

    0 讨论(0)
  • 2021-01-12 01:21

    I had already half-way answered my own question in the question its self. One thing I had thought of was reading the pixels of the image in the direction of the movement, and check if there's a line there or not. I now realize that I can have an extra layer under the FBMap map layer for the background, and leave the map layer as it is with only the collidable walls drawn.

    When moving, scan the pixels in the direction of the movement on that particular layer, not the full image. Since I already have a pre-drawn layer sitting there, I can read it rather than the main image. Based on the speed of movement, I only need to look so many pixels ahead (at least a few more pixels than the number of pixels of movement).

    Also, in case the background of the image has a picture representing the walls rather than straight plain lines, then this layer doesn't even have to be drawn at all. This layer can be explicitly used just for scanning a few pixels ahead of movement for collision areas. As a matter of fact, since I also need to recognize collision with other moving objects, I can draw all the objects on here as well (in black/white).

    A few iterations of pixels across a canvas, for example 20, is nothing compared to extensive iterations through the map lines, for example 2000.

    0 讨论(0)
  • 2021-01-12 01:23

    Every time the key is pressed, you compute the new coordinate of the object after the move would be executed. Then you can test for intersections between the object trajectory and the line in the map.

    Since your map can be considered a set of line segments, and given that your object path is linear, you can find all the possible collisions by finding intersections between the object path and the lines on which the segments of your map lie. You will only have two slopes for the object path: zero and infinity. So for each map segment:

    1. Compute its slope. If the map segment slope is the same as object path slope, they will not intersect.
    2. Compute the intersection between the lines that the map segment and the object path are one (see here for instance)
    3. Check if the map segment ends before the collision point: if yes, then no collision
    4. Check if the object path ends before the collision point: if yes, then no collision
    0 讨论(0)
提交回复
热议问题