Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get unrotated display object width/height of a rotated display object?

If I create a rectangle with 100px width and 100px height and then rotate it, the size of the element's "box" will have increased.

With 45 rotation, the size becomes about 143x143 (from 100x100).

Doing sometimes like cos(angleRad) * currentWidth seems to work for 45 rotation, but for other bigger angles it doesn't.

At the moment I am doing this:

var currentRotation = object.rotation;
object.rotation = 0;
var normalizedWidth = object.width;
var normalizedHeight = object.height;
object.rotation = currentRotation;

Surely, there must be a better and more efficient way. How should I get the "normalized" width and height of a displayobject, aka the size when it has not been rotated?

like image 734
Tom Avatar asked Jan 31 '10 19:01

Tom


3 Answers

The best approach would probably be to use the code posted in the question - i.e. to unrotate the object, check its width, and then re-rotate it. Here's why.

First, simplicity. It's obvious what's being done, and why it works. Anyone coming along later should have no trouble understanding it.

Second, accuracy. Out of curiosity I coded up all three suggestions currently in this thread, and I was not really surprised to find that for an arbitrarily scaled object, they give three slightly different answers. The reason for this, in a nutshell, is that Flash's rendering internals are heavily optimized, and among other things, width and height are not stored internally as floats. They're stored as "twips" (twentieths of a pixel) on the ground that further accuracy is visually irrelevant.

Anyway, if the three methods give different answers, which is the most accurate? For my money, the most correct answer is what Flash thinks the width of the object is when it's unrotated, which is what the simple method gives us. Also, this method is the only one that always give answers rounded to the nearest 1/20, which I surmise (though I'm guessing) to mean it's probably equal to the value being stored internally, as opposed to being a calculated value.

Finally, speed. I assume this will surprise you, but when I coded the three methods up, the simple approach was the fastest by a small margin. (Don't read too much into that - they were all very close, and if you tweak my code, a different method might edge into the lead. The point is they're very comparable.)

You probably expected the simple method to be slower on the grounds that changing an object's rotation would cause lots of other things to be recalculated, incurring overhead. But all that really happens immediately when you change the rotation is that the object's transform matrix gets some new values. Flash doesn't really do much with that matrix until it's next time to draw the object on the screen. As for what math occurs when you then read the object's width/height, it's difficult to say. But it's worth noting that whatever math takes place in the simple method is done by the Player's heavily optimized internals, rather than being done in AS3 like the algebraic method.

Anyway I invite you to try out the sample code, and I think you'll find that the simple straightforward method is, at the least, no slower than any other. That plus simplicity makes it the one I'd go with.


Here's the code I used:

// init
var clip:MovieClip = new MovieClip();
clip.graphics.lineStyle( 10 );
clip.graphics.moveTo( 12.345, 37.123 ); // arbitrary
clip.graphics.lineTo( 45.678, 29.456 ); // arbitrary
clip.scaleX = .87; // arbitrary
clip.scaleY = 1.12; // arbitrary
clip.rotation = 47.123; // arbitrary

// run the test
var iterations:int = 1000000;
test( method1, iterations );
test( method2, iterations );
test( method3, iterations );

function test( fcn:Function, iter:int ) {
 var t0:uint = getTimer();
 for (var i:int=0; i<iter; i++) {
  fcn( clip, i==0 );
 }
 trace(["Elapsed time", getTimer()-t0]);
}

// the "simple" method
function method1( m:MovieClip, traceSize:Boolean ) {
 var rot:Number = m.rotation;
 m.rotation = 0;
 var w:Number = m.width;
 var h:Number = m.height;
 m.rotation = rot;
 if (traceSize) { trace([ "method 1", w, h ]); }
}

// the "algebraic" method
function method2( m:MovieClip, traceSize:Boolean ) {
 var r:Number = m.rotation * Math.PI/180;
 var c:Number = Math.abs( Math.cos( r ) );
 var s:Number = Math.abs( Math.sin( r ) );
 var denominator:Number = (c*c - s*s); // an optimization
 var w:Number = (m.width * c - m.height * s) / denominator;
 var h:Number = (m.height * c - m.width * s) / denominator;
 if (traceSize) { trace([ "method 2", w, h ]); }
}

// the "getBounds" method
function method3( m:MovieClip, traceSize:Boolean ) {
 var r:Rectangle = m.getBounds(m);
 var w:Number = r.width*m.scaleX;
 var h:Number = r.height*m.scaleY;
 if (traceSize) { trace([ "method 3", w, h ]); }
}

And my output:

method 1,37.7,19.75
Elapsed time,1416
method 2,37.74191378925391,19.608455916982187
Elapsed time,1703
method 3,37.7145,19.768000000000004
Elapsed time,1589

Surprising, eh? But there's an important lesson here about Flash development. I hereby christen Fen's Law of Flash Laziness:

Whenever possible, avoid tricky math by getting the renderer to do it for you.

It not only gets you done quicker, in my experience it usually results in a performance win anyway. Happy optimizing!

like image 92
fenomas Avatar answered Nov 18 '22 10:11

fenomas


Here's the algorithmic approach, and its derivation.

First, let's do the opposite problem: Given a rectangle of unrotated width w, unrotated height h, and rotation r, what is the rotated width and height?

wr = abs(sin(r)) * h + abs(cos(r)) * w
hr = abs(sin(r)) * w + abs(cos(r)) * h

Now, try the problem as given: Given a rectangle of rotated width wr, rotated height hr, and rotation r, what is the unrotated width and height?

We need to solve the above equations for h and w. Let c represent abs(cos(r)) and s represent abs(sin(r)). If my rusty algebra skills still work, then the above equations can be solved with:

w = (wr * c - hr * s) / (c2 - s2)
h = (hr * c - wr * s) / (c2 - s2)

like image 32
Chip Uni Avatar answered Nov 18 '22 10:11

Chip Uni


You should get the bounds of your square in your object's coordinate space (which means no rotations).

e.g.

var b:Sprite = new Sprite();
b.graphics.lineStyle(0.1);
b.graphics.drawRect(0,0,100,100);
b.rotation = 10;
trace('global coordinate bounds: ' + b.getBounds(this));//prints global coordinate bounds: (x=-17.35, y=0, w=115.85, h=115.85);
trace('local coordinate bounds: ' + b.getBounds(b));//prints local coordinate bounds: (x=0, y=0, w=100, h=100)

HTH, George

like image 3
George Profenza Avatar answered Nov 18 '22 12:11

George Profenza