package electrosphere.server.pathfinding; import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import electrosphere.server.pathfinding.blocker.NavBlocker; import electrosphere.server.pathfinding.navmesh.NavCube; import electrosphere.server.pathfinding.navmesh.NavMesh; import electrosphere.server.pathfinding.navmesh.NavShape; import electrosphere.server.terrain.manager.ServerTerrainChunk; import java.util.LinkedList; import java.util.List; import org.joml.Vector2i; import org.joml.Vector4i; /** * * @author satellite */ public class NavMeshUtils { static float NAVMESH_PHASE_ONE_DISPARITY_TOLERANCE = 0.5f; static float NAVMESH_PHASE_TWO_DISPARITY_TOLERANCE = 0.5f; public static NavMesh createMeshFromChunk(ServerTerrainChunk chunk, NavBlocker navBlocker){ NavMesh rVal = Globals.navMeshManager.createNavMesh(); float[][] heightMap = chunk.getWeights()[0]; boolean[][] navMeshGeneratorMask = navBlocker.getHeightfieldBlocker(); List firstPassBoxes = new LinkedList(); int numInCurrent = 0; float currentMin = 0; float currentMax = 0; int startPos = 0; int endPos = 0; for(int x = 0; x < Globals.serverTerrainManager.getAugmentedChunkWidth() - 1; x++){ numInCurrent = 0; currentMin = 0; currentMax = 0; for(int y = 0; y < Globals.serverTerrainManager.getAugmentedChunkWidth(); y++){ //create node if(navMeshGeneratorMask[x][y]){ if(numInCurrent > 0){ firstPassBoxes.add(new FirstPhaseBox(x,startPos,endPos,currentMin,currentMax)); } numInCurrent = 0; } else { if(numInCurrent == 0){ currentMin = heightMap[x][y]; currentMax = heightMap[x][y]; startPos = y; endPos = y+1; numInCurrent = 1; } else { if(currentMin > heightMap[x][y]){ if(currentMax - heightMap[x][y] < NAVMESH_PHASE_ONE_DISPARITY_TOLERANCE){ currentMin = heightMap[x][y]; endPos = y; numInCurrent++; } else { //expel previous rectangle firstPassBoxes.add(new FirstPhaseBox(x,startPos,endPos,currentMin,currentMax)); //start new one currentMin = heightMap[x][y]; currentMax = heightMap[x][y]; startPos = y; endPos = y+1; numInCurrent = 1; } } else if(currentMax < heightMap[x][y]){ if(heightMap[x][y] - currentMin < NAVMESH_PHASE_ONE_DISPARITY_TOLERANCE){ currentMin = heightMap[x][y]; endPos = y; numInCurrent++; } else { //expel previous rectangle firstPassBoxes.add(new FirstPhaseBox(x,startPos,endPos,currentMin,currentMax)); //start new one currentMin = heightMap[x][y]; currentMax = heightMap[x][y]; startPos = y; endPos = y+1; numInCurrent = 1; } } else { endPos = y; numInCurrent++; } } } // if(x > 0){ // //add back neighbor // } // if(y > 0){ // //add back neighbor // } } //close off last box on row // FirstPhaseBox boxy = new FirstPhaseBox(1, 1, 1, 1, 1); firstPassBoxes.add(new FirstPhaseBox(x,startPos,endPos,currentMin,currentMax)); } //phase two //??? List secondPhaseBoxes = new LinkedList(); List toRemove = new LinkedList(); for(FirstPhaseBox firstPhaseBox : firstPassBoxes){ SecondPhaseBox newBox = new SecondPhaseBox( firstPhaseBox.x,firstPhaseBox.yStart, firstPhaseBox.x+1,firstPhaseBox.yEnd, firstPhaseBox.minHeight,firstPhaseBox.maxHeight); // System.out.println( // firstPhaseBox.x + " " + // firstPhaseBox.yStart + " " + // (firstPhaseBox.x+1) + " " + // firstPhaseBox.yEnd + " " + // firstPhaseBox.minHeight + " " + // firstPhaseBox.maxHeight // ); // System.out.println( // "(" + (newBox.boundMinX + chunk.getWorldX() * Globals.serverTerrainManager.getChunkWidth()) + ", " + // (newBox.minHeight - 0.5f) + ", " + // (newBox.boundMinY + chunk.getWorldY() * Globals.serverTerrainManager.getChunkWidth()) + ") (" + // (newBox.boundMaxX + chunk.getWorldX() * Globals.serverTerrainManager.getChunkWidth()) + ", " + // (newBox.maxHeight + 0.5f) + ", " + // (newBox.boundMaxY + chunk.getWorldY() * Globals.serverTerrainManager.getChunkWidth()) + ")" // ); toRemove.clear(); //TODO: iterate this backwards and check for x adjacency, if no adjacency end loop early List neighborUpdateList = new LinkedList(); for(SecondPhaseBox interim : secondPhaseBoxes){ if(firstPhaseBox.yStart == interim.boundMinY && firstPhaseBox.yEnd == interim.boundMaxY && firstPhaseBox.x == interim.boundMaxX){ for(SecondPhaseBox neighbor : interim.getNeighbors()){ neighborUpdateList.add(neighbor); // System.out.println("ADD NEIGHBOR: " + neighbor.boundMaxX + " " + neighbor.boundMaxY + " " + neighbor.boundMinX + " " + neighbor.boundMinY); } for(SecondPhaseBox update : neighborUpdateList){ update.removeNeighbor(interim); interim.removeNeighbor(update); update.addNeighbor(newBox); newBox.addNeighbor(update); } toRemove.add(interim); newBox.setBoundMinX(interim.getBoundMinX()); //TODO: calculate new min/max height } else { //because of the way the rectangles are constructed, we can never have a neighbor along y axis //only neighbors will be behind us if(newBox.boundMinX == interim.boundMaxX && !toRemove.contains(interim)){ if( (interim.boundMaxY < newBox.boundMaxY && interim.boundMaxY > newBox.boundMinY) || (interim.boundMinY < newBox.boundMaxY && interim.boundMinY > newBox.boundMinY) || (newBox.boundMaxY < interim.boundMaxY && newBox.boundMaxY > interim.boundMinY) || (newBox.boundMinY < interim.boundMaxY && newBox.boundMinY > interim.boundMinY) ){ // System.out.println("ADD INTERIM: " + interim.boundMaxX + " " + interim.boundMaxY + " " + interim.boundMinX + " " + interim.boundMinY); newBox.addNeighbor(interim); interim.addNeighbor(newBox); } } } } secondPhaseBoxes.removeAll(toRemove); secondPhaseBoxes.add(newBox); } int id = 0; for(SecondPhaseBox box : secondPhaseBoxes){ box.setId(id); // System.out.println("getId:" + box.getId()); id++; // System.out.println( // "(" + (box.boundMinX + chunk.getWorldX() * Globals.serverTerrainManager.getChunkWidth()) + ", " + // (box.minHeight - 0.5f) + ", " + // (box.boundMinY + chunk.getWorldY() * Globals.serverTerrainManager.getChunkWidth()) + ") (" + // (box.boundMaxX + chunk.getWorldX() * Globals.serverTerrainManager.getChunkWidth() - 1) + ", " + // (box.maxHeight + 0.5f) + ", " + // (box.boundMaxY + chunk.getWorldY() * Globals.serverTerrainManager.getChunkWidth()) + ")" // ); // System.out.println(box.neighbors.size()); //why the -1? I think the array fiddling above is causing the bounds to be off normally //this fixes that NavCube cube = new NavCube( box.boundMinX + chunk.getWorldX() * Globals.serverTerrainManager.getChunkWidth(), box.minHeight - 0.5f, box.boundMinY + chunk.getWorldY() * Globals.serverTerrainManager.getChunkWidth(), box.boundMaxX + chunk.getWorldX() * Globals.serverTerrainManager.getChunkWidth(), box.maxHeight + 0.5f, box.boundMaxY + chunk.getWorldY() * Globals.serverTerrainManager.getChunkWidth() ); rVal.addNode(cube); } id = 0; for(NavShape shape : rVal.getNodes()){ NavCube cube = (NavCube)shape; SecondPhaseBox currentBox = secondPhaseBoxes.get(id); // System.out.println("getId:" + currentBox.getId()); id++; for(SecondPhaseBox neighbor : currentBox.getNeighbors()){ //TODO: solve bug where items are getting picked up from not in second phase boxes if(!secondPhaseBoxes.contains(neighbor)){ LoggerInterface.loggerGameLogic.WARNING("Found bad neighbor adjacency in navmesh generation"); for(SecondPhaseBox newNeighbor : secondPhaseBoxes){ if(newNeighbor.boundMaxX >= neighbor.boundMaxX && newNeighbor.boundMaxY >= neighbor.boundMaxY && newNeighbor.boundMinX <= neighbor.boundMinX && newNeighbor.boundMinY <= neighbor.boundMinY ){ cube.addNodeNeighbor(rVal.getNodes().get(newNeighbor.getId())); LoggerInterface.loggerGameLogic.WARNING("Managed to replace bad neighbor"); break; } } // System.out.println("ERR!"); // System.out.println(neighbor.boundMaxX + " " + neighbor.boundMaxY + " " + neighbor.boundMinX + " " + neighbor.boundMinY); } else { cube.addNodeNeighbor(rVal.getNodes().get(neighbor.getId())); } } // System.out.println(cube.getNodeNeighbors().size()); } // System.out.println(); // System.out.println(); // System.out.println(secondPhaseBoxes.size()); return rVal; } static class FirstPhaseBox { int x; int yStart; int yEnd; float minHeight; float maxHeight; public FirstPhaseBox(int x, int yStart, int yEnd, float minHeight, float maxHeight) { this.x = x; this.yStart = yStart; this.yEnd = yEnd; this.minHeight = minHeight; this.maxHeight = maxHeight; } public int getX() { return x; } public int getyStart() { return yStart; } public int getyEnd() { return yEnd; } public float getMinHeight() { return minHeight; } public float getMaxHeight() { return maxHeight; } } static class SecondPhaseBox { float minHeight; float maxHeight; int boundMinX; int boundMinY; int boundMaxX; int boundMaxY; List neighbors = new LinkedList(); int id = -1; SecondPhaseBox(int boundMinX, int boundMinY, int boundMaxX, int boundMaxY, float minHeight, float maxHeight){ this.boundMinX = boundMinX; this.boundMinY = boundMinY; this.boundMaxX = boundMaxX; this.boundMaxY = boundMaxY; this.minHeight = minHeight; this.maxHeight = maxHeight; } float getMinHeight(){ return minHeight; } float getMaxHeight(){ return maxHeight; } public int getBoundMinX() { return boundMinX; } public int getBoundMinY() { return boundMinY; } public int getBoundMaxX() { return boundMaxX; } public int getBoundMaxY() { return boundMaxY; } public void setMinHeight(float minHeight) { this.minHeight = minHeight; } public void setMaxHeight(float maxHeight) { this.maxHeight = maxHeight; } public void setBoundMinX(int boundMinX) { this.boundMinX = boundMinX; } public void setBoundMinY(int boundMinY) { this.boundMinY = boundMinY; } public void setBoundMaxX(int boundMaxX) { this.boundMaxX = boundMaxX; } public void setBoundMaxY(int boundMaxY) { this.boundMaxY = boundMaxY; } public List getNeighbors() { return neighbors; } public void addNeighbor(SecondPhaseBox neighbor){ neighbors.add(neighbor); } public void removeNeighbor(SecondPhaseBox neighbor){ neighbors.remove(neighbor); } public int getId() { return id; } public void setId(int id) { this.id = id; } } }