NavMesh

Generating a NavMesh

The easiest way to generate a NavMesh is using the high level generator functions from recast-navigation/generators:

  • generateSoloNavMesh – Generates a NavMesh with a single tile. You can use this for smaller environments.
  • generateTiledNavMesh – Generates a NavMesh with multiple tiles. You should use this for larger environments.
  • generateTileCache – Generates a TileCache that supports temporary obstacles. See the Temporary Obstacles section.

The input positions and indices should adhere to OpenGL conventions:

  • Use the right-handed coordinate system
  • Indices should be in counter-clockwise winding order
  • The positions and indices arguments should be flat arrays of numbers
import { generateSoloNavMesh } from 'recast-navigation/generators';

const positions = [
  /* flat array of positions */
  /* e.g. x1, y1, z1, x2, y2, z2, ... */
];

const indices = [
  /* flat array of indices */
];

const navMeshConfig = {
  /* ... */
};

const { success, navMesh } = generateSoloNavMesh(
  positions,
  indices,
  navMeshConfig
);

Builing a NavMesh in a Web Worker

It’s possible to build a NavMesh in a Web Worker. This can be useful for offloading heavy computation from the main thread.

The library doesn’t include a web worker, but it’s straightforward to create your own. An example of solo nav mesh generation in a web worker can be found here: https://github.com/isaac-mason/recast-navigation-js/tree/next/examples/three-vite-worker-example

The example uses importNavMesh and exportNavMesh to serialize and deserialize a NavMesh for transfer between the main thread and the web worker.

Customizing the NavMesh Generation Process

This library provides low-level APIs that aim to match the recast and detour c++ api, allowing you to create custom navigation mesh generators based on your specific needs. You can use the NavMesh generators provided by @recast-navigation/generators as a basis: https://github.com/isaac-mason/recast-navigation-js/tree/main/packages/recast-navigation-generators/src/generators

An example of a custom NavMesh generator with custom areas can be found here: https://recast-navigation-js.isaacmason.com/?path=/story/advanced-custom-areas–compute-path

Please note that not all recast and detour functionality is exposed yet. If you require unexposed functionality, please submit an issue or a pull request.

Querying a NavMesh

Creating a NavMeshQuery class

import { NavMeshQuery } from 'recast-navigation';

const navMeshQuery = new NavMeshQuery(navMesh);

Compute a straight path between two points

const start = { x: 0, y: 0, z: 0 };
const end = { x: 2, y: 0, z: 0 };
const { success, error, path } = navMeshQuery.computePath(start, end);

Find the closest point on the NavMesh to a given position

const position = { x: 0, y: 0, z: 0 };

const { success, status, point, polyRef, isPointOverPoly } =
  navMeshQuery.findClosestPoint(position);

Find a random point on the NavMesh around a given position

const radius = 0.5;
const {
  success,
  status,
  randomPolyRef,
  randomPoint: initialAgentPosition,
} = navMeshQuery.findRandomPointAroundCircle(position, radius);

Crowds and Agents

Creating a Crowd

import { Crowd } from 'recast-navigation';

const maxAgents = 10;
const maxAgentRadius = 0.6;

const crowd = new Crowd(navMesh, { maxAgents, maxAgentRadius });

Updating a Crowd

There are a few options for updating a crowd:

Variable time stepping

The simplest approach is to do varible time stepping. Simply call crowd.update with your delta time every frame.

crowd.update(timeSinceLastFrame);

This approach is suitable for most use cases, but the variable timestep will result in non-deterministic behaviour.

Depending on your use case, you might want to clamp timeSinceLastFrame to a maximum value to prevent large time steps causing issues.

Fixed time stepping with interpolation

Fixed time stepping with interpolation can be preferable if you need deterministic behaviour, but still want smooth agent position updates each frame.

If you provide update with a dt value and a timeSinceLastFrame value, the crowd update will do fixed time stepping with interpolation.

const dt = 1 / 60;
const maxSubSteps = 10;

crowd.update(dt, timeSinceLastFrame, maxSubSteps);

This will update the interpolatedPosition vector3 on each agent, which you can use to render a smoothly interpolated agent position between updates.

console.log(agent.interpolatedPosition); // { x: 1, y: 2, z: 3 }

Manual fixed time stepping

If you want full control over crowd updates, you can simply call crowd.update with a given dt value.

const dt = 1 / 60;

crowd.update(dt);

Creating an Agent in a Crowd

const position = { x: 0, y: 0, z: 0 };
const radius = 2;

const {
  success,
  status,
  randomPolyRef,
  randomPoint: initialAgentPosition,
} = navMeshQuery.findRandomPointAroundCircle(position, radius);

const agent = crowd.addAgent(initialAgentPosition, {
  radius: 0.5,
  height: 0.5,
  maxAcceleration: 4.0,
  maxSpeed: 1.0,
  collisionQueryRange: 0.5,
  pathOptimizationRange: 0.0,
  separationWeight: 1.0,
});

Setting an Agent’s Target

const targetPosition = { x: 2, y: 0, z: 0 };
agent.requestMoveTarget(targetPosition);

Clearing an Agent’s Target

agent.resetMoveTarget();

Leave a comment

Your email address will not be published. Required fields are marked *