Pixel-Perfect Collision Detection Revisited

I've finally gotten around to updating my pixel-perfect collision detection post (which is by far the most popular post on this blog with 48+ comments and a double-digit percentage of all my pageviews). Of course, now that I've gotten around to updating it there's not much point, as others have improved upon and trumped what I had. So, I'll just provide a quick recap of their pursuits (and my version of them).

First, The Actionscript Man grabbed my version, Grant Skinner's (which mine was indirectly based on), digested them, then spit out a new one that eliminated some draw calls. He even made a nice little app that lets you toggle between the various versions and compare them. The only drawback of his version is that it assumes the two objects have the same parent.

Next, the venerable Tink posted his version that uses the same technique as The Actionscript Man but factors a more complicated parent-child relationship. Tink's still assumes that both objects are on the stage. His code also gets bitten by a bug I've run into related to localToGlobal and nested SWFs (though I still haven't explored it enough to write about it yet), as discussed in the comments. Tink added an "accuracy" parameter that basically scales the bitmap used for testing up and down. I've not found this makes enough of a performance difference to warrant the dramatic differences it can make in a pixel-perfect test.

So, I updated my version to include the rendering method Tink and The Actionscript Man used, and included the "common parent" math my original one had. The upside of my version is that it doesn't care whether your objects are on the stage or not, just that they share a common parent.

CODE:
  1. /** Get the collision rectangle between two display objects. **/
  2. public static function getCollisionRect(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:int = 255):Rectangle
  3. {
  4.     // get bounding boxes in common parent's coordinate space
  5.     var rect1:Rectangle = target1.getBounds(commonParent);
  6.     var rect2:Rectangle = target2.getBounds(commonParent);
  7.    
  8.     // find the intersection of the two bounding boxes
  9.     var intersectionRect:Rectangle = rect1.intersection(rect2);
  10.    
  11.     // if not pixel-precise, we're done
  12.     if (!pixelPrecise) return intersectionRect;
  13.    
  14.     // size of rect needs to be integer size for bitmap data
  15.     intersectionRect.x = Math.floor(intersectionRect.x);
  16.     intersectionRect.y = Math.floor(intersectionRect.y);
  17.     intersectionRect.width = Math.ceil(intersectionRect.width);
  18.     intersectionRect.height = Math.ceil(intersectionRect.height);
  19.    
  20.     // if the rect is empty, we're done
  21.     if (intersectionRect.isEmpty()) return intersectionRect;
  22.    
  23.     // calculate the transform for the display object relative to the common parent
  24.     var parentXformInvert:Matrix = commonParent.transform.concatenatedMatrix.clone();
  25.     parentXformInvert.invert();
  26.     var target1Xform:Matrix = target1.transform.concatenatedMatrix.clone();
  27.     target1Xform.concat(parentXformInvert);
  28.     var target2Xform:Matrix = target2.transform.concatenatedMatrix.clone();
  29.     target2Xform.concat(parentXformInvert);
  30.    
  31.     // translate the target into the rect's space
  32.     target1Xform.translate(-intersectionRect.x, -intersectionRect.y);
  33.     target2Xform.translate(-intersectionRect.x, -intersectionRect.y);
  34.    
  35.     // combine the display objects
  36.     var bd:BitmapData = new BitmapData(intersectionRect.width, intersectionRect.height, false);
  37.     bd.draw(target1, target1Xform, new ColorTransform(1, 1, 1, 1, 255, -255, -255, tolerance), BlendMode.NORMAL);
  38.     bd.draw(target2, target2Xform, new ColorTransform(1, 1, 1, 1, 255, 255, 255, tolerance), BlendMode.DIFFERENCE);
  39.    
  40.     // find overlap
  41.     var overlapRect:Rectangle = bd.getColorBoundsRect(0xffffffff, 0xff00ffff);
  42.     overlapRect.offset(intersectionRect.x, intersectionRect.y);
  43.    
  44.     return overlapRect;
  45. }

Finally, if you really want some sophisticated, fully-featured, pixel-perfect collision detection *and* handling, check out Corey O'Neil's Collision Detection Kit. I haven't used it yet, but it looks pretty comprehensive with good performance.

  • http://www.jacksondunstan.com Jackson Dunstan

    This looks pretty expensive! I wonder if some caching could be used to avoid lots of the expensive work in here. I’ll bet a lot of this would be made possible if you knew what kind of assets you were using. For example, if your assets never resize, you could probably keep using the same BitmapData over and over. Just a thought. Thanks for the article!

  • K-Mac

    I’m trying to make a simple car game where the player uses the keys to make the (rectangle) car go around the track. When it hits the wall (for now) I just want it to back up at a certain speed so that the user can correct it and continue around the track. I’m using your class, calling it in a function that’s fun on an ENTER_FRAME and it works fine, to an extent.
    I’m having problems though, because, obviously, in a car game the car doesn’t always face the same direction: it gets rotated. What happens to me is that when the rotated car hits a wall, sometimes it gets stuck in the wall and doesn’t bounce back. Any ideas as to what I need to do differently so it bounces back no matter the orientation?

  • http://troygilbert.com/ Troy

    This collision detection code is not best-suited for this scenario. For what you’re describing, I’d recommend checking out one of the rigid body physics solutions. Box2D is well-maintained, well-documented, and has a healthy featureset. APE, which would handle the scenario you’re describing, is very, very easy to use (though doesn’t appear to be maintained any more).

  • Filipe

    I can't figure out how to use this class :( I figured out Skinner's class perfectly, since it's AS file followed a structure I'm used to work with. This, I can't. How do I pass the movieclips to be tested and how do I return them?

    Thanks for your work.

  • Filipe

    Well I think I got it. But nontheless, this solution still doesn't solve the rotation problem. My transparent objects, when rotated and still separated by as much as 20px (joined by transparent pixels of course) performs true on the hit test. Am I doing something else wrong?

    Thanks!

  • http://gamedev.michaeljameswilliams.com/2010/02/10/optimise-as3-for-speed/ Learn to Optimise your AS3 Code for Speed — Michael James Williams

    [...] can use a pixel-perfect collision method (such as this one), but these are very slow — you’re making Flash check every single pixel of one object [...]

  • cpucpu

    Hi, i'm just using your detection script.
    First of all, i'd like to say it is great, works very smooth.
    Second, considering the topic of this blog you shouldn't endorse nor advice using CDK as i come directly from it, and i call tell you is very heavy, the drop perfomance is noticeable.
    CDK is good, and has plenty of funcitons, but for gaming is not suitable.

  • cpucpu

    Question:
    I was looking at the code of this detection script, but as i dont really underestand it so much, i cannot judge how perfomance-friendly it be.

    So i dare to question you some things:
    -do you thing it has a good preformance, do you think could exist some others ways to achieve perfect collition detections with a huge amount of perfomance difference?
    -Trying to get collition detections with pixelPrecise=false attribute is better than hitTestObject?

  • Lock

    Great, thax

  • Evan M

    This runs great so long as I don't set the “pixelPrecise” argument to “true”. Once I do that then the collision rectangle never get made, its width and height remain at 0. I think I'm implementing this correctly and I've toyed with the tolerance and with various colliding objects, all to no effect. When the boolean is set to “false” this runs like a dream. Much faster than the CDK, which I've also used and was disappointed with the ridiculous lag.

  • Evan M

    This runs great so long as I don't set the “pixelPrecise” argument to “true”. Once I do that then the collision rectangle never get made, its width and height remain at 0. I think I'm implementing this correctly and I've toyed with the tolerance and with various colliding objects, all to no effect. When the boolean is set to “false” this runs like a dream. Much faster than the CDK, which I've also used and was disappointed with the ridiculous lag.

  • http://www.facebook.com/LeDiscoChat Benjamin Jaeger

    YOU RULE

    THANKS :D

  • Dude

    nice code but it doesn’t seem to work well with rotated mc’s

  • Aaron Hardy

    I’m seeing something similar to what Evan’s seeing. Take this example:

    var wrapper:Sprite = new Sprite();
    addChild(wrapper);

    var glyph:Sprite;

    var existingGlyphs:Sprite = new Sprite();
    for (var i:uint; i < 50; i++)
    {
    glyph = new Clef();
    glyph.x = i * 10;
    existingGlyphs.addChild(glyph);
    }
    wrapper.addChild(existingGlyphs);

    glyph = new Clef();
    glyph.y = 20;
    //addChild(glyph);
    //wrapper.addChild(glyph);

    var collision:Rectangle = PixelPerfectCollisionDetection.getCollisionRect(glyph, existingGlyphs, wrapper, true);
    //var collision:Rectangle = HitTest.complexIntersectionRectangle(existingGlyphs, glyph);
    existingGlyphs.x += collision.width;
    trace(collision);
    addChild(glyph);

    Notice that glyph is not added to the stage until after the collision detection. The traced output is (x=-14, y=-26, w=0, h=0). However, if I add the glyph to wrapper before the detection, the output is (x=-9, y=-25, w=22, h=48) which is correct. Note that these “glyphs” are symbols with a registration point in the center. I’ll try to dig into it a bit but if you post a fix that’d be awesome to hear about.

  • http://www.facebook.com/SiNT4X J.d. Ballard

    yep pixelPerfect = false returns a 0 width/height rectangle. Any fixes? I’ll dig around and see if I can’t fix it myself.

  • http://www.facebook.com/SiNT4X J.d. Ballard

    Ah, nevermind, works like a charm; parent cannot be the stage object or a Sprite. Use a MovieClip.

  • AlColella

    Men! Try use “this” on commonParent:DisplayObjectContainer argument.
    I used stage but didn´t work.
    With “this” all works fine!!

  • http://twitter.com/pendorcho Milton Maciel Loyola

    Wow! Thanks man! Much better performance than the Collision Detection Kit.

  • http://ughzoid.wordpress.com/2011/06/20/collision-detection-alternatives-to-hittest-and-hittestobject/ Collision detection alternatives to hitTest and hitTestObject |

    [...] Troy Gilbert’s Pixel-Perfect Collision [...]

  • Thomas Lecoz

    Hello !

    Actually, it’s possible to optimize it a little bit :)
    intersectionRect is useless, it’s defined by the bounding box of the tested object.
    Math.floor is useless too if you base the whole work on  bitmapData.
    Matrix calculations are useless too : only 4 substractions is needed. 
    BlendMode is useless : if you base the find-color function on static colors, you can easily know (via trace) what color you’re looking for if collide

    Here is an exemple :

    //1) At the beginning, before the collision detection process :

    //bgData is the bitmapData of the background
    //mcData is the bitmapData of the moving object

    var bgData:BitmapData = new BitmapData(100, 100, true, 0xff123456);var mcData:BitmapData = new BitmapData(100, 100, true, 0xff987654);

    //We assume that the position of the background  are 0,0
    //mcx = the position x of the moving object 
    //mcy = the position y of the moving object

    var mcx:int = 75;var mcy:int = 95;

    //I set defined colors to my objects//I want a red-color with 0.5 alpha for the backgroundbgData.colorTransform(bgData.rect, new ColorTransform(0, 0, 0, 1, 255, 0, 0, 255));
    bgData.colorTransform(bgData.rect, new ColorTransform(1, 1, 1, 0.5)); 

    //and a green_color with 0.5 alpha for the moving object 

    mcData.colorTransform(mcData.rect, new ColorTransform(0, 0, 0, 1, 0, 255, 0, 255));
    mcData.colorTransform(mcData.rect, new ColorTransform(1, 1, 1, 0.5));//I set a new Rectangle  and a new Point that I will use in collision detection processvar rect:Rectangle = new Rectangle();var pt:Point = new Point();//I get width & height of background into variablesvar bgw:int = bgData.width;var bgh:int = bgData.height;//now, still at start, I want to know what will be the collision colorvar test:BitmapData = mcData.clone();test.copyPixels(bgData,mcData.rect,new Point(),null,null,true);var collisionColor:int = test.getPixel32(1,1);//2)now that all is ready, I can process the collision detection fastly :)//I’m looking for bounding  intersectionsrect.width = bdw – mcx;rect.height = bgh – mcy;if(rect.width < 0 || rect.height doesn’t colliderect.x = mcx;
    rect.y = mcy;var productData:BitmapData = mc.clone(); productData.copyPixels(bgData, rect,pt, null, null, true);trace(“collisionRect = “+productData.getColorBoundsRect(0xffffffff,
    collisionColor , true) );++