views:

372

answers:

3

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?

+3  A: 

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

George Profenza
Keep in mind this will give you the width/height of the object's contents, so not only rotation but also scaling, skewing, etc. will not be taken into account.
fenomas
Scaling etc. does need to be taken into account. Still, I will look into this asap and inform you with the results.
Tom
+3  A: 

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)

Chip Uni
yum! yum! I'll save this for later :)
George Profenza
@George -- You're welcome!
Chip Uni
I haven't tested, but I'm pretty sure this will give slightly inaccurate results in Flash, because when you get the rotated w/h of the object, those values are measurements of the object's rendered state, which are stored at an accuracy of 1/20 of a pixel. If accuracy is important, use George's answer or the code you post in the question (which is probably what I'd do).
fenomas
Fenomas, are you saying that you actually recommend the method I am using at this moment? Which is seting rotation to 0, receiving width/height and restoring the rotation? I'll try getBounds later (I did try it before and it gave some strange results, but maybe I've entered an invalid parameter). To Chip Uni, this seems very good. I'm going to try it out later and give you an update on how accurate it is!
Tom
Tom - yes, the code you posted is what I'd do, and is probably the most straightforward approach. The "why" will take a bit of explaining, so I'll write it out as a separate answer.
fenomas
Suprising but interesting to hear, I'm looking forward to your answer! (self note: suggest multi-accept feature to SO for situations where multiple answers complete each other)
Tom
+3  A: 

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!

fenomas
Very good answer as usual Fenomas. I've decided to use the simple method. Thanks!
Tom
Superb answer, Fenomas!
Chip Uni
Glad it helped! I wasn't really sure if the simple approach would be quick or not til I tried it. And be wary of doing this with things like Flex components (which might do arcane things like notice they've been transformed and do something as a result).
fenomas
Very nice answer. I used the exact same simple method of finding the true width and height of an object. Great to know I'm coming up with same ideas as you smart folk.
Daniel Carvalho