1

A normal level (picture 1) A normal level (picture 1)

The squares are tiles and the red dots are vertices (picture 2) The squares are tiles and the red dots are vertices (picture 2)

What I'm trying to make is a set of blocks (grey things) which can be destroyed (removed) so water can go trough them. As shown in the picture this works most of the time, some blocks have already been removed and water is flowing trough the gaps. The problem is that it doesn't work all the time. Sometimes when a block is removed water doesn't fill the gap and there comes a hole in the water.

The code

public class WaterManager(){

    //level is the map, it is the parent containing the tile mesh and the water
    public Node level;
    Mesh water = new Mesh();
    Geometry geo;
    Vector3f[] vertices;
    //subdivision is used so the mesh is more detailed, a subdivision of 2 means there are 9 vertices per tile (cube) as shown on picture 2 
    public int subdivision = 2;
    //The amount of vertices on the x and z-axis
    int xVertices, zVertices;
    //The size of one tile, this is 10 in my case
    float cubeSize;
    //The amount of tiles on the x and z-axis
    int xCubes, zCubes;
    List<Integer> triangles = new ArrayList<>();
    
    List<Vector3f> emptyCubes = new ArrayList<>();
    List<Vector3f> newEmptyCubes = new ArrayList<>();
    Dictionary<Vector3f, Tile> tiles = new Hashtable<Vector3f, Tile>();

    public void Loop(Vector3f startPos){
        
        newEmptyCubes.clear();
        emptyCubes.clear();
        
        boolean loop = true;
        
        List<Vector3f> temp = new ArrayList<>();
        
        //This is the first check to get all the connected blocks that should be filled with water
        //including the block clicked on
        GetConnectedEmptyTiles(startPos, newEmptyCubes);
        
        //If there is more then 1 block which should be filled than loop through all the blocks
        //and get the ones connected to those that should be filled with water
        if (newEmptyCubes.size() > 1){
            do{
                for(Vector3f cube : newEmptyCubes){
                    GetConnectedEmptyTiles(cube, temp);
                }
                emptyCubes.addAll(newEmptyCubes);
                newEmptyCubes.clear();
                if (temp.size() <= 0)
                    loop = false;
                newEmptyCubes.addAll(temp);
                temp.clear();
            
            }while(loop);
        } else emptyCubes.addAll(newEmptyCubes);
        
        //This creates the mesh
        PaintEmptyTiles();        
    }

    private void GetConnectedEmptyTiles(Vector3f pos, List<Vector3f> list){
        
        //The blocks connected to a given posistion including the given position
        Vector3f[] connectedTilesPlusPos = new Vector3f[]{
            new Vector3f(pos.x + cubeSize, pos.y, pos.z),
            new Vector3f(pos.x - cubeSize, pos.y, pos.z),
            new Vector3f(pos.x, pos.y, pos.z + cubeSize),
            new Vector3f(pos.x, pos.y, pos.z - cubeSize),
            pos
        };
        
        //If the removed block is not connected to water then it shouldn't be filled
        //with water
        boolean connectedToWater = false;
        
        List<Vector3f> tempList = new ArrayList<>();
        
        //Check if the given position exists
        if (tiles.get(pos) != null){
            System.out.println("tile has water: " + tiles.get(pos).water);
            //Loop through all the connected blocks including the given position
            //and if they should be filled with water than add them to tempList
           for(Vector3f tile : connectedTilesPlusPos){
               if (tiles.get(tile) != null){
                   //Check if there is a block at the position (tile), if there
                   // is not than it can be filled with water
                   if (tiles.get(tile).tileObj == null){
                       //Check if the position already has water in it **(this is were it goes wrong)**
                       if (tiles.get(tile).water == false){
                            tempList.add(tile);
                            tiles.get(tile).water = true;
                        } else connectedToWater = true;  //If there is already water attached to the
                       //the given position then it is connected to water
                    }
                }
            }
           if (!connectedToWater)
               tempList.clear();
           else list.addAll(tempList);
        }
    }
    private void PaintEmptyTiles(){
        for(Vector3f cube : emptyCubes){
            MakeTriangles(cube);
        }
        MakeMesh();        
    }
    public void MakeTriangles(Vector3f pos){
        //Gets an array of vertices that are used to create water at a given position as shown in picture 2
        Vector3f[] verticesToUse = GetVerticesForPosition(pos);
        
        List<Integer> indexes = new ArrayList<>();
        for(int i = 0; i < vertices.length; i++){
            for(Vector3f vert : verticesToUse){
                if (vertices[i].equals(vert)){
                    indexes.add(i);
                }
            }
        }
     
        int xVerticesSize = rowSize(indexes, tiles.get(pos));
        int zVerticesSize = (int)indexes.size() / xVerticesSize;
        int verticesLenght = (int)Math.round(Math.sqrt(vertices.length));//The amount of vertices in one row.
        int i = 0;
        for(int x = 0; x < xVerticesSize; x++){
            for (int z = 0; z < zVerticesSize; z++){
                
                int vert = indexes.get(i);
                
                if (indexes.contains(vert + verticesLenght + 1)){
                    //Creates a quad, since the water is build from small quads
                    triangles.add(vert);
                    triangles.add(vert + 1);
                    triangles.add(vert + verticesLenght + 1);
                    triangles.add(vert + verticesLenght + 1);
                    triangles.add(vert + verticesLenght);
                    triangles.add(vert);
                }
                
                i++;                
              
            }
        }
    }
        private Vector3f[] GetVerticesForPosition(Vector3f pos){
        
        List<Vector3f> tempVertices = new ArrayList<>();
        
        for (Vector3f vertex : vertices) {
            if (vertex != null){
                float distance = DistanceOnXAndZ(pos, vertex);
                float roundedDistance = roundedTo(distance, cubeSize / subdivision);
                if (distance < cubeSize || (roundedDistance <= cubeSize && vertex.y <= -100 )){
                    tempVertices.add(vertex);
                }
            }
        }
        
        Vector3f[] returnVertices = new Vector3f[tempVertices.size()];
        returnVertices = tempVertices.toArray(returnVertices);
        
        return returnVertices;
    }
    
