/* * Copyright (C) 2014 Alfons Wirtz * website www.freerouting.net * * 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 at <http://www.gnu.org/licenses/> * for more details. * * PolygonShape.java * * Created on 13. Juni 2003, 12:12 */ package geometry.planar; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; /** * Shape described bei a closed polygon of corner points. * The corners are ordered in counterclock sense around the border of the shape. * The corners are normalysed, so that the corner with the lowest y-value comes first. * In case of equal y-value the corner with the lowest x-value comes first. * * @author Alfons Wirtz */ public class PolygonShape extends PolylineShape { /** Creates a new instance of PolygonShape */ public PolygonShape(Polygon p_polygon) { Polygon curr_polygon = p_polygon; if (p_polygon.winding_number_after_closing() < 0) { // the the corners of the polygon are in clockwise sense curr_polygon = p_polygon.revert_corners(); } Point [] curr_corners = curr_polygon.corner_array(); int last_corner_no = curr_corners.length - 1; if (last_corner_no > 0) { if (curr_corners[0].equals(curr_corners[last_corner_no])) { // skip last point --last_corner_no; } } boolean last_point_collinear = false; if (last_corner_no >= 2) { last_point_collinear = curr_corners[last_corner_no].side_of(curr_corners[last_corner_no - 1], curr_corners[0]) == Side.COLLINEAR; } if (last_point_collinear) { // skip last point --last_corner_no; } int first_corner_no = 0; boolean first_point_collinear = false; if (last_corner_no - first_corner_no >= 2) { first_point_collinear = curr_corners[0].side_of(curr_corners[1], curr_corners[last_corner_no]) == Side.COLLINEAR; } if (first_point_collinear) { // skip first point ++first_corner_no; } // search the point with the lowest y and then with the lowest x int start_corner_no = first_corner_no; FloatPoint start_corner = curr_corners[start_corner_no].to_float(); for (int i = start_corner_no + 1; i <= last_corner_no; ++i) { FloatPoint curr_corner = curr_corners[i].to_float(); if (curr_corner.y < start_corner.y || curr_corner.y == start_corner.y && curr_corner.x < start_corner.x) { start_corner_no = i; start_corner = curr_corner; } } int new_corner_count = last_corner_no - first_corner_no + 1; Point[] result = new Point[new_corner_count]; int curr_corner_no = 0; for (int i = start_corner_no; i <= last_corner_no; ++i) { result[curr_corner_no] = curr_corners[i]; ++curr_corner_no; } for (int i = first_corner_no; i < start_corner_no; ++i) { result[curr_corner_no] = curr_corners[i]; ++curr_corner_no; } corners = result; } public PolygonShape(Point[] p_corner_arr) { this(new Polygon(p_corner_arr)); } public Point corner(int p_no) { if (p_no < 0 || p_no >= corners.length) { System.out.println("PolygonShape.corner: p_no out of range"); return null; } return corners[p_no]; } public int border_line_count() { return corners.length; } public boolean corner_is_bounded(int p_no) { return true; } public boolean intersects(Shape p_shape) { return p_shape.intersects(this); } public boolean intersects(Circle p_circle) { TileShape[] convex_pieces = split_to_convex(); for (int i = 0; i < convex_pieces.length; ++i) { if (convex_pieces[i].intersects(p_circle)) return true; } return false; } public boolean intersects(Simplex p_simplex) { TileShape[] convex_pieces = split_to_convex(); for (int i = 0; i < convex_pieces.length; ++i) { if (convex_pieces[i].intersects(p_simplex)) return true; } return false; } public boolean intersects(IntOctagon p_oct) { TileShape[] convex_pieces = split_to_convex(); for (int i = 0; i < convex_pieces.length; ++i) { if (convex_pieces[i].intersects(p_oct)) return true; } return false; } public boolean intersects(IntBox p_box) { TileShape[] convex_pieces = split_to_convex(); for (int i = 0; i < convex_pieces.length; ++i) { if (convex_pieces[i].intersects(p_box)) return true; } return false; } public Polyline[] cutout(Polyline p_polyline) { System.out.println("PolygonShape.cutout not yet implemented"); return null; } public PolygonShape enlarge(double p_offset) { if (p_offset == 0) { return this; } System.out.println("PolygonShape.enlarge not yet implemented"); return null; } public double border_distance(FloatPoint p_point) { System.out.println("PolygonShape.border_distance not yet implemented"); return 0; } public double smallest_radius() { return border_distance(centre_of_gravity()); } public boolean contains(FloatPoint p_point) { TileShape[] convex_pieces = split_to_convex(); for (int i = 0; i < convex_pieces.length; ++i) { if (convex_pieces[i].contains(p_point)) return true; } return false; } public boolean contains_inside(Point p_point) { if (contains_on_border(p_point)) { return false; } return !is_outside(p_point); } public boolean is_outside(Point p_point) { TileShape[] convex_pieces = split_to_convex(); for (int i = 0; i < convex_pieces.length; ++i) { if (!convex_pieces[i].is_outside(p_point)) return false; } return true; } public boolean contains(Point p_point) { return !is_outside(p_point); } public boolean contains_on_border(Point p_point) { //System.out.println("PolygonShape.contains_on_edge not yet implemented"); return false; } public double distance(FloatPoint p_point) { System.out.println("PolygonShape.distance not yet implemented"); return 0; } public PolygonShape translate_by(Vector p_vector) { if (p_vector.equals(Vector.ZERO)) { return this; } Point[] new_corners = new Point[corners.length]; for (int i = 0; i < corners.length; ++i) { new_corners[i] = corners[i].translate_by(p_vector); } return new PolygonShape(new_corners); } public RegularTileShape bounding_shape(ShapeBoundingDirections p_dirs) { return p_dirs.bounds(this); } public IntBox bounding_box() { if (precalculated_bounding_box == null) { double llx = Integer.MAX_VALUE; double lly = Integer.MAX_VALUE; double urx = Integer.MIN_VALUE; double ury = Integer.MIN_VALUE; for (int i = 0; i < corners.length; ++i) { FloatPoint curr = corners[i].to_float(); llx = Math.min(llx, curr.x); lly = Math.min(lly, curr.y); urx = Math.max(urx, curr.x); ury = Math.max(ury, curr.y); } IntPoint lower_left = new IntPoint((int)Math.floor(llx),(int)Math.floor(lly)); IntPoint upper_right = new IntPoint((int)Math.ceil(urx),(int)Math.ceil(ury)); precalculated_bounding_box = new IntBox(lower_left, upper_right); } return precalculated_bounding_box; } public IntOctagon bounding_octagon() { if (precalculated_bounding_octagon == null) { double lx = Integer.MAX_VALUE; double ly = Integer.MAX_VALUE; double rx = Integer.MIN_VALUE; double uy = Integer.MIN_VALUE; double ulx = Integer.MAX_VALUE; double lrx = Integer.MIN_VALUE; double llx = Integer.MAX_VALUE; double urx = Integer.MIN_VALUE; for (int i = 0; i < corners.length; ++i) { FloatPoint curr = corners[i].to_float(); lx = Math.min(lx, curr.x); ly = Math.min(ly, curr.y); rx = Math.max(rx, curr.x); uy = Math.max(uy, curr.y); double tmp = curr.x - curr.y; ulx = Math.min(ulx, tmp); lrx = Math.max(lrx, tmp); tmp = curr.x + curr.y; llx = Math.min(llx, tmp); urx = Math.max(urx, tmp); } precalculated_bounding_octagon = new IntOctagon((int)Math.floor(lx), (int)Math.floor(ly), (int)Math.ceil(rx), (int)Math.ceil(uy), (int)Math.floor(ulx), (int)Math.ceil(lrx), (int)Math.floor(llx), (int)Math.ceil(urx)); } return precalculated_bounding_octagon; } /** * Checks, if every line segment between 2 points of the shape is contained * completely in the shape. */ public boolean is_comvex() { if (corners.length <= 2) return true; Point prev_point = corners[corners.length - 1]; Point curr_point = corners[0]; Point next_point = corners[1]; for (int ind = 0; ind < corners.length; ++ind) { if (next_point.side_of(prev_point, curr_point) == Side.ON_THE_RIGHT) return false; prev_point = curr_point; curr_point = next_point; if (ind == corners.length - 2) next_point = corners[0]; else next_point = corners[ind + 2]; } // check, if the sum of the interior angles is at most 2 * pi Line first_line = new Line(corners[corners.length - 1], corners[0]); Line curr_line = new Line(corners[0], corners[1]); IntDirection first_direction = (IntDirection)first_line.direction(); IntDirection curr_direction = (IntDirection)curr_line.direction(); double last_det = first_direction.determinant(curr_direction); for (int ind2 = 2; ind2 < corners.length; ++ind2) { curr_line = new Line(curr_line.b, corners[ind2]); curr_direction = (IntDirection)curr_line.direction(); double curr_det = first_direction.determinant(curr_direction); if (last_det <= 0 && curr_det > 0) return false; last_det = curr_det; } return true; } public PolygonShape convex_hull() { if (corners.length <= 2) return this; Point prev_point = corners[corners.length - 1]; Point curr_point = corners[0]; Point next_point; for (int ind = 0; ind < corners.length; ++ind) { if (ind == corners.length - 1) { next_point = corners[0]; } else { next_point = corners[ind + 1]; } if (next_point.side_of(prev_point, curr_point) != Side.ON_THE_LEFT) { //skip curr_point; Point[] new_corners = new Point[corners.length - 1]; for (int i = 0; i < ind; ++i) { new_corners[i] = corners[i]; } for (int i = ind; i < new_corners.length; ++i) { new_corners[i] = corners[i + 1]; } PolygonShape result = new PolygonShape(new_corners); return result.convex_hull(); } prev_point = curr_point; curr_point = next_point; } return this; } public TileShape bounding_tile() { PolygonShape hull = convex_hull(); Line[] bounding_lines = new Line[hull.corners.length]; for (int i = 0; i < bounding_lines.length - 1; ++i) { bounding_lines[i] = new Line(hull.corners[i], hull.corners[i + 1]); } bounding_lines[bounding_lines.length - 1] = new Line(hull.corners[hull.corners.length - 1], hull.corners[0]); return TileShape.get_instance(bounding_lines); } public double area() { if (dimension() <= 2) { return 0; } // calculate half of the absolute value of // x0 (y1 - yn-1) + x1 (y2 - y0) + x2 (y3 - y1) + ...+ xn-1( y0 - yn-2) // where xi, yi are the coordinates of the i-th corner of this polygon. double result = 0; FloatPoint prev_corner = corners[corners.length - 2].to_float(); FloatPoint curr_corner = corners[corners.length - 1].to_float(); for (int i = 0; i < corners.length; ++i) { FloatPoint next_corner = corners[i].to_float(); result += curr_corner.x * (next_corner.y - prev_corner.y); prev_corner = curr_corner; curr_corner = next_corner; } result = 0.5 * Math.abs(result); return result; } public int dimension() { if (corners.length == 0) return -1; if (corners.length == 1) return 0; if (corners.length == 2) return 1; return 2; } public boolean is_bounded() { return true; } public boolean is_empty() { return corners.length == 0; } public Line border_line(int p_no) { if (p_no < 0 || p_no >= corners.length) { System.out.println("PolygonShape.edge_line: p_no out of range"); return null; } Point next_corner; if (p_no == corners.length - 1) { next_corner = corners[0]; } else { next_corner = corners[p_no + 1]; } return new Line(corners[p_no], next_corner); } public FloatPoint nearest_point_approx(FloatPoint p_from_point) { double min_dist = Double.MAX_VALUE; FloatPoint result = null; TileShape[] convex_shapes = split_to_convex(); for (int i = 0; i < convex_shapes.length; ++i) { FloatPoint curr_nearest_point = convex_shapes[i].nearest_point_approx(p_from_point); double curr_dist = curr_nearest_point.distance_square(p_from_point); if (curr_dist < min_dist) { min_dist = curr_dist; result = curr_nearest_point; } } return result; } public PolygonShape turn_90_degree(int p_factor, IntPoint p_pole) { Point[] new_corners = new Point[corners.length]; for (int i = 0; i < corners.length; ++i) { new_corners[i] = corners[i].turn_90_degree(p_factor, p_pole); } return new PolygonShape(new_corners); } public PolygonShape rotate_approx(double p_angle, FloatPoint p_pole) { if (p_angle == 0) { return this; } Point[] new_corners = new Point[corners.length]; for (int i = 0; i < corners.length; ++i) { new_corners[i] = corners[i].to_float().rotate(p_angle, p_pole).round(); } return new PolygonShape(new_corners); } public PolygonShape mirror_vertical(IntPoint p_pole) { Point[] new_corners = new Point[corners.length]; for (int i = 0; i < corners.length; ++i) { new_corners[i] = corners[i].mirror_vertical(p_pole); } return new PolygonShape(new_corners); } public PolygonShape mirror_horizontal(IntPoint p_pole) { Point[] new_corners = new Point[corners.length]; for (int i = 0; i < corners.length; ++i) { new_corners[i] = corners[i].mirror_horizontal(p_pole); } return new PolygonShape(new_corners); } /** * Splits this polygon shape into convex pieces. * The result is not exact, because rounded intersections of lines are * used in the result pieces. It can be made exact, if Polylines are returned * instead of Polygons, so that no intersection points are needed in the result. */ public TileShape[] split_to_convex() { if (this.precalculated_convex_pieces == null) // not yet precalculated { // use a fixed seed to get reproducable result random_generator.setSeed(seed); Collection<PolygonShape> convex_pieces = split_to_convex_recu(); if(convex_pieces == null) { // split failed, maybe the polygon has selfontersections return null; } precalculated_convex_pieces = new TileShape[convex_pieces.size()]; Iterator<PolygonShape> it = convex_pieces.iterator(); for (int i = 0; i < precalculated_convex_pieces.length; ++i) { PolygonShape curr_piece = it.next(); precalculated_convex_pieces[i] = TileShape.get_instance(curr_piece.corners); } } return this.precalculated_convex_pieces; } /** * Crivate recursive part of split_to_convex. * Returns a collection of polygon shape pieces. */ private Collection<PolygonShape> split_to_convex_recu() { // start with a hashed corner and search the first concave corner int start_corner_no = random_generator.nextInt(corners.length); Point curr_corner = corners[start_corner_no]; Point prev_corner; if (start_corner_no != 0) prev_corner = corners[start_corner_no - 1]; else prev_corner = corners[corners.length - 1]; Point next_corner = null; // search for the next concave corner from here int concave_corner_no = -1; for (int i = 0; i < corners.length; ++i) { if (start_corner_no < corners.length - 1) next_corner = corners[start_corner_no + 1]; else next_corner = corners[0]; if (next_corner.side_of(prev_corner, curr_corner) == Side.ON_THE_RIGHT) { // concave corner found concave_corner_no = start_corner_no; break; } prev_corner = curr_corner; curr_corner = next_corner; start_corner_no = (start_corner_no + 1) % corners.length; } Collection<PolygonShape> result = new LinkedList<PolygonShape>(); if (concave_corner_no < 0) { // no concave corner found, this shape is already convex result.add(this); return result; } DivisionPoint d = new DivisionPoint(concave_corner_no); if (d.projection == null) { // projection not found, maybe polygon has selfintersections return null; } // construct the result pieces from p_polygon and the division point int corner_count = d.corner_no_after_projection - concave_corner_no; if (corner_count < 0) corner_count += corners.length; ++corner_count; Point[] first_arr = new Point[corner_count]; int corner_ind = concave_corner_no; for (int i = 0; i < corner_count - 1; ++i) { first_arr[i] = corners[corner_ind]; corner_ind = (corner_ind + 1) % corners.length; } first_arr[corner_count - 1] = d.projection.round(); PolygonShape first_piece = new PolygonShape(first_arr); corner_count = concave_corner_no - d.corner_no_after_projection; if (corner_count < 0) corner_count += corners.length; corner_count += 2; Point[] last_arr = new Point[corner_count]; last_arr[0] = d.projection.round(); corner_ind = d.corner_no_after_projection; for (int i = 1; i < corner_count; ++i) { last_arr[i] = corners[corner_ind]; corner_ind = (corner_ind + 1) % corners.length; } PolygonShape last_piece = new PolygonShape(last_arr); Collection<PolygonShape> c1 = first_piece.split_to_convex_recu(); if (c1 == null) return null; Collection<PolygonShape> c2 = last_piece.split_to_convex_recu(); if (c2 == null) return null; result.addAll(c1); result.addAll(c2); return result; } public final Point[] corners; /** * the following fields are for storing precalculated data */ transient private IntBox precalculated_bounding_box = null; transient private IntOctagon precalculated_bounding_octagon = null; transient private TileShape[] precalculated_convex_pieces = null; static private int seed = 99; static private java.util.Random random_generator = new java.util.Random(seed); private class DivisionPoint { /** At a concave corner of the closed polygon, a minimal axis parallel * division line is constructed, to divide the closed polygon into two. */ DivisionPoint(int p_concave_corner_no) { FloatPoint concave_corner = corners[p_concave_corner_no].to_float(); FloatPoint before_concave_corner; if (p_concave_corner_no != 0) before_concave_corner = corners[p_concave_corner_no - 1].to_float(); else before_concave_corner = corners[corners.length - 1].to_float(); FloatPoint after_concave_corner; if (p_concave_corner_no == corners.length - 1) after_concave_corner = corners[0].to_float(); else after_concave_corner = corners [p_concave_corner_no + 1].to_float(); boolean search_right = before_concave_corner.y > concave_corner.y || concave_corner.y > after_concave_corner.y; boolean search_left = before_concave_corner.y < concave_corner.y || concave_corner.y < after_concave_corner.y; boolean search_up = before_concave_corner.x < concave_corner.x || concave_corner.x < after_concave_corner.x; boolean search_down = before_concave_corner.x > concave_corner.x || concave_corner.x > after_concave_corner.x; double min_projection_dist = Integer.MAX_VALUE; FloatPoint min_projection = null; int corner_no_after_min_projection = 0; int corner_no_after_curr_projection = (p_concave_corner_no + 2) % corners.length; Point corner_before_curr_projection; if (corner_no_after_curr_projection != 0) corner_before_curr_projection = corners[corner_no_after_curr_projection - 1]; else corner_before_curr_projection = corners[corners.length - 1]; FloatPoint corner_before_projection_approx = corner_before_curr_projection.to_float(); double curr_dist; int loop_end = corners.length - 2; for (int i = 0; i < loop_end; ++i) { Point corner_after_curr_projection = corners[corner_no_after_curr_projection]; FloatPoint corner_after_projection_approx = corner_after_curr_projection.to_float(); if (corner_before_projection_approx.y != corner_after_projection_approx.y) // try a horizontal division { double min_y; double max_y; if (corner_after_projection_approx.y > corner_before_projection_approx.y) { min_y = corner_before_projection_approx.y; max_y = corner_after_projection_approx.y; } else { min_y = corner_after_projection_approx.y; max_y = corner_before_projection_approx.y; } if (concave_corner.y >= min_y && concave_corner.y <= max_y) { Line curr_line = new Line(corner_before_curr_projection, corner_after_curr_projection); double x_intersect = curr_line.function_in_y_value_approx(concave_corner.y); curr_dist = Math.abs(x_intersect - concave_corner.x); // Make shure, that the new shape will not be concave at the projection point. // That might happen, if the boundary curve runs back in itself. boolean projection_ok = curr_dist < min_projection_dist && (search_right && x_intersect > concave_corner.x && concave_corner.y <= corner_after_projection_approx.y || search_left && x_intersect < concave_corner.x && concave_corner.y >= corner_after_projection_approx.y); if (projection_ok) { min_projection_dist = curr_dist; corner_no_after_min_projection = corner_no_after_curr_projection; min_projection = new FloatPoint(x_intersect, concave_corner.y); } } } if (corner_before_projection_approx.x != corner_after_projection_approx.x) // try a vertical division { double min_x; double max_x; if (corner_after_projection_approx.x > corner_before_projection_approx.x) { min_x = corner_before_projection_approx.x; max_x = corner_after_projection_approx.x; } else { min_x = corner_after_projection_approx.x; max_x = corner_before_projection_approx.x; } if (concave_corner.x >= min_x && concave_corner.x <= max_x) { Line curr_line = new Line(corner_before_curr_projection, corner_after_curr_projection); double y_intersect = curr_line.function_value_approx(concave_corner.x); curr_dist = Math.abs(y_intersect - concave_corner.y); // make shure, that the new shape will be convex at the projection point boolean projection_ok = curr_dist < min_projection_dist && (search_up && y_intersect > concave_corner.y && concave_corner.x >= corner_after_projection_approx.x || search_down && y_intersect < concave_corner.y && concave_corner.x <= corner_after_projection_approx.x); if (projection_ok) { min_projection_dist = curr_dist; corner_no_after_min_projection = corner_no_after_curr_projection; min_projection = new FloatPoint(concave_corner.x, y_intersect); } } } corner_before_curr_projection = corner_after_curr_projection; corner_before_projection_approx = corner_after_projection_approx; if (corner_no_after_curr_projection == corners.length - 1) { corner_no_after_curr_projection = 0; } else { ++corner_no_after_curr_projection; } } if (min_projection_dist == Integer.MAX_VALUE ) { System.out.println("PolygonShape.DivisionPoint: projection not found"); } projection = min_projection; corner_no_after_projection = corner_no_after_min_projection; } final int corner_no_after_projection; final FloatPoint projection; } }