11

I'm coding a game, and I'd like to be able to find the center of mass of an arbitrary shape on a black and white bitmap such as this:

 012345678
0.XX......
1..XXX....
2...XXX...
3..XXXXXXX
4...XXX...

All "cells" have the same weight. Diagonally adjacent cells are not considered to be connected, and the shape will always be a single one since it's already split by another function before this.

It's only going to be used for reasonably low resolution (maybe 50x50 at most) images and it doesn't need to be super accurate, speed is preferable.

I get a feeling there's a proper way to do this, but I don't really know what to google for.

I'm coding this in Actionscript 3, but examples in any language are appreciated, moreso if they're made to be understood by humans.

EDIT: Feel free to assume that the data is stored in whatever data structure you think is most convenient for your example. I'm using bitmaps, but two-dimensional arrays or even a single array is just fine too!

EDIT: This is the code I ended up using, it can most likely be done faster, but I find this to be very readable:

// _bmp is a private BitmapData instance
public function getCenterOfMass():Point {
    var avg     :Point  = new Point(0, 0);
    var points  :uint   = 0;

    for (var ix:uint = 0; ix < _bmp.width; ix++) {
        for (var iy:uint = 0; iy < _bmp.height; iy++) {
            if (_bmp.getPixel(ix, iy) == ACTIVE_COLOR) {
                avg.x += ix;
                avg.y += iy;
                points++;
            }
        }
    }

    avg.x /= points;
    avg.y /= points;

    return avg;
}
grapefrukt
  • 27,016
  • 6
  • 49
  • 73
  • How arbitrary? Will there tend to be largish areas/clumps of black or white? Or will it be more like random noise? – Artelius Jan 03 '09 at 00:27
  • Doesn't matter, the center of mass calculation will be the same (as per Yuval's answer). What does matter is whether the OP needs the "center of mass" to be on an 'X'. If the X's are all around the boundary, the center of mass will be in the (empty) center. – Peter K. Jan 03 '09 at 01:10
  • They will be coherent clumps, and it's okay if the center is on an empty cell. – grapefrukt Jan 03 '09 at 01:15

3 Answers3

20

How about this algorithm (psuedo-code) based on a boolean matrix like in your example :

xSum = 0
ySum = 0
points = 0

for point in matrix
    if point is marked
        xSum += pointX
        ySum += pointY
        points++

return (xSum/points, ySum/points)

Nothing too complicated, calculate where X is the most present, same for Y, divide by number of points you counted, and you got the center of mass. You can further complicate this by giving certain points different weight in the averaging, but this should be your main direction.


This question got me thinking about an expansion to this question which I could not find a good answer to. I posted the question here: Finding clusters of mass in a matrix/bitmap

Community
  • 1
  • 1
Yuval Adam
  • 161,610
  • 92
  • 305
  • 395
  • If the bitmaps aren't created at runtime or if you use them often, store point count for rows, this will speed up the whole thing massively, but you shouldn't need this. – schnaader Jan 03 '09 at 00:54
  • You need to check edge conditions: e.g. if there are no marked points, that error needs to be detected; e.g. if there is one marked point at (0,0) how do you tell the difference between that and there being no marked points. – Peter K. Jan 03 '09 at 01:08
  • @schnaader - I only check the shapes on creation so that's okay – grapefrukt Jan 03 '09 at 01:28
  • @Yuval A - Great! I knew it was easier than I thought! – grapefrukt Jan 03 '09 at 01:28
2

You could count the number of adjoining cells, then center on the cells with the highest neighbor count.

 012345678
0 XX      |
1  XXX    |
2   XXX   |
3  XXXXXXX|
4   XXX   |

 012345678
0 23      |
1  454    |
2   775   |
3  3686421|
4   454   |

In this example you could use the only cell with an 8, as the center point.

If you came up with multiple cells with the same number of neighbors, you would just run the counting routine again, but this time just with the high number cells.

For example, lets pretend that the cells that had 6, 7, and 8, instead all had eight neighbors.

 012345678
0 23      |
1  454    |
2   885   |
3  3888421|
4   454   |

 012345678
0 --      |
1  ---    |
2   XX-   |
3  -XXX---|
4   ---   |

 012345678
0 --      |
1  ---    |
2   34-   |
3  -342---|
4   ---   |

 012345678
0 --      |
1  ---    |
2   -X-   |
3  --X----|
4   ---   |

In the case of a simple tie, I would go with the one that was nearer the center. In this case it would be the upper one of the two.

Note: I'm assuming your not using this for an accurate physics simulation.

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
  • I'm not sure I understand what this variant would do better than Yuval's suggestion, or is it just a different way to reach the same goal? – grapefrukt Jan 03 '09 at 14:18
  • I thought this might be useful in some circumstances, not necessarily the one proposed by this particular question. – Brad Gilbert Jan 04 '09 at 05:47
1

Depending on the nature of your actionscript interpreter and the pre-processing done on the shapes, you may see a speed improvement (over the direct approach pointed out by Yuval) by initially creating a second copy of the bitmap/array mirrored diagonally, then using row or string manipulation functions to sum the points on each row and column in single steps. This would be O(2mn) instead of O(nn) [see below], but with more overhead.

xSum = 0
ySum = 0
points = 0

for row in matrix
  ySum += rowX * countpoints(row)
  points += countpoints(row)
for row in mirroredmatrix
  xSum += rowX * countpoints(row)

return (xSum/points, ySum/points)

Where countpoints() counts the on-points in a row. This relies on countpoints (which has O(n) runtime) having a lower constant multiplier than the naive approach's runtime (hence above, 'm' being the runtime of countpoints and 'n' being the time for the interpreter to loop through a row). The nature of countpoints() depends on your storage method, which may involve counting characters in a string, or bits in a bitfield.

Sparr
  • 7,489
  • 31
  • 48