Jme3ai: Combining separate Navmeshes

Happy Easter everybody,
I am still working on my project and getting pretty close to finishing, actually :slight_smile:!
I’ve run into a problem with the Navmeshes still and have been trying to fix this for ages.
The problem:

  • I can’t simply use jme3ai on the whole scene, because it’s too large
  • I tried using the geometries directly as a Navmesh which does not work as expected. When I try to use that, I get several “Warning, normal of the plane faces downward!!!” in loadFromMesh().
  • When I use optimized Meshes, i’ts fine
  • I can’t just optimize all meshes together, because the resulting Navmesh is not close enough to the scene (think that I want to preserve some Geometries, for example textures on the ground that mark areas where I can walk)
  • I can run the optimize step on the separate parts of my scene which gets me pretty close to what I want

BUT:

  • I can’t combine the optimized meshes into one (I wrote a method to merge the meshes, but I get an UnsupportedOperationException in the FloatBufferArray)

      private Mesh mergeMeshes(Mesh one, Mesh two) {
      Mesh result = new Mesh();
      VertexBuffer vbPosOne = one.getBuffer(Type.Position);
      VertexBuffer vbInOne = one.getBuffer(Type.Index);
      VertexBuffer vbNormalOne = one.getBuffer(Type.Normal);
    
      VertexBuffer vbPosTwo = two.getBuffer(Type.Position);
      VertexBuffer vbInTwo = two.getBuffer(Type.Index);
      VertexBuffer vbNormalTwo = two.getBuffer(Type.Normal);
    
      int numElemOne = vbPosOne.getNumElements();
      int numElemTwo = vbPosTwo.getNumElements();
    
      int numElemResult = numElemOne + numElemTwo;
    
      FloatBuffer oneBuffer = (FloatBuffer) vbPosOne.getData();
      int oneBufferSize = oneBuffer.remaining();
      System.out.println(oneBuffer.hasArray());
      float[] firstArray = oneBuffer.array();
    
      FloatBuffer twoBuffer = (FloatBuffer) vbPosTwo.getData();
      int twoBufferSize = twoBuffer.remaining();
      float[] secondArray = twoBuffer.array();
    
      float[] finalArray = new float[oneBufferSize + twoBufferSize];
    
      System.arraycopy(firstArray, 0, finalArray, 0, firstArray.length);
      System.arraycopy(secondArray, 0, finalArray, firstArray.length, secondArray.length);
    
      FloatBuffer resultBuffer = BufferUtils.createFloatBuffer(finalArray);
    
      resultBuffer.rewind();
    
      result.setBuffer(Type.Position, 3, resultBuffer);
    
      int numInOne = vbInOne.getNumElements();
      int numInTwo = vbInTwo.getNumElements();
    
      int numInResult = numInOne + numInTwo;
    
      ShortBuffer oneIndexBuffer = (ShortBuffer) vbInOne.getData();
      int oneIndexBufferSize = oneIndexBuffer.remaining();
      short[] firstIndexArray = oneIndexBuffer.array();
    
      ShortBuffer twoIndexBuffer = (ShortBuffer) vbInTwo.getData();
      int twoIndexBufferSize = twoIndexBuffer.remaining();
      short[] secondIndexArray = twoIndexBuffer.array();
    
      short[] finalIndexArray = new short[oneIndexBufferSize + twoIndexBufferSize];
    
      System.arraycopy(firstIndexArray, 0, finalIndexArray, 0, firstIndexArray.length);
      System.arraycopy(secondIndexArray, 0, finalIndexArray, firstIndexArray.length, secondIndexArray.length);
    
      ShortBuffer resultIndexBuffer = BufferUtils.createShortBuffer(finalIndexArray);
    
      resultIndexBuffer.rewind();
    
      result.setBuffer(Type.Index, 3, resultIndexBuffer);
    
      return result;
    

    }

I know that the UnsupportedOperationException comes from the fact that the optimized mesh that I get from the Generator is not backed by an array, but a “direct buffer” (?).

Can anyone tell me

  • How to get rid of the exception? Can I somehow convert the direct Buffer to an array-backed one, or ist that a bad idea?
  • Does anyone have an idea why I get “Warning, normal of the plane faces downward!!!” in loadFromMesh() when trying to use the geometries as a navmesh directly?

Any help to make me understand what is going wrong is much appreciated!

Best,
j

To create a wrapped FloatBuffer instead of a direct one, start with a float[] of the desired size and use FloatBuffer.wrap().

1 Like

i dont know how to do it using jme3-ai.
but share with you some information.

Recast4j allow “tile navmesh generate” (that as i see might interest you)

the only problem is that not much documentation there. and need look at recast in C for it.

i hope @mitm has some more tutorial for it now. I think JME should just have nice wrapper for it.

here some old video that show it work:

1 Like