    //This calculates the distance from a point to another point but only using the x and z-axis
    private float DistanceOnXAndZ(Vector3f origin, Vector3f target){
        float originX = origin.x;
        float originZ = origin.z;
        
        float targetX = target.x;
        float targetZ = target.z;
        
        float xDistance = targetX - originX;
        float zDistance = targetZ - originZ;
        
        float distance = (float)Math.sqrt((xDistance * xDistance) + (zDistance * zDistance));
        
        return distance;
    }
        private void MakeMesh(){
        water.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
        
        int[] tri = new int[triangles.size()];
        for(int i = 0; i < triangles.size(); i++){tri[i] = triangles.get(i);}
        
        water.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(tri));
        water.updateBound();
        
        geo = new Geometry("Water", water);
        Material mat = new Material(assetManager,
        "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geo.setMaterial(mat);
        level.attachChild(geo);
    }
}

//The tile Object
public class Tile {    

    //not used anymore, but not deleted yet
    public enum CellType {        
        LEFTORRIGHTSIDE, OTHER
    }
    public CellType cellType;
    
    //The cube the Tile contains, if there is no cube then this is null
    public Spatial tileObj;
    
    //True if the tile contains water, false if not
    public boolean water = false;
    
    public Tile(CellType type, Spatial obj){
        //Nothing is done with cellType
        cellType = type;
        tileObj = obj;
    }
    
}

The reason

The reason this occurs is still unknown, but I think it has something to do with the water boolean in Tile already set to true even when it has a cube (grey thing) in it.

Things to know

  • tiles is a list containing all the positions that contain a tile, if it contains a block then tileObj is not null. If the position contains water then the water boolean should be true.
  • PaintEmptyTiles(), in Loop() is a function that creates the water mesh
  • When a block is destroyed by clicking on it, the Loop() function is called and the tileObj in Tile is set to null in the tiles list
  • A tile is a square with, containing either a grey block, water or nothing.
  • 3
    The best thing for you to do would be to step through this with a debugger, so you can see the value of each variable at each step. – Dawood ibn Kareem Mar 18 '20 at 11:16
  • 1
    Please post a [mcve] - that is, complete code that we can compile and see. We don't know how `tiles.get(tile).water` is defined (is it a `boolean` or a `Boolean`?), or what `tiles.get` is (is it guaranteed to return the same object for the same input?) , etc. It's not useful to post a little bit of code with a lot of relevant code removed. – Erwin Bolwidt Mar 18 '20 at 11:27
  • @ErwinBolwidt I have edited the post and all the code used to make the water is shown, except for how the vertices are made and at which positions but that is irrelevant. I don't think the compile will work since the code uses some of jMonkeyEngines build in features and some other things are missing which are irrelevant. – Bernd Koggel Mar 18 '20 at 16:56
  • @DawoodsaysreinstateMonica I have done that and I found that the issue is something different then I thought, the problem is that although a tile has a tileObj or seems to have when the GetConnectedEmptyTiles() is called it still becomes water, or water is set to true, somehow. – Bernd Koggel Mar 18 '20 at 17:34

0 Answers0