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.