Pathfinding!

This commit is contained in:
austin 2021-11-03 21:30:41 -04:00
parent 3185866fa4
commit 5f4eb22fa9
23 changed files with 1001 additions and 31 deletions

View File

@ -14,6 +14,7 @@
"graphicsDebugDrawCollisionSpheres" : false,
"graphicsDebugDrawPhysicsObjects" : false,
"graphicsDebugDrawMovementVectors" : false
"graphicsDebugDrawMovementVectors" : false,
"graphicsDebugDrawNavmesh" : true
}

BIN
assets/Models/waypoint1.fbx Normal file

Binary file not shown.

View File

@ -29,7 +29,7 @@ import electrosphere.game.server.saves.SaveUtils;
import electrosphere.game.server.terrain.models.TerrainModification;
import electrosphere.game.server.town.Town;
import electrosphere.game.server.world.MacroData;
import electrosphere.game.server.world.datacell.DataCellManager;
import electrosphere.game.server.datacell.DataCellManager;
import electrosphere.game.state.MicroSimulation;
import electrosphere.logger.LoggerInterface;
import electrosphere.main.Globals;
@ -42,6 +42,9 @@ import electrosphere.renderer.ActorUtils;
import electrosphere.renderer.Model;
import electrosphere.renderer.RenderUtils;
import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.game.server.pathfinding.NavMeshPathfinder;
import electrosphere.game.server.pathfinding.navmesh.NavCube;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@ -591,6 +594,13 @@ public class LoadingThread extends Thread {
Entity bow = ItemUtils.spawnBasicItem("Bow");
EntityUtils.getPosition(bow).set(1, 1, 2);
NavMeshPathfinder.navigatePointToPointInMesh(Globals.navMeshManager.getMeshes().get(0), new Vector3d(10,0,5), new Vector3d(5,0,10));
// NavMesh mesh = new NavMesh();
// NavCube cube = new NavCube(5,0,0,10,5,5);
// mesh.addNode(cube);
// Globals.navMeshManager.addMesh(mesh);
// Entity fallOak = FoliageUtils.spawnBasicFoliage("FallOak1");
// EntityUtils.getPosition(fallOak).set(1,0,3);
//

View File

@ -0,0 +1,9 @@
package electrosphere.entity.types.waypoint;
/**
*
* @author amaterasu
*/
public class WaypointUtils {
}

View File

@ -2,7 +2,7 @@ package electrosphere.game.client.world;
import electrosphere.game.server.world.*;
import electrosphere.game.server.terrain.manager.ServerTerrainManager;
import electrosphere.game.server.world.datacell.ServerDataCell;
import electrosphere.game.server.datacell.ServerDataCell;
import java.util.List;
import org.joml.Vector2f;
import org.joml.Vector3f;

View File

@ -37,6 +37,7 @@ public class UserSettings {
boolean graphicsDebugDrawCollisionSpheres;
boolean graphicsDebugDrawPhysicsObjects;
boolean graphicsDebugDrawMovementVectors;
boolean graphicsDebugDrawNavmesh;
@ -88,6 +89,10 @@ public class UserSettings {
public int getGraphicsPerformanceLODChunkRadius() {
return graphicsPerformanceLODChunkRadius;
}
public boolean graphicsDebugDrawNavmesh() {
return graphicsDebugDrawNavmesh;
}
@ -116,6 +121,7 @@ public class UserSettings {
rVal.graphicsDebugDrawCollisionSpheres = false;
rVal.graphicsDebugDrawMovementVectors = false;
rVal.graphicsDebugDrawPhysicsObjects = false;
rVal.graphicsDebugDrawNavmesh = false;
rVal.graphicsPerformanceLODChunkRadius = 5;
rVal.graphicsFOV = 90.0f;
rVal.graphicsPerformanceDrawShadows = true;

View File

@ -1,4 +1,4 @@
package electrosphere.game.server.world.datacell;
package electrosphere.game.server.datacell;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;

View File

@ -1,4 +1,4 @@
package electrosphere.game.server.world.datacell;
package electrosphere.game.server.datacell;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;

View File

@ -1,4 +1,4 @@
package electrosphere.game.server.world.datacell;
package electrosphere.game.server.datacell;
import electrosphere.entity.Entity;
import electrosphere.entity.types.creature.CreatureUtils;
@ -6,6 +6,8 @@ import electrosphere.entity.types.item.ItemUtils;
import electrosphere.entity.types.structure.StructureUtils;
import electrosphere.net.server.Player;
import electrosphere.game.server.character.Character;
import electrosphere.game.server.pathfinding.NavMeshUtils;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import electrosphere.main.Globals;
import electrosphere.net.parser.net.message.NetworkMessage;
import java.util.LinkedList;
@ -22,6 +24,7 @@ public class ServerDataCell {
List<Entity> loadedEntities = new LinkedList();
List<Player> activePlayers = new LinkedList();
NavMesh navMesh;
/**
@ -41,6 +44,7 @@ public class ServerDataCell {
//else create from scratch
EnvironmentGenerator.generatePlains(rVal.loadedEntities, worldX, worldY, Globals.serverTerrainManager.getRandomizerAtPoint(worldX, worldY));
}
rVal.navMesh = NavMeshUtils.createMeshFromChunk(Globals.serverTerrainManager.getChunk(worldX, worldY),Globals.navMeshManager.getBlockerCache().getBlocker(worldX, worldY));
return rVal;
}

View File

@ -1,11 +1,16 @@
package electrosphere.game.server.pathfinding;
import electrosphere.game.server.pathfinding.blocker.NavBlocker;
import electrosphere.game.server.pathfinding.blocker.NavTerrainBlockerCache;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import electrosphere.game.server.pathfinding.navmesh.NavShape;
import electrosphere.game.server.pathfinding.path.Waypoint;
import electrosphere.game.server.terrain.manager.ServerTerrainChunk;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.joml.Vector3d;
/**
*
@ -15,6 +20,34 @@ public class NavMeshManager {
List<NavMesh> meshes = new LinkedList();
Map<ServerTerrainChunk,ChunkMeshList> chunkToMeshListMap = new HashMap();
NavTerrainBlockerCache blockerCache = new NavTerrainBlockerCache();
public NavMesh createNavMesh(){
NavMesh rVal = new NavMesh();
meshes.add(rVal);
return rVal;
}
public void addMesh(NavMesh mesh){
meshes.add(mesh);
}
public List<NavMesh> getMeshes(){
return meshes;
}
public List<NavMesh> navigateMeshToMesh(NavMesh startMesh, NavMesh endMesh){
List<NavMesh> rVal = null;
return rVal;
}
public NavBlocker getNavBlockerForChunk(int x, int y){
NavBlocker rVal = null;
return rVal;
}
public NavTerrainBlockerCache getBlockerCache(){
return blockerCache;
}
}

View File

@ -0,0 +1,450 @@
package electrosphere.game.server.pathfinding;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.game.server.pathfinding.navmesh.NavCube;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import electrosphere.game.server.pathfinding.navmesh.NavShape;
import electrosphere.game.server.pathfinding.path.Waypoint;
import java.awt.geom.Line2D;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import org.joml.Vector3d;
/**
*
* @author amaterasu
*/
public class NavMeshPathfinder {
//TODO: add movement type mask to this function
public static List<Waypoint> navigatePointToPointInMesh(NavMesh mesh, Vector3d start, Vector3d end){
List<Waypoint> rVal = new LinkedList();
NavShape startNode = null;
NavShape endNode = null;
for(NavShape node : mesh.getNodes()){
if(node.containsPoint(start.x, start.y, start.z)){
startNode = node;
}
if(node.containsPoint(end.x, end.y, end.z)){
endNode = node;
}
if(startNode != null && endNode != null){
break;
}
}
//if the start and end nodes aren't both present, exit
//need to do broadphase still
if(startNode == null || endNode == null){
return null;
}
//theta *
List<NavShape> openSet = new LinkedList();
List<NavShape> closedSet = new LinkedList();
PriorityQueue<SetItem> pathItemsQueue = new PriorityQueue();
Map<NavShape,SetItem> pathItems = new HashMap();
openSet.add(startNode);
SetItem startSetItem = new SetItem(null,startNode,0,start);
startSetItem.currentPos = start;
pathItems.put(startNode, startSetItem);
pathItemsQueue.add(startSetItem);
while(!openSet.isEmpty()){
//get lowest cost item
SetItem currentItem = pathItemsQueue.poll();
NavShape currentNode = currentItem.getNode();
openSet.remove(currentNode);
//return path if found end
if(currentNode == endNode){
System.out.println("Found path!");
//TODO: return path
while(currentItem != null){
if(currentItem.node == endNode){
Entity waypoint = EntityUtils.spawnDrawableEntity("Models/waypoint1.fbx");
System.out.println(end);
EntityUtils.getPosition(waypoint).set(end.x,end.y + 1,end.z);
EntityUtils.getRotation(waypoint).rotateLocalX(-(float)Math.PI/2.0f);
} else {
Entity waypoint = EntityUtils.spawnDrawableEntity("Models/waypoint1.fbx");
System.out.println(currentItem.currentPos);
EntityUtils.getPosition(waypoint).set(currentItem.currentPos.x,currentItem.currentPos.y + 1,currentItem.currentPos.z);
EntityUtils.getRotation(waypoint).rotateLocalX(-(float)Math.PI/2.0f);
}
currentItem = currentItem.parent;
}
break;
}
closedSet.add(currentNode);
for(NavShape neighbor : currentNode.getNodeNeighbors()){
if(!closedSet.contains(neighbor)){
if(!openSet.contains(neighbor)){
Vector3d centerPoint = calculateCenterOfShape(neighbor);
SetItem newSetItem = new SetItem(currentItem,neighbor,currentItem.getCost() + (float)currentItem.currentPos.distance(centerPoint),centerPoint);
pathItems.put(neighbor, newSetItem);
pathItemsQueue.add(newSetItem);
openSet.add(neighbor);
}
//update vertex
updateVertices(currentItem, pathItems.get(neighbor),pathItemsQueue);
}
}
}
return null;
}
static void updateVertices(SetItem current, SetItem neighbor, PriorityQueue<SetItem> queue){
// This part of the algorithm is the main difference between A* and Theta*
if(current.getParent() != null){
LOSResult losResult = null;
Vector3d neighborCenter = calculateCenterOfShape(neighbor.node);
if(current.getParent().currentPos == null){
current.getParent().currentPos = calculateCenterOfShape(current.getParent().node);
}
Vector3d parentPos = current.getParent().currentPos;
if((losResult = lineOfSight(current.getParent(), current, neighbor))==null){
// Vector3d neighborCenter = calculateCenterOfShape(neighbor.node);
// Vector3d parentPos = current.getParent().currentPos;
float dist = (float)neighborCenter.distance(parentPos);
// If there is line-of-sight between parent(s) and neighbor
// then ignore s and use the path from parent(s) to neighbor
if(current.getParent().cost + dist < neighbor.cost){
// c(s, neighbor) is the Euclidean distance from s to neighbor
neighbor.cost = current.getParent().cost + dist;
neighbor.parent = current.getParent();
//update prio q
queue.remove(neighbor);
queue.add(neighbor);
// if(neighbor in open){
// open.remove(neighbor);
// }
// open.insert(neighbor, gScore(neighbor) + heuristic(neighbor));
}
} else {
//find midpoint
// Vector3d neighborCenter = calculateCenterOfShape(neighbor.node);
// Vector3d parentPos = current.getParent().currentPos;
if(losResult.hit){
float distToMidpoint = (float)neighborCenter.distance(losResult.currentPos);
float distToParent = (float)losResult.currentPos.distance(losResult.parentPos);
float dist = distToMidpoint + distToParent;
// If the length of the path from start to s and from s to
// neighbor is shorter than the shortest currently known distance
// from start to neighbor, then update node with the new distance
if(current.cost + dist < neighbor.cost){
neighbor.cost = current.cost + dist;
neighbor.parent = current;
//update prio q
queue.remove(neighbor);
queue.add(neighbor);
current.getParent().currentPos = losResult.parentPos;
current.currentPos = losResult.currentPos;
}
} else {
float dist = (float)neighborCenter.distance(parentPos);
// If there is line-of-sight between parent(s) and neighbor
// then ignore s and use the path from parent(s) to neighbor
if(current.getParent().cost + dist < neighbor.cost){
// c(s, neighbor) is the Euclidean distance from s to neighbor
neighbor.cost = current.getParent().cost + dist;
neighbor.parent = current.getParent();
//update prio q
queue.remove(neighbor);
queue.add(neighbor);
}
}
}
}
}
static class LOSResult{
Vector3d parentPos;
Vector3d currentPos;
boolean hit;
}
//calculates the line of sight ACROSS current FROM PARENT TO NEIGHBOR
static LOSResult lineOfSight(SetItem parent, SetItem current, SetItem neighbor){
if(parent.node instanceof NavCube && current.node instanceof NavCube && neighbor.node instanceof NavCube){
//get points on border of current and neighbor, this is one of the lines
double borderMinX = 0;
double borderMinZ = 0;
double borderMaxX = 0;
double borderMaxZ = 0;
NavCube parentCube = (NavCube)parent.node;
NavCube currentCube = (NavCube)current.node;
NavCube neighborCube = (NavCube)neighbor.node;
//resolve min/max pos
// Vector3d currentMin = currentCube.getMinPoint();
// Vector3d currentMax = currentCube.getMaxPoint();
// Vector3d neighborMin = neighborCube.getMinPoint();
// Vector3d neighborMax = neighborCube.getMaxPoint();
BoxInternalBorder parentCurrentBorder = getBoxInternalBorder(parentCube.getMinPoint(),parentCube.getMaxPoint(),currentCube.getMinPoint(),currentCube.getMaxPoint());
BoxInternalBorder currentChildBorder = getBoxInternalBorder(currentCube.getMinPoint(),currentCube.getMaxPoint(),neighborCube.getMinPoint(),neighborCube.getMaxPoint());
boolean intersectsParentBorder = Line2D.linesIntersect(
parentCurrentBorder.minPoint.x, parentCurrentBorder.minPoint.z,
parentCurrentBorder.maxPoint.x, parentCurrentBorder.maxPoint.z,
neighbor.currentPos.x, neighbor.currentPos.z,
parent.currentPos.x, parent.currentPos.z
);
boolean intersectsChildBorder = Line2D.linesIntersect(
currentChildBorder.minPoint.x, currentChildBorder.minPoint.z,
currentChildBorder.maxPoint.x, currentChildBorder.maxPoint.z,
neighbor.currentPos.x, neighbor.currentPos.z,
parent.currentPos.x, parent.currentPos.z
);
LOSResult rVal = new LOSResult();
if(intersectsParentBorder && intersectsChildBorder){
rVal.hit = false;
} else {
rVal.hit = true;
// rVal.currentPos = new Vector3d();
if(neighbor.currentPos.distance(currentChildBorder.minPoint) < neighbor.currentPos.distance(currentChildBorder.maxPoint)){
rVal.currentPos = currentChildBorder.minPoint;
} else {
rVal.currentPos = currentChildBorder.maxPoint;
}
if(parent.currentPos.distance(parentCurrentBorder.minPoint) < parent.currentPos.distance(parentCurrentBorder.maxPoint)){
rVal.parentPos = parentCurrentBorder.minPoint;
} else {
rVal.parentPos = parentCurrentBorder.maxPoint;
}
}
return rVal;
//the other line comes from earlier
// if(Line2D.linesIntersect(borderMinX, borderMinZ, borderMaxX, borderMaxZ, rayStart.x, rayStart.z, rayEnd.x, rayEnd.z)){
// return null;
// } else {
// double distMin = Line2D.ptLineDist(rayStart.x, rayStart.z, rayEnd.x, rayEnd.z, borderMinX, borderMinZ);
// double distMax = Line2D.ptLineDist(rayStart.x, rayStart.z, rayEnd.x, rayEnd.z, borderMaxX, borderMaxZ);
// if(distMin < distMax){
// return new Vector3d(borderMinX, 0, borderMinZ);
// } else {
// return new Vector3d(borderMaxX, 0, borderMaxZ);
// }
// }
}
return null;
}
static class BoxInternalBorder {
Vector3d minPoint = new Vector3d();
Vector3d maxPoint = new Vector3d();
}
static BoxInternalBorder getBoxInternalBorder(Vector3d currentMin, Vector3d currentMax, Vector3d neighborMin, Vector3d neighborMax){
BoxInternalBorder rVal = new BoxInternalBorder();
if(currentMin.x == neighborMax.x){
double targetX = currentMin.x;
if(currentMin.z >= neighborMin.z && currentMax.z <= neighborMax.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = currentMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = currentMax.z;
} else if(neighborMin.z >= currentMin.z && neighborMax.z <= currentMax.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = neighborMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = neighborMax.z;
} else if(currentMin.z >= neighborMin.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = currentMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = neighborMax.z;
} else if(neighborMin.z >= currentMin.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = neighborMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = currentMax.z;
} else {
//they all line up or something
rVal.minPoint.x = targetX;
rVal.minPoint.z = currentMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = currentMax.z;
}
}
if(currentMax.x == neighborMin.x){
double targetX = currentMax.x;
if(currentMin.z >= neighborMin.z && currentMax.z <= neighborMax.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = currentMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = currentMax.z;
} else if(neighborMin.z >= currentMin.z && neighborMax.z <= currentMax.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = neighborMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = neighborMax.z;
} else if(currentMin.z >= neighborMin.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = currentMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = neighborMax.z;
} else if(neighborMin.z >= currentMin.z){
rVal.minPoint.x = targetX;
rVal.minPoint.z = neighborMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = currentMax.z;
} else {
//they all line up or something
rVal.minPoint.x = targetX;
rVal.minPoint.z = currentMin.z;
rVal.maxPoint.x = targetX;
rVal.maxPoint.z = currentMax.z;
}
}
if(currentMin.z == neighborMax.z){
double targetZ = currentMin.x;
if(currentMin.x >= neighborMin.x && currentMax.x <= neighborMax.x){
rVal.minPoint.x = currentMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = currentMax.x;
rVal.maxPoint.z = targetZ;
} else if(neighborMin.x >= currentMin.x && neighborMax.x <= currentMax.x){
rVal.minPoint.x = neighborMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = neighborMax.x;
rVal.maxPoint.z = targetZ;
} else if(currentMin.x >= neighborMin.x){
rVal.minPoint.x = currentMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = neighborMax.x;
rVal.maxPoint.z = targetZ;
} else if(neighborMin.x >= currentMin.x){
rVal.minPoint.x = neighborMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = currentMax.x;
rVal.maxPoint.z = targetZ;
} else {
//they all line up or something
rVal.minPoint.x = currentMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = currentMax.x;
rVal.maxPoint.z = targetZ;
}
}
if(currentMax.z == neighborMin.z){
double targetZ = currentMax.x;
if(currentMin.x >= neighborMin.x && currentMax.x <= neighborMax.x){
rVal.minPoint.x = currentMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = currentMax.x;
rVal.maxPoint.z = targetZ;
} else if(neighborMin.x >= currentMin.x && neighborMax.x <= currentMax.x){
rVal.minPoint.x = neighborMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = neighborMax.x;
rVal.maxPoint.z = targetZ;
} else if(currentMin.x >= neighborMin.x){
rVal.minPoint.x = currentMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = neighborMax.x;
rVal.maxPoint.z = targetZ;
} else if(neighborMin.x >= currentMin.x){
rVal.minPoint.x = neighborMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = currentMax.x;
rVal.maxPoint.z = targetZ;
} else {
//they all line up or something
rVal.minPoint.x = currentMin.x;
rVal.minPoint.z = targetZ;
rVal.maxPoint.x = currentMax.x;
rVal.maxPoint.z = targetZ;
}
}
return rVal;
}
// static float lineDistCalc(SetItem current, SetItem target){
// float rVal = -1.0f;
// return rVal;
// }
static Vector3d calculateCenterOfShape(NavShape shape){
Vector3d rVal = new Vector3d();
if(shape instanceof NavCube){
NavCube cube = (NavCube)shape;
rVal.add(cube.getMaxPoint()).add(cube.getMinPoint());
rVal.mul(0.5);
}
return rVal;
}
static class SetItem implements Comparable {
SetItem parent;
NavShape node;
float cost;
//closest point from parent's parent
Vector3d currentPos;
public SetItem(SetItem parent, NavShape node, float cost, Vector3d currentPos){
this.parent = parent;
this.node = node;
this.cost = cost;
this.currentPos = currentPos;
}
public SetItem getParent() {
return parent;
}
public NavShape getNode() {
return node;
}
public float getCost() {
return cost;
}
public void setCost(float cost) {
this.cost = cost;
}
@Override
public int compareTo(Object o) {
SetItem target = (SetItem)o;
if(this.cost < target.cost){
return -1;
}
if(this.cost > target.cost){
return 1;
}
return 0;
}
public Vector3d getCurrentPos() {
return currentPos;
}
public void setCurrentPos(Vector3d currentPos) {
this.currentPos = currentPos;
}
}
}

View File

@ -1,8 +1,16 @@
package electrosphere.game.server.pathfinding;
import electrosphere.game.server.pathfinding.blocker.NavBlocker;
import electrosphere.game.server.pathfinding.navmesh.NavCube;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import electrosphere.game.server.pathfinding.navmesh.NavShape;
import electrosphere.game.server.terrain.manager.ServerTerrainChunk;
import electrosphere.logger.LoggerInterface;
import electrosphere.main.Globals;
import java.util.LinkedList;
import java.util.List;
import org.joml.Vector2i;
import org.joml.Vector4i;
/**
*
@ -10,22 +18,341 @@ import electrosphere.main.Globals;
*/
public class NavMeshUtils {
public static NavMesh createMeshFromChunk(ServerTerrainChunk chunk){
NavMesh rVal = new NavMesh();
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.getHeightMap();
for(int x = 0; x < Globals.serverTerrainManager.getChunkWidth(); x++){
for(int y = 0; y < Globals.serverTerrainManager.getChunkWidth(); y++){
boolean[][] navMeshGeneratorMask = navBlocker.getHeightfieldBlocker();
List<FirstPhaseBox> 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(x > 0){
//add back neighbor
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(y > 0){
//add back neighbor
// 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<SecondPhaseBox> secondPhaseBoxes = new LinkedList();
List<SecondPhaseBox> 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
for(SecondPhaseBox interim : secondPhaseBoxes){
if(firstPhaseBox.yStart == interim.boundMinY && firstPhaseBox.yEnd == interim.boundMaxY && firstPhaseBox.x == interim.boundMaxX){
for(SecondPhaseBox neighbor : interim.getNeighbors()){
neighbor.removeNeighbor(interim);
interim.removeNeighbor(neighbor);
neighbor.addNeighbor(newBox);
newBox.addNeighbor(neighbor);
// System.out.println("ADD NEIGHBOR: " + neighbor.boundMaxX + " " + neighbor.boundMaxY + " " + neighbor.boundMinX + " " + neighbor.boundMinY);
}
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<SecondPhaseBox> 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<SecondPhaseBox> 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;
}
}
}

View File

@ -0,0 +1,31 @@
package electrosphere.game.server.pathfinding.blocker;
import electrosphere.main.Globals;
/**
*
* Why it's own class? in case we want to use separate logic for non-heightfield chunks later (dungeons mby)
*
* @author amaterasu
*/
public class NavBlocker {
boolean[][] heightfieldBlocker;
public NavBlocker(){
heightfieldBlocker = new boolean[Globals.serverTerrainManager.getAugmentedChunkWidth()][Globals.serverTerrainManager.getAugmentedChunkWidth()];
}
public NavBlocker(boolean[][] field){
heightfieldBlocker = field;
}
public boolean[][] getHeightfieldBlocker(){
return heightfieldBlocker;
}
public void setHeightfieldBlockerValue(int x, int y, boolean value){
heightfieldBlocker[x][y] = value;
}
}

View File

@ -0,0 +1,59 @@
package electrosphere.game.server.pathfinding.blocker;
import java.util.ArrayList;
import java.util.HashMap;
/**
*
* @author amaterasu
*/
/**
* A cache of the blocker fields for terrain meshes
*/
public class NavTerrainBlockerCache {
//Basic idea is we associate string that contains chunk x&y with elevation
//While we incur a penalty with converting ints -> string, think this will
//offset regenerating the array every time we want a new one
int cacheSize = 50;
HashMap<String, NavBlocker> navBlockerMapCache = new HashMap();
ArrayList<String> navBlockerMapCacheContents = new ArrayList();
public String getKey(int x, int y){
return x + "-" + y;
}
public NavBlocker getBlocker(int x, int y){
NavBlocker rVal;
String key = getKey(x,y);
//if in cache
if(navBlockerMapCache.containsKey(key)){
navBlockerMapCacheContents.remove(key);
navBlockerMapCacheContents.add(0, key);
rVal = navBlockerMapCache.get(key);
return rVal;
} else {
//else fetch from sql if available
}
//else, create an empty one I guess
rVal = new NavBlocker();
rVal.setHeightfieldBlockerValue(5,5,true);
rVal.setHeightfieldBlockerValue(6,5,true);
rVal.setHeightfieldBlockerValue(5,6,true);
rVal.setHeightfieldBlockerValue(6,6,true);
rVal.setHeightfieldBlockerValue(7,5,true);
rVal.setHeightfieldBlockerValue(11,5,true);
rVal.setHeightfieldBlockerValue(5,8,true);
rVal.setHeightfieldBlockerValue(5,22,true);
rVal.setHeightfieldBlockerValue(5,50,true);
if(navBlockerMapCache.size() > cacheSize){
String oldChunk = navBlockerMapCacheContents.remove(navBlockerMapCacheContents.size() - 1);
navBlockerMapCache.remove(oldChunk);
}
navBlockerMapCacheContents.add(0, key);
navBlockerMapCache.put(key, rVal);
return rVal;
}
}

View File

@ -19,7 +19,7 @@ public class NavCube extends NavShape {
Vector3d minPoint;
Vector3d maxPoint;
List<NavCube> neighbors = new LinkedList();
List<NavShape> neighbors = new LinkedList();
public NavCube(double minX, double minY, double minZ, double maxX, double maxY, double maxZ){
@ -28,12 +28,12 @@ public class NavCube extends NavShape {
}
@Override
public void addNeighbor(NavCube neighbor){
public void addNodeNeighbor(NavShape neighbor){
neighbors.add(neighbor);
}
@Override
public List<NavCube> getNeighbors(){
public List<NavShape> getNodeNeighbors(){
return neighbors;
}
@ -42,6 +42,13 @@ public class NavCube extends NavShape {
return x >= minPoint.x && y >= minPoint.y && z >= minPoint.z && x <= maxPoint.x && y <= maxPoint.y && z <= maxPoint.z;
}
public Vector3d getMinPoint(){
return minPoint;
}
public Vector3d getMaxPoint(){
return maxPoint;
}
}

View File

@ -13,14 +13,14 @@ public class NavMesh {
String method;
List<NavShape> navNodes = new LinkedList();
List<NavMesh> neighbors = new LinkedList();
List<NavMesh> meshNeighbors = new LinkedList();
public void addNeighbor(NavMesh neighbor){
neighbors.add(neighbor);
public void addMeshNeighbor(NavMesh neighbor){
meshNeighbors.add(neighbor);
}
public List<NavMesh> getNeighbors(){
return neighbors;
public List<NavMesh> getMeshNeighbors(){
return meshNeighbors;
}
public void addNode(NavShape node){

View File

@ -8,9 +8,9 @@ import java.util.List;
*/
public abstract class NavShape {
public abstract void addNeighbor(NavCube neighbor);
public abstract void addNodeNeighbor(NavShape neighbor);
public abstract List<NavCube> getNeighbors();
public abstract List<NavShape> getNodeNeighbors();
public abstract boolean containsPoint(double x, double y, double z);

View File

@ -3,7 +3,7 @@ package electrosphere.game.server.saves;
import electrosphere.game.server.db.DatabaseUtils;
import electrosphere.game.server.terrain.manager.ServerTerrainManager;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.game.server.world.datacell.DataCellManager;
import electrosphere.game.server.datacell.DataCellManager;
import electrosphere.main.Globals;
import electrosphere.util.FileUtils;
import java.util.LinkedList;

View File

@ -24,11 +24,11 @@ public class ServerTerrainChunk {
this.randomizer = randomizer;
}
public static ServerTerrainChunk getArenaChunk(int width){
public static ServerTerrainChunk getArenaChunk(int width, int x, int y){
float[][] macroValues = new float[5][5];
long[][] randomizer = new long[5][5];
float[][] heightmap = new float[width + 1][width + 1];
ServerTerrainChunk rVal = new ServerTerrainChunk(0, 0, heightmap, macroValues, randomizer);
ServerTerrainChunk rVal = new ServerTerrainChunk(x, y, heightmap, macroValues, randomizer);
return rVal;
}

View File

@ -296,7 +296,7 @@ public class ServerTerrainManager {
returnedChunk = elevationMapCache.get(key);
return returnedChunk;
} else {
returnedChunk = ServerTerrainChunk.getArenaChunk(dynamicInterpolationRatio + 1);
returnedChunk = ServerTerrainChunk.getArenaChunk(dynamicInterpolationRatio + 1, x, y);
elevationMapCache.put(key, returnedChunk);
elevationMapCacheContents.add(key);
return returnedChunk;

View File

@ -1,7 +1,7 @@
package electrosphere.game.server.world;
import electrosphere.game.server.terrain.manager.ServerTerrainManager;
import electrosphere.game.server.world.datacell.ServerDataCell;
import electrosphere.game.server.datacell.ServerDataCell;
import java.util.List;
import org.joml.Vector3f;

View File

@ -33,7 +33,7 @@ import electrosphere.game.server.terrain.manager.ServerTerrainManager;
import electrosphere.game.server.town.Town;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.game.server.world.MacroData;
import electrosphere.game.server.world.datacell.DataCellManager;
import electrosphere.game.server.datacell.DataCellManager;
import electrosphere.game.state.MicroSimulation;
import electrosphere.menu.Menu;
import electrosphere.net.client.ClientNetworking;
@ -46,6 +46,7 @@ import electrosphere.renderer.RenderingEngine;
import electrosphere.renderer.ShaderProgram;
import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.engine.assetmanager.AssetManager;
import electrosphere.game.server.pathfinding.NavMeshManager;
import electrosphere.renderer.ui.WidgetManager;
import electrosphere.renderer.ui.WidgetUtils;
import electrosphere.renderer.ui.font.FontUtils;
@ -242,6 +243,8 @@ public class Globals {
//constant for how far in game units you have to move to load chunks
public static DrawCellManager drawCellManager;
//navmesh manager
public static NavMeshManager navMeshManager;
//famous fuckin last words, but temporary solution
//global arraylist of values for the skybox colors
@ -320,6 +323,8 @@ public class Globals {
aiManager = new AIManager();
//collision engine
collisionEngine = new CollisionEngine();
//nav mesh manager
navMeshManager = new NavMeshManager();
//game config
gameConfigDefault = electrosphere.game.config.Config.loadDefaultConfig();
gameConfigCurrent = gameConfigDefault;

View File

@ -7,6 +7,9 @@ import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.hitbox.HitboxData;
import electrosphere.entity.types.hitbox.HitboxUtils;
import electrosphere.game.config.creature.type.CollidableTemplate;
import electrosphere.game.server.pathfinding.navmesh.NavCube;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import electrosphere.game.server.pathfinding.navmesh.NavShape;
import electrosphere.logger.LoggerInterface;
import electrosphere.main.Globals;
import static electrosphere.main.Main.deltaTime;
@ -545,6 +548,31 @@ public class RenderingEngine {
}
}
if(Globals.userSettings.graphicsDebugDrawNavmesh()){
Model shapeGraphicsModel;
for(NavMesh mesh : Globals.navMeshManager.getMeshes()){
for(NavShape shape : mesh.getNodes()){
if(shape instanceof NavCube){
if((shapeGraphicsModel = Globals.assetManager.fetchModel("Models/unitcube.fbx")) != null){
NavCube cube = (NavCube)shape;
Vector3d position = new Vector3d(cube.getMinPoint()).add(cube.getMaxPoint()).mul(0.5);
Vector3f scale = new Vector3f((float)(cube.getMaxPoint().x-cube.getMinPoint().x)/2,(float)(cube.getMaxPoint().y-cube.getMinPoint().y)/2,(float)(cube.getMaxPoint().z-cube.getMinPoint().z)/2);
Quaternionf rotation = new Quaternionf();
//calculate camera-modified vector3f
Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
modelTransformMatrix.identity();
modelTransformMatrix.translate(cameraModifiedPosition);
modelTransformMatrix.rotate(rotation);
// modelTransformMatrix.translate(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()); //center sphere
modelTransformMatrix.scale(scale);
shapeGraphicsModel.modelMatrix = modelTransformMatrix;
shapeGraphicsModel.draw(true, true, false, true, true, true, true);
}
}
}
}
}
// glBindVertexArray(0);