/* * 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 datastructures.Signum; /** * Implements functionality for line segments. * The difference between a LineSegment and a * Line is, that a Line is infinite and a * LineSegment has a start and an endpoint. * * * @author Alfons Wirtz */ public class LineSegment implements java.io.Serializable { /** * Creates a line segment from the 3 input lines. * It starts at the intersection of p_start_line and p_middle_line * and ends at the intersection of p_middle_line and p_end_line. * p_start_line and p_end_line must not be parallel to p_middle_line. */ public LineSegment(Line p_start_line, Line p_middle_line, Line p_end_line) { start = p_start_line; middle = p_middle_line; end = p_end_line; } /** * creates the p_no-th line segment of p_polyline * for p_no between 1 and p_polyline.line_count - 2. */ public LineSegment(Polyline p_polyline, int p_no) { if (p_no <= 0 || p_no >= p_polyline.arr.length - 1) { System.out.println("LineSegment from Polyline: p_no out of range"); start = null; middle = null; end = null; return; } start = p_polyline.arr[p_no - 1]; middle = p_polyline.arr[p_no]; end = p_polyline.arr[p_no + 1]; } /** * Creates the p_no-th line segment of p_shape * for p_no between 0 and p_shape.line_count - 1. */ public LineSegment(PolylineShape p_shape, int p_no) { int line_count = p_shape.border_line_count(); if (p_no < 0 || p_no >= line_count) { System.out.println("LineSegment from TileShape: p_no out of range"); start = null; middle = null; end = null; return; } if (p_no == 0) { start = p_shape.border_line(line_count - 1); } else { start = p_shape.border_line(p_no - 1); } middle = p_shape.border_line(p_no); if (p_no == line_count - 1) { end = p_shape.border_line(0); } else { end = p_shape.border_line(p_no + 1); } } /** * Returns the intersection of the first 2 lines of this segment */ public Point start_point() { if (precalculated_start_point == null) { precalculated_start_point = middle.intersection(start); } return precalculated_start_point; } /** * Returns the intersection of the last 2 lines of this segment */ public Point end_point() { if (precalculated_end_point == null) { precalculated_end_point = middle.intersection(end); } return precalculated_end_point; } /** * Returns an approximation of the intersection of the first 2 lines of this segment */ public FloatPoint start_point_approx() { FloatPoint result; if (precalculated_start_point != null) { result = precalculated_start_point.to_float(); } else { result = this.start.intersection_approx(this.middle); } return result; } /** * Returns an approximation of the intersection of the last 2 lines of this segment */ public FloatPoint end_point_approx() { FloatPoint result; if (precalculated_end_point != null) { result = precalculated_end_point.to_float(); } else { result = this.end.intersection_approx(this.middle); } return result; } /** * Returns the (infinite) line of this segment. */ public Line get_line() { return middle; } /** * Returns the start closing line of this segment. */ public Line get_start_closing_line() { return start; } /** * Returns the end closing line of this segment. */ public Line get_end_closing_line() { return end; } /** * Returns the line segment with tje opposite direction. */ public LineSegment opposite() { return new LineSegment(end.opposite(), middle.opposite(), start.opposite()); } /** * Transforms this LinsSegment into a polyline of lenght 3. */ public Polyline to_polyline() { Line[] lines = new Line[3]; lines[0] = start; lines[1] = middle; lines[2] = end; return new Polyline(lines); } /** * Creates a 1 dimensional simplex rom this line segment, which * has the same shape as the line sgment. */ public Simplex to_simplex() { Line[] line_arr = new Line[4]; if (this.end_point().side_of(this.start) == Side.ON_THE_RIGHT) { line_arr[0] = this.start.opposite(); } else { line_arr[0] = this.start; } line_arr[1] = this.middle; line_arr[2] = this.middle.opposite(); if (this.start_point().side_of(this.end) == Side.ON_THE_RIGHT) { line_arr[3] = this.end.opposite(); } else { line_arr[3] = this.end; } Simplex result = Simplex.get_instance(line_arr); return result; } /** * Checks if p_point is contained in this line segment */ public boolean contains(Point p_point) { if (!(p_point instanceof IntPoint)) { System.out.println("LineSegments.contains currently only implementet for IntPoints"); return false; } if (middle.side_of(p_point) != Side.COLLINEAR) { return false; } // create a perpendicular line at p_point and check, that the two // endpoints of this segment are on difcferent sides of that line. Direction perpendicular_direction = middle.direction().turn_45_degree(2); Line perpendicular_line = new Line(p_point, perpendicular_direction); Side start_point_side = perpendicular_line.side_of(this.start_point()); Side end_point_side = perpendicular_line.side_of(this.end_point()); if (start_point_side != Side.COLLINEAR && end_point_side != Side.COLLINEAR && start_point_side == end_point_side) { return false; } return true; } /** * calculates the smallest surrounding box of this line segmant */ public IntBox bounding_box() { FloatPoint start_corner = middle.intersection_approx(start); FloatPoint end_corner = middle.intersection_approx(end); double llx = Math.min(start_corner.x, end_corner.x); double lly = Math.min(start_corner.y, end_corner.y); double urx = Math.max(start_corner.x, end_corner.x); double ury = Math.max(start_corner.y, end_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); } /** * calculates the smallest surrounding octagon of this line segmant */ public IntOctagon bounding_octagon() { FloatPoint start_corner = middle.intersection_approx(start); FloatPoint end_corner = middle.intersection_approx(end); double lx = Math.floor(Math.min(start_corner.x, end_corner.x)); double ly = Math.floor(Math.min(start_corner.y, end_corner.y)); double rx = Math.ceil(Math.max(start_corner.x, end_corner.x)); double uy = Math.ceil(Math.max(start_corner.y, end_corner.y)); double start_x_minus_y = start_corner.x - start_corner.y; double end_x_minus_y = end_corner.x - end_corner.y; double ulx = Math.floor(Math.min(start_x_minus_y, end_x_minus_y)); double lrx = Math.ceil(Math.max(start_x_minus_y, end_x_minus_y)); double start_x_plus_y = start_corner.x + start_corner.y; double end_x_plus_y = end_corner.x + end_corner.y; double llx = Math.floor(Math.min(start_x_plus_y, end_x_plus_y)); double urx = Math.ceil(Math.max(start_x_plus_y, end_x_plus_y)); IntOctagon result = new IntOctagon((int) lx, (int) ly, (int) rx, (int) uy, (int) ulx, (int) lrx, (int) llx, (int) urx); return result.normalize(); } /** * Creates a new line segment with the same start and middle line and an end line, * so that the length of the new line segment is about p_new_length. */ public LineSegment change_length_approx(double p_new_length) { FloatPoint new_end_point = start_point_approx().change_length(end_point_approx(), p_new_length); Direction perpendicular_direction = this.middle.direction().turn_45_degree(2); Line new_end_line = new Line(new_end_point.round(), perpendicular_direction); LineSegment result = new LineSegment(this.start, this.middle, new_end_line); return result; } /** * Looks up the intersections of this line segment with p_other. * The result array may have length 0, 1 or 2. * If the segments do not intersect the result array will have length 0. * The result lines are so that the intersections of the result lines with * this line segment will deliver the intersection points. * If the segments overlap, the result array has length 2 and the intersection * points are the first and the last overlap point. * Otherwise the result array has length 1 and the intersection point is the * the unique intersection or touching point. * The result is not symmetric in this and p_other, because intersecting lines * and not the intersection points are returned. */ public Line[] intersection(LineSegment p_other) { if (!this.bounding_box().intersects(p_other.bounding_box())) { return new Line[0]; } Side start_point_side = start_point().side_of(p_other.middle); Side end_point_side = end_point().side_of(p_other.middle); if (start_point_side == Side.COLLINEAR && end_point_side == Side.COLLINEAR) { // there may be an overlap LineSegment this_sorted = this.sort_endpoints_in_x_y(); LineSegment other_sorted = p_other.sort_endpoints_in_x_y(); LineSegment left_line; LineSegment right_line; if (this_sorted.start_point().compare_x_y(other_sorted.start_point()) <= 0) { left_line = this_sorted; right_line = other_sorted; } else { left_line = other_sorted; right_line = this_sorted; } int cmp = left_line.end_point().compare_x_y(right_line.start_point()); if (cmp < 0) { // end point of the left line is to the lsft of the start point of the right line return new Line[0]; } if (cmp == 0) { // end point of the left line is equal to the start point of the right line Line[] result = new Line[1]; result[0] = left_line.end; return result; } // now there is a real overlap Line[] result = new Line[2]; result[0] = right_line.start; if (right_line.end_point().compare_x_y(left_line.end_point()) >= 0) { result[1] = left_line.end; } else { result[1] = right_line.end; } return result; } if (start_point_side == end_point_side || p_other.start_point().side_of(this.middle) == p_other.end_point().side_of(this.middle)) { return new Line[0]; // no intersection possible } // now both start points and both end points are on different sides of the middle // line of the other segment. Line[] result = new Line[1]; result[0] = p_other.middle; return result; } /** * Checks if this LineSegment and p_other contain a commen point */ public boolean intersects(LineSegment p_other) { Line[] intersections = this.intersection(p_other); return intersections.length > 0; } /** * Checks if this LineSegment and p_other contain a common LineSegment, * which is not reduced to a point. */ public boolean overlaps(LineSegment p_other) { Line[] intersections = this.intersection(p_other); return intersections.length > 1; } /** * Constructs an approximation of this line segment by orthogonal stairs with integer coordinates. * The length of the stairs will be at most p_stair_width. * If p_to_the_right, the stairs will be to the right of this line segment, else to the left. */ public IntPoint[] stair_approximation(double p_width, boolean p_to_the_right) { IntPoint start_point = this.start_point().to_float().round(); IntPoint end_point = this.end_point().to_float().round(); if (start_point.equals(end_point)) { return new IntPoint[0]; } if (start_point.x == end_point.x || start_point.y == end_point.y) { IntPoint[] result = new IntPoint[2]; result[0] = start_point; result[1] = end_point; return result; } int dx = end_point.x - start_point.x; int dy = end_point.y - start_point.y; int abs_dx = Math.abs(dx); int abs_dy = Math.abs(dy); boolean function_of_x = abs_dx >= abs_dy; // use otherwise function of y for better numerical stability int stair_width; int stair_count; if (function_of_x) { stair_width = (int) Math.round(((p_width * (double) abs_dx) / (double) abs_dy)); stair_count = (abs_dx - 1) / stair_width + 1; if (end_point.x < start_point.x) { stair_width = -stair_width; } } else { stair_width = (int) Math.round((p_width * (double) abs_dy) / (double) abs_dx); stair_count = (abs_dy - 1) / stair_width + 1; if (end_point.y < start_point.y) { stair_width = -stair_width; } } IntPoint[] result = new IntPoint[2 * stair_count + 1]; result[0] = start_point; double det = (double) dx * (double) dy; boolean change_x_first = p_to_the_right && det > 0 || !p_to_the_right && det < 0; int curr_index = 0; int prev_line_point_x = start_point.x; int prev_line_point_y = start_point.y; for (int i = 1; i < stair_count; ++i) { int curr_line_point_x; int curr_line_point_y; if (function_of_x) { curr_line_point_x = start_point.x + i * stair_width; curr_line_point_y = (int) Math.round(this.get_line().function_value_approx(curr_line_point_x)); } else { curr_line_point_y = start_point.y + i * stair_width; curr_line_point_x = (int) Math.round(this.get_line().function_in_y_value_approx(curr_line_point_y)); } ++curr_index; if (change_x_first) { result[curr_index] = new IntPoint(curr_line_point_x, prev_line_point_y); } else { result[curr_index] = new IntPoint(prev_line_point_x, curr_line_point_y); } ++curr_index; result[curr_index] = new IntPoint(curr_line_point_x, curr_line_point_y); prev_line_point_x = curr_line_point_x; prev_line_point_y = curr_line_point_y; } ++curr_index; if (change_x_first) { result[curr_index] = new IntPoint(end_point.x, prev_line_point_y); } else { result[curr_index] = new IntPoint(prev_line_point_x, end_point.y); } ++curr_index; result[curr_index] = end_point; return result; } /** * Constructs an approximation of this line segment by 45 degree stairs with integer coordinates. * The length of the stairs will be at most p_stair_width. * If p_to_the_right, the stairs will be to the right of this line segment, else to the left. */ public IntPoint[] stair_approximation_45(double p_width, boolean p_to_the_right) { IntPoint start_point = this.start_point().to_float().round(); IntPoint end_point = this.end_point().to_float().round(); if (start_point.equals(end_point)) { return new IntPoint[0]; } IntVector delta = end_point.difference_by(start_point); if (delta.is_multiple_of_45_degree()) { IntPoint[] result = new IntPoint[2]; result[0] = start_point; result[1] = end_point; return result; } IntVector abs_delta = new IntVector(Math.abs(delta.x), Math.abs(delta.y)); boolean function_of_x = abs_delta.x >= abs_delta.y; // use otherwise function of y for better numerical stability double det = (double) delta.x * (double) delta.y; int stair_width; int stair_count; if (function_of_x) { stair_width = (int) Math.round((p_width * (double) abs_delta.x) / (double) abs_delta.y); stair_count = (abs_delta.x - 1) / stair_width + 1; if (end_point.x < start_point.x) { stair_width = -stair_width; } } else { stair_width = (int) Math.round((p_width * (double) abs_delta.y) / (double) abs_delta.x); stair_count = (abs_delta.y - 1) / stair_width + 1; if (end_point.y < start_point.y) { stair_width = -stair_width; } } IntPoint[] result = new IntPoint[2 * stair_count + 1]; result[0] = start_point; IntPoint prev_line_point = start_point; int curr_index = 0; for (int i = 1; i <= stair_count; ++i) { IntPoint curr_line_point; int curr_x; int curr_y; if (i == stair_count) { curr_line_point = end_point; } else { if (function_of_x) { curr_x = start_point.x + i * stair_width; curr_y = (int) Math.round(this.get_line().function_value_approx(curr_x)); } else { curr_y = start_point.y + i * stair_width; curr_x = (int) Math.round(this.get_line().function_value_approx(curr_y)); } curr_line_point = new IntPoint(curr_x, curr_y); } if (function_of_x) { boolean diagonal_first = p_to_the_right && det < 0 || !p_to_the_right && det > 0; if (diagonal_first) { curr_x = prev_line_point.x + Signum.as_int(stair_width) * Math.abs(curr_line_point.y - prev_line_point.y); curr_y = curr_line_point.y; } else // horizontal first { curr_x = curr_line_point.x - Signum.as_int(stair_width) * Math.abs(curr_line_point.y - prev_line_point.y); curr_y = prev_line_point.y; } } else // function of y { boolean diagonal_first = p_to_the_right && det > 0 || !p_to_the_right && det < 0; if (diagonal_first) { curr_x = curr_line_point.x; curr_y = prev_line_point.y + Signum.as_int(stair_width) * Math.abs(curr_line_point.x - prev_line_point.x); } else { curr_x = prev_line_point.x; curr_y = curr_line_point.y - Signum.as_int(stair_width) * Math.abs(curr_line_point.x - prev_line_point.x); } } ++curr_index; result[curr_index] = new IntPoint (curr_x, curr_y); ++curr_index; result[curr_index] = curr_line_point; prev_line_point = curr_line_point; } return result; } /** * Returns an array with the borderline numbers of p_shape, * which are intersected by this line segment. Intersections at an endpoint * of this line segment are only counted, if the line segment intersects * with the interiour of p_shape. * The result array may have lenght 0, 1 or 2. * With 2 intersections the intersection which is nearest to the start * point of the line segment comes first. */ public int[] border_intersections(TileShape p_shape) { int[] empty_result = new int[0]; if (!this.bounding_box().intersects(p_shape.bounding_box())) { return empty_result; } int edge_count = p_shape.border_line_count(); Line prev_line = p_shape.border_line(edge_count - 1); Line curr_line = p_shape.border_line(0); int[] result = new int[2]; Point[] intersection = new Point[2]; int intersection_count = 0; Point line_start = this.start_point(); Point line_end = this.end_point(); for (int edge_line_no = 0; edge_line_no < edge_count; ++edge_line_no) { Line next_line; if (edge_line_no == edge_count - 1) { next_line = p_shape.border_line(0); } else { next_line = p_shape.border_line(edge_line_no + 1); } Side start_point_side = curr_line.side_of(line_start); Side end_point_side = curr_line.side_of(line_end); if (start_point_side == Side.ON_THE_LEFT && end_point_side == Side.ON_THE_LEFT) { // both endpoints are outside the border_line, // no intersection possible return empty_result; } if (start_point_side == Side.COLLINEAR) { // the start is on curr_line, check that the end point is inside // the halfplane, because touches count only, if the interiour // is entered if (end_point_side != Side.ON_THE_RIGHT) { return empty_result; } } if (end_point_side == Side.COLLINEAR) { // the end is on curr_line, check that the start point is inside // the halfplane, because touches count only, if the interiour // is entered if (start_point_side != Side.ON_THE_RIGHT) { return empty_result; } } if (start_point_side != Side.ON_THE_RIGHT || end_point_side != Side.ON_THE_RIGHT) { // not both points are inside the halplane defined by curr_line Point is = this.middle.intersection(curr_line); Side prev_line_side_of_is = prev_line.side_of(is); Side next_line_side_of_is = next_line.side_of(is); if (prev_line_side_of_is != Side.ON_THE_LEFT && next_line_side_of_is != Side.ON_THE_LEFT) { // this line segment intersects curr_line between the // previous and the next corner of p_simplex if (prev_line_side_of_is == Side.COLLINEAR) { // this line segment goes through the previous // corner of p_simplex. Check, that the intersection // isn't merely a touch. Point prev_prev_corner; if (edge_line_no == 0) { prev_prev_corner = p_shape.corner(edge_count - 1); } else { prev_prev_corner = p_shape.corner(edge_line_no - 1); } Point next_corner; if (edge_line_no == edge_count - 1) { next_corner = p_shape.corner(0); } else { next_corner = p_shape.corner(edge_line_no + 1); } // check, that prev_prev_corner and next_corner // are on different sides of this line segment. Side prev_prev_corner_side = this.middle.side_of(prev_prev_corner); Side next_corner_side = this.middle.side_of(next_corner); if (prev_prev_corner_side == Side.COLLINEAR || next_corner_side == Side.COLLINEAR || prev_prev_corner_side == next_corner_side) { return empty_result; } } if (next_line_side_of_is == Side.COLLINEAR) { // this line segment goes through the next // corner of p_simplex. Check, that the intersection // isn't merely a touch. Point prev_corner = p_shape.corner(edge_line_no); Point next_next_corner; if (edge_line_no == edge_count - 2) { next_next_corner = p_shape.corner(0); } else if (edge_line_no == edge_count - 1) { next_next_corner = p_shape.corner(1); } else { next_next_corner = p_shape.corner(edge_line_no + 2); } // check, that prev_corner and next_next_corner // are on different sides of this line segment. Side prev_corner_side = this.middle.side_of(prev_corner); Side next_next_corner_side = this.middle.side_of(next_next_corner); if (prev_corner_side == Side.COLLINEAR || next_next_corner_side == Side.COLLINEAR || prev_corner_side == next_next_corner_side) { return empty_result; } } boolean intersection_already_handeled = false; for (int i = 0; i < intersection_count; ++i) { if (is.equals(intersection[i])) { intersection_already_handeled = true; break; } } if (!intersection_already_handeled) { if (intersection_count < result.length) { // a new intersection is found result[intersection_count] = edge_line_no; intersection[intersection_count] = is; ++intersection_count; } else { System.out.println("border_intersections: intersection_count to big!"); } } } } prev_line = curr_line; curr_line = next_line; } if (intersection_count == 0) { return empty_result; } if (intersection_count == 2) { // assure the correct order FloatPoint is0 = intersection[0].to_float(); FloatPoint is1 = intersection[1].to_float(); FloatPoint curr_start = line_start.to_float(); if (curr_start.distance_square(is1) < curr_start.distance_square(is0)) // swap the result points { int tmp = result[0]; result[0] = result[1]; result[1] = tmp; } return result; } if (intersection_count != 1) { System.out.println( "LineSegment.border_intersections: intersection_count 1 expected"); } int[] normalised_result = new int[1]; normalised_result[0] = result[0]; return normalised_result; } /** * Inverts the direction of this.middle, if start_point() has a bigger * x coordinate than end_point(), or an equal x coordinate and a bigger y coordinate. */ public LineSegment sort_endpoints_in_x_y() { boolean swap_endlines = (start_point().compare_x_y(end_point()) > 0); LineSegment result; if (swap_endlines) { result = new LineSegment(this.end, this.middle, this.start); result.precalculated_start_point = this.precalculated_end_point; result.precalculated_end_point = this.precalculated_start_point; } else { result = this; } return result; } private final Line start; private final Line middle; private final Line end; transient private Point precalculated_start_point = null; transient private Point precalculated_end_point = null; }