Trouble using the NavMeshPathfinder

I tried to create two monsters which walk around randomly on the terrain.
The first monster works well and does what it should do but the second monster moves to the waypoints of the first monster and gets incorrect waypoints.


The white boxes are the waypoints for the first monster and the blue boxes are the waypoints for the second monster.
The second monster starts with his blue boxes, then he walks to a white box and then back to his blue boxes.

Does someone know how to fix it?

I appreciate every answer or hint :slight_smile:

final class Main extends SimpleApplication {

  /**
   * Defines the object of the UCMonster class.
   */
  private UCMonster firstMonster;

  /**
   * Defines the object of the UCMonster class.
   */
  private UCMonster secondMonster;

  public static void main(String[] inArgs) throws Exception {
    MainRunner aMain = new MainRunner();

    AppSettings aCustomSetting = createSettings();
    aMain.setSettings(aCustomSetting);

    aMain.start();
  }

  @Override
  public void simpleInitApp() {

    // create a firstMonster.
    firstMonster = new UCMonster(assetManager, rootNode, miniMap, this, terrain);
    // sets the start position for the firstMonster.
    firstMonster.setStartPoint(terrain.getRandomWaypoint());
    // sets the destination point for the firstMonster.
    firstMonster.setDestinationPoint(terrain.getRandomWaypoint());
    // shows the firstMonster immediately. Is used to test the firstMonster.
    firstMonster.initMonster();
    // add firstMonster to the monsterList of the MonsterRegistry.
    MonsterRegistryUtils.getInstance().addMonster(firstMonster);
    // set the size of the monsterModel.
    firstMonster.setMonsterSize(0);

    // create a secondMonster.
    secondMonster = new UCMonster(assetManager, rootNode, miniMap, this, terrain);
    // sets the start position for the secondMonster.
    secondMonster.setStartPoint(terrain.getRandomWaypoint());
    // sets the destination point for the secondMonster.
    secondMonster.setDestinationPoint(terrain.getRandomWaypoint());
    // shows the secondMonster immediately. Is used to test the secondMonster.
    secondMonster.initMonster();
    // add secondMonster to the monsterList of the MonsterRegistry.
    MonsterRegistryUtils.getInstance().addMonster(secondMonster);
    // set the size of the monsterModel.
    secondMonster.setMonsterSize(0);
  }

  @Override
  public void simpleUpdate(float inTimePerFrame) {

    // updates methods of the firstMonster.
    firstMonster.updateMonster();

    // updates methods of the secondMonster.
    secondMonster.updateMonster();
  }

}
final class UCMonster {

  /**
   * Defines the mass of the monsterControl.
   */
  private static final float MASS_OF_MONSTER_CONTROL = 0.0f;

  /**
   * The gravity value in the Y axis for the monster.
   */
  private static final float GRAVITY_VALUE_IN_Y_AXIS = -30f;

  /**
   * The radius of the hitbox around the monster.
   */
  public static final float RADIUS_OF_MONSTER = 1f;

  /**
   * The height of the cylindrical hitbox of the monster.
   */
  private static final float HEIGHT_OF_MONSTER = 4f;

  /**
   * The weight of the monster.
   */
  private static final float WEIGHT_OF_MONSTER = 80f;

  /**
   * Defines the main class.
   */
  private MainRunner main;

  /**
   * Defines the visible two dimensional monster.
   */
  private Node monster;

  /**
   * Provides data assets of a jME3 application.
   */
  private final AssetManager assetManager;

  /**
   * Defines an internal node of a scene graph.
   */
  private Node rootNode;

  /**
   * Defines customized miniMap.
   */
  private UCMiniMap miniMap;

  /**
   * Defines customized terrain.
   */
  private UCTerrain terrain;

  /**
   * The loaded monster model.
   */
  private Spatial monsterModel;

  /**
   * Defines the Control for the monsters.
   */
  private UCMonsterControl monsterControl;

  /**
   * Sets the coord for the monster.
   */
  private int monsterXCoord;

  /**
   * Sets the coord for the monster.
   */
  private int monsterYCoord;

  /**
   * Sets the coord for the monster.
   */
  private int monsterZCoord;

  /**
   * Set boolean to control the number of the initialized monster.
   */
  private boolean monsterExistence = false;

  /**
   * Default constructor initializing mandatory fields.
   * 
   * @param inAssetManager
   *          provides an interface for managing the data assets of a jME3 application.
   * @param inRootNode
   *          defines an internal node of a scene graph. It is used to attach objects to the terrain.
   * @param inMiniMap
   *          defines the costume minimap on the HUD.
   * @param inMain
   *          defines the main class.
   * @param inTerrain
   *          defines the costume terrain for this game.
   */
  protected UCMonster(AssetManager inAssetManager, Node inRootNode, UCMiniMap inMiniMap, MainRunner inMain,
      UCTerrain inTerrain) {
    super();
    assetManager = inAssetManager;
    rootNode = inRootNode;
    monster = new Node();
    miniMap = inMiniMap;
    main = inMain;
    terrain = inTerrain;
    monsterModel = assetManager.loadModel("Models/Wall.j3o");
    monsterControl = new UCMonsterControl(RADIUS_OF_MONSTER, HEIGHT_OF_MONSTER,
            WEIGHT_OF_MONSTER, terrain, assetManager);
  }

  /**
   * This method defines the size of the monster and attach it to the game.
   */
  public void initMonster() {

    ServiceRegistry.getLogProxy().info(this, "initializing Monster", new Object[0]);

    // set the size of the monster
    Box aMonsterMesh = new Box(1f, 1f, 0f);

    // get the geometry for the collisionBox
    Geometry aMonsterGeometry = createMonsterGeometry(aMonsterMesh);

    // get the material for the monster
    Material aMonsterMaterial = createMaterial();

    // set material of monsterGeo
    aMonsterGeometry.setMaterial(aMonsterMaterial);

    // attach geometry to monster
    monster.attachChild(aMonsterGeometry);

    // attach monster to the game
    rootNode.attachChild(monster);

    // shows the playerMarker
    miniMap.showMonsterMarker();

    // shows the playerMarker
    miniMap.showSmileyMonsterMarker();

    // initialize monsterModel
    initMonsterModel();

    // starts movement of the monster.
    startMonsterMovement();
  }

  /**
   * This method defines the texture of the monster. The png-File is the picture that is used for the texture.
   * 
   * @return The optical surface of the monster
   */
  private Material createMaterial() {
    // create material
    Material outMonsterMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    // set blender mode of material
    outMonsterMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);

    // get texture and set the material
    Texture aMonkeyTex = assetManager.loadTexture("Textures/transparent/cookie_monster.png");
    outMonsterMat.setTexture("ColorMap", aMonkeyTex);

    // return the material
    return outMonsterMat;
  }

  /**
   * This method sets the location for the monster. The location of the monster is specified by the coords.
   * 
   * @param inRandomWaypoint
   *          defines the randomized waypoint.
   */
  public void setStartPoint(Vector3f inRandomWaypoint) {
    monsterXCoord = (int) inRandomWaypoint.x;
    monsterYCoord = 1;
    monsterZCoord = (int) inRandomWaypoint.z;
  }

  /**
   * This method creates a geometry with a physical body for the monster. The size of the geometry is specified by the
   * parameter
   * 
   * @param inMonsterMesh
   *          is the size of the monster.
   * @return monsterGeo with a geometry and a physical body
   */
  private Geometry createMonsterGeometry(Box inMonsterMesh) {

    // set monster size.
    Box aMonsterMesh = inMonsterMesh;

    // create geometry with the monster size.
    Geometry outMonsterGeo = new Geometry("A Textured Box", aMonsterMesh);

    // set start position of the geometry.
    outMonsterGeo.setLocalTranslation(monsterXCoord, monsterYCoord, monsterZCoord);

    // set queueBucket of geometry to transparent.
    outMonsterGeo.setQueueBucket(RenderQueue.Bucket.Transparent);

    // create physical body
    RigidBodyControl aMonsterRigidBodyControl = new RigidBodyControl(MASS_OF_MONSTER_CONTROL);

    // add physical body to geometry
    outMonsterGeo.addControl(aMonsterRigidBodyControl);

    // return geometry
    return outMonsterGeo;
  }

  /**
   * This method updates the location of the monsters.
   */
  public void updateMonster() {

    // get current position of the monsterModel.
    Vector3f aMonsterPath = getMonsterModel().getLocalTranslation();

    // give position of monsterModel to monster.
    setMonsterPath(aMonsterPath);
  }

  /**
   * This method sets the new position of the monster.
   * 
   * @param inPosition
   *          is the new calculated position of the monster.
   */
  public void setMonsterPath(Vector3f inPosition) {
    monster.setLocalTranslation(inPosition);
  }

  /**
   * This method returns the current position of the monster.
   * 
   * @return position of the monster.
   */
  public Vector3f getMonsterLocation() {
    return monster.getLocalTranslation();
  }
  
  /**
   * This method sets model for the monster to add a control for it.
   *
   */
  private void initMonsterModel() {

    // set position of monsterModel.
    monsterModel.setLocalTranslation(monsterXCoord, 0, monsterZCoord);

    // create new monsterControl from the MonsterControl class.
    // add monsterControl to physic space.
    main.getStateManager().getState(BulletAppState.class).getPhysicsSpace().add(monsterControl);

    // add monsterControl to monsterModel
    monsterModel.addControl(monsterControl);

    // set gravity of monsterControl to -30f in the Y-axis.
    monsterControl.getGravity(new Vector3f(0f, GRAVITY_VALUE_IN_Y_AXIS, 0f));

    // attach monsterNode to the rootNode.
    rootNode.attachChild(monsterModel);
  }

  /**
   * This method sets the size for the model of the monster.
   * 
   * @param inMonsterSize disired size of the monster.
   */
  public void setMonsterSize(int inMonsterSize){
    monsterModel.scale(inMonsterSize);
  }
  
  /**
   * This method transmits the destination point of the monster to his monsterControl.
   * 
   * @param inRandomWaypoint
   *          is the randomized waypoint.
   */
  public void setDestinationPoint(Vector3f inRandomWaypoint) {
    monsterControl.setMonsterDestinationPoint(inRandomWaypoint);
  }

  /**
   * This method starts the movement of the monster.
   */
  public void startMonsterMovement() {
    
    // sets when the monster starts to follow.
    monsterControl.followPath();
  }

  /**
   * This method returns a Spatial monsterModel.
   * 
   * @return the edited model of the monster.
   */
  public Spatial getMonsterModel() {
    return monsterModel;
  }

}
public final class UCMonsterControl extends BetterCharacterControl {

  /**
   * Defines monster model.
   */
  private Spatial monsterSpatial;

  /**
   * Whether the monster has been initialized.
   */
  private boolean walking;

  /**
   * Defines customized terrain.
   */
  private UCTerrain terrain;

  /**
   * The pathfinder to be able to calculate a path for a monster on the NavMesh.
   */
  private static NavMeshPathfinder _pathFinder;

  /**
   * Sets the X Coordination for the destination point of the monster.
   */
  private float xCoord;

  /**
   * Sets the Y Coordination for the destination point of the monster.
   */
  private float yCoord;

  /**
   * Sets the Z Coordination for the destination point of the monster.
   */
  private float zCoord;

  /**
   * The current path to follow.
   */
  private Queue<Waypoint> currentPath;

  /**
   * The assetManager to load assets.
   */
  private AssetManager assetManager;

  /**
   * Initializes mandatory parameters.
   * 
   * @param inRadius
   *          is the radius of the physical monster body.
   * @param inHeight
   *          is the height of the physical monster body.
   * @param inWeight
   *          is the weight of the physical monster body.
   * @param inTerrain
   *          is the personalized terrain.
   * @param inAssetManager
   *          The manager for loading assets.
   */
  protected UCMonsterControl(float inRadius, float inHeight, float inWeight, UCTerrain inTerrain,
      AssetManager inAssetManager) {

    // super constructor of BetterCharacterControl.
    super(inRadius, inHeight, inWeight);

    // object of the personalized terrain class.
    terrain = inTerrain;

    assetManager = inAssetManager;

    // the pathfinder for the navMesh from the terrain.
    if (_pathFinder == null) {
      _pathFinder = new NavMeshPathfinder(terrain.getNavMesh());
    }// if

  }

  /**
   * Control if the is initialized and set his path.
   * 
   * @param inTimePerFrame
   *          is the updating value.
   */
  @Override
  public void update(float inTimePerFrame) {

    // update super constructor of the BetterCharacterControl.
    super.update(inTimePerFrame);

    // control if walking is true.
    if (walking) {

      // check if currentWaypoint isn't null or not empty.
      if (currentPath != null && currentPath.isEmpty() == false) {

        // set currentWaypoint to the next Waypoint.
        Waypoint aCurrentWaypoint = currentPath.element();

        // set walk direction to the position of the monster spatial.
        Vector3f aVector = aCurrentWaypoint.getPosition().subtract(monsterSpatial.getLocalTranslation()).normalize();
        setWalkDirection(aVector);

        // set walking speed.
        walkDirection.multLocal(5);

        // set view direction to the walk direction.
        setViewDirection(walkDirection.normalize());

        // check if a next waypoint is available. if true the monster walks to the next waypoint.
        if (currentPath.size() == 1
            && monsterSpatial.getLocalTranslation().distance(aCurrentWaypoint.getPosition()) <= 0.5f) {
          currentPath.clear();
          currentPath = null;
          setWalkDirection(Vector3f.ZERO);
          walking = false;
        } // if

        // check if a next waypoint is available. if false the monster stops.
        else if (monsterSpatial.getLocalTranslation().distance(aCurrentWaypoint.getPosition()) <= 0.5f
            && !currentPath.isEmpty()) {
          currentPath.remove();
        } // else-if
      }// if
    }// if
  }

  /**
   * Sets monsterSpatial to the spatial currentMonsterSpatial. {@inheritDoc}
   */
  @Override
  public void setSpatial(Spatial inCurrentMonsterSpatial) {
    super.setSpatial(inCurrentMonsterSpatial);
    if (inCurrentMonsterSpatial != null) {
      monsterSpatial = inCurrentMonsterSpatial;
    }// if
  }

  /**
   * Sets the coordinates for the destination point of the monster.
   * 
   * @param inWaypoint
   *          is the randomized waypoint.
   */
  public void setMonsterDestinationPoint(Vector3f inWaypoint) {
    xCoord = inWaypoint.x;
    yCoord = 0.0f;
    zCoord = inWaypoint.z;
  }

  /**
   * This method gets the current position and calculates the new position for the monster on the NavMesh.
   */
  public void followPath() {

    // set walking to true.
    walking = true;

    // set the position of the object to the location of the monsterSpatial.
    _pathFinder.setPosition(monsterSpatial.getLocalTranslation());

    // set position of the selected destination point.
    _pathFinder.computePath(new Vector3f(xCoord, yCoord, zCoord));

    currentPath = new LinkedBlockingQueue<Path.Waypoint>(_pathFinder.getPath().getWaypoints());

    // this for-loop is used to display and control the selected waypoints.
    for (Waypoint aCurrentWaypoint : currentPath) {
      /* A colored lit cube. Needs light source! */
      Box aBoxMesh = new Box(0.5f, 1f, 0.5f);
      Geometry aBoxGeo = new Geometry("Colored Box", aBoxMesh);
      Material aBoxMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
      aBoxMat.setBoolean("UseMaterialColors", true);
      ColorRGBA aColour = new ColorRGBA(MonsterRegistryUtils.getInstance().getMonsterList().size()*30f, 255f, 255f, 255f);
      aBoxMat.setColor("Ambient", aColour);
      aBoxMat.setColor("Diffuse", aColour);
      aBoxGeo.setMaterial(aBoxMat);

      // set position to waypoint position.
      aBoxGeo.setLocalTranslation(aCurrentWaypoint.getPosition());

      terrain.getRootNode().attachChild(aBoxGeo);
    }// for
  }
}

Hi Had had a similar issue
I solved by creating
MyNavMeshPathfinder extends NavMeshPathfinder
which is just a exact copy of NavMeshPathfinder
except i changed
heap.initialize(SessionMap.getSessionID(sessionID), startPos);
in
private boolean buildNavigationPath(Path navPath,
Cell startCell, Vector3f startPos,
Cell endCell, Vector3f endPos,
float entityRadius, DebugInfo debugInfo) {

which just give the pathFinder a unique sessionID across multiple threads

1 Like

@dharshanar
Thx for the great answer.

Could you explain how you’ve created your MyNavMeshPathfinder class? I’ve tried it by my self but fail because the NavMeshPathfinder needs some package protected classes and methods. Did you just copied all the package protected stuff from the navmesh package into your package or is there a better way?
Would appreciate it if someone could help me :slight_smile:

@dharshanar
OK I managed to create the MyNavMeshPathfinder.
You said I should change
heap.initialize(sessionID, startPos);
to
heap.initialize(SessionMap.getSessionID(sessionID), startPos);
but I couldn’t find SessionMap. From where did you get it ???

Again I would really appreciate it if I could get some help :slight_smile: