Skip to content

Commit

Permalink
Merge pull request #101 from patheloper/trunk
Browse files Browse the repository at this point in the history
[pathetic 3.1.1] improved heuristic, fixed abort state, removed counterCheck
  • Loading branch information
Metaphoriker authored Oct 1, 2024
2 parents 1c4a66c + 53da8fe commit 32373dc
Show file tree
Hide file tree
Showing 21 changed files with 275 additions and 158 deletions.
239 changes: 171 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,82 +1,185 @@
![pathetic](https://github.com/patheloper/pathetic/assets/50031457/2af0e918-dd57-48aa-b8e1-87356271ac1d)
# ![Pathetic](https://github.com/patheloper/pathetic/assets/50031457/2af0e918-dd57-48aa-b8e1-87356271ac1d)

# Pathetic
# Pathetic - A Pathfinding library for Minecraft

Pathetic is a simple and intuitive backwards-compatible up-to-date pathfinding API for Spigot, Paper and forks.
See more info here: https://www.spigotmc.org/threads/how-pathetic.578998/#post-4644823
**Pathetic** is a high-performance, backwards-compatible, and asynchronous pathfinding library designed for **Spigot**,
**Paper**, and their forks. Pathetic leverages the **A*** algorithm with customizable heuristics for real-time
pathfinding in Minecraft server environments.

### How to import
Pathetic excels in handling complex terrains with features such as diagonal movement, vertical pathing, and user-defined
filters for greater flexibility.

## Key Features

- **Advanced A\* Algorithm**: Employs multiple distance metrics (Manhattan, Octile, Perpendicular) and height
differences
for pathfinding, optimized for 3D worlds like Minecraft.
- **Asynchronous Pathfinding**: Non-blocking operations using `CompletableFuture` to minimize server impact during
pathfinding.
- **Fibonacci Heap for Efficient Queuing**: The open set (frontier) is managed using a **Fibonacci heap**, ensuring
optimal node retrieval with faster `insert` and `extract min` operations.
- **Customizable Heuristics**: Fine-tune pathfinding behavior using `HeuristicWeights` for balanced navigation in any
world configuration.
- **Regional Grid Optimization**: Uses `ExpiringHashMap` and **Bloom filters** to efficiently track explored regions,
minimizing memory overhead.
- **Dynamic Path Filters**: Define custom filters to modify node validity or prioritize paths based on criteria such as
passability, block type, or world boundaries.

## Installation

### Maven

#### Maven
```xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>

<dependency>
<groupId>com.github.patheloper.pathetic</groupId>
<artifactId>pathetic-mapping</artifactId>
<version>VERSION</version>
</dependency>

<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>

<dependency>
<groupId>com.github.patheloper.pathetic</groupId>
<artifactId>pathetic-mapping</artifactId>
<version>VERSION</version>
</dependency>
```

#### Gradle
```xml
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.patheloper.pathetic:pathetic-mapping:VERSION'
}
### Gradle

```groovy
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.patheloper.pathetic:pathetic-mapping:VERSION'
}
```

### Example API Usage
## Advanced Usage: Filtering and Prioritizing Paths

Here’s how to set up and use Pathetic to find paths between random points in a Minecraft world:

```java
public class PathExample extends JavaPlugin {

@Override
public void onEnable() {
PatheticMapper.initialize(this);
goFindSomePath(randomLocation(), randomLocation());
}

@Override
public void onDisable() {
PatheticMapper.shutdown();
}

private void goFindSomePath(PathPosition start, PathPosition end) {
Pathfinder pathfinder = PatheticMapper.newPathfinder();
pathfinder
.findPath(start, end, List.of(new PassablePathFilter()))
.thenAccept(
pathfinderResult ->
pathfinderResult
.getPath()
.forEach(
location ->
player.sendBlockChange(
location, Material.YELLOW_STAINED_GLASS.createBlockData())));
}

private PathPosition randomLocation() {
ThreadLocalRandom instance = ThreadLocalRandom.current();
return new PathPosition(
instance.nextInt(0, 100), instance.nextInt(0, 100), instance.nextInt(0, 100));
}
}
public class AdvancedPathExample extends JavaPlugin {

@Override
public void onEnable() {
PatheticMapper.initialize(this);
findOptimizedPath(randomLocation(), randomLocation());
}

@Override
public void onDisable() {
PatheticMapper.shutdown();
}

private void findOptimizedPath(PathPosition start, PathPosition end) {
Pathfinder pathfinder = PatheticMapper.newPathfinder();
List<PathFilter> filters = List.of(new PassablePathFilter(), new CustomHeightFilter());
List<PathFilterStage> filterStages = List.of(new EarlyExitFilterStage());

pathfinder
.findPath(start, end, filters, filterStages)
.thenAccept(
pathfinderResult -> {
if (pathfinderResult.getPathState() == PathState.FOUND) {
pathfinderResult
.getPath()
.forEach(
location ->
player.sendBlockChange(
location, Material.GOLD_BLOCK.createBlockData()));
} else {
getLogger().info("Pathfinding failed or exceeded limits.");
}
});
}

private PathPosition randomLocation() {
ThreadLocalRandom random = ThreadLocalRandom.current();
return new PathPosition(random.nextInt(0, 100), random.nextInt(0, 100), random.nextInt(0, 100));
}
}
```

#### See the `pathetic-example` module for a more in-depth example plugin.
## Technical Overview

### A* Pathfinding

Pathetic uses a robust implementation of the **A*** algorithm, tailored for Minecraft's 3D world and large-scale
terrains. Key technical highlights include:

- **Heuristic Metrics**:
- Combines multiple metrics—**Manhattan**, **Octile**, **Perpendicular**, and **Height Differences**—for a
comprehensive cost evaluation.
- These metrics are dynamically weighted using `HeuristicWeights`, allowing precise tuning for the environment and
movement constraints in Minecraft.

- **Fibonacci Heap**:
- Pathetic employs a **Fibonacci Heap** for managing the frontier (open set), optimizing node insertion and
retrieval with amortized O(1) insertion and O(log n) extraction times.
- This significantly improves performance when handling large grids with numerous nodes, reducing overhead compared
to traditional binary heaps.

- **Regional Grid Optimization**:
- **ExpiringHashMap** and **Bloom Filters** are used to manage explored nodes efficiently.
- This allows Pathetic to minimize memory usage by clearing out unnecessary or redundant node data while still
preventing revisits to already-explored nodes.

- **Customizable Heuristics**:
- Fine-tune pathfinding behavior by adjusting the weights for different distances:
- **Manhattan Weight**: Prioritizes straight-line paths on grids.
- **Octile Weight**: Best suited for paths allowing diagonal movement.
- **Perpendicular Weight**: Avoids sharp turns by encouraging smoother paths.
- **Height Weight**: Penalizes paths with large vertical movements.

### Asynchronous Pathfinding

Pathetic runs pathfinding operations asynchronously via `CompletableFuture`. This ensures that pathfinding does not
block the server's main thread, allowing smooth gameplay even in complex or large pathfinding operations.

- Asynchronous execution is particularly useful for handling real-time pathfinding in busy environments without
impacting server performance.
- Results can be processed or displayed upon completion, providing a non-blocking experience for the user.

### Dynamic Path Filters

Pathetic allows for flexible pathfinding with **custom filters** that can be applied at various stages of the
pathfinding process. For example:

- Filters like `PassablePathFilter` ensure nodes are only considered if they meet specific conditions (e.g., passability
of the terrain).
- **Filter Stages**: Allow multi-stage processing, adjusting node evaluation dynamically based on the current
pathfinding state.

### Node Prioritization and Filtering

- Pathetic dynamically adjusts node prioritization based on **filter stages** and environmental context, ensuring
optimal paths are found based on the criteria set by developers.
- Nodes that pass through filter stages can receive a priority boost, enabling more intelligent pathing decisions based
on the environment or specific goals.

### Documentation:

- **Javadocs**: [View Javadocs](https://javadocs.pathetic.ollieee.xyz/)
- **API Documentation**: [Access our Docs](https://docs.pathetic.ollieee.xyz/)

### License:

Pathetic is released under the GPL License.

### Contributions:

We welcome contributions! Feel free to fork the repository and submit pull requests. For major changes, open an issue
first to discuss what you’d like to change.

### Support:

For help and support, join our community on
the [SpigotMC forum thread](https://www.spigotmc.org/threads/how-pathetic.578998/)
or [Discord Server](https://discord.gg/HMqCbdQjX9).

### Docs:
Access the Javadocs [here](https://javadocs.pathetic.ollieee.xyz/)
Access our Docs [here](https://docs.pathetic.ollieee.xyz/)
4 changes: 2 additions & 2 deletions pathetic-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<parent>
<artifactId>pathetic-main</artifactId>
<groupId>org.patheloper</groupId>
<version>3.1</version>
<version>3.1.1</version>
</parent>

<artifactId>pathetic-api</artifactId>
<version>3.1</version>
<version>3.1.1</version>

<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,38 @@
import com.google.common.eventbus.EventBus;
import lombok.experimental.UtilityClass;

/**
* Utility class for publishing and managing events using the Google Guava EventBus. This class
* provides methods to raise events and register/unregister event listeners.
*/
@UtilityClass
public class EventPublisher {

private static final EventBus eventBus = new EventBus();

/**
* Raises an event by posting it to the EventBus.
*
* @param pathingEvent the event to be raised
*/
public static void raiseEvent(PathingEvent pathingEvent) {
eventBus.post(pathingEvent);
}

/**
* Registers an event listener with the EventBus.
*
* @param listener the listener to be registered
*/
public static void registerListener(Object listener) {
eventBus.register(listener);
}

/**
* Unregisters an event listener from the EventBus.
*
* @param listener the listener to be unregistered
*/
public static void unregisterListener(Object listener) {
eventBus.unregister(listener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,6 @@ public class PathfinderConfiguration {
*/
boolean loadingChunks;

/**
* If pathfinding fails, determines whether to run a reverse pathfinding check (from target to
* start) to verify the result. This is a computationally expensive fallback but can help identify
* some failure cases.
*
* @deprecated This feature is deprecated and may be removed in a future release.
*/
@Deprecated
boolean counterCheck;

/**
* Determines whether the pathfinding algorithm should see PathFilterStages as prioritization,
* instead of filtering. This means that the pathfinding algorithm will prioritize paths that pass
Expand Down Expand Up @@ -138,7 +128,6 @@ public static PathfinderConfiguration deepCopy(PathfinderConfiguration pathfinde
.allowingFailFast(pathfinderConfiguration.allowingFailFast)
.allowingFallback(pathfinderConfiguration.allowingFallback)
.loadingChunks(pathfinderConfiguration.loadingChunks)
.counterCheck(pathfinderConfiguration.counterCheck)
.heuristicWeights(pathfinderConfiguration.heuristicWeights)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,39 @@

import lombok.experimental.UtilityClass;

/** Utility class for common number operations. */
@UtilityClass
public final class NumberUtils {

/**
* Interpolates between two values based on the given progress.
*
* @param a the start value
* @param b the end value
* @param progress the interpolation progress (0.0 to 1.0)
* @return the interpolated value
*/
public static double interpolate(double a, double b, double progress) {
return a + (b - a) * progress;
}

/**
* Squares the given value.
*
* @param value the value to be squared
* @return the squared value
*/
public static double square(double value) {
return value * value;
}

/**
* Computes the square root of the given value using an approximation method.
*
* @param input the value to compute the square root of
* @return the approximated square root
*/
public static double sqrt(double input) {

double sqrt =
Double.longBitsToDouble((Double.doubleToLongBits(input) - (1L << 52) >> 1) + (1L << 61));
double better = (sqrt + input / sqrt) / 2.0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ public class PathPosition implements Cloneable {
private double y;
private double z;

/**
* Interpolates between two positions based on the given progress.
*
* @param other The other position to interpolate with
* @param progress The interpolation progress (0.0 to 1.0)
* @return The interpolated position
*/
public PathPosition interpolate(PathPosition other, double progress) {
double x = NumberUtils.interpolate(this.x, other.x, progress);
double y = NumberUtils.interpolate(this.y, other.y, progress);
Expand Down
Loading

0 comments on commit 32373dc

Please sign in to comment.