Different NavMesh data generated in browser and backend

Hi @Cedric,

I need the navigation computed on the backend and the browser side just replay the path travel. It seems to be working mostly but not quite right.

Please take a look at my PG. https://playground.babylonjs.com/#WCSDE1#10

Line 59 and 60 are the baked navigation mesh data generated by the backend and browser, respectively. The array size are different. You can comment out either line to see the different behaviors. The baked nav mesh data generated in the browser works fine. But if the baked nav mesh data from backend is used, the cube is still able to walk through the obstacle. The cube cannot walk through the static obstacles in both cases, so I think the baked data from backend seems to be correct to some extent.

I followed this document to run babylon.js on the backend. Running Babylon.js On A Server | Babylon.js Documentation The code I used look like the following:

    const BABYLON = require("../babylon/babylon.max");
    const Recast = require('recast-detour')();

    const engine = new BABYLON.NullEngine();
    const scene = new BABYLON.Scene(engine);
    const mesh = createStaticMesh(scene);
    const navigationPlugin = new BABYLON.RecastJSPlugin(Recast);
    navigationPlugin.createNavMesh([mesh], parameters);
    const data = navigationPlugin.getNavmeshData();

Function createStaticMesh() and parameters variable used for the backend run are exactly the same as in the PG.

Some more investigation and findings:

I created a createNavMeshDebug() function based on createNavMesh() source code in Babylon.js/recastJSPlugin.ts at 34802bcae6c23cc4a452a31101305f0bc051bf76 · BabylonJS/Babylon.js · GitHub

I used this createNavMeshDebug() function to capture positions, indices, and offset that are passed to the recast library. See PG:
https://playground.babylonjs.com/#WCSDE1#11

I used the captured values for positions, indices, and offset from PG and applied these values to recast function running in nodejs backend like this. And the output is correct.

    const navMesh = new Recast.NavMesh();
    const positions = [...];
    const offset = 57;
    const indices = [...];
    navMesh.build(positions, offset, indices, indices.length, rc);
    let navmeshData = navMesh.getNavmeshData();
    var arrView = new Uint8Array(Recast.HEAPU8.buffer, navmeshData.dataPointer, navmeshData.size);
    var ret = new Uint8Array(navmeshData.size);
    ret.set(arrView);

I did some more tests. I was able to produce correct output on the backend if I use the positions and indices generated by createNavMeshDebug() function and I pass these values directly to the recast-detour 1.3.0 (same version used in my browser) that I imported like this.

    const Recast = require('recast-detour')();
    const navMesh = new Recast.NavMesh();
    // positions, offset, indices are calculated by createNavMeshDebug, which is a augmented copy of createNavMesh()
    navMesh.build(positions, offset, indices, indices.length, rc);
    // Code copied from getNavmeshData() implementation
    let navmeshData = navMesh.getNavmeshData();
    var arrView = new Uint8Array(Recast.HEAPU8.buffer, navmeshData.dataPointer, navmeshData.size);
    var ret = new Uint8Array(navmeshData.size);
    ret.set(arrView);

But if I used the Recast plugin api like the following, the output is incorrect.

    const Recast = require('recast-detour')();
    const engine = new BABYLON.NullEngine();
    const scene = new BABYLON.Scene(engine);
    const mesh = createStaticMesh(scene);
    const navigationPlugin = new BABYLON.RecastJSPlugin(Recast);
    navigationPlugin.createNavMesh([mesh], parameters);
    const data = navigationPlugin.getNavmeshData();

@Cedric Is there a default version of recast-detour when I run RecastPlugin on the backend? And it seems this version is generating different navmesh data?

Since this PR : obstacles and corners by CedricGuillemet · Pull Request #9813 · BabylonJS/Babylon.js · GitHub
recastjs supports tiled navigation mesh creation that is used for dynamic obstacles.
Depending on the navigation mesh creation (basically if tileSize if non zero, then create a tile cache), the navigation mesh can be fully static or tiled.
To make the difference at load time, the first 4 bytes are different
it is MSET for static and TSET for tile cache.
With previous recastjs version, the tileSize parameter was unused and only static navmesh was created.
In your PG, the first 4 bytes indicate that navMeshArray from line 59 is static and line 60 is a tilecache.
So, if creation is different with same parameters, then the recastjs version is different between browser and backend.

2 Likes

Hi @Cedric

Thanks for confirming. I was using babylon.max.js 4.2.0. If I switch to use the version from Babylon.js/babylon.max.js at preview · BabylonJS/Babylon.js · GitHub, it works fine. It seems this dist already has a recast-detour library bundled together, and it doesn’t accept the recast-detour version I passed to RecastPlugin constructor.

Some additional info that might help others (and correction of my statement above):

  • Babylon.js bundle doesn’t contain recast. It is required to load recast-detour for RecastPlugin to work in NodeJS environment.

     const Recast = require('recast-detour')();
    
  • The root cause of the difference is the older version of babylonjs doesn’t pass the tileSize parameter to recast-detour as mentioned by Cedric (obstacles and corners by CedricGuillemet · Pull Request #9813 · BabylonJS/Babylon.js · GitHub). So using the latest version 5.0.0 helps to address the problem.

  • It is not necessary to manually download babylon.js bundle from CDN. It is also available via npm.

    “dependencies”: {
    …
    “babylonjs”: “5.0.0-alpha.17”
    },

And it can be import as

  import * as BABYLON from 'babylonjs';

Why not @babylonjs/* node modules. This is the library I use for react webapp. And webpack bundles it fine. I believe it should also work for node.js environment. But for my current backend configuration, this version has ES6 syntax and since it is in node_modules, it is skipped by ts-jest transpile and throws error (I only tested recast running in node.js in a jest test). There are many things with higher priority so I am happy with using babylonjs module for now.

1 Like