Friday, September 22, 2017

Adding to my list of oddities in computer science.

CS folks are enamored of dividing 2D space in 2D space chunks? It makes all of the math needlessly harder, even if it makes the math-sense and understandability easier. You divide 2D space into two instances of 1D space. The algorithms don't require trees, you can still easily access the needed data in log(n) time, update them as easy as a sorted list can be updated, and with a bit of style you can easily prove points don't need to be checked. It's weird because no algorithms do this, but you can do an amazing amount of very simple math to solve all the traditional problems in computational geometry with simpler algorithms.

• You can build the space in d*n*log(n) time (sorting lists).
• With sorted lists of points you can insert in as easy as inserting into a list.
• You can remove as easily as removing from a list.
• You can find the nearest neighbor to a point in a short algorithm in log(n) time.
• You can find k nearest neighbors to a point in a similar short algorithm in ~k*log(k)*log(n) time.
• You can do basically all the AABB math quite easily too. You can easily find in n time the bounding boxes relevant to a point. And update that point very easily.
• You can find if a point is inside a polygon.
• You can find all points within an axis aligned rectangle in trivial time.

But the really really pressing bits of this is that categorically these algorithms are tiny. Like they don't take much work at all. They are exceptionally short. And the datastructure is what modern memory systems are optimized for so you won't be cache missing all the time. And it's so common it's implemented in like four lines of code. So why the hell are all games doing collision detection of divided up 2D space in 2D chunks or 3D chunks in 3D space? Also, these algorithms obviously scale really easily with the degree of dimensionality. They don't violate theoretical points though. If you do a Nearest Neighbor Search in 3D it's basically identical code with the steps as the 2D code but since we're dealing with 3D, it can easily end up checking most all the points anyway simply because you can't rule out the remaining checks as quickly.

I needed a nearest neighbor search for my android app to find the nearest point to a point and do this many many times in real time. But, when a point is moved it required a lot to fix the pre-processed datastructure since they were organized into trees so the nodes needed to be fiddled with etc. This allowed for nodes to be updated en masse, and the lists to simply be very quickly resorted in a trivial amount of time and perfectly ready for the next tick.

I hit upon this algorithm largely because I had also done considerable work with filling monotone polygons using a scanline, and used a sort of step-raytrace with line segments which is another brilliant algorithm (it's very similar to the good solution to the skyline problem), it's largely just a single list of all the points sorted along one axis. Then you scan through the list, keeping a working list of active line segments. This solves the perpendicular rays and finds all intersections of that perpendicular ray (it's literally the intersections of the active list of line segments) in fast enough time that a little android phone can do the ray traced lines in real time.

To speed up my attract tool (requires many nearest neighbor searches), I previously made an interesting nearest neighbor search with a point quadtree which equally used the same general idea that you could prove the invalidity of other parts of the tree by showing that the distance in just 1 dimension is greater than the shortest distance already found. I thought this was awesome as nobody seemed to have any nearest neighbor algorithms based on that rather rare datastructure.

But, if you could do this with a tree it would be much easier to do this with 2 different 1D sorted lists. Just binary search into the list, and go to each point in the +X, +Y, -X, -Y directions until the distance in just dX is greater than your best candidate so far.  You can also conclude things like if +X and -X are exhausted then there can be no closer point, as you proved all points exhausted (the points found in the other dimensions are going to be duplicates of the ones you ruled out). But, the point is implementing all of that is trivial compared to the other ways. And all the tricks in computational geometry are simply proving you don't have to check those other points, which is can be done with the same trick over and over, can this point further along this axis possibly be closer or included? For the K-nearest neighbor algorithm, you store the candidates in a heap sorted by distance and only compare the worst of them to the new potential candidates until just that one direction along is enough to be worse in which case no more points along that axis can possibly be better.

For Axis Aligned Bounding Boxes (AABB) you can rather simply add the start points and end points of the shapes. Then when you cross a threshold by traveling along the axis (only going to the next relevant point always as that's how the traversal works), you can trivially check if the relevant point is within that bounding box. So you can rather easily linearly iterate into the list and you'll know exactly which bounding boxes that point is within tracking them basically exactly as one would the skyline problem. Then moving that point within the space equally quite quick and just means updating any active elements as you cross thresholds (and if you don't cross any relevant boundaries, changing nothing at all). Equally it solves a number of common problems in collision detections like not updating something that moved in time to see that it struck something. As the object moves it needs to find it's place in the lists, which means you must process each element in order, which means you know if you hit anything, regardless whether you would now be completely on the other side of that object now.

I totally understand that it's easier to think in 3d space in 3d and 2d space in 2d so when you're partitioning space you are wildly more likely to divide the space in ways that does not screw up the dimensionality. It's easy to think of dividing a square into two half-squares but it's a bit weird to divide it into two equally lengthed lines. But the math is easier if you divide it into two lines for obvious reasons, even if it's harder to conceptualize.