From 4fd0680ae78fe96d4247b182cff25eeb4ba44df5 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Sun, 20 May 2007 21:03:21 +0000 Subject: [PATCH] New "Cygon" Packer is working and promptly achieved a new space efficiency record git-svn-id: file:///srv/devel/repo-conversion/nusu@22 d2e56fa2-650e-0410-a79f-9358c0239efd --- Source/Packing/CygonRectanglePacker.cs | 124 ++++++++++++++----------- Source/Packing/RectanglePacker.Test.cs | 2 - 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/Source/Packing/CygonRectanglePacker.cs b/Source/Packing/CygonRectanglePacker.cs index 1c6cc67..cd43084 100644 --- a/Source/Packing/CygonRectanglePacker.cs +++ b/Source/Packing/CygonRectanglePacker.cs @@ -103,76 +103,95 @@ namespace Nuclex.Support.Packing { /// Height of the rectangle to find a position for /// The best position for a rectangle with the specified width private int findBestPosition(int rectangleWidth, int rectangleHeight) { + + // Index and score of the best slice we could find for the rectangle + int bestSliceIndex = -1; + int bestScore = MaxPackingAreaWidth * MaxPackingAreaHeight; // 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 + // placement of the rectangle. int leftSliceIndex = 0; + + // Determine the slice in which the right end of the rectangle is located int rightSliceIndex = this.heightSlices.BinarySearch( new Point(rectangleWidth, 0), SliceStartComparer.Default ); if(rightSliceIndex < 0) rightSliceIndex = ~rightSliceIndex; - int leastWastedSliceIndex = -1; - int leastWastedArea = MaxPackingAreaWidth * MaxPackingAreaHeight; - while(rightSliceIndex <= this.heightSlices.Count) { - // final time (this is the special case where the rectangle is attempted - // to be placed at the rightmost end of the packing area) - - /* - // Determine the highest slice at this position. We cannot put the rectangle - // any lower than this without colliding into other rectangles - int highest = this.heightSlices[leftSliceIndex].Y; - for(int index = leftSliceIndex + 1; index < rightSliceIndex; ++index) - highest = Math.Max(highest, this.heightSlices[index].Y); - // Calculate the amount of space that would go to waste if the rectangle - // would be placed at this location - int wastedArea = 0; - for(int index = leftSliceIndex; index < rightSliceIndex - 1; ++index) { - int sliceWidth = this.heightSlices[index + 1].X - this.heightSlices[index].X; - wastedArea += (highest - this.heightSlices[index].Y) * sliceWidth; - } - wastedArea += - (highest - this.heightSlices[rightSliceIndex - 1].Y) * - ( - (this.heightSlices[leftSliceIndex].X + rectangleWidth) - - this.heightSlices[rightSliceIndex - 1].X - ); + // Determine the highest slice within the slices covered by the rectangle at + // its current placement. We cannot put the rectangle any lower than this without + // overlapping the other rectangles. + int highest = this.heightSlices[leftSliceIndex].Y; + for(int index = leftSliceIndex + 1; index < rightSliceIndex; ++index) + if(this.heightSlices[index].Y > highest) + highest = this.heightSlices[index].Y; - // If this beats the current record for the least wasted area, remember this as - // being the best position found so far - if( - (wastedArea < leastWastedArea) && - (this.heightSlices[leftSliceIndex].Y + rectangleHeight < MaxPackingAreaHeight) - ) { - leastWastedArea = wastedArea; - leastWastedSliceIndex = leftSliceIndex; + if((highest + rectangleHeight < MaxPackingAreaHeight)) { + int score = highest; - // No sense looking any further if we found the perfect place! - if(leastWastedArea == 0) - break; - } - */ + // TESTING -------------------------------------------------- + /* + // Calculate the amount of space that would go to waste if the rectangle + // would be placed at this location + int wastedArea = 0; + for(int index = leftSliceIndex; index < rightSliceIndex - 1; ++index) { + int sliceWidth = this.heightSlices[index + 1].X - this.heightSlices[index].X; + wastedArea += (highest - this.heightSlices[index].Y) * sliceWidth; + } + wastedArea += + (highest - this.heightSlices[rightSliceIndex - 1].Y) * + ( + (this.heightSlices[leftSliceIndex].X + rectangleWidth) - + this.heightSlices[rightSliceIndex - 1].X + ); + //score += Math.Sign(wastedArea); + //score += (int)Math.Sqrt((double)wastedArea); + //score = wastedArea; + */ + // TESTING -------------------------------------------------- - // If this already was the loop after the final slice, terminate it now! - if(rightSliceIndex == this.heightSlices.Count) - break; + if(score < bestScore) { + bestSliceIndex = leftSliceIndex; + bestScore = score; + } + } // Advance the starting slice to the next slice start ++leftSliceIndex; - int rightEnd = this.heightSlices[leftSliceIndex].X + rectangleWidth; + if(leftSliceIndex >= this.heightSlices.Count) + break; - // Advance the ending slice to where the rectangle ends now - while(rightEnd > this.heightSlices[rightSliceIndex].X) { - ++rightSliceIndex; - - // If the end is reached, stop shifting and make the outer loop run one final time + // Advance the ending slice until we're on the right slice again, given the new + // starting position of the rectangle. + int rightRectangleEnd = this.heightSlices[leftSliceIndex].X + rectangleWidth; + for(; rightSliceIndex <= this.heightSlices.Count; ++rightSliceIndex) { + int rightSliceStart; if(rightSliceIndex == this.heightSlices.Count) + rightSliceStart = MaxPackingAreaWidth; + else + rightSliceStart = this.heightSlices[rightSliceIndex].X; + + // Is this the slice we're looking for? + if(rightSliceStart > rightRectangleEnd) break; } + + // If we crossed the end of the slice array, the rectangle's right end has left + // the packing area, and thus, our search ends. + if(rightSliceIndex > this.heightSlices.Count) + break; + } - return leastWastedSliceIndex; + // 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; + } /// Integrates a new rectangle into the height slice table @@ -213,8 +232,8 @@ namespace Nuclex.Support.Packing { if(startSlice >= this.heightSlices.Count) { // If the slice ends within the last slice (usual case, unless it has the - // exact same with the packing area has), add another slice to return to the - // original height at the end of the rectangle. + // 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) this.heightSlices.Add(new Point(right, firstSliceOriginalHeight)); @@ -242,9 +261,8 @@ namespace Nuclex.Support.Packing { else returnHeight = this.heightSlices[endSlice - 1].Y; - // Remove all slices covered by the rectangle and began a new slice at - // its end to return to the height the slice in which the rectangle ends - // has had. + // 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) this.heightSlices.Insert(startSlice, new Point(right, returnHeight)); diff --git a/Source/Packing/RectanglePacker.Test.cs b/Source/Packing/RectanglePacker.Test.cs index 7fa13b8..702ecb3 100644 --- a/Source/Packing/RectanglePacker.Test.cs +++ b/Source/Packing/RectanglePacker.Test.cs @@ -54,8 +54,6 @@ namespace Nuclex.Support.Packing { return (float)areaCovered / 4900.0f; } - - } } // namespace Nuclex.Support.Packing