#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