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
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?
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.
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));
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?
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
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???
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.
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));
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 :-)
D'oh! Of course. I had kinda started down that line, but then just didn't think it all the way through.
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
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.
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)); };
Don't see what you're looking for? Try a search.
|