diff --git a/Nuclex.Support (PC).csproj b/Nuclex.Support (PC).csproj
index c9181b2..6232ffb 100644
--- a/Nuclex.Support (PC).csproj
+++ b/Nuclex.Support (PC).csproj
@@ -150,6 +150,15 @@
RectanglePacker.Test
RectanglePacker.cs
+
+ false
+ SimpleRectanglePacker
+
+
+ false
+ SimpleRectanglePacker.Test
+ SimpleRectanglePacker.cs
+
false
BinarySerializer.Test
diff --git a/Source/Packing/ArevaloRectanglePacker.cs b/Source/Packing/ArevaloRectanglePacker.cs
index 5529f39..0f2adcc 100644
--- a/Source/Packing/ArevaloRectanglePacker.cs
+++ b/Source/Packing/ArevaloRectanglePacker.cs
@@ -86,9 +86,8 @@ namespace Nuclex.Support.Packing {
/// Right anchor point that will be compared
/// The relation of the two anchor point's ranks to each other
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);
+ return (left.X + left.Y) - (right.X + right.Y);
}
}
diff --git a/Source/Packing/CygonRectanglePacker.Test.cs b/Source/Packing/CygonRectanglePacker.Test.cs
index bcea2ec..a89c710 100644
--- a/Source/Packing/CygonRectanglePacker.Test.cs
+++ b/Source/Packing/CygonRectanglePacker.Test.cs
@@ -28,7 +28,7 @@ using NUnit.Framework;
namespace Nuclex.Support.Packing {
- /// Unit test for the arevalo rectangle packer class
+ /// Unit test for the cygon rectangle packer class
[TestFixture]
public class CygonRectanglePackerTest : RectanglePackerTest {
diff --git a/Source/Packing/CygonRectanglePacker.cs b/Source/Packing/CygonRectanglePacker.cs
index ffaf234..d70370a 100644
--- a/Source/Packing/CygonRectanglePacker.cs
+++ b/Source/Packing/CygonRectanglePacker.cs
@@ -24,20 +24,41 @@ using Microsoft.Xna.Framework;
namespace Nuclex.Support.Packing {
- /// Simplified packer for rectangles which don't vary greatly in size
- ///
- /// 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.
- ///
+ /// Packer using a custom algorithm by Markus Ewald
public class CygonRectanglePacker : RectanglePacker {
+ #region class SliceStartComparer
+
+ /// Compares the starting position of height slices
+ private class SliceStartComparer : IComparer {
+
+ /// Provides a default instance for the anchor rank comparer
+ public static SliceStartComparer Default = new SliceStartComparer();
+
+ /// Compares the starting position of two height slices
+ /// Left slice start that will be compared
+ /// Right slice start that will be compared
+ /// The relation of the two slice starts ranks to each other
+ public int Compare(Point left, Point right) {
+ return left.X - right.X;
+ }
+
+ }
+
+ #endregion
+
/// Initializes a new rectangle packer
/// Maximum width of the packing area
/// Maximum height of the packing area
public CygonRectanglePacker(int maxPackingAreaWidth, int maxPackingAreaHeight)
- : base(maxPackingAreaWidth, maxPackingAreaHeight) { }
+ : base(maxPackingAreaWidth, maxPackingAreaHeight) {
+
+ this.heightSlices = new List();
+
+ // At the beginning, the packing area is a single slice of height 0
+ this.heightSlices.Add(new Point(0, 0));
+
+ }
/// Tries to allocate space for a rectangle in the packing area
/// Width of the rectangle to allocate
@@ -47,45 +68,102 @@ namespace Nuclex.Support.Packing {
public override bool TryAllocate(
int rectangleWidth, int rectangleHeight, out Point placement
) {
+ integrateRectangle(0, 1, 5);
+ integrateRectangle(20, 5, 30);
+ integrateRectangle(10, 10, 50);
+ integrateRectangle(10, 15, 25);
+ integrateRectangle(35, 20, 25);
+ integrateRectangle(40, 25, 15);
- // 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;
+ placement = Point.Zero;
+ return false;
}
- /// Current packing line
- private int currentLine;
- /// Height of the current packing line
- private int lineHeight;
- /// Current column in the current packing line
- private int column;
+ /// Integrates a new rectangle into the height slice table
+ /// Position of the rectangle's left side
+ /// Position of the rectangle's lower side
+ /// Width of the rectangle
+ private void integrateRectangle(int left, int bottom, int width) {
+
+ // Find the first slice that is touched by the rectangle
+ int startSlice = this.heightSlices.BinarySearch(
+ new Point(left, 0), SliceStartComparer.Default
+ );
+ int firstSliceOriginalHeight;
+
+ // Did we score a direct hit on an existing slice start?
+ if(startSlice >= 0) {
+
+ // We scored a direct hit, so we can replace the slice we have hit
+ firstSliceOriginalHeight = this.heightSlices[startSlice].Y;
+ this.heightSlices[startSlice] = new Point(left, bottom);
+ ++startSlice;
+
+ } else { // No direct hit, slice starts inside another slice
+
+ // Add a new slice after the slice in which we start
+ startSlice = ~startSlice;
+ firstSliceOriginalHeight = this.heightSlices[startSlice - 1].Y;
+ this.heightSlices.Insert(startSlice, new Point(left, bottom));
+
+ }
+
+ // Special case, the rectangle was on the last slice, so we cannot
+ // use the start slice + 1 as start index for the binary search
+ if(startSlice >= this.heightSlices.Count) {
+ } else {
+ int right = left + width;
+
+ int endSlice = this.heightSlices.BinarySearch(
+ startSlice, this.heightSlices.Count - startSlice,
+ new Point(right, 0), SliceStartComparer.Default
+ );
+ }
+
+ //this.heightSlices.RemoveRange(startSlice, endSlice - startSlice);
+/*
+ int nextSlice = firstSlice + 1;
+ bool isLastSlice = (nextSlice >= this.heightSlices.Count);
+
+*/
+/*
+
+
+ int nextSlice = firstSlice + 1;
+ bool isLastSlice = (nextSlice >= this.heightSlices.Count);
+
+ //
+ bool endsInFirstSlice;
+ if(isLastSlice)
+ endsInFirstSlice = right < MaxPackingAreaWidth;
+ else
+ endsInFirstSlice = right < this.heightSlices[nextSlice].X;
+
+ if(endsInFirstSlice) {
+ this.heightSlices.Insert(
+ nextSlice, new Point(right, this.heightSlices[firstSlice].Y)
+ );
+ this.heightSlices[firstSlice] = new Point(left, bottom);
+ return;
+ } else { // Integrated rect continues beyond the discovered slice
+ this.heightSlices[firstSlice] = new Point(left, bottom);
+ }
+
+ } else {
+
+ firstSlice = ~firstSlice;
+
+ //firstSliceOriginalHeight = this.heightSlices[firstSlice].Y;
+
+ this.heightSlices.Insert(firstSlice, new Point(left, bottom));
+ ++firstSlice;
+
+ }
+*/
+ }
+
+ /// Stores the height silhouette of the rectangles
+ private List heightSlices;
}
diff --git a/Source/Packing/SimpleRectanglePacker.Test.cs b/Source/Packing/SimpleRectanglePacker.Test.cs
new file mode 100644
index 0000000..0a2de4a
--- /dev/null
+++ b/Source/Packing/SimpleRectanglePacker.Test.cs
@@ -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 {
+
+ /// Unit test for the simple rectangle packer class
+ [TestFixture]
+ public class SimpleRectanglePackerTest : RectanglePackerTest {
+
+ /// Tests the packer's efficiency using a deterministic benchmark
+ [Test]
+ public void TestSpaceEfficiency() {
+ float efficiency = calculateEfficiency(new SimpleRectanglePacker(70, 70));
+
+ Assert.GreaterOrEqual(efficiency, 0.75, "Packer achieves 75% efficiency");
+ }
+
+ }
+
+} // namespace Nuclex.Support.Packing
+
+#endif // UNITTEST
diff --git a/Source/Packing/SimpleRectanglePacker.cs b/Source/Packing/SimpleRectanglePacker.cs
new file mode 100644
index 0000000..5e98aa6
--- /dev/null
+++ b/Source/Packing/SimpleRectanglePacker.cs
@@ -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 {
+
+ /// Simplified packer for rectangles which don't vary greatly in size
+ ///
+ /// 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.
+ ///
+ public class SimpleRectanglePacker : RectanglePacker {
+
+ /// Initializes a new rectangle packer
+ /// Maximum width of the packing area
+ /// Maximum height of the packing area
+ public SimpleRectanglePacker(int maxPackingAreaWidth, int maxPackingAreaHeight)
+ : base(maxPackingAreaWidth, maxPackingAreaHeight) { }
+
+ /// Tries to allocate space for a rectangle in the packing area
+ /// Width of the rectangle to allocate
+ /// Height of the rectangle to allocate
+ /// Output parameter receiving the rectangle's placement
+ /// True if space for the rectangle could be allocated
+ 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;
+ }
+
+ /// Current packing line
+ private int currentLine;
+ /// Height of the current packing line
+ private int lineHeight;
+ /// Current column in the current packing line
+ private int column;
+
+ }
+
+} // namespace Nuclex.Support.Packing