Skip to main content
GameDev.net gamedev.net
🔒 Locked

pixel perfect collision (almost?)

Started by dhardin Apr 3, 2010 at 11:58 PM 3 replies 2.5k views
Original Post
dhardin
dhardin
EDIT: WHoops! I forgot to tell you. This game is made using DarkGDK, MS Visual C++, and DirectX Hey everyone, I've been working on trying to get this pixel perfect collision function to work for days and I can't seem to get it. I can only get pixel perfect when I move a sprite into the top left quadrant of another sprite, everywhere else is kinda weird. NOTE: All sprite coordinates are at top left corner of sprite. So when I say dbSprite(i, 1); this will move sprite with ID "i" to the right 1 pixel Here is the beta version of "Pixel Perfect Collisions" Headers/Prototypes
#include "DarkGDK.h"
#include <vector>
#if _MSC_VER > 1020   // if VC++ version is > 4.2
   using namespace std;  // std c++ libs implemented in std
   #endif
bool BuildSpriteMask(int s1, vector<int> &v);
bool PixelCollision( int s1, int s2 );
Main
void DarkGDK ( void )
{

	// when starting a Dark GDK program it is useful to set global
	// application properties, we begin by turning the sync rate on,
	// this means we control when the screen is updated, we also set
	// the maximum rate to 60 which means the maximum frame rate will
	// be set at 60 frames per second
	dbSyncOn   ( );
	dbSyncRate ( 60 );

	// a call is made to this function so we can stop the GDK from
	// responding to the escape key, we can then add in some code in our
	// main loop so we can control what happens when the escape key is pressed
	dbDisableEscapeKey ( );

	dbLoadImage ( "land.bmp", 1 );
	dbSprite ( 1, 0, 0, 1 );
	dbLoadImage("collisionTXT.bmp", 3);
	dbLoadImage ("circle1.bmp", 4);
	dbLoadImage("circlePlayer.bmp",2);
    dbSprite (4, 300, 100, 4);
	dbSprite(3,0,0,3);
	dbSprite ( 2,0, 0, 2 );

	// now we come to our main loop, we call LoopGDK so some internal
	// work can be carried out by the GDK
	while ( LoopGDK ( ) )
	{
			if(dbRightKey() == 1)
			{
				dbRotateSprite(2,90);
				dbMoveSprite(2,1);
				dbRotateSprite(2,0);

			}

			if(dbLeftKey() == 1)
			{
				dbRotateSprite(2,270);
				dbMoveSprite(2,1);
				dbRotateSprite(2,0);
			}
			
			if(dbUpKey())
			{
				dbMoveSprite(2,1);
			}

			if(dbDownKey())
			{
				dbRotateSprite(2,180);
				dbMoveSprite(2,1);
				dbRotateSprite(2,0);
			}

			
			if(PixelCollision(2,4))
			{
				dbShowSprite(3);
			}
			else
			{
				dbHideSprite(3);
			}



	
		if ( dbEscapeKey ( ) )
			break;

		dbSync();

		// here we make a call to update the contents of the screen
	}

	// when the user presses escape the code will break out to this location
	// and we can free up any previously allocated resources
	
	// delete all the sprites
	for(int i = 1; i < 5; i++)
		dbDeleteSprite ( i );

	// delete the backdrop image
	dbDeleteImage ( 1 );

	// and now everything is ready to return back to Windows
	return;
}
Sprite Mask
bool BuildSpriteMask(int s1, vector<int> &v)
{
  bool result = FALSE;

  LPDIRECT3DTEXTURE9 pTexture = dbGetImagePointer(s1);

  // Lock the texture:
  D3DLOCKED_RECT rect;
  HRESULT hResult = pTexture->LockRect(0, ▭, NULL, D3DLOCK_READONLY);
  if(FAILED(hResult))
  {
    // Failed to lock the texture, error
    return 0;
  }

  // Get the bits as a BYTE*
  const BYTE* pBits = (BYTE*)rect.pBits;

  // Seek to the correct pixel and read it
  DWORD nBytesPerPixel = 4;
  const DWORD* pPixel = 0;  // (DWORD*)(pBits + y * rect.Pitch + x*nBytesPerPixel);
  //DWORD dwPixel = *pPixel;
 
  int sh = dbSpriteHeight(s1);
  int sw = dbSpriteWidth(s1);

  for(int y = 0; y < sh; y++)
  {
    for(int x = 0; x < sw; x++)
    {
      /* NOTE: I'm a little rusty on my math, but it should just be the same as before */
      pPixel = (DWORD*)(pBits + y * rect.Pitch + x*nBytesPerPixel);
      if( *pPixel == D3DCOLOR_XRGB(255,0,255))
      {
        v.push_back(0);
      }
      else
      {
        v.push_back(1);
      }
    }
  }

  // Unlock the texture
  pTexture->UnlockRect(0);

  return result;
}
Pixel Collision
bool PixelCollision( int s1, int s2 )
{

  /* 
    Load the Sprite1/Sprite2 variables into local memory so that we
    can avoid the extra overhead required for multiple calls to the
    related GDK calls. Functions are slower than memory reads, so
    we want to call them as few times as possible...
  */
  int s1x=dbSpriteX(s1);
  int s1y=dbSpriteY(s1);
  int s1w=dbSpriteWidth(s1);
  int s1h=dbSpriteHeight(s1);

  int s2x=dbSpriteX(s2);
  int s2y=dbSpriteY(s2);
  int s2w=dbSpriteWidth(s2);
  int s2h=dbSpriteHeight(s2);

  int s1x2 = s1x+s1w; 
  int s1y2 = s1y+s1h;

  int s2x2 = s2x+s2w;
  int s2y2 = s2y+s2h;

  /* Calculates the intersection rectangle between the two sprites */ 
  int minX = max(s1x, s2x);
  int maxX = min(s1x2, s2x2);
  int minY = max(s1y, s2y);
  int maxY = min(s1y2, s2y2);


  /* 
    if our instersection rect's Width is greater than the combined
    widths of both sprites, then no collision could have taken
    place since there would be at least one pixel between both
    sprites
  */
  if (s1x2 < s2x || s2x2 < s1x)
    return false; 

  /* 
    Similarly, if our instersection rect's height is greater than
    the combined heights of both sprites, then no collision could 
    have taken place since there would be at least one pixel between 
    both sprites
  */
  if (s1y2 < s2y || s2y2 < s1y)
    return false;



  /* 
    Now we know that there is still a potential for collision. In
    order to find out for sure, we have to test every pixel between
    both sprites that fall within the intersection rect - we can
    ignore all of the others.
  */

  /*
    First, we have to build the sprite masks of the images. To
    gain a performance boost, we wait until this point to actually
    build them because there was possibilities of no collisions
    that could be eliminated before wasting time reading video
    memory. Alas, we can't put it off any longer...

    Note: Ideally, the sprite masks would be built just once and
    stored in memory so that repeated calls to BuildSpriteMask()
    wouldn't be necessary...
  */
  vector<int> spriteOneVect;
  vector<int> spriteTwoVect;

  /* 
    We place a single call to BuildSpriteMask() for each sprite
    so that we only have to lock the video memory once instead of
    once for each pixel...
  */
  BuildSpriteMask(  s1, spriteOneVect);
  BuildSpriteMask(  s2, spriteTwoVect);



  /* Now that the sprite masks are built, let's test them */  
  /*FIRST: check to see what direction we are coming from
   Think of this in quadrants (We'll start in the top left,
   then to top right, then bottom left, then bottom right*/
  	int s1XAP;
	int s1YAP;
	int s2XAP;
	int s2YAP;

	 for(int currentY = minY; currentY < maxY; currentY++)
	 {
		 s1YAP = currentY - s1y;
		 s2YAP = currentY - s2y;
		for(int currentX = minX; currentX < maxX; currentX++)
		{
			s1XAP = currentX - s1x;
			s2XAP = currentX - s2x;
			if(spriteOneVect[s1XAP*s1YAP] == spriteTwoVect[s2XAP*s2YAP] && spriteOneVect[s1XAP*s1YAP] == 1)
				return true;
		}
	 }
  return false;
}
[Edited by - dhardin on April 4, 2010 1:06:18 PM]
dhardin
dhardin
I went through and tested most of the code out. I'm pretty sure the function for creating the bitmap is correct, if it is, then I'm 90% sure my error is in the last set of nested for loops.

EDIT: I just tested the bitmasks. I did this by creating an output file and just writing the desired colors as 1's and the others as 0's. Both bitmasks were created flawlessly.

[Edited by - dhardin on April 4, 2010 2:08:11 PM]
Vorpy
Vorpy
It looks like you aren't looking up the pixels in the masks correctly.

Instead of spriteOneVect[s1XAP*s1YAP], something like spriteOneVect[s1XAP+sprite1width*s1YAP]
Evil Steve
Evil Steve
I'd check that the collision mask is what you think it is first. The easiest way is to use 0 instead of D3DLOCK_READONLY, and then set the pixel to 0x00000000 if it's not collidable, or 0xffffffff if it is. Then when you render the sprite, you'll be able to clearly see what your code thinks the collision mask is.

If the collision mask is ok, then you can start looking at the actual collision function.
dhardin
dhardin
Thanks guys, I just got it late last night.

I posted the files here

http://forum.thegamecreators.com/?m=forum_view&t=168273&b=22


Yep, and it was indeed the way I was looking up the pixels in the collision function! Bitmasks were created flawlessly (I had them exported to text file as a picture of 1's and 0's). But it works great now!

Thanks everyone


p.s. Steve, thanks for posting your get pixel colors online. I could not have done this without that function.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.