/* * 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. */ package geometry.planar; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; /** * * A Polyline is a sequence of lines, where no 2 consecutive * lines may be parallel. A Polyline of n lines defines a Polygon of * n-1 intersection points of consecutive lines. * The lines of the objects of class Polyline are normally defined * by points with integer coordinates, wheras the intersections * of Lines can be representated in general only by infinite precision * rational points. We use polylines with integer coordinates instead * of polygons with infinite precision rational coordinates * because of its better performance in geometric calculations. * * * @author Alfons Wirtz */ public class Polyline implements java.io.Serializable { /** * creates a polyline of length p_polygon.corner_count + 1 from p_polygon, * so that the i-th corner of p_polygon will be the intersection of * the i-th and the i+1-th lines of the new created p_polyline * for 0 <= i < p_point_arr.length. p_polygon must have at least 2 corners */ public Polyline(Polygon p_polygon) { Point[] point_arr = p_polygon.corner_array(); if (point_arr.length < 2) { System.out.println("Polyline: must contain at least 2 different points"); arr = new Line[0]; return; } arr = new Line [point_arr.length + 1]; for (int i = 1; i < point_arr.length; ++i) { arr[i] = new Line(point_arr[i - 1], point_arr[i]); } // construct perpendicular lines at the start and at the end to represent // the first and the last point of point_arr as intersection of lines. Direction dir = Direction.get_instance(point_arr[0], point_arr[1]); arr [0] = Line.get_instance(point_arr[0], dir.turn_45_degree(2)); dir = Direction.get_instance(point_arr[point_arr.length - 1], point_arr[point_arr.length - 2]); arr[point_arr.length] = Line.get_instance(point_arr[point_arr.length - 1], dir.turn_45_degree(2)); } public Polyline(Point[] p_points) { this(new Polygon(p_points)); } /** * creates a polyline consisting of 3 lines */ public Polyline(Point p_from_corner, Point p_to_corner) { if (p_from_corner.equals(p_to_corner)) { arr = new Line [0]; return; } arr = new Line [3]; Direction dir = Direction.get_instance(p_from_corner, p_to_corner); arr [0] = Line.get_instance(p_from_corner, dir.turn_45_degree(2)); arr[1] = new Line(p_from_corner, p_to_corner); dir = Direction.get_instance(p_from_corner, p_to_corner); arr [2] = Line.get_instance(p_to_corner, dir.turn_45_degree(2)); } /** * Creates a polyline from an array of lines. * Lines, which are parallel to the previous line are skipped. * The directed lines are normalized, so that they intersect * the previous line before the next line */ public Polyline(Line[] p_line_arr) { Line [] lines = remove_consecutive_parallel_lines(p_line_arr); lines = remove_overlaps(lines); if (lines.length < 3 ) { arr = new Line[0]; return; } precalculated_float_corners = new FloatPoint [lines.length - 1]; // turn evtl the direction of the lines that they point always // from the previous corner to the next corner for (int i = 1; i < lines.length - 1; ++i) { precalculated_float_corners[i] = lines[i].intersection_approx(lines[i + 1]); Side side_of_line = lines[i - 1].side_of(precalculated_float_corners[i]); if (side_of_line != Side.COLLINEAR) { Direction d0 = lines[i - 1].direction(); Direction d1 = lines[i].direction(); Side side1 = d0.side_of(d1); if (side1 != side_of_line) { lines[i] = lines[i].opposite(); } } } arr = lines; } /** * Returns the number of lines minus 1 */ public int corner_count() { return arr.length - 1; } public boolean is_empty() { return arr.length < 3; } /** * Checks, if this polyline is empty or if all corner points are equal. */ public boolean is_point() { if ( arr.length < 3) { return true; } Point first_corner = this.corner(0); for (int i = 1; i < arr.length - 1; ++i) { if (!(this.corner(i).equals(first_corner))) { return false; } } return true; } /** * checks, if all lines of this polyline are orthogonal */ public boolean is_orthogonal() { for (int i = 0; i < arr.length; ++i) { if (!arr[i].is_orthogonal()) { return false; } } return true; } /** * checks, if all lines of this polyline are multiples of 45 degree */ public boolean is_multiple_of_45_degree() { for (int i = 0; i < arr.length; ++i) { if (!arr[i].is_multiple_of_45_degree()) { return false; } } return true; } /** * returns the intersection of the first line with the second line */ public Point first_corner() { return corner(0); } /** * returns the intersection of the last line with the line before * the last line */ public Point last_corner() { return corner(arr.length - 2); } /** * returns the array of the intersection of two consecutive lines * approximated by FloatPoint's. */ public Point [] corner_arr() { if (arr.length < 2) { return new Point[0]; } if (precalculated_corners == null) // corner array is not yet allocated { precalculated_corners = new Point[arr.length - 1]; } for (int i = 0; i < precalculated_corners.length; ++i) { if (precalculated_corners[i] == null) { precalculated_corners[i] = arr[i].intersection(arr[i + 1]); } } return precalculated_corners; } /** * returns the array of the intersection of two consecutive lines * approximated by FloatPoint's. */ public FloatPoint [] corner_approx_arr() { if (arr.length < 2) { return new FloatPoint[0]; } if (precalculated_float_corners == null) // corner array is not yet allocated { precalculated_float_corners = new FloatPoint[arr.length - 1]; } for (int i = 0; i < precalculated_float_corners.length; ++i) { if (precalculated_float_corners[i] == null) { precalculated_float_corners[i] = arr[i].intersection_approx(arr[i + 1]); } } return precalculated_float_corners; } /** * Returns an approximation of the intersection of the p_no-th with * the (p_no - 1)-th line by a FloatPoint. */ public FloatPoint corner_approx(int p_no) { int no; if (p_no < 0) { System.out.println("Polyline.corner_approx: p_no is < 0"); no = 0; } else if (p_no >= arr.length - 1) { System.out.println("Polyline.corner_approx: p_no must be less than arr.length - 1"); no = arr.length - 2; } else { no = p_no; } if (precalculated_float_corners == null) // corner array is not yet allocated { precalculated_float_corners = new FloatPoint[arr.length - 1]; for (int i = 0; i < precalculated_float_corners.length; ++i) { precalculated_float_corners[i] = null; } } if (precalculated_float_corners [no] == null) // corner is not yet calculated { precalculated_float_corners[no] = arr[no].intersection_approx(arr[no + 1]); } return precalculated_float_corners [no]; } /** * Returns the intersection of the p_no-th with the (p_no - 1)-th edge line. */ public Point corner(int p_no) { if (arr.length < 2) { System.out.println("Polyline.corner: arr.length is < 2"); return null; } int no; if (p_no < 0) { System.out.println("Polyline.corner: p_no is < 0"); no = 0; } else if (p_no >= arr.length - 1) { System.out.println("Polyline.corner: p_no must be less than arr.length - 1"); no = arr.length - 2; } else { no = p_no; } if (precalculated_corners == null) // corner array is not yet allocated { precalculated_corners = new Point[arr.length - 1]; for (int i = 0; i < precalculated_corners.length; ++i) { precalculated_corners[i] = null; } } if (precalculated_corners [no] == null) // corner is not yet calculated { precalculated_corners[no] = arr[no].intersection(arr[no + 1]); } return precalculated_corners [no]; } /** * return the polyline with the reversed order of lines */ public Polyline reverse() { Line [] reversed_lines = new Line[arr.length]; for (int i = 0; i < arr.length; ++i) { reversed_lines[i] = arr[arr.length - i - 1].opposite(); } return new Polyline(reversed_lines); } /** * Calculates the length of this polyline from p_from_corner * to p_to_corner. */ public double length_approx(int p_from_corner, int p_to_corner) { int from_corner = Math.max(p_from_corner, 0); int to_corner = Math.min(p_to_corner, arr.length - 2); double result = 0; for (int i = from_corner; i < to_corner; ++i) { result += this.corner_approx(i + 1).distance(this.corner_approx(i)); } return result; } /** * Calculates the cumulative distance between consecutive corners of * this polyline. */ public double length_approx() { return length_approx(0, arr.length - 2); } /** * calculates for each line a shape around this line * where the right and left edge lines have the distance p_half_width * from the center line * Returns an array of convex shapes of length line_count - 2 */ public TileShape[] offset_shapes(int p_half_width) { return offset_shapes(p_half_width, 0, arr.length -1); } /** * calculates for each line between p_from_no and p_to_no a shape around * this line, where the right and left edge lines have the distance p_half_width * from the center line */ public TileShape[] offset_shapes(int p_half_width, int p_from_no, int p_to_no) { int from_no = Math.max(p_from_no, 0); int to_no = Math.min(p_to_no, arr.length -1); int shape_count = Math.max(to_no - from_no -1, 0); TileShape[] shape_arr = new TileShape[shape_count]; if (shape_count == 0) { return shape_arr; } Vector prev_dir = arr[from_no].direction().get_vector(); Vector curr_dir = arr[from_no + 1].direction().get_vector(); for (int i = from_no + 1; i < to_no; ++i) { Vector next_dir = arr[i + 1].direction().get_vector(); Line[] lines = new Line[4]; lines[0] = arr[i].translate(-p_half_width); // current center line translated to the right // create the front line of the offset shape Side next_dir_from_curr_dir = next_dir.side_of(curr_dir); // left turn from curr_line to next_line if (next_dir_from_curr_dir == Side.ON_THE_LEFT) { lines[1] = arr[i + 1].translate(-p_half_width); // next right line } else { lines[1] = arr[i + 1].opposite().translate(-p_half_width); // next left line in opposite direction } lines[2] = arr[i].opposite().translate(-p_half_width); // current left line in opposite direction // create the back line of the offset shape Side curr_dir_from_prev_dir = curr_dir.side_of(prev_dir); // left turn from prev_line to curr_line if (curr_dir_from_prev_dir == Side.ON_THE_LEFT) { lines[3] = arr[i - 1].translate(-p_half_width); // previous line translated to the right } else { lines[3] = arr[i - 1].opposite().translate(-p_half_width); // previous left line in opposite direction } // cut off outstanding corners with following shapes FloatPoint corner_to_check = null; Line curr_line = lines[1]; Line check_line = null; if (next_dir_from_curr_dir == Side.ON_THE_LEFT) { check_line = lines[2]; } else { check_line = lines[0]; } FloatPoint check_distance_corner = corner_approx(i); final double check_dist_square = 2.0 * p_half_width * p_half_width; Collection<Line> cut_dog_ear_lines = new LinkedList<Line>(); Vector tmp_curr_dir = next_dir; boolean direction_changed = false; for (int j = i + 2; j < arr.length - 1; ++j) { if (corner_approx(j - 1).distance_square(check_distance_corner) > check_dist_square) { break; } if (!direction_changed) { corner_to_check = curr_line.intersection_approx(check_line); } Vector tmp_next_dir = arr[j].direction().get_vector(); Line next_border_line = null; Side tmp_next_dir_from_tmp_curr_dir = tmp_next_dir.side_of(tmp_curr_dir); direction_changed = tmp_next_dir_from_tmp_curr_dir != next_dir_from_curr_dir; if (!direction_changed) { if (tmp_next_dir_from_tmp_curr_dir == Side.ON_THE_LEFT) { next_border_line = arr[j].translate(-p_half_width); } else { next_border_line = arr[j].opposite().translate(-p_half_width); } if (next_border_line.side_of(corner_to_check) == Side.ON_THE_LEFT && next_border_line.side_of(this.corner(i)) == Side.ON_THE_RIGHT && next_border_line.side_of(this.corner(i - 1)) == Side.ON_THE_RIGHT) // an outstanding corner { cut_dog_ear_lines.add(next_border_line); } tmp_curr_dir = tmp_next_dir; curr_line = next_border_line; } } // cut off outstanding corners with previous shapes check_distance_corner = corner_approx(i - 1); if (curr_dir_from_prev_dir == Side.ON_THE_LEFT) { check_line = lines[2]; } else { check_line = lines[0]; } curr_line = lines [3]; tmp_curr_dir = prev_dir; direction_changed = false; for (int j = i - 2; j >= 1; --j) { if (corner_approx(j).distance_square(check_distance_corner) > check_dist_square) { break; } if (!direction_changed) { corner_to_check = curr_line.intersection_approx(check_line); } Vector tmp_prev_dir = arr[j].direction().get_vector(); Line prev_border_line = null; Side tmp_curr_dir_from_tmp_prev_dir = tmp_curr_dir.side_of(tmp_prev_dir); direction_changed = tmp_curr_dir_from_tmp_prev_dir != curr_dir_from_prev_dir; if (!direction_changed) { if (tmp_curr_dir.side_of(tmp_prev_dir) == Side.ON_THE_LEFT) { prev_border_line = arr[j].translate(-p_half_width); } else { prev_border_line = arr[j].opposite().translate(-p_half_width); } if (prev_border_line.side_of(corner_to_check) == Side.ON_THE_LEFT && prev_border_line.side_of(this.corner(i)) == Side.ON_THE_RIGHT && prev_border_line.side_of(this.corner(i - 1)) == Side.ON_THE_RIGHT) // an outstanding corner { cut_dog_ear_lines.add(prev_border_line); } tmp_curr_dir = tmp_prev_dir; curr_line = prev_border_line; } } TileShape s1 = TileShape.get_instance(lines); int cut_line_count = cut_dog_ear_lines.size(); if (cut_line_count > 0) { Line[] cut_lines = new Line[cut_line_count]; Iterator<Line> it = cut_dog_ear_lines.iterator(); for (int j = 0; j < cut_line_count; ++j) { cut_lines[j] = it.next(); } s1 = s1.intersection(TileShape.get_instance(cut_lines)); } int curr_shape_no = i - from_no - 1; TileShape bounding_shape; if (USE_BOUNDING_OCTAGON_FOR_OFFSET_SHAPES) // intersect with the bounding octagon { IntOctagon surr_oct = bounding_octagon(i-1, i); bounding_shape = surr_oct.offset(p_half_width); } else // intersect with the bounding box { IntBox surr_box = bounding_box(i-1, i); IntBox offset_box = surr_box.offset(p_half_width); bounding_shape = offset_box.to_Simplex(); } shape_arr[curr_shape_no] = bounding_shape.intersection_with_simplify(s1); if (shape_arr[curr_shape_no].is_empty()) { System.out.println("offset_shapes: shape is empty"); } prev_dir = curr_dir; curr_dir = next_dir; } return shape_arr; } /** * Calculates for the p_no-th line segment a shape around this line * where the right and left edge lines have the distance p_half_width * from the center line. 0 <= p_no <= arr.length - 3 */ public TileShape offset_shape(int p_half_width, int p_no) { if (p_no < 0 || p_no > arr.length - 3) { System.out.println("Polyline.offset_shape: p_no out of range"); return null; } TileShape[] result = offset_shapes(p_half_width, p_no, p_no + 2); return result[0]; } /** * Calculates for the p_no-th line segment a box shape around this line * where the border lines have the distance p_half_width * from the center line. 0 <= p_no <= arr.length - 3 */ public IntBox offset_box(int p_half_width, int p_no) { LineSegment curr_line_segment = new LineSegment(this, p_no + 1); IntBox result = curr_line_segment.bounding_box().offset(p_half_width); return result; } /** * Returns the by p_vector translated polyline */ public Polyline translate_by(Vector p_vector) { if (p_vector.equals(Vector.ZERO)) { return this; } Line [] new_arr = new Line[arr.length]; for (int i = 0; i < new_arr.length; ++i) { new_arr[i] = arr[i].translate_by(p_vector); } return new Polyline(new_arr); } /** * Returns the polyline turned by p_factor times 90 degree around p_pole. */ public Polyline turn_90_degree(int p_factor, IntPoint p_pole) { Line [] new_arr = new Line[arr.length]; for (int i = 0; i < new_arr.length; ++i) { new_arr[i] = arr[i].turn_90_degree(p_factor, p_pole); } return new Polyline(new_arr); } public Polyline rotate_approx(double p_angle, FloatPoint p_pole) { if (p_angle == 0) { return this; } IntPoint [] new_corners = new IntPoint[this.corner_count()]; for (int i = 0; i < new_corners.length; ++i) { new_corners[i] = this.corner_approx(i).rotate(p_angle, p_pole).round(); } return new Polyline(new_corners); } /** Mirrors this polyline at the vertical line through p_pole */ public Polyline mirror_vertical(IntPoint p_pole) { Line [] new_arr = new Line[arr.length]; for (int i = 0; i < new_arr.length; ++i) { new_arr[i] = arr[i].mirror_vertical(p_pole); } return new Polyline(new_arr); } /** Mirrors this polyline at the horizontal line through p_pole */ public Polyline mirror_horizontal(IntPoint p_pole) { Line [] new_arr = new Line[arr.length]; for (int i = 0; i < new_arr.length; ++i) { new_arr[i] = arr[i].mirror_horizontal(p_pole); } return new Polyline(new_arr); } /** * Returns the smallest box containing the intersection points * from index p_from_corner_no to index p_to_corner_no * of the lines of this polyline */ public IntBox bounding_box(int p_from_corner_no, int p_to_corner_no) { int from_corner_no = Math.max(p_from_corner_no, 0); int to_corner_no = Math.min(p_to_corner_no, arr.length - 2); double llx = Integer.MAX_VALUE; double lly = llx; double urx = Integer.MIN_VALUE; double ury = urx; for (int i = from_corner_no; i <= to_corner_no; ++i) { FloatPoint curr_corner = corner_approx(i); llx = Math.min(llx, curr_corner.x); lly = Math.min(lly, curr_corner.y); urx = Math.max(urx, curr_corner.x); ury = Math.max(ury, curr_corner.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)); return new IntBox(lower_left, upper_right); } /** * Returns the smallest box containing the intersection points * of the lines of this polyline */ public IntBox bounding_box() { if (precalculated_bounding_box == null) { precalculated_bounding_box = bounding_box(0, corner_count() - 1); } return precalculated_bounding_box; } /** * Returns the smallest octagon containing the intersection points * from index p_from_corner_no to index p_to_corner_no * of the lines of this polyline */ public IntOctagon bounding_octagon(int p_from_corner_no, int p_to_corner_no) { int from_corner_no = Math.max(p_from_corner_no, 0); int to_corner_no = Math.min(p_to_corner_no, arr.length - 2); 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 = from_corner_no; i <= to_corner_no; ++i) { FloatPoint curr = corner_approx(i); 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); } IntOctagon surrounding_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 surrounding_octagon; } /** * Calculates an aproximation of the nearest point on this * polyline to p_from_point. */ public FloatPoint nearest_point_approx(FloatPoint p_from_point) { double min_distance = Double.MAX_VALUE; FloatPoint nearest_point = null; // calculate the nearest corner point FloatPoint[] corners = corner_approx_arr(); for (int i = 0; i < corners.length; ++i) { double curr_distance = corners[i].distance(p_from_point); if (curr_distance < min_distance) { min_distance = curr_distance; nearest_point = corners[i]; } } final double c_tolerance = 1; for (int i = 1; i < arr.length - 1; ++i) { FloatPoint projection = p_from_point.projection_approx(arr[i]); double curr_distance = projection.distance(p_from_point); if (curr_distance < min_distance) { // look, if the projection is inside the segment double segment_length = corners[i].distance(corners[i - 1]); if (projection.distance(corners[i]) + projection.distance(corners[i - 1]) < segment_length + c_tolerance) { min_distance = curr_distance; nearest_point = projection; } } } return nearest_point; } /** * Calculates the distance of p_from_point to the the nearest point * on this polyline */ public double distance(FloatPoint p_from_point) { double result = p_from_point.distance(nearest_point_approx(p_from_point)); return result; } /** * Combines the two polylines, if they have a common end corner. * The order of lines in this polyline will be preserved. * Returns the combined polyline or this polyline, if this polyline * and p_other have no common end corner. * If there is something to combine at the start of this polyline, * p_other is inserted in front of this polyline. * If there is something to combine at the end of this polyline, * this polyline is inserted in front of p_other. */ public Polyline combine(Polyline p_other) { if (p_other == null || arr.length < 3 || p_other.arr.length < 3) { return this; } boolean combine_at_start; boolean combine_other_at_start; if (first_corner().equals(p_other.first_corner())) { combine_at_start = true; combine_other_at_start = true; } else if (first_corner().equals(p_other.last_corner())) { combine_at_start = true; combine_other_at_start = false; } else if (last_corner().equals(p_other.first_corner())) { combine_at_start = false; combine_other_at_start = true; } else if (last_corner().equals(p_other.last_corner())) { combine_at_start = false; combine_other_at_start = false; } else { return this; // no common endpoint } Line [] line_arr = new Line [arr.length + p_other.arr.length - 2]; if (combine_at_start) { // insert the lines of p_other in front if (combine_other_at_start) { // insert in reverse order, skip the first line of p_other for (int i = 0; i < p_other.arr.length - 1; ++i) { line_arr[i] = p_other.arr[p_other.arr.length - i - 1].opposite(); } } else { // skip the last line of p_other for (int i = 0; i < p_other.arr.length - 1; ++i) { line_arr[i] = p_other.arr[i]; } } // append the lines of this polyline, skip the first line for (int i = 1; i < arr.length; ++i) { line_arr[p_other.arr.length + i - 2] = arr[i]; } } else { // insert the lines of this polyline in front, skip the last line for (int i = 0; i < arr.length - 1; ++i) { line_arr[i] = arr[i]; } if (combine_other_at_start) { // skip the first line of p_other for (int i = 1; i < p_other.arr.length; ++i) { line_arr[arr.length + i - 2] = p_other.arr[i]; } } else { // insert in reverse order, skip the last line of p_other for (int i = 1; i < p_other.arr.length; ++i) { line_arr[arr.length + i - 2] = p_other.arr[p_other.arr.length - i - 1].opposite(); } } } return new Polyline(line_arr); } /** * Splits this polyline at the line with number p_line_no * into two by inserting p_endline as concluding line of the first split piece * and as the start line of the second split piece. * p_endline and the line with number p_line_no must not be parallel. * The order of the lines ins the two result pieces is preserved. * p_line_no must be bigger than 0 and less then arr.length - 1. * Returns null, if nothing was split. */ public Polyline[] split(int p_line_no, Line p_end_line) { if (p_line_no < 1 || p_line_no > arr.length - 2) { System.out.println("Polyline.split: p_line_no out of range"); return null; } if (this.arr[p_line_no].is_parallel(p_end_line)) { return null; } Point new_end_corner = this.arr[p_line_no].intersection(p_end_line); if (p_line_no <= 1 && new_end_corner.equals(this.first_corner()) || p_line_no >= arr.length - 2 && new_end_corner.equals(this.last_corner())) { // No split, if p_end_line does not intersect, but touches // only tnis Polyline at an end point. return null; } Line[] first_piece; if (this.corner(p_line_no - 1).equals(new_end_corner)) { // skip line segment of length 0 at the end of the first piece first_piece = new Line [p_line_no + 1]; System.arraycopy(arr, 0, first_piece, 0, first_piece.length); } else { first_piece = new Line [p_line_no + 2]; System.arraycopy(arr, 0, first_piece, 0, p_line_no + 1); first_piece[p_line_no + 1] = p_end_line; } Line[] second_piece; if (this.corner(p_line_no).equals(new_end_corner)) { // skip line segment of length 0 at the beginning of the second piece second_piece = new Line [arr.length - p_line_no]; System.arraycopy(this.arr, p_line_no,second_piece, 0, second_piece.length); } else { second_piece = new Line [arr.length - p_line_no + 1]; second_piece[0] = p_end_line; System.arraycopy(this.arr, p_line_no, second_piece, 1, second_piece.length - 1); } Polyline [] result = new Polyline[2]; result[0] = new Polyline(first_piece); result[1] = new Polyline(second_piece); if (result[0].is_point() || result[1].is_point()) { return null; } return result; } /** * create a new Polyline by skipping the lines of this Polyline * from p_from_no to p_to_no */ public Polyline skip_lines(int p_from_no, int p_to_no) { if (p_from_no < 0 || p_to_no > arr.length - 1 || p_from_no > p_to_no) { return this; } Line [] new_lines = new Line [arr.length - (p_to_no - p_from_no + 1)]; System.arraycopy(arr, 0, new_lines, 0, p_from_no); System.arraycopy(arr, p_to_no + 1, new_lines, p_from_no, new_lines.length - p_from_no); return new Polyline(new_lines); } public boolean contains(Point p_point) { for (int i = 1; i < arr.length - 1; ++i) { LineSegment curr_segment = new LineSegment(this, i); if (curr_segment.contains(p_point)) { return true; } } return false; } /** * Creates a perpendicular line segment from p_from_point onto the nearest * line segment of this polyline to p_from_side. * Returns null, if the perpendicular line does not intersect the neares line * segment inside its segment bounds or if p_from_point is contained in * this polyline. */ public LineSegment projection_line(Point p_from_point) { FloatPoint from_point = p_from_point.to_float(); double min_distance = Double.MAX_VALUE; Line result_line = null; Line nearest_line = null; for (int i = 1; i < arr.length - 1; ++i) { FloatPoint projection = from_point.projection_approx(arr[i]); double curr_distance = projection.distance(from_point); if (curr_distance < min_distance) { Direction direction_towards_line = this.arr[i].perpendicular_direction(p_from_point); if (direction_towards_line == null) { continue; } Line curr_result_line = new Line(p_from_point, direction_towards_line); Point prev_corner = this.corner(i - 1); Point next_corner = this.corner(i); Side prev_corner_side = curr_result_line.side_of(prev_corner); Side next_corner_side = curr_result_line.side_of(next_corner); if (prev_corner_side != Side.COLLINEAR && next_corner_side != Side.COLLINEAR && prev_corner_side == next_corner_side) { // the projection point is outside the line segment continue; } nearest_line = this.arr[i]; min_distance = curr_distance; result_line = curr_result_line; } } if (nearest_line == null) { return null; } Line start_line = new Line(p_from_point, nearest_line.direction()); LineSegment result = new LineSegment(start_line, result_line, nearest_line); return result; } /** * Shortens this polyline to p_new_line_count lines. Additioanally * the last line segment will be approximately shortened to p_new_length. * The last corner of the new polyline will be an IntPoint. */ public Polyline shorten(int p_new_line_count, double p_last_segment_length) { FloatPoint last_corner = this.corner_approx(p_new_line_count - 2); FloatPoint prev_last_corner = this.corner_approx(p_new_line_count - 3); IntPoint new_last_corner = prev_last_corner.change_length(last_corner, p_last_segment_length).round(); if (new_last_corner.equals(this.corner(this.corner_count() - 2))) { // skip the last line return skip_lines( p_new_line_count - 1, p_new_line_count - 1); } Line[] new_lines = new Line [p_new_line_count]; System.arraycopy(arr, 0, new_lines, 0, p_new_line_count - 2); // create the last 2 lines of the new polyline Point first_line_point = arr[p_new_line_count - 2].a; if (first_line_point.equals(new_last_corner)) { first_line_point = arr[p_new_line_count - 2].b; } Line new_prev_last_line = new Line(first_line_point, new_last_corner); new_lines[p_new_line_count - 2] = new_prev_last_line; new_lines[p_new_line_count - 1] = Line.get_instance(new_last_corner, new_prev_last_line.direction().turn_45_degree(6)); return new Polyline(new_lines); } private static Line[] remove_consecutive_parallel_lines( Line [] p_line_arr) { if (p_line_arr.length < 3) { // polyline must have at least 3 lines return p_line_arr; } Line [] tmp_arr = new Line [p_line_arr.length]; int new_length = 0; tmp_arr[0] = p_line_arr [0]; for (int i = 1; i < p_line_arr.length; ++i) { // skip multiple lines if (!tmp_arr[new_length].is_parallel(p_line_arr[i])) { ++new_length; tmp_arr[new_length] = p_line_arr[i]; } } ++new_length; if (new_length == p_line_arr.length) { // nothing skipped return p_line_arr; } // at least 1 line is skipped, adjust the array if (new_length < 3) { return new Line[0]; } Line [] result = new Line[new_length]; System.arraycopy(tmp_arr, 0, result, 0, new_length); return result; } /** * checks if previous and next line are equal or opposite and * removes the resulting overlap */ private static Line [] remove_overlaps(Line [] p_line_arr) { if (p_line_arr.length < 4) { return p_line_arr; } int new_length = 0; Line [] tmp_arr = new Line [p_line_arr.length]; tmp_arr[0] = p_line_arr[0]; if (!p_line_arr[0].is_equal_or_opposite(p_line_arr[2])) { ++new_length; } // else skip the first line tmp_arr[new_length] = p_line_arr[1]; ++new_length; for (int i = 2; i < p_line_arr.length - 2; ++i) { if (tmp_arr[new_length - 1].is_equal_or_opposite(p_line_arr [i + 1])) { // skip 2 lines --new_length; } else { tmp_arr[new_length] = p_line_arr [i]; ++new_length; } } tmp_arr [new_length] = p_line_arr[p_line_arr.length - 2]; ++new_length; if (!p_line_arr[p_line_arr.length - 1].is_equal_or_opposite(tmp_arr[new_length - 2])) { tmp_arr[new_length] = p_line_arr[p_line_arr.length - 1]; ++new_length; } // else skip the last line if (new_length == p_line_arr.length) { // nothing skipped return p_line_arr; } // at least 1 line is skipped, adjust the array if (new_length < 3) { return new Line[0]; } Line [] result = new Line[new_length]; System.arraycopy(tmp_arr, 0, result, 0, new_length); return result; } /** * the array of lines of this Polyline. */ public final Line[] arr; transient private FloatPoint[] precalculated_float_corners = null; transient private Point[] precalculated_corners = null; transient private IntBox precalculated_bounding_box = null; private static final boolean USE_BOUNDING_OCTAGON_FOR_OFFSET_SHAPES = true; }