Renamed Allocate() methods to Pack() in RectanglePacker; fixed an oversight in Nuclex.Support that made overlapping rectangles possible; reactivated wasted area calculation for 'cygon' rectangle packer
git-svn-id: file:///srv/devel/repo-conversion/nusu@23 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
4fd0680ae7
commit
116fb53b0a
|
@ -27,6 +27,10 @@ namespace Nuclex.Support.Packing {
|
|||
/// <summary>Rectangle packer using an algorithm by Javier Arevalo</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Original code by Javier Arevalo (jare at iguanademos dot com). Rewritten
|
||||
/// to C# / .NET by Markus Ewald (cygon at nuclex dot org).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// You have a bunch of rectangular pieces. You need to arrange them in a
|
||||
/// rectangular surface so that they don't overlap, keeping the total area of the
|
||||
/// rectangle as small as possible. This is fairly common when arranging characters
|
||||
|
@ -95,10 +99,10 @@ namespace Nuclex.Support.Packing {
|
|||
#endregion
|
||||
|
||||
/// <summary>Initializes a new rectangle packer</summary>
|
||||
/// <param name="maxPackingAreaWidth">Maximum width of the packing area</param>
|
||||
/// <param name="maxPackingAreaHeight">Maximum height of the packing area</param>
|
||||
public ArevaloRectanglePacker(int maxPackingAreaWidth, int maxPackingAreaHeight)
|
||||
: base(maxPackingAreaWidth, maxPackingAreaHeight) {
|
||||
/// <param name="packingAreaWidth">Maximum width of the packing area</param>
|
||||
/// <param name="packingAreaHeight">Maximum height of the packing area</param>
|
||||
public ArevaloRectanglePacker(int packingAreaWidth, int packingAreaHeight)
|
||||
: base(packingAreaWidth, packingAreaHeight) {
|
||||
|
||||
this.packedRectangles = new List<Rectangle>();
|
||||
this.anchors = new List<Point>();
|
||||
|
@ -113,7 +117,7 @@ namespace Nuclex.Support.Packing {
|
|||
/// <param name="rectangleHeight">Height of the rectangle to allocate</param>
|
||||
/// <param name="placement">Output parameter receiving the rectangle's placement</param>
|
||||
/// <returns>True if space for the rectangle could be allocated</returns>
|
||||
public override bool TryAllocate(
|
||||
public override bool TryPack(
|
||||
int rectangleWidth, int rectangleHeight, out Point placement
|
||||
) {
|
||||
|
||||
|
@ -181,8 +185,8 @@ namespace Nuclex.Support.Packing {
|
|||
// If we reach this point, the rectangle did not fit in the current packing
|
||||
// area and our only choice is to try and enlarge the packing area.
|
||||
|
||||
bool canEnlargeWidth = (packingAreaWidth < MaxPackingAreaWidth);
|
||||
bool canEnlargeHeight = (packingAreaHeight < MaxPackingAreaHeight);
|
||||
bool canEnlargeWidth = (packingAreaWidth < PackingAreaWidth);
|
||||
bool canEnlargeHeight = (packingAreaHeight < PackingAreaHeight);
|
||||
|
||||
// Try to enlarge the smaller of the two dimensions first (unless the smaller
|
||||
// dimension is already at its maximum size)
|
||||
|
@ -195,7 +199,7 @@ namespace Nuclex.Support.Packing {
|
|||
// Try to double the height of the packing area
|
||||
return selectAnchorRecursive(
|
||||
rectangleWidth, rectangleHeight,
|
||||
packingAreaWidth, Math.Min(packingAreaHeight * 2, MaxPackingAreaHeight)
|
||||
packingAreaWidth, Math.Min(packingAreaHeight * 2, PackingAreaHeight)
|
||||
);
|
||||
|
||||
} else if(canEnlargeWidth) {
|
||||
|
@ -203,7 +207,7 @@ namespace Nuclex.Support.Packing {
|
|||
// Try to double the width of the packing area
|
||||
return selectAnchorRecursive(
|
||||
rectangleWidth, rectangleHeight,
|
||||
Math.Min(packingAreaWidth * 2, MaxPackingAreaWidth), packingAreaHeight
|
||||
Math.Min(packingAreaWidth * 2, PackingAreaWidth), packingAreaHeight
|
||||
);
|
||||
|
||||
} else {
|
||||
|
|
|
@ -24,13 +24,20 @@ using Microsoft.Xna.Framework;
|
|||
|
||||
namespace Nuclex.Support.Packing {
|
||||
|
||||
/// <summary>Packer using a custom algorithm by Markus Ewald</summary>
|
||||
/// <summary>Packer using a custom algorithm by Markus 'Cygon' Ewald</summary>
|
||||
/// <remarks>
|
||||
/// This algorithms always places as close to the top as possible. So, for any new
|
||||
/// rectangle, the packer has to determine a X coordinate at which the rectangle
|
||||
/// can be placed at the highest point. To quickly discover these locations,
|
||||
/// the packer keeps a dynamically list of "height slices", which store the
|
||||
/// baseline of the rectangles that have been placed so far.
|
||||
/// <para>
|
||||
/// Algorithm conceived by Markus Ewald (cygon at nuclex dot org), thought
|
||||
/// I'm quite sure I'm not the first one to invent this algorithm :)
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This algorithm always places rectangles as low as possible. So, for any
|
||||
/// new rectangle that is to be added into the packing area, the packer has
|
||||
/// to determine the X coordinate at which the rectangle has the lowest height.
|
||||
/// To quickly discover these locations, the packer keeps a dynamically updated
|
||||
/// list of "height slices" which store the silhouette of the rectangles that
|
||||
/// have been placed so far.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class CygonRectanglePacker : RectanglePacker {
|
||||
|
||||
|
@ -55,10 +62,10 @@ namespace Nuclex.Support.Packing {
|
|||
#endregion
|
||||
|
||||
/// <summary>Initializes a new rectangle packer</summary>
|
||||
/// <param name="maxPackingAreaWidth">Maximum width of the packing area</param>
|
||||
/// <param name="maxPackingAreaHeight">Maximum height of the packing area</param>
|
||||
public CygonRectanglePacker(int maxPackingAreaWidth, int maxPackingAreaHeight)
|
||||
: base(maxPackingAreaWidth, maxPackingAreaHeight) {
|
||||
/// <param name="packingAreaWidth">Maximum width of the packing area</param>
|
||||
/// <param name="packingAreaHeight">Maximum height of the packing area</param>
|
||||
public CygonRectanglePacker(int packingAreaWidth, int packingAreaHeight)
|
||||
: base(packingAreaWidth, packingAreaHeight) {
|
||||
|
||||
this.heightSlices = new List<Point>();
|
||||
|
||||
|
@ -72,41 +79,37 @@ namespace Nuclex.Support.Packing {
|
|||
/// <param name="rectangleHeight">Height of the rectangle to allocate</param>
|
||||
/// <param name="placement">Output parameter receiving the rectangle's placement</param>
|
||||
/// <returns>True if space for the rectangle could be allocated</returns>
|
||||
public override bool TryAllocate(
|
||||
public override bool TryPack(
|
||||
int rectangleWidth, int rectangleHeight, out Point placement
|
||||
) {
|
||||
int sliceIndex = findBestPosition(rectangleWidth, rectangleHeight);
|
||||
|
||||
// TODO: Rectangle might not even fit there!
|
||||
if(sliceIndex == -1) {
|
||||
|
||||
// If the rectangle is larger than the packing area in any dimension,
|
||||
// it will never fit!
|
||||
if(
|
||||
(rectangleWidth > PackingAreaWidth) || (rectangleHeight > PackingAreaHeight)
|
||||
) {
|
||||
placement = Point.Zero;
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
placement = this.heightSlices[sliceIndex];
|
||||
|
||||
integrateRectangle(
|
||||
this.heightSlices[sliceIndex].X,
|
||||
rectangleWidth,
|
||||
this.heightSlices[sliceIndex].Y + rectangleHeight
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool fits = findBestPosition(rectangleWidth, rectangleHeight, out placement);
|
||||
if(fits)
|
||||
integrateRectangle(placement.X, rectangleWidth, placement.Y + rectangleHeight);
|
||||
|
||||
return fits;
|
||||
}
|
||||
|
||||
/// <summary>Finds the best position for a rectangle of the given width</summary>
|
||||
/// <param name="rectangleWidth">Width of the rectangle to find a position for</param>
|
||||
/// <param name="rectangleHeight">Height of the rectangle to find a position for</param>
|
||||
/// <returns>The best position for a rectangle with the specified width</returns>
|
||||
private int findBestPosition(int rectangleWidth, int rectangleHeight) {
|
||||
private bool findBestPosition(
|
||||
int rectangleWidth, int rectangleHeight, out Point placement
|
||||
) {
|
||||
|
||||
// Index and score of the best slice we could find for the rectangle
|
||||
int bestSliceIndex = -1;
|
||||
int bestScore = MaxPackingAreaWidth * MaxPackingAreaHeight; // lower == better!
|
||||
// Slice index, vertical position and score of the best placement we could find
|
||||
int bestSliceIndex = -1; // Slice index where the best placement was found
|
||||
int bestSliceY = 0; // Y position of the best placement found
|
||||
int bestScore = PackingAreaWidth * PackingAreaHeight; // lower == better!
|
||||
|
||||
// This is the counter for the currently checked position. The search works by
|
||||
// skipping from slice to slice, determining the suitability of the location for the
|
||||
|
@ -130,11 +133,11 @@ namespace Nuclex.Support.Packing {
|
|||
if(this.heightSlices[index].Y > highest)
|
||||
highest = this.heightSlices[index].Y;
|
||||
|
||||
if((highest + rectangleHeight < MaxPackingAreaHeight)) {
|
||||
if((highest + rectangleHeight < PackingAreaHeight)) {
|
||||
int score = highest;
|
||||
|
||||
// TESTING --------------------------------------------------
|
||||
/*
|
||||
// WASTED AREA CALCULATION --------------------------------------------------
|
||||
|
||||
// Calculate the amount of space that would go to waste if the rectangle
|
||||
// would be placed at this location
|
||||
int wastedArea = 0;
|
||||
|
@ -149,14 +152,13 @@ namespace Nuclex.Support.Packing {
|
|||
this.heightSlices[rightSliceIndex - 1].X
|
||||
);
|
||||
|
||||
//score += Math.Sign(wastedArea);
|
||||
//score += (int)Math.Sqrt((double)wastedArea);
|
||||
//score = wastedArea;
|
||||
*/
|
||||
// TESTING --------------------------------------------------
|
||||
score += (int)Math.Sqrt((double)wastedArea) / 10;
|
||||
|
||||
// WASTED AREA CALCULATION --------------------------------------------------
|
||||
|
||||
if(score < bestScore) {
|
||||
bestSliceIndex = leftSliceIndex;
|
||||
bestSliceY = highest;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +174,7 @@ namespace Nuclex.Support.Packing {
|
|||
for(; rightSliceIndex <= this.heightSlices.Count; ++rightSliceIndex) {
|
||||
int rightSliceStart;
|
||||
if(rightSliceIndex == this.heightSlices.Count)
|
||||
rightSliceStart = MaxPackingAreaWidth;
|
||||
rightSliceStart = PackingAreaWidth;
|
||||
else
|
||||
rightSliceStart = this.heightSlices[rightSliceIndex].X;
|
||||
|
||||
|
@ -190,7 +192,13 @@ namespace Nuclex.Support.Packing {
|
|||
|
||||
// Return the index of the best slice we found for this rectangle. If the rectangle
|
||||
// didn't fit, this variable will still have its initialization value of -1.
|
||||
return bestSliceIndex;
|
||||
if(bestSliceIndex == -1) {
|
||||
placement = Point.Zero;
|
||||
return false;
|
||||
} else {
|
||||
placement = new Point(this.heightSlices[bestSliceIndex].X, bestSliceY);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -234,7 +242,7 @@ namespace Nuclex.Support.Packing {
|
|||
// If the slice ends within the last slice (usual case, unless it has the
|
||||
// exact same width the packing area has), add another slice to return to
|
||||
// the original height at the end of the rectangle.
|
||||
if(right < MaxPackingAreaWidth)
|
||||
if(right < PackingAreaWidth)
|
||||
this.heightSlices.Add(new Point(right, firstSliceOriginalHeight));
|
||||
|
||||
} else { // The rectangle doesn't start on the last slice
|
||||
|
@ -264,7 +272,7 @@ namespace Nuclex.Support.Packing {
|
|||
// Remove all slices covered by the rectangle and begin a new slice at its end
|
||||
// to return back to the height of the slice on which the rectangle ends.
|
||||
this.heightSlices.RemoveRange(startSlice, endSlice - startSlice);
|
||||
if(right < MaxPackingAreaWidth)
|
||||
if(right < PackingAreaWidth)
|
||||
this.heightSlices.Insert(startSlice, new Point(right, returnHeight));
|
||||
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace Nuclex.Support.Packing {
|
|||
for(int size = 24; size >= 1; --size) {
|
||||
Point placement;
|
||||
|
||||
if(packer.TryAllocate(size, size, out placement))
|
||||
if(packer.TryPack(size, size, out placement))
|
||||
areaCovered += size * size;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,28 +32,28 @@ namespace Nuclex.Support.Packing {
|
|||
/// performant one for a given job.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// An almost exhaustive list of rectangle packers can be found here:
|
||||
/// An almost exhaustive list of packing algorithms can be found here:
|
||||
/// http://www.csc.liv.ac.uk/~epa/surveyhtml.html
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class RectanglePacker {
|
||||
|
||||
/// <summary>Initializes a new rectangle packer</summary>
|
||||
/// <param name="maxPackingAreaWidth">Maximum width of the packing area</param>
|
||||
/// <param name="maxPackingAreaHeight">Maximum height of the packing area</param>
|
||||
protected RectanglePacker(int maxPackingAreaWidth, int maxPackingAreaHeight) {
|
||||
this.maxPackingAreaWidth = maxPackingAreaWidth;
|
||||
this.maxPackingAreaHeight = maxPackingAreaHeight;
|
||||
/// <param name="packingAreaWidth">Width of the packing area</param>
|
||||
/// <param name="packingAreaHeight">Height of the packing area</param>
|
||||
protected RectanglePacker(int packingAreaWidth, int packingAreaHeight) {
|
||||
this.packingAreaWidth = packingAreaWidth;
|
||||
this.packingAreaHeight = packingAreaHeight;
|
||||
}
|
||||
|
||||
/// <summary>Allocates space for a rectangle in the packing area</summary>
|
||||
/// <param name="rectangleWidth">Width of the rectangle to allocate</param>
|
||||
/// <param name="rectangleHeight">Height of the rectangle to allocate</param>
|
||||
/// <returns>The location at which the rectangle has been placed</returns>
|
||||
public virtual Point Allocate(int rectangleWidth, int rectangleHeight) {
|
||||
public virtual Point Pack(int rectangleWidth, int rectangleHeight) {
|
||||
Point point;
|
||||
|
||||
if(!TryAllocate(rectangleWidth, rectangleHeight, out point))
|
||||
if(!TryPack(rectangleWidth, rectangleHeight, out point))
|
||||
throw new Exception("Rectangle does not fit in packing area");
|
||||
|
||||
return point;
|
||||
|
@ -64,24 +64,24 @@ namespace Nuclex.Support.Packing {
|
|||
/// <param name="rectangleHeight">Height of the rectangle to allocate</param>
|
||||
/// <param name="placement">Output parameter receiving the rectangle's placement</param>
|
||||
/// <returns>True if space for the rectangle could be allocated</returns>
|
||||
public abstract bool TryAllocate(
|
||||
public abstract bool TryPack(
|
||||
int rectangleWidth, int rectangleHeight, out Point placement
|
||||
);
|
||||
|
||||
/// <summary>Maximum width the packing area is allowed to have</summary>
|
||||
protected int MaxPackingAreaWidth {
|
||||
get { return this.maxPackingAreaWidth; }
|
||||
protected int PackingAreaWidth {
|
||||
get { return this.packingAreaWidth; }
|
||||
}
|
||||
|
||||
/// <summary>Maximum height the packing area is allowed to have</summary>
|
||||
protected int MaxPackingAreaHeight {
|
||||
get { return this.maxPackingAreaHeight; }
|
||||
protected int PackingAreaHeight {
|
||||
get { return this.packingAreaHeight; }
|
||||
}
|
||||
|
||||
/// <summary>Maximum allowed width of the packing area</summary>
|
||||
private int maxPackingAreaWidth;
|
||||
private int packingAreaWidth;
|
||||
/// <summary>Maximum allowed height of the packing area</summary>
|
||||
private int maxPackingAreaHeight;
|
||||
private int packingAreaHeight;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -34,38 +34,38 @@ namespace Nuclex.Support.Packing {
|
|||
public class SimpleRectanglePacker : RectanglePacker {
|
||||
|
||||
/// <summary>Initializes a new rectangle packer</summary>
|
||||
/// <param name="maxPackingAreaWidth">Maximum width of the packing area</param>
|
||||
/// <param name="maxPackingAreaHeight">Maximum height of the packing area</param>
|
||||
public SimpleRectanglePacker(int maxPackingAreaWidth, int maxPackingAreaHeight)
|
||||
: base(maxPackingAreaWidth, maxPackingAreaHeight) { }
|
||||
/// <param name="packingAreaWidth">Maximum width of the packing area</param>
|
||||
/// <param name="packingAreaHeight">Maximum height of the packing area</param>
|
||||
public SimpleRectanglePacker(int packingAreaWidth, int packingAreaHeight)
|
||||
: base(packingAreaWidth, packingAreaHeight) { }
|
||||
|
||||
/// <summary>Tries to allocate space for a rectangle in the packing area</summary>
|
||||
/// <param name="rectangleWidth">Width of the rectangle to allocate</param>
|
||||
/// <param name="rectangleHeight">Height of the rectangle to allocate</param>
|
||||
/// <param name="placement">Output parameter receiving the rectangle's placement</param>
|
||||
/// <returns>True if space for the rectangle could be allocated</returns>
|
||||
public override bool TryAllocate(
|
||||
public override bool TryPack(
|
||||
int rectangleWidth, int rectangleHeight, out Point placement
|
||||
) {
|
||||
|
||||
// If the rectangle is larger than the packing area in any dimension,
|
||||
// it will never fit!
|
||||
if(
|
||||
(rectangleWidth > MaxPackingAreaWidth) || (rectangleHeight > MaxPackingAreaHeight)
|
||||
(rectangleWidth > PackingAreaWidth) || (rectangleHeight > PackingAreaHeight)
|
||||
) {
|
||||
placement = Point.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do we have to start a new line ?
|
||||
if(this.column + rectangleWidth > MaxPackingAreaWidth) {
|
||||
if(this.column + rectangleWidth > PackingAreaWidth) {
|
||||
this.currentLine += this.lineHeight;
|
||||
this.lineHeight = 0;
|
||||
this.column = 0;
|
||||
}
|
||||
|
||||
// If it doesn't fit vertically now, the packing area is considered full
|
||||
if(this.currentLine + rectangleHeight > MaxPackingAreaHeight) {
|
||||
if(this.currentLine + rectangleHeight > PackingAreaHeight) {
|
||||
placement = Point.Zero;
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user