浏览代码

Merge pull request #6494 from CedricGuillemet/Navigation

Navigation
David Catuhe 6 年之前
父节点
当前提交
cba3347ca6

+ 1 - 0
Playground/debug.html

@@ -38,6 +38,7 @@
 
     <!-- Babylon.js -->
     <script src="https://preview.babylonjs.com/ammo.js"></script>
+    <script src="https://preview.babylonjs.com/recast.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 1 - 0
Playground/frame.html

@@ -16,6 +16,7 @@
     <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
     <!-- Babylon.js -->
     <script src="https://preview.babylonjs.com/ammo.js"></script>
+    <script src="https://preview.babylonjs.com/recast.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/earcut.min.js"></script>

+ 1 - 0
Playground/full.html

@@ -14,6 +14,7 @@
     <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
     <!-- Babylon.js -->
     <script src="https://preview.babylonjs.com/ammo.js"></script>
+    <script src="https://preview.babylonjs.com/recast.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 1 - 0
Playground/index-local.html

@@ -18,6 +18,7 @@
     <script src="js/libs/fileSaver.js"></script>
     <!-- Dependencies -->
     <script src="../dist/preview%20release/ammo.js"></script>
+    <script src="../dist/preview%20release/recast.js"></script>
     <script src="../dist/preview%20release/cannon.js"></script>
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/gltf_validator.js"></script>

+ 1 - 0
Playground/index.html

@@ -19,6 +19,7 @@
     <script src="js/libs/fileSaver.js"></script>
     <!-- Dependencies -->
     <script src="https://preview.babylonjs.com/ammo.js"></script>
+    <script src="https://preview.babylonjs.com/recast.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

文件差异内容过多而无法显示
+ 38 - 0
dist/preview release/recast.js


+ 1 - 0
dist/preview release/what's new.md

@@ -5,6 +5,7 @@
 - WIP: Node material editor (NEED OR AND VIDEOS) ([Deltakosh](https://github.com/deltakosh/) / [TrevorDev](https://github.com/TrevorDev))
 - WIP: WebGPU support (NEED DOC OR SAMPLES) ([Sebavan](https://github.com/sebavan/))
 - .basis texture file format support [Doc](https://doc.babylonjs.com/resources/multi-platform_compressed_textures#basis-file-format) ([TrevorDev](https://github.com/TrevorDev))
+- WIP: Recast navigation mesh and crowd of moving agents [Demo](https://www.babylonjs-playground.com/#AJTRIL) ([CedricGuillemet](https://github.com/CedricGuillemet))
 - Classes decoupling ending up with smaller bundle sizes [Blog](https://medium.com/@babylonjs/size-matters-e0e94dad01a7) ([Deltakosh](https://github.com/deltakosh/))
 - Babylon.js controls [Doc](https://doc.babylonjs.com/features/controls) ([Sebavan](https://github.com/sebavan/) / [Deltakosh](https://github.com/deltakosh/))
 

+ 1 - 0
localDev/index.html

@@ -9,6 +9,7 @@
     <script src="../dist/preview%20release/cannon.js"></script>
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/ammo.js"></script>
+    <script src="../dist/preview%20release/recast.js"></script>
     <script src="../dist/preview%20release/gltf_validator.js"></script>
     <script src="../Tools/DevLoader/BabylonLoader.js"></script>
 

+ 247 - 0
src/Navigation/INavigationEngine.ts

@@ -0,0 +1,247 @@
+import { TransformNode } from "../Meshes/transformNode";
+import { Vector3 } from "../Maths/math";
+import { Mesh } from "../Meshes/mesh";
+import { Scene } from "../scene";
+
+/**
+ * Navigation plugin interface to add navigation constrained by a navigation mesh
+ */
+export interface INavigationEnginePlugin {
+    /**
+     * plugin name
+     */
+    name: string;
+
+    /**
+     * Creates a navigation mesh
+     * @param meshes array of all the geometry used to compute the navigatio mesh
+     * @param parameters bunch of parameters used to filter geometry
+     */
+    createMavMesh(meshes: Array<Mesh>, parameters: INavMeshParameters): void;
+
+    /**
+     * Create a navigation mesh debug mesh
+     * @param scene is where the mesh will be added
+     * @returns debug display mesh
+     */
+    createDebugNavMesh(scene: Scene): Mesh;
+
+    /**
+     * Get a navigation mesh constrained position, closest to the parameter position
+     * @param position world position
+     * @returns the closest point to position constrained by the navigation mesh
+     */
+    getClosestPoint(position: Vector3): Vector3;
+
+    /**
+     * Get a navigation mesh constrained position, within a particular radius
+     * @param position world position
+     * @param maxRadius the maximum distance to the constrained world position
+     * @returns the closest point to position constrained by the navigation mesh
+     */
+    getRandomPointAround(position: Vector3, maxRadius: number): Vector3;
+
+    /**
+     * Compute a navigation path from start to end. Returns an empty array if no path can be computed
+     * @param start world position
+     * @param end world position
+     * @returns array containing world position composing the path
+     */
+    computePath(start: Vector3, end: Vector3): Vector3[];
+
+    /**
+     * If this plugin is supported
+     * @returns true if plugin is supported
+     */
+    isSupported(): boolean;
+
+    /**
+     * Create a new Crowd so you can add agents
+     * @param maxAgents the maximum agent count in the crowd
+     * @param maxAgentRadius the maximum radius an agent can have
+     * @param scene to attach the crowd to
+     * @returns the crowd you can add agents to
+     */
+    createCrowd(maxAgents: number, maxAgentRadius: number, scene: Scene): ICrowd;
+
+    /**
+     * Release all resources
+     */
+    dispose(): void;
+}
+
+/**
+ * Crowd Interface. A Crowd is a collection of moving agents constrained by a navigation mesh
+ */
+export interface ICrowd {
+    /**
+     * Add a new agent to the crowd with the specified parameter a corresponding transformNode.
+     * You can attach anything to that node. The node position is updated in the scene update tick.
+     * @param pos world position that will be constrained by the navigation mesh
+     * @param parameters agent parameters
+     * @param transform hooked to the agent that will be update by the scene
+     * @returns agent index
+     */
+    addAgent(pos: Vector3, parameters: IAgentParameters, transform: TransformNode): number;
+
+    /**
+     * Returns the agent position in world space
+     * @param index agent index returned by addAgent
+     * @returns world space position
+     */
+    getAgentPosition(index: number): Vector3;
+
+    /**
+     * Gets the agent velocity in world space
+     * @param index agent index returned by addAgent
+     * @returns world space velocity
+     */
+    getAgentVelocity(index: number): Vector3;
+
+    /**
+     * remove a particular agent previously created
+     * @param index agent index returned by addAgent
+     */
+    removeAgent(index: number): void;
+
+    /**
+     * get the list of all agents attached to this crowd
+     * @returns list of agent indices
+     */
+    getAgents() : number[];
+
+    /**
+     * Tick update done by the Scene. Agent position/velocity/acceleration is updated by this function
+     * @param deltaTime in seconds
+     */
+    update(deltaTime: number): void;
+
+    /**
+     * Asks a particular agent to go to a destination. That destination is constrained by the navigation mesh
+     * @param index agent index returned by addAgent
+     * @param destination targeted world position
+     */
+    agentGoto(index: number, destination: Vector3): void;
+
+    /**
+     * Release all resources
+     */
+    dispose() : void;
+}
+
+/**
+ * Configures an agent
+ */
+export interface IAgentParameters {
+    /**
+     *  Agent radius. [Limit: >= 0]
+     */
+    radius: number;
+
+    /**
+     * Agent height. [Limit: > 0]
+     */
+    height: number;
+
+    /**
+     *  Maximum allowed acceleration. [Limit: >= 0]
+     */
+    maxAcceleration: number;
+
+    /**
+     * Maximum allowed speed. [Limit: >= 0]
+     */
+    maxSpeed: number;
+
+    /**
+     * Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
+     */
+    collisionQueryRange: number;
+
+    /**
+     * The path visibility optimization range. [Limit: > 0]
+     */
+    pathOptimizationRange: number;
+
+    /**
+     * How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
+     */
+    separationWeight: number;
+}
+
+/**
+ * Configures the navigation mesh creation
+ */
+export interface INavMeshParameters {
+    /**
+     * The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu]
+     */
+    cs: number;
+
+    /**
+     * The y-axis cell size to use for fields. [Limit: > 0] [Units: wu]
+     */
+    ch: number;
+
+    /**
+     * The maximum slope that is considered walkable. [Limits: 0 <= value < 90] [Units: Degrees]
+     */
+    walkableSlopeAngle: number;
+
+    /**
+     * Minimum floor to 'ceiling' height that will still allow the floor area to
+     * be considered walkable. [Limit: >= 3] [Units: vx]
+     */
+    walkableHeight: number;
+
+    /**
+     * Maximum ledge height that is considered to still be traversable. [Limit: >=0] [Units: vx]
+     */
+    walkableClimb: number;
+
+    /**
+     * The distance to erode/shrink the walkable area of the heightfield away from
+     * obstructions.  [Limit: >=0] [Units: vx]
+     */
+    walkableRadius: number;
+
+    /**
+     * The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx]
+     */
+    maxEdgeLen: number;
+
+    /**
+     * The maximum distance a simplfied contour's border edges should deviate
+     * the original raw contour. [Limit: >=0] [Units: vx]
+     */
+    maxSimplificationError: number;
+
+    /**
+     * The minimum number of cells allowed to form isolated island areas. [Limit: >=0] [Units: vx]
+     */
+    minRegionArea: number;
+
+    /**
+     * Any regions with a span count smaller than this value will, if possible,
+     * be merged with larger regions. [Limit: >=0] [Units: vx]
+     */
+    mergeRegionArea: number;
+
+    /**
+     * The maximum number of vertices allowed for polygons generated during the
+     * contour to polygon conversion process. [Limit: >= 3]
+     */
+    maxVertsPerPoly: number;
+
+    /**
+     * Sets the sampling distance to use when generating the detail mesh.
+     * (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu]
+     */
+    detailSampleDist: number;
+
+    /**
+     * The maximum distance the detail mesh surface should deviate from heightfield
+     * data. (For height detail only.) [Limit: >=0] [Units: wu]
+     */
+    detailSampleMaxError: number;
+}

+ 1 - 0
src/Navigation/Plugins/index.ts

@@ -0,0 +1 @@
+export * from "./recastJSPlugin";

+ 380 - 0
src/Navigation/Plugins/recastJSPlugin.ts

@@ -0,0 +1,380 @@
+import { INavigationEnginePlugin, ICrowd, IAgentParameters, INavMeshParameters } from "../../Navigation/INavigationEngine";
+import { Logger } from "../../Misc/logger";
+import { VertexData } from "../../Meshes/mesh.vertexData";
+import { Mesh } from "../../Meshes/mesh";
+import { Scene } from "../../scene";
+import { Vector3 } from '../../Maths/math';
+import { TransformNode } from "../../Meshes/transformNode";
+import { Observer } from "../../Misc/observable";
+import { Nullable } from "../../types";
+import { VertexBuffer } from "../../Meshes/buffer";
+
+declare var Recast: any;
+
+/**
+ * RecastJS navigation plugin
+ */
+export class RecastJSPlugin implements INavigationEnginePlugin {
+    /**
+     * Reference to the Recast library
+     */
+    public bjsRECAST: any = {};
+
+    /**
+     * plugin name
+     */
+    public name: string = "RecastJSPlugin";
+
+    /**
+     * the first navmesh created. We might extend this to support multiple navmeshes
+     */
+    public navMesh: any;
+
+    /**
+     * Initializes the recastJS plugin
+     * @param recastInjection can be used to inject your own recast reference
+     */
+    public constructor(recastInjection: any = Recast) {
+        if (typeof recastInjection === "function") {
+            recastInjection(this.bjsRECAST);
+        } else {
+            this.bjsRECAST = recastInjection;
+        }
+
+        if (!this.isSupported()) {
+            Logger.Error("RecastJS is not available. Please make sure you included the js file.");
+            return;
+        }
+    }
+
+    /**
+     * Creates a navigation mesh
+     * @param meshes array of all the geometry used to compute the navigatio mesh
+     * @param parameters bunch of parameters used to filter geometry
+     */
+    createMavMesh(meshes: Array<Mesh>, parameters: INavMeshParameters): void {
+        const rc = new this.bjsRECAST.rcConfig();
+        rc.cs = parameters.cs;
+        rc.ch = parameters.ch;
+        rc.borderSize = 0;
+        rc.tileSize = 0;
+        rc.walkableSlopeAngle = parameters.walkableSlopeAngle;
+        rc.walkableHeight = parameters.walkableHeight;
+        rc.walkableClimb = parameters.walkableClimb;
+        rc.walkableRadius = parameters.walkableRadius;
+        rc.maxEdgeLen = parameters.maxEdgeLen;
+        rc.maxSimplificationError = parameters.maxSimplificationError;
+        rc.minRegionArea = parameters.minRegionArea;
+        rc.mergeRegionArea = parameters.mergeRegionArea;
+        rc.maxVertsPerPoly = parameters.maxVertsPerPoly;
+        rc.detailSampleDist = parameters.detailSampleDist;
+        rc.detailSampleMaxError = parameters.detailSampleMaxError;
+
+        this.navMesh = new this.bjsRECAST.NavMesh();
+
+        var index: number;
+        var tri: number;
+        var pt: number;
+
+        var indices = [];
+        var positions = [];
+        var offset = 0;
+        for (index = 0; index < meshes.length; index++) {
+            if (meshes[index]) {
+                var mesh = meshes[index];
+
+                const meshIndices = mesh.getIndices();
+                if (!meshIndices) {
+                    continue;
+                }
+                const meshPositions = mesh.getVerticesData(VertexBuffer.PositionKind, false, false);
+                if (!meshPositions) {
+                    continue;
+                }
+
+                const wm = mesh.computeWorldMatrix(false);
+
+                for (tri = 0; tri < meshIndices.length; tri++) {
+                    indices.push(meshIndices[tri] + offset);
+                }
+
+                var transformed = Vector3.Zero();
+                var position = Vector3.Zero();
+                for (pt = 0; pt < meshPositions.length; pt += 3) {
+                    Vector3.FromArrayToRef(meshPositions, pt, position);
+                    Vector3.TransformCoordinatesToRef(position, wm, transformed);
+                    positions.push(transformed.x, transformed.y, transformed.z);
+                }
+
+                offset += meshPositions.length / 3;
+            }
+        }
+
+        this.navMesh.build(positions, offset, indices, indices.length, rc);
+    }
+
+    /**
+     * Create a navigation mesh debug mesh
+     * @param scene is where the mesh will be added
+     * @returns debug display mesh
+     */
+    createDebugNavMesh(scene: Scene): Mesh {
+        var tri: number;
+        var pt: number;
+        var debugNavMesh = this.navMesh.getDebugNavMesh();
+        let triangleCount = debugNavMesh.getTriangleCount();
+
+        var indices = [];
+        var positions = [];
+        for (tri = 0; tri < triangleCount * 3; tri++)
+        {
+            indices.push(tri);
+        }
+        for (tri = 0; tri < triangleCount; tri++)
+        {
+            for (pt = 0; pt < 3 ; pt++)
+            {
+                let point = debugNavMesh.getTriangle(tri).getPoint(pt);
+                positions.push(point.x, point.y, point.z);
+            }
+        }
+
+        var mesh = new Mesh("NavMeshDebug", scene);
+        var vertexData = new VertexData();
+
+        vertexData.indices = indices;
+        vertexData.positions = positions;
+        vertexData.applyToMesh(mesh, false);
+        return mesh;
+    }
+
+    /**
+     * Get a navigation mesh constrained position, closest to the parameter position
+     * @param position world position
+     * @returns the closest point to position constrained by the navigation mesh
+     */
+    getClosestPoint(position: Vector3) : Vector3
+    {
+        var p = new this.bjsRECAST.Vec3(position.x, position.y, position.z);
+        var ret = this.navMesh.getClosestPoint(p);
+        var pr = new Vector3(ret.x, ret.y, ret.z);
+        return pr;
+    }
+
+    /**
+     * Get a navigation mesh constrained position, within a particular radius
+     * @param position world position
+     * @param maxRadius the maximum distance to the constrained world position
+     * @returns the closest point to position constrained by the navigation mesh
+     */
+    getRandomPointAround(position: Vector3, maxRadius: number): Vector3 {
+        var p = new this.bjsRECAST.Vec3(position.x, position.y, position.z);
+        var ret = this.navMesh.getRandomPointAround(p, maxRadius);
+        var pr = new Vector3(ret.x, ret.y, ret.z);
+        return pr;
+    }
+
+    /**
+     * Compute a navigation path from start to end. Returns an empty array if no path can be computed
+     * @param start world position
+     * @param end world position
+     * @returns array containing world position composing the path
+     */
+    computePath(start: Vector3, end: Vector3): Vector3[]
+    {
+        var pt: number;
+        let startPos = new this.bjsRECAST.Vec3(start.x, start.y, start.z);
+        let endPos = new this.bjsRECAST.Vec3(end.x, end.y, end.z);
+        let navPath = this.navMesh.computePath(startPos, endPos);
+        let pointCount = navPath.getPointCount();
+        var positions = [];
+        for (pt = 0; pt < pointCount; pt++)
+        {
+            let p = navPath.getPoint(pt);
+            positions.push(new Vector3(p.x, p.y, p.z));
+        }
+        return positions;
+    }
+
+    /**
+     * Create a new Crowd so you can add agents
+     * @param maxAgents the maximum agent count in the crowd
+     * @param maxAgentRadius the maximum radius an agent can have
+     * @param scene to attach the crowd to
+     * @returns the crowd you can add agents to
+     */
+    createCrowd(maxAgents: number, maxAgentRadius: number, scene: Scene) : ICrowd
+    {
+        var crowd = new RecastJSCrowd(this, maxAgents, maxAgentRadius, scene);
+        return crowd;
+    }
+
+    /**
+     * Disposes
+     */
+    public dispose() {
+
+    }
+
+    /**
+     * If this plugin is supported
+     * @returns true if plugin is supported
+     */
+    public isSupported(): boolean {
+        return this.bjsRECAST !== undefined;
+    }
+}
+
+/**
+ * Recast detour crowd implementation
+ */
+export class RecastJSCrowd implements ICrowd {
+    /**
+     * Recast/detour plugin
+     */
+    public bjsRECASTPlugin: RecastJSPlugin;
+    /**
+     * Link to the detour crowd
+     */
+    public recastCrowd: any = {};
+    /**
+     * One transform per agent
+     */
+    public transforms: TransformNode[] = new Array<TransformNode>();
+    /**
+     * All agents created
+     */
+    public agents: number[] = new Array<number>();
+    /**
+     * Link to the scene is kept to unregister the crowd from the scene
+     */
+    private _scene: Scene;
+
+    /**
+     * Observer for crowd updates
+     */
+    private _onBeforeAnimationsObserver: Nullable<Observer<Scene>> = null;
+
+    /**
+     * Constructor
+     * @param plugin recastJS plugin
+     * @param maxAgents the maximum agent count in the crowd
+     * @param maxAgentRadius the maximum radius an agent can have
+     * @param scene to attach the crowd to
+     * @returns the crowd you can add agents to
+     */
+    public constructor(plugin: RecastJSPlugin, maxAgents: number, maxAgentRadius: number, scene: Scene) {
+        this.bjsRECASTPlugin = plugin;
+        this.recastCrowd = new this.bjsRECASTPlugin.bjsRECAST.Crowd(maxAgents, maxAgentRadius, this.bjsRECASTPlugin.navMesh.getNavMesh());
+        this._scene = scene;
+
+        this._onBeforeAnimationsObserver = scene.onBeforeAnimationsObservable.add(() => {
+            this.update(scene.getEngine().getDeltaTime() * 0.001);
+        });
+    }
+
+    /**
+     * Add a new agent to the crowd with the specified parameter a corresponding transformNode.
+     * You can attach anything to that node. The node position is updated in the scene update tick.
+     * @param pos world position that will be constrained by the navigation mesh
+     * @param parameters agent parameters
+     * @param transform hooked to the agent that will be update by the scene
+     * @returns agent index
+     */
+    addAgent(pos: Vector3, parameters: IAgentParameters, transform: TransformNode): number
+    {
+        var agentParams = new this.bjsRECASTPlugin.bjsRECAST.dtCrowdAgentParams();
+        agentParams.radius = parameters.radius;
+        agentParams.height = parameters.height;
+        agentParams.maxAcceleration = parameters.maxAcceleration;
+        agentParams.maxSpeed = parameters.maxSpeed;
+        agentParams.collisionQueryRange = parameters.collisionQueryRange;
+        agentParams.pathOptimizationRange = parameters.pathOptimizationRange;
+        agentParams.separationWeight = parameters.separationWeight;
+        agentParams.updateFlags = 7;
+        agentParams.obstacleAvoidanceType = 0;
+        agentParams.queryFilterType = 0;
+        agentParams.userData = 0;
+
+        var agentIndex = this.recastCrowd.addAgent(new this.bjsRECASTPlugin.bjsRECAST.Vec3(pos.x, pos.y, pos.z), agentParams);
+        this.transforms.push(transform);
+        this.agents.push(agentIndex);
+        return agentIndex;
+    }
+
+    /**
+     * Returns the agent position in world space
+     * @param index agent index returned by addAgent
+     * @returns world space position
+     */
+    getAgentPosition(index: number): Vector3 {
+        var agentPos = this.recastCrowd.getAgentPosition(index);
+        return new Vector3(agentPos.x, agentPos.y, agentPos.z);
+    }
+
+    /**
+     * Returns the agent velocity in world space
+     * @param index agent index returned by addAgent
+     * @returns world space velocity
+     */
+    getAgentVelocity(index: number): Vector3 {
+        var agentVel = this.recastCrowd.getAgentVelocity(index);
+        return new Vector3(agentVel.x, agentVel.y, agentVel.z);
+    }
+
+    /**
+     * Asks a particular agent to go to a destination. That destination is constrained by the navigation mesh
+     * @param index agent index returned by addAgent
+     * @param destination targeted world position
+     */
+    agentGoto(index: number, destination: Vector3): void {
+        this.recastCrowd.agentGoto(index, new this.bjsRECASTPlugin.bjsRECAST.Vec3(destination.x, destination.y, destination.z));
+    }
+
+    /**
+     * remove a particular agent previously created
+     * @param index agent index returned by addAgent
+     */
+    removeAgent(index: number): void {
+        this.recastCrowd.removeAgent(index);
+
+        var item = this.agents.indexOf(index);
+        if (item > -1) {
+            this.agents.splice(item, 1);
+            this.transforms.splice(item, 1);
+        }
+    }
+
+    /**
+     * get the list of all agents attached to this crowd
+     * @returns list of agent indices
+     */
+    getAgents(): number[] {
+        return this.agents;
+    }
+
+    /**
+     * Tick update done by the Scene. Agent position/velocity/acceleration is updated by this function
+     * @param deltaTime in seconds
+     */
+    update(deltaTime: number): void {
+        // update crowd
+        this.recastCrowd.update(deltaTime);
+
+        // update transforms
+        for (let index = 0; index < this.agents.length; index++)
+        {
+            this.transforms[index].position = this.getAgentPosition(this.agents[index]);
+        }
+    }
+
+    /**
+     * Release all resources
+     */
+    dispose() : void
+    {
+        this.recastCrowd.destroy();
+        this._scene.onBeforeAnimationsObservable.remove(this._onBeforeAnimationsObserver);
+        this._onBeforeAnimationsObserver = null;
+    }
+}

+ 2 - 0
src/Navigation/index.ts

@@ -0,0 +1,2 @@
+export * from "./INavigationEngine";
+export * from "./Plugins/index";

+ 1 - 0
src/index.ts

@@ -23,6 +23,7 @@ export * from "./Materials/index";
 export * from "./Maths/index";
 export * from "./Meshes/index";
 export * from "./Morph/index";
+export * from "./Navigation/index";
 export * from "./node";
 export * from "./Offline/index";
 export * from "./Particles/index";