Actionscript – How to rotate BitmapData when using draw method. Width height bounds clipping / cropping problem

actionscriptactionscript-3flash

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();

Best Answer

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 ^_^

Related Topic