Added a small rectangle packing library for optimally arranging smaller rectangles within one larger rectangle
git-svn-id: file:///srv/devel/repo-conversion/nusu@19 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
acdea736b6
commit
d09bb30cec
|
@ -123,6 +123,33 @@
|
||||||
<Name>LicenseKey.Test</Name>
|
<Name>LicenseKey.Test</Name>
|
||||||
<DependentUpon>LicenseKey.cs</DependentUpon>
|
<DependentUpon>LicenseKey.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\Packing\ArevaloRectanglePacker.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>ArevaloRectanglePacker</Name>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Packing\ArevaloRectanglePacker.Test.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>ArevaloRectanglePacker.Test</Name>
|
||||||
|
<DependentUpon>ArevaloRectanglePacker.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Packing\CygonRectanglePacker.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>CygonRectanglePacker</Name>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Packing\CygonRectanglePacker.Test.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>CygonRectanglePacker.Test</Name>
|
||||||
|
<DependentUpon>CygonRectanglePacker.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Packing\RectanglePacker.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>RectanglePacker</Name>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Packing\RectanglePacker.Test.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>RectanglePacker.Test</Name>
|
||||||
|
<DependentUpon>RectanglePacker.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\Serialization\BinarySerializer.Test.cs">
|
<Compile Include="Source\Serialization\BinarySerializer.Test.cs">
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
<Name>BinarySerializer.Test</Name>
|
<Name>BinarySerializer.Test</Name>
|
||||||
|
|
|
@ -151,10 +151,9 @@ namespace Nuclex.Support.Licensing {
|
||||||
for(int j = 0; j < 31; ++j)
|
for(int j = 0; j < 31; ++j)
|
||||||
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
|
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
|
||||||
|
|
||||||
// Using 7 bits, a number up to 2.147.483.648 can be represented,
|
// Using 31 bits, a number up to 2.147.483.648 can be represented,
|
||||||
// while 6 alpha-numerical characters allow for 2.176.782.336 possible values,
|
// while 6 alpha-numerical characters allow for 2.176.782.336 possible values,
|
||||||
// which is enough to fit 7 bits into each 6 alpha-numerical characters
|
// which means we can fit 31 bits into every 6 alpha-numerical characters.
|
||||||
// nun in 6 alphanumerische Zeichen zu verpacken.
|
|
||||||
for(int j = 0; j < 6; ++j) {
|
for(int j = 0; j < 6; ++j) {
|
||||||
resultBuilder.Append(codeTable[sequence % 36]);
|
resultBuilder.Append(codeTable[sequence % 36]);
|
||||||
sequence /= 36;
|
sequence /= 36;
|
||||||
|
@ -176,13 +175,11 @@ namespace Nuclex.Support.Licensing {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now build a nice, readable string from the decoded characters
|
// Now build a nice, readable string from the decoded characters
|
||||||
string s = resultBuilder.ToString();
|
resultBuilder.Insert(5, '-');
|
||||||
return
|
resultBuilder.Insert(11, '-');
|
||||||
s.Substring(0, 5) + "-" +
|
resultBuilder.Insert(17, '-');
|
||||||
s.Substring(5, 5) + "-" +
|
resultBuilder.Insert(23, '-');
|
||||||
s.Substring(10, 5) + "-" +
|
return resultBuilder.ToString();
|
||||||
s.Substring(15, 5) + "-" +
|
|
||||||
s.Substring(20, 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Mangles a bit array</summary>
|
/// <summary>Mangles a bit array</summary>
|
||||||
|
|
47
Source/Packing/ArevaloRectanglePacker.Test.cs
Normal file
47
Source/Packing/ArevaloRectanglePacker.Test.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Packing {
|
||||||
|
|
||||||
|
/// <summary>Unit test for the arevalo rectangle packer class</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class ArevaloRectanglePackerTest : RectanglePackerTest {
|
||||||
|
|
||||||
|
/// <summary>Tests the packer's efficiency using a deterministic benchmark</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSpaceEfficiency() {
|
||||||
|
float efficiency = calculateEfficiency(new ArevaloRectanglePacker(70, 70));
|
||||||
|
|
||||||
|
Assert.GreaterOrEqual(efficiency, 0.75, "Packer achieves 75% efficiency");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Packing
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
325
Source/Packing/ArevaloRectanglePacker.cs
Normal file
325
Source/Packing/ArevaloRectanglePacker.cs
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Packing {
|
||||||
|
|
||||||
|
/// <summary>Rectangle packer using an algorithm by Javier Arevalo</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <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
|
||||||
|
/// in a bitmapped font, lightmaps for a 3D engine, and I guess other situations as
|
||||||
|
/// well.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The idea of this algorithm is that, as we add rectangles, we can pre-select
|
||||||
|
/// "interesting" places where we can try to add the next rectangles. For optimal
|
||||||
|
/// results, the rectangles should be added in order. I initially tried using area
|
||||||
|
/// as a sorting criteria, but it didn't work well with very tall or very flat
|
||||||
|
/// rectangles. I then tried using the longest dimension as a selector, and it
|
||||||
|
/// worked much better. So much for intuition...
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// These "interesting" places are just to the right and just below the currently
|
||||||
|
/// added rectangle. The first rectangle, obviously, goes at the top left, the next
|
||||||
|
/// one would go either to the right or below this one, and so on. It is a weird way
|
||||||
|
/// to do it, but it seems to work very nicely.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The way we search here is fairly brute-force, the fact being that for most off-
|
||||||
|
/// line purposes the performance seems more than adequate. I have generated a
|
||||||
|
/// japanese font with around 8500 characters and all the time was spent generating
|
||||||
|
/// the bitmaps.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Also, for all we care, we could grow the parent rectangle in a different way
|
||||||
|
/// than power of two. It just happens that power of 2 is very convenient for
|
||||||
|
/// graphics hardware textures.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// I'd be interested in hearing of other approaches to this problem. Make sure
|
||||||
|
/// to post them on http://www.flipcode.com
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public class ArevaloRectanglePacker : RectanglePacker {
|
||||||
|
|
||||||
|
#region class AnchorRankComparer
|
||||||
|
|
||||||
|
/// <summary>Compares the 'rank' of anchoring points</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Anchoring points are potential locations for the placement of new rectangles.
|
||||||
|
/// Each time a rectangle is inserted, an anchor point is generated on its upper
|
||||||
|
/// right end and another one at its lower left end. The anchor points are kept
|
||||||
|
/// in a list that is ordered by their closeness to the upper left corner of the
|
||||||
|
/// packing area (their 'rank') so the packer favors positions that are closer to
|
||||||
|
/// the upper left for new rectangles.
|
||||||
|
/// </remarks>
|
||||||
|
private class AnchorRankComparer : IComparer<Point> {
|
||||||
|
|
||||||
|
/// <summary>Provides a default instance for the anchor rank comparer</summary>
|
||||||
|
public static AnchorRankComparer Default = new AnchorRankComparer();
|
||||||
|
|
||||||
|
/// <summary>Compares the rank of two anchors against each other</summary>
|
||||||
|
/// <param name="left">Left anchor point that will be compared</param>
|
||||||
|
/// <param name="right">Right anchor point that will be compared</param>
|
||||||
|
/// <returns>The relation of the two anchor point's ranks to each other</returns>
|
||||||
|
public int Compare(Point left, Point right) {
|
||||||
|
return 0;
|
||||||
|
//return Math.Min(left.X, left.Y) - Math.Min(right.X, right.Y);
|
||||||
|
//return (left.X + left.Y) - (right.X + right.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#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) {
|
||||||
|
|
||||||
|
this.packedRectangles = new List<Rectangle>();
|
||||||
|
this.anchors = new List<Point>();
|
||||||
|
this.anchors.Add(new Point(0, 0));
|
||||||
|
|
||||||
|
this.actualPackingAreaWidth = 1;
|
||||||
|
this.actualPackingAreaHeight = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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(
|
||||||
|
int rectangleWidth, int rectangleHeight, out Point placement
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Try to find an anchor where the rectangle fits in, enlarging the packing
|
||||||
|
// area and repeating the search recursively until it fits or the
|
||||||
|
// maximum allowed size is exceeded.
|
||||||
|
int anchorIndex = selectAnchorRecursive(
|
||||||
|
rectangleWidth, rectangleHeight,
|
||||||
|
this.actualPackingAreaWidth, this.actualPackingAreaHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// No anchor could be found at which the rectangle did fit in
|
||||||
|
if(anchorIndex == -1) {
|
||||||
|
placement = Point.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
placement = this.anchors[anchorIndex];
|
||||||
|
|
||||||
|
// Remove the used anchor and add new anchors at the upper right and lower left
|
||||||
|
// positions of the new rectangle
|
||||||
|
this.anchors.RemoveAt(anchorIndex);
|
||||||
|
this.anchors.Add(new Point(placement.X + rectangleWidth, placement.Y));
|
||||||
|
this.anchors.Add(new Point(placement.X, placement.Y + rectangleHeight));
|
||||||
|
|
||||||
|
this.packedRectangles.Add(
|
||||||
|
new Rectangle(placement.X, placement.Y, rectangleWidth, rectangleHeight)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches for a free anchor and enlarges the packing area if none can be found
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rectangleWidth">Width of the rectangle to be placed</param>
|
||||||
|
/// <param name="rectangleHeight">Height of the rectangle to be placed</param>
|
||||||
|
/// <param name="packingAreaWidth">Total width of the packing area</param>
|
||||||
|
/// <param name="packingAreaHeight">Total height of the packing area</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Index of the anchor the rectangle is to be placed at or -1 if the rectangle
|
||||||
|
/// does not fit in the packing area anymore
|
||||||
|
/// </returns>
|
||||||
|
private int selectAnchorRecursive(
|
||||||
|
int rectangleWidth, int rectangleHeight,
|
||||||
|
int packingAreaWidth, int packingAreaHeight
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Try to locate an anchor point where the rectangle fits in
|
||||||
|
int freeAnchorIndex = findFirstFreeAnchor(
|
||||||
|
rectangleWidth, rectangleHeight, packingAreaWidth, packingAreaHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// If a the rectangle fits without resizing packing area (any further in case
|
||||||
|
// of a recursive call), take over the new packing area size and return the
|
||||||
|
// anchor at which the rectangle can be placed.
|
||||||
|
if(freeAnchorIndex != -1) {
|
||||||
|
this.actualPackingAreaWidth = packingAreaWidth;
|
||||||
|
this.actualPackingAreaHeight = packingAreaHeight;
|
||||||
|
|
||||||
|
return freeAnchorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Try to enlarge the smaller of the two dimensions first (unless the smaller
|
||||||
|
// dimension is already at its maximum size)
|
||||||
|
if(
|
||||||
|
(
|
||||||
|
(packingAreaHeight < packingAreaWidth) || !canEnlargeWidth
|
||||||
|
) && canEnlargeHeight
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Try to double the height of the packing area
|
||||||
|
return selectAnchorRecursive(
|
||||||
|
rectangleWidth, rectangleHeight,
|
||||||
|
packingAreaWidth, Math.Min(packingAreaHeight * 2, MaxPackingAreaHeight)
|
||||||
|
);
|
||||||
|
|
||||||
|
} else if(canEnlargeWidth) {
|
||||||
|
|
||||||
|
// Try to double the width of the packing area
|
||||||
|
return selectAnchorRecursive(
|
||||||
|
rectangleWidth, rectangleHeight,
|
||||||
|
Math.Min(packingAreaWidth * 2, MaxPackingAreaWidth), packingAreaHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Both dimensions are at the maximum sizes and the rectangle still
|
||||||
|
// didn't fit. We give up!
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Locates the first free anchor at which the rectangle fits</summary>
|
||||||
|
/// <param name="rectangleWidth">Width of the rectangle to be placed</param>
|
||||||
|
/// <param name="rectangleHeight">Height of the rectangle to be placed</param>
|
||||||
|
/// <param name="packingAreaWidth">Total width of the packing area</param>
|
||||||
|
/// <param name="packingAreaHeight">Total height of the packing area</param>
|
||||||
|
/// <returns>The index of the first free anchor or -1 if none is found</returns>
|
||||||
|
private int findFirstFreeAnchor(
|
||||||
|
int rectangleWidth, int rectangleHeight,
|
||||||
|
int packingAreaWidth, int packingAreaHeight
|
||||||
|
) {
|
||||||
|
Rectangle potentialLocation = new Rectangle(
|
||||||
|
0, 0, rectangleWidth, rectangleHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// Walk over all anchors (which are ordered by their distance to the
|
||||||
|
// upper left corner of the packing area) until one is discovered that
|
||||||
|
// can house the new rectangle.
|
||||||
|
for(int index = 0; index < this.anchors.Count; ++index) {
|
||||||
|
potentialLocation.X = this.anchors[index].X;
|
||||||
|
potentialLocation.Y = this.anchors[index].Y;
|
||||||
|
|
||||||
|
// See if the rectangle would fit in at this anchor point
|
||||||
|
if(isFree(ref potentialLocation, packingAreaWidth, packingAreaHeight))
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No anchor points were found where the rectangle would fit in
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the rectangle can be placed in the packing area
|
||||||
|
/// at its current location.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rectangle">Rectangle whose position to check</param>
|
||||||
|
/// <param name="packingAreaWidth">Total width of the packing area</param>
|
||||||
|
/// <param name="packingAreaHeight">Total height of the packing area</param>
|
||||||
|
/// <returns>True if the rectangle can be placed at its current position</returns>
|
||||||
|
private bool isFree(
|
||||||
|
ref Rectangle rectangle, int packingAreaWidth, int packingAreaHeight
|
||||||
|
) {
|
||||||
|
|
||||||
|
bool leavesPackingArea =
|
||||||
|
(rectangle.X < 0) ||
|
||||||
|
(rectangle.Y < 0) ||
|
||||||
|
(rectangle.Right >= packingAreaWidth) ||
|
||||||
|
(rectangle.Bottom >= packingAreaHeight);
|
||||||
|
|
||||||
|
// If the rectangle is partially or completely outside of the packing
|
||||||
|
// area, it can't be placed at its current location
|
||||||
|
if(leavesPackingArea)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Brute-force search whether the rectangle touches any of the other
|
||||||
|
// rectangles already in the packing area
|
||||||
|
for(int index = 0; index < this.packedRectangles.Count; ++index) {
|
||||||
|
|
||||||
|
if(this.packedRectangles[index].Intersects(rectangle))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success! The rectangle is inside the packing area and doesn't overlap
|
||||||
|
// with any other rectangles that have already been packed.
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Inserts a new anchor point into the anchor list</summary>
|
||||||
|
/// <param name="anchor">Anchor point that will be inserted</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method tries to keep the anchor list ordered by ranking the anchors
|
||||||
|
/// depending on the distance from the top left corner in the packing area.
|
||||||
|
/// </remarks>
|
||||||
|
private void insertAnchor(ref Point anchor) {
|
||||||
|
|
||||||
|
// Find out where to insert the new anchor based on its rank (which is
|
||||||
|
// calculated based on the anchor's distance to the top left corner of
|
||||||
|
// the packing area).
|
||||||
|
//
|
||||||
|
// From MSDN on BinarySearch():
|
||||||
|
// "If the List does not contain the specified value, the method returns
|
||||||
|
// a negative integer. You can apply the bitwise complement operation (~) to
|
||||||
|
// this negative integer to get the index of the first element that is
|
||||||
|
// larger than the search value."
|
||||||
|
int insertIndex = this.anchors.BinarySearch(anchor, AnchorRankComparer.Default);
|
||||||
|
if(insertIndex < 0)
|
||||||
|
insertIndex = ~insertIndex;
|
||||||
|
|
||||||
|
// Insert the anchor at the index matching its rank
|
||||||
|
this.anchors.Insert(insertIndex, anchor);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Current width of the packing area</summary>
|
||||||
|
private int actualPackingAreaWidth;
|
||||||
|
/// <summary>Current height of the packing area</summary>
|
||||||
|
private int actualPackingAreaHeight;
|
||||||
|
/// <summary>Rectangles contained in the packing area</summary>
|
||||||
|
private List<Rectangle> packedRectangles;
|
||||||
|
/// <summary>Anchoring points where new rectangles can potentially be placed</summary>
|
||||||
|
private List<Point> anchors;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Packing
|
47
Source/Packing/CygonRectanglePacker.Test.cs
Normal file
47
Source/Packing/CygonRectanglePacker.Test.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Packing {
|
||||||
|
|
||||||
|
/// <summary>Unit test for the arevalo rectangle packer class</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class CygonRectanglePackerTest : RectanglePackerTest {
|
||||||
|
|
||||||
|
/// <summary>Tests the packer's efficiency using a deterministic benchmark</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSpaceEfficiency() {
|
||||||
|
float efficiency = calculateEfficiency(new CygonRectanglePacker(70, 70));
|
||||||
|
|
||||||
|
Assert.GreaterOrEqual(efficiency, 0.75, "Packer achieves 75% efficiency");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Packing
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
92
Source/Packing/CygonRectanglePacker.cs
Normal file
92
Source/Packing/CygonRectanglePacker.cs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Packing {
|
||||||
|
|
||||||
|
/// <summary>Simplified packer for rectangles which don't vary greatly in size</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is a highly performant packer that sacrifices space efficiency for
|
||||||
|
/// low memory usage and runtime performance. It achieves good results with
|
||||||
|
/// near-uniform sized rectangles but will waste lots of space with rectangles
|
||||||
|
/// of varying dimensions.
|
||||||
|
/// </remarks>
|
||||||
|
public class CygonRectanglePacker : 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 CygonRectanglePacker(int maxPackingAreaWidth, int maxPackingAreaHeight)
|
||||||
|
: base(maxPackingAreaWidth, maxPackingAreaHeight) { }
|
||||||
|
|
||||||
|
/// <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(
|
||||||
|
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)
|
||||||
|
) {
|
||||||
|
placement = Point.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have to start a new line ?
|
||||||
|
if(this.column + rectangleWidth > MaxPackingAreaWidth) {
|
||||||
|
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) {
|
||||||
|
placement = Point.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rectangle appears to fit at the current location
|
||||||
|
placement = new Point(this.column, this.currentLine);
|
||||||
|
|
||||||
|
this.column += rectangleWidth; // Can be larger than cache width till next run
|
||||||
|
if(rectangleHeight > this.lineHeight)
|
||||||
|
this.lineHeight = rectangleHeight;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Current packing line</summary>
|
||||||
|
private int currentLine;
|
||||||
|
/// <summary>Height of the current packing line</summary>
|
||||||
|
private int lineHeight;
|
||||||
|
/// <summary>Current column in the current packing line</summary>
|
||||||
|
private int column;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Packing
|
61
Source/Packing/RectanglePacker.Test.cs
Normal file
61
Source/Packing/RectanglePacker.Test.cs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Packing {
|
||||||
|
|
||||||
|
/// <summary>Base class for unit testing the rectangle packers</summary>
|
||||||
|
public abstract class RectanglePackerTest {
|
||||||
|
|
||||||
|
/// <summary>Determines the efficiency of a packer with a packing area of 70x70</summary>
|
||||||
|
/// <param name="packer">Packer with a packing area of 70x70 units</param>
|
||||||
|
/// <returns>The efficiency factor of the packer</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// A perfect packer would achieve an efficiency rating of 1.0. This is
|
||||||
|
/// impossible however since the 24 squares cannot all be packed into
|
||||||
|
/// the 70x70 square with no overlap (Bitner & Reingold 1975). The closer
|
||||||
|
/// the efficiency rating is to 1.0, the better, with 0.99 being the
|
||||||
|
/// mathematically best rating achievable.
|
||||||
|
/// </remarks>
|
||||||
|
public float calculateEfficiency(RectanglePacker packer) {
|
||||||
|
// If we take a 1x1 square, a 2x2 square, etc. up to a 24x24 square,
|
||||||
|
// the sum of the areas of these squares is 4900, which is 70². This
|
||||||
|
// is the only nontrivial sum of consecutive squares starting with
|
||||||
|
// one which is a perfect square (Watson 1918).
|
||||||
|
int areaCovered = 0;
|
||||||
|
|
||||||
|
for(int size = 24; size >= 1; --size) {
|
||||||
|
Point placement;
|
||||||
|
|
||||||
|
if(packer.TryAllocate(size, size, out placement))
|
||||||
|
areaCovered += size * size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float)areaCovered / 4900.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Packing
|
88
Source/Packing/RectanglePacker.cs
Normal file
88
Source/Packing/RectanglePacker.cs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Packing {
|
||||||
|
|
||||||
|
/// <summary>Base class for rectangle packing algorithms</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// By uniting all rectangle packers under this common base class, you can
|
||||||
|
/// easily switch between different algorithms to find the most efficient or
|
||||||
|
/// performant one for a given job.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// An almost exhaustive list of rectangle packers 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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) {
|
||||||
|
Point point;
|
||||||
|
|
||||||
|
if(!TryAllocate(rectangleWidth, rectangleHeight, out point))
|
||||||
|
throw new Exception("Rectangle does not fit in packing area");
|
||||||
|
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 abstract bool TryAllocate(
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Maximum height the packing area is allowed to have</summary>
|
||||||
|
protected int MaxPackingAreaHeight {
|
||||||
|
get { return this.maxPackingAreaHeight; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Maximum allowed width of the packing area</summary>
|
||||||
|
private int maxPackingAreaWidth;
|
||||||
|
/// <summary>Maximum allowed height of the packing area</summary>
|
||||||
|
private int maxPackingAreaHeight;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Packing
|
|
@ -1,3 +1,22 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
|
@ -1,3 +1,22 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
|
@ -1,3 +1,22 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2007 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user