Results 1 to 2 of 2

Thread: Pixel level collision detection in PowerDraw

  1. #1

    Pixel level collision detection in PowerDraw

    No one answered my question about getting pixelcolor in Powerdraw (for use in collision detection) . I guess not many people use PowerDraw which is a shame as it's powerful and fast for making 2D stuff and just lacks documentation. It has everything you could ask for like tga support and is far better and much faster if you have half decent hardware than DelphiX, which most people use for 2D in Delphi. I found out what I needed myself and fought I'd write a tutorial on simple 2D pixel level collision detection for powerdraw. My method uses alpha channel to determine if pixels are collidable and only work when drawing images without scaling/rotation. I'm a lousy self taught programmer from Lithuania, so this means foggy comments, strange terms and not too well optimized code, so if you see a way to improve it share your thoughts. I hope someone will find this useful.
    { ------ } means missing code which is not important for this topic, but might be necessary like Initializing/finalizeing stuff, clearing back buffer, etc. I assume that our resolution will be 640x480, and we'll use d3dfmt_a1r5g5b5 format for textures, because it's supported by most 3D cards and offers best colors with 16bit textures which have an alpha channel. Alpha channel is necessary in order to have transparent pixels. If you want different settings then the code needs some changes.
    Here goes:

    [pascal]{Firstly we declare a type to hold data about our images}
    TImg=record
    Image:TAGFimage; //Image data
    ImageFileName:string; //File name to load the file from
    Width,height,TexWidth,Texheight:integer; //Sizes of actual Images and textures they'll be on
    ColMap: array of array of boolean; //pixels collidable or not map
    end;

    {Now we need another type for Objects(Images/Sprites) so we can have a lot of them on screen
    and don't have data duplicates if their Images(pixels data) are the same}
    TObj=record
    Imgnr:integer //Number of the object?¢_Ts image
    x,y,xOld,yOld:integer; //object coordinates and their previous values for simple reaction on collision procedure
    Collidable:boolean; //Can this object collide with other objects
    end;

    {We need variables for holding TImg, TObj and their sizes.}
    var
    ImgArraySize:integer=0;
    ImgArray: array of Timg;
    ObjArraySize:integer=0;
    ObjArray: array of TObj;


    {A procedure to generate image collidabe pixel maps. What it does is scans through every images pixel colors and fills a 2d array of Boolean (False if alpha=0 or true otherwise). Why waste memory and not check alpha values in real time? Well we sacrifice some memory, but save quite a few precious cpu cycles}
    procedure TMainForm.GenPixMap(num:integer);
    var
    LRect: TD3DLocked_Rect; //PowerDraws variable which we'll need for pointer address calculation
    i, j: Integer;
    SrcPtr: Pointer; //a pointer to images pixel memory
    begin
    {we need to lock the image to access it's data, remember to unlock it or you won't be able to draw it}
    if (ImgArray[num].image.Lock(0, LRect) <> 0) then
    begin
    FinalizeAll();
    PowerGraph.Finalize();
    ShowMessage('Error locking source image!');
    Application.Terminate();
    Exit;
    end;

    {we allocate enough memory for ColMap}
    setlength(ImgArray[num].ColMap,ImgArray[num].height,ImgArray[num].Width);

    {this is a tricky part}
    for j:= 0 to ImgArray[num].height-1 do
    begin
    SrcPtr:= Pointer(Integer(LRect.pBits) + (LRect.Pitch * j));
    for i:= 0 to ImgArray[num].Width-1 do
    begin
    {We ignore rgb bits, because we only care about alpha, then check if it's 0. If you want to use oter then d3dfmt_a1r5g5b5 texture format you'll need to change these hexadecimal values, if you have no idea what the next line does I recommend you check out Allimonsters tutorial on bit manipulation in his website. If your textures will be 32Bit use longword instead of word on typecast}
    if not (Word(SrcPtr^) and word($f000)= word($0000))
    then imgarray[num].ColMap[j,i]:=true ;
    {If your textures are 32bit then increment SrcPtr by 4. Think like memory is addressed in bytes. Byte= 8bits, we want the next color so if it's format is 16bit then we need to move a pointer 16/8=2 bytes, if its 32bit then 32/8= 4 bytes.}
    Inc(Integer(SrcPtr), 2);
    end;
    end;
    ImgArray[num].image.Unlock(0);
    end;


    {Main collision management procedure}
    procedure collision_detection;
    var
    Which,Which1:integer; //Stores numbers of objects being checked
    begin
    for Which:=0 to ObjArraySize-1 do
    if ObjArray[Which].collidable then
    with ObjArray[Which] do
    begin
    {check for collisions between Obj's}
    for Which1:=0 to ImgArraySize-1 do
    if (Which1<>Which)and(ObjArray[Which1].collidable)
    then DetectColBetweenObj(Which,Which1); // calls another procedure which doe's what it's name says(detects collisions between objects)
    {collision with window borders assuming you are using 640x480 resolution}
    if x<0 then x:=0
    else if x+ImgArray[Imgnr].width> 640 then x:=640-ImgArray[Imgnr].width;
    if y<0 then y:=0
    else if y+ImgArray[Imgnr].height> 480 then y:=480-ImgArray[Imgnr].height;
    end;
    end;


    {Procedure to check if Objects collide with each other. Warning to use min,max functions you must add math to uses or make these simple functions yourself}
    procedure DetectColBetweenObj(Obj1,Obj2:integer);
    var
    colrect:trect;
    iscolided:boolean;
    i,j:integer;
    xo1,yo1,xo2,yo2,x11,x12,x21,x22,y11,y12,y21,y22,CR ectLen,CRectHgt:integer;// Great lot of integers, some are not needed (no speedup if optimizations are on) for the process, but greatly improve readability
    begin
    {We use these temporary integers to shorten important procedure text}
    x11:=ObjArray[Obj1].x;
    x12:=ObjArray[Obj1].x+ImgArray[ObjArray[Obj1].Imgnr].width;
    y11:=ObjArray[Obj1].y;
    y12:=ObjArray[Obj1].y+ImgArray[ObjArray[Obj1].Imgnr].height;
    x21:=ObjArray[Obj2].x;
    x22:=ObjArray[Obj2].x+ImgArray[ObjArray[Obj2].Imgnr].width;
    y21:=ObjArray[Obj2].y;
    y22:=ObjArray[Obj2].y+ImgArray[ObjArray[Obj2].Imgnr].height;
    {We get a rectangle which has 2 images overlapping coordinates}
    colrect:=rect(max(x11,x21),max(y11,y21),min(x12,x2 2),min(y12,y22));

    {this optimizes following loops a bit}
    xo1:=colrect.left-x11;
    yo1:=colrect.top -y11;
    xo2:=colrect.left-x21;
    yo2:=colrect.top -y21;
    {unnecessary easy reading optimization }
    CRectLen:=colrect.right-colrect.Left-1;
    CRectHgt:=colrect.Bottom-colrect.Top-1;
    iscolided:=false;

    {If Object images do not overlap this pixel check loop won't be entered}
    for i:=0 to CRectHgt do
    for j:=0 to CRectLen do
    if (ImgArray[ObjArray[Obj1].Imgnr].colmap[yo1+i,xo1+j]=true)and
    (ImgArray[ObjArray[Obj2].Imgnr].colmap[yo2+i,xo2+j]=true) then
    begin
    {we set this var to true instead of simply calling ActOnCol, because calling a procedure from a loop within a loop of another procedure = some speed loss)}
    iscolided:=true;
    {If we found overlapping object pixel we exit the loop}
    break;
    end;
    if iscolided= true then ActOnCol(Obj1,Obj2); //calls another procedure to take care of collided objects
    end;


    {Procedure for stuff we do if objects collide}
    procedure ActOnCol(Obj1,Obj2:integer);
    begin
    {We should do something like shifting collided object coordinates to their previous values,
    but in real world situations we usually need something more complicated like
    Checking collided objects types, comparing their movement speeds
    and changing their properties based on such data}
    ObjArray[Obj1].x:=ObjArray[Obj1].xold;
    ObjArray[Obj1].y:=ObjArray[Obj1].yold;
    ObjArray[Obj2].x:=ObjArray[Obj2].xold;
    ObjArray[Obj2].y:=ObjArray[Obj2].yold;
    end;


    //We need to add a few lines to timer procedure
    procedure TMainForm.PowerTimerProcess(Sender: TObject);
    var i :integer;// variable for loop control
    begin
    { ------ }
    collision_detection;// procedure detecting collisions
    {Keeping old coordinates (only for lame ActOnCol)}
    for i:=0 to ObjArraySize-1 do
    begin
    ObjArray[i].xOld:=ObjArray[i].x;
    ObjArray[i].yOld:=ObjArray[i].y;
    end;


    {We need a procedure to initialize our images. PowerDraw component and image initialization should be separate from oncreate like events because we'll need to re-init these every time on change to fullscreen/windowd mode, or they won't work.}
    procedure TMainForm.Initstuff;
    var
    i,j,k: Integer;//For loop control and error check
    begin
    { ----- }
    for i:=0 to Imgarraysize-1 do
    begin
    ImgArray[i].image:=TAGFImage.Create();
    k:=ImgArray[i].image.LoadFromFile(PowerGraph.D3DDevice8,ImgArray[i].ImageFileName,ImgArray[i].Width,ImgArray[i].height,ImgArray[i].TexWidth,ImgArray[i].Texheight, d3dfmt_a1r5g5b5);
    if (k <> 0) then
    begin
    MessageDlg(ImgArray[0].image.ErrorString(k), mtError, [mbOk], 0);
    for j:=0 to i-1 do ImgArray[j].image.Finalize;
    PowerGraph.Finalize();
    Application.Terminate();
    Exit;
    end;
    end;
    end;


    {like with initstuff procedure we'll want a procedure to finalize our images for use when changing to fullscreen/windowd mode and application termination}
    procedure TMainForm.FinalizeAll();
    var i:integer;
    begin
    { ------ }
    for i:=0 to ImgArraysize-1 do ImgArray[i].image.Finalize;
    end;


    {We should set up our ImgArray properties on onCreate event. I recommend loading these properties from a file, so we could change images that we use a don't need to recompile the whole program}
    procedure TMainForm.FormCreate(Sender: TObject);
    var
    i,j: Integer;//For loop control
    begin
    { ------ }
    {seting up imgArray properties, lets say we'll have 3 images}
    ImgArraySize:=3;
    setlength(imgarray,imgarraysize);
    {we must specify image sizes and their filenames}
    imgarray[0].Width:=49;
    imgarray[0].height:=49;
    imgarray[1].Width:=99;
    imgarray[1].height:=99;
    imgarray[2].Width:=29;
    imgarray[2].height:=29;
    imgarray[0].ImageFileName:='1.tga';
    imgarray[1].ImageFileName:='2.tga';
    imgarray[2].ImageFileName:='3.tga';

    {This automatically calculates minimal texture size for less video memory waste}
    for i:=0 to ImgArraySize-1 do with ImgArray[i] do
    begin
    j:=1;
    repeat
    j:=j*2
    until j>width;
    texwidth:=j;
    j:=1;
    repeat
    j:=j*2
    until j>height;
    texheight:=j;
    end;

    {setting up ObjArray properties, lets say there are 10 objects}
    ObjArraySize:=10;
    setlength(ObjArray,ObjArraySize);

    for i:=0 to ObjArraySize-1 do with ObjArray[i] do
    begin
    {here we should set whatever object properties we want on startup}
    Imgnr:=I mod 4;
    collidable:=true;
    x:=60*i;
    y:=80+100*(i mod 4);
    xOld:=x;
    yOld:=y;
    end;

    Initstuff;//initializes all our images
    for i:=0 to ImgArraySize-1 do GenPixMap(i);//generate colmaps
    end;


    {don't forget to call finalize all on application termination}
    procedure TMainForm.FormDestroy(Sender: TObject);
    begin
    FinalizeAll();
    { ------ }
    end;[/pascal]

  2. #2

    Pixel level collision detection in PowerDraw

    Hi, I've just got to this forums, I didn't know people know about PowerDraw here . It's nice you posted the tutorial, I'm also adding the pixel collision detection to full-screen release. Oh and thanks for your comments about PowerDraw!

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •