views:

606

answers:

2

I wrote a collision detection class that works off using BitmapData.hitTest. Basically, every update, the .draw method is called and then two BitmapData's are hitTest'ed against each other to see if there is a collision.

It works pretty well, however if I start rotating the source MovieClip I use for the BitmapData, the transformations don't get registered in the draw method. So what I did was pass through a Matrix in the draw method. That works. But here's the actual issue. If I output the BitmapData to a Bitmap on the stage, I see that Bitmap is, in fact, rotating, but now it's exceeding the bounds (width and height) of the BitmapData. Which is cropping / clipping the image of the source MovieClip.

My first guess would be to rotate the BitmapData, but rotation doesn't seem possible. There's just no properties that help you pull that off. Is there another way?

UPDATE: Basically, when the Bitmap clone of the MovieClip rotates outside the positive co-ordinate space, it doesn't get drawn. It rotates outside the bounds of the BitmapData set width and height. I can multiply the bounds by 2, and then center the bitmap inside the set bounds, but then the origin point is never fixed and the object is always being moved around. Works pretty well, but makes aligning the object to any specific point impossible.

Here's my isolated test code. It requires you to have a MovieClip on the stage named "iTest". I used a fairly vertical arrow shape (higher than it is wide) so I could keep track of rotations visually and help accentuate the clipping problem:

var rotateTimer:Timer = new Timer(10);

function rotateObject_init():void
{
 rotateTimer.addEventListener(TimerEvent.TIMER, rotateObject);
 rotateTimer.start();
}

function rotateObject_stop():void
{
 rotateTimer.removeEventListener(TimerEvent.TIMER, rotateObject);
 rotateTimer.stop();
}

function rotateObject(_event:TimerEvent):void
{
 // Deletes the previous bitmap
 if (this.getChildByName("iTestClone") != null)
 {
  removeChild(this.getChildByName("iTestClone"));

  testClone = null;
  testData = null;
 }

 iTest.rotation += 1;

 // Capture source BitmapData
 var testData:BitmapData = new BitmapData(iTest.width, iTest.height, false, 0xFFFFFF); 

 // Capture source transformations
 var testMatrix:Matrix = new Matrix();
 testMatrix.scale(iTest.scaleX, iTest.scaleY);
 testMatrix.rotate( iTest.rotation * (Math.PI / 180) );
 testMatrix.translate(0, 0);

 // Draw source with matrix
 testData.draw(iTest, testMatrix);

 // Output Bitmap
 var testClone:Bitmap = new Bitmap(testData);
 testClone.name = "iTestClone";
 testClone.transform = testMatrix.tra
 addChild(testClone);

 // Always makes sure source is visible
 this.swapChildren(iTest, testClone);
}

rotateObject_init();
A: 

You say that the bounds of the bitmapData are causing the clipping, but you are setting the bounds to be the width and height of the object. This suggests that the width and height returned are actually incorrect (i.e. not taking into account the rotation).

Perhaps try using getBounds() to find the actual width and height of iTest:

// Capture source BitmapData
var bounds:Rectangle = iTest.getBounds(iTest);
var testData:BitmapData = new BitmapData(bounds.width, bounds.height, false, 0xFFFFFF); 

I'm not entirely sure if this will work though -- I haven't tested any of this :-)

Cameron
Nice implementation of getBounds as an idea, but the width and height isn't the issue. It's the fact that once the cloned iTest Bitmap rotates outside the bounds of the BitmapData, it gets cliped. Basically, once it moves past the origin point of 0, 0. Once it rotates past 0 to the negatives, you see nothing. Hope this explains it well.
Daniel Carvalho
Ah, never mind in that case. I was thinking of a straight-forward case where the object was rotating around its center... which obviously isn't the case.
Cameron
+1  A: 

Hello Daniel,

Good question!

I have tried to make a BitmapData clone of a square rotated by 10 degrees. To get the rotation into the bitmap data I used the cloned object's matrix. The problem, as you mention is the offset you get because of the rotation pivot.

Here's how mine looks:

bounds

To get around that I though that if I get the difference between the with of the BitmapData and the right most point, that will give me the offset for x, and doing the similar operation for y, would get me in the right place.

Almost, I looks better (just one or two pixels seem harmed ):

bounds 2

Ok, I feel I got :)

offset

That offset is for x, but he misleading thing is, it is defined by the height of the bounding rectangle, not the width. Try this:

var box:Sprite = new Sprite();
box.graphics.lineStyle(0.1);
box.graphics.drawRect(0,0,150,100);
box.rotation = 10;
//get the size of the object rotated
var bounds:Rectangle = box.getBounds(this);
//make a bitmap data
var extra:int = 2;//cheating the trimmed pixels :)
var bitmapData:BitmapData = new BitmapData(bounds.width+extra,bounds.height+extra,false, 0x00009900);
//get the bottom left point
var bl:Point = getBottomLeft(box,bitmapData);
//get the matrix
var m:Matrix = box.transform.matrix;
//offset if
m.tx += Math.abs(bl.x);
//draw, using the offset, rotated matrix
bitmapData.draw(box,m);

var bitmap:Bitmap = new Bitmap(bitmapData);
bitmap.x = bitmap.y = 100;
addChild(bitmap);

function getBottomLeft(displayObject:DisplayObject,data:BitmapData):Point{
    var radius:int = displayObject.getBounds(displayObject).height;
    var angle:Number = displayObject.rotation * Math.PI / 180;
    var angle90:Number = angle + Math.PI * .5;
    return new Point(Math.cos(angle90) * radius, Math.sin(angle90) * radius);
}

You don't need a green background in the BitmapData constructor, that's for debugging. Also, you only need the the bottom left x position, therefore you need to return a Number instead of a Point.

so the slightly shorter version would be:

var box:Sprite = new Sprite();
box.graphics.lineStyle(0.1);
box.graphics.drawRect(0,0,150,100);
box.rotation = 10;

var bounds:Rectangle = box.getBounds(this);
var extra:int = 2;//cheating the trimmed pixels :)
var bitmapData:BitmapData = new BitmapData(bounds.width+extra,bounds.height+extra,false, 0x00009900);

var m:Matrix = box.transform.matrix;
m.tx += Math.abs(getLeftmost(box,bitmapData));
bitmapData.draw(box,m);

var bitmap:Bitmap = new Bitmap(bitmapData);
bitmap.x = bitmap.y = 100;
addChild(bitmap);

function getLeftmost(displayObject:DisplayObject,data:BitmapData):Number{
    var radius:int = displayObject.getBounds(displayObject).height;
    var angle90:Number = displayObject.rotation * Math.PI / 180 + Math.PI * .5;
    return Math.cos(angle90) * radius;
}

result:

offset 2

HTH,George

Note to self: be carefull what you wish for ^_^

George Profenza
Yeah, I've seen this approach before. It's a pitty it only works with boxes. Although, I'm starting to think that maybe creating a box over the object I'm wanting to do collision detection is the only way forward. A generalized hit box if you will. I don't see any other logical solution in sight.
Daniel Carvalho
I know what you mean...I wish I knew algorithms, or a bit more geometry.
George Profenza