[Solved] Storing a spatial's BoundingVolume


#1

I’m trying to store the bounding volumes of certain models to do ray casting and collisions on the server.

I loop through all of the loaded models when the server is initiated, store the bounding volumes, and delete the reference to the model to prevent an out of memory error.

       //init collidable item on server
         BoundingVolume bv = item.getWorldBound();
         collidableItems.add(bv);
         assetManager.deleteFromCache(item.getKey());

But when I try to test for a collision using the following code, it appears as though the shape of the model is ignored, and the bounding volume instead takes the shape of the blue box that appears around the Spatial when opened in the scene composer.

   //this code returns a huge bounding box rather than the model's shape
         BoundingVolume bounds = (BoundingVolume) collidableItems.get(i);
         bounds.collideWith(collidable, results);

I have no problem with single player or client side collision when I can test collisions using the original spatial as opposed to extracting its bounding volume

     //this code returns proper collisions
         Spatial item = (Spatial) collidableItems.get(i);
         item.collideWith(collidable, results);

Any ideas how I can get this working or achieve the same thing in any other ways? Thanks.


#2

A bounding volume is not a mesh. It’s a simplified shape (sphere or box) that represents the object.

It is probably not what you want. But then again, neither is the mesh since that’s generally WAAAAY too detailed for hit checks.


#3

What else could I use aside from the mesh or a bounding volume? For projectiles and players I typically use a simple bounding box or bounding sphere to test spell and weapon hits on players and NPCs, but for this current situation, I’m trying to cast a ray to see where spells and projectiles hit the terrain on the server. So I’ve been assuming I need to get a bounding volume or some type of collision object with the exact shape of the terrain on the server in order to have accurate results

I was able to set up the bullet physics system on the server correctly - I can use the CollisionShapeFactory to create a rigidbody control with the shape of the terrain on the server, but I can’t seem to find a way to create anything else with the same shape as the terrain to do simple ray casting. Worst case scenario I can toss my own physics system and use physics ray casting wit the BulletAppState on the server, but I thought that would be even less efficient


#4

Where did the terrain come from? A height map? Hand crafted in Blender? Delivered by elves on your door step? :slight_smile:

If it’s JME terrain then you may just want the height map to do ray tests. Or just use the mesh itself instead of doing math.

I had no idea you were talking about terrain because I didn’t see that in your first post. Terrain may be something you want an accurate mesh for. For mobs, objects, etc. probably not. You generally want simplified collision shapes for those so that bullets don’t hit people in the hair, flowing scarf, cape, etc…


#5

Yes I’m using a JME terrain I made in the terrain editor, I should have been more specific about that in my original post.

Is there a way I can store the Terrain’s mesh without storing all of the extra visual data that isn’t necessary on the server? Otherwise I run into a DirectMemoryError when i try storing the terrain as a spatial on the server with all of its texture and material data I don’t need on the server.

I can do CollisionShape cs = CollisionShapeFactory.createMeshShape(item); to create a mesh with the correct shape for objects that use bullet physics, but I can’t use CollisionShapes for normal ray casting and I don’t know any other way to get just a mesh’s shape without all the extra data :frowning: that’s what initially lead me to try and store the Terrain’s shape in a bounding volume, but it looks like that doesn’t work unfortunately

I guess the question I should be asking (I think), is how do you extract just the mesh from a Spatial?


#6

If the spatial is a geometry, you can use Geometry.getMesh() to access the mesh.


#7

But also note that JME terrain is based on a heightmap and there should be a way to get that data, also. That may or may not be “good enough” for what you want to do on the server. If you go that route then you may be able to just cut-paste the ray collision that JME’s terrain classes use against the heightmap data.

Else, yeah, Geometry.getMesh() and then keep track of the world transform.


#8

Heres a hack of the SDK code for grabbing geometries from a heightmap generated terrain and or a model.

    //Merges terrain meshes into one mesh.
    public Mesh terrain2mesh(Terrain terr) {
        float[] heights = terr.getHeightMap();
        int length = heights.length;
        int side = (int) FastMath.sqrt(heights.length);
        float[] vertices = new float[length * 3];
        int[] indices = new int[(side - 1) * (side - 1) * 6];

//        Vector3f trans = ((Node) terr).getWorldTranslation().clone();
        Vector3f trans = new Vector3f(0, 0, 0);
        trans.x -= terr.getTerrainSize() / 2f;
        trans.z -= terr.getTerrainSize() / 2f;
        float offsetX = trans.x;
        float offsetZ = trans.z;

        // do vertices
        int i = 0;
        for (int z = 0; z < side; z++) {
            for (int x = 0; x < side; x++) {
                vertices[i++] = x + offsetX;
                vertices[i++] = heights[z * side + x];
                vertices[i++] = z + offsetZ;
            }
        }

        // do indexes
        i = 0;
        for (int z = 0; z < side - 1; z++) {
            for (int x = 0; x < side - 1; x++) {
                // triangle 1
                indices[i++] = z * side + x;
                indices[i++] = (z + 1) * side + x;
                indices[i++] = (z + 1) * side + x + 1;
                // triangle 2
                indices[i++] = z * side + x;
                indices[i++] = (z + 1) * side + x + 1;
                indices[i++] = z * side + x + 1;
            }
        }

        Mesh mesh2 = new Mesh();
        mesh2.setBuffer(VertexBuffer.Type.Position, 3, vertices);
        mesh2.setBuffer(VertexBuffer.Type.Index, 3, indices);
        mesh2.updateBound();
        mesh2.updateCounts();

        return mesh2;
    }

    //Gathers all geometries in supplied node into supplied List. Uses 
    //terrain2mesh to merge found Terrain meshes into one geometry prior to 
    //adding. Scales and sets translation of merged geometry.
    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);
                System.out.println("findGeometries " + spatial.getName());
            } else if (spatial instanceof Node) {
                if (spatial instanceof Terrain) {
                    Mesh merged = this.terrain2mesh((Terrain) spatial);
                    Geometry g = new Geometry("mergedTerrain");
                    g.setMesh(merged);
                    g.setLocalScale(spatial.getLocalScale());
                    g.setLocalTranslation(spatial.getLocalTranslation());
                    geoms.add(g);
                    System.out.println("findGeometries " + spatial.getName());
                } else {
                    findGeometries((Node) spatial, geoms);
                }
            }       
        }
