AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Projekte Pixelgenaue Kollision Abfrage mit Bitmaske
Thema durchsuchen
Ansicht
Themen-Optionen

Pixelgenaue Kollision Abfrage mit Bitmaske

Ein Thema von Gandalfus · begonnen am 16. Apr 2007 · letzter Beitrag vom 21. Jun 2010
Antwort Antwort
Gandalfus
Registriert seit: 19. Apr 2003
Also es geht um Pixelgenaue Kollision Abfrage mit Bitmaske

Das Prinzip ist hier beschreiben:
http://www.codeworx.org/gamedev_tuts...sion_pixel.php

Ich habe es jetzt für beliebig grosse Bilder realisiert.

die Klasse
Delphi-Quellcode:
{************************************************************}
{                                                            }
{   Pixelgenaue Kollision Abfrage mit Bitmaske               }
{                                                            }
{   Copyright (c) 2007 Henning Brackmann  [url]www.blubplayer.de[/url]  }
{                                                            }
{************************************************************}

unit U_KollisionAbfrage;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TBildBitMaske = class
  private
    SegmentSize: integer;//Segment der Maske in Bit
    procedure initMaske;
    procedure getMaske(aBild: TBitmap);
    function isKollisionWidth(x1, y1, x2, y2: integer;
      Bild2: TBildBitMaske): boolean;
  public
    Maske: array of array of Cardinal;
    width,height: integer;
    widthSegmentCount: integer;
    constructor create(aBild: TBitmap);
  end;

implementation

function RectinRect(rect1,rect2: Trect): boolean;
begin
  result := true;
  if (rect1.Left >= rect2.BottomRight.x) then result:=false;
  if (rect1.top >= rect2.BottomRight.y) then result:=false;
  if (rect2.Left >= rect1.BottomRight.x) then result:=false;
  if (rect2.top >= rect1.BottomRight.y) then result:=false;
end;

{ TBildBitMaske }

constructor TBildBitMaske.create(aBild: TBitmap);
begin
  width := aBild.width;
  height := aBild.height;
  SegmentSize := sizeof(Cardinal)*8;
  initMaske;
  getMaske(aBild);
end;

procedure TBildBitMaske.initMaske;
var
  tempWidth,tempHeight: integer;
  x,y: integer;
begin
  //Unterscheidung ob Rest oder nicht
  //Bei Rest würde sonst Addition von 1 falsches Ergebnis ergeben
  if (Width mod SegmentSize) = 0 then
    tempWidth := Width div SegmentSize
  else
    tempWidth := trunc(Width/SegmentSize)+1;

  widthSegmentCount := tempWidth;
  tempHeight := Height;
  setlength(Maske,tempWidth,tempHeight);
  for x := 0 to tempWidth - 1 do
    for y := 0 to tempHeight - 1 do
      Maske[x,y]:=0;

end;

procedure TBildBitMaske.getMaske(aBild: TBitmap);
type
  PixArray = Array [1..3] of byte;
var
  p: ^PixArray;
  x,y: integer;
  Color: longint;
  Bild: TBitmap;
  Segment: Cardinal;
begin
  Bild:= TBitmap.create;
  Bild.Assign(aBild);
  aBild.PixelFormat := pf24bit;
   //Reihenfolge (Scanline) der Farbwerte pro Pixel: Blau - Grün - Rot.


  Color:=ColortoRGB(aBild.TransparentColor);
  for y:=0 to bild.Height-1 do
  begin
    p:= bild.ScanLine[y];
    for x:=bild.Width-1 downto 0 do //downto wegen or 1 und nicht and 100000...
    begin
      Segment := Maske[widthSegmentCount-1 - (x div SegmentSize),y];
      if (GetBValue(Color)=p^[1]) and (GetGValue(Color)=p^[2]) and (GetRValue(Color)=p^[3]) then
      begin
        //transparentefarbe --> 0
       Segment := Segment shl 1;
      end
      else
      begin
        //nicht transparentefarbe --> 1
        Segment := Segment shl 1;
        Segment := Segment or 1;
      end;
      Maske[widthSegmentCount-1 - (x div SegmentSize),y] := Segment;
      Inc(p);
    end;
  end;

  Bild.free;
