Einzelnen Beitrag anzeigen

Benutzerbild von Aphton
Aphton

Registriert seit: 31. Mai 2009
1.198 Beiträge
 
Turbo Delphi für Win32
 
#20

AW: Überprüfen ob Mausklick in bestimmten bereich war

  Alt 23. Mär 2012, 17:03
Hier, eine etwas allgemeinere Lösung SAT (Separating Axis Theorem)

Kurze Erläuterung der Funktionsweise (soweit ich das richtig in Erinnerung habe):
Man berechnet zu allen Seiten beider Polygone schrittweise die Normalen, und projeziert alle Punkte auf diese Achse (Normale). Dann berechnet man sich die Min-Max Werte auf der Achse für Polygon 1 & 2. Schneiden sich nun diese beiden Bereiche nicht, so gibt es keine Kollision -> die Überprüfung kann vorzeitig beendet werden. Ansonsten kommt es höchstwahrscheinlich (nicht sicher!) zu einer Kollision.

Bild 1
Bild 2
(bei Bild 2 sieht man, dass der grüne und rote Bereich sich nicht schneiden -> keine Kollision)

Delphi-Quellcode:
type
  TVector = record
    X, Y: Single;
    procedure Assign(const AX, AY: Single);
  end;
  TVectorArray = Array of TVector;
  PPolygon = ^TPolygon;
  TPolygon = TVectorArray;

procedure TVector.Assign(const AX, AY: Single);
begin
  X := AX;
  Y := AY;
end;

function vector(const AX, AY: Single): TVector;
begin
  Result.Assign(AX, AY);
end;

function scalarProduct(const A, B: TVector): Single;
begin
  Result := A.X*B.X + A.Y*B.Y;
end;

procedure normalize(var V: TVector);
var
  size: Single;
begin
  size := SQRT(scalarProduct(V, V));
  if size <> 0 then V.Assign(V.X/size, V.Y/size);
end;

function normal(const V: TVector): TVector;
begin
  Result.Assign(V.Y, -V.X);
end;

function BoundingBoxCollisionCheck(const A1, A2, B1, B2: TVector): Boolean; overload;
{  1
    +---------+
    |        |
    |        |
    +---------+ 2
}

begin
  Result := not((A1.X > B2.X) or (A2.X < B1.X) or
    (A1.Y > B2.Y) or (A2.Y < B1.Y));
end;

function BoundingBoxCollisionCheck(const PolyA, PolyB: TPolygon): Boolean; overload;
var
  boxA1, boxA2,
  boxB1, boxB2: TVector;
  procedure createBoundingBox(const P: TPolygon; var bbox1, bbox2: TVector);
  var
    i: Integer;
  begin
    for i := 0 to High(P) do
    begin
      if P[i].X < bbox1.X then
        bbox1.X := P[i].X
      else
      if P[i].X > bbox2.X then
        bbox2.X := P[i].X;
      if P[i].Y < bbox1.Y then
        bbox1.Y := P[i].Y
      else
      if P[i].Y > bbox2.Y then
        bbox2.Y := P[i].Y
    end;
  end;
begin
  with PolyA[0] do
  begin
    boxA1.Assign(X, Y);
    boxA2.Assign(X, Y);
  end;
  with PolyB[0] do
  begin
    boxB1.Assign(X, Y);
    boxB2.Assign(X, Y);
  end;
  createBoundingBox(PolyA, boxA1, boxA2);
  createBoundingBox(PolyB, boxB1, boxB2);
  Result := BoundingBoxCollisionCheck(boxA1, boxA2, boxB1, boxB2);
end;

function SATCollisionCheck(const PolyA, PolyB: TPolygon): Boolean;
var
  P1, P2: PPolygon;
  function collisionCheck: boolean;
  var
    len: Integer;
    i: Integer;
    n: TVector;
    minMaxA, minMaxB: TVector;
    procedure getMinMaxAxisMappedValues(const Polygon: PPolygon; var min, max: Single);
    // axis = n
    var
      j: Integer;
      scalar: Single;
    begin
      min := scalarProduct(Polygon^[0], n);
      max := min;
      for j := 1 to high(Polygon^) do
      begin
        scalar := scalarProduct(Polygon^[j], n);
        if (scalar < min) then
          min := scalar
        else
        if scalar > max then
          max := scalar;
      end;
    end;
    function collision1DCheck(const A, B: TVector): Boolean;
    begin
      Result := not((A.X > B.Y) or (A.Y < B.X));
    end;
  begin
    Result := False;
    // gehe alle Punkte des Polygons 1 durch, bilde die Gerade und berechne darauf die Normale
    len := Length(P1^);
    for i := 0 to len do
    begin
      with P1^[(i+1) mod len] do
        n := normal(vector(X - P1^[i].X, Y - P1^[i].Y)); // n = normal(AB)
      normalize(n);
      // mappe nun alle punkte beider Polygone auf die Achse (punkt * n) und merke dir
      // min max Werte
      getMinMaxAxisMappedValues(P1, minMaxA.X, minMaxA.Y);
      getMinMaxAxisMappedValues(P2, minMaxB.X, minMaxB.Y);
      // wenn beide Bereiche sich NICHT schneiden, so kommts zu keienr Kollision und es kann beendet werden
      if not collision1DCheck(minMaxA, minMaxB) then Exit;
    end;
    Result := True;
  end;
begin
  Result := False;
  if (Length(PolyA) < 2) or (Length(PolyB) < 2) then Exit;
  if not BoundingBoxCollisionCheck(PolyA, PolyB) then Exit;
  P1 := @PolyA;
  P2 := @PolyB;
  if collisionCheck then
  begin
    P1 := @PolyB;
    P2 := @PolyA;
    Result := collisionCheck;
  end;
end;
Das ganze ist noch mit Bounding Boxes optimiert.
Hf
das Erkennen beginnt, wenn der Erkennende vom zu Erkennenden Abstand nimmt
MfG

Geändert von Aphton (23. Mär 2012 um 17:12 Uhr)
  Mit Zitat antworten Zitat