block pathfinding work

This commit is contained in:
austin 2025-05-04 13:58:08 -04:00
parent e33c57f983
commit 8462932abd
9 changed files with 280 additions and 28 deletions

View File

@ -1658,6 +1658,10 @@ Remove several usages of concurrent datastructures
Fix block chunk data allocation explosion
Fix pathfinding voxel hashing calculating
(05/04/2025)
Path to nearest valid voxel instead of a non-walkable voxel
Blocks factor into voxel pathfinding

View File

@ -175,14 +175,7 @@ public class ClientInteractionEngine {
public static void destroyCollidableTemplate(Entity rVal){
lock.lock();
CollisionEngine interactionEngine = Globals.clientSceneWrapper.getInteractionEngine();
Collidable collidable = (Collidable)rVal.getData(EntityDataStrings.INTERACTION_COLLIDABLE);
DBody body = (DBody)rVal.getData(EntityDataStrings.INTERACTION_BODY);
if(body != null){
PhysicsUtils.destroyBody(interactionEngine, body);
if(collidable != null){
interactionEngine.deregisterCollisionObject(body, collidable);
}
}
interactionEngine.destroyPhysics(rVal);
lock.unlock();
}

View File

@ -18,6 +18,7 @@ import electrosphere.renderer.actor.ActorTextureMask;
import electrosphere.renderer.ui.imgui.ImGuiWindow;
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
import electrosphere.server.ai.AI;
import electrosphere.server.ai.nodes.plan.PathfindingNode;
import electrosphere.server.datacell.gridded.GriddedDataCellManager;
import electrosphere.server.datacell.utils.EntityLookupUtils;
import electrosphere.server.pathfinding.voxel.VoxelPathfinder;
@ -74,14 +75,6 @@ public class ImGuiAI {
}
int serverIdForClientEntity = Globals.clientSceneWrapper.mapClientToServerId(Globals.playerEntity.getId());
Entity serverPlayerEntity = EntityLookupUtils.getEntityById(serverIdForClientEntity);
AI playerAi = AI.getAI(serverPlayerEntity);
ImGui.text("AI applied to player entity: " + playerAi.isApplyToPlayer());
if(ImGui.button("Toggle AI on player entity")){
playerAi.setApplyToPlayer(!playerAi.isApplyToPlayer());
}
if(ImGui.collapsingHeader("Statuses")){
for(AI ai : Globals.aiManager.getAIList()){
if(ImGui.collapsingHeader(ai.getParent().getId() + " - " + ai.getStatus())){
@ -93,6 +86,23 @@ public class ImGuiAI {
}
if(ImGui.collapsingHeader("Debug Player AI")){
int serverIdForClientEntity = Globals.clientSceneWrapper.mapClientToServerId(Globals.playerEntity.getId());
Entity serverPlayerEntity = EntityLookupUtils.getEntityById(serverIdForClientEntity);
AI playerAi = AI.getAI(serverPlayerEntity);
Vector3d playerTargetPos = null;
if(PathfindingNode.hasPathfindingPoint(playerAi.getBlackboard())){
playerTargetPos = PathfindingNode.getPathfindingPoint(playerAi.getBlackboard());
}
ImGui.text("AI applied to player entity: " + playerAi.isApplyToPlayer());
if(playerTargetPos != null){
ImGui.text("Target pos: " + playerTargetPos.x + "," + playerTargetPos.y + "," + playerTargetPos.z);
} else {
ImGui.text("Target pos: UNDEFINED");
}
if(ImGui.button("Toggle AI on player entity")){
playerAi.setApplyToPlayer(!playerAi.isApplyToPlayer());
}
if(ImGui.button("Reset iterations")){
numIterations = 1;
}
@ -101,7 +111,11 @@ public class ImGuiAI {
VoxelPathfinder voxelPathfinder = new VoxelPathfinder();
GriddedDataCellManager griddedDataCellManager = (GriddedDataCellManager)Globals.realmManager.getEntityRealm(serverPlayerEntity).getDataCellManager();
Vector3d playerPos = new Vector3d(EntityUtils.getPosition(serverPlayerEntity));
List<PathfinderNode> closedSet = voxelPathfinder.aStarStep(griddedDataCellManager, playerPos, new Vector3d(playerPos).add(10,0,0), 1000, numIterations);
Vector3d targetPos = new Vector3d(playerPos).add(10,0,0);
if(playerTargetPos != null){
targetPos = playerTargetPos;
}
List<PathfinderNode> closedSet = voxelPathfinder.aStarStep(griddedDataCellManager, playerPos, targetPos, 1000, numIterations);
if(displayEntity.size() > 0){
for(Entity entity : displayEntity){
@ -123,7 +137,11 @@ public class ImGuiAI {
VoxelPathfinder voxelPathfinder = new VoxelPathfinder();
GriddedDataCellManager griddedDataCellManager = (GriddedDataCellManager)Globals.realmManager.getEntityRealm(serverPlayerEntity).getDataCellManager();
Vector3d playerPos = new Vector3d(EntityUtils.getPosition(serverPlayerEntity));
List<PathfinderNode> closedSet = voxelPathfinder.aStarStepOpen(griddedDataCellManager, playerPos, new Vector3d(playerPos).add(10,0,0), 1000, numIterations);
Vector3d targetPos = new Vector3d(playerPos).add(10,0,0);
if(PathfindingNode.hasPathfindingPoint(playerAi.getBlackboard())){
targetPos = PathfindingNode.getPathfindingPoint(playerAi.getBlackboard());
}
List<PathfinderNode> closedSet = voxelPathfinder.aStarStepOpen(griddedDataCellManager, playerPos, targetPos, 1000, numIterations);
if(displayEntity.size() > 0){
for(Entity entity : displayEntity){

View File

@ -584,13 +584,13 @@ public class PhysicsEntityUtils {
for(Entity parent : toReposition){
Vector3d parentPos = EntityUtils.getPosition(parent);
if(ServerWorldData.convertRealToChunkSpace(parentPos.x) >= worldDat.getWorldSizeDiscrete()){
parentPos.x = worldDat.convertWorldToReal(worldDat.getWorldSizeDiscrete()) - WORLD_MARGIN;
parentPos.x = ServerWorldData.convertWorldToReal(worldDat.getWorldSizeDiscrete()) - WORLD_MARGIN;
}
if(ServerWorldData.convertRealToChunkSpace(parentPos.y) >= worldDat.getWorldSizeDiscrete()){
parentPos.y = worldDat.convertWorldToReal(worldDat.getWorldSizeDiscrete()) - WORLD_MARGIN;
parentPos.y = ServerWorldData.convertWorldToReal(worldDat.getWorldSizeDiscrete()) - WORLD_MARGIN;
}
if(ServerWorldData.convertRealToChunkSpace(parentPos.z) >= worldDat.getWorldSizeDiscrete()){
parentPos.z = worldDat.convertWorldToReal(worldDat.getWorldSizeDiscrete()) - WORLD_MARGIN;
parentPos.z = ServerWorldData.convertWorldToReal(worldDat.getWorldSizeDiscrete()) - WORLD_MARGIN;
}
ServerEntityUtils.repositionEntity(parent,parentPos);
}

View File

@ -73,9 +73,9 @@ public class ServerEntityUtils {
position.x < 0 ||
position.y < 0 ||
position.z < 0 ||
worldDat.convertRealToChunkSpace(position.x) >= worldDat.getWorldSizeDiscrete() ||
worldDat.convertRealToChunkSpace(position.y) >= worldDat.getWorldSizeDiscrete() ||
worldDat.convertRealToChunkSpace(position.z) >= worldDat.getWorldSizeDiscrete()
ServerWorldData.convertRealToChunkSpace(position.x) >= worldDat.getWorldSizeDiscrete() ||
ServerWorldData.convertRealToChunkSpace(position.y) >= worldDat.getWorldSizeDiscrete() ||
ServerWorldData.convertRealToChunkSpace(position.z) >= worldDat.getWorldSizeDiscrete()
){
throw new Error("Providing invalid location to reposition! " + position);
}

View File

@ -32,7 +32,7 @@ public class FellTree {
/**
* Distance to start attacking at
*/
static final float FELL_RANGE = 0.5f;
static final float FELL_RANGE = 0.7f;
/**
* Creates a fell tree

View File

@ -285,6 +285,20 @@ public class ServerWorldData {
public static double convertVoxelToRealSpace(int voxelPos, int worldPos){
return voxelPos + ServerWorldData.convertWorldToReal(worldPos);
}
/**
* Converts a chunk space vector to a real space vector
* @param voxelPos The voxel's position within the chunk
* @param worldPos The world pos of the chunk
* @return The real pos
*/
public static Vector3d convertVoxelToRealSpace(Vector3i voxelPos, Vector3i worldPos){
return new Vector3d(
ServerWorldData.convertVoxelToRealSpace(voxelPos.x, worldPos.x),
ServerWorldData.convertVoxelToRealSpace(voxelPos.y, worldPos.y),
ServerWorldData.convertVoxelToRealSpace(voxelPos.z, worldPos.z)
);
}
public double getRelativeLocation(double real, int world){
return real - (world * dynamicInterpolationRatio);

View File

@ -1096,7 +1096,8 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
if(trackingData == null){
throw new Error("Failed to find tracking data for " + start);
}
List<Vector3d> points = this.pathfinder.findPath(this, start, end, VoxelPathfinder.DEFAULT_MAX_COST);
Vector3d nearestValidGoal = this.pathfinder.scanNearestWalkable(this, end, VoxelPathfinder.DEFAULT_MAX_TARGET_SCAN_DIST);
List<Vector3d> points = this.pathfinder.findPath(this, start, nearestValidGoal, VoxelPathfinder.DEFAULT_MAX_COST);
return points;
}

View File

@ -11,6 +11,7 @@ import java.util.stream.Collectors;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.block.BlockChunkData;
import electrosphere.server.datacell.ServerWorldData;
import electrosphere.server.datacell.interfaces.VoxelCellManager;
import electrosphere.server.physics.terrain.manager.ServerTerrainChunk;
@ -31,10 +32,15 @@ public class VoxelPathfinder {
*/
public static final int DEFAULT_MAX_COST = 12000;
/**
* Maximum distance to scan for a walkable position
*/
public static final double DEFAULT_MAX_TARGET_SCAN_DIST = 3;
/**
* The heuristic lookup table
*/
int[][][] heuristic = new int[3][3][3];
private int[][][] heuristic = new int[3][3][3];
/**
* Finds a path between two points given a voxel cell manager
@ -885,7 +891,223 @@ public class VoxelPathfinder {
int aboveType = chunk.getType(voxelPos.x, aboveVoxel, voxelPos.z);
float aboveWeight = chunk.getWeight(voxelPos.x, aboveVoxel, voxelPos.z);
return voxelType != ServerTerrainChunk.VOXEL_TYPE_AIR && voxelWeight > 0 && (aboveType == ServerTerrainChunk.VOXEL_TYPE_AIR || aboveWeight <= 0);
//checks
boolean standingOnGround = voxelType != ServerTerrainChunk.VOXEL_TYPE_AIR && voxelWeight > 0;
boolean aboveIsAir = (aboveType == ServerTerrainChunk.VOXEL_TYPE_AIR || aboveWeight <= 0);
//check blocks as well
BlockChunkData blockChunkData = null;
Vector3i blockPos = new Vector3i();
Vector3i currChunk = new Vector3i(chunkPos);
Vector3i offsets = new Vector3i();
int numBlocks = 0;
for(int x = 0; x < 4; x++){
for(int y = 0; y < 4; y++){
for(int z = 0; z < 4; z++){
blockPos.set(voxelPos);
currChunk.set(chunkPos);
offsets.set(
x,
y + BlockChunkData.BLOCKS_PER_UNIT_DISTANCE,
z
);
VoxelPathfinder.clampBlockOffsets(blockPos, currChunk, offsets);
blockChunkData = voxelCellManager.getBlocksAtPosition(currChunk);
if(blockChunkData.getType(
blockPos.x,
blockPos.y,
blockPos.z
) != BlockChunkData.BLOCK_TYPE_EMPTY){
numBlocks++;
}
}
}
}
boolean blocksChestHeight = numBlocks > 10;
return standingOnGround && aboveIsAir && !blocksChestHeight;
}
/**
* Scans for the nearest walkable position to the specified target point
* @param voxelCellManager The voxel cell manager
* @param targetPoint The target point to scan from
* @param maxScanRadius The maximum radius to scan for walkable positions
* @return The closest point that is walkable, or null if no point is found
*/
public Vector3d scanNearestWalkable(VoxelCellManager voxelCellManager, Vector3d targetPoint, double maxScanRadius){
int radius = 1;
int scanned = 0;
Vector3i originVoxel = ServerWorldData.convertRealToVoxelSpace(targetPoint);
Vector3i originChunk = ServerWorldData.convertRealToChunkSpace(targetPoint);
//check if supplied point is valid
if(this.isWalkable(voxelCellManager, originChunk, originVoxel)){
return targetPoint;
}
Vector3i currVoxel = new Vector3i();
Vector3i currChunk = new Vector3i();
Vector3d realPos;
Vector3i offsets = new Vector3i();
while(true){
scanned = 0;
for(int x = -radius; x <= radius; x++){
for(int y = -radius; y <= radius; y++){
currVoxel.set(originVoxel);
currChunk.set(originChunk);
offsets.set(-radius,x,y);
VoxelPathfinder.clampVoxelOffsets(currVoxel, currChunk, offsets);
realPos = ServerWorldData.convertVoxelToRealSpace(currVoxel.set(originVoxel).add(-radius,x,y), currChunk);
if(realPos.distance(targetPoint) < maxScanRadius){
scanned++;
if(this.isWalkable(voxelCellManager, currChunk, currVoxel)){
return realPos;
}
}
currVoxel.set(originVoxel);
currChunk.set(originChunk);
offsets.set(radius,x,y);
VoxelPathfinder.clampVoxelOffsets(currVoxel, currChunk, offsets);
realPos = ServerWorldData.convertVoxelToRealSpace(currVoxel.set(originVoxel).add(-radius,x,y), currChunk);
if(realPos.distance(targetPoint) < maxScanRadius){
scanned++;
if(this.isWalkable(voxelCellManager, currChunk, currVoxel)){
return realPos;
}
}
currVoxel.set(originVoxel);
currChunk.set(originChunk);
offsets.set(x,-radius,y);
VoxelPathfinder.clampVoxelOffsets(currVoxel, currChunk, offsets);
realPos = ServerWorldData.convertVoxelToRealSpace(currVoxel.set(originVoxel).add(-radius,x,y), currChunk);
if(realPos.distance(targetPoint) < maxScanRadius){
scanned++;
if(this.isWalkable(voxelCellManager, currChunk, currVoxel)){
return realPos;
}
}
currVoxel.set(originVoxel);
currChunk.set(originChunk);
offsets.set(x,radius,y);
VoxelPathfinder.clampVoxelOffsets(currVoxel, currChunk, offsets);
realPos = ServerWorldData.convertVoxelToRealSpace(currVoxel.set(originVoxel).add(-radius,x,y), currChunk);
if(realPos.distance(targetPoint) < maxScanRadius){
scanned++;
if(this.isWalkable(voxelCellManager, currChunk, currVoxel)){
return realPos;
}
}
currVoxel.set(originVoxel);
currChunk.set(originChunk);
offsets.set(x,y,-radius);
VoxelPathfinder.clampVoxelOffsets(currVoxel, currChunk, offsets);
realPos = ServerWorldData.convertVoxelToRealSpace(currVoxel.set(originVoxel).add(-radius,x,y), currChunk);
if(realPos.distance(targetPoint) < maxScanRadius){
scanned++;
if(this.isWalkable(voxelCellManager, currChunk, currVoxel)){
return realPos;
}
}
currVoxel.set(originVoxel);
currChunk.set(originChunk);
offsets.set(x,y,radius);
VoxelPathfinder.clampVoxelOffsets(currVoxel, currChunk, offsets);
realPos = ServerWorldData.convertVoxelToRealSpace(currVoxel.set(originVoxel).add(-radius,x,y), currChunk);
if(realPos.distance(targetPoint) < maxScanRadius){
scanned++;
if(this.isWalkable(voxelCellManager, currChunk, currVoxel)){
return realPos;
}
}
}
}
if(scanned == 0){
break;
}
radius++;
}
return null;
}
/**
* Clamps offsets to valid voxel positions
* @param voxelPos The existing voxel position
* @param chunkPos The existing chunk position
* @param offsets The offsets to apply
*/
private static void clampVoxelOffsets(Vector3i voxelPos, Vector3i chunkPos, Vector3i offsets){
//calculate chunk offsets
voxelPos.x = (voxelPos.x + offsets.x);
voxelPos.y = (voxelPos.y + offsets.y);
voxelPos.z = (voxelPos.z + offsets.z);
if(voxelPos.x < 0){
voxelPos.x = -1;
} else {
voxelPos.x = voxelPos.x / ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET;
}
if(voxelPos.y < 0){
voxelPos.y = -1;
} else {
voxelPos.y = voxelPos.y / ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET;
}
if(voxelPos.z < 0){
voxelPos.z = -1;
} else {
voxelPos.z = voxelPos.z / ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET;
}
//update world position
chunkPos.x = chunkPos.x + voxelPos.x;
chunkPos.y = chunkPos.y + voxelPos.y;
chunkPos.z = chunkPos.z + voxelPos.z;
voxelPos.x = (voxelPos.x + offsets.x + ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET) % ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET;
voxelPos.y = (voxelPos.y + offsets.y + ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET) % ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET;
voxelPos.z = (voxelPos.z + offsets.z + ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET) % ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET;
}
/**
* Clamps offsets to valid block positions
* @param blockPos The existing block position
* @param chunkPos The existing chunk position
* @param offsets The offsets to apply
*/
private static void clampBlockOffsets(Vector3i blockPos, Vector3i chunkPos, Vector3i offsets){
//calculate chunk offsets
blockPos.x = (blockPos.x + offsets.x);
blockPos.y = (blockPos.y + offsets.y);
blockPos.z = (blockPos.z + offsets.z);
if(blockPos.x < 0){
blockPos.x = -1;
} else {
blockPos.x = blockPos.x / BlockChunkData.CHUNK_DATA_WIDTH;
}
if(blockPos.y < 0){
blockPos.y = -1;
} else {
blockPos.y = blockPos.y / BlockChunkData.CHUNK_DATA_WIDTH;
}
if(blockPos.z < 0){
blockPos.z = -1;
} else {
blockPos.z = blockPos.z / BlockChunkData.CHUNK_DATA_WIDTH;
}
//update world position
chunkPos.x = chunkPos.x + blockPos.x;
chunkPos.y = chunkPos.y + blockPos.y;
chunkPos.z = chunkPos.z + blockPos.z;
blockPos.x = (blockPos.x + offsets.x + BlockChunkData.CHUNK_DATA_WIDTH) % BlockChunkData.CHUNK_DATA_WIDTH;
blockPos.y = (blockPos.y + offsets.y + BlockChunkData.CHUNK_DATA_WIDTH) % BlockChunkData.CHUNK_DATA_WIDTH;
blockPos.z = (blockPos.z + offsets.z + BlockChunkData.CHUNK_DATA_WIDTH) % BlockChunkData.CHUNK_DATA_WIDTH;
}
/**