diff --git a/Source/Packing/ArevaloRectanglePacker.Test.cs b/Source/Packing/ArevaloRectanglePacker.Test.cs index 5b66cf6..eb18bda 100644 --- a/Source/Packing/ArevaloRectanglePacker.Test.cs +++ b/Source/Packing/ArevaloRectanglePacker.Test.cs @@ -36,8 +36,8 @@ namespace Nuclex.Support.Packing { [Test] public void TestSpaceEfficiency() { float efficiency = CalculateEfficiency(new ArevaloRectanglePacker(70, 70)); - - Assert.GreaterOrEqual(efficiency, 0.75, "Packer achieves 75% efficiency"); + + Assert.GreaterOrEqual(efficiency, 0.75f, "Packer achieves 75% efficiency"); } /// Tests the packer's stability by running a complete benchmark diff --git a/Source/Packing/ArevaloRectanglePacker.cs b/Source/Packing/ArevaloRectanglePacker.cs index 21ee988..9bfeed3 100644 --- a/Source/Packing/ArevaloRectanglePacker.cs +++ b/Source/Packing/ArevaloRectanglePacker.cs @@ -147,7 +147,7 @@ namespace Nuclex.Support.Packing { // positions of the new rectangle { // The anchor is only removed if the placement optimization didn't - // move the rectangle so far that the anchor isn't used at all + // move the rectangle so far that the anchor isn't blocked anymore bool blocksAnchor = ((placement.X + rectangleWidth) > this.anchors[anchorIndex].X) && ((placement.Y + rectangleHeight) > this.anchors[anchorIndex].Y); @@ -165,8 +165,8 @@ namespace Nuclex.Support.Packing { new Rectangle(placement.X, placement.Y, rectangleWidth, rectangleHeight) ); - return true; - + return true; + } /// @@ -209,7 +209,8 @@ namespace Nuclex.Support.Packing { } /// - /// Searches for a free anchor and enlarges the packing area if none can be found + /// Searches for a free anchor and recursively enlarges the packing area + /// if none can be found. /// /// Width of the rectangle to be placed /// Height of the rectangle to be placed @@ -217,7 +218,7 @@ namespace Nuclex.Support.Packing { /// Height of the tested packing area /// /// Index of the anchor the rectangle is to be placed at or -1 if the rectangle - /// does not fit in the packing area anymore + /// does not fit in the packing area anymore. /// private int selectAnchorRecursive( int rectangleWidth, int rectangleHeight, @@ -248,13 +249,13 @@ namespace Nuclex.Support.Packing { // any further in its width and in its height bool canEnlargeWidth = (testedPackingAreaWidth < PackingAreaWidth); bool canEnlargeHeight = (testedPackingAreaHeight < PackingAreaHeight); - + // Try to enlarge the smaller of the two dimensions first (unless the smaller // dimension is already at its maximum size) if( canEnlargeHeight && ( (testedPackingAreaHeight < testedPackingAreaWidth) || !canEnlargeWidth - ) + ) ) { // Try to double the height of the packing area @@ -284,12 +285,12 @@ namespace Nuclex.Support.Packing { /// Locates the first free anchor at which the rectangle fits /// Width of the rectangle to be placed /// Height of the rectangle to be placed - /// Total width of the packing area - /// Total height of the packing area + /// Total width of the packing area + /// Total height of the packing area /// The index of the first free anchor or -1 if none is found private int findFirstFreeAnchor( int rectangleWidth, int rectangleHeight, - int packingAreaWidth, int packingAreaHeight + int testedPackingAreaWidth, int testedPackingAreaHeight ) { Rectangle potentialLocation = new Rectangle( 0, 0, rectangleWidth, rectangleHeight @@ -303,7 +304,7 @@ namespace Nuclex.Support.Packing { potentialLocation.Y = this.anchors[index].Y; // See if the rectangle would fit in at this anchor point - if(isFree(ref potentialLocation, packingAreaWidth, packingAreaHeight)) + if(isFree(ref potentialLocation, testedPackingAreaWidth, testedPackingAreaHeight)) return index; } @@ -316,11 +317,11 @@ namespace Nuclex.Support.Packing { /// at its current location. /// /// Rectangle whose position to check - /// Total width of the packing area - /// Total height of the packing area + /// Total width of the packing area + /// Total height of the packing area /// True if the rectangle can be placed at its current position private bool isFree( - ref Rectangle rectangle, int packingAreaWidth, int packingAreaHeight + ref Rectangle rectangle, int testedPackingAreaWidth, int testedPackingAreaHeight ) { // If the rectangle is partially or completely outside of the packing @@ -328,8 +329,8 @@ namespace Nuclex.Support.Packing { bool leavesPackingArea = (rectangle.X < 0) || (rectangle.Y < 0) || - (rectangle.Right >= packingAreaWidth) || - (rectangle.Bottom >= packingAreaHeight); + (rectangle.Right >= testedPackingAreaWidth) || + (rectangle.Bottom >= testedPackingAreaHeight); if(leavesPackingArea) return false; diff --git a/Source/Packing/CygonRectanglePacker.cs b/Source/Packing/CygonRectanglePacker.cs index eee6dc9..0acdadd 100644 --- a/Source/Packing/CygonRectanglePacker.cs +++ b/Source/Packing/CygonRectanglePacker.cs @@ -28,15 +28,20 @@ namespace Nuclex.Support.Packing { /// /// /// 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 :) + /// I'm quite sure I'm not the first one to come up with it :) /// /// - /// 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. + /// The algorithm always places rectangles as low as possible in the packing + /// area. 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 can have + /// lowest overall height without overlapping any other rectangles. + /// + /// + /// To quickly discover these locations, the packer uses a sophisticated + /// data structure that stores the upper silhouette of the packing area. When + /// a new rectangle needs to be added, only the silouette edges need to be + /// analyzed to find the position where the rectangle would achieve the lowest + /// placement possible in the packing area. /// /// public class CygonRectanglePacker : RectanglePacker { @@ -91,7 +96,11 @@ namespace Nuclex.Support.Packing { return false; } - bool fits = findBestPosition(rectangleWidth, rectangleHeight, out placement); + // Determine the placement for the new rectangle + bool fits = findBestPlacement(rectangleWidth, rectangleHeight, out placement); + + // If a place for the rectangle could be found, update the height slice table to + // mark the region of the rectangle as being taken. if(fits) integrateRectangle(placement.X, rectangleWidth, placement.Y + rectangleHeight); @@ -101,8 +110,8 @@ namespace Nuclex.Support.Packing { /// Finds the best position for a rectangle of the given width /// Width of the rectangle to find a position for /// Height of the rectangle to find a position for - /// The best position for a rectangle with the specified width - private bool findBestPosition( + /// The best position for a rectangle of the specified dimensions + private bool findBestPlacement( int rectangleWidth, int rectangleHeight, out Point placement ) { @@ -136,6 +145,7 @@ namespace Nuclex.Support.Packing { if(this.heightSlices[index].Y > highest) highest = this.heightSlices[index].Y; + // Only process this position if it doesn't leave the packing area if((highest + rectangleHeight < PackingAreaHeight)) { int score = highest; @@ -151,7 +161,7 @@ namespace Nuclex.Support.Packing { if(leftSliceIndex >= this.heightSlices.Count) break; - // Advance the ending slice until we're on the right slice again, given the new + // Advance the ending slice until we're on the proper slice again, given the new // starting position of the rectangle. int rightRectangleEnd = this.heightSlices[leftSliceIndex].X + rectangleWidth; for(; rightSliceIndex <= this.heightSlices.Count; ++rightSliceIndex) { @@ -171,10 +181,11 @@ namespace Nuclex.Support.Packing { if(rightSliceIndex > this.heightSlices.Count) break; - } + } // while rightSliceIndex <= this.heightSlices.Count - // 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 the best placement we found for this rectangle. If the rectangle + // didn't fit anywhere, the slice index will still have its initialization value + // of -1 and we can report that no placement could be found. if(bestSliceIndex == -1) { placement = Point.Zero; return false; @@ -258,9 +269,9 @@ namespace Nuclex.Support.Packing { if(right < PackingAreaWidth) this.heightSlices.Insert(startSlice, new Point(right, returnHeight)); - } + } // if endSlice > 0 - } + } // if startSlice >= this.heightSlices.Count } diff --git a/Source/Packing/RectanglePacker.Test.cs b/Source/Packing/RectanglePacker.Test.cs index fa6db4b..2cf99ef 100644 --- a/Source/Packing/RectanglePacker.Test.cs +++ b/Source/Packing/RectanglePacker.Test.cs @@ -80,7 +80,7 @@ namespace Nuclex.Support.Packing { RectanglePacker packer = buildPacker(); // Try to cramp as many rectangles into the packing area as possible - for(;; ++rectanglesPacked) { + for(; ; ++rectanglesPacked) { Point placement; int width = dimensionGenerator.Next(16, 64); diff --git a/Source/Packing/SimpleRectanglePacker.Test.cs b/Source/Packing/SimpleRectanglePacker.Test.cs index ee028c4..9784e53 100644 --- a/Source/Packing/SimpleRectanglePacker.Test.cs +++ b/Source/Packing/SimpleRectanglePacker.Test.cs @@ -36,8 +36,8 @@ namespace Nuclex.Support.Packing { [Test] public void TestSpaceEfficiency() { float efficiency = CalculateEfficiency(new SimpleRectanglePacker(70, 70)); - - Assert.GreaterOrEqual(efficiency, 0.75, "Packer achieves 75% efficiency"); + + Assert.GreaterOrEqual(efficiency, 0.75f, "Packer achieves 75% efficiency"); } /// Tests the packer's stability by running a complete benchmark