I have a rectangular and circle. I need to verify whether a rectangle is inside that circle.
I tried to Shape.intersects but intersects is checked the number 1.
Does anyone know this kind of algorithm in javafx?
Just to exemplifly, in the figure only rectangles 1, 2, 3, and 4 are inside the circle.
thanks for your help.
Solution
The basic idea behind this solution is that any polygon is contained within any convex (see comments) shape iff every point within the polygon is within the shape. The intersects() method that you're attempting to use returns true if at least one point of the polygon is within the shape. You've already figured out that it'll work, but it'll also offer false positives for any partially-intersected shapes. To fix it, we define our own intersection test which looks at all points.
This can be generalized to scan any given polygon for "total intersection" with any given shape:
public boolean totalIntersects(Polygon poly, Shape testShape) {
List<Point> points = flatDoublesToPoints(poly.getPoints());
boolean inside = true; // If this is false after testing all points, the poly has at least one point outside of the shape.
for(Point point : points) {
if(!testShape.intersects(point.x, point.y, 1, 1)) { // The 3rd and 4th parameters here are "width" and "height". 1 for a point.
inside = false;
}
}
return inside;
}
where flatDoublesToPoints() and Point are defined as:
private List<Point> flatDoublesToPoints(List<Double> flatDoubles) {
List<Point> points = new ArrayList<>();
for(int i = 0; i < flatDoubles.size(); i += 2) {
points.add(new Point(flatDoubles.get(i), flatDoubles.get(i + 1)));
}
return points;
}
class Point {
public double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}
flatDoublesToPoints() is needed to split the "flat" {x1, y1, x2, y2, x3, y3...} polygon lists into a more easy-to-understand data structure. If you're doing tons of comparisons, it may be helpful to skip this step, however, and operate on the "flat list" directly for memory reasons.
Application
The following applies the other methods to a situation extremely similar to yours. (Not exact, because I didn't have your code.)
public class Main extends Application {
public static final int SIZE = 600;
#Override
public void start(Stage primaryStage) throws Exception {
Pane rootPane = new Pane();
List<Rectangle> rects = new ArrayList<>();
for (int j = 0; j < 2; j++) {
for(int i = 0; i < 5; i++) {
Rectangle r = new Rectangle(i * 100, j == 0 ? 0 : 300, 100, 200);
r.setFill(Color.BEIGE);
r.setStroke(Color.BLACK);
rects.add(r);
}
}
rootPane.getChildren().addAll(rects);
Circle circle = new Circle(350, 100, 200);
circle.setStroke(Color.BLACK);
circle.setFill(null);
rootPane.getChildren().add(circle);
List<Polygon> polys = new ArrayList<>();
for(Rectangle rect : rects) {
polys.add(rectangleToPolygon(rect));
}
List<Polygon> intersects = getTotalIntersections(polys, circle);
System.out.println(intersects);
primaryStage.setScene(new Scene(rootPane, SIZE, SIZE));
primaryStage.show();
}
public List<Polygon> getTotalIntersections(List<Polygon> polys, Shape testShape) {
List<Polygon> intersections = new ArrayList<>();
for(Polygon poly : polys) {
if(totalIntersects(poly, testShape)) {
intersections.add(poly);
}
}
return intersections;
}
public static Polygon rectangleToPolygon(Rectangle rect) {
double[] points = {rect.getX(), rect.getY(),
rect.getX() + rect.getWidth(), rect.getY(),
rect.getX() + rect.getWidth(), rect.getY() + rect.getHeight(),
rect.getX(), rect.getY() + rect.getHeight()};
return new Polygon(points);
}
public static void main(String[] args) {
Main.launch(args);
}
}
This code will print the following:
[Polygon[points=[200.0, 0.0, 300.0, 0.0, 300.0, 200.0, 200.0, 200.0], fill=0x000000ff], Polygon[points=[300.0, 0.0, 400.0, 0.0, 400.0, 200.0, 300.0, 200.0], fill=0x000000ff], Polygon[points=[400.0, 0.0, 500.0, 0.0, 500.0, 200.0, 400.0, 200.0], fill=0x000000ff]]
Which is your three polygons labeled 2, 3, and 4.
I don't think that JavaFX will have some special methods for this case.
To draw that circle you need coordinates (X_c, Y_c) of center and radius (R).
To draw rectangles you need to have coordinates ((X_1, Y_1), (X_2, Y_2) etc.) of angle points.
Then all you need is to check if all points of the rectangle is inside of the circle:
(X_1 - X_c)^2 + (Y_1 - Y_c)^2 < R^2
(X_2 - X_c)^2 + (Y_2 - Y_c)^2 < R^2
...
Try this :
import javafx.geometry.Point2D;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
/*
Check if a rectangle is contained with in a circle by checking
all rectangle corners.
For the rectangle to be contained in a circle, all its corners should be
in a distance smaller or equal to the circle's radius, from the circle's center.
Note:
Requires some more testing. I tested only a few test cases.
I am not familiar with javafx. This solution does not take into
calculation rectangle's arc or other attributes I may not be aware of.
*/
public class Test{
//apply
public static void main(String[] args){
Circle circle = new Circle(0 ,0, 100);
Rectangle rec = new Rectangle(0, 0, 50 , 50);
System.out.println("Is rectungle inside the circle ? "
+ isContained(circle,rec));
}
//check if rectangle is contained within a circle
private static boolean isContained(Circle circle,Rectangle rec) {
boolean isInside = true;
//get circle center & radius
Point2D center = new Point2D(circle.getCenterX(), circle.getCenterY());
double radius= circle.getRadius();
Point2D[] corners = getRectangleCorners(rec);
for(Point2D corner : corners) {
//if any corner falls outside the circle
//the rectangle is not contained in the circle
if(distanceBetween2Points(corner, center) > radius) {
return false;
}
}
return isInside;
}
//calculate distance between two points
//(updated a per fabian's suggestion)
private static double distanceBetween2Points
(Point2D corner, Point2D center) {
return corner.distance(center);
}
private static Point2D[] getRectangleCorners(Rectangle rec) {
Point2D[] corners = new Point2D[4];
corners[0] = new Point2D(rec.getX(), rec.getY());
corners[1] = new Point2D(rec.getX()+ rec.getWidth() , rec.getY());
corners[2] = new Point2D(rec.getX()+ rec.getWidth(), rec.getY()+ rec.getHeight());
corners[3] = new Point2D(rec.getX(), rec.getY()+ rec.getHeight());
return corners;
}
}
There is a working simple solution here : https://stackoverflow.com/a/8721483/1529139
Copy-paste here:
class Boundary {
private final Point[] points; // Points making up the boundary
...
/**
* Return true if the given point is contained inside the boundary.
* See: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
* #param test The point to check
* #return true if the point is inside the boundary, false otherwise
*
*/
public boolean contains(Point test) {
int i;
int j;
boolean result = false;
for (i = 0, j = points.length - 1; i < points.length; j = i++) {
if ((points[i].y > test.y) != (points[j].y > test.y) &&
(test.x < (points[j].x - points[i].x) * (test.y - points[i].y) / (points[j].y-points[i].y) + points[i].x)) {
result = !result;
}
}
return result;
}
}
Related
How can one find the centroid of a javafx Shape object?
Longer story: I am highlighting the intersections of 2 or more shapes, and need to position text over this intersection - ideally centering the text at the centroid. Given the potential for these intersections to be irregular, using the center of the intersection's layout bounds for calculations quite often result in mis-positioning of the text. For example, constructing the code below results in the center point that is offset relative to the conceptual center:
public class Test extends Application{
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage){
/*
* Create shapes
*/
Circle s1 = new Circle(100, 100, 75);
s1.setFill(new Color(1, 0, 0, 0.25));
Circle s2 = new Circle(200, 100, 75);
s2.setFill(new Color(0, 1, 0, 0.25));
Circle s3 = new Circle(150, 125, 50);
s3.setFill(new Color(0, 0, 1, 0.25));
/*
* Create the intersection
*/
Shape intersection = Shape.intersect(s3, s2);
intersection = Shape.subtract(intersection, s1);
intersection.setStroke(Color.RED);
/*
* Create a point that shows the layout bounds center
*/
Circle boundsCenter = new Circle();
boundsCenter.setRadius(2d);
boundsCenter.setCenterX(intersection.getLayoutBounds().getMinX() + intersection.getLayoutBounds().getWidth()/2);
boundsCenter.setCenterY(intersection.getLayoutBounds().getMinY() + intersection.getLayoutBounds().getHeight()/2);
boundsCenter.setFill(Color.RED);
intersection.setFill(null);
Pane pane = new Pane();
pane.getChildren().addAll(s1, s2, s3, intersection, boundsCenter);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
}
}
I would prefer to rely on the javafx API to do so (java 8 compatible), if at all possible.
One Option I have considered is to estimate the centroid by sampling using the Shape API and calculating the centroid of Points that fall within the given Shape:
//estimate the centroid by random sampling
int maxEstimate = 100;
double x = 0; double y = 0; double count = 0;
while(count < maxEstimate) {
double randomX = intersection.getLayoutBounds().getMinX() + Math.random() * intersection.getLayoutBounds().getWidth();
double randomY = intersection.getLayoutBounds().getMinY() + Math.random() * intersection.getLayoutBounds().getHeight();
Circle test = new Circle(randomX, randomY, 1d);
Shape isInside = Shape.intersect(c, intersection);
if ( isInside.getLayoutBounds().getWidth() > 0 || isInside.getLayoutBounds().getHeight() > 0 ) {
x += randomX;
y += randomY;
count++;
}
}
double centroidX = x / count;
double centroidY = y / count;
The problem with this method is that it is non-deterministic, can be inaccurate for smaller number of iterations, and quite intensive and time consuming for a larger number of shapes (and/or a more accurate measurement)
I have a set of two dimensions points. Their X and Y are greater than -2 and lesser than 2. Such point could be : (-0.00012 ; 1.2334 ).
I would want to display these points on a graph, using rectangles (a rectangle illustrates a point, and has its coordinates set to its point's ones - moreover, it has a size of 10*10).
Rectangles like (... ; Y) should be displayed above any rectangles like (... ; Y-1) (positive Y direction is up). Thus, I must set the graph's origin not at the top-left hand-corner, but somewhere else.
I'm trying to use Graphics2D's AffineTransform to do that.
I get the minimal value for all the X coordinates
I get the minimal value for all the Y coordinates
I get the maximal value for all the X coordinates
I get the maximal value for all the Y coordinates
I get the distance xmax-xmin and ymax-ymin
Then, I wrote the code I give you below.
Screenshots
Some days ago, using my own method to scale, I had this graph:
(so as I explained, Y are inverted and that's not a good thing)
For the moment, i.e., with the code I give you below, I have only one point that takes all the graph's place! Not good at all.
I would want to have:
(without lines, and without graph's axis. The important here is that points are correctly displayed, according to their coordinates).
Code
To get min and max coordinates value:
x_min = Double.parseDouble((String) list_all_points.get(0).get(0));
x_max = Double.parseDouble((String) list_all_points.get(0).get(0));
y_min = Double.parseDouble((String) list_all_points.get(0).get(1));
y_max = Double.parseDouble((String) list_all_points.get(0).get(1));
for(StorableData s : list_all_points) {
if(Double.parseDouble((String) s.get(0)) < x_min) {
x_min = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(0)) > x_max) {
x_max = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(1)) < y_min) {
y_min = Double.parseDouble((String) s.get(1));
}
if(Double.parseDouble((String) s.get(1)) > y_max) {
y_max = Double.parseDouble((String) s.get(1));
}
}
To draw a point:
int x, y;
private void drawPoint(Cupple storable_data) {
//x = (int) (storable_data.getNumber(0) * scaling_coef + move_x);
//y = (int) (storable_data.getNumber(1) * scaling_coef + move_y);
x = storable_data.getNumber(0).intValue();
y = storable_data.getNumber(1).intValue();
graphics.fillRect(x, y, 10, 10);
graphics.drawString(storable_data.toString(), x - 5, y - 5);
}
To paint the graph:
#Override
public void paint(Graphics graphics) {
this.graphics = graphics;
Graphics2D graphics_2d = ((Graphics2D) this.graphics);
AffineTransform affine_transform = graphics_2d.getTransform();
affine_transform.scale(getWidth()/(x_max - x_min), getHeight()/(y_max - y_min));
affine_transform.translate(x_min, y_min);
graphics_2d.transform(affine_transform);
for(StorableData storable_data : list_all_points) {
graphics_2d.setColor(Color.WHITE);
this.drawPoint((Cupple) storable_data);
}
I suggest you map each data point to a point on the screen, thus avoiding the following coordinate system pitfalls. Take your list of points and create from them a list of points to draw. Take into account that:
The drawing is pixel-based, so you will want to scale your points (or you would have rectangles 1 to 4 pixels wide...).
You will need to translate all your points because negative values will be outside the boundaries of the component on which you draw.
The direction of the y axis is reversed in the drawing coordinates.
Once that is done, use the new list of points for the drawing and the initial one for calculations. Here is an example:
public class Graph extends JPanel {
private static int gridSize = 6;
private static int scale = 100;
private static int size = gridSize * scale;
private static int translate = size / 2;
private static int pointSize = 10;
List<Point> dataPoints, scaledPoints;
Graph() {
setBackground(Color.WHITE);
// points taken from your example
Point p1 = new Point(-1, -2);
Point p2 = new Point(-1, 0);
Point p3 = new Point(1, 0);
Point p4 = new Point(1, -2);
dataPoints = Arrays.asList(p1, p2, p3, p4);
scaledPoints = dataPoints.stream()
.map(p -> new Point(p.x * scale + translate, -p.y * scale + translate))
.collect(Collectors.toList());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// draw a grid
for (int i = 0; i < gridSize; i++) {
g2d.drawLine(i * scale, 0, i * scale, size);
g2d.drawLine(0, i * scale, size, i * scale);
}
// draw the rectangle
g2d.setPaint(Color.RED);
g2d.drawPolygon(scaledPoints.stream().mapToInt(p -> p.x).toArray(),
scaledPoints.stream().mapToInt(p -> p.y).toArray(),
scaledPoints.size());
// draw the points
g2d.setPaint(Color.BLUE);
// origin
g2d.fillRect(translate, translate, pointSize, pointSize);
g2d.drawString("(0, 0)", translate, translate);
// data
for (int i = 0; i < dataPoints.size(); i++) {
Point sp = scaledPoints.get(i);
Point dp = dataPoints.get(i);
g2d.fillRect(sp.x, sp.y, pointSize, pointSize);
g2d.drawString("(" + dp.x + ", " + dp.y + ")", sp.x, sp.y);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setContentPane(new Graph());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
And another:
You might want to have the points aligned on the grid intersections and not below and to the right of them. I trust you will figure this one out.
Also, I ordered the points so that drawPolygon will paint the lines in the correct order. If your points are arbitrarily arranged, look for ways to find the outline. If you want lines between all points like in your example, iterate over all combinations of them with drawLine.
Given a simple monochrome bitmap that contains a single, randomly rotated rectangle. How can I find the left/top, left/bottom, right/bottom and right/top corner positions of the rectangle inside the bitmap?
For example, this is how the bitmap could look like, where the X marks the pixels in question:
......... ......... ......... .........
.X11111X. ....X.... ..X11.... ....11X..
.1111111. ...111... ..11111X. X111111..
.1111111. ..X111X.. ..111111. .111111..
.X11111X. ...111... .1111111. .1111111.
......... ....X.... .111111.. ..111111.
......... ......... .X11111.. ..11111X.
......... ......... ....11X.. ..X11....
......... ......... ......... .........
Please excuse the bad ascii art.
For the second example, the corner pixel at the top could either be the rectangles left/top or right/top corner. Either is fine.
What steps are required to determine the corner pixels/positions in the above examples?
The corner pixels are the pixels the furthest apart. Find the top most row and the bottom most row. There will always be a corner pixel in those.
The corner pixel can only be the first or last pixel in this the topmost row row (or both if there's just the one).
So compare the distances between the first pixel in the topmost row and the last pixel in the bottom most row. And last pixel in topmost with the first in bottom most. The corners there are the the ones that are the furthest apart.
Since they are all the same distance in the Y you need the pixels with the greatest difference with regard to their x location. The corners are the pixels for which abs(x0-x1) is the greatest, where x0 is in the topmost row and x1 is in the bottom most.
Repeat this for the rightmost and leftmost rows.
If the topmost corner is on the left then the leftmost corner is on the bottom, the bottom most corner is on the right and the rightmost corner is on the top. Once you have the top, bottom, left, and right rows there's really just the two possibilities that can be solved in an if statement. But, due to the edge condition of having one pixel on the topmost row and two on the rightmost row, you're better off just running the algorithm again with transposed x and ys to solve for the other two corners rather than trying to spare yourself an if statement.
Not every monochrome bitmap is going to give you an answer. A complete algorithm needs an output that says "Unique corners not present". The following figures give an example of the problem:
......... ......... ..........
...XX.... ....X.... ....XX....
..X11X... ...111... ...1111...
..X11X... ..X111X.. ..X1111X..
...XX.... ...111... ..X1111X..
......... ....X.... ...X11X...
......... ......... ....XX....
......... ......... ..........
The degeneracy illustrated happens when the slopes of the rectangle are +1 and -1 and the position of the center is half-integral . It can also occur with other combinations of slopes and positions. The general answer will need to contain pixel-pairs as the best approximation of a vertex.
Start with the bounding box of the rectangle.
For each corner, move it clockwise until there is a black square.
public class Test {
String[][] squares = {
{
".........",
".X11111X.",
".1111111.",
".1111111.",
".X11111X.",
".........",
".........",
".........",
".........",},
{
".........",
"....X....",
"...111...",
"..X111X..",
"...111...",
"....X....",
".........",
".........",
".........",},
{
".........",
"..X11....",
"..11111X.",
"..111111.",
".1111111.",
".111111..",
".X11111..",
"....11X..",
".........",},
{
".........",
"....11X..",
"X111111..",
".111111..",
".1111111.",
"..111111.",
"..11111X.",
"..X11....",
".........",}};
private static final int WHITE = 0;
private static final int BLACK = 1;
class Point {
private final int x;
private final int y;
public Point(Point p) {
this.x = p.x;
this.y = p.y;
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public String toString() {
return "{" + x + "," + y + '}';
}
// What colour is there?
public int colour(int[][] bmp) {
// Make everything off-bmp black.
if (x < 0 || y < 0 || y >= bmp.length || x >= bmp[y].length) {
return BLACK;
}
return bmp[y][x];
}
private Point step(Point d) {
return new Point(x + d.x, y + d.y);
}
}
class Rectangle {
private final Point[] corners = new Point[4];
public Rectangle(Point[] corners) {
// Points are immutable but corners are not.
System.arraycopy(corners, 0, this.corners, 0, corners.length);
}
public Rectangle(Rectangle r) {
this(r.corners());
}
public Rectangle(Point a, Point b, Point c, Point d) {
corners[0] = a;
corners[1] = b;
corners[2] = c;
corners[3] = d;
}
private Rectangle(Point tl, Point br) {
this(tl, new Point(br.x, tl.y), br, new Point(tl.x, br.y));
}
public Point[] corners() {
return Arrays.copyOf(corners, corners.length);
}
#Override
public String toString() {
return Arrays.toString(corners);
}
}
private Rectangle getBoundingBox(int[][] bmp) {
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, maxX = 0, maxY = 0;
for (int r = 0; r < bmp.length; r++) {
for (int c = 0; c < bmp[r].length; c++) {
if (bmp[r][c] != WHITE) {
if (minX > c) {
minX = c;
}
if (minY > r) {
minY = r;
}
if (maxX < c) {
maxX = c;
}
if (maxY < r) {
maxY = r;
}
}
}
}
return new Rectangle(new Point(minX, minY), new Point(maxX, maxY));
}
Point[] clockwise = new Point[]{
new Point(1, 0),
new Point(0, 1),
new Point(-1, 0),
new Point(0, -1)};
private void test(int[][] bmp) {
// Find the bounding box.
Rectangle bBox = getBoundingBox(bmp);
System.out.println("bbox = " + bBox);
Point[] corners = bBox.corners();
// Move each corner clockwise until it is black.
for (int p = 0; p < corners.length; p++) {
while (corners[p].colour(bmp) == WHITE) {
corners[p] = corners[p].step(clockwise[p]);
}
}
System.out.println("rect = " + new Rectangle(corners));
}
private void test(String[] square) {
// Build the int[][].
// . -> White
// X/1 -> Black
int[][] bmp = new int[square.length][];
for (int r = 0; r < square.length; r++) {
bmp[r] = new int[square[r].length()];
for (int c = 0; c < bmp[r].length; c++) {
switch (square[r].charAt(c)) {
case '.':
bmp[r][c] = WHITE;
break;
case 'X':
case '1':
bmp[r][c] = BLACK;
break;
}
}
}
test(bmp);
}
public void test() {
for (String[] square : squares) {
test(square);
}
}
public static void main(String args[]) {
try {
new Test().test();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
}
prints
bbox = [{1,1}, {7,1}, {7,4}, {1,4}]
rect = [{1,1}, {7,1}, {7,4}, {1,4}]
bbox = [{2,1}, {6,1}, {6,5}, {2,5}]
rect = [{4,1}, {6,3}, {4,5}, {2,3}]
bbox = [{1,1}, {7,1}, {7,7}, {1,7}]
rect = [{2,1}, {7,2}, {6,7}, {1,6}]
bbox = [{0,1}, {7,1}, {7,7}, {0,7}]
rect = [{4,1}, {7,4}, {4,7}, {0,2}]
Could be improved by looking for a run of black and choosing the middle of the run.
Scan the image from the top, row by row until you find a black run.
Repeat four ways, from the bottom up, left, right, giving you eight corner candidates.
Take the run endpoints the farthest apart in the top and bottom rows. This tells you which endpoints to take vertically.
One of the exercises in my Java textbook says "Consult the API documentation to find methods for:
Computing the smallest rectangle that contains two given rectangles. • Returning a random floating-point number."
I've looked at the Java API for class Rectangle, but I can't find one that computes the smaller rectangle. The closest methods I've found are union and bounds, but I don't think that's correct.
I found min from the Java Math class and wrote a test program to see if it would work, but min cannot have arguments of rectangles.
Here's the code I wrote:
import java.awt.Rectangle;
public class RectangleSize {
public static void main(String[] args)
{
Rectangle a = new Rectangle(5, 5, 10, 10);
Rectangle b = new Rectangle(5, 5, 20, 20);
int min = Math.min(a, b); //In Eclipse, I get an error.
System.out.println(min);
}
}
I am studying java recently using the textbook of Big Java Early Object. I found the same question in chapter 2. Eventually I found the answer by looking at Java SE8 API list.
First use method .add() to combine 2 rectangles.
Note: actually, this combined one is already the one you need. But you can get a new rectangle (same size and location) at step 2.
Them use .getBounds() to get the smallest rectangle containing the combined one.
.
import java.awt.*;
public class RectangleTester01 {
public static void main(String[] args) {
Rectangle box1 = new Rectangle(10, 20, 40, 40);
Rectangle box2 = new Rectangle(20, 30, 60, 60);
box1.add(box2);
System.out.println(box1);
Rectangle box3 = box1.getBounds();
System.out.println(box3);
}
}
output is:
java.awt.Rectangle[x=10,y=20,width=70,height=70]
java.awt.Rectangle[x=10,y=20,width=70,height=70]
You want to use Rectangle.contains . You would be given many rectangles. You'd need to loop through all the rectangles and see if it contains the two rectangles given. If it does you should calculate the size of that rectangle. In the end you take the rectangle with the smallest size.
public Rectangle getSmallest(Rectangle one, Rectangle two, Rectangle[] rectangles) {
Rectangle smallest = null;
double area = Double.MAX_VALUE;
for (Rectangle r: rectangles) {
if (r.contains(one) && r.contains(two)) {
calculatedArea = r.getWidth() * r.getHeight();
if (calculatedArea < area) {
area = calculatedArea;
smallest = r;
}
}
}
return r;
}
I think the method you're looking for is Rectangle2D.createUnion. It combines two rectangles to make a bigger one that contains both with a minimum of extra space.
Here is another question to find a rectangle which contains a list of rectangles.find-smallest-area-that-contains-all-the-rectangles.
Here is my brute answer which is not accuracy and did not try the union function of Rectengle and also the createUnion(Rectengle2D r) as well.
Math.min can help find the minimum number in a number array. You may write a similar one for Rectangle. This is something from 1D to 2D in my mind. BTW, it would be much interesting if you extends it to 3D or nD objects calculation.
package com.stackoverflow.q26311076;
import java.awt.Rectangle;
public class Test {
public static void main(String[] args) {
Rectangle a = new Rectangle(5, 5, 10 , 10);
Rectangle b = new Rectangle(5, 5, 20 , 20);
Rectangle min = getMin(a, b) ;
// ...
}
public static Rectangle getMin(Rectangle a, Rectangle b) {
//find the min range in X.
{
double x1 = a.getX();
double x2 = a.getX() + a.getWidth();
double x3 = b.getX();
double x4 = b.getX() + b.getWidth();
double minX1 =Math.min( Math.min(x1, x2), Math.min(x3, x4)) ;
double maxX1 =Math.max( Math.max(x1, x2), Math.max(x3, x4)) ;
}
//find the min range in Y.
{
double y1 = a.getY();
double y2 = a.getY() + a.getHeight();
double y3 = b.getY();
double y4 = b.getY() + b.getHeight();
double minY1 =Math.min( Math.min(y1, y2), Math.min(y3, y4)) ;
double maxY1 =Math.max( Math.max(y1, y2), Math.max(y3, y4)) ;
}
//build new rectangle with X & Y
Rectangle r = new Rectangle();
r.setRect(minX1, minY1, maxX1 - minX1, maxY1 - minY1);
return r;
}
}
I have a program where an entity moves around in two-dimensional space. To move one step, the entity picks its next point, and then sets it as his current point.
Sometimes, however, the entity's next point lies in an Area (java.awt.geom.Area) that is forbidden (the "forbidden area" is actually a velocity obstacle).
How can the entity pick the point outside the Area which is closest to the entity's preferred point?
The Area is composed of different shapes (sometimes, the shapes are not touching).
My initial plan was to simply draw a line to the preferred point. Wherever the line intersected the Area first, this would be the next-best point. However, finding the intersection between a line and an Area turns out to be quite complex.
EDIT: This wouldn't necessarily find the closest point. This would just find the closet point on the same trajectory. I'm looking for the closest possible point.
Perhaps Area isn't the best class to use. All I require is something that can add multiple shapes, even when the shapes aren't touching.
I've solved the problem:
First, find all the line segments that constrain the Area. I've written code to do that on a different answer.
Then, it's just a matter of iterating through each line segment, and recording the point on the segment that's closest to the entity's desired point. Store these in the data structure of your choice (e.g., an ArrayList).
See: Shortest distance between a point and a line segment
Lastly, determine which of the points is closest to the desired point. Voilà!
Here's a demonstration:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class AreaTest extends JFrame{
private static final long serialVersionUID = -2221432546854106311L;
Area area = new Area();
ArrayList<Line2D.Double> areaSegments = new ArrayList<Line2D.Double>();
Point2D.Double insidePoint = new Point2D.Double(225, 225);
Point2D.Double closestPoint = new Point2D.Double(-1, -1);
Point2D.Double bestPoint = new Point2D.Double(-1, -1);
ArrayList<Point2D.Double> closestPointList = new ArrayList<Point2D.Double>();
AreaTest() {
Path2D.Double triangle = new Path2D.Double();
Random random = new Random();
// Draw three random triangles
for (int i = 0; i < 3; i++) {
triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.closePath();
area.add(new Area(triangle));
triangle.reset();
}
// Place a point inside the area
if (!area.contains(insidePoint)); {
while (!area.contains(insidePoint)) {
insidePoint.setLocation(random.nextInt(400) + 50, random.nextInt(400) + 50);
}
}
// Note: we're storing double[] and not Point2D.Double
ArrayList<double[]> areaPoints = new ArrayList<double[]>();
double[] coords = new double[6];
for (PathIterator pi = area.getPathIterator(null); !pi.isDone(); pi.next()) {
// Because the Area is composed of straight lines
int type = pi.currentSegment(coords);
// We record a double array of {segment type, x coord, y coord}
double[] pathIteratorCoords = {type, coords[0], coords[1]};
areaPoints.add(pathIteratorCoords);
}
double[] start = new double[3]; // To record where each polygon starts
for (int i = 0; i < areaPoints.size(); i++) {
// If we're not on the last point, return a line from this point to the next
double[] currentElement = areaPoints.get(i);
// We need a default value in case we've reached the end of the ArrayList
double[] nextElement = {-1, -1, -1};
if (i < areaPoints.size() - 1) {
nextElement = areaPoints.get(i + 1);
}
// Make the lines
if (currentElement[0] == PathIterator.SEG_MOVETO) {
start = currentElement; // Record where the polygon started to close it later
}
if (nextElement[0] == PathIterator.SEG_LINETO) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
nextElement[1], nextElement[2]
)
);
} else if (nextElement[0] == PathIterator.SEG_CLOSE) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
start[1], start[2]
)
);
}
}
// Calculate the nearest point on the edge
for (Line2D.Double line : areaSegments) {
// From: https://stackoverflow.com/questions/6176227
double u =
((insidePoint.getX() - line.x1) * (line.x2 - line.x1) + (insidePoint.getY() - line.y1) * (line.y2 - line.y1))
/ ((line.x2 - line.x1) * (line.x2 - line.x1) + (line.y2 - line.y1) * (line.y2 - line.y1));
double xu = line.x1 + u * (line.x2 - line.x1);
double yu = line.y1 + u * (line.y2 - line.y1);
if (u < 0) {
closestPoint.setLocation(line.getP1());
} else if (u > 1) {
closestPoint.setLocation(line.getP2());
} else {
closestPoint.setLocation(xu, yu);
}
closestPointList.add((Point2D.Double) closestPoint.clone());
if (closestPoint.distance(insidePoint) < bestPoint.distance(insidePoint)) {
bestPoint.setLocation(closestPoint);
}
}
setSize(new Dimension(500, 500));
setLocationRelativeTo(null); // To center the JFrame on screen
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setVisible(true);
}
public void paint(Graphics g) {
// Fill the area
Graphics2D g2d = (Graphics2D) g;
g.setColor(Color.lightGray);
g2d.fill(area);
// Draw the border line by line
g.setColor(Color.black);
for (Line2D.Double line : areaSegments) {
g2d.draw(line);
}
// Draw the inside point
g.setColor(Color.red);
g2d.fill(
new Ellipse2D.Double(
insidePoint.getX() - 3,
insidePoint.getY() - 3,
6,
6
)
);
// Draw the other close points
for (Point2D.Double point : closestPointList) {
g.setColor(Color.black);
g2d.fill(
new Ellipse2D.Double(
point.getX() - 3,
point.getY() - 3,
6,
6
)
);
}
// Draw the outside point
g.setColor(Color.green);
g2d.fill(
new Ellipse2D.Double(
bestPoint.getX() - 3,
bestPoint.getY() - 3,
6,
6
)
);
}
public static void main(String[] args) {
new AreaTest();
}
}
Here's the result:
And again:
View my answer on this post
You can get the closest point outside of a polygon with a simple and lightweight approach:
Simply find the closest line segment, and find the perpendicular angle to that segment that intercepts the input point.
Example Code:
Vector2 is 2 doubles, x and y (Like Unity)
public class PolyCollisions {
// Call this function...
public static Vector2 doCollisions (Vector2[] polygon, Vector2 point) {
if(!pointIsInPoly(polygon, point)) {
// The point is not colliding with the polygon, so it does not need to change location
return point;
}
// Get the closest point off the polygon
return closestPointOutsidePolygon(polygon, point);
}
// Check if the given point is within the given polygon (Vertexes)
//
// If so, call on collision if required, and move the point to the
// closest point outside of the polygon
public static boolean pointIsInPoly(Vector2[] verts, Vector2 p) {
int nvert = verts.length;
double[] vertx = new double[nvert];
double[] verty = new double[nvert];
for(int i = 0; i < nvert; i++) {
Vector2 vert = verts[i];
vertx[i] = vert.x;
verty[i] = vert.y;
}
double testx = p.x;
double testy = p.y;
int i, j;
boolean c = false;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
// Gets the closed point that isn't inside the polygon...
public static Vector2 closestPointOutsidePolygon (Vector2[] poly, Vector2 point) {
return getClosestPointInSegment(closestSegment(poly, point), point);
}
public static Vector2 getClosestPointInSegment (Vector2[] segment, Vector2 point) {
return newPointFromCollision(segment[0], segment[1], point);
}
public static Vector2 newPointFromCollision (Vector2 aLine, Vector2 bLine, Vector2 p) {
return nearestPointOnLine(aLine.x, aLine.y, bLine.x, bLine.y, p.x, p.y);
}
public static Vector2 nearestPointOnLine(double ax, double ay, double bx, double by, double px, double py) {
// https://stackoverflow.com/questions/1459368/snap-point-to-a-line-java
double apx = px - ax;
double apy = py - ay;
double abx = bx - ax;
double aby = by - ay;
double ab2 = abx * abx + aby * aby;
double ap_ab = apx * abx + apy * aby;
double t = ap_ab / ab2;
if (t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
return new Vector2(ax + abx * t, ay + aby * t);
}
public static Vector2[] closestSegment (Vector2[] points, Vector2 point) {
Vector2[] returns = new Vector2[2];
int index = closestPointIndex(points, point);
returns[0] = points[index];
Vector2[] neighbors = new Vector2[] {
points[(index+1+points.length)%points.length],
points[(index-1+points.length)%points.length]
};
double[] neighborAngles = new double[] {
getAngle(new Vector2[] {point, returns[0], neighbors[0]}),
getAngle(new Vector2[] {point, returns[0], neighbors[1]})
};
if(neighborAngles[0] < neighborAngles[1]) {
returns[1] = neighbors[0];
} else {
returns[1] = neighbors[0];
}
return returns;
}
public static double getAngle (Vector2[] abc) {
// https://stackoverflow.com/questions/1211212/how-to-calculate-an-angle-from-three-points
// atan2(P2.y - P1.y, P2.x - P1.x) - atan2(P3.y - P1.y, P3.x - P1.x)
return Math.atan2(abc[2].y - abc[0].y, abc[2].x - abc[0].x) - Math.atan2(abc[1].y - abc[0].y, abc[1].x - abc[0].x);
}
//public static Vector2 lerp (Vector2 a, Vector2 b, double c) {
//
// return new Vector2(c*(a.x-b.x)+b.x, c*(a.y-b.y)+b.y);
//
//}
/*public static Vector2 closestPoint (Vector2[] points, Vector2 point) {
int leastDistanceIndex = 0;
double leastDistance = Double.MAX_VALUE;
for(int i = 0; i < points.length; i++) {
double dist = distance(points[i], point);
if(dist < leastDistance) {
leastDistanceIndex = i;
leastDistance = dist;
}
}
return points[leastDistanceIndex];
}*/
public static int closestPointIndex (Vector2[] points, Vector2 point) {
int leastDistanceIndex = 0;
double leastDistance = Double.MAX_VALUE;
for(int i = 0; i < points.length; i++) {
double dist = distance(points[i], point);
if(dist < leastDistance) {
leastDistanceIndex = i;
leastDistance = dist;
}
}
return leastDistanceIndex;
}
public static double distance (Vector2 a, Vector2 b) {
return Math.sqrt(Math.pow(Math.abs(a.x-b.x), 2)+Math.pow(Math.abs(a.y-b.y), 2));
}
}
Useful Links / Answers
Snap Point to Line
How to calculate an angle from 3 points
The most easy (and most inefficient) approach would be a brute force.
You have a preferred point inside an area. to find the closest point to it: hold two variables, one for minimal distance and one for current closest point. now simply step over every other point in your two dimensional space: if that point is not inside the forbidden area (or any forbidden area if there are many), then calculate the distance between it and the preferred point. If that distance is less than the current minimal distance, then make it become the current minimal distance and make the point become the current closest point.
when you finish, you will have the closest point outside the area and if none was found, you stay on your original point.
I am not specialist in geometry algorithms, but if the two dimensional space is very big and the calculation is not finishing fast enough, maybe you can try to improve it with the following: the Area class has a contains method that "tests if the interior of the Shape entirely contains the specified rectangular area". therefore, start creating rectangles(or squares) around the preferred point. you start with the minimal rectangle surrounding the point and on every loop you increase it by one point in each direction. for every rectangle that you create, check if it is contained in the area. you stop calculating rectangles when you hit the first rectangle that is not entirely contained in the area. then, you use the above algorithm (the brute force) but only on points contained in this rectangle and that are not inside the area.
The formula for distance between two points is (javascript):
var xDiff = ( point1x - point2x ),
yDiff = ( point1y - point2y ),
distance = Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );
Loop around your "proposed new point", starting at one x-1, y-1 to x+1, y+1. At each point check to see that it's not a forbidden point, not the point you just came from, and not off the boundaries of the map. If it meets all those criteria, use the above formula to measure the distance and add it to an array. At the end of your "1-point out" loop, check if there are any distances in that array. If so, take the smallest one and you're done. If there aren't any, move onto x-2, y-2 to x+2, y+2 (2 points out).
This will be extremely fast for the small area you are referring to.
Demo: http://jsfiddle.net/ThinkingStiff/V7Bqm/
var X = 0,
Y = 1,
currentPoint = [5,5],
proposedPoint = [5,6],
forbiddenPoints = [[5,6],[6,6],[4,7],[5,7],[6,7],[4,8],[5,8]],
map = { left:1, top:1, right:10, bottom:10 };
function closestSafePoint( point ) {
var x = point[X], y = point[Y], safePoints = [];
for( var left = x - 1, top = y - 1, right = x + 1, bottom = y + 1;
left <= map.left || top <= map.top || right <= map.right || bottom <= map.bottom;
left--, top--, right++, bottom++) {
checkHorizontalPoints( safePoints, point, left, right, top );
checkHorizontalPoints( safePoints, point, left, right, bottom );
checkVerticalPoints( safePoints, point, top + 1, bottom - 1, left );
checkVerticalPoints( safePoints, point, top + 1, bottom - 1, right );
safePoints.sort( function( a, b ){ return a[1] - b[1] } );
return safePoints.length ? safePoints[0] : point;
};
};
function checkHorizontalPoints( points, fromPoint, startX, endX, y ) {
for( var x = startX; x <= endX ; x++ ) {
var toPoint = [x, y];
if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {
points.push( [toPoint, distance( fromPoint, toPoint )] );
};
};
};
function checkVerticalPoints( points, fromPoint, startY, endY, x ) {
for( var y = startY; y <= endY ; y++ ) {
var toPoint = [x, y];
if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {
points.push( [toPoint, distance( fromPoint, toPoint )] );
};
};
};
function isForbidden( point ) {
for( var index = 0; index < forbiddenPoints.length; index++ ) {
if( forbiddenPoints[index].toString() == point.toString() ) return true;
};
};
function isCurrent( point ) {
return currentPoint.toString() == point.toString() ? true : false;
};
function onMap( point ) {
var x = point[X], y = point[Y];
return x >= map.left && y >= map.top && x <= map.right && y <= map.bottom;
};
function distance( pointA, pointB ) {
var xDiff = ( pointA[X] - pointB[X] ),
yDiff = ( pointA[Y] - pointB[Y] );
return Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );
};
console.log(
'current: ' + currentPoint + ', '
+ 'proposed: ' + proposedPoint + ', '
+ 'closest: ' + closestSafePoint( proposedPoint )[0]
);
One optimization you could make to this, if you're fairly sure most of your safe spots will be one or two points away is to break out as soon as you get to a point thats distance is the same as the level you're on. So if you're on loop one, and you get a point that is distance = 1, stop, since you'll never get closer than that.
UPDATE: I noticed you added "same trajectory" to your question. But in one of the comments, you also say it can't jump over the forbidden area. Those statements seem to conflict.
Same trajectory is a little more tricky and requires some trig. Check out my demo of circular divs at http://jsfiddle.net/ThinkingStiff/uLu7v/. There is a "point on ray" function halfway down at:
$this.siblings( ".circle" ).each( function()
This calculates the distance to move the surrounding circles on a ray away from the selected circle. This could be used to calculate a point on your trajectory. But, I think my original function is actually what you're looking for and you didn't mean same trajectory.