jme3-libraries-ai, path finding and navmesh

Running the below code gives this output:

run:
Nov 06, 2015 2:14:38 PM java.util.prefs.WindowsPreferences <init>
WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
Nov 06, 2015 2:14:40 PM com.jme3.system.JmeDesktopSystem initialize
INFO: Running on jMonkeyEngine 3.1-5281
 * Branch: master
 * Git Hash: 2a1addd
 * Build Date: 2015-10-26
Nov 06, 2015 2:14:40 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: LWJGL 2.9.3 context running on thread jME3 Main
 * Graphics Adapter: nvd3dumx,nvwgf2umx,nvwgf2umx,nvwgf2umx
 * Driver Version: 10.18.13.5850
 * Scaling Factor: 1
Nov 06, 2015 2:14:40 PM com.jme3.renderer.opengl.GLRenderer loadCapabilitiesCommon
INFO: OpenGL Renderer Information
 * Vendor: NVIDIA Corporation
 * Renderer: GeForce GTX 960/PCIe/SSE2
 * OpenGL Version: 4.5.0 NVIDIA 358.50
 * GLSL Version: 4.50 NVIDIA
 * Profile: Compatibility
Nov 06, 2015 2:14:40 PM net.java.games.input.DefaultControllerEnvironment getControllers
WARNING: Found unknown Windows version: Windows 8.1
Nov 06, 2015 2:14:40 PM net.java.games.input.DefaultControllerEnvironment getControllers
INFO: Attempting to use default windows plug-in.
Nov 06, 2015 2:14:40 PM net.java.games.input.DefaultControllerEnvironment getControllers
INFO: Loading: net.java.games.input.DirectAndRawInputEnvironmentPlugin
Nov 06, 2015 2:14:41 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
INFO: Audio Renderer Information
 * Device: OpenAL Soft
 * Vendor: OpenAL Community
 * Renderer: OpenAL Soft
 * Version: 1.1 ALSOFT 1.15.1
 * Supported channels: 64
 * ALC extensions: ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE ALC_EXT_DEDICATED ALC_EXT_disconnect ALC_EXT_EFX ALC_EXT_thread_local_context ALC_SOFT_loopback
 * AL extensions: AL_EXT_ALAW AL_EXT_DOUBLE AL_EXT_EXPONENT_DISTANCE AL_EXT_FLOAT32 AL_EXT_IMA4 AL_EXT_LINEAR_DISTANCE AL_EXT_MCFORMATS AL_EXT_MULAW AL_EXT_MULAW_MCFORMATS AL_EXT_OFFSET AL_EXT_source_distance_model AL_LOKI_quadriphonic AL_SOFT_buffer_samples AL_SOFT_buffer_sub_data AL_SOFTX_deferred_updates AL_SOFT_direct_channels AL_SOFT_loop_points AL_SOFT_source_latency
Nov 06, 2015 2:14:41 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
WARNING: Pausing audio device not supported.
Nov 06, 2015 2:14:41 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
INFO: Audio effect extension version: 1.0
Nov 06, 2015 2:14:41 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
INFO: Audio max auxiliary sends: 4
Warning, normal of the plane faces downward!!!
Warning, normal of the plane faces downward!!!
---------------- 8< ----------------
Warning, normal of the plane faces downward!!!
Warning, normal of the plane faces downward!!!
getNumCells 80 getTriangleCount 160
BUILD SUCCESSFUL (total time: 9 seconds)

So that spat out 80 errors of “Warning, normal of the plane faces downward!!!”, created 80 cells in the navmesh, 160 triangles in the sphere.

package mygame;

import com.jme3.ai.navmesh.NavMesh;
import com.jme3.ai.navmesh.NavMeshPathfinder;
import com.jme3.app.SimpleApplication;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.shape.Sphere;

public class Main extends SimpleApplication {
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {

        Sphere test = new Sphere(10,10,10);
                
        NavMesh navMesh = new NavMesh(test);
        NavMeshPathfinder navi = new NavMeshPathfinder(navMesh);
        navi.setPosition(Vector3f.ZERO);
        
        System.out.println("getNumCells " + navMesh.getNumCells() + " getTriangleCount " + test.getTriangleCount());

    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}

Using a Quad rather than a sphere

        Quad test = new Quad(10, 10);

gives me this output:

-------- 8< --------
INFO: Audio max auxiliary sends: 4
Warning, normal of the plane faces downward!!!
Warning, normal of the plane faces downward!!!
getNumCells 0 getTriangleCount 2
BUILD SUCCESSFUL (total time: 14 seconds)

I don’t know how to rotate a quad so its normal is facing up rather than sideways…

Here is my test program, it gives me this output

run:
Nov 06, 2015 5:29:18 PM java.util.prefs.WindowsPreferences <init>
WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
Nov 06, 2015 5:29:20 PM com.jme3.system.JmeDesktopSystem initialize
INFO: Running on jMonkeyEngine 3.1-5281
 * Branch: master
 * Git Hash: 2a1addd
 * Build Date: 2015-10-26
Nov 06, 2015 5:29:20 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: LWJGL 2.9.3 context running on thread jME3 Main
 * Graphics Adapter: nvd3dumx,nvwgf2umx,nvwgf2umx,nvwgf2umx
 * Driver Version: 10.18.13.5850
 * Scaling Factor: 1
Nov 06, 2015 5:29:20 PM com.jme3.renderer.opengl.GLRenderer loadCapabilitiesCommon
INFO: OpenGL Renderer Information
 * Vendor: NVIDIA Corporation
 * Renderer: GeForce GTX 960/PCIe/SSE2
 * OpenGL Version: 4.5.0 NVIDIA 358.50
 * GLSL Version: 4.50 NVIDIA
 * Profile: Compatibility
Nov 06, 2015 5:29:20 PM net.java.games.input.DefaultControllerEnvironment getControllers
WARNING: Found unknown Windows version: Windows 8.1
Nov 06, 2015 5:29:20 PM net.java.games.input.DefaultControllerEnvironment getControllers
INFO: Attempting to use default windows plug-in.
Nov 06, 2015 5:29:20 PM net.java.games.input.DefaultControllerEnvironment getControllers
INFO: Loading: net.java.games.input.DirectAndRawInputEnvironmentPlugin
Nov 06, 2015 5:29:21 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
INFO: Audio Renderer Information
 * Device: OpenAL Soft
 * Vendor: OpenAL Community
 * Renderer: OpenAL Soft
 * Version: 1.1 ALSOFT 1.15.1
 * Supported channels: 64
 * ALC extensions: ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE ALC_EXT_DEDICATED ALC_EXT_disconnect ALC_EXT_EFX ALC_EXT_thread_local_context ALC_SOFT_loopback
 * AL extensions: AL_EXT_ALAW AL_EXT_DOUBLE AL_EXT_EXPONENT_DISTANCE AL_EXT_FLOAT32 AL_EXT_IMA4 AL_EXT_LINEAR_DISTANCE AL_EXT_MCFORMATS AL_EXT_MULAW AL_EXT_MULAW_MCFORMATS AL_EXT_OFFSET AL_EXT_source_distance_model AL_LOKI_quadriphonic AL_SOFT_buffer_samples AL_SOFT_buffer_sub_data AL_SOFTX_deferred_updates AL_SOFT_direct_channels AL_SOFT_loop_points AL_SOFT_source_latency
Nov 06, 2015 5:29:21 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
WARNING: Pausing audio device not supported.
Nov 06, 2015 5:29:21 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
INFO: Audio effect extension version: 1.0
Nov 06, 2015 5:29:21 PM com.jme3.audio.openal.ALAudioRenderer initOpenAL
INFO: Audio max auxiliary sends: 4
Warning, normal of the plane faces downward!!!
Warning, normal of the plane faces downward!!!
---------------- 8< ---------------- x 1084
Warning, normal of the plane faces downward!!!
Warning, normal of the plane faces downward!!!
computePath true  numwaypoints 5
position (10.0, 10.0, 10.0) cell Cell: 9.666667,10.666667position (10.0, 1.9259478E-6, 10.0)
position (10.0, 1.9259478E-6, 10.0) cell Cell: 9.666667,10.666667position (10.0, 1.9259478E-6, 10.0)
position (63.0, 5.3403233E-22, 63.0) cell Cell: 63.666668,62.666668position (63.0, 5.3403127E-22, 63.0)
position (160.0, 10.0, 160.0) cell Cell: 63.666668,63.333332position (160.0, NaN, 160.0)
position (160.0, NaN, 160.0) cell Cell: 63.666668,63.333332position (160.0, NaN, 160.0)
BUILD SUCCESSFUL (total time: 53 seconds)

5 waypoints, one with Not a number as its y value. Perhaps this is an issue with the library and its compatibility with jme3.1 alpha.

package mygame;

import com.jme3.ai.navmesh.NavMesh;
import com.jme3.ai.navmesh.NavMeshPathfinder;
import com.jme3.ai.navmesh.Path;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import jme3tools.optimize.GeometryBatchFactory;


/**
 * test
 *
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    private TerrainQuad terrain;
    Material mat_terrain;

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {

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

        Texture rock = assetManager.loadTexture("Textures/grid8.png");
        rock.setWrap(Texture.WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 512f);

        Texture heightMapImage = assetManager.loadTexture("Textures/small.png");

        AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();
        heightmap.smooth(1f);

        int patchSize = 129;
        terrain = new TerrainQuad("my terrain", patchSize, 129, heightmap.getHeightMap());

        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, 0, 0);
        terrain.setLocalScale(1, 1, 1);

        rootNode.attachChild(terrain);

        Mesh mesh = new Mesh();
        GeometryBatchFactory.mergeGeometries(findGeometries(terrain, new LinkedList<Geometry>()), mesh);
        
//        LinkedList<Geometry> foo = new LinkedList<Geometry>();
//        GeometryBatchFactory.gatherGeoms(terrain, foo);  
//        GeometryBatchFactory.mergeGeometries(foo, mesh);

        NavMesh navMesh = new NavMesh(mesh);
        NavMeshPathfinder navi = new NavMeshPathfinder(navMesh);
        navi.setPosition(new Vector3f(10, 10, 10));
        navi.setEntityRadius(1);

        boolean computePath = navi.computePath(new Vector3f(160, 10, 160));
        System.out.println("computePath " + computePath + "  numwaypoints " + navi.getPath().getWaypoints().size());

        Iterator it = navi.getPath().iterator();

        Vector3f previous = null;

        while (it.hasNext()) {
            Path.Waypoint wp = (Path.Waypoint) it.next();

            System.out.print("position " + wp.getPosition() + " cell " + wp.getCell());

            Vector3f blaa = wp.getPosition().setY(terrain.getHeight(new Vector2f(wp.getPosition().getX(), wp.getPosition().getZ())));

            System.out.println("position " + blaa);

            Spatial lookatme = new Geometry("lookat", new Sphere(100, 100, 5));
            Material mat_brick = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
            mat_brick.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
            lookatme.setMaterial(mat_brick);
            lookatme.setLocalTranslation(blaa);

            if (previous != null) {
                Line line = new Line(previous, blaa);

                Geometry drawLine = new Geometry("aline", line);

                Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
                mat.setColor("Color", ColorRGBA.Blue);
                drawLine.setMaterial(mat);

                rootNode.attachChild(drawLine);
            }

            previous = blaa;

            rootNode.attachChild(lookatme);
        }

        flyCam.setMoveSpeed(500);

    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    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;
    }

}

That sounds reasonable, since 80 triangles of 160 triangles in that Sphere point “up”.

So this library just gives you all the triangles that face up? It’s kind of lame - usually you could define the maximum angle in the Navmesh generator (very steep faces you can not walk on).

I still wonder why it’s giving you so many warnings with the terrain, since all normals should be pointing up in that case…

You could try it in jME 3.0 and see if that works.

I found out that if you select a different install directory (like “jmonkey_3_0” and “jmonkey_3_1”) those two versions can be used in parallel on your computer.
I think @normen will agree that it’s possible to have them together without problems.

I modified the error to spit out the normal its moaning about - on a smaller terrain:

I am getting 1084 warnings, and 32768 successes.

I am not 100% sure exactly whats being tested here:

public void loadFromMesh(Mesh mesh) {
        clear();
        int errorcount = 0;
        int successcount = 0;
        Vector3f a = new Vector3f();
        Vector3f b = new Vector3f();
        Vector3f c = new Vector3f();

        Plane up = new Plane();
        up.setPlanePoints(Vector3f.UNIT_X, Vector3f.ZERO, Vector3f.UNIT_Z);
        up.getNormal();

        IndexBuffer ib = mesh.getIndexBuffer();
        FloatBuffer pb = mesh.getFloatBuffer(Type.Position);
        pb.clear();
        for (int i = 0; i < mesh.getTriangleCount() * 3; i += 3) {
            int i1 = ib.get(i + 0);
            int i2 = ib.get(i + 1);
            int i3 = ib.get(i + 2);
            BufferUtils.populateFromBuffer(a, pb, i1);
            BufferUtils.populateFromBuffer(b, pb, i2);
            BufferUtils.populateFromBuffer(c, pb, i3);

            Plane p = new Plane();
            p.setPlanePoints(a, b, c);

            if (up.pseudoDistance(p.getNormal()) <= 0.0f) {
                errorcount++;
                System.out.println(errorcount + " Warning, normal of the plane faces downward!!! up " + up + " p.getNormal() " + p.getNormal());
                continue;
            }
            successcount++;
            System.out.println(successcount + " normal of the plane doesn't face downward!!! up " + up + " p.getNormal() " + p.getNormal());

            addFace(a, b, c);
        }

        linkCells();
    }

We set up a plane “up” on 3 points 1,0,0 0,0,0 and 0,0,1 and this gives us a plane with a normal 0,1,0 .
Then we grab three indices from the mesh and make a plane “p” based on that.
We test the distance from the plane “up” to the normal of plane “p” and its its > 0 we add it to the list of cells we can consider for pathfinding.
We do the above for all triangles in the mesh.

Doesn’t this mean that any triangles whose normal is a point below the “up” plane won’t be considered despite its direction?

No, the code seems okay.
The normal should be a vector of unit length and if it points less then (or equal - that’s why your cube had 10 and not only 2 faces rejected) then the normal is pointing sideways or downward.

Is this little black image your terrain heightmap?
What happens if you take dark gray and some light gray stripes on it?
What happens if you take gray and some black stripes on it?

How can it be that there are terrain faces pointing sideways or downward… ?
What you can do: use a wireframe mode view of your terrain:
http://wiki.jmonkeyengine.org/doku.php/jme3:advanced:debugging
→ search “Example: Toggle Wireframe on the scene”
→ then have a look on your terrain

Question: Do you have scale on your terrain? (getLocalScale should give you (1,1,1) if not)

And even if you have a scale factor != 1, it should not matter.
The explanation is probably something else.
I would try the wireframe thing first and look around to find triangles in the terrain that point sideways or downward.

So, after a break, I downloaded jMonkeyEngine 3.0, installed the jME3 AI Library from the plugins. Then I tried to make the simplest NavMesh on a terrain that I could, and see what happens.

Its kinda sorta working, but I am not sure its generating the NavMesh properly - the pathfinding is pretty much ignoring hills. I am just plain out of ideas :confused:

Here is a screenshot of my simple terrain, the green sphere is the starting vector and the red sphere is the end vector. I draw a blue line between all the waypoints (of which there are always 4 - 2 at the start vector and 2 at the end vector). I snagged the code from the picking tutorial so when I click the waypoints are recalculated and the red sphere placed where I clicked:

Here is the log:

run:
Nov 18, 2015 8:36:43 PM java.util.prefs.WindowsPreferences <init>
WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
Nov 18, 2015 8:36:45 PM com.jme3.system.JmeDesktopSystem initialize
INFO: Running on jMonkeyEngine 3.0.10
Nov 18, 2015 8:36:45 PM com.jme3.system.Natives extractNativeLibs
INFO: Extraction Directory: C:\Users\Jacob\Documents\jMonkeyEngine Projects\navmeshtest3.0
Nov 18, 2015 8:36:46 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: Lwjgl 2.9.0 context running on thread LWJGL Renderer Thread
Nov 18, 2015 8:36:46 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: Adapter: nvd3dumx,nvwgf2umx,nvwgf2umx,nvwgf2umx
Nov 18, 2015 8:36:46 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: Driver Version: 10.18.13.5891
Nov 18, 2015 8:36:46 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: Vendor: NVIDIA Corporation
Nov 18, 2015 8:36:46 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: OpenGL Version: 4.5.0 NVIDIA 358.91
Nov 18, 2015 8:36:46 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: Renderer: GeForce GTX 960/PCIe/SSE2
Nov 18, 2015 8:36:46 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
INFO: GLSL Ver: 4.50 NVIDIA
Nov 18, 2015 8:36:46 PM com.jme3.asset.AssetConfig loadText
WARNING: Cannot find loader com.jme3.scene.plugins.blender.BlenderModelLoader
WARNING: Found unknown Windows version: Windows 8
Attempting to use default windows plug-in.
Loading: net.java.games.input.DirectAndRawInputEnvironmentPlugin
Nov 18, 2015 8:36:46 PM com.jme3.audio.lwjgl.LwjglAudioRenderer initInThread
INFO: Audio Device: OpenAL Soft
Nov 18, 2015 8:36:46 PM com.jme3.audio.lwjgl.LwjglAudioRenderer initInThread
INFO: Audio Vendor: OpenAL Community
Nov 18, 2015 8:36:46 PM com.jme3.audio.lwjgl.LwjglAudioRenderer initInThread
INFO: Audio Renderer: OpenAL Soft
Nov 18, 2015 8:36:46 PM com.jme3.audio.lwjgl.LwjglAudioRenderer initInThread
INFO: Audio Version: 1.1 ALSOFT 1.15.1
Nov 18, 2015 8:36:46 PM com.jme3.audio.lwjgl.LwjglAudioRenderer initInThread
INFO: AudioRenderer supports 64 channels
Nov 18, 2015 8:36:46 PM com.jme3.audio.lwjgl.LwjglAudioRenderer initInThread
INFO: Audio effect extension version: 1.0
Nov 18, 2015 8:36:46 PM com.jme3.audio.lwjgl.LwjglAudioRenderer initInThread
INFO: Audio max auxilary sends: 4
Warning, normal of the plane faces downward!!!
Warning, normal of the plane faces downward!!!
-------- 8< MANY ERRORS --------
Warning, normal of the plane faces downward!!!
Warning, normal of the plane faces downward!!!
Navigating to (-33.63971, 0.0, -14.722342)

---------------------- drawing the path to (-33.63971, 0.0, -14.722342) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-33.63971, 0.0, -14.722342)
  -- drawing line from (-33.63971, 0.0, -14.722342) to (-33.63971, 0.0, -14.722342)
Navigating to (0.6174187, 1.4975667E-6, -19.15402)

---------------------- drawing the path to (0.6174187, 1.4975667E-6, -19.15402) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (0.6174187, 1.4975667E-6, -19.15402)
  -- drawing line from (0.6174187, 1.4975667E-6, -19.15402) to (0.6174187, 1.4975667E-6, -19.15402)
Navigating to (6.468271, 0.0, 3.6891866)

---------------------- drawing the path to (6.468271, 0.0, 3.6891866) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (6.468271, 0.0, 3.6891866)
  -- drawing line from (6.468271, 0.0, 3.6891866) to (6.468271, 0.0, 3.6891866)
Navigating to (-10.664963, 0.0032653809, 21.562746)

---------------------- drawing the path to (-10.664963, 0.0032653809, 21.562746) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-10.664963, 0.0032653809, 21.562746)
  -- drawing line from (-10.664963, 0.0032653809, 21.562746) to (-10.664963, 0.0032653809, 21.562746)
Navigating to (-17.053392, 0.0, 35.465214)

---------------------- drawing the path to (-17.053392, 0.0, 35.465214) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-17.053392, 0.0, 35.465214)
  -- drawing line from (-17.053392, 0.0, 35.465214) to (-17.053392, 0.0, 35.465214)
Navigating to (-28.099236, 0.0, 27.934511)

---------------------- drawing the path to (-28.099236, 0.0, 27.934511) ----------------------
computePath true  numwaypoints 5
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-27.0, -0.0, 27.0)
  -- drawing line from (-27.0, -0.0, 27.0) to (-28.099236, 0.0, 27.934511)
  -- drawing line from (-28.099236, 0.0, 27.934511) to (-28.099236, 0.0, 27.934511)
Navigating to (-36.994263, 0.0, 17.938808)

---------------------- drawing the path to (-36.994263, 0.0, 17.938808) ----------------------
computePath true  numwaypoints 5
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-17.0, 0.07108292, 18.0)
  -- drawing line from (-17.0, 0.07108292, 18.0) to (-36.994263, 0.0, 17.938808)
  -- drawing line from (-36.994263, 0.0, 17.938808) to (-36.994263, 0.0, 17.938808)
Navigating to (-17.046722, 14.656652, 9.358051)

---------------------- drawing the path to (-17.046722, 14.656652, 9.358051) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-17.046722, 14.656652, 9.358051)
  -- drawing line from (-17.046722, 14.656652, 9.358051) to (-17.046722, 14.656652, 9.358051)
Navigating to (-16.151163, 15.918786, 8.657517)

---------------------- drawing the path to (-16.151163, 15.918786, 8.657517) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-16.151163, 15.918786, 8.657517)
  -- drawing line from (-16.151163, 15.918786, 8.657517) to (-16.151163, 15.918786, 8.657517)
Navigating to (-23.259264, 1.1920929E-7, -2.3510997)

---------------------- drawing the path to (-23.259264, 1.1920929E-7, -2.3510997) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-23.259264, 1.1920929E-7, -2.3510997)
  -- drawing line from (-23.259264, 1.1920929E-7, -2.3510997) to (-23.259264, 1.1920929E-7, -2.3510997)
Navigating to (-40.682274, 0.0, -12.367432)

---------------------- drawing the path to (-40.682274, 0.0, -12.367432) ----------------------
computePath true  numwaypoints 4
  -- drawing line from (0.0, 1.0, 0.0) to (0.0, 1.0, 0.0)
  -- drawing line from (0.0, 1.0, 0.0) to (-40.682274, 0.0, -12.367432)
  -- drawing line from (-40.682274, 0.0, -12.367432) to (-40.682274, 0.0, -12.367432)
BUILD SUCCESSFUL (total time: 7 minutes 42 seconds)

Here is my code:

package mygame;

import com.jme3.ai.navmesh.NavMesh;
import com.jme3.ai.navmesh.NavMeshPathfinder;
import com.jme3.ai.navmesh.Path;
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
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.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import jme3tools.optimize.GeometryBatchFactory;

/**
 * test
 *
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    private TerrainQuad terrain;
    Material mat_terrain;
    Spatial endSphere;
    NavMeshPathfinder navi;

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {

        initCrossHairs(); // a "+" in the middle of the screen to help aiming
        initKeys();       // load custom key mappings

        Vector3f startvec = new Vector3f(0, 1, 0);
        Vector3f endvecdefault = new Vector3f(320, 5, 320);

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

        Texture rock = assetManager.loadTexture("Textures/grid8.png");
        rock.setWrap(Texture.WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 512f);

        Texture heightMapImage = assetManager.loadTexture("Textures/small_1.png");

        AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();
        heightmap.smooth(1f);

        int patchSize = 129;
        terrain = new TerrainQuad("my terrain", patchSize, 129, heightmap.getHeightMap());

        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, 0, 0);
        terrain.setLocalScale(1, 1, 1);

        rootNode.attachChild(terrain);

        Material mat_brick = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_brick.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));


        Material matstart = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        matstart.setColor("Color", ColorRGBA.Green);

        Material matend = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        matend.setColor("Color", ColorRGBA.Red);

        Spatial startSphere = new Geometry("start", new Sphere(100, 100, 1));
        startSphere.setMaterial(matstart);
        startSphere.setLocalTranslation(startvec);

        endSphere = new Geometry("end", new Sphere(100, 100, 1));
        endSphere.setMaterial(matend);
        endSphere.setLocalTranslation(endvecdefault);

        rootNode.attachChild(startSphere);
        rootNode.attachChild(endSphere);

        flyCam.setMoveSpeed(500);

        Mesh mesh = new Mesh();
        GeometryBatchFactory.mergeGeometries(findGeometries(terrain, new LinkedList<Geometry>()), mesh);

        NavMesh navMesh = new NavMesh(mesh);
        
        navi = new NavMeshPathfinder(navMesh);
        navi.setPosition(startvec);
        navi.setEntityRadius(1);
                        
        //doClick(endvecdefault);


    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    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;
    }

    /**
     * A centred plus sign to help the player aim.
     */
    protected void initCrossHairs() {
        setDisplayStatView(false);
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        ch.setText("+"); // crosshairs
        ch.setLocalTranslation( // center
                settings.getWidth() / 2 - ch.getLineWidth() / 2, settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
        guiNode.attachChild(ch);
    }

    /**
     * Declaring the "Shoot" action and mapping to its triggers.
     */
    private void initKeys() {
        inputManager.addMapping("Shoot",
                new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar
                new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
        inputManager.addListener(actionListener, "Shoot");
    }

    public void computeNavigationPath(Vector3f endvec) {
        boolean computePath = navi.computePath(endvec);
        System.out.println("");
        System.out.println("---------------------- drawing the path to " + endvec + " ----------------------");
        System.out.println("computePath " + computePath + "  numwaypoints " + navi.getPath().getWaypoints().size());

        Iterator it = navi.getPath().iterator();

        Vector3f previous = null;

        while (it.hasNext()) {
            Path.Waypoint wp = (Path.Waypoint) it.next();

            if (previous != null) {
                Line line = new Line(previous, wp.getPosition());

                Geometry drawLine = new Geometry("aline", line);

                Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
                mat.setColor("Color", ColorRGBA.Blue);
                drawLine.setMaterial(mat);
                System.out.println("  -- drawing line from " + previous + " to " + wp.getPosition());

                rootNode.attachChild(drawLine);
            }

            previous = wp.getPosition();
        }
    }
    /**
     * Defining the "Shoot" action: Determine what was hit and how to respond.
     */
    private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean keyPressed, float tpf) {
            if (name.equals("Shoot") && !keyPressed) {
                // 1. Reset results list.
                CollisionResults results = new CollisionResults();
                // 2. Aim the ray from cam loc to cam direction.
                Ray ray = new Ray(cam.getLocation(), cam.getDirection());
                // 3. Collect intersections between Ray and Shootables in results list.
                rootNode.collideWith(ray, results);

                // 5. Use the results (we mark the hit object)
                if (results.size() > 0) {
                    
                    Vector3f closest = results.getClosestCollision().getContactPoint();
                    
                    endSphere.setLocalTranslation(closest);
                    rootNode.attachChild(endSphere);
                    System.out.println("Navigating to " + closest);
                    computeNavigationPath(closest);

                } else {
                    // No hits? Then remove the red sphere.
                    rootNode.detachChild(endSphere);
                }
            }
        }
    };
}

