From 97e32fabdb390af696e08a82292aee4f87fdfacf Mon Sep 17 00:00:00 2001 From: austin Date: Tue, 20 May 2025 12:09:48 -0400 Subject: [PATCH] town places roads --- docs/src/progress/renderertodo.md | 5 ++ .../electrosphere/server/datacell/Realm.java | 2 +- .../server/macro/MacroDataUpdater.java | 6 +- .../civilization/CivilizationGenerator.java | 8 -- .../server/macro/civilization/road/Road.java | 87 ++++++++----------- .../server/macro/town/TownLayout.java | 59 ++++++++++++- .../generation/ProceduralChunkGenerator.java | 2 +- .../electrosphere/util/math/GeomUtils.java | 14 +++ .../electrosphere/util/math/VoronoiUtils.java | 84 ++++++++++++++++++ 9 files changed, 203 insertions(+), 64 deletions(-) create mode 100644 src/main/java/electrosphere/util/math/VoronoiUtils.java diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index d3e799b9..768712a2 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1910,6 +1910,11 @@ Transparent block support Grab hardware data on init rendering engine Delete unused class +(05/20/2025) +Calculate road-interection nodes for town layout +Place roads using line segments instead of splines +Town layout tries to connect intersection nodes with roads + diff --git a/src/main/java/electrosphere/server/datacell/Realm.java b/src/main/java/electrosphere/server/datacell/Realm.java index 2c1972a6..6298cffc 100644 --- a/src/main/java/electrosphere/server/datacell/Realm.java +++ b/src/main/java/electrosphere/server/datacell/Realm.java @@ -374,7 +374,7 @@ public class Realm { throw new Error("Null position!"); } if(macroData != null){ - MacroDataUpdater.update(macroData, playerPosition); + MacroDataUpdater.update(this, macroData, playerPosition); } } diff --git a/src/main/java/electrosphere/server/macro/MacroDataUpdater.java b/src/main/java/electrosphere/server/macro/MacroDataUpdater.java index b7ba8a22..5492908e 100644 --- a/src/main/java/electrosphere/server/macro/MacroDataUpdater.java +++ b/src/main/java/electrosphere/server/macro/MacroDataUpdater.java @@ -2,6 +2,7 @@ package electrosphere.server.macro; import org.joml.Vector3d; +import electrosphere.server.datacell.Realm; import electrosphere.server.macro.town.Town; import electrosphere.server.macro.town.TownLayout; @@ -17,10 +18,11 @@ public class MacroDataUpdater { /** * Updates the macro data + * @param realm The realm related to this data * @param macroData The data * @param playerPos The player's position */ - public static void update(MacroData macroData, Vector3d playerPos){ + public static void update(Realm realm, MacroData macroData, Vector3d playerPos){ //scan for all towns within update range for(Town town : macroData.getTowns()){ //only generate data for towns that aren't already full resolution @@ -29,7 +31,7 @@ public class MacroDataUpdater { } Vector3d townPos = town.getPos(); if(townPos.distance(playerPos) < TOWN_GENERATION_DIST){ - TownLayout.layoutTown(macroData, town); + TownLayout.layoutTown(realm, macroData, town); } } } diff --git a/src/main/java/electrosphere/server/macro/civilization/CivilizationGenerator.java b/src/main/java/electrosphere/server/macro/civilization/CivilizationGenerator.java index b88a9544..37a221e8 100644 --- a/src/main/java/electrosphere/server/macro/civilization/CivilizationGenerator.java +++ b/src/main/java/electrosphere/server/macro/civilization/CivilizationGenerator.java @@ -5,11 +5,9 @@ import org.joml.Vector3d; import electrosphere.data.Config; import electrosphere.server.datacell.ServerWorldData; import electrosphere.server.macro.MacroData; -import electrosphere.server.macro.civilization.road.Road; import electrosphere.server.macro.race.Race; import electrosphere.server.macro.town.Town; import electrosphere.server.physics.terrain.manager.ServerTerrainChunk; -import electrosphere.util.ds.Spline3d; /** * Generates civilizations @@ -35,12 +33,6 @@ public class CivilizationGenerator { Civilization newCiv = Civilization.createCivilization(macroData, race); newCiv.addRace(race); Town.createTown(macroData, spawnPoint, INITIAL_TOWN_RADIUS, newCiv.getId()); - Road.createRoad(macroData, Spline3d.createCatmullRom(new Vector3d[]{ - new Vector3d(spawnPoint).add(-20,0,0), - new Vector3d(spawnPoint).add(-10,0,0), - new Vector3d(spawnPoint).add( 10,0,0), - new Vector3d(spawnPoint).add( 20,0,0), - })); } } diff --git a/src/main/java/electrosphere/server/macro/civilization/road/Road.java b/src/main/java/electrosphere/server/macro/civilization/road/Road.java index 65859568..4a7596a4 100644 --- a/src/main/java/electrosphere/server/macro/civilization/road/Road.java +++ b/src/main/java/electrosphere/server/macro/civilization/road/Road.java @@ -5,7 +5,6 @@ import org.joml.Vector3d; import electrosphere.server.macro.MacroData; import electrosphere.server.macro.spatial.MacroAreaObject; -import electrosphere.util.ds.Spline3d; /** * A road @@ -15,7 +14,7 @@ public class Road implements MacroAreaObject { /** * The default radius */ - public static final double DEFAULT_RADIUS = 3; + public static final double DEFAULT_RADIUS = 5; /** * The default material @@ -28,9 +27,14 @@ public class Road implements MacroAreaObject { int id; /** - * The spline that the road is aligned along + * The start position of the road segment */ - Spline3d spline; + Vector3d startPos; + + /** + * The end position of the road segment + */ + Vector3d endPos; /** * The radius of the road @@ -55,13 +59,15 @@ public class Road implements MacroAreaObject { /** * Creates a road * @param macroData The macro data - * @param spline The spline - * @param material The material for the road + * @param start The start position + * @param end The end position * @return The road */ - public static Road createRoad(MacroData macroData, Spline3d spline){ + public static Road createRoad(MacroData macroData, Vector3d start, Vector3d endPos){ Road road = new Road(); - road.setSpline(spline); + road.startPos = start; + road.endPos = endPos; + road.computeAABB(); macroData.addRoad(road); return road; } @@ -82,23 +88,6 @@ public class Road implements MacroAreaObject { this.id = id; } - /** - * Gets the spline that the road is aligned along - * @return The spline - */ - public Spline3d getSpline() { - return spline; - } - - /** - * Sets the spline that the road is aligned along - * @param spline The spline - */ - public void setSpline(Spline3d spline) { - this.spline = spline; - this.computeAABB(); - } - /** * Gets the radius of the road * @return The radius @@ -136,32 +125,28 @@ public class Road implements MacroAreaObject { */ private void computeAABB(){ this.aabb = new AABBd(); - for(Vector3d point : this.spline.getPoints()){ - if(this.aabb.minX > point.x){ - this.aabb.minX = point.x; - } - if(this.aabb.minY > point.y){ - this.aabb.minY = point.y; - } - if(this.aabb.minZ > point.z){ - this.aabb.minZ = point.z; - } - if(this.aabb.maxX < point.x){ - this.aabb.maxX = point.x; - } - if(this.aabb.maxY < point.y){ - this.aabb.maxY = point.y; - } - if(this.aabb.maxZ < point.z){ - this.aabb.maxZ = point.z; - } - } - this.aabb.minX = this.aabb.minX - radius; - this.aabb.minY = this.aabb.minY - radius; - this.aabb.minZ = this.aabb.minZ - radius; - this.aabb.maxX = this.aabb.maxX + radius; - this.aabb.maxY = this.aabb.maxY + radius; - this.aabb.maxZ = this.aabb.maxZ + radius; + this.aabb.minX = Math.min(this.startPos.x,this.endPos.x) - radius; + this.aabb.minY = Math.min(this.startPos.y,this.endPos.y) - radius; + this.aabb.minZ = Math.min(this.startPos.z,this.endPos.z) - radius; + this.aabb.maxX = Math.max(this.startPos.x,this.endPos.x) + radius; + this.aabb.maxY = Math.max(this.startPos.y,this.endPos.y) + radius; + this.aabb.maxZ = Math.max(this.startPos.z,this.endPos.z) + radius; + } + + /** + * Gets the first point of the road segment + * @return The first point + */ + public Vector3d getPoint1(){ + return startPos; + } + + /** + * Gets the send point of the road segment + * @return The second point + */ + public Vector3d getPoint2(){ + return endPos; } @Override diff --git a/src/main/java/electrosphere/server/macro/town/TownLayout.java b/src/main/java/electrosphere/server/macro/town/TownLayout.java index 3cff71e5..aef18aa2 100644 --- a/src/main/java/electrosphere/server/macro/town/TownLayout.java +++ b/src/main/java/electrosphere/server/macro/town/TownLayout.java @@ -4,24 +4,42 @@ import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; +import org.joml.Vector3d; + import electrosphere.data.struct.StructureData; import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; +import electrosphere.server.datacell.Realm; import electrosphere.server.macro.MacroData; import electrosphere.server.macro.civilization.Civilization; +import electrosphere.server.macro.civilization.road.Road; import electrosphere.server.macro.race.Race; +import electrosphere.util.math.VoronoiUtils; /** * Lays out town objects */ public class TownLayout { + /** + * Scaler for town layout nodes + */ + public static final double TOWN_LAYOUT_SCALER = 16; + + /** + * Relaxation factor for regularizing placement of town center nodes + */ + public static final double VORONOI_RELAXATION_FACTOR = 0.5; + /** * Lays out structures for a town * @param macroData The macro data * @param town The town */ - public static void layoutTown(MacroData macroData, Town town){ + public static void layoutTown(Realm realm, MacroData macroData, Town town){ + + // + //figure out what structures we're allowed to place Civilization parentCiv = town.getParent(macroData); List raceIds = parentCiv.getRaceIds(); List races = raceIds.stream().map((String raceId) -> Globals.gameConfigCurrent.getRaceMap().getRace(raceId)).filter((Race race) -> race != null).collect(Collectors.toList()); @@ -41,6 +59,45 @@ public class TownLayout { throw new Error("No structures found! " + raceIds); } LoggerInterface.loggerEngine.DEBUG("Allowed structure count: " + allowedStructures.size()); + + // + //find the nodes to connect + Vector3d townCenter = town.getPos(); + + //get center loc + Vector3d centerNodeLoc = VoronoiUtils.solveClosestVoronoiNode( + townCenter.x / TOWN_LAYOUT_SCALER, + 0, + townCenter.z / TOWN_LAYOUT_SCALER, + VORONOI_RELAXATION_FACTOR + ).mul(TOWN_LAYOUT_SCALER,1,TOWN_LAYOUT_SCALER); + centerNodeLoc.y = realm.getServerWorldData().getServerTerrainManager().getElevation(centerNodeLoc); + + //get north node + Vector3d upNodeLoc = VoronoiUtils.solveClosestVoronoiNode( + townCenter.x / TOWN_LAYOUT_SCALER, + 0, + townCenter.z / TOWN_LAYOUT_SCALER + TOWN_LAYOUT_SCALER, + VORONOI_RELAXATION_FACTOR).mul(TOWN_LAYOUT_SCALER,1,TOWN_LAYOUT_SCALER); + upNodeLoc.y = realm.getServerWorldData().getServerTerrainManager().getElevation(upNodeLoc); + + //get left node + Vector3d leftNodeLoc = VoronoiUtils.solveClosestVoronoiNode( + townCenter.x / TOWN_LAYOUT_SCALER - TOWN_LAYOUT_SCALER, + 0, + townCenter.z / TOWN_LAYOUT_SCALER, + VORONOI_RELAXATION_FACTOR + ).mul(TOWN_LAYOUT_SCALER,1,TOWN_LAYOUT_SCALER); + leftNodeLoc.y = realm.getServerWorldData().getServerTerrainManager().getElevation(leftNodeLoc); + + + //up-facing road + Road.createRoad(macroData, upNodeLoc, centerNodeLoc); + + //up-facing road + Road.createRoad(macroData, leftNodeLoc, centerNodeLoc); + + town.setResolution(Town.TOWN_RES_MAX); } diff --git a/src/main/java/electrosphere/server/physics/terrain/generation/ProceduralChunkGenerator.java b/src/main/java/electrosphere/server/physics/terrain/generation/ProceduralChunkGenerator.java index 3920e6fa..49464e43 100644 --- a/src/main/java/electrosphere/server/physics/terrain/generation/ProceduralChunkGenerator.java +++ b/src/main/java/electrosphere/server/physics/terrain/generation/ProceduralChunkGenerator.java @@ -274,7 +274,7 @@ public class ProceduralChunkGenerator implements ChunkGenerator { Road road = (Road)object; //broad phase intersection if(road.getAABB().testPoint(realX, realY, realZ)){ - if(GeomUtils.pointIntersectsSpline(realPt, road.getSpline(), road.getRadius())){ + if(GeomUtils.pointIntersectsLineSegment(realPt, road.getPoint1(), road.getPoint2(), road.getRadius())){ if(voxel.type != ServerTerrainChunk.VOXEL_TYPE_AIR){ voxel.type = 1; rVal = true; diff --git a/src/main/java/electrosphere/util/math/GeomUtils.java b/src/main/java/electrosphere/util/math/GeomUtils.java index 690272e0..fc810d76 100644 --- a/src/main/java/electrosphere/util/math/GeomUtils.java +++ b/src/main/java/electrosphere/util/math/GeomUtils.java @@ -1,5 +1,6 @@ package electrosphere.util.math; +import org.joml.Intersectiond; import org.joml.Vector3d; import org.joml.Vector3i; @@ -649,5 +650,18 @@ public class GeomUtils { public static boolean pointIntersectsSpline(Vector3d point, Spline3d spline, double radius) { return GeomUtils.pointIntersectsSpline(point, spline, radius, SPLINE_SAMPLE_RATE); } + + /** + * Checks whether a point intersects a tube defined by a line + * @param point The point + * @param lineStart The start of the line segment + * @param lineEnd The end of the line segment + * @param radius The radius of the tube around the line + * @return true if they intersect, false otherwise + */ + public static boolean pointIntersectsLineSegment(Vector3d point, Vector3d lineStart, Vector3d lineEnd, double radius) { + double dist = Intersectiond.distancePointLine(point.x, point.y, point.z, lineStart.x, lineStart.y, lineStart.z, lineEnd.x, lineEnd.y, lineEnd.z); + return dist < radius; + } } diff --git a/src/main/java/electrosphere/util/math/VoronoiUtils.java b/src/main/java/electrosphere/util/math/VoronoiUtils.java new file mode 100644 index 00000000..0e8aa33a --- /dev/null +++ b/src/main/java/electrosphere/util/math/VoronoiUtils.java @@ -0,0 +1,84 @@ +package electrosphere.util.math; + +import org.joml.Vector3d; + +import io.github.studiorailgun.RandUtils; + +/** + * Utilities for dealing with voronoi noise + */ +public class VoronoiUtils { + + /** + * x offsets for a 3x3x3 kernel + */ + static final int[] KERNEL_3_3_3_X = new int[]{ + 1, 0, -1, + 1, 0, -1, + 1, 0, -1, + 1, 0, -1, + 1, 0, -1, + 1, 0, -1, + 1, 0, -1, + 1, 0, -1, + 1, 0, -1, + }; + + /** + * y offsets for a 3x3x3 kernel + */ + static final int[] KERNEL_3_3_3_Y = new int[]{ + 1, 1, 1, + 0, 0, 0, + -1, -1, -1, + 1, 1, 1, + 0, 0, 0, + -1, -1, -1, + 1, 1, 1, + 0, 0, 0, + -1, -1, -1, + }; + + /** + * z offsets for a 3x3x3 kernel + */ + static final int[] KERNEL_3_3_3_Z = new int[]{ + 1, 1, 1, + 1, 1, 1, + 1, 1, 1, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + -1, -1, -1, + -1, -1, -1, + -1, -1, -1, + }; + + /** + * Calculates voronoi noise within a cube + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @param relaxationFactor The relaxation factor + * @return The voronoi value + */ + public static Vector3d solveClosestVoronoiNode(double x, double y, double z, double relaxationFactor){ + //integer of the point coordinates + double x_i = Math.floor(x); + double y_i = Math.floor(y); + double z_i = Math.floor(z); + + //get the point + double p_x = RandUtils.rand(x_i, y_i, z_i, 0); + double p_y = RandUtils.rand(x_i, y_i, z_i, 1); + double p_z = RandUtils.rand(x_i, y_i, z_i, 2); + + //relax the point based on relaxation factor + double x_relaxed = p_x * (1.0 - relaxationFactor) + (relaxationFactor / 2.0); + double y_relaxed = p_y * (1.0 - relaxationFactor) + (relaxationFactor / 2.0); + double z_relaxed = p_z * (1.0 - relaxationFactor) + (relaxationFactor / 2.0); + + return new Vector3d(x_i + x_relaxed,y_i + y_relaxed,z_i + z_relaxed); + } + +}