fix foliage bugs
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-03-30 20:01:58 -04:00
parent 67212d6003
commit a1aef00d23
8 changed files with 149 additions and 60 deletions

View File

@ -49,6 +49,37 @@
]
}
],
"surfaceGenerationParams": {
"surfaceGenTag": "hills",
"heightOffset": 10,
"floorVariants": [
],
"foliageDescriptions": [
]
}
},
{
"id": "hills_forested",
"displayName": "Hills (Forested)",
"isAerial": false,
"isSurface": true,
"isSubterranean": false,
"regions": [
{
"frequency": 1.0,
"baseFloorVoxel": 1,
"floorVariants": [
{
"voxelId": 2,
"frequency": 1.0,
"dispersion": 1.0,
"priority": 1.0
}
],
"foliageDescription": [
]
}
],
"surfaceGenerationParams": {
"surfaceGenTag": "hills",
"heightOffset": 10,
@ -60,7 +91,7 @@
"pine2"
],
"regularity": 0.6,
"threshold": 0.05,
"threshold": 0.2,
"scale": 0.5,
"priority": 1.0
}

View File

@ -1395,6 +1395,8 @@ Simplify WorldOctTree to reduce lag with large node counts
Increase memory allowance, mostly fixed latency while walking around
AssetManager streaming budget
Better chunk position hashing algo
Hills generator work
Fix foliage manager and cells confusing worldpos and absolutevoxelpos

View File

@ -17,7 +17,6 @@ import electrosphere.entity.Entity;
import electrosphere.entity.btree.BehaviorTree;
import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes;
import electrosphere.renderer.buffer.ShaderAttribute;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.util.ds.octree.WorldOctTree.WorldOctTreeNode;
import electrosphere.util.math.GeomUtils;
@ -134,7 +133,7 @@ public class FoliageCell {
/**
* The data for generating the visuals
*/
TransvoxelChunkData chunkData;
ChunkData chunkData;
/**
* Tracks whether the foliage cell has requested its chunk data or not
@ -226,7 +225,7 @@ public class FoliageCell {
worldPos.x,
worldPos.y,
worldPos.z,
0
ChunkData.NO_STRIDE
);
if(currentChunk == null){
success = false;
@ -238,9 +237,11 @@ public class FoliageCell {
this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
return;
}
this.chunkData = new TransvoxelChunkData(currentChunk.getVoxelWeight(), currentChunk.getVoxelType(), 0);
this.chunkData = currentChunk;
}
if(success){
this.generate();
}
this.generate();
}
@ -248,40 +249,41 @@ public class FoliageCell {
* Generates the foliage cell
*/
protected void generate(){
int airID = 0;
boolean shouldGenerate = false;
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos,ChunkData.NO_STRIDE);
if(data == null){
return;
}
//get foliage types supported
List<String> foliageTypesSupported = new LinkedList<String>();
Map<Integer,Boolean> handledTypes = new HashMap<Integer,Boolean>();
handledTypes.put(airID,true);
boolean airAbove = true;
int scale = (int)Math.pow(2,lod);
for(int x = 0; x < scale; x++){
for(int y = 0; y < scale; y++){
for(int z = 0; z < scale; z++){
int voxelType = chunkData.getType(new Vector3i(this.voxelPos).add(x,y,z));
if(handledTypes.containsKey(voxelType)){
continue;
}
if(voxelPos.y + y >= ServerTerrainChunk.CHUNK_DIMENSION){
continue;
}
List<String> currentList = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(new Vector3i(voxelPos).add(x,y,z))).getAmbientFoliage();
List<String> currentList = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(voxelType).getAmbientFoliage();
if(currentList == null){
handledTypes.put(voxelType,true);
continue;
}
foliageTypesSupported.addAll(currentList);
airAbove = data.getType(voxelPos.x + x,voxelPos.y + y + 1,voxelPos.z + z) == 0;
airAbove = chunkData.getType(voxelPos.x + x,voxelPos.y + y + 1,voxelPos.z + z) == airID;
if(foliageTypesSupported != null && foliageTypesSupported.size() > 0 && airAbove){
shouldGenerate = true;
break;
handledTypes.put(voxelType,true);
}
}
if(shouldGenerate){
break;
}
}
if(shouldGenerate){
break;
}
}
// if(Math.abs(worldPos.x - 32767) < 5 && Math.abs(worldPos.y - 5) < 3 && Math.abs(worldPos.z - 32767) < 3 && this.chunkData.getHomogenousValue() != 0){
// System.out.println(worldPos.x + " " + worldPos.y + " " + worldPos.z + " - " + shouldGenerate);
// }
if(shouldGenerate){
Entity oldEntity = this.modelEntity;
//create entity
@ -447,16 +449,16 @@ public class FoliageCell {
/**
* Gets the minimum distance from a node to a point
* @param pos the position to check against
* @param absVoxelPos the position to check against
* @param node the node
* @param distCache the lod value under which distance caches are invalidated
* @return the distance
*/
public long getMinDistance(Vector3i worldPos, WorldOctTreeNode<FoliageCell> node, int distCache){
public long getMinDistance(Vector3i absVoxelPos, WorldOctTreeNode<FoliageCell> node, int distCache){
if(cachedMinDistance != INVALID_DIST_CACHE && distCache < lod){
return cachedMinDistance;
} else {
double dist = GeomUtils.approxMinDistanceAABB(worldPos, node.getMinBound(), node.getMaxBound());
double dist = GeomUtils.approxMinDistanceAABB(absVoxelPos, node.getMinBound(), node.getMaxBound());
if(Double.isFinite(dist)){
this.cachedMinDistance = (long)dist;
} else {

View File

@ -8,6 +8,7 @@ import java.util.Map;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.game.data.foliage.type.FoliageType;
@ -22,6 +23,11 @@ import electrosphere.util.math.GeomUtils;
*/
public class FoliageCellManager {
/**
* If moved this many cells in 1 frame, completely bust meta cells
*/
public static final int TELEPORT_DISTANCE = 5000;
/**
* Number of times to try updating per frame. Lower this to reduce lag but slow down terrain mesh generation.
*/
@ -87,6 +93,11 @@ public class FoliageCellManager {
*/
public static final int ALL_RES_LOD = 5;
/**
* Lod value for busting up meta cells
*/
public static final int BUST_META_CELLS = 40;
/**
* The octree holding all the chunks to evaluate
*/
@ -142,6 +153,11 @@ public class FoliageCellManager {
*/
boolean initialized = false;
/**
* The list of points to break at next evaluation
*/
List<Vector3i> breakPoints = new LinkedList<Vector3i>();
/**
* Constructor
* @param worldDim The size of the world in chunks
@ -177,6 +193,9 @@ public class FoliageCellManager {
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
Vector3i absVoxelPos = Globals.clientWorldData.convertRealToAbsoluteVoxelSpace(playerPos);
int distCache = this.getDistCache(this.lastPlayerPos, absVoxelPos);
if(absVoxelPos.distance(this.lastPlayerPos) > TELEPORT_DISTANCE){
distCache = BUST_META_CELLS;
}
this.lastPlayerPos.set(absVoxelPos);
//the sets to iterate through
updatedLastFrame = true;
@ -190,6 +209,9 @@ public class FoliageCellManager {
if(!updatedLastFrame && !this.initialized){
this.initialized = true;
}
if(this.breakPoints.size() > 0){
this.breakPoints.clear();
}
}
Globals.profiler.endCpuSample();
}
@ -204,19 +226,37 @@ public class FoliageCellManager {
*/
private boolean recursivelyUpdateCells(WorldOctTreeNode<FoliageCell> node, Vector3i absVoxelPos, Map<WorldOctTreeNode<FoliageCell>,Boolean> evaluationMap, int minLeafLod, int distCache){
boolean updated = false;
//breakpoint handling
if(this.breakPoints.size() > 0){
for(Vector3i breakpoint : breakPoints){
if(GeomUtils.approxMinDistanceAABB(breakpoint, node.getMinBound(), node.getMaxBound()) == 0){
System.out.println("Break at " + breakpoint + " " + node.getLevel());
System.out.println(" " + node.getMinBound() + " " + node.getMaxBound());
System.out.println(" Generated: " + node.getData().hasGenerated());
System.out.println(" Homogenous: " + node.getData().isHomogenous());
System.out.println(" Leaf: " + node.isLeaf());
System.out.println(" Cached min dist: " + node.getData().cachedMinDistance);
System.out.println(" Actual min dist: " + GeomUtils.approxMinDistanceAABB(breakpoint, node.getMinBound(), node.getMaxBound()));
}
}
}
if(evaluationMap.containsKey(node)){
return false;
}
if(node.getData().hasGenerated() &&
(
node.getData().isHomogenous() ||
this.getMinDistance(absVoxelPos, node, distCache) > SIXTEENTH_RES_DIST
)
if(
node.getData().hasGenerated() &&
(
node.getData().isHomogenous() ||
this.getMinDistance(absVoxelPos, node, distCache) > SIXTEENTH_RES_DIST
) &&
distCache != BUST_META_CELLS
){
return false;
}
if(node.isLeaf()){
if(this.isMeta(absVoxelPos, node, distCache)){
if(distCache == BUST_META_CELLS){
node.getData().setHasGenerated(false);
} if(this.isMeta(absVoxelPos, node, distCache)){
this.flagAsMeta(node);
} else if(this.shouldSplit(absVoxelPos, node, distCache)){
Globals.profiler.beginCpuSample("FoliageCellManager.split");
@ -321,12 +361,12 @@ public class FoliageCellManager {
/**
* Gets the minimum distance from a node to a point
* @param pos the position to check against
* @param absVoxelPos the position to check against
* @param node the node
* @return the distance
*/
public long getMinDistance(Vector3i worldPos, WorldOctTreeNode<FoliageCell> node, int distCache){
return node.getData().getMinDistance(worldPos, node, distCache);
public long getMinDistance(Vector3i absVoxelPos, WorldOctTreeNode<FoliageCell> node, int distCache){
return node.getData().getMinDistance(absVoxelPos, node, distCache);
}
/**
@ -734,7 +774,8 @@ public class FoliageCellManager {
* @return true if all cells were successfully requested, false otherwise
*/
private boolean requestChunks(WorldOctTree.WorldOctTreeNode<FoliageCell> node){
Vector3i worldPos = node.getMinBound();
//min bound is in absolute voxel coordinates, need to convert to world coordinates
Vector3i worldPos = Globals.clientWorldData.convertAbsoluteVoxelToWorldSpace(node.getMinBound());
if(
worldPos.x >= 0 &&
worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() &&
@ -742,11 +783,11 @@ public class FoliageCellManager {
worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.z >= 0 &&
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() &&
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, 0)
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, ChunkData.NO_STRIDE)
){
//client should request chunk data from server for each chunk necessary to create the model
LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + worldPos);
if(!Globals.clientTerrainManager.requestChunk(worldPos.x, worldPos.y, worldPos.z, 0)){
if(!Globals.clientTerrainManager.requestChunk(worldPos.x, worldPos.y, worldPos.z, ChunkData.NO_STRIDE)){
return false;
}
}
@ -756,16 +797,12 @@ public class FoliageCellManager {
/**
* Checks if all chunk data required to generate this foliage cell is present
* @param node The node
* @param highResFace The higher resolution face of a not-full-resolution chunk. Null if the chunk is max resolution or there is no higher resolution face for the current chunk
* @return true if all data is available, false otherwise
*/
private boolean containsDataToGenerate(WorldOctTree.WorldOctTreeNode<FoliageCell> node){
FoliageCell cell = node.getData();
Vector3i worldPos = cell.getWorldPos();
if(!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, 0)){
return false;
}
return true;
return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, ChunkData.NO_STRIDE);
}
/**
@ -878,6 +915,14 @@ public class FoliageCellManager {
public int getNodeCount(){
return this.chunkTree.getNodeCount();
}
/**
* Logs when the manager next evaluates the supplied absolute voxel position. Should be used to break at that point.
* @param absVoxelPos The absolute voxel position to break at
*/
public void addBreakPoint(Vector3i absVoxelPos){
this.breakPoints.add(absVoxelPos);
}
}

View File

@ -1,6 +1,9 @@
package electrosphere.client.ui.menu.debug;
import org.joml.Vector3i;
import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.renderer.ui.imgui.ImGuiWindow;
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
import imgui.ImGui;
@ -47,6 +50,12 @@ public class ImGuiChunkMonitor {
if(Globals.foliageCellManager != null){
ImGui.text("Foliage node count: " + Globals.foliageCellManager.getNodeCount());
}
if(ImGui.button("Break at chunk")){
if(Globals.foliageCellManager != null){
Vector3i absVoxelPos = Globals.clientWorldData.convertRealToAbsoluteVoxelSpace(EntityUtils.getPosition(Globals.playerEntity));
Globals.foliageCellManager.addBreakPoint(absVoxelPos);
}
}
}
});
chunkMonitorWindow.setOpen(false);

View File

@ -39,7 +39,7 @@ public class ServerFallTree implements BehaviorTree {
/**
* The minimum frames to wait before scanning if it should activate due to gravity
*/
public static final int MIN_FRAMES_BEFORE_ACTIVATION_SCAN = 10;
public static final int MIN_FRAMES_BEFORE_ACTIVATION_SCAN = 60;
/**
* The minimum frames to wait before playing landing animation on fall

View File

@ -11,7 +11,7 @@ public class HillsGen implements HeightmapGenerator {
/**
* Offset from baseline to place the noisemap at
*/
static final float HEIGHT_OFFSET = 10;
static final float HEIGHT_OFFSET = 100;
/**
* Scales the input positions
@ -21,7 +21,7 @@ public class HillsGen implements HeightmapGenerator {
/**
* Scales the output height
*/
static final float VERTICAL_SCALE = 200.0f;
static final float VERTICAL_SCALE = 100.0f;
/**
* The different scales of noise to sample from

View File

@ -127,9 +127,9 @@ public class GeomUtils {
int maxX = cubeMax.x;
int maxY = cubeMax.y;
int maxZ = cubeMax.z;
if(pos.x > minX && pos.x < maxX){
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
if(pos.x >= minX && pos.x <= maxX){
if(pos.y >= minY && pos.y <= maxY){
if(pos.z >= minZ && pos.z <= maxZ){
return 0;
} else if(Math.abs(pos.z - minZ) < Math.abs(pos.z - maxZ)){
return pos.distance(pos.x,pos.y,minZ);
@ -137,8 +137,8 @@ public class GeomUtils {
return pos.distance(pos.x,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(Math.abs(pos.y - maxY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - maxY);
if(Math.abs(pos.y - minY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - minY);
}
if(pos.z > minZ && pos.z < maxZ){
return pos.distance(pos.x,minY,pos.z);
@ -148,8 +148,8 @@ public class GeomUtils {
return pos.distance(pos.x,minY,maxZ);
}
} else {
if(Math.abs(pos.y - minY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - minY);
if(Math.abs(pos.y - maxY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - maxY);
}
if(pos.z > minZ && pos.z < maxZ){
return pos.distance(pos.x,maxY,pos.z);
@ -160,8 +160,8 @@ public class GeomUtils {
}
}
} else if(Math.abs(pos.x - minX) < Math.abs(pos.x - maxX)){
if(Math.abs(pos.x - maxX) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.x - maxX);
if(Math.abs(pos.x - minX) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.x - minX);
}
if(pos.y > minY && pos.y < maxY){
if(pos.z > minZ && pos.z < maxZ){
@ -172,8 +172,8 @@ public class GeomUtils {
return pos.distance(minX,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(Math.abs(pos.y - maxY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - maxY);
if(Math.abs(pos.y - minY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - minY);
}
if(pos.z > minZ && pos.z < maxZ){
return pos.distance(minX,minY,pos.z);
@ -183,8 +183,8 @@ public class GeomUtils {
return pos.distance(minX,minY,maxZ);
}
} else {
if(Math.abs(pos.y - minY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - minY);
if(Math.abs(pos.y - maxY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - maxY);
}
if(pos.z > minZ && pos.z < maxZ){
return pos.distance(minX,maxY,pos.z);
@ -195,7 +195,7 @@ public class GeomUtils {
}
}
} else {
if(Math.abs(pos.x - minX) > SIMPLIFICATION_CUTOFF){
if(Math.abs(pos.x - maxX) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.x - maxX);
}
if(pos.y > minY && pos.y < maxY){
@ -207,8 +207,8 @@ public class GeomUtils {
return pos.distance(maxX,pos.y,maxZ);
}
} else if(Math.abs(pos.y - minY) < Math.abs(pos.y - maxY)){
if(Math.abs(pos.y - maxY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - maxY);
if(Math.abs(pos.y - minY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - minY);
}
if(pos.z > minZ && pos.z < maxZ){
return pos.distance(maxX,minY,pos.z);
@ -218,8 +218,8 @@ public class GeomUtils {
return pos.distance(maxX,minY,maxZ);
}
} else {
if(Math.abs(pos.y - minY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - minY);
if(Math.abs(pos.y - maxY) > SIMPLIFICATION_CUTOFF){
return Math.abs(pos.y - maxY);
}
if(pos.z > minZ && pos.z < maxZ){
return pos.distance(maxX,maxY,pos.z);