//        System.out.println(geoms);
        return geoms;
    }

Feed in a node where your terrains are located.

        Mesh mesh = new Mesh();
        GeometryBatchFactory.mergeGeometries(findGeometries(app.getRootNode(),
                new LinkedList<>()), mesh);

#9

Thanks for the help, I’m making progress but It still looks like something is going wrong somewhere. I’m not sure if it’s a bug with mesh collision or just something I’m still doing wrong.

For the terrain, I tried to I use the geometry2mesh() method and stored the mesh for the terrain, but I get the same problem where the collision zone doesn’t match the terrain, and instead it returns a collision as long as the projectile intersects this blue box that appears around the terrain in the scene composer

       if(item instanceof Terrain){
           Mesh mesh = terrain2mesh(terr);
           mesh.getBound().setCenter(item.getWorldTranslation());
           collidableItemss.add(mesh);
        }

I tried a different method for the terrain that got close to working, but with that code the collision ignored the terrain’s height and always caused a collision with a Y-value of zero.

                              if(item instanceof Terrain){
                                    TerrainQuad terr = (TerrainQuad) item;
                                    ArrayList children = new ArrayList();
                                    terr.getAllTerrainPatches(children);

                                    for(int v = 0 ; v < children.size(); v++){
                                        TerrainPatch tp = (TerrainPatch) children.get(v); //tried to use this instead of a geometry, but it also did not work
       
                                        Geometry geom = (Geometry)children.get(v);
                                        Mesh mesh = geom.getMesh();
                                        mesh.getBound().setCenter(tp.getWorldTranslation()); 
                                        collidableItems.add(mesh);
                                }

Aside from having issues with the terrain, I also cannot get a normal model like trees or rocks to return proper collision when using the mesh. The trees seem to have a collision zone too large, and the rocks don’t seem to have a consistent collision zone at all

             ArrayList alist = new ArrayList();
             alist = (ArrayList) findGeometries((Node) item, alist);
             for(int v = 0; v < alist.size(); v++){
                   Geometry geo = (Geometry) alist.get(v);
                    if(!geo.getName().equals("tree:Impostor")){ //makes sure to not collide with billboard tree imposters
                         Mesh mesh = geo.getMesh();
                         mesh.getBound().setCenter(geo.getWorldTranslation());
                         collidableItems.add(mesh);
                     }
              }

I turned terrain collision off for this example video to show the weird results with the world models.

if I switch back to using the original Spatial object as the collidable like I showed in my orginal post…

as opposed to how I’m doing things with the meshes now:

     Mesh mesh = (Mesh ) collidableItems.get(i);
     mesh.getBounds().collideWith(collidable, results);

… then the hitboxes match every model (and the terrain) perfectly on the server just like they usually would in single player- as soon as I do collision with the mesh, I get these weird results.

Does it look like I may be doing the mesh collision wrong, otherwise is this possibly a bug with mesh collision shapes?


#10

This is not doing what you think.

Essentially, you have a mesh of points. Those points are used to calculate a bounding box. Your telling the calculated bounding box that it should be in a different place. The points are still exactly where they were before.

Given your relative skill level, it might be easier just to load the scene graph on your server until you learn how to invert transforms and stuff.


#11

Also… this is not colliding with the mesh. Just the bounds.

Mesh != bounds
Bounds != mesh.

Totally different things. Repeat as needed until that clicks.


#12

I can do this for now with this map since it’s very small, but eventually I will need to figure this out for larger maps that load a few tiles at a time and cause a memory error if stored on the server

Is there anywhere you could point me to in order to start learning how to do so?

What would be the correct way to collide a Ray with a mesh once it’s properly positioned? The only other way I could think of is to test the ray against each triangle contained in the mesh


#13

http://javadoc.jmonkeyengine.org/com/jme3/scene/Mesh.html#collideWith-com.jme3.collision.Collidable-com.jme3.math.Matrix4f-com.jme3.bounding.BoundingVolume-com.jme3.collision.CollisionResults-

…which will actually do the inverse transform for you I guess.

The idea is that you don’t move the mesh into ray space but move the ray into mesh space. But the mesh.collideWith() method will do that for you if you give it all of the right parameters.


#14

Using the mesh.collideWith() method worked, thanks! :grin:

I stored the original Geometry’s bounds and world matrix alongside each mesh, and used those as the parameters for the Mesh.collideWith() method. Now the collisions are turning out perfectly. I appreciate all the help :slight_smile: