Using recast-detour package server side with null engine and typescript definitions

Hi again!

I decided to open a new thread with this problem. Read through the recent one that had the similar problem, but I for some reason I do not see the types in my npm pulled ‘recast-detour’ -package. See down below, this is version 1.2.0:
image

What I am trying to do is boot up null engine and run the recast calculations server side :smiley: So basically I need to get the recast instance and pluck it into new RecastJSPlugin(recast)… I think :laughing:

Pinging the master himself: @Cedric

recast.d.ts is in the babylon.js repo :

I can put it in the npm for the next release if you think it makes more sense.

1 Like

I think that would be nice yeah, so it can be imported straight from the npm recast-detour repo :sunglasses:

1 Like

npm recast-detour at version 1.3 with types for TS bundled inside.

1 Like

Woop woop thank you! :star_struck:

1 Like

I still have a bit of a problem with the nature of Recast as a commonjs module… I currently use esm module/import syntax in my backend service to fetch, for example, @babylons/core -package and some specific item from there. Now, everything works fine until I try to import ‘recast-detour’ and use it as input for the recast plugin. Like so:

navigation = new RecastJSPlugin(Recast)

I seem to not find the correct way to import the library to my local code and use it as the input for RecastJSPlugin. :thinking: Is there some information laying around that could help me with this problem?

I’m not familiar at all with module import. Maybe @sebavan or @RaananW can help.

Would be great to know how you import recast-detour, and what the datatype of the Recast variable you pass to the plugin. Is any exception thrown when you do that?

Currently there is some problem with the d.ts typings, as I get the following error:

'C:/projects/babylonjs-typescript-nullengine-example/node_modules/recast-detour/recast.d.ts' is not a module.

I created a small repo to illustrate this problem:

If you would be so kind to clone this and run the following commands, you should be able to see the error above:
npm install
npm run build

Rant warning: My use case for this is to use modern web technologies to build a multiplayer game. I am trying to use es module syntax as much as possible. I have a project structure like this:
image
As you can see, the project is a monorepo. In the packages/game, I have all the Babylon.js and game specific stuff, that the server and the client are both using (by importing required parts). Everything works pretty nicely, but all the problems stem from the Babylon.js plugins such as physics and now recast, that are not es modules at all. Added with TypeScript, everything seems really complex to set up. Is there any way that the emscripten created plugin packages could provide es module style npm repos. Es module style is also becoming a new standard, as, for example, @babylon/core package is already built this way.

And furthermore, for the game client, I can import the recast library with regular script tags that add the package to the window -variable. However, this is not the case in server side node.js environment. I’m pretty desperate at this point since I have lost many hours battling with the plugin problems without any viable solution :pensive:

It seems like the declaration file of recast is not module friendly.
I am not sure if we are responsible for this package I will discuss this internally with the team regarding an update to the declaration file, but even without external intervention you can solve it locally. To do that you will need to create a new .d.ts file and have Recast re-defined there. I added recast.d.ts in the src folder. The file contents looks like this:

declare module "recast-detour" {
  export class rcConfig {
    new();
    width: number;
    height: number;
    tileSize: number;
    borderSize: number;
    cs: number;
    ch: number;
    bmin: any;
    bmax: any;
    walkableSlopeAngle: number;
    walkableHeight: number;
    walkableClimb: number;
    walkableRadius: number;
    maxEdgeLen: number;
    maxSimplificationError: number;
    minRegionArea: number;
    mergeRegionArea: number;
    maxVertsPerPoly: number;
    detailSampleDist: number;
    detailSampleMaxError: number;
  }
  export class Vec3 {
    new();
    new(x: number, y: number, z: number);
    x: number;
    y: number;
    z: number;
  }
  export class Triangle {
    new();
    getPoint(n: number): Vec3;
  }
  export class DebugNavMesh {
    new();
    getTriangleCount(): number;
    getTriangle(n: number): Triangle;
  }
  export class dtNavMesh {}
  export class dtObstacleRef {}
  export class NavmeshData {
    new();
    dataPointer: any;
    size: number;
  }
  export class NavPath {
    getPointCount(): number;
    getPoint(n: number): Vec3;
  }
  export class dtCrowdAgentParams {
    new();
    radius: number;
    height: number;
    maxAcceleration: number;
    maxSpeed: number;
    collisionQueryRange: number;
    pathOptimizationRange: number;
    separationWeight: number;
    updateFlags: number;
    obstacleAvoidanceType: number;
    queryFilterType: number;
    userData: unknown;
  }
  export class NavMesh {
    new();
    destroy(): void;
    build(
      positions: any,
      positionCount: number,
      indices: any,
      indexCount: number,
      config: rcConfig
    ): void;
    buildFromNavmeshData(data: NavmeshData): void;
    getNavmeshData(): NavmeshData;
    freeNavmeshData(data: NavmeshData): void;
    getDebugNavMesh(): DebugNavMesh;
    getClosestPoint(position: Vec3): Vec3;
    getRandomPointAround(position: Vec3, maxRadius: number): Vec3;
    moveAlong(position: Vec3, destination: Vec3): Vec3;
    getNavMesh(): dtNavMesh;
    computePath(start: Vec3, end: Vec3): NavPath;
    setDefaultQueryExtent(extent: Vec3): void;
    getDefaultQueryExtent(): Vec3;
    addCylinderObstacle(
      position: Vec3,
      radius: number,
      height: number
    ): dtObstacleRef;
    addBoxObstacle(position: Vec3, extent: Vec3, angle: number): dtObstacleRef;
    removeObstacle(obstacle: dtObstacleRef): void;
    update(): void;
  }
  export class Crowd {
    new(maxAgents: number, maxAgentRadius: number, nav: dtNavMesh);
    destroy(): void;
    addAgent(position: Vec3, params: dtCrowdAgentParams): number;
    removeAgent(idx: number): void;
    update(dt: number): void;
    getAgentPosition(idx: number): Vec3;
    getAgentVelocity(idx: number): Vec3;
    getAgentNextTargetPath(idx: number): Vec3;
    getAgentState(idx: number): number;
    overOffmeshConnection(idx: number): boolean;
    agentGoto(idx: number, destination: Vec3): void;
    agentTeleport(idx: number, destination: Vec3): void;
    getAgentParameters(idx: number): dtCrowdAgentParams;
    setAgentParameters(idx: number, params: dtCrowdAgentParams): void;
    setDefaultQueryExtent(extent: Vec3): void;
    getDefaultQueryExtent(): Vec3;
    getCorners(idx: number): NavPath;
  }
}

The main difference is the declaration of the module recast-detour.
Afterwards you need to tell typescript (and typescript only) to load the declaration from a different location. To do that edit your tsconfig.json:

{
  "compilerOptions": {
    "module": "ESNext",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "target": "ESNext",
    "noImplicitAny": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist",
    "baseUrl": ".",
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "paths": {
      "recast-detour": ["./src/recast.d.ts"]
    }
  },
  "include": ["./src/**/*", ".env"],
  "exclude": ["node_modules"]
}

The main different here is the paths key, which tells typescript to load the declaration from the newly created module file.

One last change would be to load the default definition of Recast. instead of

import  * as Recast from 'recast-detour'

use:

import Recast from 'recast-detour'

It compiles correctly afterwards.

1 Like

Thank you @RaananW and @Cedric , it seems to work now in the backend server code! However I still have problems with the front end import, but I think I will go with the script tag approach to move forwards :sunglasses: I use https://vitejs.dev/ as a dev environment/bundler instead of webpack and it kind of requires you to use es modules…

Anyway, I would like to add a suggestion to the Babylon.js ecosystem regarding plugin usage. When the JavaScript world is now moving more and more to the es modules, I would suggest that the required plugins are distributed in a bit more nicer way than they are now. Currently the most used plugins such as physics (ammo, cannon) and navigation (recast) should be in their own respective github repositories and their own respective npm packages. If I am correct, they are now just compiled in someone’s computer and added to the Babylon.js github repository. It would be super cool that you could just like use similar style to following code:

import { CannonJSPlugin, RecastJSPlugin } from '@babylonjs/core'
import { Cannon, Recast } from '@babylonjs/plugins'
const recast = new RecastJSPlugin(Recast)
const physics = new CannonJSPlugin(true, 10, Cannon)

I managed to fix the issue in the front end as well. How? Well, I had to do it a bit hard way; I copy pasted recast.js to my local project, removed type checking, and modified the code a bit to do the export the way I wanted it to :laughing: Not the best way, but I had to do it to move forwards quickly. Hoping to see some modified es module plugin development in the future though :slight_smile:

1 Like