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

Pathfinding #211

Open
wants to merge 11 commits into
base: master
from

Conversation

4 participants
@Supermanu
Contributor

Supermanu commented Dec 31, 2017

This PR shows the work in progress related to pathfinding and must not be included as is. I guess it still needs some clean up and refactoring but most of the work has been done. If you have any suggestions, comments or fixes, let me know!

What currently works:

  • A generic A* implementation that can tuned in each game. The output is a path of faces (and not a line to follow). It can be found in src/engines/aurora/astar.*
  • A Pathfinding class that handles the walkmesh where an A* instance can run and provides an interface to find paths.
  • The Pathfinding class also provides a smoothing algorithm that transforms a path of faces into a line that a player/creature can follow. It can also take the creature's size into account though it won't always give a nice path (it may go through unwalkable surface). Although this is only a temporary feature as explained below.
  • A KotOR implementation, that loads the walkmesh from a wok file.
  • A NWN implementation, that loads the walkmesh from a wok file. This implementation was trickier and some bugs may still come up. The difficulty was to connect the walkmesh of the different tiles in order to have one big walkmesh at the end. As I found no information related to tile connections, the current implementation tries to look if there is an adjacent walkable surface in the near neighborhood and makes some adjustment if needed.
  • A local walkmesh for dynamic content (only placeable up-to-now).
  • A graphical debug output that shows the result.
  • A showcase of what currently works in NWN in aa5507e. This is only for test and will be removed/refactored later.

TODO

Up to now, only the static content (everything that's in the wok file) is taken into account. All the dynamic contents as well as some static contents (like some crates) are not considered. The walkmesh for NWN and KotOR is made of triangles with different sizes and shapes which make it hard and not performance wise to modify on-the-fly. After some readings, I think that the best solution would be to create on-the-fly and frequently a small and local walkmesh made of squares (a grid) around the player/creature. It will use the output of the smoothing algorithm to know where it will have to go. It will directly include the creature's size into the walkmesh so it won't be necessary to compute it apart from A*.

That leads us to:

  • Build a grid from the static walkmesh,
  • Add dynamic content to it,
  • Take creature's size into account,
  • Make it fast,
  • Add tests for AABB,
  • Adapt to current walkmesh.
  • Clean up.
@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jan 3, 2018

Very nice work already, thanks! Looking forward to see this progress. :)

About the build failures on our CIs, might be because your code uses features from a newer Boost version? Can you maybe try if there's a way you can downgrade your code to use features also available on the older Boost versions (1.54 on Travis CI, 1.59 on AppVeyor and xoreos' build docs say >= 1.53)?

If that isn't possible or feasible, we need to mandate a newer Boost version in our build docs. I'd prefer not to, but if it has to be, it has to be.

Updating Travis CI will be annoying, especially with the C++11 ABI change, so I will probably also upgrade the compilers; so the result might look like the Travis config in the Phaethon QT branch. Updating AppVeyor... I hope berenm has time to look at that, then. Looking at the AppVeyor config, I don't see where the Boost stuff is even coming from...

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jan 4, 2018

In deed, the code uses features from a newer Boost version though there are quite basic. As I'm not very satisfied with Boost.Geometry, I'm looking at CGAL right now which has everything that we need and should also work with "old" versions.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jan 4, 2018

Keep in mind that the version of CGAL in Ubuntu Trusty (which is what Travis CI runs) is 4.2. So you probably shouldn't use any of the newer features there either (whatever they may be), if that's possible. Since it's a C++ library, like Boost, it's probably also affected by the C++ ABI change, so pulling in a newer version from a more recent Ubuntu release could be problematic.

Unless CGAL is small enough that we might consider pulling it into xoreos, but from a cursory look at their Github page, it doesn't seem to be that way.

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jan 15, 2018

@mirv-sillyfish I'm looking right now at how to rasterize efficiently triangle mesh so I can build a grid and use it for pathfinding. I was wondering if there is a simple way to do that with the GPU i.e. send some triangles and get a boolean matrix of where triangles are in the XY plane? Otherwise, I'll stick with a software rasterizer.

@mirv-sillyfish

This comment has been minimized.

Contributor

mirv-sillyfish commented Jan 15, 2018

