Compare commits
	
		
			2 Commits
		
	
	
		
			8c44525493
			...
			18023872b0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					18023872b0 | ||
| 
						 | 
					4637770997 | 
@ -1647,6 +1647,8 @@ New AI behaviors
 | 
				
			|||||||
Fix bug where sync messages eternally bounce if the entity was already deleted
 | 
					Fix bug where sync messages eternally bounce if the entity was already deleted
 | 
				
			||||||
Fix blocks not saving to disk when being ejected from cache
 | 
					Fix blocks not saving to disk when being ejected from cache
 | 
				
			||||||
Block chunk memory pooling
 | 
					Block chunk memory pooling
 | 
				
			||||||
 | 
					Rename MoveToTree
 | 
				
			||||||
 | 
					Major pathfinding work -- breaking MoteToTree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -84,6 +84,7 @@ import electrosphere.server.datacell.EntityDataCellMapper;
 | 
				
			|||||||
import electrosphere.server.datacell.RealmManager;
 | 
					import electrosphere.server.datacell.RealmManager;
 | 
				
			||||||
import electrosphere.server.db.DatabaseController;
 | 
					import electrosphere.server.db.DatabaseController;
 | 
				
			||||||
import electrosphere.server.entity.poseactor.PoseModel;
 | 
					import electrosphere.server.entity.poseactor.PoseModel;
 | 
				
			||||||
 | 
					import electrosphere.server.pathfinding.Pathfinder;
 | 
				
			||||||
import electrosphere.server.saves.Save;
 | 
					import electrosphere.server.saves.Save;
 | 
				
			||||||
import electrosphere.server.simulation.MacroSimulation;
 | 
					import electrosphere.server.simulation.MacroSimulation;
 | 
				
			||||||
import electrosphere.server.simulation.MicroSimulation;
 | 
					import electrosphere.server.simulation.MicroSimulation;
 | 
				
			||||||
@ -430,6 +431,9 @@ public class Globals {
 | 
				
			|||||||
    public static Entity draggedItem = null;
 | 
					    public static Entity draggedItem = null;
 | 
				
			||||||
    public static Object dragSourceInventory = null;
 | 
					    public static Object dragSourceInventory = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //pathfinder
 | 
				
			||||||
 | 
					    public static Pathfinder pathfinder;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -518,6 +522,9 @@ public class Globals {
 | 
				
			|||||||
        gameConfigCurrent = gameConfigDefault;
 | 
					        gameConfigCurrent = gameConfigDefault;
 | 
				
			||||||
        NetConfig.readNetConfig();
 | 
					        NetConfig.readNetConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //pathfinder
 | 
				
			||||||
 | 
					        Globals.pathfinder = new Pathfinder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //
 | 
					        //
 | 
				
			||||||
        //Values that depend on the loaded config
 | 
					        //Values that depend on the loaded config
 | 
				
			||||||
        Globals.clientSelectedVoxelType = (VoxelType)gameConfigCurrent.getVoxelData().getTypes().toArray()[1];
 | 
					        Globals.clientSelectedVoxelType = (VoxelType)gameConfigCurrent.getVoxelData().getTypes().toArray()[1];
 | 
				
			||||||
 | 
				
			|||||||
@ -70,4 +70,9 @@ public class BlackboardKeys {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public static final String HARVEST_TARGET_TYPE = "harvestTargetType";
 | 
					    public static final String HARVEST_TARGET_TYPE = "harvestTargetType";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The pathfinding data
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static final String PATHFINDING_DATA = "pathfindingData";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					package electrosphere.server.ai.nodes.plan;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.joml.Vector3d;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import electrosphere.engine.Globals;
 | 
				
			||||||
 | 
					import electrosphere.entity.Entity;
 | 
				
			||||||
 | 
					import electrosphere.entity.EntityUtils;
 | 
				
			||||||
 | 
					import electrosphere.server.ai.blackboard.Blackboard;
 | 
				
			||||||
 | 
					import electrosphere.server.ai.blackboard.BlackboardKeys;
 | 
				
			||||||
 | 
					import electrosphere.server.ai.nodes.AITreeNode;
 | 
				
			||||||
 | 
					import electrosphere.server.datacell.Realm;
 | 
				
			||||||
 | 
					import electrosphere.server.datacell.interfaces.PathfindingManager;
 | 
				
			||||||
 | 
					import electrosphere.server.pathfinding.PathingProgressiveData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A node that performs pathfinding
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class PathfindingNode implements AITreeNode {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The blackboard key to lookup the target entity under
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String targetEntityKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param targetEntityKey
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static PathfindingNode createPathEntity(String targetEntityKey){
 | 
				
			||||||
 | 
					        PathfindingNode rVal = new PathfindingNode();
 | 
				
			||||||
 | 
					        rVal.targetEntityKey = targetEntityKey;
 | 
				
			||||||
 | 
					        return rVal;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
 | 
				
			||||||
 | 
					        if(!PathfindingNode.hasPathfindingData(blackboard)){
 | 
				
			||||||
 | 
					            Vector3d targetPos = null;
 | 
				
			||||||
 | 
					            if(this.targetEntityKey != null){
 | 
				
			||||||
 | 
					                Entity targetEnt = (Entity)blackboard.get(targetEntityKey);
 | 
				
			||||||
 | 
					                targetPos = EntityUtils.getPosition(targetEnt);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw new Error("Target position is null!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Realm realm = Globals.realmManager.getEntityRealm(entity);
 | 
				
			||||||
 | 
					            PathfindingManager pathfindingManager = realm.getPathfindingManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Vector3d entityPos = EntityUtils.getPosition(entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            List<Vector3d> path = pathfindingManager.findPath(entityPos, targetPos);
 | 
				
			||||||
 | 
					            PathingProgressiveData pathingProgressiveData = new PathingProgressiveData(path);
 | 
				
			||||||
 | 
					            PathfindingNode.setPathfindingData(blackboard, pathingProgressiveData);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AITreeNodeResult.SUCCESS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sets the pathfinding data in the blackboard
 | 
				
			||||||
 | 
					     * @param blackboard The blackboard
 | 
				
			||||||
 | 
					     * @param pathfindingData The pathfinding data
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static void setPathfindingData(Blackboard blackboard, PathingProgressiveData pathfindingData){
 | 
				
			||||||
 | 
					        blackboard.put(BlackboardKeys.PATHFINDING_DATA, pathfindingData);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Gets the current pathfinding data
 | 
				
			||||||
 | 
					     * @param blackboard The blackboard
 | 
				
			||||||
 | 
					     * @return The pathfinding data if it exists, null otherwise
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static PathingProgressiveData getPathfindingData(Blackboard blackboard){
 | 
				
			||||||
 | 
					        return (PathingProgressiveData)blackboard.get(BlackboardKeys.PATHFINDING_DATA);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Checks if the blackboard has pathfinding data
 | 
				
			||||||
 | 
					     * @param blackboard The blackboard
 | 
				
			||||||
 | 
					     * @return true if it has pathfinding data, false otherwise
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static boolean hasPathfindingData(Blackboard blackboard){
 | 
				
			||||||
 | 
					        return blackboard.has(BlackboardKeys.PATHFINDING_DATA);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Clears the pathfinding data
 | 
				
			||||||
 | 
					     * @param blackboard The pathfinding data
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static void clearPathfindingData(Blackboard blackboard){
 | 
				
			||||||
 | 
					        blackboard.delete(BlackboardKeys.PATHFINDING_DATA);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -44,7 +44,7 @@ public class AcquireItemTree {
 | 
				
			|||||||
                    new SourcingTypeNode(SourcingType.PICKUP, BlackboardKeys.ITEM_TARGET_CATEGORY),
 | 
					                    new SourcingTypeNode(SourcingType.PICKUP, BlackboardKeys.ITEM_TARGET_CATEGORY),
 | 
				
			||||||
                    //logic to pick up the item
 | 
					                    //logic to pick up the item
 | 
				
			||||||
                    new TargetEntityCategoryNode(BlackboardKeys.ITEM_TARGET_CATEGORY),
 | 
					                    new TargetEntityCategoryNode(BlackboardKeys.ITEM_TARGET_CATEGORY),
 | 
				
			||||||
                    MoveToTarget.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET),
 | 
					                    MoveToTree.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET),
 | 
				
			||||||
                    new CollectItemNode(),
 | 
					                    new CollectItemNode(),
 | 
				
			||||||
                    new RunnerNode(null)
 | 
					                    new RunnerNode(null)
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
@ -60,7 +60,7 @@ public class AcquireItemTree {
 | 
				
			|||||||
                    //check if we should be sourcing this from harvesting foliage
 | 
					                    //check if we should be sourcing this from harvesting foliage
 | 
				
			||||||
                    new SourcingTypeNode(SourcingType.HARVEST, blackboardKey),
 | 
					                    new SourcingTypeNode(SourcingType.HARVEST, blackboardKey),
 | 
				
			||||||
                    new TargetEntityCategoryNode(BlackboardKeys.HARVEST_TARGET_TYPE),
 | 
					                    new TargetEntityCategoryNode(BlackboardKeys.HARVEST_TARGET_TYPE),
 | 
				
			||||||
                    MoveToTarget.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET),
 | 
					                    MoveToTree.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET),
 | 
				
			||||||
                    new HarvestNode(),
 | 
					                    new HarvestNode(),
 | 
				
			||||||
                    new RunnerNode(null)
 | 
					                    new RunnerNode(null)
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
				
			|||||||
@ -10,11 +10,12 @@ import electrosphere.server.ai.nodes.meta.collections.SelectorNode;
 | 
				
			|||||||
import electrosphere.server.ai.nodes.meta.collections.SequenceNode;
 | 
					import electrosphere.server.ai.nodes.meta.collections.SequenceNode;
 | 
				
			||||||
import electrosphere.server.ai.nodes.meta.decorators.RunnerNode;
 | 
					import electrosphere.server.ai.nodes.meta.decorators.RunnerNode;
 | 
				
			||||||
import electrosphere.server.ai.nodes.meta.decorators.SucceederNode;
 | 
					import electrosphere.server.ai.nodes.meta.decorators.SucceederNode;
 | 
				
			||||||
 | 
					import electrosphere.server.ai.nodes.plan.PathfindingNode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Moves to a target
 | 
					 * Moves to a target
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class MoveToTarget {
 | 
					public class MoveToTree {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Name of the tree
 | 
					     * Name of the tree
 | 
				
			||||||
@ -38,7 +39,7 @@ public class MoveToTarget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            //not in range of target, keep moving towards it
 | 
					            //not in range of target, keep moving towards it
 | 
				
			||||||
            new SequenceNode(
 | 
					            new SequenceNode(
 | 
				
			||||||
                //check that dependencies exist
 | 
					                PathfindingNode.createPathEntity(targetKey),
 | 
				
			||||||
                new FaceTargetNode(targetKey),
 | 
					                new FaceTargetNode(targetKey),
 | 
				
			||||||
                new RunnerNode(new MoveStartNode(MovementRelativeFacing.FORWARD))
 | 
					                new RunnerNode(new MoveStartNode(MovementRelativeFacing.FORWARD))
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@ -16,7 +16,7 @@ import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode;
 | 
				
			|||||||
import electrosphere.server.ai.nodes.meta.decorators.RunnerNode;
 | 
					import electrosphere.server.ai.nodes.meta.decorators.RunnerNode;
 | 
				
			||||||
import electrosphere.server.ai.nodes.meta.decorators.TimerNode;
 | 
					import electrosphere.server.ai.nodes.meta.decorators.TimerNode;
 | 
				
			||||||
import electrosphere.server.ai.nodes.meta.decorators.UntilNode;
 | 
					import electrosphere.server.ai.nodes.meta.decorators.UntilNode;
 | 
				
			||||||
import electrosphere.server.ai.trees.creature.MoveToTarget;
 | 
					import electrosphere.server.ai.trees.creature.MoveToTree;
 | 
				
			||||||
import electrosphere.server.ai.trees.creature.inventory.EquipToolbarTree;
 | 
					import electrosphere.server.ai.trees.creature.inventory.EquipToolbarTree;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -68,7 +68,7 @@ public class FellTree {
 | 
				
			|||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                //move to target
 | 
					                //move to target
 | 
				
			||||||
                MoveToTarget.create(FellTree.FELL_RANGE, targetKey),
 | 
					                MoveToTree.create(FellTree.FELL_RANGE, targetKey),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                //movement succeeded, but failed to attack -- tree is currently running
 | 
					                //movement succeeded, but failed to attack -- tree is currently running
 | 
				
			||||||
                new RunnerNode(null)
 | 
					                new RunnerNode(null)
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode;
 | 
				
			|||||||
import electrosphere.server.ai.nodes.meta.decorators.SucceederNode;
 | 
					import electrosphere.server.ai.nodes.meta.decorators.SucceederNode;
 | 
				
			||||||
import electrosphere.server.ai.nodes.solvers.SolveBuildMaterialNode;
 | 
					import electrosphere.server.ai.nodes.solvers.SolveBuildMaterialNode;
 | 
				
			||||||
import electrosphere.server.ai.trees.creature.AcquireItemTree;
 | 
					import electrosphere.server.ai.trees.creature.AcquireItemTree;
 | 
				
			||||||
import electrosphere.server.ai.trees.creature.MoveToTarget;
 | 
					import electrosphere.server.ai.trees.creature.MoveToTree;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A tree to build whatever the current structure target is
 | 
					 * A tree to build whatever the current structure target is
 | 
				
			||||||
@ -44,7 +44,7 @@ public class BuildStructureTree {
 | 
				
			|||||||
                        //if we're within range to place the material
 | 
					                        //if we're within range to place the material
 | 
				
			||||||
                        new SequenceNode(
 | 
					                        new SequenceNode(
 | 
				
			||||||
                            //not in range, move to within range
 | 
					                            //not in range, move to within range
 | 
				
			||||||
                            MoveToTarget.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.STRUCTURE_TARGET),
 | 
					                            MoveToTree.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.STRUCTURE_TARGET),
 | 
				
			||||||
                            //equip the type of block to place
 | 
					                            //equip the type of block to place
 | 
				
			||||||
                            EquipToolbarNode.equipBlock(BlackboardKeys.BUILDING_MATERIAL_CURRENT),
 | 
					                            EquipToolbarNode.equipBlock(BlackboardKeys.BUILDING_MATERIAL_CURRENT),
 | 
				
			||||||
                            //in range, place block
 | 
					                            //in range, place block
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ import java.util.concurrent.locks.ReentrantLock;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.joml.Vector3d;
 | 
					import org.joml.Vector3d;
 | 
				
			||||||
import org.joml.Vector3i;
 | 
					import org.joml.Vector3i;
 | 
				
			||||||
 | 
					import org.recast4j.detour.MeshData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import electrosphere.client.block.BlockChunkData;
 | 
					import electrosphere.client.block.BlockChunkData;
 | 
				
			||||||
import electrosphere.client.terrain.data.TerrainChunkData;
 | 
					import electrosphere.client.terrain.data.TerrainChunkData;
 | 
				
			||||||
@ -364,7 +365,11 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
 | 
				
			|||||||
        ServerDataCell serverDataCell = this.groundDataCells.get(key);
 | 
					        ServerDataCell serverDataCell = this.groundDataCells.get(key);
 | 
				
			||||||
        GriddedDataCellTrackingData trackingData = this.cellTrackingMap.get(serverDataCell);
 | 
					        GriddedDataCellTrackingData trackingData = this.cellTrackingMap.get(serverDataCell);
 | 
				
			||||||
        if(terrainMeshData.getVertices().length > 0){
 | 
					        if(terrainMeshData.getVertices().length > 0){
 | 
				
			||||||
            trackingData.setNavMeshData(NavMeshConstructor.constructNavmesh(terrainMeshData));
 | 
					            MeshData pathingMeshData = NavMeshConstructor.constructNavmesh(terrainMeshData);
 | 
				
			||||||
 | 
					            if(pathingMeshData == null){
 | 
				
			||||||
 | 
					                throw new Error("Failed to build pathing data from existing vertices!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            trackingData.setNavMeshData(pathingMeshData);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loadedCellsLock.lock();
 | 
					        loadedCellsLock.lock();
 | 
				
			||||||
@ -802,7 +807,11 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                //create pathfinding mesh
 | 
					                //create pathfinding mesh
 | 
				
			||||||
                if(terrainMeshData.getVertices().length > 0){
 | 
					                if(terrainMeshData.getVertices().length > 0){
 | 
				
			||||||
                    trackingData.setNavMeshData(NavMeshConstructor.constructNavmesh(terrainMeshData));
 | 
					                    MeshData pathingMeshData = NavMeshConstructor.constructNavmesh(terrainMeshData);
 | 
				
			||||||
 | 
					                    if(pathingMeshData == null){
 | 
				
			||||||
 | 
					                        throw new Error("Failed to build pathing data from existing vertices!");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    trackingData.setNavMeshData(pathingMeshData);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                //set ready
 | 
					                //set ready
 | 
				
			||||||
@ -1104,7 +1113,18 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public List<Vector3d> findPath(Vector3d start, Vector3d end) {
 | 
					    public List<Vector3d> findPath(Vector3d start, Vector3d end) {
 | 
				
			||||||
        throw new UnsupportedOperationException("Unimplemented method 'findPath'");
 | 
					        Vector3i startChunkPos = ServerWorldData.convertRealToChunkSpace(start);
 | 
				
			||||||
 | 
					        ServerDataCell cell = this.getCellAtWorldPosition(startChunkPos);
 | 
				
			||||||
 | 
					        GriddedDataCellTrackingData trackingData = this.cellTrackingMap.get(cell);
 | 
				
			||||||
 | 
					        if(trackingData == null){
 | 
				
			||||||
 | 
					            throw new Error("Failed to find tracking data for " + start);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        MeshData trackingMeshData = trackingData.getNavMeshData();
 | 
				
			||||||
 | 
					        if(trackingMeshData == null){
 | 
				
			||||||
 | 
					            throw new Error("Tracking mesh data is null!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        List<Vector3d> points = Globals.pathfinder.solve(trackingMeshData, start, end);
 | 
				
			||||||
 | 
					        return points;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -1,111 +1,131 @@
 | 
				
			|||||||
package electrosphere.server.pathfinding;
 | 
					package electrosphere.server.pathfinding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.joml.Vector3d;
 | 
				
			||||||
import org.recast4j.detour.MeshData;
 | 
					import org.recast4j.detour.MeshData;
 | 
				
			||||||
import org.recast4j.detour.NavMeshBuilder;
 | 
					import org.recast4j.detour.NavMeshBuilder;
 | 
				
			||||||
import org.recast4j.detour.NavMeshDataCreateParams;
 | 
					import org.recast4j.detour.NavMeshDataCreateParams;
 | 
				
			||||||
import org.recast4j.recast.AreaModification;
 | 
					import org.recast4j.recast.AreaModification;
 | 
				
			||||||
 | 
					import org.recast4j.recast.CompactHeightfield;
 | 
				
			||||||
 | 
					import org.recast4j.recast.Heightfield;
 | 
				
			||||||
import org.recast4j.recast.PolyMesh;
 | 
					import org.recast4j.recast.PolyMesh;
 | 
				
			||||||
import org.recast4j.recast.PolyMeshDetail;
 | 
					import org.recast4j.recast.PolyMeshDetail;
 | 
				
			||||||
import org.recast4j.recast.RecastBuilder;
 | 
					import org.recast4j.recast.RecastBuilder;
 | 
				
			||||||
import org.recast4j.recast.RecastBuilder.RecastBuilderResult;
 | 
					import org.recast4j.recast.RecastBuilder.RecastBuilderResult;
 | 
				
			||||||
import org.recast4j.recast.RecastConstants.PartitionType;
 | 
					 | 
				
			||||||
import org.recast4j.recast.RecastBuilderConfig;
 | 
					import org.recast4j.recast.RecastBuilderConfig;
 | 
				
			||||||
import org.recast4j.recast.RecastConfig;
 | 
					import org.recast4j.recast.RecastConfig;
 | 
				
			||||||
 | 
					import org.recast4j.recast.RecastConstants;
 | 
				
			||||||
 | 
					import org.recast4j.recast.RecastConstants.PartitionType;
 | 
				
			||||||
 | 
					import org.recast4j.recast.Span;
 | 
				
			||||||
import org.recast4j.recast.geom.SingleTrimeshInputGeomProvider;
 | 
					import org.recast4j.recast.geom.SingleTrimeshInputGeomProvider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import electrosphere.client.terrain.data.TerrainChunkData;
 | 
					import electrosphere.entity.state.collidable.TriGeomData;
 | 
				
			||||||
 | 
					import electrosphere.util.math.GeomUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Constructor methods for nav meshes
 | 
					 * Constructor methods for nav meshes
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class NavMeshConstructor {
 | 
					public class NavMeshConstructor {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Minimum size of geometry aabb
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static final float AABB_MIN_SIZE = 0.01f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Size of a recast cell
 | 
					     * Size of a recast cell
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final float RECAST_CELL_SIZE = 1.0f;
 | 
					    static final float RECAST_CELL_SIZE = 0.3f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Height of a recast cell
 | 
					     * Height of a recast cell
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final float RECAST_CELL_HEIGHT = 1.0f;
 | 
					    static final float RECAST_CELL_HEIGHT = 0.2f;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Size of a recast agent
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    static final float RECAST_AGENT_SIZE = 1.0f;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Height of a recast agent
 | 
					     * Height of a recast agent
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final float RECAST_AGENT_HEIGHT = 1.0f;
 | 
					    static final float RECAST_AGENT_HEIGHT = 1.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Size of a recast agent
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    static final float RECAST_AGENT_SIZE = 0.5f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Maximum height a recast agent can climb
 | 
					     * Maximum height a recast agent can climb
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final float RECAST_AGENT_MAX_CLIMB = 0.1f;
 | 
					    static final float RECAST_AGENT_MAX_CLIMB = 0.9f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Maximum slope a recast agent can handle
 | 
					     * Maximum slope a recast agent can handle
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final float RECAST_AGENT_MAX_SLOPE = 0.4f;
 | 
					    static final float RECAST_AGENT_MAX_SLOPE = 60.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Minimum size of a recast region
 | 
					     * Minimum size of a recast region
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final int RECAST_MIN_REGION_SIZE = 1;
 | 
					    static final int RECAST_MIN_REGION_SIZE = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Merge size of a recast region
 | 
					     * Merge size of a recast region
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final int RECAST_REGION_MERGE_SIZE = 1;
 | 
					    static final int RECAST_REGION_MERGE_SIZE = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final float RECAST_REGION_EDGE_MAX_LEN = 1.0f;
 | 
					    static final float RECAST_REGION_EDGE_MAX_LEN = 20.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final float RECAST_REGION_EDGE_MAX_ERROR = 1.0f;
 | 
					    static final float RECAST_REGION_EDGE_MAX_ERROR = 3.3f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final int RECAST_VERTS_PER_POLY = 3;
 | 
					    static final int RECAST_VERTS_PER_POLY = 6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final float RECAST_DETAIL_SAMPLE_DIST = 0.1f;
 | 
					    static final float RECAST_DETAIL_SAMPLE_DIST = 1.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final float RECAST_DETAIL_SAMPLE_MAX_ERROR = 0.1f;
 | 
					    static final float RECAST_DETAIL_SAMPLE_MAX_ERROR = 1.0f;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Constructs a navmesh
 | 
					     * Constructs a navmesh
 | 
				
			||||||
     * @param terrainChunk The terrain chunk
 | 
					     * @param terrainChunk The terrain chunk
 | 
				
			||||||
     * @return the MeshData
 | 
					     * @return the MeshData
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static MeshData constructNavmesh(TerrainChunkData terrainChunkData){
 | 
					    public static MeshData constructNavmesh(TriGeomData geomData){
 | 
				
			||||||
        MeshData rVal = null;
 | 
					        MeshData rVal = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            RecastConfig recastConfig = new RecastConfig(
 | 
					
 | 
				
			||||||
                PartitionType.WATERSHED,
 | 
					            //build polymesh
 | 
				
			||||||
                RECAST_CELL_SIZE,
 | 
					            RecastBuilderResult recastBuilderResult = NavMeshConstructor.buildPolymesh(geomData);
 | 
				
			||||||
                RECAST_CELL_HEIGHT,
 | 
					 | 
				
			||||||
                RECAST_AGENT_HEIGHT,
 | 
					 | 
				
			||||||
                RECAST_AGENT_SIZE,
 | 
					 | 
				
			||||||
                RECAST_AGENT_MAX_CLIMB,
 | 
					 | 
				
			||||||
                RECAST_AGENT_MAX_SLOPE,
 | 
					 | 
				
			||||||
                RECAST_MIN_REGION_SIZE,
 | 
					 | 
				
			||||||
                RECAST_REGION_MERGE_SIZE,
 | 
					 | 
				
			||||||
                RECAST_REGION_EDGE_MAX_LEN,
 | 
					 | 
				
			||||||
                RECAST_REGION_EDGE_MAX_ERROR,
 | 
					 | 
				
			||||||
                RECAST_VERTS_PER_POLY,
 | 
					 | 
				
			||||||
                RECAST_DETAIL_SAMPLE_DIST,
 | 
					 | 
				
			||||||
                RECAST_DETAIL_SAMPLE_MAX_ERROR,
 | 
					 | 
				
			||||||
                new AreaModification(0)
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            SingleTrimeshInputGeomProvider geomProvider = new SingleTrimeshInputGeomProvider(terrainChunkData.getVertices(), terrainChunkData.getFaceElements());
 | 
					 | 
				
			||||||
            RecastBuilderConfig recastBuilderConfig = new RecastBuilderConfig(recastConfig, geomProvider.getMeshBoundsMin(), geomProvider.getMeshBoundsMax());
 | 
					 | 
				
			||||||
            RecastBuilder recastBuilder = new RecastBuilder();
 | 
					 | 
				
			||||||
            RecastBuilderResult recastBuilderResult = recastBuilder.build(geomProvider, recastBuilderConfig);
 | 
					 | 
				
			||||||
            PolyMesh polyMesh = recastBuilderResult.getMesh();
 | 
					            PolyMesh polyMesh = recastBuilderResult.getMesh();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Vector3d polyMin = new Vector3d(polyMesh.bmin[0],polyMesh.bmin[1],polyMesh.bmin[2]);
 | 
				
			||||||
 | 
					            Vector3d polyMax = new Vector3d(polyMesh.bmax[0],polyMesh.bmax[1],polyMesh.bmax[2]);
 | 
				
			||||||
 | 
					            if(polyMin.x < 0 || polyMin.y < 0 || polyMin.z < 0){
 | 
				
			||||||
 | 
					                String message = "Min bound is less than 0\n" +
 | 
				
			||||||
 | 
					                NavMeshConstructor.polyMeshToString(polyMesh);
 | 
				
			||||||
 | 
					                throw new Error(message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(polyMin.distance(polyMax) < AABB_MIN_SIZE){
 | 
				
			||||||
 | 
					                String message = "Bounding box is too small for polymesh\n" +
 | 
				
			||||||
 | 
					                NavMeshConstructor.polyMeshToString(polyMesh);
 | 
				
			||||||
 | 
					                throw new Error(message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //error check the built result
 | 
				
			||||||
 | 
					            if(polyMesh.nverts < 1){
 | 
				
			||||||
 | 
					                String message = "Failed to generate verts in poly mesh\n" +
 | 
				
			||||||
 | 
					                NavMeshConstructor.polyMeshToString(polyMesh);
 | 
				
			||||||
 | 
					                throw new Error(message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(polyMesh.npolys < 1){
 | 
				
			||||||
 | 
					                String message = "Failed to generate polys in poly mesh\n" +
 | 
				
			||||||
 | 
					                NavMeshConstructor.polyMeshToString(polyMesh);
 | 
				
			||||||
 | 
					                throw new Error(message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //set flags
 | 
				
			||||||
            for(int i = 0; i < polyMesh.npolys; i++){
 | 
					            for(int i = 0; i < polyMesh.npolys; i++){
 | 
				
			||||||
                polyMesh.flags[i] = 1;
 | 
					                polyMesh.flags[i] = 1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
            //set params
 | 
					            //set params
 | 
				
			||||||
            NavMeshDataCreateParams params = new NavMeshDataCreateParams();
 | 
					            NavMeshDataCreateParams params = new NavMeshDataCreateParams();
 | 
				
			||||||
            params.verts = polyMesh.verts;
 | 
					            params.verts = polyMesh.verts;
 | 
				
			||||||
@ -163,4 +183,120 @@ public class NavMeshConstructor {
 | 
				
			|||||||
        return rVal;
 | 
					        return rVal;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Builds a builder result from a geom data
 | 
				
			||||||
 | 
					     * @param geomData The geom data
 | 
				
			||||||
 | 
					     * @return The builder result
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected static RecastBuilderResult buildPolymesh(TriGeomData geomData){
 | 
				
			||||||
 | 
					        //create the geometry provider and error check
 | 
				
			||||||
 | 
					        SingleTrimeshInputGeomProvider geomProvider = NavMeshConstructor.getSingleTrimeshInputGeomProvider(geomData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //build configs
 | 
				
			||||||
 | 
					        RecastConfig recastConfig = new RecastConfig(
 | 
				
			||||||
 | 
					            PartitionType.WATERSHED,
 | 
				
			||||||
 | 
					            RECAST_CELL_SIZE,
 | 
				
			||||||
 | 
					            RECAST_CELL_HEIGHT,
 | 
				
			||||||
 | 
					            RECAST_AGENT_HEIGHT,
 | 
				
			||||||
 | 
					            RECAST_AGENT_SIZE,
 | 
				
			||||||
 | 
					            RECAST_AGENT_MAX_CLIMB,
 | 
				
			||||||
 | 
					            RECAST_AGENT_MAX_SLOPE,
 | 
				
			||||||
 | 
					            RECAST_MIN_REGION_SIZE,
 | 
				
			||||||
 | 
					            RECAST_REGION_MERGE_SIZE,
 | 
				
			||||||
 | 
					            RECAST_REGION_EDGE_MAX_LEN,
 | 
				
			||||||
 | 
					            RECAST_REGION_EDGE_MAX_ERROR,
 | 
				
			||||||
 | 
					            RECAST_VERTS_PER_POLY,
 | 
				
			||||||
 | 
					            RECAST_DETAIL_SAMPLE_DIST,
 | 
				
			||||||
 | 
					            RECAST_DETAIL_SAMPLE_MAX_ERROR,
 | 
				
			||||||
 | 
					            new AreaModification(1)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        float[] boundMax = new float[]{
 | 
				
			||||||
 | 
					            geomProvider.getMeshBoundsMax()[0],
 | 
				
			||||||
 | 
					            geomProvider.getMeshBoundsMax()[1] + RECAST_AGENT_HEIGHT,
 | 
				
			||||||
 | 
					            geomProvider.getMeshBoundsMax()[2]
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        RecastBuilderConfig recastBuilderConfig = new RecastBuilderConfig(recastConfig, geomProvider.getMeshBoundsMin(), boundMax);
 | 
				
			||||||
 | 
					        RecastBuilder recastBuilder = new RecastBuilder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //actually build polymesh
 | 
				
			||||||
 | 
					        RecastBuilderResult recastBuilderResult = recastBuilder.build(geomProvider, recastBuilderConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return recastBuilderResult;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Creates the geom provider from a given trimesh
 | 
				
			||||||
 | 
					     * @return The geom provider
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected static SingleTrimeshInputGeomProvider getSingleTrimeshInputGeomProvider(TriGeomData geomData){
 | 
				
			||||||
 | 
					        //error check input
 | 
				
			||||||
 | 
					        if(!GeomUtils.isWindingClockwise(geomData.getVertices(), geomData.getFaceElements())){
 | 
				
			||||||
 | 
					            throw new Error("Geometry is not wound clockwise!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        //create the geometry provider and error check
 | 
				
			||||||
 | 
					        SingleTrimeshInputGeomProvider geomProvider = new SingleTrimeshInputGeomProvider(geomData.getVertices(), geomData.getFaceElements());
 | 
				
			||||||
 | 
					        //check the bounding box
 | 
				
			||||||
 | 
					        Vector3d aabbStart = new Vector3d(geomProvider.getMeshBoundsMin()[0],geomProvider.getMeshBoundsMin()[1],geomProvider.getMeshBoundsMin()[2]);
 | 
				
			||||||
 | 
					        Vector3d aabbEnd = new Vector3d(geomProvider.getMeshBoundsMax()[0],geomProvider.getMeshBoundsMax()[1],geomProvider.getMeshBoundsMax()[2]);
 | 
				
			||||||
 | 
					        if(aabbStart.distance(aabbEnd) < AABB_MIN_SIZE){
 | 
				
			||||||
 | 
					            throw new Error("Geometry provider's AABB is too small " + aabbStart.distance(aabbEnd));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return geomProvider;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Counts the spans of a building result
 | 
				
			||||||
 | 
					     * @param recastBuilderResult The result
 | 
				
			||||||
 | 
					     * @return The number of spans
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected static int countSpans(RecastBuilderResult recastBuilderResult){
 | 
				
			||||||
 | 
					        Heightfield heightfield = recastBuilderResult.getSolidHeightfield();
 | 
				
			||||||
 | 
					        int count = 0;
 | 
				
			||||||
 | 
					        int w = heightfield.width;
 | 
				
			||||||
 | 
					        int h = heightfield.height;
 | 
				
			||||||
 | 
					        for(int y = 0; y < h; ++y) {
 | 
				
			||||||
 | 
					            for(int x = 0; x < w; ++x) {
 | 
				
			||||||
 | 
					                for(Span s = heightfield.spans[x + y * w]; s != null; s = s.next) {
 | 
				
			||||||
 | 
					                    if(s.area != RecastConstants.RC_NULL_AREA){
 | 
				
			||||||
 | 
					                        count++;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return count;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Counts the walkable spans in the compact heightfield
 | 
				
			||||||
 | 
					     * @param recastBuilderResult The build result
 | 
				
			||||||
 | 
					     * @return The number of walkable spans
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected static int countWalkableSpans(RecastBuilderResult recastBuilderResult){
 | 
				
			||||||
 | 
					        CompactHeightfield chf = recastBuilderResult.getCompactHeightfield();
 | 
				
			||||||
 | 
					        int count = 0;
 | 
				
			||||||
 | 
					        for (int i = 0; i < chf.spanCount; i++){
 | 
				
			||||||
 | 
					            if(chf.spans[i] != null){
 | 
				
			||||||
 | 
					                count++;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return count;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Converts a polymesh to a string
 | 
				
			||||||
 | 
					     * @param polyMesh The polymesh
 | 
				
			||||||
 | 
					     * @return The string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected static String polyMeshToString(PolyMesh polyMesh){
 | 
				
			||||||
 | 
					        return "" +
 | 
				
			||||||
 | 
					        "nverts: " + polyMesh.nverts + "\n" +
 | 
				
			||||||
 | 
					        "verts.length: " + polyMesh.verts.length + "\n" +
 | 
				
			||||||
 | 
					        "npolys: " + polyMesh.npolys + "\n" +
 | 
				
			||||||
 | 
					        "polys.length: " + polyMesh.polys.length + "\n" +
 | 
				
			||||||
 | 
					        "bmin: " + polyMesh.bmin[0] + "," + polyMesh.bmin[1] + "," + polyMesh.bmin[2] + "\n" +
 | 
				
			||||||
 | 
					        "bmin: " + polyMesh.bmax[0] + "," + polyMesh.bmax[1] + "," + polyMesh.bmax[2] + "\n" +
 | 
				
			||||||
 | 
					        "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,8 +2,10 @@ package electrosphere.server.pathfinding;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.LinkedList;
 | 
					import java.util.LinkedList;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.joml.Vector3d;
 | 
					import org.joml.Vector3d;
 | 
				
			||||||
 | 
					import org.joml.Vector3f;
 | 
				
			||||||
import org.recast4j.detour.DefaultQueryFilter;
 | 
					import org.recast4j.detour.DefaultQueryFilter;
 | 
				
			||||||
import org.recast4j.detour.FindNearestPolyResult;
 | 
					import org.recast4j.detour.FindNearestPolyResult;
 | 
				
			||||||
import org.recast4j.detour.MeshData;
 | 
					import org.recast4j.detour.MeshData;
 | 
				
			||||||
@ -11,8 +13,11 @@ import org.recast4j.detour.NavMesh;
 | 
				
			|||||||
import org.recast4j.detour.NavMeshQuery;
 | 
					import org.recast4j.detour.NavMeshQuery;
 | 
				
			||||||
import org.recast4j.detour.QueryFilter;
 | 
					import org.recast4j.detour.QueryFilter;
 | 
				
			||||||
import org.recast4j.detour.Result;
 | 
					import org.recast4j.detour.Result;
 | 
				
			||||||
 | 
					import org.recast4j.detour.Status;
 | 
				
			||||||
import org.recast4j.detour.StraightPathItem;
 | 
					import org.recast4j.detour.StraightPathItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import electrosphere.server.physics.terrain.manager.ServerTerrainChunk;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Performs pathfinding
 | 
					 * Performs pathfinding
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@ -33,6 +38,10 @@ public class Pathfinder {
 | 
				
			|||||||
    public List<Vector3d> solve(MeshData mesh, Vector3d startPos, Vector3d endPos){
 | 
					    public List<Vector3d> solve(MeshData mesh, Vector3d startPos, Vector3d endPos){
 | 
				
			||||||
        List<Vector3d> rVal = new LinkedList<Vector3d>();
 | 
					        List<Vector3d> rVal = new LinkedList<Vector3d>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(mesh == null){
 | 
				
			||||||
 | 
					            throw new Error("Mesh data is null!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //construct objects
 | 
					        //construct objects
 | 
				
			||||||
        NavMesh navMesh = new NavMesh(mesh,6,0);
 | 
					        NavMesh navMesh = new NavMesh(mesh,6,0);
 | 
				
			||||||
        NavMeshQuery query = new NavMeshQuery(navMesh);
 | 
					        NavMeshQuery query = new NavMeshQuery(navMesh);
 | 
				
			||||||
@ -41,11 +50,11 @@ public class Pathfinder {
 | 
				
			|||||||
        //convert points to correct datatypes
 | 
					        //convert points to correct datatypes
 | 
				
			||||||
        float[] startArr = new float[]{(float)startPos.x, (float)startPos.y, (float)startPos.z};
 | 
					        float[] startArr = new float[]{(float)startPos.x, (float)startPos.y, (float)startPos.z};
 | 
				
			||||||
        float[] endArr = new float[]{(float)endPos.x, (float)endPos.y, (float)endPos.z};
 | 
					        float[] endArr = new float[]{(float)endPos.x, (float)endPos.y, (float)endPos.z};
 | 
				
			||||||
        float[] polySearchBounds = new float[]{10,10,10};
 | 
					        float[] polySearchBounds = new float[]{ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET,ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET,ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //find start poly
 | 
					        //find start poly
 | 
				
			||||||
        Result<FindNearestPolyResult> startPolyResult = query.findNearestPoly(startArr, polySearchBounds, filter);
 | 
					        Result<FindNearestPolyResult> startPolyResult = query.findNearestPoly(startArr, polySearchBounds, filter);
 | 
				
			||||||
        if(startPolyResult.failed()){
 | 
					        if(!startPolyResult.succeeded()){
 | 
				
			||||||
            String message = "Failed to solve for start polygon!\n" +
 | 
					            String message = "Failed to solve for start polygon!\n" +
 | 
				
			||||||
            startPolyResult.message
 | 
					            startPolyResult.message
 | 
				
			||||||
            ;
 | 
					            ;
 | 
				
			||||||
@ -55,7 +64,7 @@ public class Pathfinder {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        //find end poly
 | 
					        //find end poly
 | 
				
			||||||
        Result<FindNearestPolyResult> endPolyResult = query.findNearestPoly(endArr, polySearchBounds, filter);
 | 
					        Result<FindNearestPolyResult> endPolyResult = query.findNearestPoly(endArr, polySearchBounds, filter);
 | 
				
			||||||
        if(endPolyResult.failed()){
 | 
					        if(!endPolyResult.succeeded()){
 | 
				
			||||||
            String message = "Failed to solve for end polygon!\n" +
 | 
					            String message = "Failed to solve for end polygon!\n" +
 | 
				
			||||||
            endPolyResult.message
 | 
					            endPolyResult.message
 | 
				
			||||||
            ;
 | 
					            ;
 | 
				
			||||||
@ -63,12 +72,29 @@ public class Pathfinder {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        long endRef = endPolyResult.result.getNearestRef();
 | 
					        long endRef = endPolyResult.result.getNearestRef();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(startRef == 0){
 | 
				
			||||||
 | 
					            throw new Error("Start ref is 0!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(endRef == 0){
 | 
				
			||||||
 | 
					            throw new Error("End ref is 0!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //solve path
 | 
					        //solve path
 | 
				
			||||||
        Result<List<Long>> pathResult = query.findPath(startRef, endRef, startArr, endArr, filter);
 | 
					        Result<List<Long>> pathResult = query.findPath(startRef, endRef, startArr, endArr, filter);
 | 
				
			||||||
        if(pathResult.failed()){
 | 
					        if(pathResult.failed()){
 | 
				
			||||||
            String message = "Failed to solve for path!\n" +
 | 
					            String message = "Failed to solve for path!\n" +
 | 
				
			||||||
            pathResult.message
 | 
					            pathResult.message + "\n" +
 | 
				
			||||||
 | 
					            pathResult.status + "\n" +
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
            ;
 | 
					            ;
 | 
				
			||||||
 | 
					            if(pathResult.status == Status.FAILURE_INVALID_PARAM){
 | 
				
			||||||
 | 
					                message = "Failed to solve for path -- invalid param!\n" +
 | 
				
			||||||
 | 
					                "Message: " + pathResult.message + "\n" +
 | 
				
			||||||
 | 
					                "Status: " + pathResult.status + "\n" +
 | 
				
			||||||
 | 
					                Pathfinder.checkInvalidParam(navMesh,startRef,endRef,startArr,endArr) + "\n" +
 | 
				
			||||||
 | 
					                ""
 | 
				
			||||||
 | 
					                ;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            throw new Error(message);
 | 
					            throw new Error(message);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -89,4 +115,28 @@ public class Pathfinder {
 | 
				
			|||||||
        return rVal;
 | 
					        return rVal;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Checks params to a path query
 | 
				
			||||||
 | 
					     * @param mesh The mesh
 | 
				
			||||||
 | 
					     * @param startRef The start ref
 | 
				
			||||||
 | 
					     * @param endRef The end ref
 | 
				
			||||||
 | 
					     * @param startPos The start pos
 | 
				
			||||||
 | 
					     * @param endPos THe end pos
 | 
				
			||||||
 | 
					     * @return The string containing the data
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static String checkInvalidParam(NavMesh mesh, long startRef, long endRef, float[] startPos, float[] endPos){
 | 
				
			||||||
 | 
					        //none of these should be true
 | 
				
			||||||
 | 
					        return "" +
 | 
				
			||||||
 | 
					        "startRef: " + startRef + "\n" +
 | 
				
			||||||
 | 
					        "endRef:   " + endRef + "\n" +
 | 
				
			||||||
 | 
					        "StartRef poly area succeeded: " + mesh.getPolyArea(startRef).succeeded() + "\n" +
 | 
				
			||||||
 | 
					        "EndRef poly area succeeded:   " + mesh.getPolyArea(endRef).succeeded() + "\n" +
 | 
				
			||||||
 | 
					        "StartPos is null: " + Objects.isNull(startPos) + "\n" +
 | 
				
			||||||
 | 
					        "StartPos is finite: " + !new Vector3f(startPos[0],startPos[1],startPos[2]).isFinite() + "\n" +
 | 
				
			||||||
 | 
					        "EndPos is null:   " + Objects.isNull(endPos) + "\n" +
 | 
				
			||||||
 | 
					        "EndPos is finite:   " + !new Vector3f(endPos[0],endPos[1],endPos[2]).isFinite() + "\n" +
 | 
				
			||||||
 | 
					        ""
 | 
				
			||||||
 | 
					        ;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					package electrosphere.server.pathfinding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.joml.Vector3d;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Data tracking moving along a solved path
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class PathingProgressiveData {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The list of points that represent the path
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    List<Vector3d> points;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The current point to move towards (ie all previous points have already been pathed to)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    int currentPoint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Constructor
 | 
				
			||||||
 | 
					     * @param points The points for the path
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public PathingProgressiveData(List<Vector3d> points){
 | 
				
			||||||
 | 
					        this.points = points;
 | 
				
			||||||
 | 
					        this.currentPoint = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Gets the points that define the path
 | 
				
			||||||
 | 
					     * @return The points
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public List<Vector3d> getPoints() {
 | 
				
			||||||
 | 
					        return points;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sets the points that define the path
 | 
				
			||||||
 | 
					     * @param points The points
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public void setPoints(List<Vector3d> points) {
 | 
				
			||||||
 | 
					        this.points = points;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Gets the current point to move towards
 | 
				
			||||||
 | 
					     * @return The current point's index
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public int getCurrentPoint() {
 | 
				
			||||||
 | 
					        return currentPoint;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sets the current point to move towards
 | 
				
			||||||
 | 
					     * @param currentPoint The current point's index
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public void setCurrentPoint(int currentPoint) {
 | 
				
			||||||
 | 
					        this.currentPoint = currentPoint;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -511,4 +511,64 @@ public class GeomUtils {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Checks the winding of a given set of geometry
 | 
				
			||||||
 | 
					     * @param verts The vertices
 | 
				
			||||||
 | 
					     * @param indices The indices
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static void checkWinding(float[] verts, int[] indices) {
 | 
				
			||||||
 | 
					        for (int i = 0; i < indices.length; i += 3) {
 | 
				
			||||||
 | 
					            int ia = indices[i] * 3;
 | 
				
			||||||
 | 
					            int ib = indices[i + 1] * 3;
 | 
				
			||||||
 | 
					            int ic = indices[i + 2] * 3;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            float ax = verts[ia];
 | 
				
			||||||
 | 
					            float az = verts[ia + 2];
 | 
				
			||||||
 | 
					            float bx = verts[ib];
 | 
				
			||||||
 | 
					            float bz = verts[ib + 2];
 | 
				
			||||||
 | 
					            float cx = verts[ic];
 | 
				
			||||||
 | 
					            float cz = verts[ic + 2];
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            // Compute signed area of the triangle in XZ plane
 | 
				
			||||||
 | 
					            float signedArea = (bx - ax) * (cz - az) - (cx - ax) * (bz - az);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            if (signedArea < 0) {
 | 
				
			||||||
 | 
					                System.out.println("Triangle " + (i / 3) + " is wound CLOCKWISE (probably incorrect)");
 | 
				
			||||||
 | 
					            } else if (signedArea > 0) {
 | 
				
			||||||
 | 
					                System.out.println("Triangle " + (i / 3) + " is wound COUNTER-CLOCKWISE (likely correct)");
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                System.out.println("Triangle " + (i / 3) + " is degenerate (zero area)");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Checks that the winding of the geometry it clockwise
 | 
				
			||||||
 | 
					     * @param verts The verts
 | 
				
			||||||
 | 
					     * @param indices The indices
 | 
				
			||||||
 | 
					     * @return true if all triangles are wound clockwise, false otherwise
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static boolean isWindingClockwise(float[] verts, int[] indices){
 | 
				
			||||||
 | 
					        for (int i = 0; i < indices.length; i += 3) {
 | 
				
			||||||
 | 
					            int ia = indices[i] * 3;
 | 
				
			||||||
 | 
					            int ib = indices[i + 1] * 3;
 | 
				
			||||||
 | 
					            int ic = indices[i + 2] * 3;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            float ax = verts[ia];
 | 
				
			||||||
 | 
					            float az = verts[ia + 2];
 | 
				
			||||||
 | 
					            float bx = verts[ib];
 | 
				
			||||||
 | 
					            float bz = verts[ib + 2];
 | 
				
			||||||
 | 
					            float cx = verts[ic];
 | 
				
			||||||
 | 
					            float cz = verts[ic + 2];
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            // Compute signed area of the triangle in XZ plane
 | 
				
			||||||
 | 
					            float signedArea = (bx - ax) * (cz - az) - (cx - ax) * (bz - az);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            if (signedArea > 0) {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,182 @@
 | 
				
			|||||||
 | 
					package electrosphere.server.pathfinding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertEquals;
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertNotEquals;
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertNotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.joml.Vector3d;
 | 
				
			||||||
 | 
					import org.recast4j.detour.MeshData;
 | 
				
			||||||
 | 
					import org.recast4j.recast.ContourSet;
 | 
				
			||||||
 | 
					import org.recast4j.recast.RecastBuilder.RecastBuilderResult;
 | 
				
			||||||
 | 
					import org.recast4j.recast.geom.SingleTrimeshInputGeomProvider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import electrosphere.entity.state.collidable.TriGeomData;
 | 
				
			||||||
 | 
					import electrosphere.test.annotations.UnitTest;
 | 
				
			||||||
 | 
					import electrosphere.util.math.GeomUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for navmesh constructor
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class NavMeshConstructorTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Expected size of the geom 1 aabb
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    static final int GEOM_1_EXPECTED_AABB = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Number of spans expected for geom 1
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    static final int GEOM_1_SPAN_COUNT = 496;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Test constructing a simple navmesh
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @UnitTest
 | 
				
			||||||
 | 
					    public void test_constructNavmesh_geom1(){
 | 
				
			||||||
 | 
					        TriGeomData geom = new TriGeomData() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public float[] getVertices() {
 | 
				
			||||||
 | 
					                return new float[]{
 | 
				
			||||||
 | 
					                    0f, 0f, 0f,
 | 
				
			||||||
 | 
					                    10f, 0f, 0f,
 | 
				
			||||||
 | 
					                    0f, 0f, 10f
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public int[] getFaceElements() {
 | 
				
			||||||
 | 
					                return new int[]{
 | 
				
			||||||
 | 
					                    2, 1, 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MeshData meshData = NavMeshConstructor.constructNavmesh(geom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertNotNull(meshData);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Test constructing a simple navmesh
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @UnitTest
 | 
				
			||||||
 | 
					    public void test_countSpans_geom1(){
 | 
				
			||||||
 | 
					        TriGeomData geom = new TriGeomData() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public float[] getVertices() {
 | 
				
			||||||
 | 
					                return new float[]{
 | 
				
			||||||
 | 
					                    0f, 0f, 0f,
 | 
				
			||||||
 | 
					                    10f, 0f, 0f,
 | 
				
			||||||
 | 
					                    0f, 0f, 10f
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public int[] getFaceElements() {
 | 
				
			||||||
 | 
					                return new int[]{
 | 
				
			||||||
 | 
					                    2, 1, 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //actually build polymesh
 | 
				
			||||||
 | 
					        RecastBuilderResult recastBuilderResult = NavMeshConstructor.buildPolymesh(geom);
 | 
				
			||||||
 | 
					        int spanCount = NavMeshConstructor.countSpans(recastBuilderResult);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertEquals(GEOM_1_SPAN_COUNT, spanCount);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Test constructing a simple navmesh
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @UnitTest
 | 
				
			||||||
 | 
					    public void test_countWalkableSpans_geom1(){
 | 
				
			||||||
 | 
					        TriGeomData geom = new TriGeomData() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public float[] getVertices() {
 | 
				
			||||||
 | 
					                return new float[]{
 | 
				
			||||||
 | 
					                    0f, 0f, 0f,
 | 
				
			||||||
 | 
					                    10f, 0f, 0f,
 | 
				
			||||||
 | 
					                    0f, 0f, 10f
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public int[] getFaceElements() {
 | 
				
			||||||
 | 
					                return new int[]{
 | 
				
			||||||
 | 
					                    2, 1, 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //actually build polymesh
 | 
				
			||||||
 | 
					        RecastBuilderResult recastBuilderResult = NavMeshConstructor.buildPolymesh(geom);
 | 
				
			||||||
 | 
					        int spanCount = NavMeshConstructor.countWalkableSpans(recastBuilderResult);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertEquals(GEOM_1_SPAN_COUNT, spanCount);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Test constructing a simple navmesh
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @UnitTest
 | 
				
			||||||
 | 
					    public void test_ContourCount_geom1(){
 | 
				
			||||||
 | 
					        TriGeomData geom = new TriGeomData() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public float[] getVertices() {
 | 
				
			||||||
 | 
					                return new float[]{
 | 
				
			||||||
 | 
					                    0f, 0f, 0f,
 | 
				
			||||||
 | 
					                    10f, 0f, 0f,
 | 
				
			||||||
 | 
					                    0f, 0f, 10f
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public int[] getFaceElements() {
 | 
				
			||||||
 | 
					                return new int[]{
 | 
				
			||||||
 | 
					                    2, 1, 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //actually build polymesh
 | 
				
			||||||
 | 
					        RecastBuilderResult recastBuilderResult = NavMeshConstructor.buildPolymesh(geom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GeomUtils.checkWinding(geom.getVertices(), geom.getFaceElements());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ContourSet contourSet = recastBuilderResult.getContourSet();
 | 
				
			||||||
 | 
					        assertNotEquals(0,contourSet.width);
 | 
				
			||||||
 | 
					        assertNotEquals(0,contourSet.height);
 | 
				
			||||||
 | 
					        assertNotEquals(0,contourSet.conts.size());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Test constructing a simple navmesh
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @UnitTest
 | 
				
			||||||
 | 
					    public void test_getSingleTrimeshInputGeomProvider_geom1(){
 | 
				
			||||||
 | 
					        TriGeomData geom = new TriGeomData() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public float[] getVertices() {
 | 
				
			||||||
 | 
					                return new float[]{
 | 
				
			||||||
 | 
					                    0f, 0f, 0f,
 | 
				
			||||||
 | 
					                    10f, 0f, 0f,
 | 
				
			||||||
 | 
					                    0f, 0f, 10f
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public int[] getFaceElements() {
 | 
				
			||||||
 | 
					                return new int[]{
 | 
				
			||||||
 | 
					                    2, 1, 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SingleTrimeshInputGeomProvider geomProvider = NavMeshConstructor.getSingleTrimeshInputGeomProvider(geom);
 | 
				
			||||||
 | 
					        assertNotNull(geomProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Vector3d aabbStart = new Vector3d(geomProvider.getMeshBoundsMin()[0],geomProvider.getMeshBoundsMin()[1],geomProvider.getMeshBoundsMin()[2]);
 | 
				
			||||||
 | 
					        Vector3d aabbEnd = new Vector3d(geomProvider.getMeshBoundsMax()[0],geomProvider.getMeshBoundsMax()[1],geomProvider.getMeshBoundsMax()[2]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        boolean greaterThan = aabbStart.distance(aabbEnd) > GEOM_1_EXPECTED_AABB;
 | 
				
			||||||
 | 
					        assertEquals(true, greaterThan);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user