All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bladecoder.engine.polygonalpathfinder.PolygonalNavGraph Maven / Gradle / Ivy

There is a newer version: 4.3.1
Show newest version
/*******************************************************************************
 * Copyright 2014 Rafael Garcia Moreno.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.bladecoder.engine.polygonalpathfinder;

import java.util.ArrayList;
import java.util.Collection;

import com.badlogic.gdx.math.Polygon;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.Json.Serializable;
import com.badlogic.gdx.utils.JsonValue;
import com.bladecoder.engine.assets.EngineAssetManager;
import com.bladecoder.engine.model.BaseActor;
import com.bladecoder.engine.model.ObstacleActor;
import com.bladecoder.engine.pathfinder.AStarPathFinder;
import com.bladecoder.engine.pathfinder.NavContext;
import com.bladecoder.engine.pathfinder.NavGraph;
import com.bladecoder.engine.pathfinder.PathFinder;
import com.bladecoder.engine.util.EngineLogger;
import com.bladecoder.engine.util.PolygonUtils;

/**
 * Finds the shortest path between 2 points in a world defined by a walkzone and
 * several obstacles.
 * 
 * @author rgarcia
 */
public class PolygonalNavGraph implements NavGraph, Serializable {
	private static final int MAX_PATHFINDER_SEARCH_DISTANCE = 50;

	private static final Vector2 tmp = new Vector2();
	private static final Vector2 tmp2 = new Vector2();

	private Polygon walkZone;
	private final ArrayList obstacles = new ArrayList();

	final private PathFinder pathfinder = new AStarPathFinder(this,
			MAX_PATHFINDER_SEARCH_DISTANCE, new ManhattanDistance());
	final private NavPathPolygonal resultPath = new NavPathPolygonal();
	final private NavNodePolygonal startNode = new NavNodePolygonal();
	final private NavNodePolygonal targetNode = new NavNodePolygonal();
	final private ArrayList graphNodes = new ArrayList();

	public ArrayList findPath(float sx, float sy, float tx, float ty) {
		resultPath.clear();

		Vector2 source = new Vector2(sx, sy);
		Vector2 target = new Vector2(tx, ty);

		// 1. First verify if both the start and target points of the path are
		// inside the polygon. If the end point is outside the polygon clamp it
		// back inside.
		if (!PolygonUtils.isPointInside(walkZone, sx, sy, true)) {
			EngineLogger.debug("PolygonalPathFinder: Source not in polygon!");

			PolygonUtils.getClampedPoint(walkZone, sx, sy, source);

			if (!PolygonUtils.isPointInside(walkZone, source.x, source.y, true)) {
				EngineLogger.debug("PolygonalPathFinder: CLAMPED FAILED!!");

				return resultPath.getPath();
			}

		}

		if (!PolygonUtils.isPointInside(walkZone, tx, ty, true)) {
			PolygonUtils.getClampedPoint(walkZone, tx, ty, target);

			if (!PolygonUtils.isPointInside(walkZone, target.x, target.y, true)) {
				EngineLogger.debug("PolygonalPathFinder: CLAMPED FAILED!!");

				return resultPath.getPath();
			}
		}

		for (Polygon o : obstacles) {
			if (PolygonUtils.isPointInside(o, target.x, target.y, false)) {
				PolygonUtils.getClampedPoint(o, target.x, target.y, target);

				// If the clamped point is not in the walkzone
				// we search for the first vertex inside
				if (!PolygonUtils.isPointInside(walkZone, target.x, target.y, true)) {
					getFirstVertexInsideWalkzone(o, target);
					// We exit after processing the first polygon with the point
					// inside.
					// Overlaped obstacles are not supported
					break;
				}
			}
		}

		// 2. Then start by checking if both points are in line-of-sight. If
		// they are, there’s no need for pathfinding, just walk there!
		if (inLineOfSight(source.x, source.y, target.x, target.y)) {
			EngineLogger.debug("PolygonalPathFinder: Direct path found");

			resultPath.getPath().add(source);
			resultPath.getPath().add(target);

			return resultPath.getPath();
		}

		// 3. Otherwise, add the start and end points of your path as new
		// temporary nodes to the graph.
		// AND Connect them to every other node that they can see on the graph.
		addStartEndNodes(source.x, source.y, target.x, target.y);

		// 5. Run your A* implementation on the graph to get your path. This
		// path is guaranteed to be as direct as possible!
		pathfinder.findPath(null, startNode, targetNode, resultPath);

		return resultPath.getPath();
	}

	/**
	 * Search the first polygon vertex inside the walkzone.
	 * 
	 * @param p
	 *            the polygon
	 * @param target
	 *            the vertex found
	 */
	private void getFirstVertexInsideWalkzone(Polygon p, Vector2 target) {
		float verts[] = p.getTransformedVertices();

		for (int i = 0; i < verts.length; i += 2) {
			if (PolygonUtils.isPointInside(walkZone, verts[i], verts[i + 1], true)) {
				target.x = verts[i];
				target.y = verts[i + 1];

				return;
			}
		}
	}

