├── .gitignore ├── LICENSE ├── pom.xml └── src └── main └── java └── de └── domisum └── lib └── compitum ├── CompitumLib.java ├── UniversalPathfinder.java ├── block ├── BlockAStar.java ├── BlockPathSmoother.java └── evaluator │ ├── MaterialEvaluator.java │ └── StairEvaluator.java ├── navmesh ├── NavMesh.java ├── NavMeshManager.java ├── geometry │ ├── NavMeshPoint.java │ └── NavMeshTriangle.java ├── json │ ├── SerializationNavMesh.java │ ├── SerializationNavMeshLadder.java │ └── SerializationNavMeshTriangle.java ├── pathfinding │ ├── NavMeshPathfinder.java │ ├── NavMeshTriangleNode.java │ ├── NavMeshTrianglePathfinder.java │ └── traversal │ │ └── NavMeshTriangleTraverser.java └── transition │ ├── NavMeshLadder.java │ ├── NavMeshTrianglePortal.java │ └── NavMeshTriangleTransition.java └── path ├── BlockPath.java ├── Path.java ├── PathWaypoint.java └── node ├── BlockPathNode.java ├── TransitionType.java └── weighted ├── SortedWeightedNodeList.java └── WeightedNode.java /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | *.iml 4 | 5 | .settings/ 6 | target/ 7 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dominik Weissenseel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | de.domisum 6 | CompitumLib 7 | 2.0.0-SNAPSHOT 8 | 9 | 10 | UTF-8 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | domisum-releases-public 18 | http://vps.domisum.de:8081/nexus/content/repositories/releases-public/ 19 | 20 | 21 | domisum-snapshots-public 22 | http://vps.domisum.de:8081/nexus/content/repositories/snapshots-public/ 23 | 24 | 25 | 26 | 27 | 28 | de.domisum 29 | AuxiliumLib 30 | 1.0.0-SNAPSHOT 31 | 32 | 33 | 34 | de.domisum 35 | AuxiliumSpigotLib 36 | 1.0.0-SNAPSHOT 37 | 38 | 39 | 40 | 41 | org.spigotmc 42 | spigot-api 43 | 1.9.2-R0.1-SNAPSHOT 44 | provided 45 | 46 | 47 | 48 | org.bukkit 49 | craftbukkit 50 | 1.9.2-R0.1-SNAPSHOT 51 | provided 52 | 53 | 54 | 55 | 56 | com.darkblade12.particleeffect 57 | ParticleLib 58 | 1.0.0 59 | 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | 1.16.10 65 | provided 66 | 67 | 68 | 69 | 70 | 71 | 72 | maven-compiler-plugin 73 | 3.5.1 74 | 75 | 1.8 76 | 1.8 77 | UTF-8 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | vps 86 | vps.domisum.de-releases 87 | http://vps.domisum.de:8081/nexus/content/repositories/releases-public/ 88 | 89 | 90 | 91 | vps 92 | vps.domisum.de-snapshots 93 | http://vps.domisum.de:8081/nexus/content/repositories/snapshots-public/ 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/CompitumLib.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum; 2 | 3 | import de.domisum.lib.auxilium.util.java.annotations.API; 4 | import de.domisum.lib.auxiliumspigot.AuxiliumSpigotLib; 5 | import de.domisum.lib.compitum.block.evaluator.MaterialEvaluator; 6 | import de.domisum.lib.compitum.block.evaluator.StairEvaluator; 7 | import de.domisum.lib.compitum.navmesh.NavMeshManager; 8 | import org.bukkit.plugin.Plugin; 9 | import org.bukkit.plugin.java.JavaPlugin; 10 | 11 | import java.util.logging.Logger; 12 | 13 | @API 14 | public class CompitumLib 15 | { 16 | 17 | // SETTINGS 18 | private static boolean navMeshesEnabled = false; 19 | 20 | // REFERENCES 21 | private static CompitumLib instance; 22 | private Plugin plugin; 23 | 24 | private NavMeshManager navMeshManager; 25 | 26 | 27 | // INIT 28 | private CompitumLib(JavaPlugin plugin) 29 | { 30 | this.plugin = plugin; 31 | } 32 | 33 | @API public static void enable(JavaPlugin plugin) 34 | { 35 | if(instance != null) 36 | return; 37 | 38 | instance = new CompitumLib(plugin); 39 | instance.onEnable(); 40 | } 41 | 42 | @API public static void disable() 43 | { 44 | if(instance == null) 45 | return; 46 | 47 | getInstance().onDisable(); 48 | instance = null; 49 | } 50 | 51 | private void onEnable() 52 | { 53 | AuxiliumSpigotLib.enable(this.plugin); 54 | 55 | MaterialEvaluator.prepareEvaluation(); 56 | StairEvaluator.prepareEvaluation(); 57 | 58 | if(navMeshesEnabled) 59 | { 60 | this.navMeshManager = new NavMeshManager(); 61 | this.navMeshManager.initiialize(); 62 | } 63 | 64 | getLogger().info(this.getClass().getSimpleName()+" has been enabled"); 65 | } 66 | 67 | private void onDisable() 68 | { 69 | if(this.navMeshManager != null) 70 | this.navMeshManager.terminate(); 71 | 72 | AuxiliumSpigotLib.disable(); 73 | 74 | getLogger().info(this.getClass().getSimpleName()+" has been disabled"); 75 | } 76 | 77 | 78 | // GETTERS 79 | @API public static CompitumLib getInstance() 80 | { 81 | if(instance == null) 82 | throw new IllegalArgumentException(CompitumLib.class.getSimpleName()+" has to be initialized before usage"); 83 | 84 | return instance; 85 | } 86 | 87 | public static Logger getLogger() 88 | { 89 | return getInstance().plugin.getLogger(); 90 | } 91 | 92 | 93 | @API public static NavMeshManager getNavMeshManager() 94 | { 95 | if(!navMeshesEnabled) 96 | throw new IllegalStateException("The usage of NavMeshes has to be enabled first!"); 97 | 98 | return getInstance().navMeshManager; 99 | } 100 | 101 | @API public static boolean areNavMeshesEnabled() 102 | { 103 | return navMeshesEnabled; 104 | } 105 | 106 | 107 | // SETTERS 108 | @API public static void enableNavMeshes() 109 | { 110 | if(instance != null) 111 | throw new IllegalStateException("NavMeshes have to be enabled before enabling CompitumLib"); 112 | 113 | navMeshesEnabled = true; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/UniversalPathfinder.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum; 2 | 3 | 4 | import de.domisum.lib.auxilium.util.java.annotations.API; 5 | import de.domisum.lib.compitum.block.BlockAStar; 6 | import de.domisum.lib.compitum.block.BlockPathSmoother; 7 | import de.domisum.lib.compitum.navmesh.NavMesh; 8 | import de.domisum.lib.compitum.navmesh.NavMeshManager; 9 | import de.domisum.lib.compitum.navmesh.pathfinding.NavMeshPathfinder; 10 | import de.domisum.lib.compitum.path.BlockPath; 11 | import de.domisum.lib.compitum.path.Path; 12 | import org.bukkit.Location; 13 | 14 | import java.util.Objects; 15 | 16 | @API 17 | public class UniversalPathfinder 18 | { 19 | 20 | // INPUT 21 | private Location start; 22 | private Location target; 23 | 24 | // PROPERTIES 25 | 26 | 27 | // OUTPUT 28 | private Path path; 29 | 30 | private String diagnose; 31 | private String failure; 32 | 33 | 34 | // INIT 35 | @API public UniversalPathfinder(Location start, Location target) 36 | { 37 | this.start = fixPathfindingLocation(start); 38 | this.target = fixPathfindingLocation(target); 39 | } 40 | 41 | 42 | // GETTERS 43 | @API public Path getPath() 44 | { 45 | return this.path; 46 | } 47 | 48 | @API public boolean isPathFound() 49 | { 50 | return this.path != null; 51 | } 52 | 53 | 54 | @API public String getDiagnose() 55 | { 56 | return this.diagnose; 57 | } 58 | 59 | @API public String getFailure() 60 | { 61 | return this.failure; 62 | } 63 | 64 | 65 | // PATHFINDING 66 | @API public void findPath() 67 | { 68 | navMeshCheck: 69 | if(CompitumLib.areNavMeshesEnabled()) 70 | { 71 | NavMeshManager nmm = CompitumLib.getNavMeshManager(); 72 | NavMesh meshAtStart = nmm.getNavMeshAt(this.start); 73 | if(meshAtStart == null) 74 | break navMeshCheck; 75 | 76 | NavMesh meshAtTarget = nmm.getNavMeshAt(this.target); 77 | if(meshAtTarget == null) 78 | break navMeshCheck; 79 | 80 | if(!Objects.equals(meshAtStart, meshAtTarget)) 81 | break navMeshCheck; 82 | 83 | useNavMesh(meshAtStart); 84 | return; 85 | } 86 | 87 | useWorldAStar(); 88 | } 89 | 90 | 91 | private void useWorldAStar() 92 | { 93 | BlockAStar pathfinder = new BlockAStar(this.start, this.target); 94 | pathfinder.findPath(); 95 | this.diagnose = pathfinder.getDiagnose(); 96 | if(!pathfinder.pathFound()) 97 | { 98 | this.failure = pathfinder.getFailure(); 99 | return; 100 | } 101 | BlockPath blockPath = pathfinder.getPath(); 102 | 103 | BlockPathSmoother smoother = new BlockPathSmoother(blockPath); 104 | smoother.convert(); 105 | this.path = smoother.getSmoothPath(); 106 | } 107 | 108 | private void useNavMesh(NavMesh navMesh) 109 | { 110 | NavMeshPathfinder pathfinder = new NavMeshPathfinder(this.start, this.target, navMesh); 111 | pathfinder.findPath(); 112 | this.path = pathfinder.getPath(); 113 | 114 | //this.diagnose = pathfinder.getDiagnose(); 115 | if(this.path == null) 116 | { 117 | this.failure = getFailure(); 118 | return; 119 | } 120 | 121 | this.path = pathfinder.getPath(); 122 | } 123 | 124 | 125 | @API public static Location fixPathfindingLocation(Location location) 126 | { 127 | location.setY(Math.floor(location.getY())); 128 | 129 | String materialName = location.getBlock().getType().name(); 130 | if(materialName.contains("SLAB") || materialName.contains("STEP")) 131 | location.add(0, 1, 0); 132 | 133 | return location; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/block/BlockAStar.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.block; 2 | 3 | 4 | import de.domisum.lib.auxilium.util.java.annotations.API; 5 | import de.domisum.lib.auxilium.util.math.MathUtil; 6 | import de.domisum.lib.compitum.block.evaluator.MaterialEvaluator; 7 | import de.domisum.lib.compitum.block.evaluator.StairEvaluator; 8 | import de.domisum.lib.compitum.path.node.TransitionType; 9 | import de.domisum.lib.compitum.path.node.BlockPathNode; 10 | import de.domisum.lib.compitum.path.BlockPath; 11 | import de.domisum.lib.compitum.path.node.weighted.SortedWeightedNodeList; 12 | import org.bukkit.Location; 13 | import org.bukkit.Material; 14 | 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | @API 19 | public class BlockAStar 20 | { 21 | 22 | // CONSTANTS 23 | private static final double CLIMBING_EXPENSE = 2; 24 | 25 | // PROPERTIES 26 | private double heuristicImportance = 1.0; 27 | private int maxNodeVisits = 500; 28 | 29 | private boolean canUseDiagonalMovement = true; 30 | private boolean canUseLadders = false; 31 | 32 | // INPUT 33 | private Location startLocation; 34 | private Location endLocation; 35 | 36 | // STATUS 37 | private boolean moveDiagonally = true; 38 | private boolean useLadders = false; 39 | 40 | protected BlockPathNode endNode; 41 | 42 | private SortedWeightedNodeList unvisitedNodes = new SortedWeightedNodeList<>(this.maxNodeVisits*3); 43 | private Set visitedNodes = new HashSet<>(this.maxNodeVisits); 44 | 45 | private long pathfindingStartNano; 46 | private long pathfindingEndNano; 47 | 48 | // OUTPUT 49 | private BlockPath path; 50 | private String failure; 51 | 52 | 53 | // INIT 54 | @API public BlockAStar(Location startLocation, Location endLocation) 55 | { 56 | this.startLocation = startLocation; 57 | this.endLocation = endLocation; 58 | } 59 | 60 | 61 | // GETTERS 62 | @API public boolean pathFound() 63 | { 64 | return this.path != null; 65 | } 66 | 67 | @API public BlockPath getPath() 68 | { 69 | return this.path; 70 | } 71 | 72 | 73 | @API public String getFailure() 74 | { 75 | return this.failure; 76 | } 77 | 78 | 79 | private long getNanoDuration() 80 | { 81 | return this.pathfindingEndNano-this.pathfindingStartNano; 82 | } 83 | 84 | private double getMsDuration() 85 | { 86 | return MathUtil.round(getNanoDuration()/1000d/1000, 2); 87 | } 88 | 89 | @API public String getDiagnose() 90 | { 91 | String diagnose = ""; 92 | 93 | diagnose += "found="+pathFound()+", "; 94 | /*if(pathFound()) 95 | diagnose += "length="+getPath().getLength()+", ";*/ 96 | 97 | diagnose += "visitedNodes="+this.visitedNodes.size()+", "; 98 | diagnose += "unvisitedNodes="+this.unvisitedNodes.getSize()+", "; 99 | diagnose += "durationMs="+getMsDuration()+", "; 100 | 101 | return diagnose; 102 | } 103 | 104 | 105 | // SETTERS 106 | @API public void setHeuristicImportance(double heuristicImportance) 107 | { 108 | this.heuristicImportance = heuristicImportance; 109 | } 110 | 111 | @API public void setCanUseDiagonalMovement(boolean canUseDiagonalMovement) 112 | { 113 | this.canUseDiagonalMovement = canUseDiagonalMovement; 114 | } 115 | 116 | @API public void setCanUseLadders(boolean canUseLadders) 117 | { 118 | this.canUseLadders = canUseLadders; 119 | } 120 | 121 | 122 | // PATHFINDING 123 | @API public void findPath() 124 | { 125 | // don't set the start time if it already exists, this way duration of retries are counted together 126 | if(this.pathfindingStartNano == 0) 127 | this.pathfindingStartNano = System.nanoTime(); 128 | 129 | // validation 130 | if(this.startLocation.getWorld() != this.endLocation.getWorld()) 131 | throw new IllegalArgumentException("The start and the end location are not in the same world!"); 132 | 133 | // preparation 134 | BlockPathNode startNode = new BlockPathNode(this.startLocation.getBlockX(), 135 | this.startLocation.getBlockY(), this.startLocation.getBlockZ()); 136 | // this is needed in case the start and end nodes are the same, so the transition type is set 137 | startNode.setParent(null, TransitionType.WALK, 0); 138 | 139 | this.endNode = new BlockPathNode(this.endLocation.getBlockX(), this.endLocation.getBlockY(), 140 | this.endLocation.getBlockZ()); 141 | 142 | this.unvisitedNodes.addSorted(startNode); 143 | 144 | // pathfinding 145 | visitNodes(); 146 | 147 | 148 | // pathfinding finalization 149 | // if the end node has a parent, a pathfinding has been found 150 | if(this.endNode.getParent() != null || startNode.equals(this.endNode)) 151 | this.path = new BlockPath(this.endNode); 152 | else 153 | // this looks through the provided options and checks if an ability of the pathfinder is deactivated, 154 | // if so it activates it and reruns the pathfinding. if there are no other options available, it returns 155 | retry(); 156 | 157 | this.pathfindingEndNano = System.nanoTime(); 158 | } 159 | 160 | protected void visitNodes() 161 | { 162 | while(true) 163 | { 164 | if(this.unvisitedNodes.getSize() == 0) 165 | { 166 | // no unvisited nodes left, nowhere else to go ... 167 | this.failure = "No unvisted nodes left"; 168 | break; 169 | } 170 | 171 | if(this.visitedNodes.size() >= this.maxNodeVisits) 172 | { 173 | // reached limit of nodes to search 174 | this.failure = "Number of nodes visited exceeds maximum"; 175 | break; 176 | } 177 | 178 | BlockPathNode nodeToVisit = this.unvisitedNodes.getAndRemoveFirst(); 179 | this.visitedNodes.add(nodeToVisit); 180 | 181 | // pathing reached end node 182 | if(isTargetReached(nodeToVisit)) 183 | { 184 | this.endNode = nodeToVisit; 185 | break; 186 | } 187 | 188 | visitNode(nodeToVisit); 189 | } 190 | } 191 | 192 | protected boolean isTargetReached(BlockPathNode nodeToVisit) 193 | { 194 | return nodeToVisit.equals(this.endNode); 195 | } 196 | 197 | 198 | protected void visitNode(BlockPathNode node) 199 | { 200 | lookForWalkableNodes(node); 201 | 202 | if(this.useLadders) 203 | lookForLadderNodes(node); 204 | } 205 | 206 | protected void lookForWalkableNodes(BlockPathNode node) 207 | { 208 | for(int dX = -1; dX <= 1; dX++) 209 | for(int dZ = -1; dZ <= 1; dZ++) 210 | for(int dY = -1; dY <= 1; dY++) 211 | validateNodeOffset(node, dX, dY, dZ); 212 | } 213 | 214 | protected void validateNodeOffset(BlockPathNode node, int dX, int dY, int dZ) 215 | { 216 | // prevent movement to the same position 217 | if(dX == 0 && dY == 0 && dZ == 0) 218 | return; 219 | 220 | // prevent diagonal movement if specified 221 | if(!this.moveDiagonally && dX*dZ != 0) 222 | return; 223 | 224 | // prevent diagonal movement at the same time as moving up and down 225 | if(dX*dZ != 0 && dY != 0) 226 | return; 227 | 228 | 229 | BlockPathNode newNode = new BlockPathNode(node.x+dX, node.y+dY, node.z+dZ); 230 | 231 | if(doesNodeAlreadyExist(newNode)) 232 | return; 233 | 234 | // check if player can stand at new node 235 | if(!isValid(newNode)) 236 | return; 237 | 238 | // check if the diagonal movement is not prevented by blocks to the side 239 | if(dX*dZ != 0 && !isDiagonalMovementPossible(node, dX, dZ)) 240 | return; 241 | 242 | // check if the player hits his head when going up/down 243 | if(dY == 1 && !isBlockUnobstructed(node.getLocation(this.startLocation.getWorld()).add(0, 2, 0))) 244 | return; 245 | 246 | if(dY == -1 && !isBlockUnobstructed(newNode.getLocation(this.startLocation.getWorld()).add(0, 2, 0))) 247 | return; 248 | 249 | 250 | // get transition type (walk up stairs, jump up blocks) 251 | int transitionType = TransitionType.WALK; 252 | if(dY == 1) 253 | { 254 | boolean isStair = StairEvaluator.isStair(node, newNode, this.startLocation.getWorld()); 255 | if(!isStair) 256 | transitionType = TransitionType.JUMP; 257 | } 258 | 259 | 260 | // calculate weight 261 | // TODO punish 90° turns 262 | int sumAbs = Math.abs(dX)+Math.abs(dY)+Math.abs(dZ); 263 | double weight = 1; 264 | if(sumAbs == 2) 265 | weight = 1.41; 266 | else if(sumAbs == 3) 267 | weight = 1.73; 268 | 269 | // punish jumps to favor stair climbing 270 | if(transitionType == TransitionType.JUMP) 271 | weight += 0.5; 272 | 273 | 274 | // actually add the node to the pool 275 | newNode.setParent(node, transitionType, weight); 276 | addNode(newNode); 277 | } 278 | 279 | protected void lookForLadderNodes(BlockPathNode node) 280 | { 281 | Location feetLocation = node.getLocation(this.startLocation.getWorld()); 282 | 283 | for(int dY = -1; dY <= 1; dY++) 284 | { 285 | Location location = feetLocation.clone().add(0, dY, 0); 286 | if(location.getBlock().getType() != Material.LADDER) 287 | continue; 288 | 289 | BlockPathNode newNode = new BlockPathNode(node.x, node.y+dY, node.z); 290 | newNode.setParent(node, TransitionType.CLIMB, CLIMBING_EXPENSE); 291 | 292 | if(doesNodeAlreadyExist(newNode)) 293 | continue; 294 | 295 | addNode(newNode); 296 | } 297 | } 298 | 299 | 300 | protected boolean doesNodeAlreadyExist(BlockPathNode node) 301 | { 302 | if(this.visitedNodes.contains(node)) 303 | return true; 304 | 305 | return this.unvisitedNodes.contains(node); 306 | 307 | } 308 | 309 | protected void addNode(BlockPathNode node) 310 | { 311 | node.setHeuristicWeight(getHeuristicWeight(node)*this.heuristicImportance); 312 | this.unvisitedNodes.addSorted(node); 313 | } 314 | 315 | 316 | // NODE VALIDATION 317 | protected boolean isValid(BlockPathNode node) 318 | { 319 | return canStandAt(node.getLocation(this.startLocation.getWorld())); 320 | } 321 | 322 | protected boolean isDiagonalMovementPossible(BlockPathNode node, int dX, int dZ) 323 | { 324 | if(!isUnobstructed(node.getLocation(this.startLocation.getWorld()).clone().add(dX, 0, 0))) 325 | return false; 326 | 327 | return isUnobstructed(node.getLocation(this.startLocation.getWorld()).clone().add(0, 0, dZ)); 328 | 329 | } 330 | 331 | @SuppressWarnings("deprecation") protected boolean canStandAt(Location feetLocation) 332 | { 333 | if(!isUnobstructed(feetLocation)) 334 | return false; 335 | 336 | return MaterialEvaluator.canStandOn(feetLocation.clone().add(0, -1, 0).getBlock().getTypeId()); 337 | 338 | } 339 | 340 | protected boolean isUnobstructed(Location feetLocation) 341 | { 342 | if(!isBlockUnobstructed(feetLocation)) 343 | return false; 344 | 345 | return isBlockUnobstructed(feetLocation.clone().add(0, 1, 0)); 346 | 347 | } 348 | 349 | 350 | // LOCATION VALIDATION 351 | @SuppressWarnings("deprecation") protected boolean isBlockUnobstructed(Location location) 352 | { 353 | return MaterialEvaluator.canStandIn(location.getBlock().getTypeId()); 354 | } 355 | 356 | 357 | // HEURISTIC 358 | protected double getHeuristicWeight(BlockPathNode node) 359 | { 360 | return getEuclideanDistance(node); 361 | } 362 | 363 | @SuppressWarnings("unused") protected double getManhattanDistance(BlockPathNode node) 364 | { 365 | return Math.abs(node.x-this.endNode.x)+Math.abs(node.y-this.endNode.y)+Math.abs(node.z-this.endNode.z); 366 | } 367 | 368 | protected double getEuclideanDistance(BlockPathNode node) 369 | { 370 | int dX = this.endNode.x-node.x; 371 | int dY = this.endNode.y-node.y; 372 | int dZ = this.endNode.z-node.z; 373 | 374 | return Math.sqrt(dX*dX+dY*dY+dZ*dZ); 375 | } 376 | 377 | 378 | // RETRY 379 | protected void retry() 380 | { 381 | lookForReason: 382 | { 383 | // the special abilities are sorted by likelyhood of appearance to further speed up the pathfinding 384 | 385 | if(this.canUseDiagonalMovement && !this.moveDiagonally) 386 | { 387 | this.moveDiagonally = true; 388 | break lookForReason; 389 | } 390 | 391 | if(this.canUseLadders && !this.useLadders) 392 | { 393 | this.useLadders = true; 394 | break lookForReason; 395 | } 396 | 397 | return; 398 | } 399 | 400 | reset(); 401 | findPath(); 402 | } 403 | 404 | protected void reset() 405 | { 406 | this.endNode = null; 407 | 408 | this.unvisitedNodes.clear(); 409 | this.visitedNodes.clear(); 410 | 411 | this.path = null; 412 | this.failure = null; 413 | } 414 | 415 | } 416 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/block/BlockPathSmoother.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.block; 2 | 3 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 4 | import de.domisum.lib.compitum.path.node.BlockPathNode; 5 | import de.domisum.lib.compitum.path.BlockPath; 6 | import de.domisum.lib.compitum.path.Path; 7 | import de.domisum.lib.compitum.path.PathWaypoint; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class BlockPathSmoother 13 | { 14 | 15 | // INPUT 16 | private BlockPath blockPath; 17 | 18 | // OUTPUT 19 | private Path smoothPath; 20 | 21 | 22 | // INIT 23 | public BlockPathSmoother(BlockPath blockPath) 24 | { 25 | this.blockPath = blockPath; 26 | } 27 | 28 | 29 | // GETTERS 30 | public Path getSmoothPath() 31 | { 32 | return this.smoothPath; 33 | } 34 | 35 | 36 | // CONVERSION 37 | public void convert() 38 | { 39 | List pathWaypoints = new ArrayList<>(); 40 | for(BlockPathNode blockPathNode : this.blockPath.getNodes()) 41 | pathWaypoints.add(new PathWaypoint(new Vector3D(blockPathNode.x+0.5, blockPathNode.y, blockPathNode.z+0.5), 42 | blockPathNode.getTransitionType())); 43 | 44 | this.smoothPath = new Path(pathWaypoints); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/block/evaluator/MaterialEvaluator.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.block.evaluator; 2 | 3 | import org.bukkit.Material; 4 | 5 | @SuppressWarnings("deprecation") 6 | public class MaterialEvaluator 7 | { 8 | 9 | // REFERENCES 10 | private static boolean[] canStandOn; 11 | private static boolean[] canStandIn; 12 | 13 | // STATUS 14 | private static boolean ready = false; 15 | 16 | 17 | // INIT 18 | public static void prepareEvaluation() 19 | { 20 | int maxId = -1; 21 | for(Material mat : Material.values()) 22 | if(mat.getId() > maxId) 23 | maxId = mat.getId(); 24 | 25 | canStandOn = new boolean[maxId+1]; // value shift 26 | canStandIn = new boolean[maxId+1]; // value shift 27 | 28 | for(Material mat : Material.values()) 29 | { 30 | int id = mat.getId(); 31 | 32 | // general values 33 | canStandOn[id] = mat.isSolid(); 34 | canStandIn[id] = !mat.isSolid(); 35 | 36 | // you cannot stand on fences -> walking over fences not possible 37 | if(mat.name().contains("FENCE")) 38 | canStandOn[id] = false; 39 | 40 | // standing in signs is possible 41 | if(mat == Material.SIGN_POST) 42 | { 43 | canStandIn[id] = true; 44 | canStandOn[id] = false; 45 | } 46 | } 47 | 48 | ready = true; 49 | } 50 | 51 | 52 | // EVALUATION 53 | public static boolean canStandOn(int materialID) 54 | { 55 | if(!ready) 56 | throw new IllegalStateException("CompitumLib has to be anabled before usage!"); 57 | 58 | return canStandOn[materialID]; 59 | } 60 | 61 | public static boolean canStandIn(int materialID) 62 | { 63 | if(!ready) 64 | throw new IllegalStateException("CompitumLib has to be anabled before usage!"); 65 | 66 | return canStandIn[materialID]; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/block/evaluator/StairEvaluator.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.block.evaluator; 2 | 3 | import de.domisum.lib.compitum.path.node.BlockPathNode; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.World; 7 | import org.bukkit.block.Block; 8 | 9 | import java.util.Arrays; 10 | 11 | @SuppressWarnings("deprecation") 12 | public class StairEvaluator 13 | { 14 | 15 | // REFERENCES 16 | /** 17 | * directions: 18 | * 0: +x 19 | * 1: +z 20 | * 2: -x 21 | * 3: -z 22 | */ 23 | private static boolean[][][] isStair; // 24 | 25 | // STATUS 26 | private static boolean ready = false; 27 | 28 | 29 | // INIT 30 | public static void prepareEvaluation() 31 | { 32 | int maxId = -1; 33 | for(Material mat : Material.values()) 34 | if(mat.getId() > maxId) 35 | maxId = mat.getId(); 36 | 37 | int subIds = 16; 38 | isStair = new boolean[maxId+1][subIds][4]; 39 | 40 | // general 41 | boolean[] none = new boolean[4]; 42 | boolean[] all4 = new boolean[4]; 43 | Arrays.fill(all4, true); 44 | 45 | boolean[][] noneForEverySubId = new boolean[subIds][4]; 46 | Arrays.fill(noneForEverySubId, none); 47 | 48 | // slabs 49 | boolean[][] slabDefault = new boolean[subIds][4]; 50 | for(int i = 0; i < 8; i++) 51 | slabDefault[i] = all4; 52 | for(int i = 8; i < subIds; i++) 53 | slabDefault[i] = none; 54 | 55 | // stairs 56 | boolean[] posX = new boolean[4]; 57 | posX[0] = true; 58 | boolean[] posZ = new boolean[4]; 59 | posZ[1] = true; 60 | boolean[] negX = new boolean[4]; 61 | negX[2] = true; 62 | boolean[] negZ = new boolean[4]; 63 | negZ[3] = true; 64 | 65 | boolean[][] stairDefault = new boolean[subIds][4]; 66 | stairDefault[0] = posX; 67 | stairDefault[8] = posX; 68 | stairDefault[2] = posZ; 69 | stairDefault[2+8] = posZ; 70 | stairDefault[1] = negX; 71 | stairDefault[1+8] = negX; 72 | stairDefault[3] = negZ; 73 | stairDefault[3+8] = negZ; 74 | for(int i = 4; i < 8; i++) 75 | { 76 | stairDefault[i] = none; 77 | stairDefault[i+8] = none; 78 | } 79 | 80 | // looping over all materials 81 | for(Material mat : Material.values()) 82 | { 83 | if(mat == Material.STEP || mat == Material.STONE_SLAB2 || mat == Material.WOOD_STEP || mat == Material.PURPUR_SLAB) 84 | isStair[mat.getId()] = slabDefault; 85 | else if(mat.name().endsWith("STAIRS")) 86 | isStair[mat.getId()] = stairDefault; 87 | else 88 | isStair[mat.getId()] = noneForEverySubId; 89 | } 90 | 91 | ready = true; 92 | } 93 | 94 | 95 | // EVALUATION 96 | public static boolean isStair(BlockPathNode from, BlockPathNode to, World world) 97 | { 98 | if(!ready) 99 | throw new IllegalStateException("CompitumLib has to be enabled before usage!"); 100 | 101 | Location stairLocation = to.getLocation(world).add(0, -1, 0); 102 | Block stairBlock = stairLocation.getBlock(); 103 | int stairBlockTypeId = stairBlock.getType().getId(); 104 | int stairBlockSubId = stairBlock.getData(); 105 | 106 | boolean[] directions = isStair[stairBlockTypeId][stairBlockSubId]; 107 | int dX = to.x-from.x; 108 | int dZ = to.z-from.z; 109 | 110 | if(dX == 1 && directions[0]) 111 | return true; 112 | if(dZ == 1 && directions[1]) 113 | return true; 114 | if(dX == -1 && directions[2]) 115 | return true; 116 | if(dZ == -1 && directions[3]) 117 | return true; 118 | 119 | return false; 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/NavMesh.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh; 2 | 3 | import de.domisum.lib.auxilium.data.container.direction.Direction2D; 4 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 5 | import de.domisum.lib.auxilium.util.java.annotations.API; 6 | import de.domisum.lib.auxilium.util.keys.Base64Key; 7 | import de.domisum.lib.auxiliumspigot.util.LocationUtil; 8 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshPoint; 9 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 10 | import de.domisum.lib.compitum.navmesh.transition.NavMeshLadder; 11 | import de.domisum.lib.compitum.navmesh.transition.NavMeshTrianglePortal; 12 | import org.bukkit.Location; 13 | import org.bukkit.World; 14 | 15 | import java.util.Collection; 16 | import java.util.HashMap; 17 | import java.util.HashSet; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.Set; 21 | 22 | public class NavMesh 23 | { 24 | 25 | // CONSTANTS 26 | private static final int KEY_LENGTH = 5; 27 | 28 | // PROPERTIES 29 | private String id; 30 | private Vector3D rangeCenter; 31 | private double range; 32 | 33 | // REFERENCES 34 | private World world; 35 | private Map points = new HashMap<>(); // 36 | private Map triangles = new HashMap<>(); // 37 | 38 | 39 | // INIT 40 | public NavMesh(String id, Vector3D ranceCenter, double range, World world, Collection points, 41 | Collection triangles) 42 | { 43 | this.id = id; 44 | this.rangeCenter = ranceCenter; 45 | this.range = range; 46 | 47 | this.world = world; 48 | for(NavMeshPoint p : points) 49 | this.points.put(p.getId(), p); 50 | for(NavMeshTriangle t : triangles) 51 | this.triangles.put(t.id, t); 52 | 53 | fillInNeighbors(); 54 | determineHeuristicTriangleCenters(); 55 | } 56 | 57 | 58 | // GETTERS 59 | // general 60 | @API public String getId() 61 | { 62 | return this.id; 63 | } 64 | 65 | public Vector3D getRangeCenter() 66 | { 67 | return this.rangeCenter; 68 | } 69 | 70 | public double getRange() 71 | { 72 | return this.range; 73 | } 74 | 75 | protected boolean isInRange(Location location) 76 | { 77 | if(location.getWorld() != this.world) 78 | return false; 79 | 80 | return LocationUtil.toVector3D(location).distanceToSquared(this.rangeCenter) < this.range*this.range; 81 | } 82 | 83 | public World getWorld() 84 | { 85 | return this.world; 86 | } 87 | 88 | 89 | // poin 90 | public Collection getPoints() 91 | { 92 | return this.points.values(); 93 | } 94 | 95 | private NavMeshPoint getPoint(String id) 96 | { 97 | return this.points.get(id); 98 | } 99 | 100 | 101 | // triangle 102 | public Collection getTriangles() 103 | { 104 | return this.triangles.values(); 105 | } 106 | 107 | @API public Set getTrianglesUsingPoint(NavMeshPoint point) 108 | { 109 | Set trianglesUsingPoint = new HashSet<>(); 110 | 111 | for(NavMeshTriangle triangle : this.triangles.values()) 112 | if(triangle.isUsingPoint(point)) 113 | trianglesUsingPoint.add(triangle); 114 | 115 | return trianglesUsingPoint; 116 | } 117 | 118 | 119 | public NavMeshTriangle getTriangle(String id) 120 | { 121 | return this.triangles.get(id); 122 | } 123 | 124 | public NavMeshTriangle getTriangleAt(Location location) 125 | { 126 | // TODO optimize this, might become bottleneck with thousands of triangles 127 | 128 | for(NavMeshTriangle triangle : this.triangles.values()) 129 | if(triangle.doesContain(location)) 130 | return triangle; 131 | 132 | return null; 133 | } 134 | 135 | 136 | // POINT 137 | @API public NavMeshPoint createPoint(double x, double y, double z) 138 | { 139 | NavMeshPoint point = new NavMeshPoint(getUnusedId(), x, y, z); 140 | 141 | this.points.put(point.getId(), point); 142 | return point; 143 | } 144 | 145 | @API public void removePoint(NavMeshPoint point) 146 | { 147 | for(NavMeshTriangle t : getTrianglesUsingPoint(point)) 148 | deleteTriangle(t); 149 | 150 | this.points.remove(point.getId()); 151 | } 152 | 153 | 154 | // TRIANGLE 155 | @API public NavMeshTriangle createTriangle(NavMeshPoint point1, NavMeshPoint point2, NavMeshPoint point3) 156 | { 157 | NavMeshTriangle triangle = new NavMeshTriangle(getUnusedId(), point1, point2, point3); 158 | this.triangles.put(triangle.id, triangle); 159 | 160 | fillInNeighborsFor(triangle); 161 | 162 | return triangle; 163 | } 164 | 165 | @API public void deleteTriangle(NavMeshTriangle triangle) 166 | { 167 | this.triangles.remove(triangle.id); 168 | triangle.clearNeighbors(); 169 | } 170 | 171 | 172 | // LADDER 173 | @API public void createLadder(NavMeshTriangle triangle1, Vector3D position1, NavMeshTriangle triangle2, 174 | Vector3D position2, Direction2D ladderDirection) 175 | { 176 | NavMeshLadder ladder; 177 | if(position1.y < position2.y) 178 | ladder = new NavMeshLadder(triangle1, position1, triangle2, position2, ladderDirection); 179 | else 180 | ladder = new NavMeshLadder(triangle2, position2, triangle1, position1, ladderDirection); 181 | 182 | triangle1.makeNeighbors(triangle2, ladder); 183 | } 184 | 185 | @API public void removeLadder(NavMeshLadder ladder) 186 | { 187 | ladder.getTriangleBottom().removeNeighbor(ladder.getTriangleTop()); 188 | } 189 | 190 | 191 | // PATHFINDING 192 | private void fillInNeighbors() 193 | { 194 | for(NavMeshTriangle triangle : this.triangles.values()) 195 | fillInNeighborsFor(triangle); 196 | } 197 | 198 | private void fillInNeighborsFor(NavMeshTriangle triangle) 199 | { 200 | for(NavMeshTriangle t : this.triangles.values()) 201 | { 202 | if(Objects.equals(t, triangle)) 203 | continue; 204 | 205 | Set commonPoints = getCommonPoints(triangle, t); 206 | 207 | if(commonPoints.size() == 2) 208 | { 209 | NavMeshTrianglePortal portal = new NavMeshTrianglePortal(triangle, t, commonPoints); 210 | triangle.makeNeighbors(t, portal); 211 | } 212 | } 213 | } 214 | 215 | 216 | private void determineHeuristicTriangleCenters() 217 | { 218 | for(int i = 0; i < 20; i++) 219 | reduceHeuristicCenterDistances(0.1); 220 | } 221 | 222 | private void reduceHeuristicCenterDistances(double factor) 223 | { 224 | for(NavMeshTriangle triangle : this.triangles.values()) 225 | { 226 | Set neighbors = triangle.neighbors.keySet(); 227 | if(neighbors.size() <= 1) 228 | continue; 229 | 230 | Vector3D neighborSum = new Vector3D(); 231 | for(NavMeshTriangle n : neighbors) 232 | neighborSum = neighborSum.add(n.getHeuristicCenter()); 233 | 234 | Vector3D neighborAverage = neighborSum.divide(neighbors.size()); 235 | Vector3D currentHeuristicCenter = triangle.getHeuristicCenter(); 236 | Vector3D fromCurrentToAverage = neighborAverage.subtract(currentHeuristicCenter); 237 | 238 | Vector3D newHeuristicCenter = currentHeuristicCenter 239 | .moveTowards(neighborAverage, fromCurrentToAverage.length()*factor); 240 | if(triangle.doesContain(newHeuristicCenter)) 241 | triangle.setHeuristicCenter(newHeuristicCenter); 242 | } 243 | } 244 | 245 | 246 | // UTIL 247 | private String getUnusedId() 248 | { 249 | String id; 250 | do 251 | id = Base64Key.generate(KEY_LENGTH); 252 | while(getPoint(id) != null || getTriangle(id) != null); 253 | 254 | return id; 255 | } 256 | 257 | private Set getCommonPoints(NavMeshTriangle triangle1, NavMeshTriangle triangle2) 258 | { 259 | NavMeshPoint[] triangle1Points = new NavMeshPoint[] {triangle1.point1, triangle1.point2, triangle1.point3}; 260 | NavMeshPoint[] triangle2Points = new NavMeshPoint[] {triangle2.point1, triangle2.point2, triangle2.point3}; 261 | 262 | Set commonPoints = new HashSet<>(); 263 | for(NavMeshPoint t1p : triangle1Points) 264 | for(NavMeshPoint t2p : triangle2Points) 265 | if(Objects.equals(t1p, t2p)) 266 | commonPoints.add(t1p); 267 | 268 | return commonPoints; 269 | } 270 | 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/NavMeshManager.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh; 2 | 3 | import de.domisum.lib.auxilium.util.FileUtil; 4 | import de.domisum.lib.auxilium.util.json.GsonUtil; 5 | import de.domisum.lib.compitum.CompitumLib; 6 | import de.domisum.lib.compitum.navmesh.json.SerializationNavMesh; 7 | import org.bukkit.Location; 8 | 9 | import java.io.File; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | public class NavMeshManager 14 | { 15 | 16 | // CONSTANTS 17 | private static final String NAV_MESHES_DIRECTORY = "navMeshes"; 18 | private static final String NAV_MESH_FILE_EXTENSION = ".navMesh.json"; 19 | 20 | // REFERENCES 21 | private Set meshes = new HashSet<>(); 22 | 23 | 24 | /* 25 | // INITIALIZATION 26 | */ 27 | public void initiialize() 28 | { 29 | loadMeshes(); 30 | } 31 | 32 | public void terminate() 33 | { 34 | // don't save the meshes by default 35 | } 36 | 37 | 38 | // LOADING 39 | private void loadMeshes() 40 | { 41 | CompitumLib.getLogger().info("Loading NavMeshs..."); 42 | 43 | File baseDir = new File(NAV_MESHES_DIRECTORY); 44 | // noinspection ResultOfMethodCallIgnored 45 | baseDir.mkdirs(); 46 | 47 | for(File file : FileUtil.listFilesRecursively(baseDir, FileUtil.FileType.FILE)) 48 | { 49 | if(!file.getName().endsWith(NAV_MESH_FILE_EXTENSION)) 50 | { 51 | CompitumLib 52 | .getLogger() 53 | .warning("The file '"+file.getAbsolutePath() 54 | +" in the NavMesh directory was skipped since it doesn't end with '"+NAV_MESH_FILE_EXTENSION 55 | +"'."); 56 | continue; 57 | } 58 | 59 | String navMeshId = FileUtil.getNameWithoutCompositeExtension(file); 60 | 61 | String content = FileUtil.readString(file); 62 | NavMesh navMesh; 63 | try 64 | { 65 | SerializationNavMesh serializationNavMesh = GsonUtil.get().fromJson(content, SerializationNavMesh.class); 66 | navMesh = serializationNavMesh.convertToNavMesh(navMeshId); 67 | this.meshes.add(navMesh); 68 | 69 | CompitumLib 70 | .getLogger() 71 | .info("Loaded NavMesh '"+navMesh.getId()+"' with "+navMesh.getTriangles().size()+" triangles"); 72 | } 73 | catch(Exception e) 74 | { 75 | e.printStackTrace(); 76 | } 77 | } 78 | 79 | CompitumLib.getLogger().info("Loading NavMeshes complete: loaded "+this.meshes.size()+" NavMesh(es)"); 80 | } 81 | 82 | 83 | // SAVING 84 | public void saveMeshes() 85 | { 86 | CompitumLib.getLogger().info("Saving NavMeshs..."); 87 | 88 | File baseDir = new File(NAV_MESHES_DIRECTORY); 89 | // noinspection ResultOfMethodCallIgnored 90 | baseDir.mkdirs(); 91 | 92 | for(NavMesh navMesh : this.meshes) 93 | { 94 | File file = new File(baseDir, navMesh.getId()+NAV_MESH_FILE_EXTENSION); 95 | 96 | try 97 | { 98 | SerializationNavMesh sNavMesh = new SerializationNavMesh(navMesh); 99 | String json = GsonUtil.getPretty().toJson(sNavMesh); 100 | 101 | FileUtil.writeString(file, json); 102 | } 103 | catch(Exception e) 104 | { 105 | e.printStackTrace(); 106 | } 107 | } 108 | 109 | CompitumLib.getLogger().info("Saving NavMeshs complete: saved "+this.meshes.size()+" NavMesh(s)"); 110 | } 111 | 112 | 113 | // GETTERS 114 | public NavMesh getNavMeshAt(Location location) 115 | { 116 | for(NavMesh g : this.meshes) 117 | if(g.isInRange(location)) 118 | return g; 119 | 120 | return null; 121 | } 122 | 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/geometry/NavMeshPoint.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.geometry; 2 | 3 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 4 | import de.domisum.lib.auxilium.util.java.annotations.DeserializationNoArgsConstructor; 5 | import de.domisum.lib.auxilium.util.math.MathUtil; 6 | 7 | public class NavMeshPoint 8 | { 9 | 10 | // PROPERTIES 11 | private final String id; 12 | 13 | private double x; 14 | private double y; 15 | private double z; 16 | 17 | 18 | // INIT 19 | @DeserializationNoArgsConstructor public NavMeshPoint() 20 | { 21 | this.id = ""; 22 | } 23 | 24 | public NavMeshPoint(String id, double x, double y, double z) 25 | { 26 | this.id = id; 27 | 28 | this.x = MathUtil.round(x, 2); 29 | this.y = MathUtil.round(y, 2); 30 | this.z = MathUtil.round(z, 2); 31 | } 32 | 33 | @Override public boolean equals(Object o) 34 | { 35 | if(!(o instanceof NavMeshPoint)) 36 | return false; 37 | 38 | NavMeshPoint other = (NavMeshPoint) o; 39 | return this.id.equals(other.id); 40 | } 41 | 42 | @Override public int hashCode() 43 | { 44 | return this.id.hashCode(); 45 | } 46 | 47 | 48 | // GETTERS 49 | public String getId() 50 | { 51 | return this.id; 52 | } 53 | 54 | 55 | public double getX() 56 | { 57 | return this.x; 58 | } 59 | 60 | public double getY() 61 | { 62 | return this.y; 63 | } 64 | 65 | public double getZ() 66 | { 67 | return this.z; 68 | } 69 | 70 | public Vector3D getPositionVector() 71 | { 72 | return new Vector3D(this.x, this.y, this.z); 73 | } 74 | 75 | 76 | // SETTERS 77 | public void setX(double x) 78 | { 79 | this.x = x; 80 | } 81 | 82 | public void setY(double y) 83 | { 84 | this.y = y; 85 | } 86 | 87 | public void setZ(double z) 88 | { 89 | this.z = z; 90 | } 91 | 92 | private void setLocation(double x, double y, double z) 93 | { 94 | this.x = x; 95 | this.y = y; 96 | this.z = z; 97 | } 98 | 99 | public void setLocation(Vector3D location) 100 | { 101 | setLocation(location.x, location.y, location.z); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/geometry/NavMeshTriangle.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.geometry; 2 | 3 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 4 | import de.domisum.lib.auxiliumspigot.util.LocationUtil; 5 | import de.domisum.lib.compitum.CompitumLib; 6 | import de.domisum.lib.compitum.navmesh.transition.NavMeshTriangleTransition; 7 | import lombok.Setter; 8 | import org.bukkit.Location; 9 | 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | import java.util.Objects; 14 | 15 | public class NavMeshTriangle 16 | { 17 | 18 | // CONSTANTS 19 | private static final double CONTAINS_TOLERANCE = 0.00001; 20 | 21 | // PROPERTIES 22 | public final String id; 23 | 24 | public final NavMeshPoint point1; 25 | public final NavMeshPoint point2; 26 | public final NavMeshPoint point3; 27 | 28 | public final Map neighbors = new HashMap<>(); 29 | 30 | // STATUS 31 | @Setter private Vector3D heuristicCenter; 32 | 33 | 34 | // INIT 35 | public NavMeshTriangle(String id, NavMeshPoint point1, NavMeshPoint point2, NavMeshPoint point3) 36 | { 37 | this.id = id; 38 | 39 | this.point1 = point1; 40 | this.point2 = point2; 41 | this.point3 = point3; 42 | } 43 | 44 | @Override public String toString() 45 | { 46 | return "triangle["+this.point1.getId()+","+this.point2.getId()+","+this.point3.getId()+"]"; 47 | } 48 | 49 | @Override public int hashCode() 50 | { 51 | return this.id.hashCode(); 52 | } 53 | 54 | @Override public boolean equals(Object o) 55 | { 56 | if(this == o) 57 | return true; 58 | if(o == null || getClass() != o.getClass()) 59 | return false; 60 | 61 | NavMeshTriangle that = (NavMeshTriangle) o; 62 | 63 | if(!this.id.equals(that.id)) 64 | return false; 65 | 66 | return true; 67 | } 68 | 69 | 70 | // GETTERS 71 | // intrinsic 72 | public Vector3D getCenter() 73 | { 74 | Vector3D sum = this.point1.getPositionVector().add(this.point2.getPositionVector().add(this.point3.getPositionVector())); 75 | return sum.divide(3); 76 | } 77 | 78 | public Vector3D getHeuristicCenter() 79 | { 80 | if(this.heuristicCenter == null) 81 | return getCenter(); 82 | 83 | return this.heuristicCenter; 84 | } 85 | 86 | 87 | // relational 88 | public boolean isUsingPoint(NavMeshPoint point) 89 | { 90 | return this.point1 == point || this.point2 == point || this.point3 == point; 91 | } 92 | 93 | public NavMeshTriangleTransition getTransitionTo(NavMeshTriangle other) 94 | { 95 | return this.neighbors.get(other); 96 | } 97 | 98 | 99 | // world 100 | public boolean doesContain(Location location) 101 | { 102 | return doesContain(LocationUtil.toVector3D(location)); 103 | } 104 | 105 | public boolean doesContain(Vector3D point) 106 | { 107 | if(Math.abs(getCenter().y-point.y) >= 2) 108 | return false; 109 | 110 | Vector3D a = this.point1.getPositionVector(); 111 | Vector3D b = this.point2.getPositionVector(); 112 | Vector3D c = this.point3.getPositionVector(); 113 | 114 | double ab = sign(point, a, b); 115 | double bc = sign(point, b, c); 116 | double ca = sign(point, c, a); 117 | 118 | if(Math.abs(ab) < CONTAINS_TOLERANCE) 119 | { 120 | if(Math.abs(bc) < CONTAINS_TOLERANCE) 121 | return true; 122 | else if(Math.abs(ca) < CONTAINS_TOLERANCE) 123 | return true; 124 | 125 | return bc < 0 == ca < 0; 126 | } 127 | else if(Math.abs(bc) < CONTAINS_TOLERANCE) 128 | { 129 | if(Math.abs(ab) < CONTAINS_TOLERANCE) 130 | return true; 131 | else if(Math.abs(ca) < CONTAINS_TOLERANCE) 132 | return true; 133 | 134 | return ab < 0 == ca < 0; 135 | } 136 | else if(Math.abs(ca) < CONTAINS_TOLERANCE) 137 | { 138 | if(Math.abs(ab) < CONTAINS_TOLERANCE) 139 | return true; 140 | else if(Math.abs(bc) < CONTAINS_TOLERANCE) 141 | return true; 142 | 143 | return ab < 0 == bc < 0; 144 | } 145 | 146 | return (ab < 0 == bc < 0) && (bc < 0 == ca < 0); 147 | } 148 | 149 | 150 | // NEIGHBORS 151 | public void makeNeighbors(NavMeshTriangle other, NavMeshTriangleTransition transition) 152 | { 153 | if(this.neighbors.containsKey(other)) 154 | { 155 | NavMeshTriangleTransition currentTransition = this.neighbors.get(other); 156 | if(Objects.equals(currentTransition, transition)) 157 | CompitumLib.getLogger() 158 | .warning("Overridden NavMesh neighbor: "+currentTransition+"' overridden by '"+transition+"'"); 159 | } 160 | 161 | this.neighbors.put(other, transition); 162 | other.neighbors.put(this, transition); 163 | } 164 | 165 | public void removeNeighbor(NavMeshTriangle other) 166 | { 167 | this.neighbors.remove(other); 168 | other.neighbors.remove(this); 169 | } 170 | 171 | public void clearNeighbors() 172 | { 173 | for(NavMeshTriangle n : new HashSet<>(this.neighbors.keySet())) 174 | removeNeighbor(n); 175 | } 176 | 177 | 178 | // UTIL 179 | private double sign(Vector3D p1, Vector3D p2, Vector3D p3) 180 | { 181 | return (p1.x-p3.x)*(p2.z-p3.z)-(p2.x-p3.x)*(p1.z-p3.z); 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/json/SerializationNavMesh.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.json; 2 | 3 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 4 | import de.domisum.lib.auxilium.util.java.annotations.DeserializationNoArgsConstructor; 5 | import de.domisum.lib.auxilium.util.java.annotations.InitByDeserialization; 6 | import de.domisum.lib.compitum.navmesh.NavMesh; 7 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshPoint; 8 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 9 | import de.domisum.lib.compitum.navmesh.transition.NavMeshLadder; 10 | import de.domisum.lib.compitum.navmesh.transition.NavMeshTriangleTransition; 11 | import org.bukkit.Bukkit; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Comparator; 15 | import java.util.HashSet; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | public class SerializationNavMesh 20 | { 21 | 22 | // PROPERTIES 23 | @InitByDeserialization private String worldName; 24 | @InitByDeserialization private Vector3D rangeCenter; 25 | @InitByDeserialization private double range; 26 | 27 | // REFERENCES 28 | @InitByDeserialization private List points = new ArrayList<>(); 29 | @InitByDeserialization private List triangles = new ArrayList<>(); 30 | @InitByDeserialization private List ladders = new ArrayList<>(); 31 | 32 | 33 | // INIT 34 | @DeserializationNoArgsConstructor public SerializationNavMesh() 35 | { 36 | 37 | } 38 | 39 | 40 | // CONVERSION 41 | public SerializationNavMesh(NavMesh mesh) 42 | { 43 | this.worldName = mesh.getWorld().getName(); 44 | this.rangeCenter = mesh.getRangeCenter(); 45 | this.range = mesh.getRange(); 46 | 47 | this.points.addAll(mesh.getPoints()); 48 | for(NavMeshTriangle triangle : mesh.getTriangles()) 49 | { 50 | this.triangles.add(new SerializationNavMeshTriangle(triangle)); 51 | 52 | for(NavMeshTriangleTransition transition : triangle.neighbors.values()) 53 | if(transition instanceof NavMeshLadder) 54 | { 55 | NavMeshLadder navMeshLadder = (NavMeshLadder) transition; 56 | // only add the ladder if the triangle currently being processed is the bottom one 57 | // in order to avoid duplicating the ladder 58 | if(navMeshLadder.getTriangleBottom() == triangle) 59 | this.ladders.add(new SerializationNavMeshLadder(navMeshLadder)); 60 | } 61 | } 62 | 63 | this.points.sort(Comparator.comparing(NavMeshPoint::getId)); 64 | this.triangles.sort(Comparator.comparing(SerializationNavMeshTriangle::getId)); 65 | this.ladders.sort(Comparator.comparing(SerializationNavMeshLadder::getTriangleBottom)); 66 | } 67 | 68 | public NavMesh convertToNavMesh(String id) 69 | { 70 | Set triangles = new HashSet<>(); 71 | for(SerializationNavMeshTriangle serializationTriangle : this.triangles) 72 | triangles.add(serializationTriangle.getNavMeshTriangle(this.points)); 73 | 74 | NavMesh navMesh = new NavMesh(id, this.rangeCenter, this.range, Bukkit.getWorld(this.worldName), this.points, triangles); 75 | 76 | for(SerializationNavMeshLadder serializationLadder : this.ladders) 77 | { 78 | NavMeshLadder ladder = serializationLadder.getNavMeshLadder(navMesh); 79 | ladder.getTriangleBottom().makeNeighbors(ladder.getTriangleTop(), ladder); 80 | } 81 | 82 | return navMesh; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/json/SerializationNavMeshLadder.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.json; 2 | 3 | import de.domisum.lib.auxilium.data.container.direction.Direction2D; 4 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 5 | import de.domisum.lib.auxilium.util.java.annotations.DeserializationNoArgsConstructor; 6 | import de.domisum.lib.auxilium.util.java.annotations.InitByDeserialization; 7 | import de.domisum.lib.compitum.navmesh.NavMesh; 8 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 9 | import de.domisum.lib.compitum.navmesh.transition.NavMeshLadder; 10 | import lombok.Getter; 11 | 12 | class SerializationNavMeshLadder 13 | { 14 | 15 | // PROPERTIES 16 | @InitByDeserialization @Getter private String triangleBottom; 17 | @InitByDeserialization private Vector3D positionBottom; 18 | 19 | @InitByDeserialization private String triangleTop; 20 | @InitByDeserialization private Vector3D positionTop; 21 | 22 | @InitByDeserialization private Direction2D ladderDirection; 23 | 24 | 25 | // INIT 26 | @DeserializationNoArgsConstructor public SerializationNavMeshLadder() 27 | { 28 | 29 | } 30 | 31 | protected SerializationNavMeshLadder(NavMeshLadder navMeshLadder) 32 | { 33 | this.triangleBottom = navMeshLadder.getTriangleBottom().id; 34 | this.positionBottom = navMeshLadder.getPositionBottom(); 35 | 36 | this.triangleTop = navMeshLadder.getTriangleTop().id; 37 | this.positionTop = navMeshLadder.getPositionTop(); 38 | 39 | this.ladderDirection = navMeshLadder.getLadderDirection(); 40 | } 41 | 42 | 43 | // GETTERS 44 | protected NavMeshLadder getNavMeshLadder(NavMesh navMesh) 45 | { 46 | NavMeshTriangle triangleBottom_ = navMesh.getTriangle(this.triangleBottom); 47 | NavMeshTriangle triangleTop_ = navMesh.getTriangle(this.triangleTop); 48 | 49 | return new NavMeshLadder(triangleBottom_, this.positionBottom, triangleTop_, this.positionTop, this.ladderDirection); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/json/SerializationNavMeshTriangle.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.json; 2 | 3 | import de.domisum.lib.auxilium.util.java.annotations.DeserializationNoArgsConstructor; 4 | import de.domisum.lib.auxilium.util.java.annotations.InitByDeserialization; 5 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshPoint; 6 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 7 | import lombok.Getter; 8 | 9 | import java.util.Collection; 10 | 11 | class SerializationNavMeshTriangle 12 | { 13 | 14 | // PROPERTIES 15 | @InitByDeserialization @Getter private String id; 16 | 17 | @InitByDeserialization private String point1; 18 | @InitByDeserialization private String point2; 19 | @InitByDeserialization private String point3; 20 | 21 | 22 | // INIT 23 | @DeserializationNoArgsConstructor public SerializationNavMeshTriangle() 24 | { 25 | 26 | } 27 | 28 | SerializationNavMeshTriangle(NavMeshTriangle navMeshTriangle) 29 | { 30 | this.id = navMeshTriangle.id; 31 | 32 | this.point1 = navMeshTriangle.point1.getId(); 33 | this.point2 = navMeshTriangle.point2.getId(); 34 | this.point3 = navMeshTriangle.point3.getId(); 35 | } 36 | 37 | 38 | // GETTERS 39 | protected NavMeshTriangle getNavMeshTriangle(Collection points) 40 | { 41 | return new NavMeshTriangle(this.id, getPoint(points, this.point1), getPoint(points, this.point2), 42 | getPoint(points, this.point3)); 43 | } 44 | 45 | private NavMeshPoint getPoint(Collection points, String id) 46 | { 47 | for(NavMeshPoint point : points) 48 | if(point.getId().equals(id)) 49 | return point; 50 | 51 | return null; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/pathfinding/NavMeshPathfinder.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.pathfinding; 2 | 3 | import de.domisum.lib.auxilium.util.java.annotations.API; 4 | import de.domisum.lib.auxilium.util.time.ProfilerStopWatch; 5 | import de.domisum.lib.auxiliumspigot.util.LocationUtil; 6 | import de.domisum.lib.compitum.navmesh.NavMesh; 7 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 8 | import de.domisum.lib.compitum.navmesh.pathfinding.traversal.NavMeshTriangleTraverser; 9 | import de.domisum.lib.compitum.path.Path; 10 | import org.bukkit.Location; 11 | 12 | import java.util.List; 13 | 14 | @API 15 | public class NavMeshPathfinder 16 | { 17 | 18 | // INPUT 19 | private Location startLocation; 20 | private Location targetLocation; 21 | private NavMesh navMesh; 22 | 23 | // STATUS 24 | private ProfilerStopWatch stopWatch = new ProfilerStopWatch("pathfinding.navMesh"); 25 | private ProfilerStopWatch triangleFindingStopWatch = new ProfilerStopWatch("pathfinding.navMesh.startTargetTriangles"); 26 | 27 | // OUTPUT 28 | private Path path; 29 | private String failure; 30 | 31 | 32 | // INIT 33 | @API public NavMeshPathfinder(Location startLocation, Location targetLocation, NavMesh navMesh) 34 | { 35 | this.startLocation = startLocation; 36 | this.targetLocation = targetLocation; 37 | 38 | this.navMesh = navMesh; 39 | } 40 | 41 | 42 | // GETTERS 43 | public Path getPath() 44 | { 45 | return this.path; 46 | } 47 | 48 | @API public String getFailure() 49 | { 50 | return this.failure; 51 | } 52 | 53 | 54 | public ProfilerStopWatch getStopWatch() 55 | { 56 | return this.stopWatch; 57 | } 58 | 59 | 60 | // PATHFINDING 61 | @API public void findPath() 62 | { 63 | this.stopWatch.start(); 64 | this.triangleFindingStopWatch.start(); 65 | NavMeshTriangle startTriangle = this.navMesh.getTriangleAt(this.startLocation); 66 | if(startTriangle == null) 67 | { 68 | this.failure = "Start location is not on NavMesh"; 69 | return; 70 | } 71 | 72 | NavMeshTriangle targetTriangle = this.navMesh.getTriangleAt(this.targetLocation); 73 | if(targetTriangle == null) 74 | { 75 | this.failure = "Target location is not on NavMesh"; 76 | return; 77 | } 78 | 79 | this.triangleFindingStopWatch.stop(); 80 | 81 | NavMeshTrianglePathfinder trianglePathfinder = new NavMeshTrianglePathfinder(startTriangle, targetTriangle); 82 | trianglePathfinder.findPath(); 83 | List triangleSequence = trianglePathfinder.getTriangleSequence(); 84 | if(triangleSequence == null) 85 | { 86 | this.failure = trianglePathfinder.getFailure(); 87 | return; 88 | } 89 | 90 | NavMeshTriangleTraverser triangleTraverser = new NavMeshTriangleTraverser(LocationUtil.toVector3D(this.startLocation), 91 | LocationUtil.toVector3D(this.targetLocation), triangleSequence); 92 | triangleTraverser.traverseTriangles(); 93 | this.path = triangleTraverser.getPath(); 94 | 95 | this.stopWatch.stop(); 96 | 97 | 98 | /*DebugUtil.say(""); 99 | DebugUtil.say(getStopWatch()); 100 | DebugUtil.say(this.triangleFindingStopWatch); 101 | DebugUtil.say(trianglePathfinder.getStopWatch()); 102 | DebugUtil.say(triangleTraverser.getStopWatch());*/ 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/pathfinding/NavMeshTriangleNode.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.pathfinding; 2 | 3 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 4 | import de.domisum.lib.compitum.path.node.weighted.WeightedNode; 5 | 6 | public class NavMeshTriangleNode implements WeightedNode 7 | { 8 | 9 | // REFERENCES 10 | private NavMeshTriangle triangle; 11 | private NavMeshTriangleNode parent; 12 | 13 | // STATUS 14 | private double gValue = -1; 15 | private double heuristicValue = -1; 16 | 17 | 18 | // INIT 19 | protected NavMeshTriangleNode(NavMeshTriangle triangle, NavMeshTriangleNode parent, double heuristicValue) 20 | { 21 | this.triangle = triangle; 22 | this.parent = parent; 23 | 24 | this.heuristicValue = heuristicValue; 25 | } 26 | 27 | @Override public int hashCode() 28 | { 29 | return this.triangle.hashCode(); 30 | } 31 | 32 | @Override public boolean equals(Object o) 33 | { 34 | if(!(o instanceof NavMeshTriangleNode)) 35 | return false; 36 | 37 | NavMeshTriangleNode other = (NavMeshTriangleNode) o; 38 | return this.triangle.equals(other.triangle); 39 | } 40 | 41 | 42 | // GETTERS 43 | protected NavMeshTriangle getTriangle() 44 | { 45 | return this.triangle; 46 | } 47 | 48 | protected NavMeshTriangleNode getParent() 49 | { 50 | return this.parent; 51 | } 52 | 53 | 54 | @Override public double getGValue() 55 | { 56 | if(this.parent == null) 57 | return 0; 58 | 59 | if(this.gValue == -1) 60 | { 61 | double toParent = this.parent.getGValue(); 62 | double fromParent = this.parent.getTriangle().getTransitionTo(this.triangle).getWeight(); 63 | 64 | this.gValue = toParent+fromParent; 65 | } 66 | 67 | return this.gValue; 68 | } 69 | 70 | @Override public double getHValue() 71 | { 72 | return this.heuristicValue; 73 | } 74 | 75 | @Override public double getFValue() 76 | { 77 | return getGValue()+getHValue(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/pathfinding/NavMeshTrianglePathfinder.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.pathfinding; 2 | 3 | import de.domisum.lib.auxilium.util.java.annotations.API; 4 | import de.domisum.lib.auxilium.util.time.ProfilerStopWatch; 5 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 6 | import de.domisum.lib.compitum.navmesh.transition.NavMeshTriangleTransition; 7 | import de.domisum.lib.compitum.path.node.weighted.SortedWeightedNodeList; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | @API 17 | public class NavMeshTrianglePathfinder 18 | { 19 | 20 | // PROPERTIES 21 | private int maxNodeVisits = 200; 22 | 23 | private boolean canUseLadders = true; 24 | 25 | // INPUT 26 | private NavMeshTriangle startTriangle; 27 | private NavMeshTriangle targetTriangle; 28 | 29 | // STATUS 30 | private Set visitedNodes = new HashSet<>(this.maxNodeVisits); 31 | private SortedWeightedNodeList unvisitedNodes = new SortedWeightedNodeList<>(this.maxNodeVisits*3); 32 | 33 | private ProfilerStopWatch stopWatch = new ProfilerStopWatch("pathfinding.navMesh.triangleSequence"); 34 | 35 | // OUTPUT 36 | private List triangleSequence = null; 37 | private String failure; 38 | 39 | 40 | // INIT 41 | @API public NavMeshTrianglePathfinder(NavMeshTriangle startTriangle, NavMeshTriangle targetTriangle) 42 | { 43 | this.startTriangle = startTriangle; 44 | this.targetTriangle = targetTriangle; 45 | } 46 | 47 | 48 | // GETTERS 49 | public List getTriangleSequence() 50 | { 51 | return this.triangleSequence; 52 | } 53 | 54 | public String getFailure() 55 | { 56 | return this.failure; 57 | } 58 | 59 | @API public ProfilerStopWatch getStopWatch() 60 | { 61 | return this.stopWatch; 62 | } 63 | 64 | 65 | @API public boolean canUseLadders() 66 | { 67 | return this.canUseLadders; 68 | } 69 | 70 | 71 | // SETTERS 72 | @API public void setCanUseLadders(boolean canUseLadders) 73 | { 74 | this.canUseLadders = canUseLadders; 75 | } 76 | 77 | 78 | // PATHFINDING 79 | @API public void findPath() 80 | { 81 | this.stopWatch.start(); 82 | 83 | NavMeshTriangleNode targetNode = null; 84 | 85 | // pathfinding 86 | this.unvisitedNodes 87 | .addSorted(new NavMeshTriangleNode(this.startTriangle, null, calculateHeuristicValue(this.startTriangle))); 88 | while(true) 89 | { 90 | if(this.visitedNodes.size() >= this.maxNodeVisits) 91 | { 92 | this.failure = "Too many nodes visited"; 93 | break; 94 | } 95 | 96 | if(this.unvisitedNodes.getSize() == 0) 97 | { 98 | this.failure = "No unvisted nodes left"; 99 | break; 100 | } 101 | 102 | NavMeshTriangleNode node = this.unvisitedNodes.getAndRemoveFirst(); 103 | if(this.targetTriangle.equals(node.getTriangle())) 104 | { 105 | targetNode = node; 106 | break; 107 | } 108 | 109 | visitNode(node); 110 | this.visitedNodes.add(node); 111 | } 112 | 113 | // converting linked node list into triangle list 114 | if(targetNode != null) 115 | { 116 | this.triangleSequence = new ArrayList<>(); 117 | 118 | NavMeshTriangleNode currentNode = targetNode; 119 | while(currentNode != null) 120 | { 121 | this.triangleSequence.add(currentNode.getTriangle()); 122 | currentNode = currentNode.getParent(); 123 | } 124 | 125 | Collections.reverse(this.triangleSequence); 126 | } 127 | 128 | this.stopWatch.stop(); 129 | } 130 | 131 | private void visitNode(NavMeshTriangleNode node) 132 | { 133 | NavMeshTriangle triangle = node.getTriangle(); 134 | for(Map.Entry entry : triangle.neighbors.entrySet()) 135 | { 136 | NavMeshTriangleNode newNode = new NavMeshTriangleNode(entry.getKey(), node, calculateHeuristicValue(triangle)); 137 | if(this.visitedNodes.contains(newNode)) 138 | continue; 139 | 140 | if(this.unvisitedNodes.contains(newNode)) 141 | continue; 142 | 143 | this.unvisitedNodes.addSorted(newNode); 144 | } 145 | } 146 | 147 | 148 | private double calculateHeuristicValue(NavMeshTriangle triangle) 149 | { 150 | double dX = this.targetTriangle.getHeuristicCenter().x-triangle.getHeuristicCenter().x; 151 | double dY = this.targetTriangle.getHeuristicCenter().y-triangle.getHeuristicCenter().y; 152 | double dZ = this.targetTriangle.getHeuristicCenter().z-triangle.getHeuristicCenter().z; 153 | 154 | double dXAbs = Math.abs(dX); 155 | double dYAbs = Math.abs(dY); 156 | double dZAbs = Math.abs(dZ); 157 | 158 | // diagonal distance of x and z 159 | double minD = Math.min(dXAbs, dZAbs); 160 | double maxD = Math.max(dXAbs, dZAbs); 161 | double diagonalDistance = (maxD-minD)+minD*1.414; 162 | 163 | // add dY to account for height difference 164 | return diagonalDistance+dYAbs; 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/pathfinding/traversal/NavMeshTriangleTraverser.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.pathfinding.traversal; 2 | 3 | import de.domisum.lib.auxilium.data.container.math.LineSegment3D; 4 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 5 | import de.domisum.lib.auxilium.util.time.ProfilerStopWatch; 6 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 7 | import de.domisum.lib.compitum.navmesh.transition.NavMeshLadder; 8 | import de.domisum.lib.compitum.navmesh.transition.NavMeshTrianglePortal; 9 | import de.domisum.lib.compitum.navmesh.transition.NavMeshTriangleTransition; 10 | import de.domisum.lib.compitum.path.Path; 11 | import de.domisum.lib.compitum.path.PathWaypoint; 12 | import de.domisum.lib.compitum.path.node.TransitionType; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class NavMeshTriangleTraverser 18 | { 19 | 20 | // INPUT 21 | private Vector3D startPosition; 22 | private Vector3D targetPosition; 23 | private List triangleSequence; 24 | 25 | // STATUS 26 | private List pathWaypoints = new ArrayList<>(); 27 | // triangle traversal 28 | private Vector3D currentPosition; 29 | private Vector3D visLeft; 30 | private Vector3D visRight; 31 | private int visLeftTriangleIndex; 32 | private int visRightTriangleIndex; 33 | 34 | private int currentTriangleIndex = 0; 35 | private NavMeshTriangle triangle; 36 | private NavMeshTriangle triangleAfter; 37 | 38 | private Vector3D portalEndpointLeft; 39 | private Vector3D portalEndpointRight; 40 | 41 | private ProfilerStopWatch stopWatch = new ProfilerStopWatch("pathfinding.navMesh.triangleTraversal"); 42 | 43 | // OUTPUT 44 | private Path path; 45 | 46 | 47 | // INIT 48 | public NavMeshTriangleTraverser(Vector3D startPosition, Vector3D targetPosition, List triangleSequence) 49 | { 50 | this.startPosition = startPosition; 51 | this.targetPosition = targetPosition; 52 | 53 | this.triangleSequence = triangleSequence; 54 | } 55 | 56 | 57 | // GETTERS 58 | public Path getPath() 59 | { 60 | return this.path; 61 | } 62 | 63 | public ProfilerStopWatch getStopWatch() 64 | { 65 | return this.stopWatch; 66 | } 67 | 68 | 69 | private Vector3D getTowardsVisLeft() 70 | { 71 | return this.visLeft.subtract(this.currentPosition); 72 | } 73 | 74 | private Vector3D getTowardsVisRight() 75 | { 76 | return this.visRight.subtract(this.currentPosition); 77 | } 78 | 79 | private Vector3D getTowardsPortalEndpointLeft() 80 | { 81 | return this.portalEndpointLeft.subtract(this.currentPosition); 82 | } 83 | 84 | private Vector3D getTowardsPortalEndpointRight() 85 | { 86 | return this.portalEndpointRight.subtract(this.currentPosition); 87 | } 88 | 89 | 90 | // TRAVERSAL 91 | public void traverseTriangles() 92 | { 93 | this.stopWatch.start(); 94 | 95 | this.currentPosition = this.startPosition; 96 | 97 | if(this.triangleSequence.size() == 1) 98 | this.pathWaypoints.add(new PathWaypoint(this.targetPosition, TransitionType.WALK)); 99 | else 100 | { 101 | for(this.currentTriangleIndex = 0; 102 | this.currentTriangleIndex < this.triangleSequence.size(); this.currentTriangleIndex++) 103 | processTriangleTransition(); 104 | } 105 | 106 | this.path = new Path(this.pathWaypoints); 107 | this.stopWatch.stop(); 108 | } 109 | 110 | 111 | private void processTriangleTransition() 112 | { 113 | this.triangle = this.triangleSequence.get(this.currentTriangleIndex); 114 | this.triangleAfter = this.currentTriangleIndex+1 < this.triangleSequence.size() ? 115 | this.triangleSequence.get(this.currentTriangleIndex+1) : 116 | null; 117 | 118 | NavMeshTriangleTransition transition = this.triangle.getTransitionTo(this.triangleAfter); 119 | 120 | if(this.triangleAfter == null) 121 | traverseTrianglePortal(); 122 | else if(transition.getTransitionType() == TransitionType.WALK) 123 | traverseTrianglePortal(); 124 | else if(transition.getTransitionType() == TransitionType.CLIMB) 125 | useLadder(); 126 | } 127 | 128 | 129 | // WALKING 130 | private void traverseTrianglePortal() 131 | { 132 | if(this.triangleAfter == null) // last triangle 133 | processMovementTowardsTargetPoint(this.targetPosition); 134 | // either first triangle processing or after new corner 135 | else if(this.visLeft == null) // if visLeft is null, then visRight is also null 136 | { 137 | findPortalEndpoints(this.triangle, this.triangleAfter); 138 | this.visLeft = this.portalEndpointLeft; 139 | this.visRight = this.portalEndpointRight; 140 | this.visLeftTriangleIndex = this.currentTriangleIndex; 141 | this.visRightTriangleIndex = this.currentTriangleIndex; 142 | } 143 | else 144 | { 145 | findPortalEndpoints(this.triangle, this.triangleAfter); 146 | 147 | boolean leftSame = isSame(this.visLeft, this.currentPosition); 148 | boolean rightSame = isSame(this.visRight, this.currentPosition); 149 | 150 | // check if portal is out on one side 151 | if(isLeftOf(getTowardsVisRight(), getTowardsPortalEndpointLeft(), true) && !leftSame && !rightSame) // right turn 152 | { 153 | newWaypoint(this.visRight, TransitionType.WALK); 154 | 155 | this.currentTriangleIndex = this.visRightTriangleIndex; 156 | return; 157 | } 158 | else if(isLeftOf(getTowardsPortalEndpointRight(), getTowardsVisLeft(), true) && !leftSame && !rightSame) // left turn 159 | { 160 | newWaypoint(this.visLeft, TransitionType.WALK); 161 | 162 | this.currentTriangleIndex = this.visLeftTriangleIndex; 163 | return; 164 | } 165 | 166 | // confine movement cone 167 | if(isLeftOf(getTowardsVisLeft(), getTowardsPortalEndpointLeft(), true)) // left 168 | { 169 | this.visLeft = this.portalEndpointLeft; 170 | this.visLeftTriangleIndex = this.currentTriangleIndex; 171 | } 172 | if(isLeftOf(getTowardsPortalEndpointRight(), getTowardsVisRight(), true)) // right 173 | { 174 | this.visRight = this.portalEndpointRight; 175 | this.visRightTriangleIndex = this.currentTriangleIndex; 176 | } 177 | } 178 | } 179 | 180 | private void processMovementTowardsTargetPoint(Vector3D targetPoint) 181 | { 182 | // the vis points can be null if the transition into the previous triangle was a turn 183 | // if this is the case, the target point is guaranteed to be in the cone 184 | if(this.visLeft != null) 185 | { 186 | Vector3D towardsTargetPoint = targetPoint.subtract(this.currentPosition); 187 | 188 | if(isLeftOf(getTowardsVisRight(), towardsTargetPoint, false)) // right turn 189 | { 190 | newWaypoint(this.visRight, TransitionType.WALK); 191 | 192 | this.currentTriangleIndex = this.visRightTriangleIndex; 193 | return; 194 | } 195 | else if(isLeftOf(towardsTargetPoint, getTowardsVisLeft(), false)) // left turn 196 | { 197 | newWaypoint(this.visLeft, TransitionType.WALK); 198 | 199 | this.currentTriangleIndex = this.visLeftTriangleIndex; 200 | return; 201 | } 202 | } 203 | 204 | this.pathWaypoints.add(new PathWaypoint(targetPoint, TransitionType.WALK)); 205 | } 206 | 207 | 208 | // LADDER CLIMBING 209 | private void useLadder() 210 | { 211 | NavMeshTriangleTransition transition = this.triangle.getTransitionTo(this.triangleAfter); 212 | NavMeshLadder ladder = (NavMeshLadder) transition; 213 | 214 | boolean upwards = ladder.getTriangleBottom() == this.triangle; 215 | if(upwards) 216 | { 217 | processMovementTowardsTargetPoint(ladder.getPositionBottom()); 218 | Vector3D climbingEndPosition = new Vector3D(ladder.getPositionBottom().x, ladder.getPositionTop().y, 219 | ladder.getPositionBottom().z); 220 | 221 | PathWaypoint climbPathWaypoint = newWaypoint(climbingEndPosition, TransitionType.CLIMB); 222 | climbPathWaypoint.setData("ladderDirection", ladder.getLadderDirection()); 223 | newWaypoint(ladder.getPositionTop(), TransitionType.WALK); 224 | } 225 | else 226 | { 227 | Vector3D climbingStartPosition = new Vector3D(ladder.getPositionBottom().x, ladder.getPositionTop().y, 228 | ladder.getPositionBottom().z); 229 | 230 | processMovementTowardsTargetPoint(climbingStartPosition); 231 | PathWaypoint climbPathWaypoint = newWaypoint(ladder.getPositionBottom(), TransitionType.CLIMB); 232 | climbPathWaypoint.setData("ladderDirection", ladder.getLadderDirection()); 233 | } 234 | } 235 | 236 | 237 | // SUBROUTINES 238 | private void findPortalEndpoints(NavMeshTriangle from, NavMeshTriangle to) 239 | { 240 | NavMeshTriangleTransition transition = from.getTransitionTo(to); 241 | LineSegment3D portalLineSegment = ((NavMeshTrianglePortal) transition).getFullLineSegment(); 242 | 243 | this.portalEndpointLeft = portalLineSegment.a; 244 | this.portalEndpointRight = portalLineSegment.b; 245 | 246 | Vector3D fromCenter = from.getCenter(); 247 | if(isLeftOf(this.portalEndpointRight.subtract(fromCenter), this.portalEndpointLeft.subtract(fromCenter), false)) 248 | { 249 | Vector3D temp = this.portalEndpointLeft; 250 | this.portalEndpointLeft = this.portalEndpointRight; 251 | this.portalEndpointRight = temp; 252 | } 253 | } 254 | 255 | private PathWaypoint newWaypoint(Vector3D position, int transitionType) 256 | { 257 | PathWaypoint pathWaypoint = new PathWaypoint(position, transitionType); 258 | this.pathWaypoints.add(pathWaypoint); 259 | 260 | this.currentPosition = position; 261 | this.visLeft = null; 262 | this.visRight = null; 263 | 264 | return pathWaypoint; 265 | } 266 | 267 | 268 | // UTIL 269 | private static boolean isLeftOf(Vector3D v1, Vector3D v2, boolean onZero) 270 | { 271 | double crossY = v1.crossProduct(v2).y; 272 | 273 | if(crossY == 0) 274 | return onZero; 275 | 276 | return crossY < 0; 277 | } 278 | 279 | private static boolean isSame(Vector3D a, Vector3D b) 280 | { 281 | if(a == null) 282 | return b == null; 283 | 284 | return a.equals(b); 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/transition/NavMeshLadder.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.transition; 2 | 3 | import de.domisum.lib.auxilium.data.container.direction.Direction2D; 4 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 5 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 6 | import de.domisum.lib.compitum.path.node.TransitionType; 7 | 8 | public class NavMeshLadder implements NavMeshTriangleTransition 9 | { 10 | 11 | // CONSTANTS 12 | private static final double CLIMBING_EXPENSE = 2; 13 | 14 | // PROPERTIES 15 | private NavMeshTriangle triangleBottom; 16 | private Vector3D positionBottom; 17 | 18 | private NavMeshTriangle triangleTop; 19 | private Vector3D positionTop; 20 | 21 | private Direction2D ladderDirection; 22 | 23 | 24 | // INIT 25 | public NavMeshLadder( 26 | NavMeshTriangle triangleBottom, 27 | Vector3D positionBottom, 28 | NavMeshTriangle triangleTop, 29 | Vector3D positionTop, 30 | Direction2D ladderDirection) 31 | { 32 | this.triangleBottom = triangleBottom; 33 | this.positionBottom = positionBottom; 34 | 35 | this.triangleTop = triangleTop; 36 | this.positionTop = positionTop; 37 | 38 | this.ladderDirection = ladderDirection; 39 | } 40 | 41 | 42 | // GETTERS 43 | public NavMeshTriangle getTriangleBottom() 44 | { 45 | return this.triangleBottom; 46 | } 47 | 48 | public Vector3D getPositionBottom() 49 | { 50 | return this.positionBottom; 51 | } 52 | 53 | 54 | public NavMeshTriangle getTriangleTop() 55 | { 56 | return this.triangleTop; 57 | } 58 | 59 | public Vector3D getPositionTop() 60 | { 61 | return this.positionTop; 62 | } 63 | 64 | 65 | public Direction2D getLadderDirection() 66 | { 67 | return this.ladderDirection; 68 | } 69 | 70 | 71 | // TRANSITION 72 | @Override public int getTransitionType() 73 | { 74 | return TransitionType.CLIMB; 75 | } 76 | 77 | @Override public double getWeight() 78 | { 79 | double dY = Math.abs(this.positionTop.y-this.positionBottom.y); 80 | return dY*CLIMBING_EXPENSE; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/transition/NavMeshTrianglePortal.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.transition; 2 | 3 | import de.domisum.lib.auxilium.data.container.math.LineSegment3D; 4 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshPoint; 5 | import de.domisum.lib.compitum.navmesh.geometry.NavMeshTriangle; 6 | import de.domisum.lib.compitum.path.node.TransitionType; 7 | 8 | import java.util.Collection; 9 | import java.util.Iterator; 10 | 11 | public class NavMeshTrianglePortal implements NavMeshTriangleTransition 12 | { 13 | 14 | // REFERENCES 15 | private final NavMeshTriangle triangle1; 16 | private final NavMeshTriangle triangle2; 17 | 18 | private final NavMeshPoint point1; 19 | private final NavMeshPoint point2; 20 | 21 | // STATUS 22 | private LineSegment3D fullLineSegment; 23 | private double triangleHeuristicCenterDistance = -1; 24 | 25 | 26 | // INIT 27 | public NavMeshTrianglePortal(NavMeshTriangle triangle1, NavMeshTriangle triangle2, Collection commonPoints) 28 | { 29 | this.triangle1 = triangle1; 30 | this.triangle2 = triangle2; 31 | 32 | if(commonPoints.size() != 2) 33 | throw new IllegalArgumentException("The number of current points has to be 2 (was "+commonPoints.size()+")"); 34 | 35 | Iterator iterator = commonPoints.iterator(); 36 | this.point1 = iterator.next(); 37 | this.point2 = iterator.next(); 38 | } 39 | 40 | 41 | // GETTERS 42 | public LineSegment3D getFullLineSegment() 43 | { 44 | if(this.fullLineSegment == null) 45 | this.fullLineSegment = new LineSegment3D(this.point1.getPositionVector(), this.point2.getPositionVector()); 46 | 47 | return this.fullLineSegment; 48 | } 49 | 50 | @Override public int getTransitionType() 51 | { 52 | return TransitionType.WALK; 53 | } 54 | 55 | @Override public double getWeight() 56 | { 57 | if(this.triangleHeuristicCenterDistance == -1) 58 | this.triangleHeuristicCenterDistance = this.triangle2.getHeuristicCenter() 59 | .subtract(this.triangle1.getHeuristicCenter()).length(); 60 | 61 | return this.triangleHeuristicCenterDistance; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/navmesh/transition/NavMeshTriangleTransition.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.navmesh.transition; 2 | 3 | public interface NavMeshTriangleTransition 4 | { 5 | 6 | // GETTERS 7 | int getTransitionType(); 8 | 9 | double getWeight(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/path/BlockPath.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.path; 2 | 3 | import de.domisum.lib.auxilium.util.java.annotations.API; 4 | import de.domisum.lib.compitum.path.node.BlockPathNode; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class BlockPath 11 | { 12 | 13 | // PROPERTIES 14 | private List nodes = new ArrayList<>(); 15 | 16 | 17 | // INIT 18 | public BlockPath(BlockPathNode endNode) 19 | { 20 | generatePath(endNode); 21 | } 22 | 23 | private void generatePath(BlockPathNode endNode) 24 | { 25 | BlockPathNode currentNode = endNode; 26 | while(currentNode != null) 27 | { 28 | this.nodes.add(currentNode); 29 | currentNode = currentNode.getParent(); 30 | } 31 | 32 | Collections.reverse(this.nodes); 33 | } 34 | 35 | 36 | // GETTERS 37 | @API public List getNodes() 38 | { 39 | return this.nodes; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/path/Path.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.path; 2 | 3 | import java.util.List; 4 | 5 | public class Path 6 | { 7 | 8 | private List pathWaypoints; 9 | 10 | 11 | // INIT 12 | public Path(List pathWaypoints) 13 | { 14 | this.pathWaypoints = pathWaypoints; 15 | } 16 | 17 | 18 | // GETTERS 19 | public int getNumberOfWaypoints() 20 | { 21 | return this.pathWaypoints.size(); 22 | } 23 | 24 | public PathWaypoint getWaypoint(int index) 25 | { 26 | if(index >= this.pathWaypoints.size() || index < 0) 27 | return null; 28 | 29 | return this.pathWaypoints.get(index); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/path/PathWaypoint.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.path; 2 | 3 | import de.domisum.lib.auxilium.data.container.DataRegister; 4 | import de.domisum.lib.auxilium.data.container.math.Vector3D; 5 | 6 | public class PathWaypoint 7 | { 8 | 9 | // PROPERTIES 10 | private Vector3D position; 11 | private int transitionType; 12 | 13 | private DataRegister dataRegister = new DataRegister(); 14 | 15 | 16 | // INIT 17 | public PathWaypoint(Vector3D position, int transitionType) 18 | { 19 | this.position = position; 20 | this.transitionType = transitionType; 21 | } 22 | 23 | public String toString() 24 | { 25 | return "waypoint[position="+this.position+";transitionType="+this.transitionType+"]"; 26 | } 27 | 28 | 29 | // GETTERS 30 | public Vector3D getPosition() 31 | { 32 | return this.position; 33 | } 34 | 35 | public int getTransitionType() 36 | { 37 | return this.transitionType; 38 | } 39 | 40 | public Object getData(String key) 41 | { 42 | return this.dataRegister.get(key); 43 | } 44 | 45 | 46 | // SETTERS 47 | public void setData(String key, Object value) 48 | { 49 | this.dataRegister.set(key, value); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/path/node/BlockPathNode.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.path.node; 2 | 3 | import de.domisum.lib.compitum.path.node.weighted.WeightedNode; 4 | import org.bukkit.Location; 5 | import org.bukkit.World; 6 | 7 | public class BlockPathNode implements WeightedNode 8 | { 9 | 10 | // PROPERTIES 11 | public final int x; 12 | public final int y; 13 | public final int z; 14 | 15 | // SUCCESSOR 16 | private BlockPathNode parent; 17 | private int transitionType = 0; 18 | 19 | private double weightFromParent; 20 | private double heuristicWeight; 21 | 22 | 23 | // INIT 24 | public BlockPathNode(int x, int y, int z) 25 | { 26 | this.x = x; 27 | this.y = y; 28 | this.z = z; 29 | } 30 | 31 | @Override public boolean equals(Object other) 32 | { 33 | if(!(other instanceof BlockPathNode)) 34 | return false; 35 | 36 | BlockPathNode o = (BlockPathNode) other; 37 | 38 | return o.x == this.x && o.y == this.y && o.z == this.z; 39 | } 40 | 41 | @Override public int hashCode() 42 | { 43 | int hash = 0; 44 | hash |= (this.x%4096)<<20; // 12 bits long, in [0;11] 45 | hash |= this.y<<12; // 8 bits (2^8 = 256) long, in [12;19] 46 | hash |= (this.z%4096); // 12 bits long, in [20;31] 47 | 48 | return hash; 49 | } 50 | 51 | @Override public String toString() 52 | { 53 | return "transitionalNode[x="+this.x+",y="+this.y+",z="+this.z+"]"; 54 | } 55 | 56 | 57 | // GETTERS 58 | public BlockPathNode getParent() 59 | { 60 | return this.parent; 61 | } 62 | 63 | public int getTransitionType() 64 | { 65 | return this.transitionType; 66 | } 67 | 68 | 69 | @Override public double getGValue() 70 | { 71 | if(this.parent == null) 72 | return this.weightFromParent; 73 | 74 | return this.parent.getGValue()+this.weightFromParent; 75 | } 76 | 77 | @Override public double getHValue() 78 | { 79 | return this.heuristicWeight; 80 | } 81 | 82 | @Override public double getFValue() 83 | { 84 | return getGValue()+getHValue(); 85 | } 86 | 87 | 88 | public Location getLocation(World world) 89 | { 90 | return new Location(world, this.x, this.y, this.z); 91 | } 92 | 93 | 94 | // SETTERS 95 | public void setParent(BlockPathNode parent, int transitionType, double additionalWeight) 96 | { 97 | this.parent = parent; 98 | this.transitionType = transitionType; 99 | this.weightFromParent = additionalWeight; 100 | } 101 | 102 | public void setHeuristicWeight(double heuristicWeight) 103 | { 104 | this.heuristicWeight = heuristicWeight; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/path/node/TransitionType.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.path.node; 2 | 3 | public class TransitionType 4 | { 5 | 6 | public static final int WALK = 1; 7 | /** 8 | * This JUMP is a simple walking jump from a block below to another block above. 9 | * For a parcour jump over a hole LEAP is used. 10 | */ 11 | public static final int JUMP = 2; 12 | public static final int CLIMB = 3; 13 | public static final int FALL = 4; 14 | public static final int LEAP = 5; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/path/node/weighted/SortedWeightedNodeList.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.path.node.weighted; 2 | 3 | import de.domisum.lib.auxilium.util.java.annotations.API; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public class SortedWeightedNodeList 12 | { 13 | 14 | private List nodes; 15 | private Set nodesContainsTester; 16 | 17 | 18 | // INIT 19 | public SortedWeightedNodeList(int length) 20 | { 21 | this.nodes = new ArrayList<>(length); 22 | this.nodesContainsTester = new HashSet<>(length); 23 | } 24 | 25 | 26 | // GETTERS 27 | public T getAndRemoveFirst() 28 | { 29 | if(this.nodes.isEmpty()) 30 | return null; 31 | 32 | T firstNode = this.nodes.remove(0); 33 | this.nodesContainsTester.remove(firstNode); 34 | 35 | return firstNode; 36 | } 37 | 38 | public boolean contains(T node) 39 | { 40 | return this.nodesContainsTester.contains(node); 41 | } 42 | 43 | public int getSize() 44 | { 45 | return this.nodes.size(); 46 | } 47 | 48 | private double getValueToCompare(T node) 49 | { 50 | return node.getFValue(); 51 | } 52 | 53 | 54 | @Deprecated public List getNodes() 55 | { 56 | return this.nodes; 57 | } 58 | 59 | 60 | // CHANGERS 61 | public void addSorted(T node) 62 | { 63 | // don't subtract one from the size since the element could be added after the last current entry 64 | this.nodesContainsTester.add(node); 65 | insertIntoList(node, 0, this.nodes.size()); 66 | } 67 | 68 | private void insertIntoList(T node, int lowerBound, int upperBound) 69 | { 70 | if(lowerBound == upperBound) 71 | { 72 | this.nodes.add(lowerBound, node); 73 | return; 74 | } 75 | 76 | double nodeValue = getValueToCompare(node); 77 | 78 | int dividingIndex = (lowerBound+upperBound)/2; 79 | T dividingNode = this.nodes.get(dividingIndex); 80 | double dividingValue = getValueToCompare(dividingNode); 81 | 82 | if(nodeValue > dividingValue) 83 | insertIntoList(node, dividingIndex+1, upperBound); 84 | else 85 | insertIntoList(node, lowerBound, dividingIndex); 86 | } 87 | 88 | public void clear() 89 | { 90 | this.nodes.clear(); 91 | this.nodesContainsTester.clear(); 92 | } 93 | 94 | 95 | @API public void sort() 96 | { 97 | Collections.sort(this.nodes, (n1, n2)->getValueToCompare(n1) > getValueToCompare(n2) ? 1 : -1); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/de/domisum/lib/compitum/path/node/weighted/WeightedNode.java: -------------------------------------------------------------------------------- 1 | package de.domisum.lib.compitum.path.node.weighted; 2 | 3 | public interface WeightedNode 4 | { 5 | 6 | // GETTERS 7 | double getGValue(); 8 | 9 | double getHValue(); 10 | 11 | double getFValue(); 12 | 13 | } 14 | --------------------------------------------------------------------------------