Groups | Blog | Home
all groups > flash actionscript > july 2006 >

flash actionscript : Is there an easy way to find the area of a shape?


harrda
7/15/2006 4:32:48 PM
I'm drawing a spyder chart using scores in an XML file. I use the drawing
methods to connect a bunch of dots along some spokes. Once the shape is drawn,
I'd like to find the area. I could use trig and get the area of each
triangular wedge of the shape, but I'd like to steer clear of that if possible.
Is there a simpler way to do this?

thanks in advance
Rothrock
7/15/2006 5:54:28 PM
You are so funny. "Is there a simpler way to do this?" Not likely. All you have
to do is integrate over the space ? not easier.

If your area is made up of triangles it is easy to figure the area of each
triangle using Hero/Heron formula.
http://en.wikipedia.org/wiki/Heron%27s_formula

Just make a nice little function that takes three points and figures the area.
I don't know of any easier way to do it. Anybody else? kglad?
Wolf van Ween
7/15/2006 8:25:24 PM
If it were a movieClip you could use the Monte Carlo method:
Generate a number of random coordinates (x,y) inside your stage boundaries and
hit-test them with the movieClip. The percentage of positive hits times the
stage size is the approximate area.
The more coordinates you generate the better the approximation.
Rothrock
7/15/2006 8:56:24 PM
Rothrock
7/15/2006 9:22:37 PM
So I thought about it a bit. Instead of doing a monte carlo against the whole
stage, just do it against the bounding box of the movieclip. That will require
fewer iterations to get a good approximation. Here is what I came up with.

function monteArea(clip) {
var bounds = clip.getBounds(_root);
var totalArea = (bounds.xMax-bounds.xMin)*(bounds.yMax-bounds.yMin);
var tries=Math.floor(totalArea/5);
var count = 0;
for (var i = 0; i<tries; i++) {
var x = Math.random()*(bounds.xMax-bounds.xMin)+bounds.xMin;
var y = Math.random()*(bounds.yMax-bounds.yMin)+bounds.yMin;
if (clip.hitTest(x, y, true)) {
count++;
}
}
var area = totalArea*(count/tries);
return area;
}
trace("Area is "+monteArea(myClip));
Rothrock
7/15/2006 10:10:02 PM
hmmmm not quite working. I tested it with a square movieclip. So every random
x,y should return a positive for the hitTest. But I'm getting falses for some
that hit right on the edges of the clip.

It isn't a problem with the random, I don't think. I put the upper left corner
of the square at 100,100. I then tested the hits:

trace(clip2.hitTest(100, 100, true));<--returns false
trace(clip2.hitTest(100, 101, true));<--returns false
trace(clip2.hitTest(101, 100, true));<--returns true
trace(clip2.hitTest(101, 101, true));<--returns true

To my mind they should all return true ? or at the very least the first three
should give the same results. Very strange that it seems to be the x that makes
it return the wrong answer on the low side. And I think it is y on the other
side.

Any ideas Wolf?
blemmo
7/16/2006 11:15:34 AM
The trig way shouldn't be too hard, and is probably the best here. However,
reading about the Monte Carlo method, I guess you could also count the colored
pixels to get the area. It would need Flash 8, but should be quite easy:
--
import flash.display.BitmapData;

var area:Number = 0;
var bmp:BitmapData = new BitmapData(mc._width,mc._height);
bmp.draw(mc);
for(var i=0;i<bmp.width;i++){
for(var j=0;j<bmp.height;j++){
if(bmp.getPixel(i,j) == 0xFF0000){
area++;
}
}
}
delete bmp;
trace("area = "+area);
--
It assumes the chart is in an MC called 'mc' and drawn in red color. I don't
know how accurate this method is; it seems to be not 100% exact. I tried for a
rectangle, it gave me 12535, but the calculated area was 12687.39, so it's more
than 150 pixels difference (in this case 1.2 %). Maybe that's enough for you.
Otherwise, as already mentioned, the trig way is the most exact way.

cheers,
blemmo
Wolf van Ween
7/16/2006 1:52:27 PM
Guys, this gets really interesting now. I've just opened this forum and am
going to test this right away.

Rothrock, of course, the bounding box gives a much faster approximation. I'll
check whether the problem is with the random numbers generated or the way the
hit test function works.

blemmo, great idea. If it's monochrome, that must be the fastest way.
I don't know how well actionScript is optimized -- are the loops translated
into machine language loops and is the getPixel function call translated into
inline memory access? But anyway, even with a very large bitmap that must be a
lot less to do for the processor than let's say 100 hit tests.

As for the error, do you have full pixel coordinates, and do you have
anti-aliasing off? I could imagine that the "missing" red pixels are just not
fully red???

blemmo
7/16/2006 2:38:09 PM
Hm, don't know if you can avoid anti-aliasing for MCs. The error seems to be
related to the stroke around a shape, the position of the shape inside the MC
and the dimensions of the shape. These are the results for a 100x100 square:
- at 0,0 with a 1 pixel stroke:
area = 10200
calc = 10201
- at 0,0 without a stroke:
area = 10000
calc = 10000
- at 10,10 without a stroke:
area = 8100
calc = 10000
- at 0,0 without stroke and dimension of 100.5x100.5:
area = 10000
calc = 10100.25
(in this case, the sizes report as
sqr: width=100.5 height=100.5
bmp: width=100 height=100
guess a bitmap can't have half pixels)

It seems that the dimensions for the MC, where the square is positioned at
10,10, are 100x100 as well (not 110x110, as I would guess), so the bitmap is
100x100 as well, but it draws the MC beginning at its 0,0 point, so there
should be 10 pix missing in width and height of the shape.

So when there is no stroke and the shape is positioned at 0,0 inside its MC,
it seems to be quite accurate. That is, for a rectangle, don't know if it's the
same for other shapes.
kglad
7/16/2006 2:40:28 PM
because for-loops and hitTest() execute so quickly there's no reason to use a
monte carlo method to compute the area of a stage object. in fact, using
Math.random() and 1/4 of the for-loops is more time-consuming than calculating
the area exactly:



function calculateArea(clip) {
var bounds = clip.getBounds(_root);
var area = 0;
for (var x = bounds.xMin; x<=bounds.xMax; x++) {
for (var y = bounds.yMin; y<=bounds.yMax; y++) {
if (clip.hitTest(x, y, true)) {
area++;
}
}
}
return area;
}
trace("Area is "+calculateArea(myClip));
Wolf van Ween
7/16/2006 3:30:27 PM
Actually the Monte Carlo method does not require real random numbers. It's good
enough if they are "random enough" for the problem, i.e. not creating a
systematic error. In the case of the spider diagram for instance, you could
just test every 10th point systematically (basically a mix between Rothrock's
and kglad's function). Good enough for most uses and of course even faster than
checking every point.

By the way, using every 4th or 5th point with random numbers is indeed much
too much. I would have said use 5%, max 10%.

kglad, do you happen to know how hitTest works internally?
Does it use some sort of bitmap lookup?
But your solution is the best, of course. Logical, simple and clean.

Rothrock, I've played round with your code, and the experience is equal to
blemmo, the problem might stem from unclear rounding situation with stroke and
fill. This might change with Flash 9 which finally has an integer data type.

A circle of diameter 200 (real area: (200/2)^2 * pi = 31415.9 ... gives
with no stroke:
using kglad's "exact" method: 31468
using 10% "regular-random": 31320
using 5% random Monte Carlo: 31080-31780 (10 attempts)

with a hairline stroke or with a 1 point stroke, but still in the same 200x200
bounding box, so (theoretically) it should all be the same area:
using kglad's "exact" method: 31805
using 10% "regular-random": 31790
using 5% random Monte Carlo: 31500-32500 (10 attempts)

with a 3 point stroke:
using kglad's "exact" method: 32467
using 10% "regular-random": 32600
using 5% random Monte Carlo: 32046-32587 (10 attempts)

So, harrda, pick your favourite :-)
Rothrock
7/16/2006 3:37:13 PM
D'oh! Of course. I had kinda started down that line, but then just didn't think it all the way through.

harrda
7/17/2006 1:54:03 PM
Wow. I mean, really, these are some great answers. I was looking for
something just slightly more complicated than "use the mc.getArea() method",
but since that doesn't seem to exist yet, thanks very much for all the
suggestions.

I'm choosing kglad's method because I need an exact measure for comparing the
areas of different spyder charts.

thanks again all
kglad
7/17/2006 2:34:57 PM
you're welcome.

you should be aware that the answer given by all these hitTest() methods may
not agree with a mathmatical formula because of the way flash handles shapes.
the answers should be close, but not really exact.
Rothrock
7/17/2006 5:10:51 PM
If they are triangles and you know the points, definitely use Hero's formula.
Compare the answers from the following code.

function calculateArea(clip) {
var bounds = clip.getBounds(_root);
trace(bounds.xMin);
var area = 0;
for (var x = bounds.xMin; x<=bounds.xMax; x++) {
for (var y = bounds.yMin; y<=bounds.yMax; y++) {
if (clip.hitTest(x, y, true)) {
area++;
}
}
}
return area;
}
function heroArea(x1, y1, x2, y2, x3, y3) {
var len = new Array();
len[0] = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
len[1] = Math.sqrt((x2-x3)*(x2-x3)+(y2-y3)*(y2-y3));
len[2] = Math.sqrt((x3-x1)*(x3-x1)+(y3-y1)*(y3-y1));
len.sort(Array.DESCENDING);
var area =
..25*Math.sqrt((len[0]+(len[1]+len[2]))*(len[2]-(len[0]-len[1]))*(len[2]+(len[0]-
len[1]))*(len[0]+(len[1]-len[2])));
return area;
}
x1 = 100;
y1 = 100;
x2 = 100;
y2 = 200;
x3 = 200;
y3 = 200;
this.createEmptyMovieClip("tri", 100);
tri.beginFill(0xff0000, 100);
tri.moveTo(x1, y1);
tri.lineTo(x2, y2);
tri.lineTo(x3, y3);
tri.lineTo(x1, y1);
tri.endFill();
tri.onPress = function() {
trace("Area is "+calculateArea(tri));
trace("Hero says: "+heroArea(x1, y1, x2, y2, x3, y3));
};
AddThis Social Bookmark Button