	public void createInitialGraph(Collection actors) {
		graphNodes.clear();

		// 1.- Add WalkZone convex nodes
		float verts[] = walkZone.getTransformedVertices();

		for (int i = 0; i < verts.length; i += 2) {
			if (!PolygonUtils.isVertexConcave(walkZone, i)) {
				graphNodes.add(new NavNodePolygonal(verts[i], verts[i + 1]));
			}
		}

		// 2.- Add obstacles concave nodes
		obstacles.clear();

		for (BaseActor a : actors) {
			if (a instanceof ObstacleActor && a.isVisible())
				obstacles.add(a.getBBox());
		}

		for (Polygon o : obstacles) {
			verts = o.getTransformedVertices();

			for (int i = 0; i < verts.length; i += 2) {
				if (PolygonUtils.isVertexConcave(o, i)
						&& PolygonUtils.isPointInside(walkZone, verts[i], verts[i + 1], false)) {
					graphNodes.add(new NavNodePolygonal(verts[i], verts[i + 1]));
				}
			}
		}

		// 3.- CALC LINE OF SIGHTs
		for (int i = 0; i < graphNodes.size() - 1; i++) {
			NavNodePolygonal n1 = graphNodes.get(i);

			for (int j = i + 1; j < graphNodes.size(); j++) {
				NavNodePolygonal n2 = graphNodes.get(j);

				if (inLineOfSight(n1.x, n1.y, n2.x, n2.y)) {
					n1.neighbors.add(n2);
					n2.neighbors.add(n1);
				}
			}
		}
	}

	private boolean inLineOfSight(float p1X, float p1Y, float p2X, float p2Y) {

		tmp.set(p1X, p1Y);
		tmp2.set(p2X, p2Y);

		if (!PolygonUtils.inLineOfSight(tmp, tmp2, walkZone, false)) {
			return false;
		}

		for (Polygon o : obstacles) {
			if (!PolygonUtils.inLineOfSight(tmp, tmp2, o, true)) {
				return false;
			}
		}

		return true;
	}

	private void addStartEndNodes(float sx, float sy, float tx, float ty) {
		startNode.x = sx;
		startNode.y = sy;
		targetNode.x = tx;
		targetNode.y = ty;

		startNode.neighbors.clear();

		for (NavNodePolygonal n : graphNodes) {

			n.neighbors.removeValue(targetNode, true);

			if (inLineOfSight(startNode.x, startNode.y, n.x, n.y)) {
				startNode.neighbors.add(n);
			}

			if (inLineOfSight(targetNode.x, targetNode.y, n.x, n.y)) {
				n.neighbors.add(targetNode);
			}
		}

	}

	public Polygon getWalkZone() {
		return walkZone;
	}

	public void setWalkZone(Polygon walkZone) {
		this.walkZone = walkZone;
	}

	public ArrayList getGraphNodes() {
		return graphNodes;
	}

	@Override
	public boolean blocked(NavContext context, NavNodePolygonal targetNode) {
		return false;
	}

	@Override
	public float getCost(NavContext context, NavNodePolygonal targetNode) {
		return 1;
	}

	private void addObstacleToGrapth(Polygon poly) {
		float verts[] = poly.getTransformedVertices();
		for (int i = 0; i < verts.length; i += 2) {
			if (PolygonUtils.isVertexConcave(poly, i)
					&& PolygonUtils.isPointInside(walkZone, verts[i], verts[i + 1], false)) {
				NavNodePolygonal n1 = new NavNodePolygonal(verts[i], verts[i + 1]);

				for (int j = 0; j < graphNodes.size(); j++) {
					NavNodePolygonal n2 = graphNodes.get(j);

					if (inLineOfSight(n1.x, n1.y, n2.x, n2.y)) {
						n1.neighbors.add(n2);
						n2.neighbors.add(n1);
					}
				}

				graphNodes.add(n1);
			}
		}
	}

	public void addDinamicObstacle(Polygon poly) {

		int idx = obstacles.indexOf(poly);

		// CHECK TO AVOID ADDING THE ACTOR SEVERAL TIMES
		if (idx == -1) {
			obstacles.add(poly);
			addObstacleToGrapth(poly);
		}
	}

	public boolean removeDinamicObstacle(Polygon poly) {
		boolean exists = obstacles.remove(poly);

		if (!exists)
			return false;

		float verts[] = poly.getTransformedVertices();

		for (int i = 0; i < verts.length; i += 2) {
			if (PolygonUtils.isVertexConcave(poly, i)
					&& PolygonUtils.isPointInside(walkZone, verts[i], verts[i + 1], false)) {
				for (int j = 0; j < graphNodes.size(); j++) {
					NavNodePolygonal n = graphNodes.get(j);

					if (n.x == verts[i] && n.y == verts[i + 1]) {
						graphNodes.remove(n);
						j--;

						for (NavNodePolygonal n2 : graphNodes) {
							n2.neighbors.removeValue(n, true);
						}

					}
				}
			}
		}

		return true;
	}

	@Override
	public void write(Json json) {
		Polygon p = new Polygon(walkZone.getVertices());
		p.setPosition(walkZone.getX() / walkZone.getScaleX(), walkZone.getY() / walkZone.getScaleY());
		json.writeValue("walkZone", p);
	}

	@Override
	public void read(Json json, JsonValue jsonData) {
		float worldScale = EngineAssetManager.getInstance().getScale();

		walkZone = json.readValue("walkZone", Polygon.class, jsonData);
		walkZone.setScale(worldScale, worldScale);
		walkZone.setPosition(walkZone.getX() * worldScale, walkZone.getY() * worldScale);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy