- Introduction
- Getting Started
- Getting Started with DOTS
- Technical Information
- Roadmap
- Bibliography
- Acknowledgements
DotsNav is a fully dynamic and robust planar navmesh Unity package built on DOTS. It is fast enough to add and remove many obstacles each frame, supports agents of any size, and can be used through monobehaviours without prior knowledge of DOTS.
DotsNav is in an early stage of development. It passes demanding robustness tests as can be seen in the video and demo, but has not been used beyond developing the demo. DotsNav is known to run as Windows standalone, UWP app and on Android. Apple devices have not been tested.
To support further development consider becoming a sponsor and get access to the beta and development repositories which are a few months ahead of this repository, access tot the private discord channel and votes on the roadmap. The first improvement will be to implement local avoidance by porting rvo2-cs.
Open the package manager and choose Add package from git URL.
Enter the url:
https://github.com/dotsnav/dotsnav.git
Samples can be imported through the package manager.
To create a navmesh attach a DotsNav Navmesh behaviour to a gameobject. The navmesh dimensions will be drawn in the scene view. A top down orthogonal perspective is usually the easiest way to view navmeshes and edit obstacles. Currently only one navmesh is allowed which will be centered around the origin. The value of Expected Verts determines the size of initial allocations.
Add a Convert to Entity component to the Navmesh so it is converted to DOTS. When using monobehaviours to develop a project choose “Convert and Inject”. Obstacles can then be removed from the navmesh by destroying the associated gameobject. The navmesh can be disposed of similarly.
To create an obstacle add a DotsNav Obstacle behaviour to a different gameobject and make sure it is converted.
Add a few vertices and move them around using the position handle. The edit mode colors can be set in the DotsNav tab in Preferences.
Alternatively an obstacle's Vertices array can be populated through script. Obstacle gameobjects can be scaled, rotated around their y axis, and used as prefabs.
To enable pathfinding agents first add a DotsNav Pathfinder behaviour and make sure it is converted. The navmesh gameobject is a good place to add the pathfinder, but this is not required.
Add a DotsNav Agent behaviour to a different gameobject and make sure it is converted.
To draw the navmesh while playing, add a DotsNav Renderer component to a gameobject. If the renderer is attached to the camera it can draw in the game view as well as the scene view.
Path queries can be enqueued using DotsNavAgent.FindPath.
Next navmesh update a path will be calculated.
Each navmesh update the direction required to follow the path is calculated using the agent's position.
Using the default settings, invalidated paths are recalculated automatically.
For multiple agents, path finding is performed in parallel.
Obstacle prefabs are automatically enqueued for insertion when spawned. Alternatively obstacles can be inserted by calling InsertObstacle and providing a list of vertices.
Obstacles can be removed by destroying a previously spawned gameobject, or by calling RemoveObstacle providing an id returned by InsertObstacle.
The easiest way to get started with DOTS is to do all of the above, but instead use “Convert and Destroy”.
This creates appropriate entities and components. Alternatively you can create entities and components manually, which is described below.
Navmeshes are created by adding a NavmeshComponent to an entity. This allows you to supply the parameters used when the navmesh is created. Destroy the entity to dispose of its resources. There should only be one navmesh at any time.
There are two types of obstacles:
- Dynamic, these obstacles are associated with an entity through which they can be identified and removed
- Static, these obstacles can not be removed or identified beyond being static, but can be inserted in bulk
The following archetypes trigger obstacle insertion:
- Dynamic
- ObstacleComponent, DynamicBuffer<VertexElement>
- ObstacleComponent, VertexBlobComponent
- Static
- DynamicBuffer<VertexElement>, DynamicBuffer<VertexAmountElement>
- ObstacleBlobComponent
Entities with static archetypes are destroyed after insertion. To remove dynamic entities from the navmesh destroy their associated entity.
Add a PathFinderComponent to an entity. Use the constructor so it is initialized properly. Destroy the entity to dispose of its resources. There should be only one PathFinderComponent at any time.
Create an entity with the following archetype:
- AgentComponent
- DynamicBuffer<PathSegmentElement>
- DynamicBuffer<TriangleElement>
- AgentDirectionComponent (optional)
- AgentDrawComponent (optional)
To trigger path queries set AgentComponent.State to Pending.
The Navmesh component provides access to the triangulation through the following methods, allowing for traversal of the triangulation and development of additional algorithms:
- Edge* FindTriangleContainingPoint
- Vertex* FindClosestVertex
DotsNav uses a Local Clearance Triangulation which reduces path finding using an arbitrary agent radius to a single floating point comparison per expanded edge. It uses a quadedge to represent the triangulation, and a bucketed quadtree for point location.
DotsNav's insertion and removal algorithms are guaranteed to succeed and guarantee closed polygons remain closed, no matter how many intersecting obstacles are inserted or removed. In short, intersections use an existing point chosen to converge on the destination in the rare case no valid point can be created due to the density of the navmesh. When points chosen in this way are not connected directly, A* is used to determine which edges need to be constrained.
Due to the nature of the algorithms involved exact geometric predicates are required for a robust implementation. DotsNav relies on adaptive predicates, only when regular floating point calculations do not provide sufficient precision is the costly exact calculation performed. The predicates are available separately.
DotsNav provides locally optimal search. First, a channel of connected triangles with enough clearance is found using A*. The optimal path given this channel is then found using the funnel algorithm. While channels found are often optimal they are not guaranteed to be. An algorithm to find the optimal channel exists, but can easily take 100 times longer to execute and is not currently implemented. As there are valid use cases for globally optimal search, if only to benchmark cost functions, it is included on the roadmap.
The roadmap will be updated based on feedback.
- Collision avoidance, port rvo2c#
- Preferred radius to use where clearance allows
- Custom cost functions, so agents can prefer to avoid certain conditions
- Deterministic path finding budget and agent priorities
- Multiple and overlapping navmeshes and offmesh links
- Serialization for faster loading of large amounts of obstacles
- Queries to determine shapes are outside any obstacle
- Steering behaviours
- Globally optimal search, very slow but needed to benchmark cost functions
- Allow for loading or generating and unloading neighbouring chunks of navmesh
- Hierarchical path finding
- Dynamic and Robust Local Clearance Triangulations, Kallmann 2014
- Shortest Paths with Arbitrary Clearance from Navigation Meshes, Kallmann 2010
- Fully Dynamic Constrained Delaunay Triangulations, Kallmann 2003
- An Improved Incremental algorithm for constructing restricted Delaunay triangulations, Vigo 1997
- Incremental Delaunay Triangulation, Lischinski 1994
- Primitives for the Manipulation of General Subdivisions and the Computation of Voronoi Diagrams, Guibas and Stolfi 1985
- Adaptive Precision Floating-Point Arithmetic and Fast Robust Predicates for Computational Geometry, Shewchuk 1996
I would like to thank Marcello Kallmann for describing the local clearance triangulation including robust and dynamic insertion and removal algorithms, Jonathan Shewchuk for placing his geometric primitives in the public domain, and Govert van Drimmelen for porting them to C#.