I guess just combining the buffers versus just using all the geometries directly is probably a difference and doesn’t work as expected.

Actually it’s strange that NavMesh is a regular Mesh, given how Recast works.

“Warning, normal of the plane faces downward!!!” is a problem because the normal is used to determine if something is walkable, if the normals point down, the geometry is unwalkable.
Check your meshes which cause that error and fix it, it might lead to broken lighting as well.

Your Code is combining meshes before they get nav meshed? Some sort of like batching?

If you want to “risk” a learning curve, then there is GitHub - MeFisto94/jme3-recast4j: Abstractional Bindings to use recast4j (a java only port of recast+detour) in jMonkeyEngine 3, but apart from GitHub - MeFisto94/jme3-recast4j-demo: This is a demonstrational Application to show how the abstractionalbBindings to use recast4j (a java only port of recast+detour) in jMonkeyEngine 3 can be used we didn’t really put it to test, so if you are willing to give it a try, consider it!

Specifically you are looking at tiled nav meshing, which I think is lacking support in recast4j’s convenience methods, but it should work. Currently it builds one giant mesh. It should work, but the benefit from tiled meshes is to seamlessy load them (less ram, loading them from disk etc).

1 Like

Thanks everybody for the answers, I’ll reply below:

I know. This is a workaround becauseI have to do something different.
I’ve seen this work on Unity, I do not know how to get this on jme3ai.
Imagine the following:
You have a room with a floor and rugs on the floor.
You only are allowed to walk on the rugs.
Whe I try to use the normal jme3ai, I get a navmesh which does not distinguish between the floor and the rugs, so I get triangles going across floor and rugs.
I wanted to overcome that by using the geometries directly as a navmesh (meaning I feed the geomMeshes to the NavMesh Constructor in jme3ai), but everytime I want to do that, I get the Warning mentioned above. When I use the optimizer on a mesh first, everything works.
So I would like to optimize the meshes separately (mesh for the rug, mesh for the floor), and then join the optimized meshes.

I’ll investigate tomorrow.

Nope, after. I want to combine the navmeshes, because like I said above, I get into trouble otherwise.