The short version is "yes, it can be done". It's basically a texture read-back (use whatever terminology you wish - pbuffer, texture, whatever). Clear a framebuffer to a "blank" colour, render some appropriate geometry (simpler the better), and read it into ram. The resulting buffer is basically the averaged "height" of geometry (if any) in each cell.
Best approach might be to do this once per frame, but ping-pong between a couple framebuffers to prevent the GPU command pipeline stalling. Means the "current" matrix might be a frame out of date, but I suspect that won't really be an issue.
If logic and graphics are not coupled (as they shouldn't be), then there will need to be some kind of mechanism to stop one thread gleefully overwriting data currently in use, but again that's solvable.
Otherwise, yeah, definitely doable. Can encode a lot more data than boolean, and can also trim the clipping planes so that only geometry between a certain "height" band contributes.

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jan 16, 2018

Awesome! In deed, it doesn't need to be done instantly though it should be fast enough. I expect that it will send maximum 50-100 triangles and return a 128x128 matrix at the maximum; it will depend on how fast a path can be find inside such a grid and how good we want the path to be. I hope it doesn't require recent OpenGL though.

As I'm not familiar with OpenGL, I'll first implement a naive software rasterizer so I can check items on the TODO list. But then, I might need some help :P.

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jul 4, 2018

So I've made some progress. I've added the local grid walkmesh and it can take the creature's size and placeables into account though it misses doors and creatures. The built grid area is 65x65 with cell width about 0.1 which seems enough to me. The thing is, with larger grid the A* algorithm becomes quickly slower. To give an idea, finding the path, building the local walkmesh and finding the path on it takes, most of the time, less than 10 ms on my machine (AMD FX-8320). I feel that's a good start, and enough for this PR, but I think it could easily be improved by a factor 2 with the Jump point search optimization.
Finally, I still need to do some clean up, tests and adapt the walkmesh handling with the current code.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 4, 2018

Very nice, great seeing this progress :)

@DrMcCoy DrMcCoy added this to In progress in TODO: Engines: NWN Jul 9, 2018

@DrMcCoy DrMcCoy added this to In progress in TODO: Engines: KotOR/KotOR2 Jul 9, 2018

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jul 26, 2018

I finally adapted my work with master. I replaced @seedhartha's walkmesh implementation with a Pathfinding and LocalPathfinding class that have an overview of what's going on. They also take advantage of the AABB trees for fast search and localization. Not completely though, as static objects (or creatures) are not yet linked to AABBs but I'm not sure it's worth the investment.

The current state is still not enough to deal with a "working" real time pathfinding. We need, something like a manager, to handle the local walkmesh updates, every 200ms for instance, and how to react with other creatures; I know that in NWN, a PC moves a familiar if it crosses its path but tries to avoid NPCs and I have no idea what happens if a familiar is controlled by a player and crosses the path of another PC.

The last commit is only for testing and slows down the module loading because of placeable walkmesh loadings: walkmesh files are loaded multiple times, for each placeables and not for each models. I guess this could be easily handled.

If you have any suggestions, comments or fixes on the code, let me know!

@Supermanu Supermanu changed the title from [WIP] Pathfinding to Pathfinding Jul 26, 2018

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 26, 2018

Neat! :)

However, AddressSanitizer complains when going into the first Prelude area of NWN :P :
https://gist.github.com/DrMcCoy/4ea9e9340a485ef2dcd8ec589ed56610

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jul 27, 2018

Obviously :P. I fixed the heap-buffer-overflow and other memory leaks, AddressSanitizer doesn't complain anymore.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 27, 2018

Nice. Looks quite good for the most part, IMHO. :)

I think you forgot to reset some GL state after drawing the walkmesh. See how the ingame GUI is drawn in NWN, and how it changes when highlighting an object:
20180727t101001
20180727t101006

Can you make the walkmesh drawing switchable using the console command "showwalkmesh", like in KotOR (defaulting to off)? That would be good to have as a permanent option.

I don't understand what the slowing down is supposed to do, and I'm not even sure I can see an effect of that in NWN, or is that situational? In either case, that's not something I'd like to merge, obviously, apart from putting that behind another debug console option. :P

