Rev 636 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* Copyright (C) 2014, Gerd Petermann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package uk.me.parabola.splitter.solver;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class to combine a list of tiles with some
* values that measure the quality.
* @author GerdP
*
*/
public class Solution
{
/**
*
*/
private enum sides
{TOP,RIGHT,BOTTOM,LEFT
}
private final List<Tile
> tiles
;
private final long maxNodes
;
private double worstAspectRatio = -
1;
private int numLowCount
;
private long worstMinNodes =
Long.
MAX_VALUE;
public Solution
(long maxNodes
) {
tiles =
new ArrayList<>();
this.
maxNodes = maxNodes
;
}
public Solution copy
() {
Solution s =
new Solution
(this.
maxNodes);
tiles.
forEach(s::add
);
return s
;
}
public boolean add
(Tile tile
) {
tiles.
add(tile
);
double aspectRatio = tile.
getAspectRatio();
if (aspectRatio
< 1.0)
aspectRatio =
1.0 / aspectRatio
;
worstAspectRatio =
Math.
max(aspectRatio, worstAspectRatio
);
worstMinNodes =
Math.
min(tile.
getCount(), worstMinNodes
);
if (tile.
getCount() < maxNodes /
3)
numLowCount++
;
return true;
}
/**
* Combine this solution with the other.
* @param other
*/
public void merge
(Solution other
) {
if (other.
tiles.
isEmpty())
return;
if (tiles.
isEmpty()) {
worstAspectRatio = other.
worstAspectRatio;
worstMinNodes = other.
worstMinNodes;
} else {
if (other.
worstAspectRatio > worstAspectRatio
)
worstAspectRatio = other.
worstAspectRatio;
if (worstMinNodes
> other.
worstMinNodes)
worstMinNodes = other.
worstMinNodes;
}
numLowCount += other.
numLowCount;
tiles.
addAll(other.
tiles);
}
public List<Tile
> getTiles
() {
return tiles
;
}
public long getWorstMinNodes
() {
return worstMinNodes
;
}
public double getWorstAspectRatio
() {
return worstAspectRatio
;
}
public boolean isEmpty
() {
return tiles.
isEmpty();
}
public int size
() {
return tiles.
size();
}
/**
* Compare two solutions
* @param other
* @return -1 if this is better, 1 if other is better, 0 if both are equal
*/
public int compareTo
(Solution other
) {
if (other ==
null)
return -
1;
if (other ==
this)
return 0;
if (isEmpty
() != other.
isEmpty())
return isEmpty
() ? 1 : -
1;
int d =
Boolean.
compare(isNice
(), other.
isNice());
if (d
!=
0)
return -d
; // prefer this if nice
if (worstMinNodes
!= other.
worstMinNodes) {
// ignore minNodes when both are bad
if (Math.
max(worstMinNodes, other.
worstMinNodes) > 1000)
return (worstMinNodes
> other.
worstMinNodes) ? -
1 :
1;
}
// if aspect ratio is very different and tile sizes are almost equal,
// favour better aspect ratio
double tileRatio =
(double) tiles.
size() / other.
tiles.
size();
double arRatio = worstAspectRatio / other.
worstAspectRatio;
if (tileRatio
< 1 && tileRatio
> 0.99 && arRatio
> 1.5)
return 1;
if (tileRatio
< 1.01 && tileRatio
> 1 && arRatio
< 0.66666)
return -
1;
if (tiles.
size() != other.
tiles.
size())
return tiles.
size() < other.
tiles.
size() ? -
1 :
1;
if (worstAspectRatio
!= other.
worstAspectRatio)
return worstAspectRatio
< other.
worstAspectRatio ? -
1 :
1;
return 0;
}
/**
* Trim tiles without creating holes or gaps between tiles
*/
public void trimOuterTiles
() {
while (true) {
boolean trimmedAny =
false;
int minX =
Integer.
MAX_VALUE;
int maxX =
Integer.
MIN_VALUE;
int minY =
Integer.
MAX_VALUE;
int maxY =
Integer.
MIN_VALUE;
for (Tile tile : tiles
) {
if (minX
> tile.
x) minX = tile.
x;
if (minY
> tile.
y) minY = tile.
y;
if (maxX
< tile.
getMaxX()) maxX =
(int) tile.
getMaxX();
if (maxY
< tile.
getMaxY()) maxY =
(int) tile.
getMaxY();
}
for (sides side:sides.
values()) {
for (int direction = -
1; direction
<=
1; direction +=
2) {
int trimToPos = -
1;
switch (side
) {
case LEFT:
case BOTTOM: trimToPos =
Integer.
MAX_VALUE;
break;
case TOP:
case RIGHT: trimToPos = -
1;
}
while (true) {
Tile candidate =
null;
boolean trimmed =
false;
for (Tile tile : tiles
) {
if (tile.
getCount() ==
0)
continue;
switch (side
) {
case LEFT:
if (minX == tile.
x && (candidate ==
null ||
(direction
< 0 && candidate.
y > tile.
y)
||
(direction
> 0 && candidate.
getMaxY() < tile.
getMaxY()))) {
candidate = tile
;
}
break;
case RIGHT:
if (maxX == tile.
getMaxX()
&& (candidate ==
null ||
(direction
< 0 && candidate.
y > tile.
y)
||
(direction
> 0 && candidate.
getMaxY() < tile.
getMaxY()))) {
candidate = tile
;
}
break;
case BOTTOM:
if (minY == tile.
y && (candidate ==
null ||
(direction
< 0 && candidate.
x > tile.
x)
||
(direction
> 0 && candidate.
getMaxX() < tile.
getMaxX()))) {
candidate = tile
;
}
break;
case TOP:
if (maxY == tile.
getMaxY()
&& (candidate ==
null ||
(direction
< 0 && candidate.
x > tile.
x)
||
(direction
> 0 && candidate.
getMaxX() < tile.
getMaxX()))) {
candidate = tile
;
}
break;
}
}
if (candidate ==
null)
break;
Rectangle before =
new Rectangle(candidate
);
switch (side
) {
case LEFT:
while (candidate.
x < trimToPos
&& candidate.
getColSum(0) ==
0) {
candidate.
x ++
;
candidate.
width--
;
}
if (candidate.
x < trimToPos
)
trimToPos = candidate.
x;
break;
case RIGHT:
while ((candidate.
getMaxX() > trimToPos
) && candidate.
getColSum(candidate.
width-
1) ==
0) {
candidate.
width--
;
}
if (candidate.
getMaxX() > trimToPos
)
trimToPos =
(int) candidate.
getMaxX();
break;
case BOTTOM:
while (candidate.
y < trimToPos
&& candidate.
getRowSum(0) ==
0) {
candidate.
y ++
;
candidate.
height--
;
}
if (candidate.
y < trimToPos
)
trimToPos = candidate.
y;
break;
case TOP:
while (candidate.
getMaxY() > trimToPos
&& candidate.
getRowSum(candidate.
height-
1) ==
0) {
candidate.
height--
;
}
if (candidate.
getMaxX() > trimToPos
)
trimToPos =
(int) candidate.
getMaxY();
break;
}
if (!before.
equals(candidate
)) {
trimmed =
true;
trimmedAny =
true;
}
if (!trimmed
)
break;
}
}
}
if (!trimmedAny
)
return;
}
}
/**
* A solution is considered to be nice when aspect
* ratios are not extreme and every tile is filled
* with at least 33% of the max-nodes value or almost all tiles are filled much better.
* @return
*/
public boolean isNice
() {
if (isEmpty
() || worstAspectRatio
> SplittableDensityArea.
NICE_MAX_ASPECT_RATIO)
return false;
final long low = maxNodes /
3;
if (tiles.
size() ==
1 || worstMinNodes
>= low ||
(numLowCount
<=
2 && tiles.
size() > 20)
||
(numLowCount ==
1 && tiles.
size() > 4))
return true;
double lowRatio =
100.0 * numLowCount / tiles.
size();
return lowRatio
< 3; // less then 3 percent of the tiles are not well filled
}
@
Override
public String toString
() {
if (isEmpty
())
return "is empty";
long percentage =
100 * worstMinNodes / maxNodes
;
return tiles.
size() +
" tile(s). The smallest node count is " + worstMinNodes +
" (" + percentage +
" %)";
}
/**
* Returns true if this solution is smaller or better than the other.
* @param other the other solution
* @return true if this solution is smaller or better than the other
*/
public boolean isSmallerOrBetter
(Solution other
) {
if (isEmpty
())
return false;
if (other ==
null || other.
isEmpty() && !isEmpty
())
return true;
if (other.
size() > this.
size())
return true;
if (other.
size() ==
this.
size())
return compareTo
(other
) < 0;
return false;
}
}