package aima.core.util.math.geom; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import aima.core.util.Util; import aima.core.util.math.geom.shapes.IGeometric2D; import aima.core.util.math.geom.shapes.Point2D; import aima.core.util.math.geom.shapes.Ray2D; import aima.core.util.math.geom.shapes.Rect2D; /** * This class is a simple implementation of a Cartesian plot.<br/> * It uses a {@link IGroupParser} that generates a set of {@link IGeometric2D}. * * @author Arno von Borries * @author Jan Phillip Kretzschmar * @author Andreas Walscheid * */ public class CartesianPlot2D { private double rayRange; private IGroupParser parser; private ArrayList<IGeometric2D> shapes; private ArrayList<Rect2D> boundaries; /** * @param parser a file parser which implements {@link IGroupParser}. This parser is used to load a map file. */ public CartesianPlot2D(IGroupParser parser) { this.parser = parser; } /** * Sets the maximum length of a rayCast after which {@code Double.POSITIVE_INFINITY} may be returned. * @param rayRange the maximum non zero positive length, that the ray can reliably have. This parameter is used to speed up {@code rayCast}. * Set this to {@code Double.POSITIVE_INFINITY} if you like waiting. */ public void setRayRange(double rayRange) { this.rayRange = rayRange; } /** * Sets the set of shapes of the plot. * @param shapes the set of shapes to be set. */ public void setShapes(ArrayList<IGeometric2D> shapes) { this.shapes = shapes; boundaries = new ArrayList<Rect2D>(shapes.size()); for(IGeometric2D shape: shapes) { boundaries.add(shape.getBounds()); } } /** * Loads a map input into this Cartesian plot. * @param input the stream containing the data. * @param groupID the identification for the group of elements that will be loaded. * @throws Exception thrown if the input does not contain the group. * @throws Exception thrown by the parser when it encounters an error in the input. */ public void loadMap(InputStream input,String groupID) throws Exception { shapes = parser.parse(input, groupID); boundaries = new ArrayList<Rect2D>(shapes.size()); for(IGeometric2D shape: shapes) { boundaries.add(shape.getBounds()); } } /** * @return an iterator over the shapes. */ public Iterator<IGeometric2D> getShapes() { return shapes.iterator(); } /** * @return an iterator over the boundaries of the shapes. */ public Iterator<Rect2D> getBoundaries() { return boundaries.iterator(); } /** * Checks whether the plot does not contain any elements. * @return true if the plot contains no element. */ public boolean isEmpty() { return shapes == null ? true : shapes.isEmpty(); } /** * Calculates a random point that is somewhere inside one of the shapes of the plot or on a border of a shape. * @return a point on a shape within the plot. */ public Point2D randomPoint() { IGeometric2D shape = Util.selectRandomlyFromList(shapes); return shape.randomPoint(); } /** * Calculates the length of a ray until it intersects with a shape.<br/> * As this class uses the set maximum ray range to speed up this ray casting,<br/> * this function may return {@code Double.POSITIVE_INFINITY} for a ray that intersects with an obstacle that is further away than the rayRange from the given starting point. * @param ray the ray to be used for ray casting. * @return the length of the ray. */ public double rayCast(Ray2D ray) { double result = Double.POSITIVE_INFINITY; Rect2D rayBounding = new Rect2D(ray.getStart().getX() - rayRange,ray.getStart().getY() - rayRange, ray.getStart().getX() + rayRange, ray.getStart().getY() + rayRange); for(int i=0;i < shapes.size(); i++) { Rect2D bounding = boundaries.get(i); if( rayBounding.isInsideBorder(bounding.getLowerLeft()) || rayBounding.isInsideBorder(bounding.getUpperLeft()) || rayBounding.isInsideBorder(bounding.getLowerRight()) || rayBounding.isInsideBorder(bounding.getUpperRight())) { final double tmp = shapes.get(i).rayCast(ray); result = tmp < result ? tmp : result; } } return result; } /** * Checks whether the given point is on any of the shapes of the plot. * @param point the point to be tested. * @return true if the point is on any of the shapes. */ public boolean isPointInsideBorderShape(Point2D point) { for(int i=0;i < shapes.size();i++) { if(boundaries.get(i).isInsideBorder(point)) { if(shapes.get(i).isInsideBorder(point)) { return true; } } } return false; } /** * Checks whether the given point is inside any of the shapes of the plot. * @param point the point to be tested. * @return true if the point is in any of the shapes (excluding their borders). */ public boolean isPointInsideShape(Point2D point) { for(int i=0;i < shapes.size();i++) { if(boundaries.get(i).isInside(point)) { if(shapes.get(i).isInside(point)) { return true; } } } return false; } }