I changed the code again, to contrust a mesh out of the navmesh and draw it on the terrain - so I could see what cells were assumed to be navigable.

It looks like the NavMesh constructor is deciding that everything is navigable and not culling anything that is I dunno too steep or whatever, because the whole terrain is covered in cells:

I guess I need to dig into NavMesh and see whats going on…

here is my newest code:

package mygame;

import com.jme3.ai.navmesh.Cell;
import com.jme3.ai.navmesh.NavMesh;
import com.jme3.ai.navmesh.NavMeshPathfinder;
import com.jme3.ai.navmesh.Path;
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
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.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import jme3tools.optimize.GeometryBatchFactory;

/**
 * test
 *
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    private TerrainQuad terrain;
    Material mat_terrain;
    Spatial endSphere;
    NavMeshPathfinder navi;

    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setResolution(640, 480);
        // ... other properties, see below
        Main app = new Main();

        app.setSettings(settings);
        app.setShowSettings(false);

        app.start();

    }

    @Override
    public void simpleInitApp() {
        initCrossHairs(); // a "+" in the middle of the screen to help aiming
        initKeys();       // load custom key mappings

        Vector3f startvec = new Vector3f(0, 1, 0);
        Vector3f endvecdefault = new Vector3f(320, 5, 320);
        
        Material debugNavMeshMaterial = new Material(assetManager, "/Common/MatDefs/Misc/Unshaded.j3md");
        debugNavMeshMaterial.getAdditionalRenderState().setWireframe(true);
        
        mat_terrain = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_terrain.setColor("Color", ColorRGBA.Blue);

        Material matstart = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        matstart.setColor("Color", ColorRGBA.Green);

        Material matend = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        matend.setColor("Color", ColorRGBA.Red);

        Texture heightMapImage = assetManager.loadTexture("Textures/terrain.png");

        AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();
        heightmap.smooth(1f);

        int patchSize = 129;
        terrain = new TerrainQuad("my terrain", patchSize, 129, heightmap.getHeightMap());

        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, 0, 0);
        terrain.setLocalScale(1, 1, 1);

        rootNode.attachChild(terrain);

        Spatial startSphere = new Geometry("start", new Sphere(100, 100, 1));
        startSphere.setMaterial(matstart);
        startSphere.setLocalTranslation(startvec);

        endSphere = new Geometry("end", new Sphere(100, 100, 1));
        endSphere.setMaterial(matend);
        endSphere.setLocalTranslation(endvecdefault);

        rootNode.attachChild(startSphere);
        rootNode.attachChild(endSphere);

        flyCam.setMoveSpeed(50);

        Mesh mesh = new Mesh();
        GeometryBatchFactory.mergeGeometries(findGeometries(terrain, new LinkedList<Geometry>()), mesh);

        NavMesh navMesh = new NavMesh(mesh);

        LinkedList<Geometry> navMeshGeom = new LinkedList<Geometry>();

        Integer numcells = navMesh.getNumCells();

        for (int i = 0; i < navMesh.getNumCells(); i++) {
            Cell cell = navMesh.getCell(i);

            System.out.println("cell " + i + " has normal " + cell.getNormal());

            Geometry geom = new Geometry("cell " + i, cell.getDebugMesh());

            navMeshGeom.add(geom);
        }

        Mesh debugMesh = new Mesh();

        GeometryBatchFactory.mergeGeometries(navMeshGeom, debugMesh);

        Geometry debugGeom = new Geometry("debugGeom", debugMesh);

                
        
        debugGeom.setMaterial(debugNavMeshMaterial);
        debugGeom.setCullHint(Spatial.CullHint.Never);

        debugGeom.setLocalTranslation(0, 0.1f, 0);
        
        rootNode.attachChild(debugGeom);


        navi = new NavMeshPathfinder(navMesh);
        navi.setPosition(startvec);
        navi.setEntityRadius(1);

        //doClick(endvecdefault);


    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    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;
    }

    /**
     * A centred plus sign to help the player aim.
     */
    protected void initCrossHairs() {
        setDisplayStatView(false);
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        ch.setText("+"); // crosshairs
        ch.setLocalTranslation( // center
                settings.getWidth() / 2 - ch.getLineWidth() / 2, settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
        guiNode.attachChild(ch);
    }

    /**
     * Declaring the "Shoot" action and mapping to its triggers.
     */
    private void initKeys() {
        inputManager.addMapping("Shoot",
                new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar
                new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
        inputManager.addListener(actionListener, "Shoot");
    }

    public void computeNavigationPath(Vector3f endvec) {
        boolean computePath = navi.computePath(endvec);
        System.out.println("");
        System.out.println("---------------------- drawing the path to " + endvec + " ----------------------");
        System.out.println("computePath " + computePath + "  numwaypoints " + navi.getPath().getWaypoints().size());

        Iterator it = navi.getPath().iterator();

        Vector3f previous = null;

        while (it.hasNext()) {
            Path.Waypoint wp = (Path.Waypoint) it.next();

            if (previous != null) {
                Line line = new Line(previous, wp.getPosition());

                Geometry drawLine = new Geometry("aline", line);

                Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
                mat.setColor("Color", ColorRGBA.Green);
                drawLine.setMaterial(mat);
                System.out.println("  -- drawing line from " + previous + " to " + wp.getPosition());

                rootNode.attachChild(drawLine);
            }

            previous = wp.getPosition();
        }
    }
    /**
     * Defining the "Shoot" action: Determine what was hit and how to respond.
     */
    private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean keyPressed, float tpf) {
            if (name.equals("Shoot") && !keyPressed) {
                // 1. Reset results list.
                CollisionResults results = new CollisionResults();
                // 2. Aim the ray from cam loc to cam direction.
                Ray ray = new Ray(cam.getLocation(), cam.getDirection());
                // 3. Collect intersections between Ray and Shootables in results list.
                rootNode.collideWith(ray, results);

                // 5. Use the results (we mark the hit object)
                if (results.size() > 0) {

                    Vector3f closest = results.getClosestCollision().getContactPoint();

                    endSphere.setLocalTranslation(closest);
                    rootNode.attachChild(endSphere);
                    System.out.println("Navigating to " + closest);
                    computeNavigationPath(closest);

                } else {
                    // No hits? Then remove the red sphere.
                    rootNode.detachChild(endSphere);
                }
            }
        }
    };
}
1 Like

OOOOOOOOOOH I think I know the issue.

I have to write code that culls cells/meshes myself! durr.

And here is my first lame attempt - but it works! Yes! I am overriding the NavMesh.java class with CullingNavMesh.java where we ignore planes whose normal is less than or equal to 0.999 away from the normal (IE not very steep) :

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mygame;

import com.jme3.ai.navmesh.Plane;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;

/**
 *
 * @author jakbri
 */
public class CullingNavMesh extends com.jme3.ai.navmesh.NavMesh{
    
    
    public CullingNavMesh() {
        super();
    }

    public CullingNavMesh(Mesh mesh) {
        this.loadFromMesh(mesh);
    }
    
    @Override
    public void loadFromMesh(Mesh mesh) {
        clear();

        Vector3f a = new Vector3f();
        Vector3f b = new Vector3f();
        Vector3f c = new Vector3f();

        Plane up = new Plane();
        up.setPlanePoints(Vector3f.UNIT_X, Vector3f.ZERO, Vector3f.UNIT_Z);
        up.getNormal();

        IndexBuffer ib = mesh.getIndexBuffer();
        FloatBuffer pb = mesh.getFloatBuffer(VertexBuffer.Type.Position);
        pb.clear();
        for (int i = 0; i < mesh.getTriangleCount() * 3; i += 3) {
            int i1 = ib.get(i + 0);
            int i2 = ib.get(i + 1);
            int i3 = ib.get(i + 2);
            BufferUtils.populateFromBuffer(a, pb, i1);
            BufferUtils.populateFromBuffer(b, pb, i2);
            BufferUtils.populateFromBuffer(c, pb, i3);

            Plane p = new Plane();
            p.setPlanePoints(a, b, c);
            Float aValue = up.pseudoDistance(p.getNormal());
            
            if (aValue <= 0.0f) {
                System.out.println("CULLINGNAVMESH Warning, normal of the plane faces downward!!!" + up.pseudoDistance(p.getNormal()));
                continue;
            } else if (aValue <= 0.999f) {
                System.out.println("CULLINGNAVMESH sweet, normal of the plane is just steep   !!!" + up.pseudoDistance(p.getNormal()));   
                continue;
            }

            addFace(a, b, c);
        }

        linkCells();
    }
    