end;



function TBildBitMaske.isKollisionWidth(x1,y1: integer; x2,y2: integer; Bild2: TBildBitMaske): boolean;
var
  y1start,y1ende: integer;
  y2start,y2ende: integer;
  x1SegmentStart,x1SegmentEnde: integer;
  x2SegmentStart,x2SegmentEnde: integer;

  tempSegment: Cardinal;
  x,y: integer;
  shiftcountRight: integer;
  shiftcountLeft: integer;
  indexLeftBild1Segment: integer;
  indexRightBild1Segment: integer;
  Bild1CalcWidth,Bild2CalcWidth: integer;

  SchnittRect: TRect;
  Bild1SchnittRect: TRect;
  Bild2SchnittRect: TRect;
begin
  if RectinRect(Rect(x1,y1,x1+width,y1+height),Rect(x2,y2,x2+Bild2.width,y2+Bild2.height)) then
  begin

    //Koordinaten umrechnen durch die einteilung in elemnet
    //ist Bildbreite immer vielfaches von 32 Also muss Von der normalen breite
    //umgerechnet werden
    x1:=x1-(widthSegmentCount*SegmentSize-Width);
    x2:=x2-(Bild2.widthSegmentCount*SegmentSize-Bild2.Width);

    //Breite umrechnen
    Bild1CalcWidth := self.widthSegmentCount*SegmentSize;
    Bild2CalcWidth := Bild2.widthSegmentCount*SegmentSize;


    IntersectRect(SchnittRect,Rect(x1,y1,x1+Bild1CalcWidth,y1+height),Rect(x2,y2,x2+Bild2CalcWidth,y2+Bild2.height));
    Bild1SchnittRect := Rect(SchnittRect.Left-x1,SchnittRect.Top-y1,SchnittRect.Right-x1-1,SchnittRect.Bottom-y1-1);
    Bild2SchnittRect := Rect(SchnittRect.Left-x2,SchnittRect.Top-y2,SchnittRect.Right-x2-1,SchnittRect.Bottom-y2-1);

    y1start := Bild1SchnittRect.top;
    y1ende := Bild1SchnittRect.bottom;
    y2start := Bild2SchnittRect.top;
    y2ende := Bild2SchnittRect.bottom;

    x1SegmentStart := Bild1SchnittRect.Left div Segmentsize;
    x1SegmentEnde := Bild1SchnittRect.Right div Segmentsize;
    x2SegmentStart := Bild2SchnittRect.Left div Segmentsize;
    x2SegmentEnde := Bild2SchnittRect.Right div Segmentsize;

    shiftcountRight := (Bild2CalcWidth+(x2-x1)) mod Segmentsize;
    shiftcountLeft := Segmentsize-shiftcountRight;

    result := false;
    for x := x2SegmentStart to x2SegmentEnde do
    begin
      if (x2+x*Segmentsize)>=(x1+x1SegmentStart*Segmentsize) then
      begin //Es gibt links vom Element ein Bild1 element
        indexLeftBild1Segment := (x2+(x)*Segmentsize-x1) div Segmentsize;
        for y := y2start to y2ende do
        begin
          tempSegment := (Bild2.maske[x,y] shr shiftcountRight);
          if (Maske[indexLeftBild1Segment,y1start+y-y2start] and tempSegment)<>0 then
          begin
            result:=true;
            exit;
          end;
        end;
      end;

      if (x2+x*Segmentsize)<=(x1+x1SegmentEnde*Segmentsize) then
      begin //Es gibt rechts vom Element ein Bild1 element
        indexRightBild1Segment := ((x2-1-x1+SegmentSize+(x*Segmentsize)) div Segmentsize);
        for y := y2start to y2ende do
        begin
          tempSegment := (Bild2.maske[x,y] shl shiftcountLeft);
          if (Maske[indexRightBild1Segment,y1start+y-y2start] and tempSegment)<>0 then
          begin
            result:=true;
            exit;
          end;
        end;
      end;

    end;

  end;
