diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 75bc18af..1fc5e839 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -2074,6 +2074,7 @@ Sprinting/physics work (05/30/2025) Reorganizing macro classes Start work on macro pathfinding storage +Town constructs nav graph of road nodes diff --git a/src/main/java/electrosphere/server/macro/MacroData.java b/src/main/java/electrosphere/server/macro/MacroData.java index b3492b18..30558c9b 100644 --- a/src/main/java/electrosphere/server/macro/MacroData.java +++ b/src/main/java/electrosphere/server/macro/MacroData.java @@ -19,6 +19,7 @@ import electrosphere.server.macro.civilization.town.Town; import electrosphere.server.macro.region.MacroRegion; import electrosphere.server.macro.spatial.MacroAreaObject; import electrosphere.server.macro.spatial.MacroObject; +import electrosphere.server.macro.spatial.path.MacroPathCache; import electrosphere.server.macro.structure.VirtualStructure; import electrosphere.util.FileUtils; import electrosphere.util.annotation.Exclude; @@ -34,35 +35,30 @@ import org.joml.Vector3d; */ public class MacroData { - /** - * The maximum number of attempts to try placing something - */ - static final int MAX_PLACEMENT_ATTEMPTS = 50; - /** * List of races */ - List races = new LinkedList(); + private List races = new LinkedList(); /** * List of civilizations */ - List civilizations = new LinkedList(); + private List civilizations = new LinkedList(); /** * List of towns */ - List towns = new LinkedList(); + private List towns = new LinkedList(); /** * List of structures */ - List structures = new LinkedList(); + private List structures = new LinkedList(); /** * The list of all regions */ - List regions = new LinkedList(); + private List regions = new LinkedList(); /** * Maps structure id -> structure @@ -79,7 +75,12 @@ public class MacroData { /** * List of roads */ - List roads = new LinkedList(); + private List roads = new LinkedList(); + + /** + * The pathing cache + */ + private MacroPathCache pathingCache = new MacroPathCache(); /** * Generates a world @@ -89,15 +90,6 @@ public class MacroData { public static MacroData generateWorld(long seed, ServerWorldData serverWorldData){ Random random = new Random(seed); MacroData rVal = new MacroData(); - - //generate initial dieties - // int numDieties = 3 + Math.abs(random.nextInt()) % 7; - // for(int i = 0; i < numDieties; i++){ - // Character diety = generateInitialDiety(random.nextLong()); - // rVal.initialDieties.add(diety); - // rVal.characters.add(diety); - // CharacterService.createCharacter(null, i); - // } //generate initial races @@ -120,19 +112,6 @@ public class MacroData { //init civilizations CivilizationGenerator.generate(serverWorldData, rVal, Globals.gameConfigCurrent); - //add a test character - // Character testChar = new Character(); - // testChar.setPos(new Vector3d(ServerWorldData.convertChunkToRealSpace(new Vector3i(32774, 3, 32769)))); - // Race.setRace(testChar, Race.create("human", "human")); - // rVal.characters.add(testChar); - - //add a test character - // Vector3d structPos = ServerWorldData.convertChunkToRealSpace(new Vector3i(32774, 0, 32770)); - // double elevationAtStruct = serverWorldData.getServerTerrainManager().getElevation(32774, 32770, 0, 0); - // structPos.y = elevationAtStruct; - // Structure struct = Structure.createStructure(Globals.gameConfigCurrent.getStructureData().getType("test1"),structPos); - // rVal.structures.add(struct); - //spawn initial characters in each race //find initial positions to place characters at per race //generate initial characters @@ -188,6 +167,9 @@ public class MacroData { for(MacroRegion region : this.regions){ this.idRegionMap.put(region.getId(),region); } + if(this.pathingCache != null){ + this.pathingCache.reconstruct(); + } } /** @@ -197,22 +179,6 @@ public class MacroData { public void save(String saveName){ FileUtils.serializeObjectToSavePath(saveName, "./macro.json", this); } - - // /** - // * Generates an initial diety - // * @param seed The seed - // * @return The character for the diety - // */ - // static Character generateInitialDiety(long seed){ - // Character rVal = new Character(); - - - // Diety diety = Diety.generateDiety(seed); - // CharacterUtils.addDiety(rVal, diety); - - - // return rVal; - // } /** * Gets the list of civilizations @@ -422,5 +388,13 @@ public class MacroData { blockers.addAll(this.regions); return blockers; } + + /** + * Gets the path cache in the macro data + * @return The macro pathing cache + */ + public MacroPathCache getPathCache(){ + return this.pathingCache; + } } diff --git a/src/main/java/electrosphere/server/macro/civilization/town/TownLayout.java b/src/main/java/electrosphere/server/macro/civilization/town/TownLayout.java index 28bc2a7f..b9e674ec 100644 --- a/src/main/java/electrosphere/server/macro/civilization/town/TownLayout.java +++ b/src/main/java/electrosphere/server/macro/civilization/town/TownLayout.java @@ -1,7 +1,9 @@ package electrosphere.server.macro.civilization.town; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.joml.AABBd; @@ -19,6 +21,8 @@ import electrosphere.server.macro.character.race.Race; import electrosphere.server.macro.civilization.Civilization; import electrosphere.server.macro.civilization.road.Road; import electrosphere.server.macro.region.MacroRegion; +import electrosphere.server.macro.spatial.path.MacroPathCache; +import electrosphere.server.macro.spatial.path.MacroPathNode; import electrosphere.server.macro.structure.VirtualStructure; import electrosphere.util.math.HashUtils; import electrosphere.util.math.VoronoiUtils; @@ -80,6 +84,8 @@ public class TownLayout { */ public static void layoutTown(Realm realm, MacroData macroData, Town town){ + MacroPathCache pathCache = macroData.getPathCache(); + // //figure out what structures we're allowed to place Civilization parentCiv = town.getParent(macroData); @@ -183,6 +189,76 @@ public class TownLayout { closedSet.add(openHash); } + // + // Construct pathfinding nodes for roads and link them + // + // + //sets for breadth search + Map positionNodeMap = new HashMap(); + openSet.clear(); + closedSet.clear(); + closedSet.add(HashUtils.hashIVec(HASH_OFFSET, 0, HASH_OFFSET)); + openSet.add(HashUtils.hashIVec(HASH_OFFSET - 1, 0, HASH_OFFSET)); + openSet.add(HashUtils.hashIVec(HASH_OFFSET + 1, 0, HASH_OFFSET)); + openSet.add(HashUtils.hashIVec(HASH_OFFSET, 0, HASH_OFFSET - 1)); + openSet.add(HashUtils.hashIVec(HASH_OFFSET, 0, HASH_OFFSET + 1)); + //add pathing nodes for initial entries + MacroPathNode currentPathingNode = MacroPathNode.createRoadNode(pathCache, new Vector3d(centerNodeLoc)); + positionNodeMap.put(HashUtils.hashIVec(HASH_OFFSET, 0, HASH_OFFSET), currentPathingNode); + + //up + currentPathingNode = MacroPathNode.createRoadNode(pathCache, new Vector3d(upNodeLoc)); + positionNodeMap.put(HashUtils.hashIVec(HASH_OFFSET, 0, HASH_OFFSET + 1), currentPathingNode); + + //left + currentPathingNode = MacroPathNode.createRoadNode(pathCache, new Vector3d(leftNodeLoc)); + positionNodeMap.put(HashUtils.hashIVec(HASH_OFFSET - 1, 0, HASH_OFFSET), currentPathingNode); + + //right + currentPathingNode = MacroPathNode.createRoadNode(pathCache, new Vector3d(rightNodeLoc)); + positionNodeMap.put(HashUtils.hashIVec(HASH_OFFSET + 1, 0, HASH_OFFSET), currentPathingNode); + + //down + currentPathingNode = MacroPathNode.createRoadNode(pathCache, new Vector3d(downNodeLoc)); + positionNodeMap.put(HashUtils.hashIVec(HASH_OFFSET, 0, HASH_OFFSET - 1), currentPathingNode); + + while(openSet.size() > 0){ + long openHash = openSet.poll(); + int x = HashUtils.unhashIVec(openHash, HashUtils.UNHASH_COMPONENT_X) - HASH_OFFSET; + int z = HashUtils.unhashIVec(openHash, HashUtils.UNHASH_COMPONENT_Z) - HASH_OFFSET; + scanPoint.set(townCenter).add(TOWN_LAYOUT_SCALER * x,0,TOWN_LAYOUT_SCALER * z); + currPoint = TownLayout.getTownCenter(realm, scanPoint); + currentPathingNode = positionNodeMap.get(openHash); + if(currentPathingNode == null){ + throw new Error("Failed to find pathing node for hash"); + } + + //check below + for(int i = 0; i < 4; i++){ + int oX = x + offsetX[i]; + int oZ = z + offsetZ[i]; + scanPoint.set(townCenter).add(TOWN_LAYOUT_SCALER * oX,0,TOWN_LAYOUT_SCALER * oZ); + nearPoint = TownLayout.getTownCenter(realm, scanPoint); + long newHash = HashUtils.hashIVec(HASH_OFFSET + oX, 0, HASH_OFFSET + oZ); + if(nearPoint.distance(townCenter) < TOWN_MAX_RADIUS){ + if(!openSet.contains(newHash) && !closedSet.contains(newHash)){ + openSet.add(newHash); + } + + //link the neighbor to this node + MacroPathNode neighborPathingNode; + if(positionNodeMap.containsKey(newHash)){ + neighborPathingNode = positionNodeMap.get(newHash); + } else { + neighborPathingNode = MacroPathNode.createRoadNode(pathCache, new Vector3d(nearPoint)); + positionNodeMap.put(newHash,neighborPathingNode); + } + currentPathingNode.addNeighbor(neighborPathingNode); + } + } + closedSet.add(openHash); + } + // //Breadth search other nodes to branch outwards diff --git a/src/main/java/electrosphere/server/macro/spatial/path/MacroPathCache.java b/src/main/java/electrosphere/server/macro/spatial/path/MacroPathCache.java index 085aeb0e..284c7ef7 100644 --- a/src/main/java/electrosphere/server/macro/spatial/path/MacroPathCache.java +++ b/src/main/java/electrosphere/server/macro/spatial/path/MacroPathCache.java @@ -22,7 +22,7 @@ public class MacroPathCache { * Map of node id -> node */ @Exclude - private Map idNodeMap = new HashMap(); + private Map idNodeMap = new HashMap(); /** * Reconstructs the datastructures for this cache after deserialization @@ -46,7 +46,7 @@ public class MacroPathCache { * @param id The id * @return The corresponding node if it exists, null otherwise */ - public MacroPathNode getNodeById(int id){ + public MacroPathNode getNodeById(long id){ return idNodeMap.get(id); } diff --git a/src/main/java/electrosphere/server/macro/spatial/path/MacroPathNode.java b/src/main/java/electrosphere/server/macro/spatial/path/MacroPathNode.java index 9eb1c5d4..495053b5 100644 --- a/src/main/java/electrosphere/server/macro/spatial/path/MacroPathNode.java +++ b/src/main/java/electrosphere/server/macro/spatial/path/MacroPathNode.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import org.joml.Vector3d; +import electrosphere.server.macro.region.MacroRegion; import electrosphere.server.macro.structure.VirtualStructure; /** @@ -27,6 +28,11 @@ public class MacroPathNode { */ private static final int TYPE_VIRTUAL_STRUCTURE = 1; + /** + * A region + */ + private static final int TYPE_REGION = 2; + // // cost values @@ -37,6 +43,11 @@ public class MacroPathNode { */ private static final int COST_VIRTUAL_STRUCTURE = 10; + /** + * Cost of a region object + */ + private static final int COST_REGION = 8; + /** * Cost of a road node */ @@ -50,12 +61,12 @@ public class MacroPathNode { /** * The id of this node */ - private int id; + private long id; /** * The id of the object that this node corresponds to */ - private int objectId; + private long objectId; /** * The type of object at the node @@ -65,7 +76,7 @@ public class MacroPathNode { /** * The ids of neighboring path nodes */ - private List neighborNodes = new LinkedList(); + private List neighborNodes = new LinkedList(); /** * The position of the node @@ -102,6 +113,10 @@ public class MacroPathNode { rVal.cost = COST_VIRTUAL_STRUCTURE; rVal.objectId = structObj.getId(); rVal.objectType = TYPE_VIRTUAL_STRUCTURE; + } else if(correspondingObject instanceof MacroRegion regionObj){ + rVal.cost = COST_REGION; + rVal.objectId = regionObj.getId(); + rVal.objectType = TYPE_REGION; } else { throw new Error("Unsupported object type! " + correspondingObject); } @@ -134,7 +149,7 @@ public class MacroPathNode { * Gets the associated id * @return */ - public int getId() { + public long getId() { return id; } @@ -142,7 +157,7 @@ public class MacroPathNode { * Sets the id of this path node * @param id The id of this path node */ - public void setId(int id) { + protected void setId(int id) { this.id = id; } @@ -166,7 +181,7 @@ public class MacroPathNode { * Gets the id of the object associated with this node * @return The id of the object */ - public int getObjectId() { + public long getObjectId() { return objectId; } @@ -184,7 +199,7 @@ public class MacroPathNode { * @return The list of node ids */ public List getNeighborNodes(MacroPathCache cache) { - return this.neighborNodes.stream().map((Integer id) -> cache.getNodeById(objectId)).collect(Collectors.toList()); + return this.neighborNodes.stream().map((Long id) -> cache.getNodeById(objectId)).collect(Collectors.toList()); } /** @@ -192,7 +207,9 @@ public class MacroPathNode { * @param neighbor The neighbor */ public void addNeighbor(MacroPathNode neighbor){ - this.neighborNodes.add(neighbor.getId()); + if(!this.neighborNodes.contains(neighbor.getId())){ + this.neighborNodes.add(neighbor.getId()); + } } /**