[SOLVED] Ray collision detection returns seemingly incorrect Vector3f Y value

Ray collision detection returns seemingly incorrect Vector3f Y value

Background:
I am working on placing forests and cities on maps that I randomly generate. In order to tell where to spawn the tree / building models, I use a 2D coordinate (x,z) and have a ray try to collide with my terrain object to determine the correct y value.

My code (in my Main class that extends SimpleApplication)

    public Vector3f coordinateOf(int x, int y)
    {
        CollisionResults results = new CollisionResults();
        Ray ray = new Ray(new Vector3f(x, 1_000_000, y), new Vector3f(0, -1, 0));
        geography.collideWith(ray, results);
        
        float height = -1;
        if(results.size() > 0)
        {
            height = results.getClosestCollision().getContactPoint().y;
            return new Vector3f(x, height, y);
        }
        return null;
    }

geography is a Node with only one child, which is my terrain object.

As expected, the method returns a vector every time I pass an (x,z) value within the bounds of my terrain. However, every once in a while it will return what seems to be an invalid y value, placing the object below the terrian. (Almost always or always at y = 0.0f)

Here is the code calling Main. coordinateOf(int x, int y) :
Main.buildMap() //to place trees

...
        for(int i = 0; i < forestNum; i++)
        {   
            Vector3f treeLoc = null;
            while(treeLoc == null || (Runner.allowWater() && treeLoc.y < baseHeight))
            {
                int x = gen.nextInt(MAP_SIZE) - (int) (.5f * MAP_SIZE);
                int z = gen.nextInt(MAP_SIZE) - (int) (.5f * MAP_SIZE);
                treeLoc = coordinateOf(x, z);
            }
            trees.add(new Tree(assetManager, rootNode, bulletAppState, treeLoc, 1, false));
        }
…

Main.placeVillages() //Sets the center of a village, where I place a castle

…
while(loc == null || loc.y < baseHeight)
            {
                final int RANGE = 100;
                int x = baseX + RandomNumber.randomInt(-RANGE, RANGE);
                int z = baseZ + RandomNumber.randomInt(-RANGE, RANGE);
                loc = coordinateOf(x, z);
            }
…

and Village.getValidSpawn() //get placement for buildings, player, and NPCs,

    {
        Vector3f loc = null;
        float d = FastMath.sqrt(2);
        boolean valid = false;

        while(loc == null || !valid)
        {
            int x = (int) (center.x + RandomNumber.randomInt((int) (-villageRadius / d), (int) (villageRadius / d)));
            int z = (int) (center.z + RandomNumber.randomInt((int) (-villageRadius / d), (int) (villageRadius / d)));
            loc = main.coordinateOf(x, z);
            
            valid = true;
            if(loc != null)
            {
                if(Runner.allowWater() && loc.y < main.waterHeight())
                {
                    valid = false;
                }
                for(ArrayList<Building> buildingArray : buildings)
                {
                    for(Building b : buildingArray)
                    {         
                        float distance = b.loc().distance(loc);
                        if(distance < b.repel)
                        {
                            valid = false;
                        }
                    }
                }
            }
        }
        return loc;
    }

The vast majority of trees (probably around 99%) spawn properly. The majority of buildings and NPCs (and the player) also do, but with more frequent errors. Castle placement in Main.placeVillages() never has given incorrect results, but I also spawn few castles.
The code does not throw any Exceptions, and otherwise runs as expected.

FYI—I have a screenshot, but it won’t let me upload now. I’ll try again later.
I also am aware of the former errors in the Node.collideWith function, but since these were fixed in 2016, and since I downloaded the 3.1.0 stable version released in 2017, I do not think these apply to my code (but I may be wrong).

Does anyone has an idea as to how to fix this problem?
Thanks in advance!

Maybe theres a reason you use your y variable as z in the Vector you return but you’re aware that its Vector3f(x,y,z), not Vector3f(x,z,y) right? Just doesn’t seem to make sense to me even if your map is in the x-y plane (and not x-z like normal)…

1 Like

Thanks for pointing that ou! I changed it so it has z in place of y. That was not, unfortunately, the source of the problem…just me messing up my variable naming. I just accidentally called z “y” :slight_smile:

This new code ahs the fix, it should make more sense now

    public Vector3f coordinateOf(int x, int z)
    {
        CollisionResults results = new CollisionResults();
        Ray ray = new Ray(new Vector3f(x, 1_000_000, z), new Vector3f(0, -1, 0));
        geography.collideWith(ray, results);
        
        float height = -1;
        if(results.size() > 0)
        {
            height = results.getClosestCollision().getContactPoint().y;
            return new Vector3f(x, height, z);
        }
        return null;
    } 
1 Like

Well Bugs may happen, like test and fix for issue #710 (phantom triangles in mesh collision) by stephengold · Pull Request #725 · jMonkeyEngine/jmonkeyengine · GitHub

I assume your trees and Castles are always made of Triangle?
Because without reading your code, if you’d then trace where a tree already spawned and when this tree would be non triangular you could hit those phantom Triangles

Can you construct a Minimum Working example? That is a new project with a terrain and then try raycasting until your ray is faulty. Note its direction and add it in Code.

That way the issue should happen each time and should be reproducable so someone can Step in with the debugger

I am not entirely sure if the models are made of triangles. They were imported from blender with most of the default settings. (My only changes to the defaults were that on buildings I disabled culling, and I loaded generated textures with optimization.) In blender I used faces that were mot necessarily triangles, but I do not know how JME interpreted the data.

I have made a simple test (here: https://drive.google.com/open?id=0B_QsfZid_jtZQWp3ZERkV0ZCQms)

I have done a little experimenting:
In my real project: I found that when I shot the ray up at my landscape from below, I ran into the same placement glitch as if I had shot it from above. That makes me wonder if the ray could be passing through the landscape.
In the test: Boxes appear as the should of the landscape is only a plane, but not if it is a real landscape.

Thank you all very much for the help!!

Could be. Could be that there are micro-gaps in the mesh or something.

Where did the terrain come from?

The terrain is a TerrainQuad generated from a RawHeightMap object. I generate the RawHeightMap randomly, adding in hills and such. . When it is flat I have no errors. I typically apply a scale to the terrain, but the error appears even when I do not call a scale function on it, or when I scale by 1.

All values in the rawHeightMap should be assigned, but since it is made with a float array, even unassigned values would have a value of 0. I just realized that may be it…a lot of my mis-placements occur aroundf y = zero. I’ll check and make sure every value actually is assigned to non-zero.

Ah, that is pretty critical information.

As I recall, the terrain quad has custom collideWith() processing that may be slightly more likely to have bugs than the regular mesh code.

However, note that you should be able to get the Y values directly from the TerrainQuad object without doing (potentially slow) Ray collisions.
http://javadoc.jmonkeyengine.org/com/jme3/terrain/geomipmap/TerrainQuad.html#getHeight-com.jme3.math.Vector2f-

2 Likes

That fixed it perfectly!!! Thank you very much for the help!

For future reference: the new algorithm is

    public Vector3f coordinateOf(int x, int z)
    {
        float y = terrain.getHeight(new Vector2f(x, z));
        if(y != y) //If true, y is NaN. y == Float.NaN is always false, no matter what (https://stackoverflow.com/questions/9341653/float-nan-float-nan)
        {
            return null;
        }
        return new Vector3f(x, y, z);
    }

getHeight can return NaN, so be careful!

Note: that’s precisely what Float.isNaN() will do but wouldn’t require the really long comment and stackoverflow link. :slight_smile:

The extra method call is likely optimized away by hotspot, too.

4 Likes