299 lines
10 KiB
Java
299 lines
10 KiB
Java
/*
|
|
* 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 eu.mihosoft.freerouting.boardgraphics;
|
|
|
|
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
|
|
import eu.mihosoft.freerouting.geometry.planar.IntBox;
|
|
import eu.mihosoft.freerouting.geometry.planar.Limits;
|
|
|
|
import java.awt.Dimension;
|
|
import java.awt.geom.Point2D;
|
|
|
|
/**
|
|
* Transformation function between the eu.mihosoft.freerouting.board and the screen coordinate systems.
|
|
*
|
|
* @author Alfons Wirtz
|
|
*/
|
|
|
|
|
|
public class CoordinateTransform implements java.io.Serializable
|
|
{
|
|
public CoordinateTransform(IntBox p_design_box, Dimension p_panel_bounds )
|
|
{
|
|
this.screen_bounds = p_panel_bounds;
|
|
this.design_box = p_design_box;
|
|
this.rotation_pole = p_design_box.centre_of_gravity();
|
|
|
|
int min_ll = Math.min(p_design_box.ll.x, p_design_box.ll.y);
|
|
int max_ur = Math.max(p_design_box.ur.x, p_design_box.ur.y);
|
|
if (Math.max(Math.abs(min_ll), Math.abs(max_ur)) <= 0.3 * Limits.CRIT_INT)
|
|
{
|
|
// create an offset to p_design_box to enable deep zoom out
|
|
double design_offset = Math.max(p_design_box.width(), p_design_box.height());
|
|
design_box_with_offset = p_design_box.offset(design_offset);
|
|
}
|
|
else
|
|
{
|
|
// no offset because of danger of integer overflow
|
|
design_box_with_offset = p_design_box;
|
|
}
|
|
|
|
double x_scale_factor = screen_bounds.getWidth()/design_box_with_offset.width();
|
|
double y_scale_factor = screen_bounds.getHeight()/design_box_with_offset.height();
|
|
|
|
scale_factor = Math.min(x_scale_factor, y_scale_factor) ;
|
|
display_x_offset = scale_factor * design_box_with_offset.ll.x;
|
|
display_y_offset = scale_factor * design_box_with_offset.ll.y ;
|
|
}
|
|
|
|
/** Copy constructor */
|
|
public CoordinateTransform(CoordinateTransform p_coordinate_transform)
|
|
{
|
|
this.screen_bounds = new Dimension(p_coordinate_transform.screen_bounds);
|
|
this.design_box = new IntBox(p_coordinate_transform.design_box.ll, p_coordinate_transform.design_box.ur);
|
|
this.rotation_pole = new FloatPoint(p_coordinate_transform.rotation_pole.x, p_coordinate_transform.rotation_pole.y);
|
|
this.design_box_with_offset = new IntBox(p_coordinate_transform.design_box_with_offset.ll, p_coordinate_transform.design_box_with_offset.ur);
|
|
this.scale_factor = p_coordinate_transform.scale_factor;
|
|
this.display_x_offset = p_coordinate_transform.display_x_offset;
|
|
this.display_y_offset = p_coordinate_transform.display_y_offset;
|
|
this.mirror_left_right = p_coordinate_transform.mirror_left_right;
|
|
this.mirror_top_bottom = p_coordinate_transform.mirror_top_bottom;
|
|
this.rotation = p_coordinate_transform.rotation;
|
|
}
|
|
|
|
/**
|
|
* scale a value from the eu.mihosoft.freerouting.board to the screen coordinate system
|
|
*/
|
|
public double board_to_screen(double p_val)
|
|
{
|
|
return p_val * scale_factor ;
|
|
}
|
|
|
|
/**
|
|
* scale a value the screen to the eu.mihosoft.freerouting.board coordinate system
|
|
*/
|
|
public double screen_to_board(double p_val)
|
|
{
|
|
return p_val / scale_factor;
|
|
}
|
|
|
|
|
|
/**
|
|
* transform a eu.mihosoft.freerouting.geometry.planar.FloatPoint to a java.awt.geom.Point2D
|
|
*/
|
|
public Point2D board_to_screen(FloatPoint p_point)
|
|
{
|
|
FloatPoint rotated_point = p_point.rotate(this.rotation, this.rotation_pole);
|
|
|
|
double x, y;
|
|
if (this.mirror_left_right)
|
|
{
|
|
x = (design_box_with_offset.width() - rotated_point.x - 1) * scale_factor + display_x_offset;
|
|
}
|
|
else
|
|
{
|
|
x = rotated_point.x * scale_factor - display_x_offset;
|
|
}
|
|
if (this.mirror_top_bottom)
|
|
{
|
|
y = (design_box_with_offset.height() - rotated_point.y - 1) * scale_factor + display_y_offset;
|
|
}
|
|
else
|
|
{
|
|
y = rotated_point.y * scale_factor - display_y_offset;
|
|
}
|
|
return new Point2D.Double(x, y);
|
|
}
|
|
|
|
/**
|
|
* Transform a java.awt.geom.Point2D to a eu.mihosoft.freerouting.geometry.planar.FloatPoint
|
|
*/
|
|
public FloatPoint screen_to_board(Point2D p_point)
|
|
{
|
|
double x, y;
|
|
if (this.mirror_left_right)
|
|
{
|
|
x = design_box_with_offset.width() -(p_point.getX() - display_x_offset) / scale_factor - 1;
|
|
}
|
|
else
|
|
{
|
|
x = (p_point.getX() + display_x_offset)/ scale_factor;
|
|
}
|
|
if (this.mirror_top_bottom)
|
|
{
|
|
y = design_box_with_offset.height() -(p_point.getY() - display_y_offset) / scale_factor - 1;
|
|
}
|
|
else
|
|
{
|
|
y = (p_point.getY() + display_y_offset)/ scale_factor;
|
|
}
|
|
FloatPoint result = new FloatPoint(x, y);
|
|
return result.rotate(-this.rotation, this.rotation_pole);
|
|
}
|
|
|
|
/**
|
|
* Transforms an angle in radian on the eu.mihosoft.freerouting.board to an angle on the screen.
|
|
*/
|
|
public double board_to_screen_angle(double p_angle)
|
|
{
|
|
double result = p_angle + this.rotation;
|
|
if (this.mirror_left_right)
|
|
{
|
|
result = Math.PI - result;
|
|
}
|
|
if (this.mirror_top_bottom)
|
|
{
|
|
result = -result;
|
|
}
|
|
while (result >= 2 * Math.PI)
|
|
{
|
|
result -= 2 * Math.PI;
|
|
}
|
|
while (result < 0)
|
|
{
|
|
result += 2 * Math.PI;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Transform a eu.mihosoft.freerouting.geometry.planar.IntBox to a java.awt.Rectangle
|
|
* If the internal rotation is not a multiple of Pi/2, a bounding rectangle of the
|
|
* rotated rectangular shape is returned.
|
|
*/
|
|
public java.awt.Rectangle board_to_screen(IntBox p_box)
|
|
{
|
|
Point2D corner_1 = board_to_screen(p_box.ll.to_float());
|
|
Point2D corner_2 = board_to_screen(p_box.ur.to_float());
|
|
double ll_x = Math.min(corner_1.getX(), corner_2.getX());
|
|
double ll_y = Math.min(corner_1.getY(), corner_2.getY());
|
|
double dx = Math.abs(corner_2.getX() - corner_1.getX());
|
|
double dy = Math.abs(corner_2.getY() - corner_1.getY());
|
|
java.awt.Rectangle result =
|
|
new java.awt. Rectangle((int) Math.floor(ll_x), (int) Math.floor(ll_y),
|
|
(int) Math.ceil(dx), (int) Math.ceil(dy));
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Transform a java.awt.Rectangle to a eu.mihosoft.freerouting.geometry.planar.IntBox
|
|
* If the internal rotation is not a multiple of Pi/2, a bounding box of the
|
|
* rotated rectangular shape is returned.
|
|
*/
|
|
public IntBox screen_to_board(java.awt.Rectangle p_rect)
|
|
{
|
|
FloatPoint corner_1 = screen_to_board(new Point2D.Double(p_rect.getX(), p_rect.getY()));
|
|
FloatPoint corner_2 = screen_to_board(new Point2D.Double(p_rect.getX() + p_rect.getWidth(),
|
|
p_rect.getY() + p_rect.getHeight()));
|
|
int llx = (int) Math.floor(Math.min(corner_1.x, corner_2.x));
|
|
int lly = (int) Math.floor(Math.min(corner_1.y, corner_2.y));
|
|
int urx = (int) Math.ceil(Math.max(corner_1.x, corner_2.x));
|
|
int ury = (int) Math.ceil(Math.max(corner_1.y, corner_2.y));
|
|
return new IntBox(llx, lly, urx, ury);
|
|
}
|
|
|
|
/**
|
|
* If p_value is true, the left side and the right side of the eu.mihosoft.freerouting.board will be swapped.
|
|
*/
|
|
public void set_mirror_left_right(boolean p_value)
|
|
{
|
|
mirror_left_right = p_value;
|
|
}
|
|
|
|
/**
|
|
* Returns, if the left side and the right side of the eu.mihosoft.freerouting.board are swapped.
|
|
*/
|
|
public boolean is_mirror_left_right()
|
|
{
|
|
return mirror_left_right;
|
|
}
|
|
|
|
/**
|
|
* If p_value is true, the top side and the botton side of the eu.mihosoft.freerouting.board will be swapped.
|
|
*/
|
|
public void set_mirror_top_bottom(boolean p_value)
|
|
{
|
|
// Because the origin of display is the upper left corner, the internal value
|
|
// will be opposite to the input value of this function.
|
|
mirror_top_bottom = !p_value;
|
|
}
|
|
|
|
/**
|
|
* Returns, if the top side and the botton side of the eu.mihosoft.freerouting.board are swapped.
|
|
*/
|
|
public boolean is_mirror_top_bottom()
|
|
{
|
|
// Because the origin of display is the upper left corner, the internal value
|
|
// is opposite to the result of this function.
|
|
return !mirror_top_bottom;
|
|
}
|
|
|
|
/**
|
|
* Sets the rotation of the displayed eu.mihosoft.freerouting.board to p_value.
|
|
*/
|
|
public void set_rotation(double p_value)
|
|
{
|
|
rotation = p_value;
|
|
}
|
|
|
|
/**
|
|
* Returns the rotation of the displayed eu.mihosoft.freerouting.board.
|
|
*/
|
|
public double get_rotation()
|
|
{
|
|
return rotation;
|
|
}
|
|
|
|
/**
|
|
* Returns the internal rotation snapped to the nearest multiple of 90 degree.
|
|
* The result will be 0, 1, 2 or 3.
|
|
*/
|
|
public int get_90_degree_rotation()
|
|
{
|
|
int multiple = (int) Math.round(Math.toDegrees(rotation) / 90.0);
|
|
while (multiple < 0)
|
|
{
|
|
multiple += 4;
|
|
}
|
|
while (multiple >= 4)
|
|
{
|
|
multiple -= 4;
|
|
}
|
|
return multiple;
|
|
}
|
|
|
|
final IntBox design_box;
|
|
final IntBox design_box_with_offset ;
|
|
final Dimension screen_bounds ;
|
|
private final double scale_factor ;
|
|
private final double display_x_offset;
|
|
private final double display_y_offset;
|
|
|
|
/**
|
|
* Left side and right side of the eu.mihosoft.freerouting.board are swapped.
|
|
*/
|
|
private boolean mirror_left_right = false;
|
|
|
|
/**
|
|
* Top side and bottom side of the eu.mihosoft.freerouting.board are swapped.
|
|
*/
|
|
private boolean mirror_top_bottom = true;
|
|
|
|
private double rotation = 0;
|
|
|
|
private FloatPoint rotation_pole;
|
|
} |