/* * 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.math.BigInteger; import datastructures.Signum; /** * * Implements functionality for lines in the plane. * * @author Alfons Wirtz */ public class Line implements Comparable<Line>, java.io.Serializable { /** * creates a directed Line from two Points */ public Line(Point p_a, Point p_b) { a = p_a; b = p_b; dir = null; if (!(a instanceof IntPoint && b instanceof IntPoint)) { System.out.println("Line(p_a, p_b) only implemented for IntPoints till now"); } } /** * creates a directed Line from four integer Coordinates */ public Line(int p_a_x, int p_a_y, int p_b_x, int p_b_y) { a = new IntPoint(p_a_x, p_a_y); b = new IntPoint(p_b_x, p_b_y); dir = null; } /** * creates a directed Line from a Point and a Direction */ public Line(Point p_a, Direction p_dir) { a = p_a; b = p_a.translate_by(p_dir.get_vector()); dir = p_dir; if (!(a instanceof IntPoint && b instanceof IntPoint)) { System.out.println("Line(p_a, p_dir) only implemented for IntPoints till now"); } } /** * create a directed line from an IntPoint and an IntDirection */ public static Line get_instance(Point p_a, Direction p_dir) { Point b = p_a.translate_by(p_dir.get_vector()); return new Line(p_a, b); } /** * returns true, if this and p_ob define the same line */ public final boolean equals( Object p_ob ) { if ( this == p_ob ) { return true; } if ( p_ob == null ) { return false; } if (!(p_ob instanceof Line)) { return false; } Line other = (Line)p_ob ; if (side_of(other.a) != Side.COLLINEAR) { return false; } return direction().equals(other.direction()); } /** * Returns true, if this and p_other define the same line. * Is designed for good performance, but * works only for lines consisting of IntPoints. */ public final boolean fast_equals(Line p_other) { IntPoint this_a = (IntPoint)a; IntPoint this_b = (IntPoint)b; IntPoint other_a = (IntPoint)p_other.a; double dx1 = other_a.x - this_a.x; double dy1 = other_a.y - this_a.y; double dx2 = this_b.x - this_a.x; double dy2 = this_b.y - this_a.y; double det = dx1 * dy2 - dx2 * dy1; if (det != 0) { return false; } return direction().equals(p_other.direction()); } /** * get the direction of this directed line */ public Direction direction() { if (dir == null) { Vector d = b.difference_by(a); dir = Direction.get_instance(d); } return dir; } /** * The function returns * Side.ON_THE_LEFT, if this Line is on the left of p_point, * Side.ON_THE_RIGHT, if this Line is on the right of p_point * and Side.COLLINEAR, if this Line contains p_point. */ public Side side_of(Point p_point) { Side result = p_point.side_of(this); return result.negate(); } /** * Returns Side.COLLINEAR, if p_point is on the line with tolerance p_tolerance. * Otherwise Side.ON_THE_LEFT, if this line is on the left of p_point, * or Side.ON_THE_RIGHT, if this line is on the right of p_point, */ public Side side_of(FloatPoint p_point, double p_tolerance) { // only implemented for IntPoint lines for performance reasons IntPoint this_a = (IntPoint)a; IntPoint this_b = (IntPoint)b; double det = (this_b.y - this_a.y) * (p_point.x - this_a.x) - (this_b.x - this_a.x) * (p_point.y - this_a.y); Side result; if (det - p_tolerance > 0) { result = Side.ON_THE_LEFT; } else if (det + p_tolerance < 0) { result = Side.ON_THE_RIGHT; } else { result = Side.COLLINEAR; } return result; } /** * returns Side.ON_THE_LEFT, if this line is on the left of p_point, * Side.ON_THE_RIGHT, if this line is on the right of p_point, * Side.COLLINEAR otherwise. */ public Side side_of(FloatPoint p_point) { return side_of(p_point, 0); } /** * Returns Side.ON_THE_LEFT, if this line is on the left of the intersection * of p_1 and p_2, Side.ON_THE_RIGHT, if this line is on the right of the intersection, * and Side.COLLINEAR, if all 3 lines intersect in exacly 1 point. */ public Side side_of_intersection(Line p_1, Line p_2) { FloatPoint intersection_approx = p_1.intersection_approx(p_2); Side result = this.side_of(intersection_approx, 1.0); if (result == Side.COLLINEAR) { // Previous calculation was with FloatPoints and a tolerance // for performance reasons. Make an exact check for // collinearity now with class Point instead of FloatPoint. Point intersection = p_1.intersection(p_2); result = this.side_of(intersection); } return result; } /** * Looks, if all interiour points of p_tile are on the right side of this line. */ public boolean is_on_the_left(TileShape p_tile) { for ( int i = 0; i < p_tile.border_line_count(); ++i) { if (this.side_of(p_tile.corner(i)) == Side.ON_THE_RIGHT) { return false; } } return true; } /** * Looks, if all interiour points of p_tile are on the left side of this line. */ public boolean is_on_the_right(TileShape p_tile) { for ( int i = 0; i < p_tile.border_line_count(); ++i) { if (this.side_of(p_tile.corner(i)) == Side.ON_THE_LEFT) { return false; } } return true; } /** * Returns the signed distance of this line from p_point. * The result will be positive, if the line is on the left of p_point, * else negative. */ public double signed_distance(FloatPoint p_point) { // only implemented for IntPoint lines for performance reasons IntPoint this_a = (IntPoint)a; IntPoint this_b = (IntPoint)b; double dx = this_b.x - this_a.x; double dy = this_b.y - this_a.y; double det = dy * (p_point.x - this_a.x) - dx * (p_point.y - this_a.y); // area of the parallelogramm spanned by the 3 points double length = Math.sqrt(dx * dx + dy * dy); return det / length; } /** * returns true, if the 2 lines defins the same set of points, but may * have opposite directions */ public boolean overlaps(Line p_other) { return side_of(p_other.a) == Side.COLLINEAR && side_of(p_other.b) == Side.COLLINEAR; } /** * Returns the line defining the same set of points, but * with opposite direction */ public Line opposite() { return new Line(b, a); } /** * Returns the intersection point of the 2 lines. * If the lines are parallel result.is_infinite() will be true. */ public Point intersection(Line p_other) { // this function is at the moment only implemented for lines // consisting of IntPoints. // The general implementation is still missing. IntVector delta_1 = (IntVector)b.difference_by(a); IntVector delta_2 = (IntVector)p_other.b.difference_by(p_other.a); // Separate handling for orthogonal and 45 degree lines for better perpormance if (delta_1.x == 0 ) // this line is vertical { if (delta_2.y == 0) // other line is horizontal { return new IntPoint(((IntPoint)this.a).x, ((IntPoint)p_other.a).y); } if (delta_2.x == delta_2.y) // other line is right diagonal { int this_x = ((IntPoint)this.a).x; IntPoint other_a = (IntPoint) p_other.a; return new IntPoint(this_x, other_a.y + this_x - other_a.x); } if (delta_2.x == -delta_2.y) // other line is left diagonal { int this_x = ((IntPoint)this.a).x; IntPoint other_a = (IntPoint) p_other.a; return new IntPoint(this_x, other_a.y + other_a.x - this_x); } } else if (delta_1.y == 0) // this line is horizontal { if (delta_2.x == 0) // other line is vertical { return new IntPoint(((IntPoint)p_other.a).x, ((IntPoint)this.a).y); } if (delta_2.x == delta_2.y) // other line is right diagonal { int this_y = ((IntPoint)this.a).y; IntPoint other_a = (IntPoint) p_other.a; return new IntPoint(other_a.x + this_y - other_a.y, this_y); } if (delta_2.x == -delta_2.y) // other line is left diagonal { int this_y = ((IntPoint)this.a).y; IntPoint other_a = (IntPoint) p_other.a; return new IntPoint(other_a.x + other_a.y - this_y, this_y); } } else if (delta_1.x == delta_1.y) // this line is right diagonal { if (delta_2.x == 0) // other line is vertical { int other_x = ((IntPoint)p_other.a).x; IntPoint this_a = (IntPoint) this.a; return new IntPoint(other_x, this_a.y + other_x - this_a.x); } if (delta_2.y == 0) // other line is horizontal { int other_y = ((IntPoint)p_other.a).y; IntPoint this_a = (IntPoint) this.a; return new IntPoint(this_a.x + other_y - this_a.y, other_y); } } else if (delta_1.x == -delta_1.y) // this line is left diagonal { if (delta_2.x == 0) // other line is vertical { int other_x = ((IntPoint)p_other.a).x; IntPoint this_a = (IntPoint) this.a; return new IntPoint(other_x, this_a.y + this_a.x - other_x); } if (delta_2.y == 0) // other line is horizontal { int other_y = ((IntPoint)p_other.a).y; IntPoint this_a = (IntPoint) this.a; return new IntPoint(this_a.x + this_a.y - other_y, other_y); } } BigInteger det_1 = BigInteger.valueOf(((IntPoint)a).determinant((IntPoint)b)); BigInteger det_2 = BigInteger.valueOf(((IntPoint)p_other.a).determinant((IntPoint)p_other.b)); BigInteger det = BigInteger.valueOf(delta_2.determinant(delta_1)); BigInteger tmp_1 = det_1.multiply(BigInteger.valueOf(delta_2.x)); BigInteger tmp_2 = det_2.multiply(BigInteger.valueOf(delta_1.x)); BigInteger is_x = tmp_1.subtract(tmp_2); tmp_1 = det_1.multiply(BigInteger.valueOf(delta_2.y)); tmp_2 = det_2.multiply(BigInteger.valueOf(delta_1.y)); BigInteger is_y = tmp_1.subtract(tmp_2); int signum = det.signum(); if (signum != 0) { if (signum < 0) { det = det.negate(); is_x = is_x.negate(); is_y = is_y.negate(); } if ((is_x.mod(det)).signum() == 0 && (is_y.mod(det)).signum() == 0) { is_x = is_x.divide(det); is_y = is_y.divide(det); if (Math.abs(is_x.doubleValue()) <= Limits.CRIT_INT && Math.abs(is_y.doubleValue()) <= Limits.CRIT_INT) { return new IntPoint(is_x.intValue(), is_y.intValue()); } det = BigInteger.ONE; } } return new RationalPoint(is_x, is_y, det); } /** * Returns an approximation of the intersection of the 2 lines by a * FloatPoint. If the lines are parallel the result coordinates will be * Integer.MAX_VALUE. Useful in situations ehere performance is * more important than accuracy. */ public FloatPoint intersection_approx(Line p_other) { // this function is at the moment only implemented for lines // consisting of IntPoints. // The general implementation is still missing. IntPoint this_a = (IntPoint) a; IntPoint this_b = (IntPoint) b; IntPoint other_a = (IntPoint) p_other.a; IntPoint other_b = (IntPoint) p_other.b; double d1x = this_b.x - this_a.x; double d1y = this_b.y - this_a.y; double d2x = other_b.x - other_a.x; double d2y = other_b.y - other_a.y; double det_1 = (double)this_a.x * this_b.y - (double)this_a.y * this_b.x; double det_2 = (double)other_a.x * other_b.y - (double)other_a.y * other_b.x; double det = d2x * d1y - d2y * d1x; double is_x; double is_y; if(det == 0) { is_x = Integer.MAX_VALUE; is_y = Integer.MAX_VALUE; } else { is_x = (d2x * det_1 - d1x * det_2) / det; is_y = (d2y * det_1 - d1y * det_2) / det; } return new FloatPoint(is_x, is_y); } /** * returns the perpendicular projection of p_point onto this line */ public Point perpendicular_projection(Point p_point) { return p_point.perpendicular_projection(this); } /** * translates the line perpendicular at about p_dist. * If p_dist > 0, the line will be translated to the left, else to the right */ public Line translate(double p_dist) { // this function is at the moment only implemented for lines // consisting of IntPoints. // The general implementation is still missing. IntPoint ai = (IntPoint) a; IntVector v = (IntVector)direction().get_vector(); double vxvx = (double)v.x * v.x; double vyvy = (double)v.y * v.y; double lenght = Math.sqrt(vxvx + vyvy); IntPoint new_a; if (vxvx <= vyvy) { // translate along the x axis int rel_x = (int) Math.round((p_dist * lenght) / v.y); new_a = new IntPoint(ai.x - rel_x, ai.y); } else { // translate along the y axis int rel_y = (int) Math.round((p_dist * lenght) / v.x); new_a = new IntPoint(ai.x, ai.y + rel_y); } return Line.get_instance(new_a, direction()); } /** * translates the line by p_vector */ public Line translate_by(Vector p_vector) { if (p_vector.equals(Vector.ZERO)) { return this; } Point new_a = a.translate_by(p_vector); Point new_b = b.translate_by(p_vector); return new Line(new_a, new_b); } /** * returns true, if the line is axis_parallel */ public boolean is_orthogonal() { return direction().is_orthogonal(); } /** * returns true, if this line is diagonal */ public boolean is_diagonal() { return direction().is_diagonal(); } /** * returns true, if the direction of this line is a multiple of 45 degree */ public boolean is_multiple_of_45_degree() { return direction().is_multiple_of_45_degree(); } /** * checks, if this Line and p_other are parallel */ public boolean is_parallel(Line p_other) { return this.direction().side_of(p_other.direction()) == Side.COLLINEAR; } /** * checks, if this Line and p_other are perpendicular */ public boolean is_perpendicular(Line p_other) { Vector v1 = direction().get_vector(); Vector v2 = p_other.direction().get_vector(); return v1.projection(v2) == Signum.ZERO; } /** * returns true, if this and p_ob define the same line */ public boolean is_equal_or_opposite(Line p_other ) { return(side_of(p_other.a) == Side.COLLINEAR && side_of(p_other.b) == Side.COLLINEAR); } /** * calculates the cosinus of the angle between this line and p_other */ public double cos_angle( Line p_other) { Vector v1 = b.difference_by(a); Vector v2 = p_other.b.difference_by(p_other.a); return v1.cos_angle(v2); } /** * A line l_1 is defined bigger than a line l_2, if the direction of l_1 * is bigger than the direction of l_2. * Implements the comparable interface. * Throws a cast exception, if p_other is not a Line. * Fast implementation only for lines consisting of IntPoints * because of critical performance */ public int compareTo(Line p_other) { IntPoint this_a = (IntPoint) a; IntPoint this_b = (IntPoint) b; IntPoint other_a = (IntPoint) p_other.a; IntPoint other_b = (IntPoint) p_other.b; int dx1 = this_b.x - this_a.x; int dy1 = this_b.y - this_a.y; int dx2 = other_b.x - other_a.x; int dy2 = other_b.y - other_a.y; if (dy1 > 0) { if (dy2 < 0) { return -1 ; } if (dy2 == 0) { if (dx2 > 0) { return 1 ; } return -1 ; } } else if (dy1 < 0) { if (dy2 >= 0) { return 1 ; } } else // dy1 == 0 { if (dx1 > 0) { if (dy2 != 0 || dx2 < 0) { return -1 ; } return 0 ; } // dx1 < 0 if (dy2 > 0 || dy2 == 0 && dx2 > 0) { return 1 ; } if (dy2 < 0) { return -1 ; } return 0; } // now this direction and p_other are located in the same // open horizontal half plane double determinant = (double) dx2 * dy1 - (double) dy2 * dx1; return Signum.as_int(determinant); } /** * Calculates an approximation of the function value of this line at p_x, * if the line is not vertical. */ public double function_value_approx(double p_x) { FloatPoint p1 = a.to_float(); FloatPoint p2 = b.to_float(); double dx = p2.x - p1.x; if (dx == 0) { System.out.println("function_value_approx: line is vertical"); return 0; } double dy = p2.y - p1.y; double det = p1.x * p2.y - p2.x * p1.y; double result = (dy * p_x - det) /dx; return result; } /** * Calculates an approximation of the function value in y of this line at p_y, * if the line is not horizontal. */ public double function_in_y_value_approx(double p_y) { FloatPoint p1 = a.to_float(); FloatPoint p2 = b.to_float(); double dy = p2.y - p1.y; if (dy == 0) { System.out.println("function_in_y_value_approx: line is horizontal"); return 0; } double dx = p2.x - p1.x; double det = p1.x * p2.y - p2.x * p1.y; double result = (dx * p_y + det) /dy; return result; } /** * Calculates the direction from p_from_point to the nearest point on * this line to p_fro_point. * Returns null, if p_from_point is contained in this line. */ public Direction perpendicular_direction(Point p_from_point) { Side line_side = this.side_of(p_from_point); if (line_side == Side.COLLINEAR) { return null; } Direction dir1 = this.direction().turn_45_degree(2); Direction dir2 = this.direction().turn_45_degree(6); Point check_point_1 = p_from_point.translate_by(dir1.get_vector()); if (this.side_of(check_point_1) != line_side) { return dir1; } Point check_point_2 = p_from_point.translate_by(dir2.get_vector()); if (this.side_of(check_point_2) != line_side) { return dir2; } FloatPoint nearest_line_point = p_from_point.to_float().projection_approx(this); Direction result; if (nearest_line_point.distance_square(check_point_1.to_float()) <= nearest_line_point.distance_square(check_point_2.to_float())) { result = dir1; } else { result = dir2; } return result; } /** * Turns this line by p_factor times 90 degree around p_pole. */ public Line turn_90_degree(int p_factor, IntPoint p_pole) { Point new_a = a.turn_90_degree(p_factor, p_pole); Point new_b = b.turn_90_degree(p_factor, p_pole); return new Line(new_a, new_b); } /** Mirrors this line at the vertical line through p_pole */ public Line mirror_vertical(IntPoint p_pole) { Point new_a = b.mirror_vertical(p_pole); Point new_b = a.mirror_vertical(p_pole); return new Line(new_a, new_b); } /** Mirrors this line at the horizontal line through p_pole */ public Line mirror_horizontal(IntPoint p_pole) { Point new_a = b.mirror_horizontal(p_pole); Point new_b = a.mirror_horizontal(p_pole); return new Line(new_a, new_b); } public final Point a; public final Point b; transient private Direction dir; // should only be accessed from get_direction(). }