How can I get my GA to converge? - genetic-algorithm

I'm trying to write a GA to solve the following puzzle...
A binary encoding is (I think) very efficient. Each piece can be:
the original way up or flipped - 1 bit
rotated by 0 (ie none), 90, 180 or 270 degs - 2 bits
at position (x, y), where x and y go from 0 to 7 - 3 bits for each co-ordinate
This means that each piece's orientation and position can be encoded in 9 bits, making a total of 117 bits for the whole puzzle.
The fitness is calculated by placing each piece in the frame, ignoring any parts that lie out of the frame, and then adding up the number of empty squares. When that hits zero, we have a solution.
I have some standard GA methods I've used in other code (which I'll paste in below), but I can't seem to get it to converge. The fitness drops to about 11 (give or take), but never seems to go any lower. I've tried fiddling with the parameters, but can't get it any better.
At the risk of posting too much code, I'll show what I've got (where it seems relevant). If anyone can give me some idea how I could improve, it would be great. This is all in C#, but it should be clear enough to people who use other languages.
After generating an initial population of 1000 chromosomes (code not shown as it's just generating random binary strings of length 117), I enter the main loop, where on each generation, I call the Breed method, passing in the current population and some parameters...
private static List<Chromosome> Breed(List<Chromosome> population, int crossoverGene,
double mutationProbability, double mutationRate) {
List<Chromosome> nextGeneration = new List<Chromosome>();
// Cross breed half of the population number
for (int nChromosome = 0; nChromosome < population.Count / 2; nChromosome++) {
Chromosome daddy = Roulette(population);
Chromosome mummy = Roulette(population);
string babyGenes = daddy.Genes.Substring(0, crossoverGene)
+ mummy.Genes.Substring(crossoverGene);
Chromosome baby = new Chromosome(babyGenes);
baby.Fitness = Fitness(baby);
nextGeneration.Add(baby);
}
// Mutate some chromosomes
int numberToMutate = (int)(P() * 100 * mutationProbability);
List<Chromosome> mutatedChromosomes = new List<Chromosome>();
for (int i = 0; i < numberToMutate; i++) {
Chromosome c = Roulette(population);
string mutatedGenes = MutateGenes(c.Genes, mutationRate);
Chromosome mutatedChromosome = new Chromosome(mutatedGenes);
mutatedChromosome.Fitness = Fitness(mutatedChromosome);
mutatedChromosomes.Add(mutatedChromosome);
}
// Get the next generation from the fittest chromosomes
nextGeneration = nextGeneration
.Union(population)
.Union(mutatedChromosomes)
.OrderBy(p => p.Fitness)
.Take(population.Count)
.ToList();
return nextGeneration;
}
MutateGenes just flips bits at random, based on the mutation rate passed in. The main loop continues until we either hit the maximum number of generations, or the fitness drops to zero. I'm currently running for 1000 generations.
Here's the roulette method...
private static Chromosome Roulette(List<Chromosome> population) {
double totalFitness = population.Sum(c => 1 / c.Fitness);
double targetProbability = totalFitness * P();
double cumProbability = 0.0;
List<Chromosome> orderedPopulation = population.OrderBy(c => c.Fitness).ToList();
for (int i = 0; i < orderedPopulation.Count; i++) {
Chromosome c = orderedPopulation[i];
cumProbability += 1 / c.Fitness;
if (cumProbability > targetProbability) {
return c;
}
}
return orderedPopulation.Last();
}
Don't know if you need to see any of the other code. I was a bit wary about posting too much in case it put people off!
Anyone able to make any suggestions as to how I can get this to improve?

Todor Balabanov's answer is very interesting. Probably using relative coordinates and a proper packing function is the keystone.
Anyway I'd like to expand upon your idea as much as possible. A full discussion is probably too long for Stackoverflow...
TL;DR
Binary encoding does not give you any advantage.
The chosen alphabet isn't the smallest that permits a natural expression of the problem.
Considering the full range of coordinates ([0;7] x [0;7]) for every piece is excessive (and somewhat misleading for fitness evaluation).
Points (2) and (3) allow to reduce the search space from 2^117 to 2^95 elements.
A more informative fitness function is a great help.
You can use a multi-value fitness score, penalizing configurations that present holes.
Squares covered by overlapping pieces shouldn't be counted: an illegal configuration cannot have a fitness greater than a legal one.
ALPS can reduce the problem of premature convergence (reference implementation here).
I've have elaborated on these points in a GitHub wiki (it's a work in progress).

If you use genetic algorithms framework like Apache GA Framework you can implement chromosomes as list of shapes and you can use permutation crossover and mutation.
You will have blank spaces, which you will try to minimize (reduce them to 0). It is not a problem that you will have blanks, just count them and include them as penalty component in the fitness function.
Generally GAs are not so strong in combinatorial problems. I did many experiments like solving Rubik’s Cube with GA or solving Puzzle 15 with GA. Another experiment was 2D Optimal Cutting Problem with GA. If you are interested I can provide you the research papers and source code (GitHub). GAs are good giving you sub-optimal solution, but they are not good in giving you the optimal solution, which is even harder when it is a combinatorial problem.
The size of the population is an open question. You should do convergence investigation with different populations. Bigger population does not mean better and faster solution. Even 100 is too much for most of the problems solved with GA.
If you use absolute coordinates you will need to handle x and y, which is too complicated. Imagine that you support list of shapes. Packing procedure can get shape by shape and place each shape as close as possible to already handled shapes. It will speed-up your convergence.
/**
* Pack function which uses bounding rectangle of the polygons in the sheet
* with specified dimensions.
*
* #param width
* Sheet width.
* #param height
* Sheet height.
*/
public void pack1(int width, int height) {
int level[] = new int[width];
for (int i = 0; i < level.length; i++) {
level[i] = 0;
}
/*
* Insure pieces width according sheet width.
*/
for (Piece piece: population.get(worstIndex)) {
if (piece.getWidth() > width) {
piece.flip();
}
}
/*
* Pack pieces.
*/
int x = 0;
int y = 0;
for (Piece piece: population.get(worstIndex)) {
if (x + (int) piece.getWidth() >= width) {
x = 0;
}
/*
* Find y offset for current piece.
*/
y = 0;
for (int dx = x; dx < (x + piece.getWidth()); dx++) {
if (dx < width && y < level[dx]) {
y = level[dx];
}
}
// TODO Check the delta after subtraction.
/*
* Set current piece coordinates.
*/
piece.moveX(x - piece.getMinX());
piece.moveY(y - piece.getMinY());
/*
* Move lines for next placement.
*/
for (int dx = x; dx < (x + piece.getWidth()); dx++) {
if (dx < width) {
level[dx] = (int)(y + piece.getHeight());
}
}
// TODO Some strange behavior with the rotation.
x += (int) piece.getWidth() + 1;
}
}
/**
* Pack function which uses exact boundaries of the polygons in the sheet
* with specified dimensions.
*
* #param width
* Sheet width.
* #param height
* Sheet height.
*/
public void pack2(int width, int height) {
/*
* Pieces already placed on the sheet.
*/
List < Piece > front = new ArrayList < Piece > ();
/*
* Virtual Y boundary.
*/
double level = 0;
/*
* Place all pieces on the sheet
*/
for (Piece current: population.get(worstIndex)) {
double bestLeft = 0;
double bestTop = level;
current.moveX(-current.getMinX());
current.moveY(-current.getMinY() + level);
/*
* Move across sheet width.
*/
while (current.getMaxX() < width) {
/*
* Touch sheet bounds of touch other piece.
*/
while (current.getMinY() > 0 && Util.overlap(current, front) == false) {
current.moveY(-1);
}
// TODO Plus one may be is wrong if the piece should be part of
// the area.
current.moveY(+2);
/*
* Keep the best found position.
*/
if (current.getMinY() < bestTop) {
bestTop = current.getMinY();
bestLeft = current.getMinX();
}
/*
* Try next position on right.
*/
current.moveX(+1);
}
/*
* Put the piece in the best available coordinates.
*/
current.moveX(-current.getMinX() + bestLeft);
current.moveY(-current.getMinY() + bestTop);
/*
* Shift sheet level if the current piece is out of previous bounds.
*/
if (current.getMaxY() > level) {
level = current.getMaxY() + 1;
}
/*
* Add current piece in the ordered set and the front set.
*/
front.add(current);
}
}
/**
* Pack function which uses exact boundaries of the polygons in the sheet
* with specified dimensions.
*
* #param width
* Sheet width.
* #param height
* Sheet height.
*/
public void pack3(int width, int height) {
Polygon stack = new Polygon(
GEOMETRY_FACTORY
.createLinearRing(new Coordinate[] {
new Coordinate(0, -2, 0), new Coordinate(width - 1, -2, 0),
new Coordinate(width - 1, 0, 0), new Coordinate(0, 0, 0), new Coordinate(0, -2, 0)
}),
null, GEOMETRY_FACTORY);
/*
* Virtual Y boundary.
*/
double level = stack.getEnvelopeInternal().getMaxX();
/*
* Place all pieces on the sheet
*/
for (Piece current: population.get(worstIndex)) {
double bestLeft = 0;
double bestTop = level;
current.moveX(-current.getMinX());
current.moveY(-current.getMinY() + level);
/*
* Move across sheet width.
*/
while (current.getMaxX() < width) {
/*
* Touch sheet bounds of touch other piece.
*/
while (current.getMinY() > 0 && Util.overlap(current, stack) == false) {
current.moveY(-1);
}
// TODO Plus one may be is wrong if the piece should be part of
// the area.
current.moveY(+2);
/*
* Keep the best found position.
*/
if (current.getMinY() < bestTop) {
bestTop = current.getMinY();
bestLeft = current.getMinX();
}
/*
* Try next position on right.
*/
current.moveX(+1);
}
/*
* Put the piece in the best available coordinates.
*/
current.moveX(-current.getMinX() + bestLeft);
current.moveY(-current.getMinY() + bestTop);
/*
* Shift sheet level if the current piece is out of previous bounds.
*/
if (current.getMaxY() > level) {
level = current.getMaxY() + 1;
}
/*
* Add current piece in the ordered set and the front set.
*/
stack = (Polygon) SnapOverlayOp.union(stack, current.getPolygon()).getBoundary().convexHull();
stack.normalize();
}
}

You have a very interesting problem to solve. I like it very much. First of all it is a combinatorial problem, which can be very hard to solve with the classical genetic algorithms. I have some comments, but they are my subjective opinion: 1) Binary encoding does not give you any advantage (only overhead for encoding and decoding), you can use C# objects; 2) It is not smart to ignore pieces outside of the frame; 3) You will be trapped in local optimum all the time, this is the nature of genetic algorithms; 4) Population size of 1K is too much, use something smaller; 5) Do not use absolute x-y coordinates, use relative coordinates and proper packing function.

Related

subdivide rectangle with specific rules

everybody.
first of all, i'm an amateur programmer. i'm trying to make a simple city-building application using C++ and SFML, for learning. it's not really a game, since it will just build the city blocks and buildings, and show them to the user.
for now, i'm able to create the city blocks. my problem is how to subdivide the blocks into buildings. i don't have a real idea on how to do it.
possible solutions would be (i don't have enough reputation to post images, but there's the link):
https://i.postimg.cc/630GKGW7/bitmap.png
the only rules are:
(1) each building must fit a minimum and maximum known size;
(2) each building must have at least one face touching any block edge;
(3) no empty spaces should remain.
i've been strugling with this for days. can anyone give me a idea on how to do it? pseudocode also would be great.
thanks in advance!
Just a note, I'll be using OOP syntax to make this easier, but it's not valid code. Let's first create an interface to define the behavior we want:
class CityBlock {
Building[] buildings // should initially contain one building taking up the whole CityBlock
double width
double height
double maxBuildingSize
double minBuildingSize
splitBuilding(horizontal/vertical, coord) // This will split a building horizontally/vertically
createRandomBuildings() // this is what we want to create!
}
class Building {
Point position // position of top-left corner
Building[] subBuildings // buildings created by subdivision
double width
double height
double size() { return width * height }
}
Now for the fun part! Let's try and make the createRandomBuildings() method. The approach I'll be taking is to repeatedly subdivide buildings until they are between 2 * minBuildingSize (less than that means no subdivide can create two valid buildings) and maxBuildingSize.
IMPORTANT NOTE: This approach only guarantees valid buildings if maxBuildingSize >= 2 * minBuildingSize, even if a valid subdivision is possible. Considering your use case, I figured the size constraint would not pose any issues, and a more "random" solution would be better as opposed to a more deterministic one.
Let's get to it! We'll create a recursive function called subdivide to do the heavy lifting.
Building[] subdivide(Building b, horizontal/vertical) {} // Subdivides b into a random number of other buildings
The way I'll subdivide each building is to split it into a random number of horizontal/vertical segments. Ex.
From this
To this
NOTE: To simplify matters, I'm going to work through this treating the subdivision as vertical, as in the image above. For a horizontal subdivision, just swap width/height.
Of course, we can't use any number of subdivisions. Too many, and all the resulting buildings will be too small. So we should first define the maximum number of subdivisions that will still allow us to create valid buildings.
minSubdivisionWidth = minSize / b.height // ensures that subdivisionWidth * b.height >= minSize
maxSubdivisions = floor(b.width / minSubdivisionWidth)
subdivisions = randomInt(2, maxSubdivisions)
Now that we have a valid number of subdivisions, we need to space them randomly while ensuring the buildings aren't too small. To do this, let's split the space we have available into two portions: minimum space and free space. Each subdivision will need to have the minimum space, but there is also free (or leftover) space equal to b.size() - minBuildingSize * subdivisions. This free space is what we want to randomly distribute among our subdivided rectangles.
Blue is minimum space, and pink is free space
Let's allocate this space:
widths[] // This will be the widths of our subdivided buildings
freeWidth = b.width - minSubdivisionWidth * subdivisions
weights[] // randomly assigned weight for free space
sumWeight
for i = 1 to subdivisions {
randWeight = random()
weights[i] = randWeight
sumWeight += randWeight
}
for i = 1 to subdivisions {
widths[i] = minSubdivisionWidth + (weights[i] / sumWeight) * freeWidth
}
And now we can do the actual subdivision:
// transform individual widths into coordinates for building split
cumulativeWidth = 0
for i = 1 to (subdivisions - 1) {
cumulativeWidth += widths[i]
splitBuilding(vertical, cumulativeWidth)
}
We're almost there! Now we just need a snippet to randomly not subdivide if the building is below the max:
probToNotSubdivide = .3 // obviously change this to whatever
if b.size() < maxBuildingSize and randomDouble(0, 1) <= probToNotSubdivide { return }
One to not subdivide if the building is too small:
if b.size() < minBuildingSize * 2 { return }
One to not subdivide if it would cut off a building from the edge of the block:
/*
If the building is touching a horizontal edge, vertical subdivisions
will not cut anything off. If the building is touching both
vertical edges, one subdivision can be made.
*/
if not (b.position.y == 0 or (b.position.y + b.height) == cityBlock.height) {
if b.width == cityBlock.width {
// do one subdivision and recurse
splitBuilding(vertical, randomDouble(minSubdivisionWidth, cityBlock.width - minSubdivisionWidth)
for subBuilding in b.subBuildings {
subdivide(horizontal, subBuilding)
}
return
} else { return }
}
Add a bit of recursion at the end and...
Building[] subdivide(Building b, horizontal/vertical) {
// exit conditions
if b.size() < maxBuildingSize and randomDouble(0, 1) <= probToNotSubdivide { return }
if b.size() < minBuildingSize * 2 { return }
/*
If the building is touching a horizontal edge, vertical subdivisions
will not cut anything off. If the building is touching both
vertical edges, one subdivision can be made.
*/
if not (b.position.y == 0 or (b.position.y + b.height) == cityBlock.height) {
if b.width == cityBlock.width {
// do one subdivision and recurse
splitBuilding(vertical, randomDouble(minSubdivisionWidth, cityBlock.width - minSubdivisionWidth)
for subBuilding in b.subBuildings {
subdivide(horizontal, subBuilding)
}
return
} else { return }
}
// get # subdivisions
minSubdivisionWidth = minSize / b.height // ensures that subdivisionWidth * b.height <= minSize
maxSubdivisions = floor(b.width / minSubdivisionWidth)
subdivisions = randomInt(2, maxSubdivisions)
// get subdivision widths
widths[] // This will be the widths of our subdivided buildings
freeWidth = b.width - minSubdivisionWidth * subdivisions
weights[] // randomly assigned weight for free space
sumWeight
for i = 1 to subdivisions {
randWeight = random()
weights[i] = randWeight
sumWeight += randWeight
}
for i = 1 to subdivisions {
widths[i] = minSubdivisionWidth + (weights[i] / sumWeight) * freeWidth
}
// transform individual widths into coordinates for building split
cumulativeWidth = 0
for i = 1 to (subdivisions - 1) {
cumulativeWidth += widths[i]
splitBuilding(vertical, cumulativeWidth)
}
// recurse
for subBuilding in b.subBuildings {
subdivide(horizontal, subBuilding)
}
}
And that's it! Now we have createRandomBuildings() { subdivide(vertical, initialBuilding) }, and we've subdivided our city block.
P.S. Again, this code isn't meant to be valid, and this is also a very long post. If something in here doesn't work right, edit/comment on this answer. I hope this gives some insight as to the approach you could take.
EDIT: To clarify, you should switch between horizontal and vertical subdivisions on each level of recursion.

Infinite Blue Noise

I am looking for an algorithm which produces a point placement result similar to blue noise.
However, it needs to work for an infinite plane. Where a bounding box is given, and it returns the locations of all points which fall inside. Any help would be appreciated. I've done a lot of research, and found nothing that suits my needs.
Finally I've managed to get results.
One way of generating point distributions with blue noise properties
is by means of a Poisson-Disk Distribution
Following the algorithm from the paper Fast Poisson disk sampling in
arbitrary dimensions, Robert Bridson I've got:
The steps mentioned in the paper are:
Step 0. Initialize an n-dimensional background grid for storing
samples and accelerating spatial searches. We pick the cell size to be
bounded by r/sqrt(n), so that each grid cell will contain at most one
sample, and thus the grid can be implemented as a simple n-dimensional
array of integers: the default −1 indicates no sample, a non-negative
integer gives the index of the sample located in a cell
Step 1. Select the initial sample, x0, randomly chosen uniformly from
the domain. Insert it into the background grid, and initialize the
“active list” (an array of sample indices) with this index (zero).
Step 2. While the active list is not empty, choose a random index from
it (say i). Generate up to k points chosen uniformly from the
spherical annulus between radius r and 2r around xi. For each point in
turn, check if it is within distance r of existing samples (using the
background grid to only test nearby samples). If a point is adequately
far from existing samples, emit it as the next sample and add it to
the active list. If after k attempts no such point is found, instead
remove i from the active list.
Note that for simplicity I've skipped step 0. Despite that the run-time is still reasonable. It's < .5s. Implementing this step would most definitely increase the performance.
Here's a sample code in Processing. It's a language built on top of Java so the syntax is very similar. Translating it for your purposes shouldn't be hard.
import java.util.List;
import java.util.Collections;
List<PVector> poisson_disk_sampling(int k, int r, int size)
{
List<PVector> samples = new ArrayList<PVector>();
List<PVector> active_list = new ArrayList<PVector>();
active_list.add(new PVector(random(size), random(size)));
int len;
while ((len = active_list.size()) > 0) {
// picks random index uniformly at random from the active list
int index = int(random(len));
Collections.swap(active_list, len-1, index);
PVector sample = active_list.get(len-1);
boolean found = false;
for (int i = 0; i < k; ++i) {
// generates a point uniformly at random in the sample's
// disk situated at a distance from r to 2*r
float angle = 2*PI*random(1);
float radius = random(r) + r;
PVector dv = new PVector(radius*cos(angle), radius*sin(angle));
PVector new_sample = dv.add(sample);
boolean ok = true;
for (int j = 0; j < samples.size(); ++j) {
if (dist(new_sample.x, new_sample.y,
samples.get(j).x, samples.get(j).y) <= r)
{
ok = false;
break;
}
}
if (ok) {
if (0 <= new_sample.x && new_sample.x < size &&
0 <= new_sample.y && new_sample.y < size)
{
samples.add(new_sample);
active_list.add(new_sample);
len++;
found = true;
}
}
}
if (!found)
active_list.remove(active_list.size()-1);
}
return samples;
}
List<PVector> samples;
void setup() {
int SIZE = 500;
size(500, 500);
background(255);
strokeWeight(4);
noLoop();
samples = poisson_disk_sampling(30, 10, SIZE);
}
void draw() {
for (PVector sample : samples)
point(sample.x, sample.y);
}
However, it needs to work for an infinite plane.
You control the size of the box with the parameter size. r controls the relative distance between the points. k controls how many new sample should you try before rejecting the current. The paper suggests k=30.

Geographic Information System and algorithm to find rides that are passing near me

I'm trying to develop an app that can find me rides not only based on people going from A to B like me, but even if I'm on the way of someone else's A to B.
For example, if someone searches for a ride from Jersey Shore to Manhattan, and there are many rides driving near there, I need an algorithm that could calculate who is passing closest to this someone. This is challenging because I'm not searching for a distance to a point, but rather a distance to a route (for example, the driver could have originally entered that he is going from Washington D.C. to Manhattan)
Google Maps API is great, but I need an algorithm/program that could solve node-to-edge distances and possibly an advanced GIS system.
Does anyone know where I could find some work on this topic?
You can try some spatial data structures, for example quadtrees,r-trees,delaunay triangulation:Find nearest edge in graph.
As you don't mention the technology you're working I can't get into specific details but here are some things/libraries to get you started:
APIs:
JTS (Java Topology Suite): contains functions for finding distance between points and lines and datastructures to speed your search for nearby lines (R-trees, quadtrees, ...)
NetTopologySuite: .NET port of JTS, for .NET there is also the DotSpatial API
geos: C++ port of JTS
for Python there is Shapely, RTree, ...
various databases have spatial extensions but two notable ones are:
PostgreSQL with PostGIS e.g. Indexed nearest neighbour search
SqlLite with Spatialite (particularly easy to get started)
If you work on Windows take also a look at OSGeo4W, which is a handy installer for various programs and libraries.
Note also that there is a stackexchange site dedicated to GIS.
You can start by getting the "distance" from your point to the linestring that represents someone else's path. You can use the technologies mentioned by Samuel to do this, or you can write your own code. For distance testing on a LineString, you can break this into two problems. Finding the shortest distance between a line segment and your point. And then repeating that test for each segment in the line string. The code posted here is simple brute force testing, and doesn't try to do anything to optimize, but you probably won't have to unless you are dealing with millions of paths, and in that case, some simple extent testing or R-trees could help optimize by narrowing the paths to test. Also, the code here is in Java, but should be simple enough that you can translate to other languages pretty easily.
Segment code to find distance from point to segment:
/**
* Gets the closest point that is on the segment to the specified point, which
* can be anywhere.
* #param point The point to get the closest point to.
* #return The Coordinate closest to the specified point.
*/
public Coord closestPointTo(Coord point)
{
EndPointInteraction endPointFlag = EndPointInteraction.OnLine;
return closestPointTo(point, false, endPointFlag);
}
/**
* Gets the closest point the the specified point, given information about
* whether the line is allowed to be infinite or not.
* #param point The point to find the closest point to.
* #param isInfiniteLine boolean. If this is true, the segment is treated as if
* it defines an infinite line.
* #param endPointFlag This contains extra information about whether the point
* is past the start or end of the segment or whether the segment is degenerate.
* #return The Coordinate that is the closest point on the line to the specified point.
*/
public Coord closestPointTo(Coord point, boolean isInfiniteLine,
EndPointInteraction endPointFlag) {
// If the points defining this segment are the same, we treat the segment as a point
// special handling to avoid 0 in denominator later
if (P2.X == P1.X && P2.Y == P1.Y) {
endPointFlag = EndPointInteraction.P1equalsP2;
return P1;
}
//http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm
Vector v = toVector(); // vector from p1 to p2 in the segment
v.Z = 0;
Vector w = new Vector(P1, point); // vector from p1 to Point
w.Z = 0;
double c1 = w.dot(v); // the dot product represents the projection onto the line
if (c1 < 0) {
endPointFlag = EndPointInteraction.PastP1;
if (!isInfiniteLine) // The closest point on the segment to Point is p1
{
return P1;
}
}
double c2 = v.dot(v);
if (c2 <= c1) {
endPointFlag = EndPointInteraction.PastP2;
if (!isInfiniteLine) // The closest point on the segment to Point is p2
{
return P2;
}
}
// The closest point on the segment is perpendicular to the point,
// but somewhere on the segment between P1 and P2
endPointFlag = EndPointInteraction.OnLine;
double b = c1 / c2;
v = v.multiply(b);
Coord pb = new Coord(P1.X + v.X, P1.Y + v.Y);
return pb;
}
/**
* Gets the minimum distance to the specified coordinate.
* #param point The point to get the distance from this segment to.
* #return The double distance.
*/
public double distanceTo(Coord point)
{
return closestPointTo(point).distance(point);
}
LineString (or any part with a list of coordinates) code to cycle through segments to get distance:
/**
* Gets the minimum distance to an edge of the part. This does not consider whether the point
* is inside the part or not.
* #param coordinate
* #return
*/
public double distance(Coord coordinate)
{
List<Segment> segs = this.getSegments();
double minDist = Double.MAX_VALUE;
for(Segment seg : segs)
{
double dist = seg.distanceTo(coordinate);
if(dist < minDist)
{
minDist = dist;
}
}
return minDist;
}
/**
* Generates a set of segments that represent this part.
* #return
*/
public List<Segment> getSegments()
{
List<Segment> result = new ArrayList<Segment>();
if(getCoordinates().size() < 2) {
return result;
}
for(int i = 0; i < getCoordinates().size()-1; i++)
{
result.add(new Segment(getCoordinates().get(i), getCoordinates().get(i+1)));
}
if(closed)
{
result.add(new Segment(getCoordinates().get(getCoordinates().size()-1), getCoordinates().get(0)));
}
return result;
}

An algorithm to space out overlapping rectangles?

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();
}
});
}
}

