diff --git a/Source/Utilities/IPackable.cs b/Source/Utilities/IPackable.cs new file mode 100644 index 00000000..15b04931 --- /dev/null +++ b/Source/Utilities/IPackable.cs @@ -0,0 +1,22 @@ +#region --- License --- +/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos + * See license.txt for license info + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenTK +{ + /// + /// Represents an item that can be packed with the TexturePacker. + /// + /// The type of the packable item. + interface IPackable : IEquatable + { + int Width { get; } + int Height { get; } + } +} diff --git a/Source/Utilities/TexturePacker.cs b/Source/Utilities/TexturePacker.cs new file mode 100644 index 00000000..ab7492f6 --- /dev/null +++ b/Source/Utilities/TexturePacker.cs @@ -0,0 +1,192 @@ +#region --- License --- +/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos + * See license.txt for license info + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Drawing.Imaging; + +namespace OpenTK +{ + class TexturePacker where T : IPackable + { + Node root; + + #region --- Constructors --- + + public TexturePacker(int width, int height) + { + if (width <= 0) + throw new ArgumentOutOfRangeException("width", width, "Must be greater than zero."); + if (height <= 0) + throw new ArgumentOutOfRangeException("height", height, "Must be greater than zero."); + + root = new Node(); + root.Rect = new Rectangle(0, 0, width, width); + } + + #endregion + + #region public Rectangle Add(T item) + + /// + /// Packs the given item into the free space of the TexturePacker. + /// + /// The item to pack. + /// A System.Drawing.Rectangle containing the coordinates of the packed item. + /// Occurs if the item is larger than the available TexturePacker area + /// Occurs if the item already exists in the TexturePacker. + public void Add(T item, out Rectangle rect) + { + if (item.Width > root.Rect.Width || item.Height > root.Rect.Height) + throw new InvalidOperationException("The item is too large for this TexturePacker"); + + Node node; + //if (!items.ContainsKey(item)) + { + node = root.Insert(item); + + // Tree is full and insertion failed: + if (node == null) + throw new InvalidOperationException("There is not enough space to add this item. Consider calling the Clear() method."); + + //items.Add(item, node); + rect = node.Rect; + } + //throw new ArgumentException("The item already exists in the TexturePacker.", "item"); + } + + #endregion + + #region public void Clear() + + /// + /// Discards all packed items. + /// + public void Clear() + { + //items.Clear(); + root.Clear(); + } + + #endregion + + #region public void ChangeSize(int new_width, int new_height) + + /// + /// Changes the dimensions of the TexturePacker surface. + /// + /// The new width of the TexturePacker surface. + /// The new height of the TexturePacker surface. + /// Changing the size of the TexturePacker surface will implicitly call TexturePacker.Clear(). + /// + public void ChangeSize(int new_width, int new_height) + { + throw new NotImplementedException(); + } + + #endregion + + #region Node + + class Node + { + public Node() + { + } + + Node left, right; + Rectangle rect; + int use_count; + + public Rectangle Rect { get { return rect; } set { rect = value; } } + public Node Left { get { return left; } set { left = value; } } + public Node Right { get { return right; } set { right = value; } } + + #region --- Constructor --- + + public bool Leaf + { + get { return left == null && right == null; } + } + + #endregion + + #region public Node Insert(T item) + + public Node Insert(T item) + { + if (!this.Leaf) + { + // Recurse towards left child, and if that fails, towards the right. + Node new_node = left.Insert(item); + return new_node ?? right.Insert(item); + } + else + { + // We have recursed to a leaf. + + // If it is not empty go back. + if (use_count != 0) + return null; + + // If this leaf is too small go back. + if (rect.Width < item.Width || rect.Height < item.Height) + return null; + + // If this leaf is the right size, insert here. + if (rect.Width == item.Width && rect.Height == item.Height) + { + use_count = 1; + return this; + } + + // This leaf is too large, split it up. We'll decide which way to split + // by checking the width and height difference between this rectangle and + // out item's bounding box. If the width difference is larger, we'll split + // horizontaly, else verticaly. + left = new Node(); + right = new Node(); + + int dw = this.rect.Width - item.Width; + int dh = this.rect.Height - item.Height; + + if (dw > dh) + { + left.rect = new Rectangle(rect.Left, rect.Top, item.Width, rect.Height); + right.rect = new Rectangle(rect.Left + item.Width, rect.Top, rect.Width - item.Width, rect.Height); + } + else + { + left.rect = new Rectangle(rect.Left, rect.Top, rect.Width, item.Height); + right.rect = new Rectangle(rect.Left, rect.Top + item.Height, rect.Width, rect.Height - item.Height); + } + + return left.Insert(item); + } + } + + #endregion + + #region public void Clear() + + public void Clear() + { + if (left != null) + left.Clear(); + if (right != null) + right.Clear(); + + left = right = null; + } + + #endregion + } + + #endregion + } +}