/*
* 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.
*
* IntBox.java
*
* Created on 2. Februar 2003, 14:09
*/
package geometry.planar;
/**
*
* Implements functionality of orthogonal rectangles in the plane
* with integer coordinates.
*
*
* @author Alfons Wirtz
*/
public class IntBox extends RegularTileShape implements java.io.Serializable
{
/**
* Standard implementataion of an empty box.
*/
public static final IntBox EMPTY = new IntBox(Limits.CRIT_INT, Limits.CRIT_INT,
-Limits.CRIT_INT, -Limits.CRIT_INT);
/**
* Creates an IntBox from its lower left and upper right corners.
*/
public IntBox(IntPoint p_ll, IntPoint p_ur)
{
ll = p_ll;
ur = p_ur;
}
/**
* creates an IntBox from the coordinates of its lower left and
* upper right corners.
*/
public IntBox(int p_ll_x, int p_ll_y, int p_ur_x, int p_ur_y)
{
ll = new IntPoint(p_ll_x, p_ll_y);
ur = new IntPoint(p_ur_x, p_ur_y);
}
public boolean is_IntOctagon()
{
return true;
}
/**
* Returns true, if the box is empty
*/
public boolean is_empty()
{
return (ll.x > ur.x || ll.y > ur.y);
}
public int border_line_count()
{
return 4;
}
/**
* returns the horizontal extension of the box.
*/
public int width()
{
return (ur.x - ll.x);
}
/**
* Returns the vertical extension of the box.
*/
public int height()
{
return (ur.y - ll.y) ;
}
public double max_width()
{
return Math.max(ur.x - ll.x, ur.y - ll.y);
}
public double min_width()
{
return Math.min(ur.x - ll.x, ur.y - ll.y);
}
public double area()
{
return ((double)(ur.x - ll.x))* ((double) (ur.y - ll.y));
}
public double circumference()
{
return 2 * ((ur.x - ll.x) + (ur.y - ll.y));
}
public IntPoint corner(int p_no)
{
if (p_no == 0)
{
return ll;
}
if (p_no == 1)
{
return new IntPoint(ur.x, ll.y);
}
if (p_no == 2)
{
return ur;
}
if (p_no == 3)
{
return new IntPoint(ll.x, ur.y);
}
throw new IllegalArgumentException("IntBox.corner: p_no out of range");
}
public int dimension()
{
if (is_empty())
{
return -1;
}
if (ll.equals(ur))
{
return 0;
}
if (ur.x == ll.x || ll.y == ur.y)
{
return 1;
}
return 2;
}
/**
* Chechs, if p_point is located in the interiour of this box.
*/
public boolean contains_inside(IntPoint p_point)
{
return p_point.x > this.ll.x && p_point.x < this.ur.x
&& p_point.y > this.ll.y && p_point.y < this.ur.y;
}
public boolean is_IntBox()
{
return true;
}
public TileShape simplify()
{
return this;
}
/**
* Calculates the nearest point of this box to p_from_point.
*/
public FloatPoint nearest_point(FloatPoint p_from_point)
{
double x;
if (p_from_point.x <= ll.x)
x = ll.x;
else if (p_from_point.x >= ur.x)
x = ur.x;
else
x = p_from_point.x;
double y;
if (p_from_point.y <= ll.y)
y = ll.y;
else if (p_from_point.y >= ur.y)
y = ur.y;
else
y = p_from_point.y;
return new FloatPoint(x,y);
}
/**
* Calculates the sorted p_max_result_points nearest points on the border of this box.
* p_point is assumed to be located in the interiour of this nox.
* The funtion is only imoplemented for p_max_result_points <= 2;
*/
public IntPoint[] nearest_border_projections(IntPoint p_point, int p_max_result_points)
{
if (p_max_result_points <= 0)
{
return new IntPoint[0];
}
p_max_result_points = Math.min(p_max_result_points, 2);
IntPoint [] result = new IntPoint[p_max_result_points];
int lower_x_diff = p_point.x - ll.x;
int upper_x_diff = ur.x - p_point.x;
int lower_y_diff = p_point.y - ll.y;
int upper_y_diff = ur.y - p_point.y;
int min_diff;
int second_min_diff;
int nearest_projection_x = p_point.x;
int nearest_projection_y = p_point.y;
int second_nearest_projection_x = p_point.x;
int second_nearest_projection_y = p_point.y;
if (lower_x_diff <= upper_x_diff)
{
min_diff = lower_x_diff;
second_min_diff = upper_x_diff;
nearest_projection_x = ll.x;
second_nearest_projection_x = ur.x;
}
else
{
min_diff = upper_x_diff;
second_min_diff = lower_x_diff;
nearest_projection_x = ur.x;
second_nearest_projection_x = ll.x;
}
if (lower_y_diff < min_diff)
{
second_min_diff = min_diff;
min_diff = lower_y_diff;
second_nearest_projection_x = nearest_projection_x;
second_nearest_projection_y = nearest_projection_y;
nearest_projection_x = p_point.x;
nearest_projection_y = ll.y;
}
else if (lower_y_diff < second_min_diff)
{
second_min_diff = lower_y_diff;
second_nearest_projection_x = p_point.x;
second_nearest_projection_y = ll.y;
}
if (upper_y_diff < min_diff)
{
second_min_diff = min_diff;
min_diff = upper_y_diff;
second_nearest_projection_x = nearest_projection_x;
second_nearest_projection_y = nearest_projection_y;
nearest_projection_x = p_point.x;
nearest_projection_y = ur.y;
}
else if (upper_y_diff < second_min_diff)
{
second_min_diff = upper_y_diff;
second_nearest_projection_x = p_point.x;
second_nearest_projection_y = ur.y;
}
result[0] = new IntPoint(nearest_projection_x, nearest_projection_y);
if (result.length > 1)
{
result[1] = new IntPoint(second_nearest_projection_x, second_nearest_projection_y);
}
return result;
}
/**
* Calculates distance of this box to p_from_point.
*/
public double distance(FloatPoint p_from_point)
{
return p_from_point.distance(nearest_point(p_from_point));
}
/**
* Computes the weighted distance to the box p_other.
*/
public double weighted_distance(IntBox p_other, double p_horizontal_weight, double p_vertical_weight)
{
double result;
double max_ll_x = Math.max(this.ll.x, p_other.ll.x);
double max_ll_y = Math.max(this.ll.y, p_other.ll.y);
double min_ur_x = Math.min(this.ur.x, p_other.ur.x);
double min_ur_y = Math.min(this.ur.y, p_other.ur.y);
if (min_ur_x >= max_ll_x)
{
result = Math.max(p_vertical_weight * (max_ll_y - min_ur_y), 0);
}
else if (min_ur_y >= max_ll_y)
{
result = Math.max(p_horizontal_weight * (max_ll_x - min_ur_x), 0);
}
else
{
double delta_x = max_ll_x - min_ur_x;
double delta_y = max_ll_y - min_ur_y;
delta_x *= p_horizontal_weight;
delta_y *= p_vertical_weight;
result = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
}
return result;
}
public IntBox bounding_box()
{
return this;
}
public IntOctagon bounding_octagon()
{
return to_IntOctagon();
}
public boolean is_bounded()
{
return true;
}
public IntBox bounding_tile()
{
return this;
}
public boolean corner_is_bounded(int p_no)
{
return true;
}
public RegularTileShape union(RegularTileShape p_other)
{
return p_other.union(this);
}
public IntBox union(IntBox p_other)
{
int llx = Math.min(ll.x, p_other.ll.x);
int lly = Math.min(ll.y, p_other.ll.y);
int urx = Math.max(ur.x, p_other.ur.x);
int ury = Math.max(ur.y, p_other.ur.y);
return new IntBox(llx, lly, urx, ury);
}
/**
* Returns the intersection of this box with an IntBox.
*/
public IntBox intersection(IntBox p_other)
{
if (p_other.ll.x > ur.x)
{
return EMPTY;
}
if (p_other.ll.y > ur.y)
{
return EMPTY;
}
if (ll.x > p_other.ur.x)
{
return EMPTY;
}
if (ll.y > p_other.ur.y)
{
return EMPTY;
}
int llx = Math.max(ll.x, p_other.ll.x);
int urx = Math.min(ur.x, p_other.ur.x);
int lly = Math.max(ll.y, p_other.ll.y);
int ury = Math.min(ur.y, p_other.ur.y);
return new IntBox(llx, lly, urx, ury);
}
/**
* returns the intersection of this box with a ConvexShape
*/
public TileShape intersection(TileShape p_other)
{
return p_other.intersection(this);
}
IntOctagon intersection(IntOctagon p_other)
{
return p_other.intersection(this.to_IntOctagon());
}
Simplex intersection(Simplex p_other)
{
return p_other.intersection(this.to_Simplex());
}
public boolean intersects(Shape p_other)
{
return p_other.intersects(this);
}
public boolean intersects(IntBox p_other)
{
if (p_other.ll.x > this.ur.x)
return false;
if (p_other.ll.y > this.ur.y)
return false;
if (this.ll.x > p_other.ur.x)
return false;
if (this.ll.y > p_other.ur.y)
return false;
return true;
}
/**
* Returns true, if this box intersects with p_other and the intersection is 2-dimensional.
*/
public boolean overlaps(IntBox p_other)
{
if (p_other.ll.x >= this.ur.x)
return false;
if (p_other.ll.y >= this.ur.y)
return false;
if (this.ll.x >= p_other.ur.x)
return false;
if (this.ll.y >= p_other.ur.y)
return false;
return true;
}
public boolean contains(RegularTileShape p_other)
{
return p_other.is_contained_in(this);
}
public RegularTileShape bounding_shape(ShapeBoundingDirections p_dirs)
{
return p_dirs.bounds(this);
}
/**
* Enlarges the box by p_offset.
* Contrary to the offset() method the result is an IntOctagon, not an IntBox.
*/
public IntOctagon enlarge(double p_offset)
{
return bounding_octagon().offset(p_offset);
}
public IntBox translate_by(Vector p_rel_coor)
{
// This function is at the moment only implemented for Vectors
// with integer coordinates.
// The general implementation is still missing.
if (p_rel_coor.equals(Vector.ZERO))
{
return this;
}
IntPoint new_ll = (IntPoint)ll.translate_by(p_rel_coor);
IntPoint new_ur = (IntPoint)ur.translate_by(p_rel_coor);
return new IntBox(new_ll, new_ur);
}
public IntBox turn_90_degree(int p_factor, IntPoint p_pole)
{
IntPoint p1 = (IntPoint) ll.turn_90_degree(p_factor, p_pole);
IntPoint p2 = (IntPoint) ur.turn_90_degree(p_factor, p_pole);
int llx = Math.min(p1.x, p2.x);
int lly = Math.min(p1.y, p2.y);
int urx = Math.max(p1.x, p2.x);
int ury = Math.max(p1.y, p2.y);
return new IntBox(llx,lly,urx,ury);
}
public Line border_line(int p_no)
{
int a_x;
int a_y;
int b_x;
int b_y;
switch (p_no)
{
case 0:
// lower boundary line
a_x = 0;
a_y = ll.y;
b_x = 1;
b_y = ll.y;
break;
case 1:
// right boundary line
a_x = ur.x;
a_y = 0;
b_x = ur.x;
b_y = 1;
break;
case 2:
// upper boundary line
a_x = 0;
a_y = ur.y;
b_x = -1;
b_y = ur.y;
break;
case 3:
// left boundary line
a_x = ll.x;
a_y = 0;
b_x = ll.x;
b_y = -1;
break;
default:
throw new IllegalArgumentException
("IntBox.edge_line: p_no out of range");
}
return new Line(a_x, a_y, b_x, b_y);
}
public int border_line_index(Line p_line)
{
System.out.println("edge_index_of_line not yet implemented for IntBoxes");
return -1;
}
/**
* Returns the box offseted by p_dist.
* If p_dist > 0, the offset is to the outside,
* else to the inside.
*/
public IntBox offset(double p_dist)
{
if (p_dist == 0 || is_empty())
{
return this;
}
int dist = (int)Math.round(p_dist);
IntPoint lower_left = new IntPoint(ll.x - dist, ll.y - dist);
IntPoint upper_right = new IntPoint(ur.x + dist, ur.y + dist);
return new IntBox(lower_left, upper_right);
}
/**
* Returns the box, where the horizontal boundary is offseted by p_dist.
* If p_dist > 0, the offset is to the outside,
* else to the inside.
*/
public IntBox horizontal_offset(double p_dist)
{
if (p_dist == 0 || is_empty())
{
return this;
}
int dist = (int)Math.round(p_dist);
IntPoint lower_left = new IntPoint(ll.x - dist, ll.y);
IntPoint upper_right = new IntPoint(ur.x + dist, ur.y);
return new IntBox(lower_left, upper_right);
}
/**
* Returns the box, where the vertical boundary is offseted by p_dist.
* If p_dist > 0, the offset is to the outside,
* else to the inside.
*/
public IntBox vertical_offset(double p_dist)
{
if (p_dist == 0 || is_empty())
{
return this;
}
int dist = (int)Math.round(p_dist);
IntPoint lower_left = new IntPoint(ll.x, ll.y - dist);
IntPoint upper_right = new IntPoint(ur.x, ur.y + dist);
return new IntBox(lower_left, upper_right);
}
/**
* Shrinks the width and height of the box by the input width.
* The box will not vanish completely.
*/
public IntBox shrink(int p_width)
{
int ll_x;
int ur_x;
if (2 * p_width <= this.ur.x - this.ll.x)
{
ll_x = this.ll.x + p_width;
ur_x = this.ur.x - p_width;
}
else
{
ll_x = (this.ll.x + this.ur.x) / 2;
ur_x = ll_x;
}
int ll_y;
int ur_y;
if (2 * p_width <= this.ur.y - this.ll.y)
{
ll_y = this.ll.y + p_width;
ur_y = this.ur.y - p_width;
}
else
{
ll_y = (this.ll.y + this.ur.y) / 2;
ur_y = ll_y;
}
return new IntBox(ll_x, ll_y, ur_x, ur_y);
}
public Side compare(RegularTileShape p_other, int p_edge_no)
{
Side result = p_other.compare(this, p_edge_no);
return result.negate();
}
public Side compare(IntBox p_other, int p_edge_no)
{
Side result;
switch (p_edge_no)
{
case 0:
// compare the lower edge line
if (ll.y > p_other.ll.y)
{
result = Side.ON_THE_LEFT;
}
else if (ll.y < p_other.ll.y)
{
result = Side.ON_THE_RIGHT;
}
else
{
result = Side.COLLINEAR;
}
break;
case 1:
// compare the right edge line
if (ur.x < p_other.ur.x)
{
result = Side.ON_THE_LEFT;
}
else if (ur.x > p_other.ur.x)
{
result = Side.ON_THE_RIGHT;
}
else
{
result = Side.COLLINEAR;
}
break;
case 2:
// compare the upper edge line
if (ur.y < p_other.ur.y)
{
result = Side.ON_THE_LEFT;
}
else if (ur.y > p_other.ur.y)
{
result = Side.ON_THE_RIGHT;
}
else
{
result = Side.COLLINEAR;
}
break;
case 3:
// compare the left edge line
if (ll.x > p_other.ll.x)
{
result = Side.ON_THE_LEFT;
}
else if (ll.x < p_other.ll.x)
{
result = Side.ON_THE_RIGHT;
}
else
{
result = Side.COLLINEAR;
}
break;
default:
throw new IllegalArgumentException
("IntBox.compare: p_edge_no out of range");
}
return result;
}
/**
* Returns an object of class IntOctagon defining the same shape
*/
public IntOctagon to_IntOctagon()
{
return new IntOctagon(ll.x, ll.y, ur.x, ur.y, ll.x - ur.y,
ur.x - ll.y, ll.x + ll.y, ur.x + ur.y);
}
/**
* Returns an object of class Simplex defining the same shape
*/
public Simplex to_Simplex()
{
Line[] line_arr;
if (is_empty())
{
line_arr = new Line[0];
}
else
{
line_arr = new Line[4];
line_arr[0] = Line.get_instance(ll, IntDirection.RIGHT);
line_arr[1] = Line.get_instance(ur, IntDirection.UP);
line_arr[2] = Line.get_instance(ur, IntDirection.LEFT);
line_arr[3] = Line.get_instance(ll, IntDirection.DOWN);
}
return new Simplex(line_arr);
}
public boolean is_contained_in( IntBox p_other)
{
if (is_empty() || this == p_other)
{
return true;
}
if (ll.x < p_other.ll.x || ll.y < p_other.ll.y
|| ur.x > p_other.ur.x || ur.y > p_other.ur.y)
{
return false;
}
return true;
}
/**
* Return true, if p_other is contained in the interiour of this box.
*/
public boolean contains_in_interiour(IntBox p_other)
{
if (p_other.is_empty())
{
return true;
}
if (p_other.ll.x <= ll.x || p_other.ll.y <= ll.y
|| p_other.ur.x >= ur.x || p_other.ur.y >= ur.y)
{
return false;
}
return true;
}
/**
* Calculates the part of p_from_box, which has minimal distance
* to this box.
*/
public IntBox nearest_part(IntBox p_from_box)
{
int ll_x;
if (p_from_box.ll.x >= this.ll.x)
{
ll_x = p_from_box.ll.x;
}
else if (p_from_box.ur.x >= this.ll.x)
{
ll_x = this.ll.x;
}
else
{
ll_x = p_from_box.ur.x;
}
int ur_x;
if (p_from_box.ur.x <= this.ur.x)
{
ur_x = p_from_box.ur.x;
}
else if (p_from_box.ll.x <= this.ur.x)
{
ur_x = this.ur.x;
}
else
{
ur_x = p_from_box.ll.x;
}
int ll_y;
if (p_from_box.ll.y >= this.ll.y)
{
ll_y = p_from_box.ll.y;
}
else if (p_from_box.ur.y >= this.ll.y)
{
ll_y = this.ll.y;
}
else
{
ll_y = p_from_box.ur.y;
}
int ur_y;
if (p_from_box.ur.y <= this.ur.y)
{
ur_y = p_from_box.ur.y;
}
else if (p_from_box.ll.y <= this.ur.y)
{
ur_y = this.ur.y;
}
else
{
ur_y = p_from_box.ll.y;
}
return new IntBox(ll_x, ll_y, ur_x, ur_y);
}
public boolean is_contained_in( IntOctagon p_other)
{
return p_other.contains(to_IntOctagon());
}
public boolean intersects( IntOctagon p_other)
{
return p_other.intersects(to_IntOctagon());
}
public boolean intersects( Simplex p_other)
{
return p_other.intersects(to_Simplex());
}
public boolean intersects( Circle p_other)
{
return p_other.intersects(this);
}
public IntOctagon union( IntOctagon p_other)
{
return p_other.union(to_IntOctagon());
}
public Side compare(IntOctagon p_other, int p_edge_no)
{
return to_IntOctagon().compare(p_other, p_edge_no);
}
/**
* Divides this box into sections with width and height at most p_max_section_width
* of about equal size.
*/
public IntBox[] divide_into_sections(double p_max_section_width)
{
if (p_max_section_width <= 0)
{
return new IntBox[0];
}
double length = this.ur.x - this.ll.x;
double height = this.ur.y - this.ll.y;
int x_count = (int) Math.ceil(length / p_max_section_width);
int y_count = (int) Math.ceil(height / p_max_section_width);
int section_length_x = (int) Math.ceil(length / x_count);
int section_length_y = (int) Math.ceil(height / y_count);
IntBox [] result = new IntBox[x_count * y_count];
int curr_index = 0;
for (int j = 0; j < y_count; ++j)
{
int curr_lly = this.ll.y + j * section_length_y;
int curr_ury;
if (j == (y_count - 1))
{
curr_ury = this.ur.y;
}
else
{
curr_ury = curr_lly + section_length_y;
}
for (int i = 0; i < x_count; ++i)
{
int curr_llx = this.ll.x + i * section_length_x;
int curr_urx;
if (i == (x_count - 1))
{
curr_urx = this.ur.x;
}
else
{
curr_urx = curr_llx + section_length_x;
}
result[curr_index] = new IntBox(curr_llx, curr_lly, curr_urx, curr_ury);
++curr_index;
}
}
return result;
}
public TileShape[] cutout(TileShape p_shape)
{
TileShape[] tmp_result = p_shape.cutout_from(this);
TileShape[] result = new TileShape[tmp_result.length];
for (int i = 0; i < result.length; ++i)
{
result[i] = tmp_result[i].simplify();
}
return result;
}
IntBox[] cutout_from(IntBox p_d)
{
IntBox c = this.intersection(p_d);
if (this.is_empty() || c.dimension() < this.dimension())
{
// there is only an overlap at the border
IntBox[] result = new IntBox[1];
result[0] = p_d;
return result;
}
IntBox[] result = new IntBox[4];
result[0] = new IntBox(p_d.ll.x, p_d.ll.y, c.ur.x, c.ll.y);
result[1] = new IntBox(p_d.ll.x, c.ll.y, c.ll.x, p_d.ur.y);
result[2] = new IntBox(c.ur.x, p_d.ll.y, p_d.ur.x, c.ur.y);
result[3] = new IntBox(c.ll.x, c.ur.y, p_d.ur.x, p_d.ur.y);
// now the division will be optimised, so that the cumulative
// circumference will be minimal.
IntBox b = null;
if (c.ll.x - p_d.ll.x > c.ll.y - p_d.ll.y)
{
// switch left dividing line to lower
b = result[0];
result[0] = new IntBox(c.ll.x, b.ll.y, b.ur.x, b.ur.y);
b = result[1];
result[1] = new IntBox(b.ll.x, p_d.ll.y, b.ur.x, b.ur.y);
}
if (p_d.ur.y - c.ur.y > c.ll.x - p_d.ll.x)
{
// switch upper dividing line to the left
b = result[1];
result[1]= new IntBox(b.ll.x, b.ll.y, b.ur.x, c.ur.y);
b = result[3];
result[3] = new IntBox(p_d.ll.x, b.ll.y, b.ur.x, b.ur.y);
}
if (p_d.ur.x - c.ur.x > p_d.ur.y - c.ur.y)
{
// switch right dividing line to upper
b = result[2];
result[2] = new IntBox(b.ll.x, b.ll.y, b.ur.x, p_d.ur.y);
b = result[3];
result[3] = new IntBox(b.ll.x, b.ll.y, c.ur.x, b.ur.y);
}
if (c.ll.y - p_d.ll.y > p_d.ur.x - c.ur.x)
{
// switch lower dividing line to the left
b = result[0];
result[0] = new IntBox(b.ll.x, b.ll.y, p_d.ur.x, b.ur.y);
b = result[2];
result[2] = new IntBox(b.ll.x, c.ll.y, b.ur.x, b.ur.y);
}
return result;
}
Simplex[] cutout_from(Simplex p_simplex)
{
return this.to_Simplex().cutout_from(p_simplex);
}
IntOctagon[] cutout_from(IntOctagon p_oct)
{
return this.to_IntOctagon().cutout_from(p_oct);
}
/**
* coordinates of the lower left corner
*/
public final IntPoint ll;
/**
* coordinates of the upper right corner
*/
public final IntPoint ur;
}