GithubHelp home page GithubHelp logo

terasology-archived / pathfinding Goto Github PK

View Code? Open in Web Editor NEW
6.0 20.0 18.0 936 KB

A pathfinding module mainly meant as a library/framework for other modules

Java 100.00%
voxel game gamedev terasology java game-development terasology-module

pathfinding's Introduction

https://cacoo.com/diagrams/huo1Fw2Xvwfy6q1N

Terasology Work module

Work in terms of this document is a process that a minion can operate on. Every Work is designated to a target. This may be any entity (normal or block entity). This is done using the WorkTargetComponent - entities having this component are targets of some work to do (for example, kill or build block).

To start operating, several preconditions must be fulfilled (most commonly this is the exact positions to operate). The definition of those preconditions and the process itself is done in a WorkType.

A WorkType is a component system which registers itself to the WorkFactory. So, modules can bring their own work type implementations into the system.

The central system of the work module is the WorkBoard. This systems manages all the work.

Internally a hierarchical k-means clustering algorithm is used to build a tree of clusters, to minimize the search space when looking for work for a minion. The WorkBoard hides this complexity - you simply request for work described by optional filters. Once work becomes available, you get informed through a callback.

For now, there are 4 WorkTypes:

  • BuildBlock - go to a target and place a block
  • RemoveBlock - go to a target and remove a block
  • WalkToBlock - just go to a target and do nothing
  • AttackMinionSystem - spawns 2 additional work types, one for each LightAndShadow team. Also tags entities with the WorkTargetComponent and a attack work type of the opposing team side. (WIP)

Example Work

@RegisterSystem
public class WalkToBlock implements Work, ComponentSystem {
    private static final Logger logger = LoggerFactory.getLogger(WalkToBlock.class);

    private final SimpleUri uri;
    @In
    private PathfinderSystem pathfinderSystem;
    @In
    private WorkFactory workFactory;

    public WalkToBlock() {
        uri = new SimpleUri("Pathfinding:walkToBlock");
    }

    @Override
    public void initialise() {
        // register this work type to the factory
        workFactory.register(this);
    }

    @Override
    public void shutdown() {
    }

    @Override
    public SimpleUri getUri() {
        return uri;
    }

    public List getTargetPositions(EntityRef block) {
        // returns a list of valid blocks to operate on block
        return ...;
    }

    @Override
    public boolean canMinionWork(EntityRef block, EntityRef minion) {
        // return true if minion can work on block, right now
        return ...;
    }

    @Override
    public boolean isAssignable(EntityRef block) {
        // return true if block is valid target. only valid block
        // will be considered when work is searched
        return ...;
    }

    @Override
    public void letMinionWork(EntityRef block, EntityRef minion) {
        // do the actual work
        block.removeComponent(WorkTargetComponent.class);
    }

    @Override
    public boolean isRequestable(EntityRef block) {
        // return true is block can be worked on right now (all preconditions are true)
        return ...;
    }

    @Override
    public float cooldownTime() {
        // returns the seconds it takes to finish working
        return 0;
    }

    @Override
    public String toString() {
        return "Walk To Block";
    }
}

Behavior nodes

FindWork

Properties: filter

Searches for open work of specific type (filter). If work is found, the actor is assigned.

SUCCESS: When work is found and assigned.

FinishWork Decorator

Does the actual work, once the actor is in range. The child node is started.

SUCCESS: when work is done (depends on work type). FAILURE: if no work is assigned or target is not reachable.

SetTargetToWork

Set MinionMoveComponent's target to the work's target.

SUCCESS: if valid work target position found. FAILURE: otherwise

Terasology Move module

Behavior nodes

FindPathTo

Requests a path to a target defined using the MinionMoveComponent.target.

SUCCESS / FAILURE: when paths is found or not found (invalid). RUNNING: as long as path is searched.

SetTargetToNearbyBlockNode

Sets the target to a random nearby block.

SUCCESS: when a target has been chosen. FAILURE: when no target could be chosen.

MoveAlongPath Decorator

Call child node, as long as the actor has not reached the end of the path. Sets MinionMoveComponent.target to next step in path.

SUCCESS: when actor has reached end of path. FAILURE: if no path was found previously.

SetTargetLocalPlayer

Set MinionMoveComponent.target to the block below local player.

Always returns SUCCESS.

MoveTo

Properties: distance

Moves the actor to the target defined by MinionMoveComponent.

SUCCESS: when distance between actor and target is below distance. FAILURE: when there is no target.

Jump

Trigger a single jump into the air.

SUCCESS: when the actor is grounded after the jump again.

Terasology Pathfinding module

Systems

PathfinderSystem

Events: PathReadyEvent

A system to find paths through the map. Calculations that may take some time are done in background (preprocessing chunks and finding path).

Once a path for a request is found, a PathReadyEvent is sent back to the entity.

If there is a block change, all paths and all nav data associated with the changed chunk is invalidated. If a block change occurs when there are pending requests the chunk update is processed before any path request.

The nav data (WalkableBlock and Floor) is only modified in the background and never, when a path is calculated.

Whenever you operate with nav data, keep in mind that the current instance of a WalkableBlock may not be up to date. Always use PathfinderSystem.getBlock() to get the latest block.

Introduction

The pathfinder system is an attempt to implement a fast and scalable pathfinding algorithm, ready to use to find the shortest path between two given blocks in Terasology.

This system is split into two parts. The first part are the algorithms to preprocess chunks and finding the path itself. The second part forms the API and encapsulate the calculations into a background thread.

This is all about the algorithm.

Finding paths in general

The probably first and best algorithm to find shortest paths in a graph is the A* (called "A Star"). A* is a concretization of the Dijkstra's algorithm.

The goal of dijkstra is finding the shortest path between a start and a target node. The idea is, to start at the target node and visit each neighbor of the target. The exact costs to walk to that neighbor node is calculated and stored including the current node at that neighbor node. Once the start node is visited, we are able to reconstruct the actual shortest path, by walking backwards to the target node (thats why predecessors are stored...). A* works exactly the same, but modifies the order in which neighbor nodes are visited, so that the finding need much fewer iterations.

Finding paths in 3D

A* works wonderful if the number of nodes to visit is known and not that big. In 3D voxel worlds like Terasology, the number of nodes is not known and really big (One chunk: 16x16x256=65536).

Of course, most of those 64k blocks are useless for finding a path, where a player can walk on. The interesting blocks are those, who are solid and have at least two air blocks above. Now, even if the chunk is totally flat and there are no holes, we have 16x16=256 blocks (per chunk!) to consider when path finding. Still too much.

Preprocessing the world

If we consider the example from above and we have three chunks (A, B and C; each flat, no holes) and we want a path from chunk A to chunk B we could decrease the number of interesting blocks in this way:

  • all blocks of A and B are needed (chunk with start and target block)
  • for chunk B, only one ("virtual") node is needed (only if there is a path between any block of A and any block of C (through B))

When finding a path in such a reduced setup, we search a path from the start block to the "virtual" node (that represents the complete chunk B) and a path from the virtual node to the target block (chunk C).

Instead of 3x16x16=768 nodes, we only need 2x16x16+1=513 nodes (the longer the path, the bigger the impact). Of course, with this optimization we dont find the shortest path anymore. We find a good approximation in shorter time.

Sweeps, Regions, Floors, Chunks

Now, we dont have a flat world and of course we have holes and big mining constructions. Additionally, our world may change (often!), so this precalculation needs to be fast. It is done in several steps:

  1. Find all walkable blocks by scanning through all blocks of the current chunk (block must be not penetrable with 2 air blocks above)
  2. Build a graph of all walkable blocks (find neighbors)
  3. Find sweeps of walkable blocks (blocks lined up next to each other, without holes or corners)
  4. Combine sweeps next to each other into regions
  5. Build a graph of all regions (find neighbors)
  6. Combine regions next to each other and are not overlapping (different heights) into floors. Notice: There exists a path from each block to each other block of this floor.
  7. Build a graph of all floors (find neighbors)

After this steps we have a graph of floors. Each floor contains a graph of regions. Each region contains a graph of walkable blocks.

Hierarchical A*

Hierarchical A* works like traveling by plane. You first use the train to reach the local airport. Then you fly to the next bigger airport. Next is oversea. Then to local airport where you take the taxi.

With our multilevel graph we do the same thing. As for A* we start at the target node. First we consider all local neighbors for this node. Next we check, if we are currently at a border to the next region (or floor). If so, we can reach any other border block of the neighboring region (or floor), so we can "fly" to those border blocks directly.

The actual paths from border block to border block can be precalculated (or at least be cached). We may not consider ALL border blocks of a region/floor. Current implementation uses corners only.

Theta*

The Theta* algorithm slightly modifies the default A*, in a way to produce more natural paths.

Instead of having every single step in a path, only the the endpoints of lines of sight are stored in a path (basically only the points, where the minion need to turn).

There are two implementations of the line of sight algorithm. One is crawling through walkable block using a 2d bresenham algorithm (recommended). The other uses a 3d bresenham with the real world blocks.

Related

Terasology Behavior Tree

Behavior trees form an API to everything AI/behavior related. They consist of nodes ordered in a strong hierarchical form.

Nodes run isolated code to either read or write values from/to the game world. Each behavior tree can be run by multiple Actors. To keep the internal state of the tree for each actor, nodes are not run directly. Instead for each node to run by an Actor, the node creates a Task.

Tasks are scheduled in a way that they can inform parent Tasks using an observer pattern. The management of the state is maintained by an Interpreter.

The Interpreter keeps a list of active Tasks. Tasks are considered active, when the currently return RUNNING state. Each tick, the update methods is called for all active tasks.

Nodes are identified using behavior prefabs. There some general settings may be applied.

Example Node and Task

import org.terasology.engine.rendering.nui.properties.Range;

public class TimerNode extends DecoratorNode {
    @Range(min = 0, max = 20)
    private float time;  // this becomes a slider in the property editor for this node

    @Override
    public Task createTask() {
        // tasks are injected, so using @In is possible in the task class
        return new TimerTask(this);
    }

    public static class TimerTask extends DecoratorTask {
        private float remainingTime; // store state for this task

        public TimerTask(Node node) {
            super(node);
        }

        @Override
        public void onInitialize() {
            // is called once, directly before the first update() call

            remainingTime = getNode().time; // init state
            start(getNode().child); // start the associated child, if any
        }

        @Override
        public Status update(float dt) {
            // is called in each tick of the behavior tree, if this task remains active

            remainingTime -= dt;
            if (remainingTime <= 0) {
                // once the time is up, this node stops with FAILURE
                return Status.FAILURE;
            }
            return Status.RUNNING; // remain active otherwise
        }

        @Override
        public void handle(Status result) {
            // is called when the state of the started child node changes to SUCCESS or FAILURE

            stop(result); // stop this task and all task, that were started within the task
        }

        @Override
        public TimerNode getNode() {
            return (TimerNode) super.getNode();
        }
    }
}

Example node prefab

{
    "BehaviorNode" : {
        "type"      : "TimerNode",
        "name"      : "Timer",
        "category"  : "logic",
        "shape"     : "rect",
        "description": "Decorator\nStarts the decorated node.\nSUCCESS: as soon as decorated node finishes with SUCCESS.\nFAILURE: after x seconds.",
        "color"     : [180, 180, 180, 255],
        "textColor" : [0, 0, 0, 255]
    }
}

Behavior nodes

Counter Decorator

Starts child a limit number of times.

SUCCESS: when child finished with SUCCESSn times. FAILURE: as soon as child finishes with FAILURE.

Inverter Decorator

Inverts the child.

SUCCESS: when child finishes FAILURE. FAILURE: when child finishes SUCCESS.

Lookup Decorator

Node that runs a behavior tree.

SUCCESS: when tree finishes with SUCCESS. FAILURE: when tree finishes with FAILURE.

Monitor Parallel

SUCCESS: as soon as one child node finishes SUCCESS FAILURE: as soon as one child node finishes FAILURE.

Parallel Composite

All children are evaluated in parallel. Policies for success and failure will define when this node finishes and in which state.

SUCCESS: when success policy is fulfilled (one or all children SUCCESS). FAILURE, when failure policy is fulfilled (one or all children FAILURE).

Repeat Decorator

Repeats the child node forever.

SUCCESS: Never. FAILURE: as soon as decorated node finishes with FAILURE.

Selector Composite

Evaluates the children one by one. Starts next child, if previous child finishes with FAILURE.

SUCCESS: as soon as a child finishes SUCCESS. FAILURE: when all children finished with FAILURE.

Sequence Composite

Evaluates the children one by one. Starts next child, if previous child finishes with SUCCESS.

SUCCESS: when all children finishes SUCCESS. FAILURE: as soon as a child finished with FAILURE.

Timer Decorator

Starts the decorated node.

SUCCESS: as soon as decorated node finishes with SUCCESS. FAILURE: after x seconds.

Wrapper Decorator

Always finishes with SUCCESS.

PlaySound

Properties: sound, volume

RUNNING: while sound is playing SUCCESS: once sound ends playing FAILURE: otherwise

PlayMusic

Properties: music

RUNNING: while music is playing SUCCESS: once music ends playing FAILURE: otherwise

pathfinding's People

Contributors

4denthusiast avatar adrijaned avatar agent-q1 avatar andytechguy avatar arpan98 avatar begla avatar cervator avatar darkweird avatar dkambersky avatar flo avatar gooeyhub avatar jdrueckert avatar josharias avatar keturn avatar mkienenb avatar msteiger avatar naman-sopho avatar nihal111 avatar nschnitzer avatar oniatus avatar pollend avatar qwertygiy avatar sanidhyaanand avatar skaldarnar avatar synopia avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pathfinding's Issues

replace HeadlessEnvironment tests with MTE

The tests that use HeadlessEnvironment are failing. (due to some changes made during the migration to gestalt-module v7)

Rather than fix HeadlessEnvironment, I recommend basing these tests on ModuleTestingEnvironment instead, so we don't have to maintain multiple testing-environment implementations.

Fix unit tests

Currently, 38 out of 40 tests fail with

java.lang.RuntimeException: Failed to find natives from .jar launch
at org.terasology.engine.paths.PathManager.<init>(PathManager.java:70)
at org.terasology.engine.paths.PathManager.getInstance(PathManager.java:121)
at org.terasology.HeadlessEnvironment.setupPathManager(HeadlessEnvironment.java:218)
at org.terasology.Environment.reset(Environment.java:53)

I suspect that this is related to the config change in build 105.

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/3478401-fix-unit-tests?utm_campaign=plugin&utm_content=tracker%2F4847892&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F4847892&utm_medium=issues&utm_source=github).

Buggy Character Movement Nodes

Buggy Character Movement

Even though pathfinding has had improvements, and seems fairly stable at the point, the main issue with in game pathfinding, is actually moving along the path. Even fundamental nodes such as the MoveToAction node that pretty much all pathfinding behaviors use/depend on seem buggy. These lead to a plethora of quirks in game well described by Terasology/Behaviors#20

Vaguely remember kaen and I discussing how the prediction systems and delta values might having something to do with the glitch, so the Character Prediction System should be a good starting point. Could also have something to do with CPU usage.
There could also be a bug in kinematic character, specifically with the code dealing with extrapolation leading to all kinds of funny behavior.

Replace usage of com.sun.xml.internal.ws.util.ByteArrayBuffer

Woo first ever module repo bug report! :D

While fixing the Jenkins build for Pathfinding (wrong source job to grab artifacts from + older Java 7u21 didn't support static imports right in one case) I ran into the use of com.sun.xml.internal.ws.util.ByteArrayBuffer in FactoryTest.java which actually causes a failure to run tests locally for me and in Jenkins

Looks to be a good practice to avoid the com.sun packages. There's probably a simple replacement?

http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html

Hoping the build will work after that.

I deleted the unique build.gradle here in favor of updating the template as Jenkins overwrites that version at compile-time anyway. Rather than introduce a unique one just update the template under modules/Core and do a "gradlew refresh"

Note that pulling the latest from Pathfinding with the existing unique build.gradle locally may cause the module to be ignored by Gradle (because it looks for a build.gradle to declare the module valid - but right after the deletion there would be none, so an updated copy isn't copied in!).

Just manually copy build.gradle from modules/Core this one time and it should work. Can even run "gradlew refresh" immediately after to see that work if you like :-)

Import some old documentation if applicable

Cleaning up the engine wiki. It had a page on behavior trees, which are somewhat in flux - unsure if doc should go in Pathfinding or Behaviors, so making issues for both and linking them. Duped by: Terasology/Behaviors#2 - pinging @synopia :-)

Original doc (make into Javadoc, readme file, or wiki elsewhere?):

TODO, copied from pathfinding module.

Nodes

Nodes are the core elements of a behavior tree. A node is a piece of code, that is run while an Actor is interpreting the behavior tree.

When run, every node must return one of this states: SUCCESS, FAILURE, RUNNING.

SUCCESS and FAILURE are returned, depending on the behavior node's task. If the task takes some time, a node may return RUNNING.

Decorators & Composites

Nodes may have child nodes assigned.
If a node accepts exactly one child, this node is called a Decorator. Such nodes add some functionality to another node, or subtree.
If a node accepts many children, this node is called a Composite. The order of the children is important, since a composite delegates execution to one of its children.
The three basic composite nodes are:

  • Sequence - All children run after each other. Failing child will fail the sequence.
  • Selector - All children run after each other. Succeeding child will succeed the selector.
  • Parallel - All children run in parallel. When the parallel finishes, depends on its policies.
  • Monitor - All children are executed in parallel. As soon as any child succeeds or fails, the monitor succeed or fail.

Some basic decorators are:

  • Invert - Inverts the child's state.
  • Timer - Stops time and finishes with given state after defined duration.
  • Repeat - Repeats its decorated behavior forever.
  • Delay - Delays the execution of its decorated node some time.

Actor and interpreter

A behavior tree can be assigned to be interpreted by several different minions. So, for each actor the tree has a unique state (state of timers, sequences, etc). This state is realized using a Task class. Instead of handling the actual work itself, a Node delegates its work to a Task class. So a node just create a Task for every actor, where the custom state data can be stored safely.

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/30164469-import-some-old-documentation-if-applicable?utm_campaign=plugin&utm_content=tracker%2F4847892&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F4847892&utm_medium=issues&utm_source=github).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.