end;

end.
Eine einfachs Beispielprogramm:
Benötigt: 2 Images, 1 Timer
Delphi-Quellcode:
procedure TForm1.Timer1Timer(Sender: TObject);
var
  BildBitMaske1,BildBitMaske2: TBildBitMaske;
begin

  if ((GetAsyncKeystate(vk_left)) <> 0) then
  begin
    Image24.Left := Image2.Left-1;
  end;
  if ((GetAsyncKeystate(vk_right)) <> 0) then
  begin
    Image2.Left := Image2.Left+1;
  end;
  if ((GetAsyncKeystate(vk_up)) <> 0) then
  begin
    Image2.top := Image2.top-1;
  end;
  if ((GetAsyncKeystate(vk_down)) <> 0) then
  begin
    Image2.top := Image2.top+1;
  end;

  BildBitMaske1 := TBildBitMaske.create(Image1.Picture.Bitmap);
  BildBitMaske2 := TBildBitMaske.create(Image2.Picture.Bitmap);
  if BildBitMaske1.isKollisionWidth(Image1.Left,Image1.top,Image2.Left,Image2.top,BildBitMaske2) then
    Form1.Canvas.textout(0,0,'Kollision ')
  else
    Form1.Canvas.textout(0,0,'keine Kollision');

end;
Bemerkung: TBildBitMaske.create aufrufen (also die Bitmaske erstellen) solte man aus Performance gründen nur einmal machen im Program machen. Ich habe es hier wegen der Übersichtlichkeit im Timer gemacht.

mfg
Besucht doch mal meine Homepage
 
Sfaizst
 
#2
  Alt 21. Jun 2010, 18:42
Hallo, tschuldigt, das ich ein solch altes Thema wieder aus der Erde grabe, jedoch habe ich festgestellt, dass die collision nicht mehr richtig funktioniert, wenn ich den create Teil nicht mehr im Timer habe.
Auch nach rumprobieren wie sonst was habe ich bisher noch keine (brauchbare) Lösung gefunden, zudem habe ich die Klasse ein wenig erweitert (Grundfunktionen wie collision nicht geändert), der fehler tritt jedoch beim original sowie bei meiner erweiterung auf (wenn das create der Klasse nicht im Timer ist)

Bsp der fehlerhaften Kollision: Project22.exe

Ich hoffe, ihr könnt mir eine Hilfe geben, woran dies liegen könnte, ich meine, wenn die collision nur funktioniert, wenn create davor aufgerufen wird, muss doch irgendeine variable in der collision geändert werden (oder bin ich auf dem holzpfad?) und wenn, welche variable(n)?

Meine Erweiterung der Klasse:
Delphi-Quellcode:
{************************************************************}
{                                                            }
{   Pixelgenaue Kollision Abfrage mit Bitmaske               }
{                                                            }
{   Copyright (c) 2007 Henning Brackmann  [url]www.blubplayer.de[/url]  }
{   Erweiterung der Klasse von Sfaizst                       }
{************************************************************}

unit U_KollisionAbfrage;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TBitMask = class
  private
    FMaske: array of array of Cardinal;
    FSegmentSize,//Segment der Maske in Bit
    Fwidth, Fheight,
    FwidthSegmentCount: integer;
    procedure initMask;
  public
    property Width : Integer read FWidth write FWidth;
    property Height : Integer read FHeight write FHeight;
    procedure LoadFromGraphic(aBild: TBitmap);
    procedure LoadFromStream(MS : TStream);
    procedure LoadFromStreamEx(MS : TMemoryStream);
    procedure SaveToStream(MS : TStream);
    procedure SaveToStreamEx(MS : TMemoryStream);
    function Collision(x1, y1, x2, y2: integer; BitMask2: TBitMask): boolean;
  end;

  TBitMaskArray = class
  private
    FSize : Integer;
  public
    Masks : Array of TBitMask;
    property Size : Integer read FSize write FSize;
    procedure LoadFromGraphic(aBild: TBitmap);
    procedure LoadFromStream(MS : TStream);
    procedure LoadFromStreamEx(MS : TMemoryStream);
    procedure SaveToStream(MS : TStream);
    procedure SaveToStreamEx(MS : TMemoryStream);
  end;

