Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve PlaceOnWalkableArea #2673

Conversation

ivan-mogilko
Copy link
Contributor

@ivan-mogilko ivan-mogilko commented Jan 30, 2025

Fix #163

This makes Character.PlaceOnWalkableArea use 1-pixel step, in order to not skip thin areas.

Refactored searching algorithm and moved to Pathfinding::FindNearestWalkablePoint(). This function still accepts search "step" parameter, so in theory we could use it if a need arises.
Renamed original function to FindNearestWalkableAreaForCharacter(). Made it clear which restrictions to the scanning area do we make, according to historical AGS rules (don't move character beyond room edges, etc).
Removed the obscure hard-coded restriction which did not let move character to the topmost 14 pixels of the room. I think that may be related to the Sierra-style Statusbar GUI from the default template.

Optimized scanning algorithm for speed (see explanation below).
Would need to make performance tests to see the actual difference in very big rooms.

Optimization:

The original algorithm took whole room (or rather section limited by edges), and scanned it awhole from left-top to right-bottom. When finding a walkable point, it would calculate a distance from the character, save the nearest found point, and continue. In the end, it would scan all the room regardless, even if it may be certain that there cannot be any point in a smaller radius already. This of course is quite suboptimal.

The new algorithm that I'm trying here does a outwards spiral scanning, checking pixels which lie on a rectangle border, where a rectangle is built with certain radius around the starting position.
That is: it first takes 3x3 rectangle and scans pixels on its border. Then it takes 5x5 rectangle and scans its border. Then it takes 7x7 rectangle, and so forth.
In this algorithm I still keep the original act where it would increment along x axis in the outer loop and increment along y axis in the internal loop. This means that pixels located towards top-left have higher priority among walkable pixels with the same distance. Idk if that matters much, but I kept it like that just in case some old game relies on that (call that a paranoia).
In order to not duplicate work, it actually skips the pixels inside rectangle, because these were already processed when scanning previous rectangles. This is done simply by adjusting internal step: when first and last rect line are scanned, we step by 1 pixel, but when any other line is scanned, we step by radius * 2, which results in only two pixels on the opposite rectangle's side checked.
Here's a illustration:
outwardscanning

Whenever a walkable point is found, it calculates a distance to it, checks if that's the smallest found distance, and also reduces the scanning range to that distance, in order to stop early, since we won't be needing any pixel at a larger distance anymore.
NOTE: that it's still necessary to keep testing for some time even if we find a walkable point, because we scan in rectangles and not in circles. Would we scan in circles, we could stop as soon as any walkable pixel is found, but with rectangles we cannot be immediately certain if that pixel is really the closest. For example, a pixel on rectangle's corner is further away from center than a pixel in the middle of a rectangle's edge.

Keep the general scan algorithm for now.

Rewrote find_nearest_walkable_area() into FindNearestWalkableAreaForCharacter().
Make any additional scanning adjustments optional and explicit.
Do not adjust scan limit by 14 pixels from the top (unless it's backwards mode), that's some ancient hack, presumably because of the Sierra-style status bar.
Do thorough scanning using step 1 always, but keep an option to pass larger step into Pathfinding::FindNearestWalkablePoint(), in case we shall require that.
@ivan-mogilko ivan-mogilko added context: game logic context: performance related to improving program speed or lowering system requirements labels Jan 30, 2025
@ivan-mogilko
Copy link
Contributor Author

ivan-mogilko commented Jan 30, 2025

Hmm, the preliminary performance test results are following.
Used a 4000x4000 room (I tried using a larger one starting with 16k x 16k, but Editor kept crashing with out of mem and other errors...) with randomly scattered walkable areas. Scripted controls to let reposition character by clicking on screen, and run PlaceOnWalkableArea by a hotkey.

Time to find a nearest walkable area:
Old algorithm with 5-pixel step: 50-70 ms on average.
Old algorithm with 1-pixel step: 900-930 ms on average.
New algorithm with 1-pixel step: 0-2 ms if walkable area is within the screen range (320x200). It will obviously be higher the further character is from the nearest area.

So, I suppose that's a success.

@ericoporto
Copy link
Member

That is more than 10x performance improvement, so on that alone it looks really good!

@ivan-mogilko ivan-mogilko merged commit a1e77c3 into adventuregamestudio:master Feb 1, 2025
21 checks passed
@ivan-mogilko ivan-mogilko deleted the 362--improve-placeonwalkable branch February 1, 2025 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
context: game logic context: performance related to improving program speed or lowering system requirements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Character.PlaceOnWalkableArea does not work at all times
2 participants