I know the package, I just can’t get it to work (-.-').
I’ve thought about using Recast4J to generate the mesh, then hand it to jme3ai, but I utterly failed at using R4J.
If all of this leads to nothing, then I’ll try it again as a last resort.

The thing is that I am not making the buffer, the optimize method in the Navmesh Class in jme3ai is.
I’ll look into it, again, tomorrow.

As I said above in this post, if everything fails, I’ll try recast4j again.
If MTM sees this, I’ll prolly get kicked in the shin ( :wink: ), (s)he’s helped me a lot already when I started this project.

1 Like

What I did in my code was generating two nav meshes (or in this case you’d only generate one), by controlling what geometries get built.
But: Why do you want to JOIN them again then? Just use the rugs-navmesh for the NPCs?

I don’t know how the NavMeshes are built directly, but trying to mess with the buffers might mess everything up, as it hasn’t been made fo rthat.

This is not R4J, but jme3-r4j, it has everything needed to work with JME, but you cannot feed it to jme3ai but need to use the utility from the Detour package.

In this case you are looking at Area Flags, which are also not really documented, but should work.

1 Like

They are documented, the files for using them have not been implemented into jme3ai-recast4j.

They can be found here.

Everything in this folder (except DemoApplication.java) overrides a feature of jme3ai-recast4j so most every feature of Recast4J can be used programmatically.

The exceptions being, playing an animation when using an off mesh connection or actually moving the character through the off mesh connection and tile cache swapping of nav meshes when an object is added or removed to a navmesh using tile cache. The tile cache part works and with a very minor tweak or two it would be complete.

I am actually working on my own game now that I started last year. It is amazing how much effort is required to actually write a networking game and just how many coding languages are need to do it right. After 8 months, I have not even added a single asset yet. I am doing this solo so I am limited on time for other things.

I would and could easily write a recast4j wiki but the one for @Darkchaos is pretty detailed. It would be best for people to actually implement that if they could and start helping with the project. Its a nice library that only needs a little more work to be crazy good for jme and it simplifies a ton of things.

My real concern though is after all the effort, something new will come along and blow everything out of the water. Probably a good thing if it was fully documented and easy to use but somehow I doubt it based off how things seem to work with things like this.

Nothing has yet come along that I have seen that even comes close to this library (recast) and what it does, even though its not been an active project for many years now.

1 Like

One last thing. The way I implemented some of the features of recast4j could easily be rewritten to a better code style. I am not as advanced as some of you are so when I figure things out I tend to do do things in a simplistic way where others with more knowledge could clean things up and take it in a better direction.

The good thing about what I did though is I figured out how things can be done, which should give others ideas on how to improve things, if needed.

1 Like

Hi everybody,
I tried to integrate jme3-recast4j inti my project, but it just does not work:

  • I imported jme3-recast4j and recast4j itself as a git project
  • linked the source folders
  • started coding away
  • The package imports are wrong, so I click on “fix project setup” (I am using Eclipse)
  • Imports are alrighty
  • Now, my codebase tells me when I run the program that “java.lang.UnsatisfiedLinkError: The required native library ‘bulletjme’ was not found in the classpath via ‘native/windows/x86_64/bulletjme.dll’. Error message: no bulletjme in java.library.path”
    Which does not occur when remove jme3-recast4j and recast4j detour and recast4j recast from the build path. But then I can’t use jme3recast4j, obviously.

I also tried the gradle import. Same thing.

Am I missing something here? I have no idea why it would give me that error when It works as soon as I remove the recast4j stuff again. I’ve wasted two whole days on this error, googled like a madman, but I can’t figure out why this is happening. I mean, it works flawlessly without r4j. o0

If there’s no bulletjme in the classpath, then you need to add the jme3-bullet-native library to your project.

1 Like
  1. I think jme3-recast4j already depends on recast4j
  2. We usually have this issue when the engine hasn’t been built correctly, but I have no clue how that could affect recast4j.

Do you use your own version of the engine or something?

1 Like

I would think this did something that shouldnt of been done. Auto fix will do whatever it feels like.

To make this work, all you need to do is build the recast4j project and jme3ai-recast4j projects and include the built jars in your project as a library if you aren’t using a build tool.

Then just add any jme library thats missing, like @sgold said.

1 Like

Hi everybody,

after a lot of hair-pulling and despair, mtm’s tip (which I tried last -.-') worked.
I want to express my amazement at how well jme3-r4j seems to be implemented, it is way faster than jme3ai.

I am still having trouble with the meshes, however:
I still have to calculate separate navmeshes and join them afterwards, which still doesn’t work.
I did see something curious during debugging though, which leads me to the following question:

Suppose we have two meshes, a and b.
Mesh a and mesh b do not have any overlapping vertices, but only vertices close to each other.
Then I can’t merge them, can I? Could that be why I get the “java.lang.UnsupportedOperationException
at java.nio.FloatBuffer.array(FloatBuffer.java:994)” error?

The problem is basically, that there is a “margin” from the mesh to the rims of the underlying geometry.
I want the mesh to go right to the edge of the geometry. Can I do that?
I already changed the edgeMaxError in jme3-R4J, but that does not get rid of the margin, it just makes the mesh have a lot more triangles. :confused:

Have you tried flags yet for the path finding as shown in the first link I posted?

This is that wiki explanation in action for a tiled mesh, which is the best way to go.

You start with Area Type flags that are turned into Ability Flags that get fed to pathfinding when creating a path that determine just where you can go or not. The pathfinding uses them as filters.

What the above code is doing is allowing you to identify and mark the geometries triangles with a Area Type flag. Note the wording here. AREAMOD

int geomLength = spat.getMesh().getTriangleCount() *3;

for example

geom.addMod(new Modification(geomLength, AREAMOD_WATER));

When you run the RecastBuilder, not the one with jme3ai-recast4j, but the one I wrote thats in the demo project, it will use the Modification to mark those triangles you created above. Thats the geom object you see here.

Then you flag those triangles with the appropriate Ability Flags here,

These are the flags you will pass to the filtering when pathfinding.

The demo project uses crowd for pathfinding but it will let you understand how filtering works.

Every parameter you see is explained in the help or just go to the source. Each panel has a help section.

Do not manipulate the navmesh geomitries. Do not combine them. Use the GeometryProviderBuilder2 instead. You will need this file because it was never added to the jme3ai-recast4j.

GeometryProviderBuilder2 will do all the merging for you. Just feed it a node and all the gemoetries under that node will be gathered for you.

Use blender on a copy of the entire model or pieces and just mark areas with flags by material naming. After you create the navmesh, save it out by using the recast file format so you can load it into your game or testing environment. Its real fast.

You will have to develop a way to transition to the next scene by swapping between navmeshes but you will be able to make large maps so it wont be that many and the meshes are fast to build from file.

GeometryProviderBuilder2 and the others missing from jme3ai-recast4j are here in the top folder.

These files must be used or jme3ai-recast4j will not work. Unless and until they are added you will have to add them to your project that you use to build the navmeshes with.

I wrote the demo examples and implemented recast4j as best I could given the situation. You may be able to improve things once you get a grasp on how things work.

I used 4 models that were 1024x1024 that I merged into one navmesh and had no noticeable impact on the testing environment. These had 25% flat and 75% mountain areas with lots of triangles. What did affect things was the number of objects. For me around 300 on my generic computer is where I notice degradation.

Here are the bit flags from the AreaModifications file, which is the glue for everything.

You can add new ones, these are just for the demo.