#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 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 System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using Nuclex.Game.Packing;
using Nuclex.Support.Plugins;
using XnaPoint = Microsoft.Xna.Framework.Point;
namespace Nuclex.Game.Demo {
/// Demonstrates the rectangle packing algorithms
public partial class PackingDemoForm : Form {
#region interface IRectanglePackerFactory
/// Interface for a rectangle packer factory
private interface IRectanglePackerFactory {
/// Human readable name of the rectangle packer
string PackerName { get; }
/// Creates a new packer for a packing area of the specified size
/// Width of the packing area
/// Height of the packing area
/// The newly created rectangle packer
RectanglePacker CreatePacker(int packingAreaWidth, int packingAreaHeight);
}
#endregion // interface IRectanglePackerFactory
#region class DynamicRectanglePackerFactory
///
/// Dynamic factory that can be configured to produce any packer by reflection
///
private class DynamicRectanglePackerFactory : IRectanglePackerFactory {
/// Initializes a new dynamic rectangle packer factory
///
/// Type of the rectangle packer that will be produced
///
public DynamicRectanglePackerFactory(Type packerType) {
this.packerType = packerType;
}
/// Determines whether the factory
///
/// Type of the packer whose compatibility will be checked
///
/// True if the packer is compatible for this factory
public static bool IsCompatible(Type packerType) {
return
(!packerType.IsAbstract) &&
typeof(RectanglePacker).IsAssignableFrom(packerType) &&
(getPackerConstructor(packerType) != null);
}
/// Human-readable name of the rectangle packer
public string PackerName {
get {
string typeName = this.packerType.Name;
// Count the number of uppercase characters in the string
int uppercaseCharacterCount = 0;
for(int index = 0; index < typeName.Length; ++index) {
if(char.IsUpper(typeName, index)) {
++uppercaseCharacterCount;
}
}
// Now we can create a string builder with the exact required capacity
StringBuilder nameBuilder = new StringBuilder(
typeName.Length + uppercaseCharacterCount
);
// Feed the type name into the string builder and each time we see an
// uppercase character, prefix it with the space character.
for(int index = 0; index < typeName.Length; ++index) {
if((index != 0) && char.IsUpper(typeName, index)) {
nameBuilder.Append(' ');
}
nameBuilder.Append(typeName[index]);
}
return nameBuilder.ToString();
}
}
///
/// Creates a packer for a packing area of the specified dimensions
///
/// Width of the packing area
/// Height of the packing area
/// The newly creating rectangle packer
public RectanglePacker CreatePacker(int packingAreaWidth, int packingAreaHeight) {
ConstructorInfo constructor = getPackerConstructor(this.packerType);
if(constructor == null) {
throw new InvalidOperationException(
"The selected packing algorithm is not compatible with the factory"
);
}
return (RectanglePacker)constructor.Invoke(
new object[] { packingAreaWidth, packingAreaHeight }
);
}
///
/// Retrieves the constructor of the packer the generic variant of this class
/// has been specialized for
///
///
/// Type of the packer whose constructor will be returned
///
/// The constructor of the provided packer type
private static ConstructorInfo getPackerConstructor(Type packerType) {
return packerType.GetConstructor(
new Type[] { typeof(int), typeof(int) }
);
}
/// Type of the packer this factory instance is constructing
private Type packerType;
}
#endregion // class DynamicRectanglePackerFactory
#region class RectanglePackerEmployer
/// Employs factories for all compatible rectangle packers
private class RectanglePackerEmployer : Employer {
/// Initializes a new rectangle packer employer
public RectanglePackerEmployer() {
this.factories = new List();
}
/// Determines whether the type suites the employer's requirements
/// Type that is checked for employability
/// True if the type can be employed
public override bool CanEmploy(Type type) {
return DynamicRectanglePackerFactory.IsCompatible(type);
}
/// Employs the specified plugin type
/// Type to be employed
public override void Employ(Type type) {
if(!CanEmploy(type)) {
throw new ArgumentException("Not a compatible rectangle packer", "type");
}
this.factories.Add(new DynamicRectanglePackerFactory(type));
}
/// Factories for the employed rectangle packers
public List Factories {
get { return this.factories; }
}
/// Factories for the rectangle packer we have employed
private List factories;
}
#endregion // class RectanglePackerEmployer
/// Initializes a new rectangle packing demonstration form
public PackingDemoForm() {
InitializeComponent();
this.randomNumberGenerator = new Random();
this.employer = new RectanglePackerEmployer();
this.pluginHost = new PluginHost(this.employer);
// Employ our own assembly in order to obtain the default GUI renderers
this.pluginHost.Repository.AddAssembly(typeof(RectanglePacker).Assembly);
initializePackerCombo();
packingViewResized(this.packingViewPicture, EventArgs.Empty);
}
/// Renders the packed rectangles into the packing view picture box
/// Picture box that is being redrawn
///
/// Contains the graphics interface through which we can draw into the picture box
///
private void paintPackingViewPicture(object sender, PaintEventArgs arguments) {
if(this.packedRectangles == null) {
return;
}
arguments.Graphics.FillRectangles(
Brushes.LightGray, this.packedRectangles.ToArray()
);
arguments.Graphics.DrawRectangles(
Pens.DarkBlue, this.packedRectangles.ToArray()
);
StringFormat format = StringFormat.GenericDefault;
format.Alignment = StringAlignment.Far;
string text = string.Format(
"Rectangles: {0}\nTime: {1} ms",
this.packedRectangles.Count,
(int)this.packingTime.TotalMilliseconds
);
arguments.Graphics.DrawString(
text,
Font,
Brushes.Black,
floatRectangleFromRectangle(this.packingViewPicture.DisplayRectangle),
format
);
}
/// Updates the size label for the packing view
/// Packing view picture box that has been resized
/// Not used
private void packingViewResized(object sender, EventArgs arguments) {
this.areaLabel.Text = string.Format(
"Size: {0} x {1}",
this.packingViewPicture.ClientSize.Width,
this.packingViewPicture.ClientSize.Height
);
}
/// Called when the pack button has been clicked on
/// Button that has been clicked on
/// Not used
private void packButtonClicked(object sender, EventArgs arguments) {
IRectanglePackerFactory factory = this.employer.Factories[
this.packingAlgorithmCombo.SelectedIndex
];
RectanglePacker packer = factory.CreatePacker(
this.packingViewPicture.ClientSize.Width,
this.packingViewPicture.ClientSize.Height
);
List packedRectangles = new List();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
// Place as many random rectangles as possible into the packing area,
// stopping as soon as the packer runs out of space once.
for(; ; ) {
Size dimensions = createRandomDimensions();
// Let the packer find a place for the randomly sized rectangle
XnaPoint placement;
bool result = packer.TryPack(
dimensions.Width, dimensions.Height, out placement
);
// If the packer was unable to find a location at which the rectangle
// could be placed, the packing area is considered full and we will stop
if(!result) {
break;
}
// Add the packed rectangle into our list. We reduce the dimensions by one
// because the .NET drawing routines draw the rectangles with the borders
// inclusive, meaning a 1x1 pixel rectangle consumes 4 pixels when drawn
packedRectangles.Add(
new Rectangle(
placement.X, placement.Y,
dimensions.Width - 1, dimensions.Height - 1
)
);
}
stopwatch.Stop();
// Take over the new packed rectangle list and tell the packing view picture box
// that it needs to be redrawn
this.packedRectangles = packedRectangles;
this.packingTime = stopwatch.Elapsed;
this.packingViewPicture.Invalidate();
}
///
/// Creates random dimensions for a rectangle according to the sizes specified
/// by the user through the dialog controls
///
/// The dimensions of a random rectangle
private Size createRandomDimensions() {
int width = this.randomNumberGenerator.Next(
(int)this.minimumWidthEdit.Value,
(int)this.maximumWidthEdit.Value
);
int height = this.randomNumberGenerator.Next(
(int)this.minimumHeightEdit.Value,
(int)this.maximumHeightEdit.Value
);
return new Size(width, height);
}
/// Initializes a the packer combo box
private void initializePackerCombo() {
this.packingAlgorithmCombo.Items.Clear();
// Add the names packers for which we have factories to the combo box
for(int index = 0; index < this.employer.Factories.Count; ++index) {
this.packingAlgorithmCombo.Items.Add(
this.employer.Factories[index].PackerName
);
}
// If the combo box is non-empty, select the first entry so it isn't in
// an invalid state when the dialog is shown
if(this.employer.Factories.Count > 0) {
this.packingAlgorithmCombo.SelectedIndex = 0;
}
}
/// Converts an integer rectangle into a floating point rectangle
/// Integer rectangle that will be converted
///
/// A floating point rectangle that is equivalent to the integer rectangle
///
private RectangleF floatRectangleFromRectangle(Rectangle rectangle) {
return new RectangleF(
(float)rectangle.X,
(float)rectangle.Y,
(float)rectangle.Width,
(float)rectangle.Height
);
}
/// Random number generator used to generate the rectangles
private Random randomNumberGenerator;
/// Employer used to find compatible rectangle packers
private RectanglePackerEmployer employer;
/// Hosts any plugins loaded into the process
private PluginHost pluginHost;
/// The packed rectangles from the last packer run
private List packedRectangles;
/// Time needed for the last packing run
private TimeSpan packingTime;
}
} // namespace Nuclex.Game.Demo