New jme3Ai bug to report

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.

http://imgur.com/a/yXvVi

If the overlapping floor has no traversable area under it then pathfinding works perfectly.

http://imgur.com/a/jd5ox

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

http://imgur.com/a/Owucy

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.

I saw that thread, I think this one

but it was supposedly fixed in mefistoAI.

Ive checked both jme3AI and mefisto fork and each acts the same.

Edit: As noted above, it only affects returned path if the area below is navigable.

I think I found problem,

/**
     * 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)));
    }

All three levels have the same points, except Y.

I think this is what’s going on, this method,

    /**
     * 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,

                if (foundHomeCell) {
                    if (thisDistance < closestHeight) {
                        closestCell = cell;
                        closestHeight = thisDistance;             
                    }
                } else {
                    closestCell = cell;
                    closestHeight = thisDistance;
                    foundHomeCell = true; //first iteration sets to true
                }

The loop hits the next cell that also contains point x and z and sets closestCell and closestHeight again if its

thisDistance < closestHeight

where closestHeight is from the last cell thisDistance, then finally returns that last cell it found to the calling method.

So how to differentiate which cell to return is the problem. This current way is just a race to the bottom.

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.

this.walkableClimb = (int) Math.floor(agentMaxClimb / ch);

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?

Edit point.y is always 0 in thisDistance.

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.

Forehead SLAP, somehow I had the source files from contributions-jme3AI inside my mefisto library.

I debugged the exact problem mentioned in the link and what you were referring to jayfella.

The fix is only partial because

and

weren’t fixed also.