Related
I need to draw a border in Unity. I have some randomly generated squares, each of them at least neighbour to another square. I'll use some kind of line renderer, so I need to give line renderer points' coordinates which will create the border.
Figure one is my squares, Figure two is expected red border. In figure three, I tried to explain what I need, points in an order which will generate the red border around those squares.
I know each boxes world position exactly. So what I'm looking is an algorithm to move from point a to b, to create that border.
Any pseudo algorithm will be helpful.
(I will use it in an turn based rpg, where I want to highlight the area which a character can move. I dont want to highlight the whole squares as area, but only the border. Like its in XCOM games)
EDIT :
I didn't realized that an ordered list of coordinates was required, my original answer was making a non oriented graph around the shape.
I updated the answer to add a function that would build an oriented list from this graph
Considering you're using Unity, I made a very simple algorithm.
Knowing only the coordinates of every vertices that is part of the figure you want to outline, and with the assumption that the distance between them is always the same, I came up with this :
Firstly a very crude way of making the squares :
The assumption is that I the coordinates have no concept of what are their edges, only the four points of each square are given.
For the sake of this example I've done this handcrafting a list of list of vertex.
public void CreateCoords()
{
_squares = new List<List<Vector3>>()
{
new List<Vector3>()
{
new Vector3(0,0,0),
new Vector3(0,0,1),
new Vector3(1,0,0),
new Vector3(1,0,1),
},
new List<Vector3>()
{
new Vector3(0,0,1),
new Vector3(1,0,1),
new Vector3(1,0,2),
new Vector3(0,0,2),
},
new List<Vector3>()
{
new Vector3(1,0,1),
new Vector3(2,0,1),
new Vector3(2,0,2),
new Vector3(1,0,2),
},
new List<Vector3>()
{
new Vector3(2,0,1),
new Vector3(2,0,2),
new Vector3(3,0,1),
new Vector3(3,0,2),
},
new List<Vector3>()
{
new Vector3(2,0,1),
new Vector3(3,0,1),
new Vector3(2,0,0),
new Vector3(3,0,0),
},
new List<Vector3>()
{
new Vector3(4,0,1),
new Vector3(3,0,1),
new Vector3(4,0,0),
new Vector3(3,0,0),
},
new List<Vector3>()
{
new Vector3(1,0,2),
new Vector3(1,0,3),
new Vector3(2,0,2),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(3,0,2),
new Vector3(3,0,3),
new Vector3(2,0,2),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(3,0,4),
new Vector3(3,0,3),
new Vector3(2,0,4),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(1,0,4),
new Vector3(1,0,3),
new Vector3(2,0,4),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(3,0,4),
new Vector3(3,0,5),
new Vector3(2,0,4),
new Vector3(2,0,5),
},
new List<Vector3>()
{
new Vector3(3,0,2),
new Vector3(3,0,3),
new Vector3(4,0,2),
new Vector3(4,0,3),
},
};
}
Here's the result :
Now for the algorithm :
My solution is based on the fact that all edges that are part of the border will only appear once.
It also works on the assumption that the coordinates of the squares are not ordered, the algorithm would be a bit simpler if it was the case.
private void ComputeEdges()
{
// The edges collection is a set of pair of vertex
_edges = new HashSet<KeyValuePair<Vector3, Vector3>>();
foreach (var square in _squares)
{
// Iterate over the coordinates to compute the edges
// Using for loop to skip already processed edges
var squareCount = square.Count;
for (var i = 0; i < squareCount; i++)
{
// The source vertex
var src = square[i];
for (var j = 0; j < squareCount; j++)
{
if (i == j) continue;
// The vertex with whom we want to determine if they form and edge
var dest = square[j];
// Check the distance between them to filter out the diagonal edges
if (!(Math.Abs(Vector3.Distance(src, dest) - edgeDistance) < 0.001)) continue;
var edge = new KeyValuePair<Vector3, Vector3>(src, dest);
// _edges is a set, making it viable to use Contains
// even when the collections contains a lot of elements
if (_edges.Contains(edge))
{
// If the edge already exists in the set,
// it means its not part of the border
_edges.Remove(edge);
}
else
{
_edges.Add(edge);
}
}
}
}
}
Here's a simple Gizmos example that will display the borders.
foreach (var (src, dest) in _edges)
{
Gizmos.DrawLine(src, dest);
}
And here's the final result :
As I understood later that an ordered list of points would be prefered, I made another algorithm that could make one out of the edges collection.
public void BuildList()
{
_coordsList = new List<Vector3>();
// Make a copy of the edges so we can remove items from it
// without destroying the original collection
var copy = new HashSet<KeyValuePair<Vector3, Vector3>>(_edges);
// Add the first pair before starting the loop
var previousEdge = _edges.First();
_coordsList.Add(previousEdge.Key);
_coordsList.Add(previousEdge.Value);
KeyValuePair<Vector3, Vector3> currentEdge;
// While there is an edge that follows the previous one
while (!(currentEdge = copy.FirstOrDefault(pair => pair.Key == previousEdge.Value))
.Equals(default(KeyValuePair<Vector3, Vector3>)))
{
// Our graph is not oriented but we want to ignores edges
// that go back from where we went
if (currentEdge.GetHashCode() == previousEdge.GetHashCode())
{
copy.Remove(currentEdge);
continue;
}
// Add the vertex to the list and continue
_coordsList.Add(currentEdge.Value);
previousEdge = currentEdge;
// Remove traversed nodes
copy.Remove(currentEdge);
}
}
A case might be made about the performance, has my algorithm has first to computes the edges, makes uses of the Vector3.Distance function and frequently uses a Contains() method.
Building the edges iterating over everyone of them using the distance is clearly not optimal performance wise, but I wanted to keep this part simple and I don't think the impact will make it that much different compared to the other algorithm proposed.
As for the Contains() method, keep in mind that it comes from a HashSet, making it of the same complexity as using the indexer operator on a Dictionnary or a List.
For the sake of completeness and ease of use, here's the full class :
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace DrawOuterGraph
{
public class OuterGraph: MonoBehaviour
{
private List<List<Vector3>> _squares;
private HashSet<KeyValuePair<Vector3, Vector3>> _edges;
private List<Vector3> _coordsList;
[SerializeField]
private float edgeDistance = 1;
private void Start()
{
CreateCoords();
ComputeEdges();
BuildList();
}
private void ComputeEdges()
{
// The edges collection is a set of pair of vertex
_edges = new HashSet<KeyValuePair<Vector3, Vector3>>();
foreach (var square in _squares)
{
// Iterate over the coordinates to compute the edges
// Using for loop to skip already processed edges
var squareCount = square.Count;
for (var i = 0; i < squareCount; i++)
{
// The source vertex
var src = square[i];
for (var j = 0; j < squareCount; j++)
{
if (i == j) continue;
// The vertex with whom we want to determine if they form and edge
var dest = square[j];
// Check the distance between them to filter out the diagonal edges
if (!(Math.Abs(Vector3.Distance(src, dest) - edgeDistance) < 0.001)) continue;
var edge = new KeyValuePair<Vector3, Vector3>(src, dest);
// _edges is a set, making it viable to use Contains
// even when the collections contains a lot of elements
if (_edges.Contains(edge))
{
// If the edge already exists in the set,
// it means its not part of the border
_edges.Remove(edge);
}
else
{
_edges.Add(edge);
}
}
}
}
}
public void BuildList()
{
_coordsList = new List<Vector3>();
// Make a copy of the edges so we can remove items from it
// without destroying the original collection
var copy = new HashSet<KeyValuePair<Vector3, Vector3>>(_edges);
// Add the first pair before starting the loop
var previousEdge = _edges.First();
_coordsList.Add(previousEdge.Key);
_coordsList.Add(previousEdge.Value);
KeyValuePair<Vector3, Vector3> currentEdge;
// While there is an edge that follows the previous one
while (!(currentEdge = copy.FirstOrDefault(pair => pair.Key == previousEdge.Value))
.Equals(default(KeyValuePair<Vector3, Vector3>)))
{
// Our graph is not oriented but we want to ignores edges
// that go back from where we went
if (currentEdge.GetHashCode() == previousEdge.GetHashCode())
{
copy.Remove(currentEdge);
continue;
}
// Add the vertex to the list and continue
_coordsList.Add(currentEdge.Value);
previousEdge = currentEdge;
// Remove traversed nodes
copy.Remove(currentEdge);
}
}
public void CreateCoords()
{
_squares = new List<List<Vector3>>()
{
new List<Vector3>()
{
new Vector3(0,0,1),
new Vector3(1,0,0),
new Vector3(0,0,0),
new Vector3(1,0,1),
},
new List<Vector3>()
{
new Vector3(0,0,1),
new Vector3(1,0,1),
new Vector3(1,0,2),
new Vector3(0,0,2),
},
new List<Vector3>()
{
new Vector3(1,0,1),
new Vector3(2,0,1),
new Vector3(2,0,2),
new Vector3(1,0,2),
},
new List<Vector3>()
{
new Vector3(2,0,1),
new Vector3(2,0,2),
new Vector3(3,0,1),
new Vector3(3,0,2),
},
new List<Vector3>()
{
new Vector3(2,0,1),
new Vector3(3,0,1),
new Vector3(2,0,0),
new Vector3(3,0,0),
},
new List<Vector3>()
{
new Vector3(4,0,1),
new Vector3(3,0,1),
new Vector3(4,0,0),
new Vector3(3,0,0),
},
new List<Vector3>()
{
new Vector3(1,0,2),
new Vector3(1,0,3),
new Vector3(2,0,2),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(3,0,2),
new Vector3(3,0,3),
new Vector3(2,0,2),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(3,0,4),
new Vector3(3,0,3),
new Vector3(2,0,4),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(1,0,4),
new Vector3(1,0,3),
new Vector3(2,0,4),
new Vector3(2,0,3),
},
new List<Vector3>()
{
new Vector3(3,0,4),
new Vector3(3,0,5),
new Vector3(2,0,4),
new Vector3(2,0,5),
},
new List<Vector3>()
{
new Vector3(3,0,2),
new Vector3(3,0,3),
new Vector3(4,0,2),
new Vector3(4,0,3),
},
};
}
private void OnDrawGizmos()
{
// Draw using the edges
if (_edges is not null)
{
foreach (var (src, dest) in _edges)
{
Gizmos.DrawLine(src, dest);
}
}
// Draw using the ordered list
if (_coordsList is null || _coordsList.Count == 0) return;
var previous = _coordsList[0];
foreach (var current in _coordsList)
{
Gizmos.DrawLine(previous, current);
previous = current;
}
}
}
}
I've found a way to help you.
I've written a function for you that does the following: iterates all the squares through their RectTransforms (easily replaceable with the Transform component), and for each of the squares it checks if its position is equal to another square with the side added up of the square. In this way, squares with at least one side in common are found.
If it finds a square with one side in common, it adds the two vertices in common to a list.
If at the end 4 vertices are found, the code understands that it is inside the figure and therefore must not have edges, but if three or less are found, they are added to the final list of external vertices.
The problem with the code is that it can be more optimized you have to customize the if () to check if they have a common side. In particular, since they are floats, it is simply not necessary to match them. I tried to round them up, but in particular cases there may be too great a margin of error. You can just change it by knowing the side of the square.
List<RectTransform> pos = new List<RectTransform>();
List<Vector2> results = new List<Vector2>();
int size = pos[0].sizeDelta.x;
for (int i = 0; i < pos.Count; i++)
{
List<Vector2> v = new List<Vector2>();
for (int o = 0; o < pos.Count; o++)
{
if (Mathf.Round(pos[i].position.x) == Mathf.Round(pos[o].position.x + size))
{
Add(new Vector2(pos[o].position.x + size / 2, pos[o].position.y + size / 2));
Add(new Vector2(pos[o].position.x + size / 2, pos[o].position.y - size / 2));
}
else if (Mathf.Round(pos[i].position.x) == Mathf.Round(pos[o].position.x - size))
{
Add(new Vector2(pos[o].position.x - size / 2, pos[o].position.y + size / 2));
Add(new Vector2(pos[o].position.x - size / 2, pos[o].position.y - size / 2));
}
else if (Mathf.Round(pos[i].position.y) == Mathf.Round(pos[o].position.y + size))
{
Add(new Vector2(pos[o].position.x + size / 2, pos[o].position.y + size / 2));
Add(new Vector2(pos[o].position.x - size / 2, pos[o].position.y + size / 2));
}
else if (Mathf.Round(pos[i].position.y) == Mathf.Round(pos[o].position.y - size))
{
Add(new Vector2(pos[o].position.x + size / 2, pos[o].position.y - size / 2));
Add(new Vector2(pos[o].position.x - size / 2, pos[o].position.y - size / 2));
}
if (v.Count == 4)
break;
}
if (v.Count == 4)
continue;
for (int o = 0; i < v.Count; o++)
if (!results.Contains(v[o]))
results.Add(v[o]);
void Add(Vector2 _v)
{
if (!v.Contains(_v))
v.Add(_v);
}
}
To create the line renderer that joins all these vertices, I suggest you think like this:
Choose a vertex to start from. Compare that vertex with all the
others and check if the distance between that vertex and the compared
vertex is equal to the side of the square. In this case it means that
it is above, below, to the right or to the left of the first vertex.
You will have a maximum of 4 results, and add them all to a list.
Now take a vertex you just found and use it to compare it to all the
others, doing the same thing as before. Also this time you will find
at most 4 vertices, with the distance from that vetice equal to the
side of the square. The difference is that for sure among those
vertices you will also find the first vertex analyzed, and then check
if it is already present, and if necessary remove it and add the one
found. They will have the same value, but the order will be
different.
Choose another vertex among those exited and start over with the for
() loop.
You have to be careful because there are some things you have to think about for it to work that I didn't specify because it would become very long.
As mentioned, if you are good with C # you will be able to transform this reasoning into code.
Good work!
I’m going to assume that the movement area is connected. If not, you can
do a flood fill and run this algorithm on each of the connected areas.
The first order of business is to find one of the clockwise arcs on the
outer boundary. The left edge of the leftmost square, oriented up, will
do. Starting with this arc, we trace clockwise around the boundary.
There are three cases for each movement, demonstrated in the Python code
below. When the boundary is straight, we suppress the interior points.
image = """
##
#
##
#### #
#####
##
#
"""
squares = {
(x + 0.5, y - 0.5)
for (y, line) in enumerate(image.splitlines())
for (x, c) in enumerate(line)
if c == "#"
}
print("squares =", squares)
leftmost = min(squares)
up = (0, -1)
x, y = leftmost
dx, dy = up
boundary = []
while True:
point = (x + 0.5 * (dx + dy), y + 0.5 * (dy - dx))
left = (x + dx + dy, y + dy - dx)
straight = (x + dx, y + dy)
if left in squares: # turn left
boundary.append(point)
x, y = left
dx, dy = dy, -dx
elif straight in squares: # go straight
x, y = straight
else: # turn right
boundary.append(point)
dx, dy = -dy, dx
if ((x, y), (dx, dy)) == (leftmost, up):
break
print("boundary =", boundary)
Output:
squares = {(3.5, 1.5), (5.5, 4.5), (0.5, 3.5), (2.5, 0.5), (1.5, 4.5), (3.5, 0.5), (2.5, 3.5), (3.5, 6.5), (3.5, 3.5), (2.5, 2.5), (5.5, 3.5), (4.5, 4.5), (3.5, 2.5), (2.5, 5.5), (1.5, 3.5), (3.5, 5.5), (2.5, 4.5), (3.5, 4.5)}
boundary = [(0.0, 3.0), (2.0, 3.0), (2.0, 2.0), (3.0, 2.0), (3.0, 1.0), (2.0, 1.0), (2.0, 0.0), (4.0, 0.0), (4.0, 4.0), (5.0, 4.0), (5.0, 3.0), (6.0, 3.0), (6.0, 5.0), (4.0, 5.0), (4.0, 7.0), (3.0, 7.0), (3.0, 6.0), (2.0, 6.0), (2.0, 5.0), (1.0, 5.0), (1.0, 4.0), (0.0, 4.0)]
My approach is as simple as you expect it to be, we just check for the neighborhood and create a line to represent the free side.
Just to contextualize, I'm using an array of bool to represent what is a valid square and what isn't, and a List<(Vector3 from, Vector3 to)> to represent the border, and with some Gizmos' help we ended with something like
private List<(Vector3 from, Vector3 to)> border = new List<(Vector3 from, Vector3 to)>();
public bool[,] ValidPositions = new bool[10, 10];
(the beautiful serialized matrix come from Odin)
As I said, simple as it can be, check for neighbor
private bool CheckBox (int i, int j, Side sideToCheck)
=> sideToCheck switch
{
Side.Up => j == 9 || !ValidPositions[i, j + 1], // By 9 I mean max range of lines in the array
Side.Right => i == 9 || !ValidPositions[i + 1, j],
Side.Down => j == 0 || !ValidPositions[i, j - 1],
Side.Left => i == 0 || !ValidPositions[i - 1, j],
_ => false
};
And add a line
private (Vector3 from, Vector3 to) AddLine (int i, int j, Side side)
=> side switch
{
Side.Up => (new Vector3(i, j + 1),
new Vector3(i + 1, j + 1)),
Side.Right => (new Vector3(i + 1, j),
new Vector3(i + 1, j + 1)),
Side.Down => (new Vector3(i, j),
new Vector3(i + 1, j)),
Side.Left => (new Vector3(i, j),
new Vector3(i, j + 1)),
};
Call it is just some conditionals
private void RefreshContourPosition()
{
border.Clear();
for (var i = 0; i < ValidPositions.GetLength(0); i++)
{
for (var j = 0; j < ValidPositions.GetLength(1); j++)
{
if (!ValidPositions[i, j])
continue;
if (CheckBox(i, j, Side.Up))
border.Add(AddLine(i, j, Side.Up));
if (CheckBox(i, j, Side.Right))
border.Add(AddLine(i, j, Side.Right));
if (CheckBox(i, j, Side.Down))
border.Add(AddLine(i, j, Side.Down));
if (CheckBox(i, j, Side.Left))
border.Add(AddLine(i, j, Side.Left));
}
}
}
After this refresh, your border will have all lines that is part of the desired border.
Hope this helps, N
This answer is a bit borderline Stackoverflow policy violation. :)
I understand that you ask for a specific solution, but I feel that as your question is still on a fairly theoretical design level I just wanted to drop some alternatives. I'm no pro, but my gut feeling is that line renderer will get quite heavy, especially for something like a basic grid.
If you are planning to have an all time visible base grid, I would probably take a look at already existing grid shaders to draw this. This is cheaper and the grid can be huge with no impact. It's also a lot easier to play around with when it comes to aesthetics, like adding glow or blend the lines against the underlying surface etc.
For a 2d game, the Unity TileMap is a must to checkout.
https://github.com/ogxd/grid-shader-unity
https://assetstore.unity.com/packages/tools/simple-grid-shader-119988
https://blog.devgenius.io/introduction-to-tilemap-in-unity-part-1-beb7b5435c2
When it comes to the outline. You might want to explore using tiles rather than drawing the edges. I think the tiles will be a lot easier to work with, both when it comes to game logic and making it look good. With a tile grid you could do bitmasking to draw nice edges. This will be resource cheap and opens up for endless possibilities to get that outline look really cool (like the curved glowing style xcom uses).
If it is a 2d game you can use the Unity tile map, and it might even be possible to use Unity auto tile for the outline.
If 3d each tile could just be a 3d mesh with texture. In 3d it would probably be better to generate a (procedural) mesh and outline it with an outline shader.
The short story is that I personally think using shaders/sprites for these kinds of things might be a better approach. :)
https://gamedevelopment.tutsplus.com/tutorials/how-to-use-tile-bitmasking-to-auto-tile-your-level-layouts--cms-25673
https://learn.unity.com/tutorial/using-rule-tiles#5fe9914fedbc2a28d93ce461
https://alexanderameye.github.io/notes/rendering-outlines/
Best of luck!
Given a rectangle i would like to split it in a given amount n of sub-rectangles where:
a is the amount of subrectangles which should have a defined z SMALL area
b is the amount of subrectangles which should have a defined y MEDIUM area
c is the amount of subrectangles which should have a defined x BIGGER area
may be I should be able to define d and e . But let's leave them for now.
With "defined area" i mean the geometric area should be the same but the rectangles could have different shape.
May I ask ..which algorithm would You suggest to apply in this case.
I should have a function like this PSEUDO code:
RectBoundaries[] function getRectangles(screenRectangle, amountOfRectangleCategories, amountOfRectanglesPerCategory[]) {
function1 => getRectanglesForCategory1(amountOfRectanglesPerCategory[0], screenRectangle)
function2 => getRectanglesForCategory2(amountOfRectanglesPerCategory[1], screenRectangle)
function3 => getRectanglesForCategory3(amountOfRectanglesPerCategory[2], screenRectangle)
return function1 + function2 + function3;
}
EDITED:
How to split a rectangle in multiple (defined amount of) categories of decreasing smaller rectangles.
By your question I understand you first split your main area in 3 area. One big, one medium and one small. Given this I made a small algo in Javascript, hope it solve your problem
let canvas = document.getElementById("output");
let ctx = canvas.getContext('2d');
ctx.strokeStyle = 'rgb(255, 255, 255)';
function draw(rect) {
ctx.rect(rect[0], rect[1], rect[2], rect[3]);
ctx.stroke();
}
function splitArea(rect, nbRect) {
let [nbColumns, nbLines] = getBestRatio(nbRect);
let x=rect[0];
let y=rect[1];
let width = rect[2]/nbColumns;
let height = rect[3]/nbLines;
let result = new Array();
for(let i=0; i<nbLines; i++) {
for(let j=0; j<nbColumns; j++) {
result.push([x+j*width, y+i*height, width, height]);
}
}
return result;
}
function getBestRatio(nb) {
for(let i=Math.round(Math.sqrt(nb)); i>0; i--) {
if(nb%i==0) {
return [i, nb/i];
}
}
}
//main rectangle
let rect = [10, 10, 280, 280]; //x, y, width, height
// 3 differents area
let bigArea = [rect[0], rect[1], rect[2], rect[3]*2/3]; // 2∕3 area
let mediumArea = [rect[0], rect[1]+rect[3]*2/3, rect[2]*2/3, rect[3]*1/3]; // 2/3 * 1/3 area
let smallArea = [rect[0]+rect[2]*2/3, rect[1]+rect[3]*2/3, rect[2]*1/3, rect[3]*1/3]; // 1/3 * 1/3 area
draw(bigArea);
draw(mediumArea);
draw(smallArea);
splitArea(bigArea, 6).forEach(area => draw(area));
splitArea(mediumArea, 12).forEach(area => draw(area));
splitArea(smallArea, 3).forEach(area => draw(area));
<canvas id="output" height="300px" width="300px" style="background-color: grey">
I have a rectangular grid of variable size but averaging 500x500 with a small number of x,y points in it (less than 5). I need to find an algorithm that returns an x,y pair that is the farthest away possible from any of the other points.
Context: App's screen (grid) and a set of x,y points (enemies). The player dies and I need an algorithm that respawns them as far away from the enemies so that they don't die immediately after respawning.
What I have so far: The algorithm I wrote works but doesn't perform that great in slower phones. I'm basically dividing up the grid into squares (much like a tic tac toe) and I assign each square a number. I then check every single square against all enemies and store what the closest enemy was at each square. The square with the highest number is the square that has the closest enemy furthest away. I also tried averaging the existing points and doing something similar to this and while the performance was acceptable, the reliability of the method was not.
This is the simplest algorithm I could think of that still gives good results. It only checks 9 possible positions: the corners, the middle of the sides, and the center point. Most of the time the player ends up in a corner, but you obviously need more positions than enemies.
The algorithm runs in 0.013ms on my i5 desktop. If you replace the Math.pow() by Math.abs(), that comes down to 0.0088ms, though obviously with less reliable results. (Oddly enough, that's slower than my other answer which uses trigonometry functions.)
Running the code snippet (repeatedly) will show the result with randomly positioned enemies in a canvas element.
function furthestFrom(enemy) {
var point = [{x:0,y:0},{x:250,y:0},{x:500,y:0},{x:0,y:250},{x:250,y:250},{x:500,y:250},{x:0,y:500},{x:250,y:500},{x:500,y:500}];
var dist2 = [500000,500000,500000,500000,500000,500000,500000,500000,500000];
var max = 0, furthest;
for (var i in point) {
for (var j in enemy) {
var d = Math.pow(point[i].x - enemy[j].x, 2) + Math.pow(point[i].y - enemy[j].y, 2);
if (d < dist2[i]) dist2[i] = d;
}
if (dist2[i] > max) {
max = dist2[i];
furthest = i;
}
}
return(point[furthest]);
}
// CREATE TEST DATA
var enemy = [];
for (var i = 0; i < 5; i++) enemy[i] = {x: Math.round(Math.random() * 500), y: Math.round(Math.random() * 500)};
// RUN FUNCTION
var result = furthestFrom(enemy);
// SHOW RESULT ON CANVAS
var canvas = document.getElementById("canvas");
canvas.width = 500; canvas.height = 500;
canvas = canvas.getContext("2d");
for (var i = 0; i < 5; i++) {
paintDot(canvas, enemy[i].x, enemy[i].y, 10, "red");
}
paintDot(canvas, result.x, result.y, 20, "blue");
function paintDot(canvas, x, y, size, color) {
canvas.beginPath();
canvas.arc(x, y, size, 0, 6.2831853);
canvas.closePath();
canvas.fillStyle = color;
canvas.fill();
}
<BODY STYLE="margin: 0; border: 0; padding: 0;">
<CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background-color: #EEE;"></CANVAS>
</BODY>
This is similar to what you are already doing, but with two passes where the first pass can be fairly crude. First decrease resolution. Divide the 500x500 grid into 10x10 grids each of which is 50x50. For each of the resulting 100 subgrids -- determine which have at least one enemy and locate the subgrid that is furthest away from a subgrid which contains an enemy. At this stage there is only 100 subgrids to worry about. Once you find the subgrid which is furthest away from an enemy -- increase resolution. That subgrid has 50x50 = 2500 squares. Do your original approach with those squares. The result is 50x50 + 100 = 2600 squares to process rather than 500x500 = 250,000. (Adjust the numbers as appropriate for the case in which there isn't 500x500 but with the same basic strategy).
Here is a Python3 implementation. It uses two functions:
1) fullResSearch(a,b,n,enemies) This function takes a set of enemies, a corner location (a,b) and an int, n, and finds the point in the nxn square of positions whose upper-left hand corner is (a,b) and finds the point in that square whose which has the maximum min-distance to an enemy. The enemies are not assumed to be in this nxn grid (although they certainly can be)
2) findSafePoint(n, enemies, mesh = 20) This function takes a set of enemies who are assumed to be in the nxn grid starting at (0,0). mesh determines the size of the subgrids, defaulting to 20. The overall grid is split into mesh x mesh subgrids (or slightly smaller along the boundaries if mesh doesn't divide n) which I think of as territories. I call a territory an enemy territory if it has an enemy in it. I create the set of enemy territories and pass it to fullResSearch with parameter n divided by mesh rather than n. The return value gives me the territory which is farthest from any enemy territory. Such a territory can be regarded as fairly safe. I feed that territory back into fullResSearch to find the safest point in that territory as the overall return function. The resulting point is either optimal or near-optimal and is computed very quickly. Here is the code (together with a test function):
import random
def fullResSearch(a,b,n,enemies):
minDists = [[0]*n for i in range(n)]
for i in range(n):
for j in range(n):
minDists[i][j] = min((a+i - x)**2 + (b+j - y)**2 for (x,y) in enemies)
maximin = 0
for i in range(n):
for j in range(n):
if minDists[i][j] > maximin:
maximin = minDists[i][j]
farthest = (a+i,b+j)
return farthest
def findSafePoint(n, enemies, mesh = 20):
m = n // mesh
territories = set() #enemy territories
for (x,y) in enemies:
i = x//mesh
j = y//mesh
territories.add((i,j))
(i,j) = fullResSearch(0,0,m,territories)
a = i*mesh
b = j*mesh
k = min(mesh,n - a,n - b) #in case mesh doesn't divide n
return fullResSearch(a,b,k,enemies)
def test(n, numEnemies, mesh = 20):
enemies = set()
count = 0
while count < numEnemies:
i = random.randint(0,n-1)
j = random.randint(0,n-1)
if not (i,j) in enemies:
enemies.add ((i,j))
count += 1
for e in enemies: print("Enemy at", e)
print("Safe point at", findSafePoint(n,enemies, mesh))
A typical run:
>>> test(500,5)
Enemy at (216, 67)
Enemy at (145, 251)
Enemy at (407, 256)
Enemy at (111, 258)
Enemy at (26, 298)
Safe point at (271, 499)
(I verified by using fullResSearch on the overall grid that (271,499) is in fact optimal for these enemies)
This method looks at all the enemies from the center point, checks the direction they're in, finds the emptiest sector, and then returns a point on a line through the middle of that sector, 250 away from the center.
The result isn't always perfect, and the safe spot is never in the center (though that could be added), but maybe it's good enough.
The algorithm runs more than a million times per second on my i5 desktop, but a phone may not be that good with trigonometry. The algorithm uses 3 trigonometry functions per enemy: atan2(), cos() and sin(). Those will probably have the most impact on the speed of execution. Maybe you could replace the cos() and sin() with a lookup table.
Run the code snippet to see an example with randomly positioned enemies.
function furthestFrom(e) {
var dir = [], widest = 0, bisect;
for (var i = 0; i < 5; i++) {
dir[i] = Math.atan2(e[i].y - 250, e[i].x - 250);
}
dir.sort(function(a, b){return a - b});
dir.push(dir[0] + 6.2831853);
for (var i = 0; i < 5; i++) {
var angle = dir[i + 1] - dir[i];
if (angle > widest) {
widest = angle;
bisect = dir[i] + angle / 2;
}
}
return({x: 250 * (1 + Math.cos(bisect)), y: 250 * (1 + Math.sin(bisect))});
}
// CREATE TEST DATA
var enemy = [];
for (var i = 0; i < 5; i++) enemy[i] = {x: Math.round(Math.random() * 500), y: Math.round(Math.random() * 500)};
// RUN FUNCTION AND SHOW RESULT ON CANVAS
var result = furthestFrom(enemy);
var canvas = document.getElementById("canvas");
canvas.width = 500; canvas.height = 500;
canvas = canvas.getContext("2d");
for (var i = 0; i < 5; i++) {
paintDot(canvas, enemy[i].x, enemy[i].y, "red");
}
paintDot(canvas, result.x, result.y, "blue");
// PAINT DOT ON CANVAS
function paintDot(canvas, x, y, color) {
canvas.beginPath();
canvas.arc(x, y, 10, 0, 6.2831853);
canvas.closePath();
canvas.fillStyle = color;
canvas.fill();
}
<BODY STYLE="margin: 0; border: 0; padding: 0">
<CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background-color: #EEE;"CANVAS>
</BODY>
Here's an interesting solution, however I cannot test it's efficiency. For each enemy, make a line of numbers from each number, starting with one and increasing by one for each increase in distance. Four initial lines will come from the four edges and each time you go one further out, you create another line coming out at a 90 degree angle, also increasing the number each change in distance. If the number line encounters an already created number that is smaller than it, it will not overwrite it and will stop reaching further. Essentially, this makes it so that if the lines find a number smaller than it, it won't check any further grid marks, eliminating the need to check the entire grid for all of the enemies.
<<<<<<^^^^^^^
<<<<<<^^^^^^^
<<<<<<X>>>>>>
vvvvvvv>>>>>>
vvvvvvv>>>>>>
public void map(int posX, int posY)
{
//left up right down
makeLine(posX, posY, -1, 0, 0, -1);
makeLine(posX, posY, 0, 1, -1, 0);
makeLine(posX, posY, 1, 0, 0, 1);
makeLine(posX, posY, 0, -1, 1, 0);
grid[posX][posY] = 1000;
}
public void makeLine(int posX, int posY, int dirX, int dirY, int dir2X, int dir2Y)
{
int currentVal = 1;
posX += dirX;
posY += dirY;
while (0 <= posX && posX < maxX && posY < maxY && posY >= 0 && currentVal < grid[posX][posY])
{
int secondaryPosX = posX + dir2X;
int secondaryPosY = posY + dir2Y;
int secondaryVal = currentVal + 1;
makeSecondaryLine( secondaryPosX, secondaryPosY, dir2X, dir2Y, secondaryVal);
makeSecondaryLine( secondaryPosX, secondaryPosY, -dir2X, -dir2Y, secondaryVal);
grid[posX][posY] = currentVal;
posX += dirX;
posY += dirY;
currentVal++;
}
}
public void makeSecondaryLine(int secondaryPosX, int secondaryPosY, int dir2X, int dir2Y, int secondaryVal)
{
while (0 <= secondaryPosX && secondaryPosX < maxX && secondaryPosY < maxY &&
secondaryPosY >= 0 && secondaryVal < grid[secondaryPosX][secondaryPosY])
{
grid[secondaryPosX][secondaryPosY] = secondaryVal;
secondaryPosX += dir2X;
secondaryPosY += dir2Y;
secondaryVal++;
}
}
}
Here is the code I used to map out the entire grid. The nice thing about this, is that the number of times the number is checked/written is not that much dependent on the number of enemies on the screen. Using a counter and randomly generated enemies, I was able to get this: 124 enemies and 1528537 checks, 68 enemies and 1246769 checks, 15 enemies and 795695 500 enemies and 1747452 checks. This is a huge difference compared to your earlier code which would do number of enemies * number of spaces.
for 124 enemies you'd have done 31000000 checks, while instead this did 1528537, which is less than 5% of the number of checks normally done.
You can choose some random point at the grid and then move it iteratively from the enemies, here is my implementation in python:
from numpy import array
from numpy.linalg import norm
from random import random as rnd
def get_pos(enem):
# chose random start position
pos = array([rnd() * 500., rnd() * 500.])
# make several steps from enemies
for i in xrange(25): # 25 steps
s = array([0., 0.]) # step direction
for e in enem:
vec = pos - array(e) # direction from enemy
dist = norm(vec) # distance from enemy
vec /= dist # normalize vector
# calculate size of step
step = (1000. / dist) ** 2
vec *= step
s += vec
# update position
pos += s
# ensure that pos is in bounds
pos[0] = min(max(0, pos[0]), 500.)
pos[1] = min(max(0, pos[1]), 500.)
return pos
def get_dist(enem, pos):
dists = [norm(pos - array(e)) for e in enem]
print 'Min dist: %f' % min(dists)
print 'Avg dist: %f' % (sum(dists) / len(dists))
enem = [(0., 0.), (250., 250.), (500., 0.), (0., 500.), (500., 500.)]
pos = get_pos(enem)
print 'Position: %s' % pos
get_dist(enem, pos)
Output:
Position: [ 0. 250.35338215]
Min dist: 249.646618
Avg dist: 373.606883
Triangulate the enemies (there's less than 5?); and triangulate each corner of the grid with the closest pair of enemies to it. The circumcenter of one of these triangles should be a decent place to re-spawn.
Below is an example in JavaScript. I used the canvas method from m69's answer for demonstration. The green dots are the candidates tested to arrive at the blue-colored suggestion. Since we are triangulating the corners, they are not offered as solutions here (perhaps the randomly-closer solutions can be exciting for a player? Alternatively, just test for the corners as well..).
// http://stackoverflow.com/questions/4103405/what-is-the-algorithm-for-finding-the-center-of-a-circle-from-three-points
function circumcenter(x1,y1,x2,y2,x3,y3)
{
var offset = x2 * x2 + y2 * y2;
var bc = ( x1 * x1 + y1 * y1 - offset ) / 2;
var cd = (offset - x3 * x3 - y3 * y3) / 2;
var det = (x1 - x2) * (y2 - y3) - (x2 - x3)* (y1 - y2);
var idet = 1/det;
var centerx = (bc * (y2 - y3) - cd * (y1 - y2)) * idet;
var centery = (cd * (x1 - x2) - bc * (x2 - x3)) * idet;
return [centerx,centery];
}
var best = 0,
candidates = [];
function better(pt,pts){
var temp = Infinity;
for (var i=0; i<pts.length; i+=2){
var d = (pts[i] - pt[0])*(pts[i] - pt[0]) + (pts[i+1] - pt[1])*(pts[i+1] - pt[1]);
if (d <= best)
return false;
else if (d < temp)
temp = d;
}
best = temp;
return true;
}
function f(es){
if (es.length < 2)
return "farthest corner";
var corners = [0,0,500,0,500,500,0,500],
bestcandidate;
// test enemies only
if (es.length > 2){
for (var i=0; i<es.length-4; i+=2){
for (var j=i+2; j<es.length-2; j+=2){
for (var k=j+2; k<es.length; k+=2){
var candidate = circumcenter(es[i],es[i+1],es[j],es[j+1],es[k],es[k+1]);
if (candidate[0] < 0 || candidate[1] < 0 || candidate[0] > 500 || candidate[1] > 500)
continue;
candidates.push(candidate[0]);
candidates.push(candidate[1]);
if (better(candidate,es))
bestcandidate = candidate.slice();
}
}
}
}
//test corners
for (var i=0; i<8; i+=2){
for (var j=0; j<es.length-2; j+=2){
for (var k=j+2; k<es.length; k+=2){
var candidate = circumcenter(corners[i],corners[i+1],es[j],es[j+1],es[k],es[k+1]);
if (candidate[0] < 0 || candidate[1] < 0 || candidate[0] > 500 || candidate[1] > 500)
continue;
candidates.push(candidate[0]);
candidates.push(candidate[1]);
if (better(candidate,es))
bestcandidate = candidate.slice();
}
}
}
best = 0;
return bestcandidate;
}
// SHOW RESULT ON CANVAS
var canvas = document.getElementById("canvas");
canvas.width = 500; canvas.height = 500;
context = canvas.getContext("2d");
//setInterval(function() {
// CREATE TEST DATA
context.clearRect(0, 0, canvas.width, canvas.height);
candidates = [];
var enemy = [];
for (var i = 0; i < 8; i++) enemy.push(Math.round(Math.random() * 500));
// RUN FUNCTION
var result = f(enemy);
for (var i = 0; i < 8; i+=2) {
paintDot(context, enemy[i], enemy[i+1], 10, "red");
}
for (var i = 0; i < candidates.length; i+=2) {
paintDot(context, candidates[i], candidates[i+1], 7, "green");
}
paintDot(context, result[0], result[1], 18, "blue");
function paintDot(context, x, y, size, color) {
context.beginPath();
context.arc(x, y, size, 0, 6.2831853);
context.closePath();
context.fillStyle = color;
context.fill();
}
//},1500);
<BODY STYLE="margin: 0; border: 0; padding: 0;">
<CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background:
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.15) 30%, rgba(255,255,255,.3) 32%, rgba(255,255,255,0) 33%) 0 0,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.3) 13%, rgba(255,255,255,0) 14%) 0 0,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 17%, rgba(255,255,255,.43) 19%, rgba(255,255,255,0) 20%) 0 110px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) -130px -170px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) 130px 370px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.2) 13%, rgba(255,255,255,0) 14%) 0 0,
linear-gradient(45deg, #343702 0%, #184500 20%, #187546 30%, #006782 40%, #0b1284 50%, #760ea1 60%, #83096e 70%, #840b2a 80%, #b13e12 90%, #e27412 100%);
background-size: 470px 470px, 970px 970px, 410px 410px, 610px 610px, 530px 530px, 730px 730px, 100% 100%;
background-color: #840b2a;"></CANVAS>
<!-- http://lea.verou.me/css3patterns/#rainbow-bokeh -->
</BODY>
I have a set of circles with given locations and radii on a two dimensional plane. I want to determine for every circle if it is intersecting with any other circle and the distance that is needed to separate the two. Under my current implementation, I just go through all the possible combinations of circles and then do the calculations. Unfortunately, this algorithm is O(n^2), which is slow.
The circles will generally be clustered in groups, and they will have similar (but different) radii. The approximate maximum for the number of circles is around 200. The algorithm does not have to be exact, but it should be close.
Here is a (slow) implementation I currently have in JavaScript:
// Makes a new circle
var circle = function(x,y,radius) {
return {
x:x,
y:y,
radius:radius
};
};
// These points are not representative of the true data set. I just made them up.
var points = [
circle(3,3,2),
circle(7,5,4),
circle(16,6,4),
circle(17,12,3),
circle(26,20,1)
];
var k = 0,
len = points.length;
for (var i = 0; i < len; i++) {
for (var j = k; j < len; j++) {
if (i !== j) {
var c1 = points[i],
c2 = points[j],
radiiSum = c1.radius+c2.radius,
deltaX = Math.abs(c1.x-c2.x);
if (deltaX < radiiSum) {
var deltaY = Math.abs(c1.y-c2.y);
if (deltaY < radiiSum) {
var distance = Math.sqrt(deltaX*deltaX+deltaY*deltaY);
if (distance < radiiSum) {
var separation = radiiSum - distance;
console.log(c1,c2,separation);
}
}
}
}
}
k++;
}
Also, I would appreciate it if you explained a good algorithm (KD Tree?) in plain English :-/
For starters, your algorithm above will be greatly sped-up if you just skipped the SQRT call. That's the most well known simple optimization for comparing distances. You can also precompute the "squared radius" distance so you don't redundantly recompute it.
Also, there looks to be lots of other little bugs in some of your algorithms. Here's my take on how to fix it below.
Also, if you want to get rid of the O(N-Squared) algorithm, you can look at using a kd-tree. There's an upfront cost of building the KD-Tree but with the benefit of searching for nearest neighbors as much faster.
function Distance_Squared(c1, c2) {
var deltaX = (c1.x - c2.x);
var deltaY = (c1.y - c2.y);
return (deltaX * deltaX + deltaY * deltaY);
}
// returns false if it's possible that the circles intersect. Returns true if the bounding box test proves there is no chance for intersection
function TrivialRejectIntersection(c1, c2) {
return ((c1.left >= c2.right) || (c2.right <= c1.left) || (c1.top >= c2.bottom) || (c2.bottom <= c1.top));
}
var circle = function(x,y,radius) {
return {
x:x,
y:y,
radius:radius,
// some helper properties
radius_squared : (radius*radius), // precompute the "squared distance"
left : (x-radius),
right: (x+radius),
top : (y - radius),
bottom : (y+radius)
};
};
// These points are not representative of the true data set. I just made them up.
var points = [
circle(3,3,2),
circle(7,5,4),
circle(16,6,4),
circle(17,12,3),
circle(26,20,1)
];
var k = 0;
var len = points.length;
var c1, c2;
var distance_squared;
var deltaX, deltaY;
var min_distance;
var seperation;
for (var i = 0; i < len; i++) {
for (var j = (i+1); j < len; j++) {
c1 = points[i];
c2 = points[j];
// try for trivial rejections first. Jury is still out if this will help
if (TrivialRejectIntesection(c1, c2)) {
continue;
}
//distance_squared is the actual distance between c1 and c2 'squared'
distance_squared = Distance_Squared(c1, c2);
// min_distance_squared is how much "squared distance" is required for these two circles to not intersect
min_distance_squared = (c1.radius_squared + c2.radius_squared + (c1.radius*c2.radius*2)); // D**2 == deltaX*deltaX + deltaY*deltaY + 2*deltaX*deltaY
// and so it follows
if (distance_squared < min_distance_squared) {
// intersection detected
// now subtract actual distance from "min distance"
seperation = c1.radius + c2.radius - Math.sqrt(distance_squared);
Console.log(c1, c2, seperation);
}
}
}
This article has been dormant for a long time, but I've run into and solved this problem reasonably well, so will post so that others don't have to do the same head scratching.
You can treat the nearest circle neighbor problem as a 3d point nearest neighbor search in a kd-tree or octree. Define the distance between two circles A and B as
D(A,B) = sqrt( (xA - xB)^2 + (yA - yB)^2 ) - rA - rB
This is a negative quantity iff the circles overlap. For this discussion I'll assume an octree, but a kd-tree with k=3 is similar.
Store a triple (x,y,r) in the octree for each circle.
To find the nearest neighbor to a target circle T, use the standard algorithm:
def search(node, T, nst)
if node is a leaf
update nst with node's (x,y,r) nearest to T
else
for each cuboid C subdividing node (there are 8 of them)
if C contains any point nearer to T than nst
search(C, T, nst)
end
end
Here nst is a reference to the nearest circle to T found so far. Initially it's null.
The slightly tricky part is determining if C contains any point nearer to T than nst. For this it is sufficent to consider the unique point (x,y,r) within C that is Euclidean nearest to T in x and y and has the maximum value of the r range contained in the cuboid. In other words, the cuboid represents a set of circles with centers ranging over a rectangular area in x and y and with a range of radii. The point you want to check is the one representing the circle with center closest to T and with largest radius.
Note the radius of T plays no part in the algorithm at all. You're only concered with how far inside any other circle the center of T lies. (I wish this had been as obvious at the start as it seems now...)
This problem actually deals with roll-overs, I'll just generalized below as such:
I have a 2D view, and I have a number of rectangles within an area on the screen. How do I spread out those boxes such that they don't overlap each other, but only adjust them with minimal moving?
The rectangles' positions are dynamic and dependent on user's input, so their positions could be anywhere.
Attached images show the problem and desired solution
The real life problem deals with rollovers, actually.
Answers to the questions in the comments
Size of rectangles is not fixed, and is dependent on the length of the text in the rollover
About screen size, right now I think it's better to assume that the size of the screen is enough for the rectangles. If there is too many rectangles and the algo produces no solution, then I just have to tweak the content.
The requirement to 'move minimally' is more for asethetics than an absolute engineering requirement. One could space out two rectangles by adding a vast distance between them, but it won't look good as part of the GUI. The idea is to get the rollover/rectangle as close as to its source (which I will then connect to the source with a black line). So either 'moving just one for x' or 'moving both for half x' is fine.
I was working a bit in this, as I also needed something similar, but I had delayed the algorithm development. You helped me to get some impulse :D
I also needed the source code, so here it is. I worked it out in Mathematica, but as I haven't used heavily the functional features, I guess it'll be easy to translate to any procedural language.
A historic perspective
First I decided to develop the algorithm for circles, because the intersection is easier to calculate. It just depends on the centers and radii.
I was able to use the Mathematica equation solver, and it performed nicely.
Just look:
It was easy. I just loaded the solver with the following problem:
For each circle
Solve[
Find new coördinates for the circle
Minimizing the distance to the geometric center of the image
Taking in account that
Distance between centers > R1+R2 *for all other circles
Move the circle in a line between its center and the
geometric center of the drawing
]
As straightforward as that, and Mathematica did all the work.
I said "Ha! it's easy, now let's go for the rectangles!". But I was wrong ...
Rectangular Blues
The main problem with the rectangles is that querying the intersection is a nasty function. Something like:
So, when I tried to feed up Mathematica with a lot of these conditions for the equation, it performed so badly that I decided to do something procedural.
My algorithm ended up as follows:
Expand each rectangle size by a few points to get gaps in final configuration
While There are intersections
sort list of rectangles by number of intersections
push most intersected rectangle on stack, and remove it from list
// Now all remaining rectangles doesn't intersect each other
While stack not empty
pop rectangle from stack and re-insert it into list
find the geometric center G of the chart (each time!)
find the movement vector M (from G to rectangle center)
move the rectangle incrementally in the direction of M (both sides)
until no intersections
Shrink the rectangles to its original size
You may note that the "smallest movement" condition is not completely satisfied (only in one direction). But I found that moving the rectangles in any direction to satisfy it, sometimes ends up with a confusing map changing for the user.
As I am designing a user interface, I choose to move the rectangle a little further, but in a more predictable way. You can change the algorithm to inspect all angles and all radii surrounding its current position until an empty place is found, although it'll be much more demanding.
Anyway, these are examples of the results (before/ after):
Edit> More examples here
As you may see, the "minimum movement" is not satisfied, but the results are good enough.
I'll post the code here because I'm having some trouble with my SVN repository. I'll remove it when the problems are solved.
Edit:
You may also use R-Trees for finding rectangle intersections, but it seems an overkill for dealing with a small number of rectangles. And I haven't the algorithms already implemented. Perhaps someone else can point you to an existing implementation on your platform of choice.
Warning! Code is a first approach .. not great quality yet, and surely has some bugs.
It's Mathematica.
(*Define some functions first*)
Clear["Global`*"];
rn[x_] := RandomReal[{0, x}];
rnR[x_] := RandomReal[{1, x}];
rndCol[] := RGBColor[rn[1], rn[1], rn[1]];
minX[l_, i_] := l[[i]][[1]][[1]]; (*just for easy reading*)
maxX[l_, i_] := l[[i]][[1]][[2]];
minY[l_, i_] := l[[i]][[2]][[1]];
maxY[l_, i_] := l[[i]][[2]][[2]];
color[l_, i_]:= l[[i]][[3]];
intersectsQ[l_, i_, j_] := (* l list, (i,j) indexes,
list={{x1,x2},{y1,y2}} *)
(*A rect does intesect with itself*)
If[Max[minX[l, i], minX[l, j]] < Min[maxX[l, i], maxX[l, j]] &&
Max[minY[l, i], minY[l, j]] < Min[maxY[l, i], maxY[l, j]],
True,False];
(* Number of Intersects for a Rectangle *)
(* With i as index*)
countIntersects[l_, i_] :=
Count[Table[intersectsQ[l, i, j], {j, 1, Length[l]}], True]-1;
(*And With r as rectangle *)
countIntersectsR[l_, r_] := (
Return[Count[Table[intersectsQ[Append[l, r], Length[l] + 1, j],
{j, 1, Length[l] + 1}], True] - 2];)
(* Get the maximum intersections for all rectangles*)
findMaxIntesections[l_] := Max[Table[countIntersects[l, i],
{i, 1, Length[l]}]];
(* Get the rectangle center *)
rectCenter[l_, i_] := {1/2 (maxX[l, i] + minX[l, i] ),
1/2 (maxY[l, i] + minY[l, i] )};
(* Get the Geom center of the whole figure (list), to move aesthetically*)
geometryCenter[l_] := (* returs {x,y} *)
Mean[Table[rectCenter[l, i], {i, Length[l]}]];
(* Increment or decr. size of all rects by a bit (put/remove borders)*)
changeSize[l_, incr_] :=
Table[{{minX[l, i] - incr, maxX[l, i] + incr},
{minY[l, i] - incr, maxY[l, i] + incr},
color[l, i]},
{i, Length[l]}];
sortListByIntersections[l_] := (* Order list by most intersecting Rects*)
Module[{a, b},
a = MapIndexed[{countIntersectsR[l, #1], #2} &, l];
b = SortBy[a, -#[[1]] &];
Return[Table[l[[b[[i]][[2]][[1]]]], {i, Length[b]}]];
];
(* Utility Functions*)
deb[x_] := (Print["--------"]; Print[x]; Print["---------"];)(* for debug *)
tableForPlot[l_] := (*for plotting*)
Table[{color[l, i], Rectangle[{minX[l, i], minY[l, i]},
{maxX[l, i], maxY[l, i]}]}, {i, Length[l]}];
genList[nonOverlap_, Overlap_] := (* Generate initial lists of rects*)
Module[{alist, blist, a, b},
(alist = (* Generate non overlapping - Tabuloid *)
Table[{{Mod[i, 3], Mod[i, 3] + .8},
{Mod[i, 4], Mod[i, 4] + .8},
rndCol[]}, {i, nonOverlap}];
blist = (* Random overlapping *)
Table[{{a = rnR[3], a + rnR[2]}, {b = rnR[3], b + rnR[2]},
rndCol[]}, {Overlap}];
Return[Join[alist, blist] (* Join both *)];)
];
Main
clist = genList[6, 4]; (* Generate a mix fixed & random set *)
incr = 0.05; (* may be some heuristics needed to determine best increment*)
clist = changeSize[clist,incr]; (* expand rects so that borders does not
touch each other*)
(* Now remove all intercepting rectangles until no more intersections *)
workList = {}; (* the stack*)
While[findMaxIntesections[clist] > 0,
(*Iterate until no intersections *)
clist = sortListByIntersections[clist];
(*Put the most intersected first*)
PrependTo[workList, First[clist]];
(* Push workList with intersected *)
clist = Delete[clist, 1]; (* and Drop it from clist *)
];
(* There are no intersections now, lets pop the stack*)
While [workList != {},
PrependTo[clist, First[workList]];
(*Push first element in front of clist*)
workList = Delete[workList, 1];
(* and Drop it from worklist *)
toMoveIndex = 1;
(*Will move the most intersected Rect*)
g = geometryCenter[clist];
(*so the geom. perception is preserved*)
vectorToMove = rectCenter[clist, toMoveIndex] - g;
If [Norm[vectorToMove] < 0.01, vectorToMove = {1,1}]; (*just in case*)
vectorToMove = vectorToMove/Norm[vectorToMove];
(*to manage step size wisely*)
(*Now iterate finding minimum move first one way, then the other*)
i = 1; (*movement quantity*)
While[countIntersects[clist, toMoveIndex] != 0,
(*If the Rect still intersects*)
(*move it alternating ways (-1)^n *)
clist[[toMoveIndex]][[1]] += (-1)^i i incr vectorToMove[[1]];(*X coords*)
clist[[toMoveIndex]][[2]] += (-1)^i i incr vectorToMove[[2]];(*Y coords*)
i++;
];
];
clist = changeSize[clist, -incr](* restore original sizes*);
HTH!
Edit: Multi-angle searching
I implemented a change in the algorithm allowing to search in all directions, but giving preference to the axis imposed by the geometric symmetry.
At the expense of more cycles, this resulted in more compact final configurations, as you can see here below:
More samples here.
The pseudocode for the main loop changed to:
Expand each rectangle size by a few points to get gaps in final configuration
While There are intersections
sort list of rectangles by number of intersections
push most intersected rectangle on stack, and remove it from list
// Now all remaining rectangles doesn't intersect each other
While stack not empty
find the geometric center G of the chart (each time!)
find the PREFERRED movement vector M (from G to rectangle center)
pop rectangle from stack
With the rectangle
While there are intersections (list+rectangle)
For increasing movement modulus
For increasing angle (0, Pi/4)
rotate vector M expanding the angle alongside M
(* angle, -angle, Pi + angle, Pi-angle*)
re-position the rectangle accorging to M
Re-insert modified vector into list
Shrink the rectangles to its original size
I'm not including the source code for brevity, but just ask for it if you think you can use it. I think that, should you go this way, it's better to switch to R-trees (a lot of interval tests needed here)
Here's a guess.
Find the center C of the bounding box of your rectangles.
For each rectangle R that overlaps another.
Define a movement vector v.
Find all the rectangles R' that overlap R.
Add a vector to v proportional to the vector between the center of R and R'.
Add a vector to v proportional to the vector between C and the center of R.
Move R by v.
Repeat until nothing overlaps.
This incrementally moves the rectangles away from each other and the center of all the rectangles. This will terminate because the component of v from step 4 will eventually spread them out enough all by itself.
I think this solution is quite similar to the one given by cape1232, but it's already implemented, so worth checking out :)
Follow to this reddit discussion: http://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/ and check out the description and implementation. There's no source code available, so here's my approach to this problem in AS3 (works exactly the same, but keeps rectangles snapped to grid's resolution):
public class RoomSeparator extends AbstractAction {
public function RoomSeparator(name:String = "Room Separator") {
super(name);
}
override public function get finished():Boolean { return _step == 1; }
override public function step():void {
const repelDecayCoefficient:Number = 1.0;
_step = 1;
var count:int = _activeRoomContainer.children.length;
for(var i:int = 0; i < count; i++) {
var room:Room = _activeRoomContainer.children[i];
var center:Vector3D = new Vector3D(room.x + room.width / 2, room.y + room.height / 2);
var velocity:Vector3D = new Vector3D();
for(var j:int = 0; j < count; j++) {
if(i == j)
continue;
var otherRoom:Room = _activeRoomContainer.children[j];
var intersection:Rectangle = GeomUtil.rectangleIntersection(room.createRectangle(), otherRoom.createRectangle());
if(intersection == null || intersection.width == 0 || intersection.height == 0)
continue;
var otherCenter:Vector3D = new Vector3D(otherRoom.x + otherRoom.width / 2, otherRoom.y + otherRoom.height / 2);
var diff:Vector3D = center.subtract(otherCenter);
if(diff.length > 0) {
var scale:Number = repelDecayCoefficient / diff.lengthSquared;
diff.normalize();
diff.scaleBy(scale);
velocity = velocity.add(diff);
}
}
if(velocity.length > 0) {
_step = 0;
velocity.normalize();
room.x += Math.abs(velocity.x) < 0.5 ? 0 : velocity.x > 0 ? _resolution : -_resolution;
room.y += Math.abs(velocity.y) < 0.5 ? 0 : velocity.y > 0 ? _resolution : -_resolution;
}
}
}
}
I really like b005t3r's implementation! It works in my test cases, however my rep is too low to leave a comment with the 2 suggested fixes.
You should not be translating rooms by single resolution increments, you should translate by the velocity you just pain stakingly calculated! This makes the separation more organic as deeply intersected rooms separate more each iteration than not-so-deeply intersecting rooms.
You should not assume velociites less than 0.5 means rooms are separate as you can get stuck in a case where you are never separated. Imagine 2 rooms intersect, but are unable to correct themselves because whenever either one attempts to correct the penetration they calculate the required velocity as < 0.5 so they iterate endlessly.
Here is a Java solution (: Cheers!
do {
_separated = true;
for (Room room : getRooms()) {
// reset for iteration
Vector2 velocity = new Vector2();
Vector2 center = room.createCenter();
for (Room other_room : getRooms()) {
if (room == other_room)
continue;
if (!room.createRectangle().overlaps(other_room.createRectangle()))
continue;
Vector2 other_center = other_room.createCenter();
Vector2 diff = new Vector2(center.x - other_center.x, center.y - other_center.y);
float diff_len2 = diff.len2();
if (diff_len2 > 0f) {
final float repelDecayCoefficient = 1.0f;
float scale = repelDecayCoefficient / diff_len2;
diff.nor();
diff.scl(scale);
velocity.add(diff);
}
}
if (velocity.len2() > 0f) {
_separated = false;
velocity.nor().scl(delta * 20f);
room.getPosition().add(velocity);
}
}
} while (!_separated);
Here's an algorithm written using Java for handling a cluster of unrotated Rectangles. It allows you to specify the desired aspect ratio of the layout and positions the cluster using a parameterised Rectangle as an anchor point, which all translations made are oriented about. You can also specify an arbitrary amount of padding which you'd like to spread the Rectangles by.
public final class BoxxyDistribution {
/* Static Definitions. */
private static final int INDEX_BOUNDS_MINIMUM_X = 0;
private static final int INDEX_BOUNDS_MINIMUM_Y = 1;
private static final int INDEX_BOUNDS_MAXIMUM_X = 2;
private static final int INDEX_BOUNDS_MAXIMUM_Y = 3;
private static final double onCalculateMagnitude(final double pDeltaX, final double pDeltaY) {
return Math.sqrt((pDeltaX * pDeltaX) + (pDeltaY + pDeltaY));
}
/* Updates the members of EnclosingBounds to ensure the dimensions of T can be completely encapsulated. */
private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double pMinimumX, final double pMinimumY, final double pMaximumX, final double pMaximumY) {
pEnclosingBounds[0] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pMinimumX);
pEnclosingBounds[1] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pMinimumY);
pEnclosingBounds[2] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pMaximumX);
pEnclosingBounds[3] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], pMaximumY);
}
private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double[] pBounds) {
BoxxyDistribution.onEncapsulateBounds(pEnclosingBounds, pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y]);
}
private static final double onCalculateMidpoint(final double pMaximum, final double pMinimum) {
return ((pMaximum - pMinimum) * 0.5) + pMinimum;
}
/* Re-arranges a List of Rectangles into something aesthetically pleasing. */
public static final void onBoxxyDistribution(final List<Rectangle> pRectangles, final Rectangle pAnchor, final double pPadding, final double pAspectRatio, final float pRowFillPercentage) {
/* Create a safe clone of the Rectangles that we can modify as we please. */
final List<Rectangle> lRectangles = new ArrayList<Rectangle>(pRectangles);
/* Allocate a List to track the bounds of each Row. */
final List<double[]> lRowBounds = new ArrayList<double[]>(); // (MinX, MinY, MaxX, MaxY)
/* Ensure Rectangles does not contain the Anchor. */
lRectangles.remove(pAnchor);
/* Order the Rectangles via their proximity to the Anchor. */
Collections.sort(pRectangles, new Comparator<Rectangle>(){ #Override public final int compare(final Rectangle pT0, final Rectangle pT1) {
/* Calculate the Distance for pT0. */
final double lDistance0 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT0.getCenterX(), pAnchor.getCenterY() - pT0.getCenterY());
final double lDistance1 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT1.getCenterX(), pAnchor.getCenterY() - pT1.getCenterY());
/* Compare the magnitude in distance between the anchor and the Rectangles. */
return Double.compare(lDistance0, lDistance1);
} });
/* Initialize the RowBounds using the Anchor. */ /** TODO: Probably better to call getBounds() here. **/
lRowBounds.add(new double[]{ pAnchor.getX(), pAnchor.getY(), pAnchor.getX() + pAnchor.getWidth(), pAnchor.getY() + pAnchor.getHeight() });
/* Allocate a variable for tracking the TotalBounds of all rows. */
final double[] lTotalBounds = new double[]{ Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
/* Now we iterate the Rectangles to place them optimally about the Anchor. */
for(int i = 0; i < lRectangles.size(); i++) {
/* Fetch the Rectangle. */
final Rectangle lRectangle = lRectangles.get(i);
/* Iterate through each Row. */
for(final double[] lBounds : lRowBounds) {
/* Update the TotalBounds. */
BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lBounds);
}
/* Allocate a variable to state whether the Rectangle has been allocated a suitable RowBounds. */
boolean lIsBounded = false;
/* Calculate the AspectRatio. */
final double lAspectRatio = (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]) / (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
/* We will now iterate through each of the available Rows to determine if a Rectangle can be stored. */
for(int j = 0; j < lRowBounds.size() && !lIsBounded; j++) {
/* Fetch the Bounds. */
final double[] lBounds = lRowBounds.get(j);
/* Calculate the width and height of the Bounds. */
final double lWidth = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X];
final double lHeight = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y];
/* Determine whether the Rectangle is suitable to fit in the RowBounds. */
if(lRectangle.getHeight() <= lHeight && !(lAspectRatio > pAspectRatio && lWidth > pRowFillPercentage * (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]))) {
/* Register that the Rectangle IsBounded. */
lIsBounded = true;
/* Update the Rectangle's X and Y Co-ordinates. */
lRectangle.setFrame((lRectangle.getX() > BoxxyDistribution.onCalculateMidpoint(lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X])) ? lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] + pPadding : lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X] - (pPadding + lRectangle.getWidth()), lBounds[1], lRectangle.getWidth(), lRectangle.getHeight());
/* Update the Bounds. (Do not modify the vertical metrics.) */
BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lRectangle.getX(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getX() + lRectangle.getWidth(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] + lHeight);
}
}
/* Determine if the Rectangle has not been allocated a Row. */
if(!lIsBounded) {
/* Calculate the MidPoint of the TotalBounds. */
final double lCentreY = BoxxyDistribution.onCalculateMidpoint(lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
/* Determine whether to place the bounds above or below? */
final double lYPosition = lRectangle.getY() < lCentreY ? lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] - (pPadding + lRectangle.getHeight()) : (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] + pPadding);
/* Create a new RowBounds. */
final double[] lBounds = new double[]{ pAnchor.getX(), lYPosition, pAnchor.getX() + lRectangle.getWidth(), lYPosition + lRectangle.getHeight() };
/* Allocate a new row, roughly positioned about the anchor. */
lRowBounds.add(lBounds);
/* Position the Rectangle. */
lRectangle.setFrame(lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getWidth(), lRectangle.getHeight());
}
}
}
}
Here's an example using an AspectRatio of 1.2, a FillPercentage of 0.8 and a Padding of 10.0.
This is a deterministic approach which allows spacing to occur around the anchor whilst leaving the location of the anchor itself unchanged. This allows the layout to occur around wherever the user's Point of Interest is. The logic for selecting a position is pretty simplistic, but I think the surrounding architecture of sorting the elements based upon their initial position and then iterating them is a useful approach for implementing a relatively predictable distribution. Plus we're not relying on iterative intersection tests or anything like that, just building up some bounding boxes to give us a broad indication of where to align things; after this, applying padding just comes kind of naturally.
Here is a version that takes cape1232's answer and is a standalone runnable example for Java:
public class Rectangles extends JPanel {
List<Rectangle2D> rectangles = new ArrayList<Rectangle2D>();
{
// x,y,w,h
rectangles.add(new Rectangle2D.Float(300, 50, 50, 50));
rectangles.add(new Rectangle2D.Float(300, 50, 20, 50));
rectangles.add(new Rectangle2D.Float(100, 100, 100, 50));
rectangles.add(new Rectangle2D.Float(120, 200, 50, 50));
rectangles.add(new Rectangle2D.Float(150, 130, 100, 100));
rectangles.add(new Rectangle2D.Float(0, 100, 100, 50));
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
rectangles.add(new Rectangle2D.Float(i * 40, j * 40, 20, 20));
}
}
}
List<Rectangle2D> rectanglesToDraw;
protected void reset() {
rectanglesToDraw = rectangles;
this.repaint();
}
private List<Rectangle2D> findIntersections(Rectangle2D rect, List<Rectangle2D> rectList) {
ArrayList<Rectangle2D> intersections = new ArrayList<Rectangle2D>();
for (Rectangle2D intersectingRect : rectList) {
if (!rect.equals(intersectingRect) && intersectingRect.intersects(rect)) {
intersections.add(intersectingRect);
}
}
return intersections;
}
protected void fix() {
rectanglesToDraw = new ArrayList<Rectangle2D>();
for (Rectangle2D rect : rectangles) {
Rectangle2D copyRect = new Rectangle2D.Double();
copyRect.setRect(rect);
rectanglesToDraw.add(copyRect);
}
// Find the center C of the bounding box of your rectangles.
Rectangle2D surroundRect = surroundingRect(rectanglesToDraw);
Point center = new Point((int) surroundRect.getCenterX(), (int) surroundRect.getCenterY());
int movementFactor = 5;
boolean hasIntersections = true;
while (hasIntersections) {
hasIntersections = false;
for (Rectangle2D rect : rectanglesToDraw) {
// Find all the rectangles R' that overlap R.
List<Rectangle2D> intersectingRects = findIntersections(rect, rectanglesToDraw);
if (intersectingRects.size() > 0) {
// Define a movement vector v.
Point movementVector = new Point(0, 0);
Point centerR = new Point((int) rect.getCenterX(), (int) rect.getCenterY());
// For each rectangle R that overlaps another.
for (Rectangle2D rPrime : intersectingRects) {
Point centerRPrime = new Point((int) rPrime.getCenterX(), (int) rPrime.getCenterY());
int xTrans = (int) (centerR.getX() - centerRPrime.getX());
int yTrans = (int) (centerR.getY() - centerRPrime.getY());
// Add a vector to v proportional to the vector between the center of R and R'.
movementVector.translate(xTrans < 0 ? -movementFactor : movementFactor,
yTrans < 0 ? -movementFactor : movementFactor);
}
int xTrans = (int) (centerR.getX() - center.getX());
int yTrans = (int) (centerR.getY() - center.getY());
// Add a vector to v proportional to the vector between C and the center of R.
movementVector.translate(xTrans < 0 ? -movementFactor : movementFactor,
yTrans < 0 ? -movementFactor : movementFactor);
// Move R by v.
rect.setRect(rect.getX() + movementVector.getX(), rect.getY() + movementVector.getY(),
rect.getWidth(), rect.getHeight());
// Repeat until nothing overlaps.
hasIntersections = true;
}
}
}
this.repaint();
}
private Rectangle2D surroundingRect(List<Rectangle2D> rectangles) {
Point topLeft = null;
Point bottomRight = null;
for (Rectangle2D rect : rectangles) {
if (topLeft == null) {
topLeft = new Point((int) rect.getMinX(), (int) rect.getMinY());
} else {
if (rect.getMinX() < topLeft.getX()) {
topLeft.setLocation((int) rect.getMinX(), topLeft.getY());
}
if (rect.getMinY() < topLeft.getY()) {
topLeft.setLocation(topLeft.getX(), (int) rect.getMinY());
}
}
if (bottomRight == null) {
bottomRight = new Point((int) rect.getMaxX(), (int) rect.getMaxY());
} else {
if (rect.getMaxX() > bottomRight.getX()) {
bottomRight.setLocation((int) rect.getMaxX(), bottomRight.getY());
}
if (rect.getMaxY() > bottomRight.getY()) {
bottomRight.setLocation(bottomRight.getX(), (int) rect.getMaxY());
}
}
}
return new Rectangle2D.Double(topLeft.getX(), topLeft.getY(), bottomRight.getX() - topLeft.getX(),
bottomRight.getY() - topLeft.getY());
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Rectangle2D entry : rectanglesToDraw) {
g2d.setStroke(new BasicStroke(1));
// g2d.fillRect((int) entry.getX(), (int) entry.getY(), (int) entry.getWidth(),
// (int) entry.getHeight());
g2d.draw(entry);
}
}
protected static void createAndShowGUI() {
Rectangles rects = new Rectangles();
rects.reset();
JFrame frame = new JFrame("Rectangles");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(rects, BorderLayout.CENTER);
JPanel buttonsPanel = new JPanel();
JButton fix = new JButton("Fix");
fix.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
rects.fix();
}
});
JButton resetButton = new JButton("Reset");
resetButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
rects.reset();
}
});
buttonsPanel.add(fix);
buttonsPanel.add(resetButton);
frame.add(buttonsPanel, BorderLayout.SOUTH);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}