implementation

function RectinRect(rect1,rect2: Trect): boolean;
begin
  result := true;
  if (rect1.Left >= rect2.BottomRight.x) then result:=false;
  if (rect1.top >= rect2.BottomRight.y) then result:=false;
  if (rect2.Left >= rect1.BottomRight.x) then result:=false;
  if (rect2.top >= rect1.BottomRight.y) then result:=false;
end;

{ TBitMask }

procedure TBitMask.initMask;
var
  tempWidth,tempHeight: integer;
  x,y: integer;
begin
  FSegmentSize := sizeof(Cardinal)*8;
  //Unterscheidung ob Rest oder nicht
  //Bei Rest würde sonst Addition von 1 falsches Ergebnis ergeben
  if (FWidth mod FSegmentSize) = 0 then
    tempWidth := FWidth div FSegmentSize
  else
    tempWidth := trunc(FWidth/FSegmentSize)+1;

  FwidthSegmentCount := tempWidth;
  tempHeight := FHeight;
  setlength(FMaske,tempWidth,tempHeight);
  for x := 0 to tempWidth - 1 do
    for y := 0 to tempHeight - 1 do
      FMaske[x,y]:=0;

end;

procedure TBitMask.LoadFromGraphic(aBild: TBitmap);
type
  PixArray = Array [1..3] of byte;
var
  p: ^PixArray;
  x,y: integer;
  Color: longint;
  Bild: TBitmap;
  Segment: Cardinal;
begin
  Fwidth := aBild.width;
  Fheight := aBild.Height;
  initMask;
  Bild:= TBitmap.create;
  Bild.Assign(aBild);
  aBild.PixelFormat := pf24bit;
   //Reihenfolge (Scanline) der Farbwerte pro Pixel: Blau - Grün - Rot.

  Color:=ColortoRGB(aBild.TransparentColor);
  for y:=0 to bild.Height-1 do
  begin
    p:= bild.ScanLine[y];
    for x:=bild.Width-1 downto 0 do //downto wegen or 1 und nicht and 100000...
    begin
      Segment := FMaske[FwidthSegmentCount-1 - (x div FSegmentSize),y];
      if (GetBValue(Color)=p^[1]) and (GetGValue(Color)=p^[2]) and (GetRValue(Color)=p^[3]) then
      begin
        //transparentefarbe --> 0
       Segment := Segment shl 1;
      end
      else
      begin
        //nicht transparentefarbe --> 1
        Segment := Segment shl 1;
        Segment := Segment or 1;
      end;
      FMaske[FwidthSegmentCount-1 - (x div FSegmentSize),y] := Segment;
      Inc(p);
    end;
  end;

  Bild.free;
end;


procedure TBitMask.LoadFromStreamEx(MS: TMemoryStream);
var x,y : Integer;
begin
  InitMask;
  for x := 0 to High(FMaske) do
    for y := 0 to High(FMaske[x]) do
      MS.ReadBuffer(FMaske[x,y],sizeof(Cardinal));
end;

procedure TBitMask.LoadFromStream(MS: TStream);
var
 TmpMs : TMemoryStream;
begin
  TmpMs := TMemoryStream.Create;
  try
    MS.Position := 0;
    TmpMs.CopyFrom(MS,MS.Size);
    TmpMs.Position := 0;
    TmpMs.ReadBuffer(FWidth,sizeof(Integer));
    TmpMs.ReadBuffer(FHeight,sizeof(Integer));
    LoadFromStreamEx(TmpMS);
    MS.Position := 0;
  finally
    TmpMS.Free;
  end;
end;

procedure TBitMask.SaveToStreamEx(MS: TMemoryStream);
var x,y : Integer;
begin
  for x := 0 to High(FMaske) do
    for y := 0 to High(FMaske[x]) do
      MS.WriteBuffer(FMaske[x,y],sizeof(Cardinal));
end;

procedure TBitMask.SaveToStream(MS: TStream);
var
 TmpMS : TMemoryStream;
begin
  TmpMS := TMemoryStream.Create;
  try
    TmpMs.WriteBuffer(FWidth,sizeof(Integer));
    TmpMs.WriteBuffer(FHeight,sizeof(Integer));
    SaveToStreamEx(TmpMS);
    Ms.Position := 0;
    MS.CopyFrom(TmpMs,TmpMs.Size);
    Ms.Position := 0;
  finally
    TmpMS.Free;
  end;
end;

function TBitMask.Collision(x1,y1: integer; x2,y2: integer; BitMask2: TBitMask): boolean;
var
  y1start,y1ende: integer;
  y2start,y2ende: integer;
  x1SegmentStart,x1SegmentEnde: integer;
  x2SegmentStart,x2SegmentEnde: integer;

  tempSegment: Cardinal;
  x,y: integer;
  shiftcountRight: integer;
  shiftcountLeft: integer;
  indexLeftBild1Segment: integer;
  indexRightBild1Segment: integer;
  Bild1CalcWidth,Bild2CalcWidth: integer;

  SchnittRect: TRect;
  Bild1SchnittRect: TRect;
  Bild2SchnittRect: TRect;
begin
  if RectinRect(Rect(x1,y1,x1+Fwidth,y1+Fheight),Rect(x2,y2,x2+BitMask2.Fwidth,y2+BitMask2.Fheight)) then
  begin

    //Koordinaten umrechnen durch die einteilung in elemnet
    //ist Bildbreite immer vielfaches von 32 Also muss Von der normalen breite
    //umgerechnet werden
    x1:=x1-(FwidthSegmentCount*FSegmentSize-FWidth);
    x2:=x2-(BitMask2.FwidthSegmentCount*FSegmentSize-BitMask2.FWidth);

    //Breite umrechnen
    Bild1CalcWidth := self.FwidthSegmentCount*FSegmentSize;
    Bild2CalcWidth := BitMask2.FwidthSegmentCount*FSegmentSize;


    IntersectRect(SchnittRect,Rect(x1,y1,x1+Bild1CalcWidth,y1+Fheight),Rect(x2,y2,x2+Bild2CalcWidth,y2+BitMask2.Fheight));
    Bild1SchnittRect := Rect(SchnittRect.Left-x1,SchnittRect.Top-y1,SchnittRect.Right-x1-1,SchnittRect.Bottom-y1-1);
    Bild2SchnittRect := Rect(SchnittRect.Left-x2,SchnittRect.Top-y2,SchnittRect.Right-x2-1,SchnittRect.Bottom-y2-1);

    y1start := Bild1SchnittRect.top;
    y1ende := Bild1SchnittRect.bottom;
    y2start := Bild2SchnittRect.top;
    y2ende := Bild2SchnittRect.bottom;

    x1SegmentStart := Bild1SchnittRect.Left div FSegmentsize;
    x1SegmentEnde := Bild1SchnittRect.Right div FSegmentsize;
    x2SegmentStart := Bild2SchnittRect.Left div FSegmentsize;
    x2SegmentEnde := Bild2SchnittRect.Right div FSegmentsize;

    shiftcountRight := (Bild2CalcWidth+(x2-x1)) mod FSegmentsize;
    shiftcountLeft := FSegmentsize-shiftcountRight;

    result := false;
    for x := x2SegmentStart to x2SegmentEnde do
    begin
      if (x2+x*FSegmentsize)>=(x1+x1SegmentStart*FSegmentsize) then
      begin //Es gibt links vom Element ein Bild1 element
        indexLeftBild1Segment := (x2+(x)*FSegmentsize-x1) div FSegmentsize;
        for y := y2start to y2ende do
        begin
          tempSegment := (BitMask2.Fmaske[x,y] shr shiftcountRight);
          if (FMaske[indexLeftBild1Segment,y1start+y-y2start] and tempSegment)<>0 then
          begin
            result:=true;
            exit;
          end;
        end;
      end;

      if (x2+x*FSegmentsize)<=(x1+x1SegmentEnde*FSegmentsize) then
      begin //Es gibt rechts vom Element ein Bild1 element
        indexRightBild1Segment := ((x2-1-x1+FSegmentSize+(x*FSegmentsize)) div FSegmentsize);
        for y := y2start to y2ende do
        begin
          tempSegment := (BitMask2.Fmaske[x,y] shl shiftcountLeft);
          if (FMaske[indexRightBild1Segment,y1start+y-y2start] and tempSegment)<>0 then
          begin
            result:=true;
            exit;
          end;
        end;
      end;

    end;

  end;