namespace Common {
AABBNode::AABBNode(float min[], float max[], int32 property) : BoundingBox(), _property(property) {

This comment has been minimized.

@DrMcCoy

DrMcCoy Jul 27, 2018

Member

Is there a reason you're using float min[] instead of float min[3] (and max)? float min[] would allow for passing arbitrary-length array and even plain pointers, where for example min[2] is not valid memory.

(Same comment applies to Pathfinding::getMinMaxFromFace() in src/engines/nwn/pathfinding.h as well, and the KotOR/KotOR2 walkmesh code)

@farmboy0

This comment has been minimized.

Contributor

farmboy0 commented Jul 27, 2018

AFAIU the slow down happens due to by default loading and drawing the pathfinding info. But hiding this behind a debug console command is obviously a good idea.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 27, 2018

Ah, I think I misread, then. I thought the slowdown was intentional, to show the walkmesh loading, and not a(n unwanted) side-effect. I probably shouldn't write comments without enough coffee in me :P

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jul 27, 2018

Or me not clear enough :P. I thought, at first, that loading the wok files (plain text) for tiles and placeables were taking quite a while since some were read several times from the same wok file: same tile/object but in different positions/orientations. But now, I'm not able to reproduce the slowdown (my guess is that the heat had effects on my head and/or computer).

@DrMcCoy Thanks for the review! I fixed the glitch and added a showwalkmesh command like in KotOR.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 28, 2018

While playing around with the pathfinding in NWN, I found another crash:

https://gist.github.com/DrMcCoy/ee4dc29f3370b0fa5fea0ca9e5c0aaba

That happened when I tried to reproduce this strange pathing behaviour here:
20180728t081951

The path that is calculated takes a loop near the start at the upper part of the image, then through the door and following the tri edge, then back to the wall. Near the door on the lower part of the image it again follows the tri edge to the opposite side of the wall, and then walks through the whole width of the door and to the pillar.

Close-ups of the start and near-end points of a similar path (i.e. that behaviour reproduced):
20180728t082706
20180728t082718

So it seems to be related to the sitation of traversing a mid-sized room into a corridor, through that corridor and into a larger room. Or possibly just the situation of that corridor.

Dunno if the crash is related or a separate issue, though.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 28, 2018

(That's the second area of the NWN OC Prelude, the Neverwinter Academy Training Halls. The starting room is the divine magic training room, near the exit to the assembly chamber where you meet Aribeth for the first time after finishing your training. The end room is the combat training room with the shouty instructor. The corridor is the one joining these two rooms, with the doors to the arcane magic and rogue rooms along the way.)

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jul 28, 2018

In deed, there is something wrong with the path generated by the funnel algorithm (smoothing the path). So maybe it is related to the crash from what I see in the gist. I haven't been able to reproduce the crash yet but I'll first fix the generated path.
Thanks!
EDIT: Also, I forgot (again) to reset a GL color, that's why the UI is a little bit pink :).

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 28, 2018

I managed to catch another (different? related?) crash, kinda reproducible by creating this same weirded-up path: https://gist.github.com/DrMcCoy/b73393adc7c33e05e379af02354010c5

For some reason: c == 64, tunnelLeftRight.size() == 64, tunnel.size() == 65.
So tunnelLeftRight[c] is bad. The code seems to assume that tunnelLeftRight.size() == tunnel.size(), which apparently isn't always true.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 28, 2018

(Probably good to add an assert() with that assumptions there, too, even after you've fixed where this gets broken, I guess)

Supermanu added some commits Jul 24, 2018

ENGINES: Add generic pathfinding tools
This commit adds a generic A* algorithm as well as helper functions.
It also removes the current walkmesh implementation.
GRAPHICS: Fix unproject() y coordinate in GraphicsManager
The y component in unproject() needed to be translated from the screen
coordinate to the OpenGL world screen coordinates. It was already done
in the getWorldObjectAt() function that uses unproject() internally but
prevented its use outside of getWorldObjectAt().

Supermanu added some commits Jul 4, 2018

KOTOR: Implement pathfinding
It now uses the pathfinding tools which takes profit of AABB and
creature's size (currently set to a constant).
KOTOR2: Implement pathfinding
It now uses the pathfinding tools which takes profit of AABB and
creature's size (currently set to a constant).
@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Jul 29, 2018

I eventually fixed the wrong generated path. It was caused by the way tiles are connected because, in NWN, they need to be connected by hand with some approximations: two vertices from different tiles are considered the same vertex if they are close enough (proximity test). It wasn't considered in the generic path generation but it is now. It led to different sizes between tunnel and tunnelLeftRight, so it is likely that it was causing the crash though I didn't managed to reproduce the crash.

Unfortunately, I still expect weird behaviour like this since the proximity test is empirical and should be tuned with testing.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 29, 2018

Yeah, can't reproduce the crash there now.

...However... (sorry :P)

There's another one, now triggering the assert() you added. Still in the Training Halls.
I marked the start and end points with green dots here: 20180729t141447

The start is the one in the upper middle, the end point in the lower left.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Jul 29, 2018

In fact, everything crossing the entry/exit to that little alcove in the rogue room (the start point), assert()s.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Nov 4, 2018

Just following up on this, what is the state of the PR right now? :)

@Supermanu

This comment has been minimized.

Contributor

Supermanu commented Nov 9, 2018

I'm really sorry, I've been really busy the last two months.
Short answer, it's still not ready for nwn though it's better.
I rewrote some parts and it's now really better about tiles connections (I'm not sure I pushed the code). But I need to tackle one last tricky specific case. I came up with a solution that should work but hadn't correctly implemented.
Regarding kotor1/2, it has features parity with the current state but I guess it should need more testing just to be sure :P.

@DrMcCoy

This comment has been minimized.

Member

DrMcCoy commented Nov 10, 2018

Okay, nice to hear. Thanks for your continuing work :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment