com.l2jfree.gameserver.geodata.pathfinding.PathFinding.java Source code

Java tutorial

Introduction

Here is the source code for com.l2jfree.gameserver.geodata.pathfinding.PathFinding.java

Source

/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.l2jfree.gameserver.geodata.pathfinding;

import java.util.ArrayList;

import org.apache.commons.lang3.ArrayUtils;

import com.l2jfree.Config;
import com.l2jfree.gameserver.geodata.GeoData;
import com.l2jfree.gameserver.geodata.pathfinding.cellnodes.CellPathFinding;
import com.l2jfree.gameserver.geodata.pathfinding.geonodes.GeoPathFinding;
import com.l2jfree.gameserver.geodata.pathfinding.utils.BinaryNodeHeap;
import com.l2jfree.gameserver.geodata.pathfinding.utils.CellNodeMap;
import com.l2jfree.gameserver.model.world.L2World;
import com.l2jfree.util.L2Arrays;
import com.l2jfree.util.L2Collections;
import com.l2jfree.util.L2FastSet;

/**
 * @author -Nemesiss-
 */
public abstract class PathFinding {
    public static PathFinding getInstance() {
        if (!Config.GEODATA_CELLFINDING)
            return GeoPathFinding.getInstance(); //Higher Memory Usage, Smaller Cpu Usage
        else
            return CellPathFinding.getInstance(); // Cell pathfinding, calculated directly from geodata files
    }

    public abstract Node[] findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId);

    public abstract Node[] readNeighbors(Node n, int idx, int instanceId);

    public final Node[] search(Node start, Node end, int instanceId) {
        // The simplest grid-based pathfinding.
        // Drawback is not having higher cost for diagonal movement (means funny routes)
        // Could be optimized e.g. not to calculate backwards as far as forwards.

        // List of Visited Nodes
        L2FastSet<Node> visited = L2Collections.newL2FastSet();

        // List of Nodes to Visit
        L2FastSet<Node> to_visit = L2Collections.newL2FastSet();
        to_visit.add(start);
        try {
            int i = 0;
            while (i < 800) {
                if (to_visit.isEmpty()) {
                    // No Path found
                    return null;
                }

                Node node = to_visit.removeFirst();

                if (node.equals(end)) //path found!
                    return constructPath(node);
                else {
                    i++;
                    visited.add(node);
                    node.attachNeighbors(instanceId);
                    Node[] neighbors = node.getNeighbors();
                    if (neighbors == null)
                        continue;
                    for (Node n : neighbors) {
                        if (!visited.contains(n) && !to_visit.contains(n)) {
                            n.setParent(node);
                            to_visit.add(n);
                        }
                    }
                }
            }
            //No Path found
            return null;
        } finally {
            L2Collections.recycle(visited);
            L2Collections.recycle(to_visit);
        }
    }

    public final Node[] searchByClosest(Node start, Node end, int instanceId) {
        // Note: This is the version for cell-based calculation, harder
        // on cpu than from block-based pathnode files. However produces better routes.

        // Always continues checking from the closest to target non-blocked
        // node from to_visit list. There's extra length in path if needed
        // to go backwards/sideways but when moving generally forwards, this is extra fast
        // and accurate. And can reach insane distances (try it with 8000 nodes..).
        // Minimum required node count would be around 300-400.
        // Generally returns a bit (only a bit) more intelligent looking routes than
        // the basic version. Not a true distance image (which would increase CPU
        // load) level of intelligence though.

        // List of Visited Nodes
        CellNodeMap known = CellNodeMap.newInstance();
        // List of Nodes to Visit
        ArrayList<Node> to_visit = L2Collections.newArrayList();
        to_visit.add(start);
        known.add(start);
        try {
            int targetx = end.getNodeX();
            int targety = end.getNodeY();
            int targetz = end.getZ();

            int dx, dy, dz;
            boolean added;
            int i = 0;
            while (i < 3500) {
                if (to_visit.isEmpty()) {
                    // No Path found
                    return null;
                }

                Node node = to_visit.remove(0);

                i++;

                node.attachNeighbors(instanceId);
                if (node.equals(end)) {
                    //path found! note that node z coordinate is updated only in attach
                    //to improve performance (alternative: much more checks)
                    //System.out.println("path found, i:"+i);
                    return constructPath(node);
                }

                Node[] neighbors = node.getNeighbors();
                if (neighbors == null)
                    continue;
                for (Node n : neighbors) {
                    if (!known.contains(n)) {

                        added = false;
                        n.setParent(node);
                        dx = targetx - n.getNodeX();
                        dy = targety - n.getNodeY();
                        dz = targetz - n.getZ();
                        n.setCost(dx * dx + dy * dy + dz / 2 * dz/*+n.getCost()*/);
                        for (int index = 0; index < to_visit.size(); index++) {
                            // supposed to find it quite early..
                            if (to_visit.get(index).getCost() > n.getCost()) {
                                to_visit.add(index, n);
                                added = true;
                                break;
                            }
                        }
                        if (!added)
                            to_visit.add(n);
                        known.add(n);
                    }
                }
            }
            //No Path found
            //System.out.println("no path found");
            return null;
        } finally {
            CellNodeMap.recycle(known);
            L2Collections.recycle(to_visit);
        }
    }

    public final Node[] searchByClosest2(Node start, Node end, int instanceId) {
        // Always continues checking from the closest to target non-blocked
        // node from to_visit list. There's extra length in path if needed
        // to go backwards/sideways but when moving generally forwards, this is extra fast
        // and accurate. And can reach insane distances (try it with 800 nodes..).
        // Minimum required node count would be around 300-400.
        // Generally returns a bit (only a bit) more intelligent looking routes than
        // the basic version. Not a true distance image (which would increase CPU
        // load) level of intelligence though.

        // List of Visited Nodes
        L2FastSet<Node> visited = L2Collections.newL2FastSet();
        // List of Nodes to Visit
        ArrayList<Node> to_visit = L2Collections.newArrayList();
        to_visit.add(start);
        try {
            int targetx = end.getNodeX();
            int targety = end.getNodeY();
            int dx, dy;
            boolean added;
            int i = 0;
            while (i < 550) {
                if (to_visit.isEmpty()) {
                    // No Path found
                    return null;
                }

                Node node = to_visit.remove(0);

                if (node.equals(end)) //path found!
                    return constructPath2(node);
                else {
                    i++;
                    visited.add(node);
                    node.attachNeighbors(instanceId);
                    Node[] neighbors = node.getNeighbors();
                    if (neighbors == null)
                        continue;
                    for (Node n : neighbors) {
                        if (!visited.contains(n) && !to_visit.contains(n)) {
                            added = false;
                            n.setParent(node);
                            dx = targetx - n.getNodeX();
                            dy = targety - n.getNodeY();
                            n.setCost(dx * dx + dy * dy);
                            for (int index = 0; index < to_visit.size(); index++) {
                                // supposed to find it quite early..
                                if (to_visit.get(index).getCost() > n.getCost()) {
                                    to_visit.add(index, n);
                                    added = true;
                                    break;
                                }
                            }
                            if (!added)
                                to_visit.add(n);
                        }
                    }
                }
            }
            //No Path found
            return null;
        } finally {
            L2Collections.recycle(visited);
            L2Collections.recycle(to_visit);
        }
    }

    public final Node[] searchAStar(Node start, Node end, int instanceId) {
        // Not operational yet?
        int start_x = start.getX();
        int start_y = start.getY();
        int end_x = end.getX();
        int end_y = end.getY();
        //List of Visited Nodes
        L2FastSet<Node> visited = L2Collections.newL2FastSet();//TODO! Add limit to cfg

        // List of Nodes to Visit
        BinaryNodeHeap to_visit = BinaryNodeHeap.newInstance();
        to_visit.add(start);
        try {
            int i = 0;
            while (i < 800)//TODO! Add limit to cfg
            {
                if (to_visit.isEmpty()) {
                    // No Path found
                    return null;
                }

                Node node;
                try {
                    node = to_visit.removeFirst();
                } catch (Exception e) {
                    // No Path found
                    return null;
                }
                if (node.equals(end)) //path found!
                    return constructPath(node);
                else {
                    visited.add(node);
                    node.attachNeighbors(instanceId);
                    for (Node n : node.getNeighbors()) {
                        if (!visited.contains(n) && !to_visit.contains(n)) {
                            i++;
                            n.setParent(node);
                            n.setCost(Math.abs(start_x - n.getNodeX()) + Math.abs(start_y - n.getNodeY())
                                    + Math.abs(end_x - n.getNodeX()) + Math.abs(end_y - n.getNodeY()));
                            to_visit.add(n);
                        }
                    }
                }
            }
            //No Path found
            return null;
        } finally {
            L2Collections.recycle(visited);
            BinaryNodeHeap.recycle(to_visit);
        }
    }

    public final Node[] constructPath(Node node) {
        ArrayList<Node> tmp = L2Collections.newArrayList();
        /*
        int previousdirectionx = -1000;
        int previousdirectiony = -1000;
        int directionx;
        int directiony;
        while (node.getParent() != null)
        {
           // only add a new route point if moving direction changes
           if (node.getParent().getParent() != null // to check and clean diagonal movement
        && Math.abs(node.getNodeX() - node.getParent().getParent().getNodeX()) == 1
        && Math.abs(node.getNodeY() - node.getParent().getParent().getNodeY()) == 1)
           {
        directionx = node.getNodeX() - node.getParent().getParent().getNodeX();
        directiony = node.getNodeY() - node.getParent().getParent().getNodeY();
           }
           else
           {
        directionx = node.getNodeX() - node.getParent().getNodeX();
        directiony = node.getNodeY() - node.getParent().getNodeY();
           }
           if (directionx != previousdirectionx || directiony != previousdirectiony)
           {
        previousdirectionx = directionx;
        previousdirectiony = directiony;
        tmp.add(node);
           }
           node = node.getParent();
        }*/

        while (node.getParent() != null) {
            tmp.add(node);

            node = node.getParent();
        }

        Node[] path = tmp.toArray(new Node[tmp.size()]);

        L2Collections.recycle(tmp);

        ArrayUtils.reverse(path);

        // then LOS based filtering to reduce the number of route points
        /*
        if (path.length > 4)
        {
           //System.out.println("pathsize:"+path.size());
           for (int index = 0; index < path.length - 3; index = index + 3)
           {
        //System.out.println("Attempt filter");
        Node n1 = path[index];
        Node n2 = path[index + 3];
            
        if (GeoData.getInstance().canMoveFromToTarget(n1.getX(), n1.getY(), n1.getZ(), n2.getX(), n2.getY(),
           n2.getZ()))
        {
           //System.out.println("filtering i:"+(index+1));
           path[index + 1] = null;
           path[index + 2] = null;
        }
           }
               
           //System.out.println("pathsize:"+path.size());
        }
        */

        for (int lastValid = 0; lastValid < path.length - 1;) {
            final Node lastValidNode = path[lastValid];

            int low = lastValid;
            int high = path.length - 1;

            while (low < high) {
                final int mid = ((low + high) >> 1) + 1;
                final Node midNode = path[mid];

                final int delta = mid - lastValid;
                final int deltaNodeX = Math.abs(midNode.getNodeX() - lastValidNode.getNodeX());
                final int deltaNodeY = Math.abs(midNode.getNodeY() - lastValidNode.getNodeY());

                if (delta <= 1) {
                    low = mid;
                } else if (delta % 2 == 0 && deltaNodeX == delta / 2 && deltaNodeY == delta / 2) {
                    low = mid;
                } else if (deltaNodeX == delta || deltaNodeY == delta) {
                    low = mid;
                } else if (GeoData.getInstance().canMoveFromToTarget(lastValidNode.getX(), lastValidNode.getY(),
                        lastValidNode.getZ(), midNode.getX(), midNode.getY(), midNode.getZ(), 0)) {
                    low = mid;
                } else {
                    high = mid - 1;
                }
            }

            final int nextValid = low;

            for (int i = lastValid + 1; i < nextValid; i++)
                path[i] = null;

            lastValid = nextValid;
        }

        return L2Arrays.compact(path);
    }

    public final Node[] constructPath2(Node node) {
        ArrayList<Node> tmp = L2Collections.newArrayList();
        int previousdirectionx = -1000;
        int previousdirectiony = -1000;
        int directionx;
        int directiony;
        while (node.getParent() != null) {
            // only add a new route point if moving direction changes
            directionx = node.getNodeX() - node.getParent().getNodeX();
            directiony = node.getNodeY() - node.getParent().getNodeY();
            if (directionx != previousdirectionx || directiony != previousdirectiony) {
                previousdirectionx = directionx;
                previousdirectiony = directiony;
                tmp.add(node);
            }
            node = node.getParent();
        }

        Node[] path = tmp.toArray(new Node[tmp.size()]);

        L2Collections.recycle(tmp);

        ArrayUtils.reverse(path);

        return path;
    }

    /**
     * Convert geodata position to pathnode position
     * 
     * @param geo_pos
     * @return pathnode position
     */
    public final short getNodePos(int geo_pos) {
        return (short) (geo_pos >> 3); //OK?
    }

    /**
     * Convert node position to pathnode block position
     * 
     * @param geo_pos
     * @return pathnode block position (0...255)
     */
    public final short getNodeBlock(int node_pos) {
        return (short) (node_pos % 256);
    }

    public final byte getRegionX(int node_pos) {
        return (byte) ((node_pos >> 8) + 10);
    }

    public final byte getRegionY(int node_pos) {
        return (byte) ((node_pos >> 8) + 10);
    }

    public final short getRegionOffset(byte rx, byte ry) {
        return (short) ((rx << 5) + ry);
    }

    /**
     * Convert pathnode x to World x position
     * 
     * @param node_x, rx
     * @return
     */
    public final int calculateWorldX(short node_x) {
        return L2World.MAP_MIN_X + node_x * 128 + 48;
    }

    /**
     * Convert pathnode y to World y position
     * 
     * @param node_y
     * @return
     */
    public final int calculateWorldY(short node_y) {
        return L2World.MAP_MIN_Y + node_y * 128 + 48;
    }
}