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
This commit is contained in:
Markus Ewald 2007-05-20 21:03:21 +00:00
parent 5756ed94b3
commit 4fd0680ae7
2 changed files with 71 additions and 55 deletions

View File

@ -103,76 +103,95 @@ namespace Nuclex.Support.Packing {
/// <param name="rectangleHeight">Height 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> /// <returns>The best position for a rectangle with the specified width</returns>
private int findBestPosition(int rectangleWidth, int rectangleHeight) { 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; int leftSliceIndex = 0;
// Determine the slice in which the right end of the rectangle is located
int rightSliceIndex = this.heightSlices.BinarySearch( int rightSliceIndex = this.heightSlices.BinarySearch(
new Point(rectangleWidth, 0), SliceStartComparer.Default new Point(rectangleWidth, 0), SliceStartComparer.Default
); );
if(rightSliceIndex < 0) if(rightSliceIndex < 0)
rightSliceIndex = ~rightSliceIndex; rightSliceIndex = ~rightSliceIndex;
int leastWastedSliceIndex = -1;
int leastWastedArea = MaxPackingAreaWidth * MaxPackingAreaHeight;
while(rightSliceIndex <= this.heightSlices.Count) { 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 // Determine the highest slice within the slices covered by the rectangle at
// would be placed at this location // its current placement. We cannot put the rectangle any lower than this without
int wastedArea = 0; // overlapping the other rectangles.
for(int index = leftSliceIndex; index < rightSliceIndex - 1; ++index) { int highest = this.heightSlices[leftSliceIndex].Y;
int sliceWidth = this.heightSlices[index + 1].X - this.heightSlices[index].X; for(int index = leftSliceIndex + 1; index < rightSliceIndex; ++index)
wastedArea += (highest - this.heightSlices[index].Y) * sliceWidth; if(this.heightSlices[index].Y > highest)
} highest = this.heightSlices[index].Y;
wastedArea +=
(highest - this.heightSlices[rightSliceIndex - 1].Y) *
(
(this.heightSlices[leftSliceIndex].X + rectangleWidth) -
this.heightSlices[rightSliceIndex - 1].X
);
// If this beats the current record for the least wasted area, remember this as if((highest + rectangleHeight < MaxPackingAreaHeight)) {
// being the best position found so far int score = highest;
if(
(wastedArea < leastWastedArea) &&
(this.heightSlices[leftSliceIndex].Y + rectangleHeight < MaxPackingAreaHeight)
) {
leastWastedArea = wastedArea;
leastWastedSliceIndex = leftSliceIndex;
// No sense looking any further if we found the perfect place! // TESTING --------------------------------------------------
if(leastWastedArea == 0) /*
break; // 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(score < bestScore) {
if(rightSliceIndex == this.heightSlices.Count) bestSliceIndex = leftSliceIndex;
break; bestScore = score;
}
}
// Advance the starting slice to the next slice start // Advance the starting slice to the next slice start
++leftSliceIndex; ++leftSliceIndex;
int rightEnd = this.heightSlices[leftSliceIndex].X + rectangleWidth; if(leftSliceIndex >= this.heightSlices.Count)
break;
// Advance the ending slice to where the rectangle ends now // Advance the ending slice until we're on the right slice again, given the new
while(rightEnd > this.heightSlices[rightSliceIndex].X) { // starting position of the rectangle.
++rightSliceIndex; int rightRectangleEnd = this.heightSlices[leftSliceIndex].X + rectangleWidth;
for(; rightSliceIndex <= this.heightSlices.Count; ++rightSliceIndex) {
// If the end is reached, stop shifting and make the outer loop run one final time int rightSliceStart;
if(rightSliceIndex == this.heightSlices.Count) 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; 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;
} }
/// <summary>Integrates a new rectangle into the height slice table</summary> /// <summary>Integrates a new rectangle into the height slice table</summary>
@ -213,8 +232,8 @@ namespace Nuclex.Support.Packing {
if(startSlice >= this.heightSlices.Count) { if(startSlice >= this.heightSlices.Count) {
// If the slice ends within the last slice (usual case, unless it has the // 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 // exact same width the packing area has), add another slice to return to
// original height at the end of the rectangle. // the original height at the end of the rectangle.
if(right < MaxPackingAreaWidth) if(right < MaxPackingAreaWidth)
this.heightSlices.Add(new Point(right, firstSliceOriginalHeight)); this.heightSlices.Add(new Point(right, firstSliceOriginalHeight));
@ -242,9 +261,8 @@ namespace Nuclex.Support.Packing {
else else
returnHeight = this.heightSlices[endSlice - 1].Y; returnHeight = this.heightSlices[endSlice - 1].Y;
// Remove all slices covered by the rectangle and began a new slice at // Remove all slices covered by the rectangle and begin a new slice at its end
// its end to return to the height the slice in which the rectangle ends // to return back to the height of the slice on which the rectangle ends.
// has had.
this.heightSlices.RemoveRange(startSlice, endSlice - startSlice); this.heightSlices.RemoveRange(startSlice, endSlice - startSlice);
if(right < MaxPackingAreaWidth) if(right < MaxPackingAreaWidth)
this.heightSlices.Insert(startSlice, new Point(right, returnHeight)); this.heightSlices.Insert(startSlice, new Point(right, returnHeight));

View File

@ -54,8 +54,6 @@ namespace Nuclex.Support.Packing {
return (float)areaCovered / 4900.0f; return (float)areaCovered / 4900.0f;
} }
} }
} // namespace Nuclex.Support.Packing } // namespace Nuclex.Support.Packing