end;

{ TBitMaskArray }

procedure TBitMaskArray.LoadFromGraphic(aBild: TBitmap);
var
  TmpBild : tBitmap;
  I, TilePosX, TilePosY : Integer;
begin
  if FSize = 0 then fSize := 32;
  TmpBild := TBitmap.Create;
  TmpBild.Width := fSize;
  TmpBild.Height := fSize;
  SetLength(Masks,(aBild.Height * aBild.Width) div FSize);
         for I := 0 to ((aBild.Height * aBild.Width) div FSize)-1 do
          begin
          if I = 0 then
             begin
               TilePosX := 0;
               TilePosY := 0;
             end Else
             begin
          if TilePosX < (aBild.Width div FSize) -1 then
             Inc(TilePosX) Else
             begin
               TilePosX := 0;
               Inc(TilePosY);
             end;
             end;
             TmpBild.Canvas.CopyRect(Rect(0, 0, TmpBild.Width, TmpBild.Height), aBild.Canvas, Rect(TilePosX*FSize,TilePosY+FSize,(TilePosX+1)*fSize,(TilePosY+1)*FSize));
             Masks[I] := TBitMask.Create;
             with Masks[I] do
               begin
                 Width := fSize;
                 Height := fSize;
                 LoadFromGraphic(TmpBild);
               end;
    end;
end;

procedure TBitMaskArray.LoadFromStreamEx(MS: TMemoryStream);
var I : Integer;
begin
  if FSize = 0 then fSize := 32;
  for I := 0 to High(Masks) do
    begin
      Masks[I] := TBitMask.Create;
      with Masks[I] do
        begin
          Width := fSize;
          Height := fSize;
          LoadFromStreamEx(MS);
        end;
    end;
end;

procedure TBitMaskArray.LoadFromStream(MS: TStream);
var
 TmpMs : TMemoryStream;
 CountInt : Integer;
begin
  TmpMs := TMemoryStream.Create;
  try
    MS.Position := 0;
    TmpMs.CopyFrom(MS,MS.Size);
    TmpMs.Position := 0;
    TmpMs.ReadBuffer(FSize,sizeof(Integer));
    TmpMs.ReadBuffer(CountInt,sizeof(Integer));
    SetLength(Masks,CountInt);
    LoadFromStreamEx(TmpMS);
    MS.Position := 0;
  finally
    TmpMS.Free;
  end;
end;

procedure TBitMaskArray.SaveToStreamEx(MS: TMemoryStream);
var I : Integer;
begin
  for I := 0 to High(Masks) do
    begin
      Masks[I].SaveToStreamEx(MS);
    end;
end;

procedure TBitMaskArray.SaveToStream(MS: TStream);
var
 TmpMs : TMemoryStream;
 CountInt : integer;
begin
  TmpMs := TMemoryStream.Create;
  try
    TmpMs.WriteBuffer(FSize,sizeof(Integer));
    CountInt := Length(Masks);
    TmpMs.WriteBuffer(CountInt,sizeof(Integer));
    SaveToStreamEx(TmpMS);
    TmpMs.Position := 0;
    Ms.CopyFrom(TmpMs,TmpMs.Size);
  finally
    TmpMS.Free;
  end;
end;


end.
Ich hoffe ihr könnt mir helfen

Viele Grüße

Sfaizst
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:29 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz