Some troubles with NavMeshGenerator

Hi,

I’m currently new with Jme3. Before build a complete game, I want to test many features.

I successed show a terrain, add physics and move a character on it with keyboard.
Now I want to move the caracter by clicking on target location.

I read that topic : AI plugin now with NavMesh pathfinding
I tried to do this but unsuccessfull.
I think my problem is located on NavMesh generation.
I use : __jmonkeyengine.googlecode.com/svn/trunk/sdk/jme3-navmesh-gen/src/com/jme3/gde/nmgen/NavMeshGenerator.java
And my terrain is mountains512.png.

I althrought use this piece of code :

public Geometry createNavMesh(Node node) {
		Mesh mesh = new Mesh();
		NavMesh navMesh = new NavMesh();
		NavMeshGenerator generator = new NavMeshGenerator();
		generator.setCellHeight(1f);

		GeometryBatchFactory.mergeGeometries(findGeometries(node, new LinkedList<Geometry>()), mesh);
		Mesh optiMesh = generator.optimize(mesh);

		navMesh.loadFromMesh(optiMesh);

		Geometry navGeom = new Geometry("NavMesh");
		navGeom.setMesh(optiMesh);
		Material green = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
		green.setColor("Color", ColorRGBA.Green);
		green.getAdditionalRenderState().setWireframe(true);
		navGeom.setMaterial(green);

		return navGeom;
	} 

Here is the result :
SCREENSHOT

I think there isn’t enough triangles no ?
Thanks for help, and sorry for my english :frowning:

Edit : here is my entire code

MyGame.java

package fr.flow10000.akkad;

import com.jme3.ai.navmesh.NavMesh;
import com.jme3.ai.navmesh.NavMeshPathfinder;
import com.jme3.ai.navmesh.Path.Waypoint;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;

import fr.flow10000.akkad.camera.MyCamera;
import fr.flow10000.akkad.utils.NavMeshUtils;

public class MyGame extends SimpleApplication implements ActionListener {
	
	private static final float FORWARD_SPEED = 10;
	private static final float BACKWARD_SPEED = 4;
	private static final float ROTATE_SPEED = .0006f;

	private MyCamera myCamera;
	
	private TerrainQuad terrain;
	private NavMesh navMesh;
	
	private Spatial player;

	private BetterCharacterControl playerControl;
	
	private Node playerNode;
	private Node terrainNode;
	
	private BulletAppState bulletAppState;
	
	private NavMeshPathfinder pathfinder = null;
	
	private Vector3f walkDirection = new Vector3f(0, 0, 0);
	private Vector3f viewDirection = new Vector3f(0, 0, 1);
	private boolean rotateLeft = false, rotateRight = false, forward = false, backward = false, walk = false;
	
	
	@Override
	public void simpleInitApp() {

		Material genericSpacialMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
		genericSpacialMaterial.setColor("Color", ColorRGBA.Blue);
		
		Material materialPlayer = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
		
		player = assetManager.loadModel("player.j3o");
		player.setMaterial(materialPlayer);
		
		playerNode = new Node();
		playerNode.attachChild(player);
		
		terrainNode = new Node();
		initTerrain(); // load terrain (mountains512)
		initNav(); // build navmesh
		
		rootNode.attachChild(playerNode);
		rootNode.attachChild(terrainNode);
		
		initPhysics();
		
		initKeys();
		
		initCamera();
		
//		bulletAppState.getPhysicsSpace().enableDebug(assetManager);
	}

	

	public void onAction(String name, boolean isPressed, float tpf) {
		
		if (name.equals("Rotate Left")) {
			rotateLeft = isPressed;
			walk = false;

		} else if (name.equals("Rotate Right")) {
			rotateRight = isPressed;
			walk = false;

		} else if (name.equals("Forward")) {
			forward = isPressed;
			walk = false;

		} else if (name.equals("Back")) {
			backward = isPressed;
			walk = false;

		} else if (name.equals("Generic Mouse Action") && isPressed) {
			this.onGenericMouseAction();
		}
		
		
	}
	
	
	private void onGenericMouseAction() {

		CollisionResults results = new CollisionResults();
		Vector2f click2d = inputManager.getCursorPosition();
		Vector3f click3d = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
		Vector3f dir = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d).normalizeLocal();
		Ray ray = new Ray(click3d, dir);
		rootNode.collideWith(ray, results);

		if (results.size() > 0) {

			CollisionResult result = results.getClosestCollision();

			Geometry target = result.getGeometry();

			if (target.getName().startsWith("TERRAIN")) {

				Vector3f startPoint = new Vector3f(player.getWorldTranslation());
				Vector3f targetPoint = new Vector3f(result.getContactPoint());

				pathfinder = new NavMeshPathfinder(navMesh);
				pathfinder.setPosition(startPoint);
				pathfinder.computePath(targetPoint);

				walk = true;
			} 

		} else {
			System.out.println(">>> NOTHING");
		}
	}
	
	@Override
	public void simpleUpdate(float tpf) {

		if (walk) {

			Waypoint waypoint = pathfinder.getNextWaypoint();

			if (waypoint == null) {
				walk = false;
				return;
			}

			Vector3f vector = waypoint.getPosition().subtract(player.getWorldTranslation());

			if (!(vector.length() < 1)) {

				player.move(vector.normalize().multLocal(FORWARD_SPEED * tpf));

			} else if (!pathfinder.isAtGoalWaypoint()) {
				pathfinder.goToNextWaypoint();
			} else {

				walk = false;
			}

			return; 
		}


		Vector3f modelForwardDir = cam.getDirection();

		walkDirection.set(0, 0, 0);
		if (forward) {
			walkDirection.addLocal(modelForwardDir.mult(FORWARD_SPEED));
			
		} else if (backward) {

			walkDirection.addLocal(modelForwardDir.mult(BACKWARD_SPEED).negate());
		}
		playerControl.setWalkDirection(walkDirection);
		if (rotateLeft) {
			Quaternion rotateL = new Quaternion().fromAngleAxis(ROTATE_SPEED, Vector3f.UNIT_Y);
			rotateL.multLocal(viewDirection);
			myCamera.onRotatePlayer(-ROTATE_SPEED);
		} else if (rotateRight) {
			Quaternion rotateR = new Quaternion().fromAngleAxis(-ROTATE_SPEED, Vector3f.UNIT_Y);
			rotateR.multLocal(viewDirection);
			myCamera.onRotatePlayer(ROTATE_SPEED);
		}

		playerControl.setViewDirection(viewDirection);
	}
	
	private void initKeys() {
		inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_Z));
		inputManager.addMapping("Back", new KeyTrigger(KeyInput.KEY_S));
		inputManager.addMapping("Rotate Left", new KeyTrigger(KeyInput.KEY_Q));
		inputManager.addMapping("Rotate Right", new KeyTrigger(KeyInput.KEY_D));

		inputManager.addMapping("Generic Mouse Action", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

		inputManager.addListener(this, "Rotate Left", "Rotate Right");
		inputManager.addListener(this, "Forward", "Back");

		inputManager.addListener(this, "Generic Mouse Action");
	}
	
	
	private void initTerrain() {

		Material mat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
		mat.setTexture("Alpha", assetManager.loadTexture("terrain/alphamap.png"));

		Texture grass = assetManager.loadTexture("terrain/grass.jpg");
		grass.setWrap(WrapMode.Repeat);
		mat.setTexture("Tex1", grass);
		mat.setFloat("Tex1Scale", 64f);

		Texture dirt = assetManager.loadTexture("terrain/dirt.jpg");
		dirt.setWrap(WrapMode.Repeat);
		mat.setTexture("Tex2", dirt);
		mat.setFloat("Tex2Scale", 32f);

		Texture rock = assetManager.loadTexture("terrain/road.jpg");
		rock.setWrap(WrapMode.Repeat);
		mat.setTexture("Tex3", rock);
		mat.setFloat("Tex3Scale", 128f);

		AbstractHeightMap heightmap = null;
		Texture heightMapImage = assetManager.loadTexture("terrain/mountains512_2.png");

		heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
		heightmap.load();

		int patchSize = 65;
		terrain = new TerrainQuad("TERRAIN_", patchSize, 513, heightmap.getHeightMap());

		terrain.setMaterial(mat);
		terrain.setLocalTranslation(0, -150, 0);

		terrainNode.attachChild(terrain);

		NavMeshUtils nmUtils = new NavMeshUtils(assetManager);
		navMesh = nmUtils.buildNavMesh(terrainNode);
	}
	
	private void initNav(){
		NavMeshUtils nmUtils = new NavMeshUtils(assetManager);
		navMesh = nmUtils.buildNavMesh(terrainNode);
		
		Geometry navGeometry = nmUtils.createNavMesh(terrainNode);
		rootNode.attachChild(navGeometry);
	}
	
	private void initCamera() {

		flyCam.setEnabled(false);
		myCamera = new MyCamera(cam, player, inputManager);
		myCamera.setSmoothMotion(false);
		myCamera.setInvertVerticalAxis(true);
	}
	
	private void initPhysics(){
		bulletAppState = new BulletAppState();
		bulletAppState.setDebugEnabled(true);
		stateManager.attach(bulletAppState);

		RigidBodyControl floorControl = new RigidBodyControl(0f);
		playerControl = new BetterCharacterControl(1.5f, 4, 30f);

		playerNode.addControl(playerControl);
		terrainNode.addControl(floorControl);

		bulletAppState.getPhysicsSpace().add(playerNode);
		bulletAppState.getPhysicsSpace().add(terrainNode);
	}
	
	
	public static void main(String[] args) {
		MyGame game = new MyGame();
		game.start();
	}
}

NavMeshUtils.java

package fr.flow10000.akkad.utils;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import jme3tools.optimize.GeometryBatchFactory;

import com.jme3.ai.navmesh.NavMesh;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

public class NavMeshUtils {

	private AssetManager assetManager;
	
	public NavMeshUtils(AssetManager assetManager){
		this.assetManager = assetManager;
	}
	
	public NavMesh buildNavMesh(Node node){
		Mesh mesh = new Mesh();
		NavMesh navMesh = new NavMesh();
		NavMeshGenerator generator = new NavMeshGenerator();
		generator.setCellHeight(1f);

		GeometryBatchFactory.mergeGeometries(findGeometries(node, new LinkedList<Geometry>()), mesh);
		Mesh optiMesh = generator.optimize(mesh);

		navMesh.loadFromMesh(optiMesh);
		return navMesh;
	}
	
	public Geometry createNavMesh(Node node) {
		Mesh mesh = new Mesh();
		NavMesh navMesh = new NavMesh();
		NavMeshGenerator generator = new NavMeshGenerator();
		generator.setCellHeight(1f);

		GeometryBatchFactory.mergeGeometries(findGeometries(node, new LinkedList<Geometry>()), mesh);
		Mesh optiMesh = generator.optimize(mesh);

		navMesh.loadFromMesh(optiMesh);

		Geometry navGeom = new Geometry("NavMesh");
		navGeom.setMesh(optiMesh);
		Material green = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
		green.setColor("Color", ColorRGBA.Green);
		green.getAdditionalRenderState().setWireframe(true);
		navGeom.setMaterial(green);

		return navGeom;
	}
	
	private List<Geometry> findGeometries(Node node, List<Geometry> geoms) {
        for (Iterator<Spatial> it = node.getChildren().iterator(); it.hasNext();) {
            Spatial spatial = it.next();
            if (spatial instanceof Geometry) {
                geoms.add((Geometry) spatial);
            } else if (spatial instanceof Node) {
                findGeometries((Node) spatial, geoms);
            }
        }
        return geoms;
    }
}

I don’t know how to help you :frowning: but I can say that the image link is broken.

Fixed link, thanks

Are you, maybe, scaling the scene but not the navmesh or vice-versa after the navmesh generation?. It would be great to see your test case.

You can also generate the navmesh directly on the scene composer (right clicking on the node you want to generate it for).

No, I only translate it, but same result without translation.
I posted my entire code.

Thanks for help anyway :slight_smile:

Sorry for double post.
This is not a NavMeshGenerator problem. I found many bugs in my code. After fix them I will post corrections and explain them.
One of the one bug to fix : pathfinder works only one time, after that it never find any path…

Great, once you have it, if you can upload your full test project it would be easier to find where is the problem.

I finally successed :smile:
Here is the complete code :

Mistakes I made :

  1. Player position
    I use a BetterCharacterControl.
    So to move player, I can’t do :

    player.move(vector.normalize().multLocal(FORWARD_SPEED * tpf));

I have to do :

walkDirection.addLocal(vector.normalize().mult(FORWARD_SPEED));
playerControl.setWalkDirection(walkDirection);

And for get the player position I have to use

playerNode.getLocalTranslation()
  1. Navmesh
    After using the navmesh for pathfinding, you have to regenerate it from the mesh.
    So I updated my NavMeshUtils to get the mesh.

I thinks it’s done for this thread, but I will be happy if someone can review my code. I i wouldn’t like to get bad habits.

I hope this thread helps someone
Thanks