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