    private void addFace(Vector3f vertA, Vector3f vertB, Vector3f vertC) {
        // some art programs can create linear polygons which have two or more
        // identical vertices. This creates a poly with no surface area,
        // which will wreak havok on our navigation mesh algorithms.
        // We only except polygons with unique vertices.
        if ((!vertA.equals(vertB)) && (!vertB.equals(vertC)) && (!vertC.equals(vertA))) {
            addCell(vertA, vertB, vertC);
        } else {
            System.out.println("Warning, Face winding incorrect");
        }
    }
}

and my Main.java

package mygame;

import com.jme3.ai.navmesh.Cell;
import com.jme3.ai.navmesh.NavMesh;
import com.jme3.ai.navmesh.NavMeshPathfinder;
import com.jme3.ai.navmesh.Path;
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
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.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import jme3tools.optimize.GeometryBatchFactory;

/**
 * test
 *
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    private TerrainQuad terrain;
    Material mat_terrain;
    Spatial endSphere;
    NavMeshPathfinder navi;

    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setResolution(640, 480);
        // ... other properties, see below
        Main app = new Main();

        app.setSettings(settings);
        app.setShowSettings(false);

        app.start();

    }

    @Override
    public void simpleInitApp() {
        initCrossHairs(); // a "+" in the middle of the screen to help aiming
        initKeys();       // load custom key mappings

        Vector3f startvec = new Vector3f(0, 1, 0);
        Vector3f endvecdefault = new Vector3f(320, 5, 320);
        
        Material debugNavMeshMaterial = new Material(assetManager, "/Common/MatDefs/Misc/Unshaded.j3md");
        debugNavMeshMaterial.getAdditionalRenderState().setWireframe(true);
        
        mat_terrain = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_terrain.setColor("Color", ColorRGBA.Blue);

        Material matstart = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        matstart.setColor("Color", ColorRGBA.Green);

        Material matend = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        matend.setColor("Color", ColorRGBA.Red);

        Texture heightMapImage = assetManager.loadTexture("Textures/terrain.png");

        AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();
        heightmap.smooth(1f);

        int patchSize = 129;
        terrain = new TerrainQuad("my terrain", patchSize, 129, heightmap.getHeightMap());

        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, 0, 0);
        terrain.setLocalScale(1, 1, 1);

        rootNode.attachChild(terrain);

        Spatial startSphere = new Geometry("start", new Sphere(100, 100, 1));
        startSphere.setMaterial(matstart);
        startSphere.setLocalTranslation(startvec);

        endSphere = new Geometry("end", new Sphere(100, 100, 1));
        endSphere.setMaterial(matend);
        endSphere.setLocalTranslation(endvecdefault);

        rootNode.attachChild(startSphere);
        rootNode.attachChild(endSphere);

        flyCam.setMoveSpeed(50);

        Mesh mesh = new Mesh();
        GeometryBatchFactory.mergeGeometries(findGeometries(terrain, new LinkedList<Geometry>()), mesh);
       
        CullingNavMesh navMesh = new CullingNavMesh(mesh);

        LinkedList<Geometry> navMeshGeom = new LinkedList<Geometry>();

        Integer numcells = navMesh.getNumCells();

        for (int i = 0; i < navMesh.getNumCells(); i++) {
            Cell cell = navMesh.getCell(i);

            System.out.println("cell " + i + " has normal " + cell.getNormal());

            Geometry geom = new Geometry("cell " + i, cell.getDebugMesh());

            navMeshGeom.add(geom);
        }

        Mesh debugMesh = new Mesh();

        GeometryBatchFactory.mergeGeometries(navMeshGeom, debugMesh);

        Geometry debugGeom = new Geometry("debugGeom", debugMesh);

                
        
        debugGeom.setMaterial(debugNavMeshMaterial);
        debugGeom.setCullHint(Spatial.CullHint.Never);

        debugGeom.setLocalTranslation(0, 0.1f, 0);
        
        rootNode.attachChild(debugGeom);


        navi = new NavMeshPathfinder(navMesh);
        navi.setPosition(startvec);
        navi.setEntityRadius(1);

        //doClick(endvecdefault);


    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    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;
    }

    /**
     * A centred plus sign to help the player aim.
     */
    protected void initCrossHairs() {
        setDisplayStatView(false);
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        ch.setText("+"); // crosshairs
        ch.setLocalTranslation( // center
                settings.getWidth() / 2 - ch.getLineWidth() / 2, settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
        guiNode.attachChild(ch);
    }

    /**
     * Declaring the "Shoot" action and mapping to its triggers.
     */
    private void initKeys() {
        inputManager.addMapping("Shoot",
                new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar
                new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
        inputManager.addListener(actionListener, "Shoot");
    }

    public void computeNavigationPath(Vector3f endvec) {
        boolean computePath = navi.computePath(endvec);
        System.out.println("");
        System.out.println("---------------------- drawing the path to " + endvec + " ----------------------");
        System.out.println("computePath " + computePath + "  numwaypoints " + navi.getPath().getWaypoints().size());

        Iterator it = navi.getPath().iterator();

        Vector3f previous = null;

        while (it.hasNext()) {
            Path.Waypoint wp = (Path.Waypoint) it.next();

            if (previous != null) {
                Line line = new Line(previous, wp.getPosition());

                Geometry drawLine = new Geometry("aline", line);

                Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
                mat.setColor("Color", ColorRGBA.Green);
                drawLine.setMaterial(mat);
                System.out.println("  -- drawing line from " + previous + " to " + wp.getPosition());

                rootNode.attachChild(drawLine);
            }

            previous = wp.getPosition();
        }
    }
    /**
     * Defining the "Shoot" action: Determine what was hit and how to respond.
     */
    private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean keyPressed, float tpf) {
            if (name.equals("Shoot") && !keyPressed) {
                // 1. Reset results list.
                CollisionResults results = new CollisionResults();
                // 2. Aim the ray from cam loc to cam direction.
                Ray ray = new Ray(cam.getLocation(), cam.getDirection());
                // 3. Collect intersections between Ray and Shootables in results list.
                rootNode.collideWith(ray, results);

                // 5. Use the results (we mark the hit object)
                if (results.size() > 0) {

                    Vector3f closest = results.getClosestCollision().getContactPoint();

                    endSphere.setLocalTranslation(closest);
                    rootNode.attachChild(endSphere);
                    System.out.println("Navigating to " + closest);
                    computeNavigationPath(closest);

                } else {
                    // No hits? Then remove the red sphere.
                    rootNode.detachChild(endSphere);
                }
            }
        }
    };
}
1 Like

Also, it works fine on jMonkeyEngine 3 alpha 1 if you manually import the com.jme3.ai.navmesh code

1 Like