🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

towards a faster A*

Started by
22 comments, last by Norman Barrows 8 years, 1 month ago

Well, i'm close to a full working implementation in the game. still need to grow obstacles by the critter's collision radius, and add the code to move from one collision map to the next once you path to an edge node. the code is not fully optimized yet. good enough for Wolfgang to follow Shana around, but may not be fast enough for 500 critters without at least round-robin. i'll post a complete postmortem later. a surprisingly few number of optimizations got things running quite fast. nothing complex like heaps or anything like that. but i did use one or two of the suggestions here.

really, i just wanted to say that ApochPiQ nailed it in the first place, when he made this call:

>> You're not talking about vast data sets here nor are you talking about having extremely complex cost functions. A vanilla A* implementation done correctly is more than fast enough to handle your case.

a big +1 for that.

and you know i don't give away my +1's lightly.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Advertisement

http://www.codeproject.com/Articles/118015/Fast-A-Star-D-Implementation-for-C

This guy here wrote an extremely fast heap priority queue.

Let's try it.

It runs very fast in my program

Looking at the large grid map (on that article) with all those zigzags what besides a computer scientist would think something real has as detailed a set of terrain information to be able to employ such a detailed pathing (Mars Rover where its GAME OVER if the thing gets stuck/rolls over - and even then the info is quite spotty to be so precise)

Anyway, animals have paths they repeat and repeated exploration to find their 'good enough' paths to things they routinely do.

Really the coarse pathfinding around blocking rivers and mountain ridges/swamps done at a fairly coarse scale (for long range movements) is the closest they do for such distances, with close range exactness for immediate (next 5 minutes) movement being where any precision exists. At the large scale in their tiny brains it is more an irregular network of interconnected nodes adjacencies of regions having certain properties (and moreso the resources in them - the goal info)

So really its an organic mapping with very rough costs (more 'can I get through' or not or 'do I want to even attempt it') And then relying on fairly short range movements stepwise precision. Solving a maze when you cant even really see the maze doesnt need to be attempted.

For the 'out of sight' beasty AI nature of the game being described it can be very roughly done.

---

SO with fairly short paths still needing efficient pathing (particularly at close range and likely in a dynamic environment requiring frequent repathings over short intervals) The HeapQ i used for the open list used pointer math because of the tree relations between the nodes where every 'parent' or left/right' sibling was a fixed mathematical offset within the data structure being used (so when you add new cadidates at the top and displace downward and pull the top and promote upwards it was very fast processing)

---

I do also remember at that time thinking that for multitudes of pathing objects using similar paths between resources, that developing common routes by some monte carlo samplings between resouce areas would allow reuse of a much simpler individual system of paths. These already determined withing the local area via A* to be leading to a particular resource (like a waterhole) that once you reached a node on that precanned route it was obvious it was part of an open path to that resource (could be followed with MUCH simpler logic - drasticly cutting down on the pathfinding processing).

--------------------------------------------[size="1"]Ratings are Opinion, not Fact

postmortem:

the open and closed lists are static arrays of structs allocated on the stack (IE local variables).

the number of open and closed nodes in each list is tracked.

init_astar_lists is simply setting num_open_nodes and num_closed_nodes to zero.

the lists use insert at end, and swap and dec count to remove.

the open list stores both x,z, and an id=z*collsion_map_size+x

the main function is:

init_astar_lists

while !quit

get_lowest_open &id

move_to_closed id

expand_neighbors id

.

extract_path

get_lowest_open does a simple for loop search of the open list array for the index with lowest f

in expand_neighbors i unrolled the nested for loops into eight explicit calls to expand_neighbor.

in expand_neighbor i combined the atomic operations such as add, remove, membership check, etc.

i left in the check for neighbor off edge of collision map, as opposed to using a border of closed nodes. it seemed to me that a border of closed nodes would increase the size of the closed list and therefore slow down membership tests. but this is only due to my implementation method. for some other method with faster membership tests, a border of closed nodes may be faster.

so expand_neighbor ended up looking like this:

if off_edge return

if in_closed_and_not_removed return

if in_open_and_not_removed return

add_to_open
in_closed_and_not_removed does the membership test. if the test succeeds and the node should be removed, it does the remove. it then returns a result, saying what it did.
in_open_and_not_removed works in a similar manner.
ints are used for f, g, and h, and h is just BBox distance to goal. i tried floats and sqrt(2) for diagonal cost and true 2d distance, its just slower.
the parents are stored in a 2d array the same size and scale as the collision map. and there's no need to initialize it.
extract_path pulls the path from the 2d array and stores it in a 1D path array, which is then copied to the entity's path array. it could be modified to extract directly to an entity's path.
the collision map system was modified to handle collision maps at arbitrary scales. collision maps are now generated at different scales, based on the diameter of the critter in question. so critters are always one square in size. obstacle radii are grown by 1 to account for critter radius when going around corners.
Astar uses the collision maps for passable/impassable node checks.
before astar actually runs, it does a number of checks:
1. if the start is in impassable terrain, it finds the closest open node and does LOS movement to there. this check may be unnecessary.
2. if the goal is in impassable, it finds the closest open node to the goal and does Astar movement to there. this can occur if the goal is a critter who seems to be in impassable terrain due to the collision map scale vs their size. IE a human of diameter 2 can move to what appears to be inside a rock on a collision map at a scale of 6.
3. it then gets the goal for the current collision map, based on the ultimate goal. this is basically a raycast to the edge, followed by an outwardly expanding edge search for open nodes on both this map's edge, and the adjacent map's edge. if the ultimate goal is in the current collision map, it simply returns the ultimate goal as the current goal.
4. if it can not find an open edge node for the current goal, it tries LOS. this case will only occur if the entire edge of a collision map is impassable. IE its ocean or impassable mountains. in such cases you're more or less screwed anyway. i mean if your target takes off on a raft across the ocean, there's not much you can do about it. especially if you're an animal that can't build a raft and give chase.
5. if the start == the current goal, either its at the ultimate goal or at the edge of a collision map, either way LOS is used to get the rest of the way, or to the next collision map.
if none of the above conditions is true, it runs astar out to a range of 50 feet. this is far enough to get the critter moving in the correct direction around large obstacles, even if it doesn't path all the way past them.
when following the path, if they are more than one node distant from node zero, they goto node zero, else they goto node 1.
optimization of real-time collision map generation at arbitrary scales may be called for.
further optimization of astar itself may be called for with a large number of active critters at once.
i'm currently running A* every update, which is probably unnecessary. it should only have to be run when the goal changes, or when they exhaust the nodes in their current path, or when they deviate drastically from the path for some reason.
notes:
1. don't forget that extracted paths are in reverse order.
2. collision maps at arbitrary scales meant i was dealing with three coordinate systems: world, collision map, and scaled collision map. this led to a bit of confusion as different parts used different coordinate systems. in the end i converted everything from world to scaled collision map coordinates, did all the astar stuff, then converted the results back to world coordinates.
3. i was quite surprised how just a few things like insert-at-end, swap and dec, unrolling the loops in expand neighbors, and especially combining the atomic calls in expand neighbor really made a big difference in performance. all my times are zero milliseconds now. i'm down to having to measure astar execution time in clock ticks.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

postmortem update:

i switched collision checks back to using high resolution collision maps (1 square is 1 foot across). Astar continues to use collision maps where width of a square = critter diameter.

i suspect a map with a minimum resolution of "width of one square = critter radius" is required for "pixel perfect" BBox collisions.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

This topic is closed to new replies.

Advertisement