I have discovered yet another bug in jme3AI while finalizing the code for use in the tutorial.

This new find happens when there are two floors, one above the other, where the overlapping part below the upper floor is traversable. The red ball is the target, boxes are the path returned, pathfinder returned a path to the closest cell, which is adjacent to rather than the one above where target is.

Sometimes pathfinder returns the nearest cell (incorrect cell) but the character move code using BetterCharacterControl splits the results so we have a flying character.

I have the exact same movement code run using recast and its flawless no matter what. The only difference in code is the Recast Control uses recast for path finding.

My question is have others seen this with pathfinding in jme3AI?

Somewhere in my head this reminds me of something to do with the y coordinate being ignored or only being two dimensional pathfinding. It irks me that i cannot find the thread im referring to.

/**
* Test to see if a 3D point is within the cell by projecting it down to 2D
* and calling the above method.
* @param point
* @return
*/
public boolean contains(Vector3f point) {
return (contains(new Vector2f(point.x, point.z)));
}

/**
* Find the closest cell on the mesh to the given point AVOID CALLING! not a
* fast routine!
* @param point
* @return
*/
public Cell findClosestCell(Vector3f point) {
float closestDistance = 3.4E+38f;
float closestHeight = 3.4E+38f;
boolean foundHomeCell = false;
float thisDistance;
Cell closestCell = null;
// oh dear this is not fast
for (Cell cell : cellList) {
if (cell.contains(point)) {
thisDistance = Math.abs(cell.getHeightOnCell(point) - point.y);
if (foundHomeCell) {
if (thisDistance < closestHeight) {
closestCell = cell;
closestHeight = thisDistance;
}
} else {
closestCell = cell;
closestHeight = thisDistance;
foundHomeCell = true;
}
}
if (!foundHomeCell) {
Vector2f start = new Vector2f(cell.getCenter().x, cell.getCenter().z);
Vector2f end = new Vector2f(point.x, point.z);
Line2D motionPath = new Line2D(start, end);
ClassifyResult Result = cell.classifyPathToCell(motionPath);
if (Result.result == Cell.PathResult.ExitingCell) {
Vector3f ClosestPoint3D = new Vector3f(
Result.intersection.x, 0.0f, Result.intersection.y);
cell.computeHeightOnCell(ClosestPoint3D);
ClosestPoint3D = ClosestPoint3D.subtract(point);
thisDistance = ClosestPoint3D.length();
if (thisDistance < closestDistance) {
closestDistance = thisDistance;
closestCell = cell;
}
}
}
}
return closestCell;
}

loops through all cells and returns the closest cell only after checking all cells.

When it first starts this loop,

boolean foundHomeCell = false;

so when there are two or more cells with the same x and z but different y, this loop first sees foundHomeCell is false so sets found to true here on the first iteration,

I found a possible fix for this but would like some input,

In recast, every tile has a MeshHeader to store all the parameters that are similar to the ones used NavvMeshGenerator of the SDK, i.e. cellSize, cellHeight, minTraversableHeight , maxTraversableStep, etc.

This MeshHeader gives you access to the parameter walkableClimb which is calculated like so.

The equivalent is calculated in CritterAI like so where vxMaxTraversableStep = walkableClimb.

int vxMaxTraversableStep = 0;
if (maxTraversableStep != 0)
{
vxMaxTraversableStep = (int)Math.ceil(
Math.max(Float.MIN_VALUE, maxTraversableStep) /
Math.max(Float.MIN_VALUE, cellHeight));
}

There is no getter in CritterAI for this variable as it is only used to generate SolidHeightfield and OpenHeightfield then no longer needed. Jme3AI doesn’t even use maxTraversableStep, and the SDK NavMeshGenerator just passes the variable maxTraversableStep to CritterAI to calculate vxMaxTraversableStep.

The possible solution I found in recast uses walkableClimb to determine the nearest poly which is similar to Cell in jme3AI.

where jme3AI just races to the lowest point and choses that,

NavMeshGenerator is an external class not associated with jme3AI at all but should actually be part of the library IMO.

Now that you understand whats happening, heres the question.

Where to store the vxMaxTraversableStep variable in jme3AI and how best to pass it?

Er, I just discovered that my mefisto library is using wrong source for compute path. I have been debugging a bug already fixed it looks like. I will change the source and see if it fixes.