Tile map collision detection

There are many topics like this, but none with concrete answers. I am drawing a tile-map in the traditional way (two for loops) and keeping my player centered except when the edges of the map is reached. How would I create collision detection? I need to know how to translate tile location in the array to screen coordinates I think.
I will give you the code i wrote for point/tilemap collision detection. The code assumes that you have a point at (xfrom, yfrom) and you want to move it to (xto, yto) and want to see if there is a collision with a block in the tilemap map[Y][X]. I assume a method isSolid(tileId) which will return true if the tile is solid.
/**
* This method returns true if there is a collision between a point and a 2D tilemap map[Y][X].
* The isSolid method must be implemented to indicate if a tile is solid or not
* Assumes the tilemap starts at (0,0) and TILEWIDTH and TILEHEIGHT hold the size of a tile (in pixels)
* #param xfrom the original x-coordinate of the point
* #param yfrom the original y-coordinate of the point
* #param xto the destination x-coordinate of the point
* #param yto the destination y-coordinate of the point
* #param outCollisionPoint output the location where the collision occurs
* #return true if a collision is found
*/
public boolean collisionDetection(int xfrom, int yfrom, int xto, int yto, Point outCollisionPoint){
//Ref: A fast voxel traversal algorithm J.Amanatides, A. Woo
float tMaxX, tMaxY, tDeltaX, tDeltaY, collisionLength;
int X, Y, stepX, stepY, endX, endY, blkX, blkY;
//Calculate direction vector
float dirX = (xto - xfrom);
float dirY = (yto - yfrom);
float length = (float) Math.sqrt(dirX * dirX + dirY * dirY);
//Normalize direction vector
dirX /= length;
dirY /= length;
//tDeltaX: distance in terms of vector(dirX,dirY) between two consecutive vertical lines
tDeltaX = TILEWIDTH / Math.abs(dirX);
tDeltaY = TILEHEIGHT / Math.abs(dirY);
//Determine cell where we originally are
X = xfrom / TILEWIDTH;
Y = yfrom / TILEHEIGHT;
endX = xto / TILEWIDTH;
endY = yto / TILEHEIGHT;
//stepX: Determine in what way do we move between cells
//tMaxX: the distance in terms of vector(dirX,dirY) to the next vertical line
if (xto > xfrom){
blkX = 0;
stepX = 1;
tMaxX = ((X+1) * TILEWIDTH - xfrom) / dirX;
}else{
blkX = 1;
stepX = -1;
tMaxX = (X * TILEWIDTH - xfrom) / dirX;
}
if (yto > yfrom){
blkY = 0;
stepY = 1;
tMaxY = ((Y+1) * TILEHEIGHT - yfrom) / dirY;
}else{
blkY = 1;
stepY = -1;
tMaxY = (Y * TILEHEIGHT - yfrom) / dirY;
}
if (isSolid(map[Y][X])) {
//point already collides
outCollisionPoint = new Point(xfrom, yfrom);
return true;
}
//Scan the cells along the line between 'from' and 'to'
while (X != endX || Y !=endY){
if(tMaxX < tMaxY){
tMaxX += tDeltaX;
X += stepX;
if (isSolid(map[Y][X])) {
collisionLength = ((X + blkX) * TILEWIDTH - xfrom) / dirX;
outCollisionPoint = new Point((int)(xfrom + dirX * collisionLength), (int)(yfrom + dirY * collisionLength));
return true;
}
}else{
tMaxY += tDeltaY;
Y += stepY;
if (isSolid(map[Y][X])) {
collisionLength= ((Y + blkY) * TILEHEIGHT - yfrom) / dirY;
outCollisionPoint = new Point((int)(xfrom + dirX * collisionLength), (int)(yfrom + dirY * collisionLength));
return true;
}
}
}
return false;
}
It depends on the model.
If your model (the data) is a grid, then a collision occurs simply when two incompatible objects occupy the same location. The easiest way to handle this type of collision is just to make sure where you are trying to move a game entity to is "available". If it is, no collision, and update the model. If it wasn't free, then there was a collision.
The screen simply renders the model. With the exception of something like of per-pixel collision detection (think the original lemmings or worms), don't use it for collision detection.
The screen/view is just the rendering agent. While you can have the model tied tightly to the screen (e.g. you only need to update parts of the screen in which things have changed such as when a piece is moved), the screen is not, and should not, generally be considered part of the model. However, with modern computing speed, you might as well simple re-render the entire visible model each frame.
(Yes, I know I repeated myself. It was on purpose.)
Now, to answer the secondary question not mentioned in the title:
When you start rendering, simply draw screen_width/cell_width/2 cells to the left and screen_width/cell_width/2 cells to the right of the player (the player is assumed to take 1x1). Do the same for the up-and-down. Make sure to not cause an Index-Out-Of-Bounds exception. You can run the for-loops with out-of-bounds values, as long long as you clamp/filter before using them. If you wish to only make the character "push" the edge when he gets close, keep track of a current model-to-view reference as well.

Resources