From d20f18a4bb5efad73a7c782d96b39f4a1e58f5ed Mon Sep 17 00:00:00 2001
From: Kyle Evans <kevans91@ksu.edu>
Date: Thu, 7 Jan 2016 22:48:25 -0600
Subject: [PATCH 1/6] I missed one last spot where a FreeBSD compilation should
 follow the path of OS X -- <signal.h> needs to be pulled in for signal
 functions

---
 src/posix/sdl/crashcatcher.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/posix/sdl/crashcatcher.c b/src/posix/sdl/crashcatcher.c
index f85713e31..70c9e7b0c 100644
--- a/src/posix/sdl/crashcatcher.c
+++ b/src/posix/sdl/crashcatcher.c
@@ -15,7 +15,7 @@
 #ifndef PR_SET_PTRACER
 #define PR_SET_PTRACER 0x59616d61
 #endif
-#elif defined (__APPLE__)
+#elif defined (__APPLE__) || defined (__FreeBSD__)
 #include <signal.h>
 #endif
 

From cf564c60c214751bbc3b8511f688104ac9f618b2 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@zdoom.fake>
Date: Sat, 9 Jan 2016 01:23:00 +0100
Subject: [PATCH 2/6] - simplified setup of polyobjects defined from explicit
 lines

The old method had a problem with missing order numbers and aborted the level load and made many assumptions that no longer apply with BSP based polyobject rendering.
---
 src/po_man.cpp | 64 ++++++++++++++------------------------------------
 1 file changed, 17 insertions(+), 47 deletions(-)

diff --git a/src/po_man.cpp b/src/po_man.cpp
index 60627e4f3..fac9a660b 100644
--- a/src/po_man.cpp
+++ b/src/po_man.cpp
@@ -1533,6 +1533,11 @@ static void IterFindPolySides (FPolyObj *po, side_t *side)
 //
 //==========================================================================
 
+static int STACK_ARGS posicmp(const void *a, const void *b)
+{
+	return (*(const side_t **)a)->linedef->args[1] - (*(const side_t **)b)->linedef->args[1];
+}
+
 static void SpawnPolyobj (int index, int tag, int type)
 {
 	unsigned int ii;
@@ -1577,59 +1582,24 @@ static void SpawnPolyobj (int index, int tag, int type)
 		// didn't find a polyobj through PO_LINE_START
 		TArray<side_t *> polySideList;
 		unsigned int psIndexOld;
-		for (j = 1; j < PO_MAXPOLYSEGS; j++)
-		{
-			psIndexOld = po->Sidedefs.Size();
-			for (ii = 0; ii < KnownPolySides.Size(); ++ii)
-			{
-				i = KnownPolySides[ii];
+		psIndexOld = po->Sidedefs.Size();
 
-				if (i >= 0 &&
-					sides[i].linedef->special == Polyobj_ExplicitLine &&
-					sides[i].linedef->args[0] == tag)
-				{
-					if (!sides[i].linedef->args[1])
-					{
-						I_Error ("SpawnPolyobj: Explicit line missing order number (probably %d) in poly %d.\n",
-							j+1, tag);
-					}
-					if (sides[i].linedef->args[1] == j)
-					{
-						po->Sidedefs.Push (&sides[i]);
-					}
-				}
-			}
-			// Clear out any specials for these segs...we cannot clear them out
-			// 	in the above loop, since we aren't guaranteed one seg per linedef.
-			for (ii = 0; ii < KnownPolySides.Size(); ++ii)
+		for (ii = 0; ii < KnownPolySides.Size(); ++ii)
+		{
+			i = KnownPolySides[ii];
+
+			if (i >= 0 &&
+				sides[i].linedef->special == Polyobj_ExplicitLine &&
+				sides[i].linedef->args[0] == tag)
 			{
-				i = KnownPolySides[ii];
-				if (i >= 0 &&
-					sides[i].linedef->special == Polyobj_ExplicitLine &&
-					sides[i].linedef->args[0] == tag && sides[i].linedef->args[1] == j)
+				if (!sides[i].linedef->args[1])
 				{
-					sides[i].linedef->special = 0;
-					sides[i].linedef->args[0] = 0;
-					KnownPolySides[ii] = -1;
-				}
-			}
-			if (po->Sidedefs.Size() == psIndexOld)
-			{ // Check if an explicit line order has been skipped.
-			  // A line has been skipped if there are any more explicit
-			  // lines with the current tag value. [RH] Can this actually happen?
-				for (ii = 0; ii < KnownPolySides.Size(); ++ii)
-				{
-					i = KnownPolySides[ii];
-					if (i >= 0 &&
-						sides[i].linedef->special == Polyobj_ExplicitLine &&
-						sides[i].linedef->args[0] == tag)
-					{
-						I_Error ("SpawnPolyobj: Missing explicit line %d for poly %d\n",
-							j, tag);
-					}
+					I_Error("SpawnPolyobj: Explicit line missing order number in poly %d, linedef %d.\n", tag, int(sides[i].linedef - lines));
 				}
+				po->Sidedefs.Push (&sides[i]);
 			}
 		}
+		qsort(&po->Sidedefs[0], po->Sidedefs.Size(), sizeof(po->Sidedefs[0]), posicmp);
 		if (po->Sidedefs.Size() > 0)
 		{
 			po->crush = (type != SMT_PolySpawn) ? 3 : 0;

From a43f5a8ecac4323309954a58ba940efdc956e06c Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Fri, 8 Jan 2016 20:08:06 -0600
Subject: [PATCH 3/6] Remove now unused variable from SpawnPolyobj

---
 src/po_man.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/po_man.cpp b/src/po_man.cpp
index fac9a660b..727591bd5 100644
--- a/src/po_man.cpp
+++ b/src/po_man.cpp
@@ -1542,7 +1542,6 @@ static void SpawnPolyobj (int index, int tag, int type)
 {
 	unsigned int ii;
 	int i;
-	int j;
 	FPolyObj *po = &polyobjs[index];
 
 	for (ii = 0; ii < KnownPolySides.Size(); ++ii)

From f330a819092c58d6d3d305d99a6ca8ef7dcb38df Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Fri, 8 Jan 2016 20:08:10 -0600
Subject: [PATCH 4/6] Rename packing textures to texture atlases for the D3D
 code

- I figure since I now know the "proper" name for these things, I should
  call them by it.
---
 src/win32/fb_d3d9.cpp  | 89 ++++++++++++++++++++++--------------------
 src/win32/win32iface.h |  6 +--
 2 files changed, 50 insertions(+), 45 deletions(-)

diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp
index e23275734..273fcde44 100644
--- a/src/win32/fb_d3d9.cpp
+++ b/src/win32/fb_d3d9.cpp
@@ -90,7 +90,7 @@ IMPLEMENT_CLASS(D3DFB)
 
 struct D3DFB::PackedTexture
 {
-	D3DFB::PackingTexture *Owner;
+	D3DFB::Atlas *Owner;
 
 	PackedTexture *Next, **Prev;
 
@@ -104,10 +104,10 @@ struct D3DFB::PackedTexture
 	bool Padded;
 };
 
-struct D3DFB::PackingTexture
+struct D3DFB::Atlas
 {
-	PackingTexture(D3DFB *fb, int width, int height, D3DFORMAT format);
-	~PackingTexture();
+	Atlas(D3DFB *fb, int width, int height, D3DFORMAT format);
+	~Atlas();
 
 	PackedTexture *GetBestFit(int width, int height, int &area);
 	void AllocateImage(PackedTexture *box, int width, int height);
@@ -115,7 +115,7 @@ struct D3DFB::PackingTexture
 	void AddEmptyBox(int left, int top, int right, int bottom);
 	void FreeBox(PackedTexture *box);
 
-	PackingTexture *Next;
+	Atlas *Next;
 	IDirect3DTexture9 *Tex;
 	D3DFORMAT Format;
 	PackedTexture *UsedList;	// Boxes that contain images
@@ -283,7 +283,7 @@ D3DFB::D3DFB (UINT adapter, int width, int height, bool fullscreen)
 	ScreenWipe = NULL;
 	InScene = false;
 	QuadExtra = new BufferedTris[MAX_QUAD_BATCH];
-	Packs = NULL;
+	Atlases = NULL;
 	PixelDoubling = 0;
 	SkipAt = -1;
 	CurrRenderTexture = 0;
@@ -496,7 +496,7 @@ void D3DFB::FillPresentParameters (D3DPRESENT_PARAMETERS *pp, bool fullscreen, b
 
 bool D3DFB::CreateResources()
 {
-	Packs = NULL;
+	Atlases = NULL;
 	if (!Windowed)
 	{
 		// Remove the window border in fullscreen mode
@@ -624,8 +624,8 @@ void D3DFB::ReleaseResources ()
 		delete ScreenWipe;
 		ScreenWipe = NULL;
 	}
-	PackingTexture *pack, *next;
-	for (pack = Packs; pack != NULL; pack = next)
+	Atlas *pack, *next;
+	for (pack = Atlases; pack != NULL; pack = next)
 	{
 		next = pack->Next;
 		delete pack;
@@ -1826,8 +1826,9 @@ IDirect3DTexture9 *D3DFB::GetCurrentScreen(D3DPOOL pool)
 //
 // D3DFB :: DrawPackedTextures
 //
-// DEBUG: Draws the packing textures to the screen, starting with the
-// 1-based packnum.
+// DEBUG: Draws the texture atlases to the screen, starting with the
+// 1-based packnum. Ignores atlases that are flagged for use by one
+// texture only.
 //
 //==========================================================================
 
@@ -1838,30 +1839,34 @@ void D3DFB::DrawPackedTextures(int packnum)
 		0xFFFF9999, 0xFF99FF99, 0xFF9999FF, 0xFFFFFF99,
 		0xFFFF99FF, 0xFF99FFFF, 0xFFFFCC99, 0xFF99CCFF
 	};
-	PackingTexture *pack;
+	Atlas *pack;
 	int x = 8, y = 8;
 
 	if (packnum <= 0)
 	{
 		return;
 	}
-	pack = Packs;
+	pack = Atlases;
+	// Find the first texture atlas that is an actual atlas.
 	while (pack != NULL && pack->OneUse)
-	{ // Skip textures that aren't used as packing containers
+	{ // Skip textures that aren't used as atlases
 		pack = pack->Next;
 	}
+	// Skip however many atlases we would have otherwise drawn
+	// until we've skipped <packnum> of them.
 	while (pack != NULL && packnum != 1)
 	{
 		if (!pack->OneUse)
-		{ // Skip textures that aren't used as packing containers
+		{ // Skip textures that aren't used as atlases
 			packnum--;
 		}
 		pack = pack->Next;
 	}
+	// Draw atlases until we run out of room on the screen.
 	while (pack != NULL)
 	{
 		if (pack->OneUse)
-		{ // Skip textures that aren't used as packing containers
+		{ // Skip textures that aren't used as atlases
 			pack = pack->Next;
 			continue;
 		}
@@ -1969,34 +1974,34 @@ void D3DFB::DrawPackedTextures(int packnum)
 //
 // D3DFB :: AllocPackedTexture
 //
-// Finds space to pack an image inside a packing texture and returns it.
+// Finds space to pack an image inside a texture atlas and returns it.
 // Large images and those that need to wrap always get their own textures.
 //
 //==========================================================================
 
 D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3DFORMAT format)
 {
-	PackingTexture *bestpack;
+	Atlas *bestpack;
 	PackedTexture *bestbox;
 	int area;
 
 	// check for 254 to account for padding
 	if (w > 254 || h > 254 || wrapping)
-	{ // Create a new packing texture.
-		bestpack = new PackingTexture(this, w, h, format);
+	{ // Create a new texture atlas.
+		bestpack = new Atlas(this, w, h, format);
 		bestpack->OneUse = true;
 		bestbox = bestpack->GetBestFit(w, h, area);
 		bestbox->Padded = false;
 	}
 	else
-	{ // Try to find space in an existing packing texture.
+	{ // Try to find space in an existing texture atlas.
 		w += 2; // Add padding
 		h += 2;
 		int bestarea = INT_MAX;
 		int bestareaever = w * h;
 		bestpack = NULL;
 		bestbox = NULL;
-		for (PackingTexture *pack = Packs; pack != NULL; pack = pack->Next)
+		for (Atlas *pack = Atlases; pack != NULL; pack = pack->Next)
 		{
 			if (pack->Format == format)
 			{
@@ -2016,8 +2021,8 @@ D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3D
 			}
 		}
 		if (bestpack == NULL)
-		{ // Create a new packing texture.
-			bestpack = new PackingTexture(this, 256, 256, format);
+		{ // Create a new texture atlas.
+			bestpack = new Atlas(this, 256, 256, format);
 			bestbox = bestpack->GetBestFit(w, h, bestarea);
 		}
 		bestbox->Padded = true;
@@ -2028,11 +2033,11 @@ D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3D
 
 //==========================================================================
 //
-// PackingTexture Constructor
+// Atlas Constructor
 //
 //==========================================================================
 
-D3DFB::PackingTexture::PackingTexture(D3DFB *fb, int w, int h, D3DFORMAT format)
+D3DFB::Atlas::Atlas(D3DFB *fb, int w, int h, D3DFORMAT format)
 {
 	Tex = NULL;
 	Format = format;
@@ -2043,8 +2048,8 @@ D3DFB::PackingTexture::PackingTexture(D3DFB *fb, int w, int h, D3DFORMAT format)
 	Width = 0;
 	Height = 0;
 
-	Next = fb->Packs;
-	fb->Packs = this;
+	Next = fb->Atlases;
+	fb->Atlases = this;
 
 #if 1
 	if (FAILED(fb->D3DDevice->CreateTexture(w, h, 1, 0, format, D3DPOOL_MANAGED, &Tex, NULL)))
@@ -2068,11 +2073,11 @@ D3DFB::PackingTexture::PackingTexture(D3DFB *fb, int w, int h, D3DFORMAT format)
 
 //==========================================================================
 //
-// PackingTexture Destructor
+// Atlas Destructor
 //
 //==========================================================================
 
-D3DFB::PackingTexture::~PackingTexture()
+D3DFB::Atlas::~Atlas()
 {
 	PackedTexture *box, *next;
 
@@ -2096,14 +2101,14 @@ D3DFB::PackingTexture::~PackingTexture()
 
 //==========================================================================
 //
-// PackingTexture :: GetBestFit
+// Atlas :: GetBestFit
 //
 // Returns the empty box that provides the best fit for the requested
 // dimensions, or NULL if none of them are large enough.
 //
 //==========================================================================
 
-D3DFB::PackedTexture *D3DFB::PackingTexture::GetBestFit(int w, int h, int &area)
+D3DFB::PackedTexture *D3DFB::Atlas::GetBestFit(int w, int h, int &area)
 {
 	PackedTexture *box;
 	int smallestarea = INT_MAX;
@@ -2133,17 +2138,17 @@ D3DFB::PackedTexture *D3DFB::PackingTexture::GetBestFit(int w, int h, int &area)
 
 //==========================================================================
 //
-// PackingTexture :: AllocateImage
+// Atlas :: AllocateImage
 //
 // Moves the box from the empty list to the used list, sizing it to the
 // requested dimensions and adding additional boxes to the empty list if
 // needed.
 //
-// The passed box *MUST* be in this packing texture's empty list.
+// The passed box *MUST* be in this texture atlas's empty list.
 //
 //==========================================================================
 
-void D3DFB::PackingTexture::AllocateImage(D3DFB::PackedTexture *box, int w, int h)
+void D3DFB::Atlas::AllocateImage(D3DFB::PackedTexture *box, int w, int h)
 {
 	RECT start = box->Area;
 
@@ -2212,13 +2217,13 @@ void D3DFB::PackingTexture::AllocateImage(D3DFB::PackedTexture *box, int w, int
 
 //==========================================================================
 //
-// PackingTexture :: AddEmptyBox
+// Atlas :: AddEmptyBox
 //
 // Adds a box with the specified dimensions to the empty list.
 //
 //==========================================================================
 
-void D3DFB::PackingTexture::AddEmptyBox(int left, int top, int right, int bottom)
+void D3DFB::Atlas::AddEmptyBox(int left, int top, int right, int bottom)
 {
 	PackedTexture *box = AllocateBox();
 	box->Area.left = left;
@@ -2236,14 +2241,14 @@ void D3DFB::PackingTexture::AddEmptyBox(int left, int top, int right, int bottom
 
 //==========================================================================
 //
-// PackingTexture :: AllocateBox
+// Atlas :: AllocateBox
 //
 // Returns a new PackedTexture box, either by retrieving one off the free
 // list or by creating a new one. The box is not linked into a list.
 //
 //==========================================================================
 
-D3DFB::PackedTexture *D3DFB::PackingTexture::AllocateBox()
+D3DFB::PackedTexture *D3DFB::Atlas::AllocateBox()
 {
 	PackedTexture *box;
 
@@ -2266,7 +2271,7 @@ D3DFB::PackedTexture *D3DFB::PackingTexture::AllocateBox()
 
 //==========================================================================
 //
-// PackingTexture :: FreeBox
+// Atlas :: FreeBox
 //
 // Removes a box from its current list and adds it to the empty list,
 // updating EmptyArea. If there are no boxes left in the used list, then
@@ -2275,7 +2280,7 @@ D3DFB::PackedTexture *D3DFB::PackingTexture::AllocateBox()
 //
 //==========================================================================
 
-void D3DFB::PackingTexture::FreeBox(D3DFB::PackedTexture *box)
+void D3DFB::Atlas::FreeBox(D3DFB::PackedTexture *box)
 {
 	*(box->Prev) = box->Next;
 	if (box->Next != NULL)
@@ -4004,7 +4009,7 @@ void D3DFB::SetPaletteTexture(IDirect3DTexture9 *texture, int count, D3DCOLOR bo
 	SetTexture(1, texture);
 }
 
-void D3DFB::SetPalTexBilinearConstants(PackingTexture *tex)
+void D3DFB::SetPalTexBilinearConstants(Atlas *tex)
 {
 #if 0
 	float con[8];
diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h
index 934931ea0..a64b1dd82 100644
--- a/src/win32/win32iface.h
+++ b/src/win32/win32iface.h
@@ -279,7 +279,7 @@ private:
 	friend class D3DPal;
 
 	struct PackedTexture;
-	struct PackingTexture;
+	struct Atlas;
 
 	struct FBVERTEX
 	{
@@ -392,7 +392,7 @@ private:
 	void SetPixelShader(IDirect3DPixelShader9 *shader);
 	void SetTexture(int tnum, IDirect3DTexture9 *texture);
 	void SetPaletteTexture(IDirect3DTexture9 *texture, int count, D3DCOLOR border_color);
-	void SetPalTexBilinearConstants(PackingTexture *texture);
+	void SetPalTexBilinearConstants(Atlas *texture);
 
 	BOOL AlphaTestEnabled;
 	BOOL AlphaBlendEnabled;
@@ -431,7 +431,7 @@ private:
 	BYTE BlockNum;
 	D3DPal *Palettes;
 	D3DTex *Textures;
-	PackingTexture *Packs;
+	Atlas *Atlases;
 	HRESULT LastHR;
 
 	UINT Adapter;

From bf31d66d312bbf3b557bab0662f654a1015c0af5 Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Fri, 8 Jan 2016 22:07:16 -0600
Subject: [PATCH 5/6] Use a better packing algorithm for the texture atlases

- The old algorithm is something I threw together that produced decent,
  but not spectacular results since it had a tendency to waste space by
  forcing everything onto "shelves".
  The new packer is the Skyline-MinWaste-WasteMap-BestFirstFit algorithm
  described by Jukka Jylanki in his paper *A Thousand Ways to Pack the Bin - A
  Practical Approach to Two-Dimensional Rectangle Bin Packing*, which can
  currently be read at http://clb.demon.fi/files/RectangleBinPack.pdf
  This is minus the optimization to rotate rectangles to make better fits.
---
 src/CMakeLists.txt        |   2 +
 src/GuillotineBinPack.cpp | 633 ++++++++++++++++++++++++++++++++++++++
 src/GuillotineBinPack.h   | 135 ++++++++
 src/Rect.h                |  94 ++++++
 src/SkylineBinPack.cpp    | 329 ++++++++++++++++++++
 src/SkylineBinPack.h      |  88 ++++++
 src/win32/fb_d3d9.cpp     | 327 +++++---------------
 src/win32/win32iface.h    |   1 +
 zdoom.vcproj              |  20 ++
 9 files changed, 1378 insertions(+), 251 deletions(-)
 create mode 100644 src/GuillotineBinPack.cpp
 create mode 100644 src/GuillotineBinPack.h
 create mode 100644 src/Rect.h
 create mode 100644 src/SkylineBinPack.cpp
 create mode 100644 src/SkylineBinPack.h

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8503fabcf..90748f257 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -995,6 +995,8 @@ add_executable( zdoom WIN32 MACOSX_BUNDLE
 	wi_stuff.cpp
 	zstrformat.cpp
 	zstring.cpp
+	GuillotineBinPack.cpp
+	SkylineBinPack.cpp
 	g_doom/a_doommisc.cpp
 	g_heretic/a_hereticmisc.cpp
 	g_hexen/a_hexenmisc.cpp
diff --git a/src/GuillotineBinPack.cpp b/src/GuillotineBinPack.cpp
new file mode 100644
index 000000000..61ac7a4ce
--- /dev/null
+++ b/src/GuillotineBinPack.cpp
@@ -0,0 +1,633 @@
+/** @file GuillotineBinPack.cpp
+	@author Jukka Jyl�nki
+
+	@brief Implements different bin packer algorithms that use the GUILLOTINE data structure.
+
+	This work is released to Public Domain, do whatever you want with it.
+*/
+
+#include <cassert>
+
+#include "GuillotineBinPack.h"
+
+using namespace std;
+
+GuillotineBinPack::GuillotineBinPack()
+:binWidth(0),
+binHeight(0)
+{
+}
+
+GuillotineBinPack::GuillotineBinPack(int width, int height)
+{
+	Init(width, height);
+}
+
+void GuillotineBinPack::Init(int width, int height)
+{
+	binWidth = width;
+	binHeight = height;
+
+#ifdef _DEBUG
+	disjointRects.Clear();
+#endif
+
+	// Clear any memory of previously packed rectangles.
+	usedRectangles.Clear();
+
+	// We start with a single big free rectangle that spans the whole bin.
+	Rect n;
+	n.x = 0;
+	n.y = 0;
+	n.width = width;
+	n.height = height;
+
+	freeRectangles.Clear();
+	freeRectangles.Push(n);
+}
+
+void GuillotineBinPack::Insert(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge, 
+	FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
+{
+	dst.Clear();
+
+	// Remember variables about the best packing choice we have made so far during the iteration process.
+	int bestFreeRect = 0;
+	int bestRect = 0;
+	bool bestFlipped = false;
+
+	// Pack rectangles one at a time until we have cleared the rects array of all rectangles.
+	// rects will get destroyed in the process.
+	while(rects.Size() > 0)
+	{
+		// Stores the penalty score of the best rectangle placement - bigger=worse, smaller=better.
+		int bestScore = INT_MAX;
+
+		for(unsigned i = 0; i < freeRectangles.Size(); ++i)
+		{
+			for(unsigned j = 0; j < rects.Size(); ++j)
+			{
+				// If this rectangle is a perfect match, we pick it instantly.
+				if (rects[j].width == freeRectangles[i].width && rects[j].height == freeRectangles[i].height)
+				{
+					bestFreeRect = i;
+					bestRect = j;
+					bestFlipped = false;
+					bestScore = INT_MIN;
+					i = freeRectangles.Size(); // Force a jump out of the outer loop as well - we got an instant fit.
+					break;
+				}
+				// If flipping this rectangle is a perfect match, pick that then.
+				else if (rects[j].height == freeRectangles[i].width && rects[j].width == freeRectangles[i].height)
+				{
+					bestFreeRect = i;
+					bestRect = j;
+					bestFlipped = true;
+					bestScore = INT_MIN;
+					i = freeRectangles.Size(); // Force a jump out of the outer loop as well - we got an instant fit.
+					break;
+				}
+				// Try if we can fit the rectangle upright.
+				else if (rects[j].width <= freeRectangles[i].width && rects[j].height <= freeRectangles[i].height)
+				{
+					int score = ScoreByHeuristic(rects[j].width, rects[j].height, freeRectangles[i], rectChoice);
+					if (score < bestScore)
+					{
+						bestFreeRect = i;
+						bestRect = j;
+						bestFlipped = false;
+						bestScore = score;
+					}
+				}
+				// If not, then perhaps flipping sideways will make it fit?
+				else if (rects[j].height <= freeRectangles[i].width && rects[j].width <= freeRectangles[i].height)
+				{
+					int score = ScoreByHeuristic(rects[j].height, rects[j].width, freeRectangles[i], rectChoice);
+					if (score < bestScore)
+					{
+						bestFreeRect = i;
+						bestRect = j;
+						bestFlipped = true;
+						bestScore = score;
+					}
+				}
+			}
+		}
+
+		// If we didn't manage to find any rectangle to pack, abort.
+		if (bestScore == INT_MAX)
+			return;
+
+		// Otherwise, we're good to go and do the actual packing.
+		Rect newNode;
+		newNode.x = freeRectangles[bestFreeRect].x;
+		newNode.y = freeRectangles[bestFreeRect].y;
+		newNode.width = rects[bestRect].width;
+		newNode.height = rects[bestRect].height;
+
+		if (bestFlipped)
+			std::swap(newNode.width, newNode.height);
+
+		// Remove the free space we lost in the bin.
+		SplitFreeRectByHeuristic(freeRectangles[bestFreeRect], newNode, splitMethod);
+		freeRectangles.Delete(bestFreeRect);
+
+		// Remove the rectangle we just packed from the input list.
+		rects.Delete(bestRect);
+
+		// Perform a Rectangle Merge step if desired.
+		if (merge)
+			MergeFreeList();
+
+		// Remember the new used rectangle.
+		usedRectangles.Push(newNode);
+
+		// Check that we're really producing correct packings here.
+		assert(disjointRects.Add(newNode) == true);
+	}
+}
+
+/// @return True if r fits inside freeRect (possibly rotated).
+bool Fits(const RectSize &r, const Rect &freeRect)
+{
+	return (r.width <= freeRect.width && r.height <= freeRect.height) ||
+		(r.height <= freeRect.width && r.width <= freeRect.height);
+}
+
+/// @return True if r fits perfectly inside freeRect, i.e. the leftover area is 0.
+bool FitsPerfectly(const RectSize &r, const Rect &freeRect)
+{
+	return (r.width == freeRect.width && r.height == freeRect.height) ||
+		(r.height == freeRect.width && r.width == freeRect.height);
+}
+
+/*
+// A helper function for GUILLOTINE-MAXFITTING. Counts how many rectangles fit into the given rectangle
+// after it has been split.
+void CountNumFitting(const Rect &freeRect, int width, int height, const TArray<RectSize> &rects, 
+	int usedRectIndex, bool splitHorizontal, int &score1, int &score2)
+{
+	const int w = freeRect.width - width;
+	const int h = freeRect.height - height;
+
+	Rect bottom;
+	bottom.x = freeRect.x;
+	bottom.y = freeRect.y + height;
+	bottom.height = h;
+
+	Rect right;
+	right.x = freeRect.x + width;
+	right.y = freeRect.y;
+	right.width = w;
+
+	if (splitHorizontal)
+	{
+		bottom.width = freeRect.width;
+		right.height = height;
+	}
+	else // Split vertically
+	{
+		bottom.width = width;
+		right.height = freeRect.height;
+	}
+
+	int fitBottom = 0;
+	int fitRight = 0;
+	for(size_t i = 0; i < rects.size(); ++i)
+		if (i != usedRectIndex)
+		{
+			if (FitsPerfectly(rects[i], bottom))
+				fitBottom |= 0x10000000;
+			if (FitsPerfectly(rects[i], right))
+				fitRight |= 0x10000000;
+
+			if (Fits(rects[i], bottom))
+				++fitBottom;
+			if (Fits(rects[i], right))
+				++fitRight;
+		}
+
+	score1 = min(fitBottom, fitRight);
+	score2 = max(fitBottom, fitRight);
+}
+*/
+/*
+// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice.
+void GuillotineBinPack::InsertMaxFitting(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge, 
+	FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
+{
+	dst.clear();
+	int bestRect = 0;
+	bool bestFlipped = false;
+	bool bestSplitHorizontal = false;
+
+	// Pick rectangles one at a time and pack the one that leaves the most choices still open.
+	while(rects.size() > 0 && freeRectangles.size() > 0)
+	{
+		int bestScore1 = -1;
+		int bestScore2 = -1;
+
+		///\todo Different sort predicates.
+		clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size(), CompareRectShortSide);
+
+		Rect &freeRect = freeRectangles[0];
+
+		for(size_t j = 0; j < rects.size(); ++j)
+		{
+			int score1;
+			int score2;
+
+			if (rects[j].width == freeRect.width && rects[j].height == freeRect.height)
+			{
+				bestRect = j;
+				bestFlipped = false;
+				bestScore1 = bestScore2 = std::numeric_limits<int>::max();
+				break;
+			}
+			else if (rects[j].width <= freeRect.width && rects[j].height <= freeRect.height)
+			{
+				CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, false, score1, score2);
+
+				if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
+				{
+					bestRect = j;
+					bestScore1 = score1;
+					bestScore2 = score2;
+					bestFlipped = false;
+					bestSplitHorizontal = false;
+				}
+
+				CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, true, score1, score2);
+
+				if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
+				{
+					bestRect = j;
+					bestScore1 = score1;
+					bestScore2 = score2;
+					bestFlipped = false;
+					bestSplitHorizontal = true;
+				}
+			}
+
+			if (rects[j].height == freeRect.width && rects[j].width == freeRect.height)
+			{
+				bestRect = j;
+				bestFlipped = true;
+				bestScore1 = bestScore2 = std::numeric_limits<int>::max();
+				break;
+			}
+			else if (rects[j].height <= freeRect.width && rects[j].width <= freeRect.height)
+			{
+				CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, false, score1, score2);
+
+				if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
+				{
+					bestRect = j;
+					bestScore1 = score1;
+					bestScore2 = score2;
+					bestFlipped = true;
+					bestSplitHorizontal = false;
+				}
+
+				CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, true, score1, score2);
+
+				if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
+				{
+					bestRect = j;
+					bestScore1 = score1;
+					bestScore2 = score2;
+					bestFlipped = true;
+					bestSplitHorizontal = true;
+				}
+			}
+		}
+
+		if (bestScore1 >= 0)
+		{
+			Rect newNode;
+			newNode.x = freeRect.x;
+			newNode.y = freeRect.y;
+			newNode.width = rects[bestRect].width;
+			newNode.height = rects[bestRect].height;
+			if (bestFlipped)
+				std::swap(newNode.width, newNode.height);
+
+			assert(disjointRects.Disjoint(newNode));
+			SplitFreeRectAlongAxis(freeRect, newNode, bestSplitHorizontal);
+
+			rects.erase(rects.begin() + bestRect);
+
+			if (merge)
+				MergeFreeList();
+
+			usedRectangles.push_back(newNode);
+#ifdef _DEBUG
+			disjointRects.Add(newNode);
+#endif
+		}
+
+		freeRectangles.erase(freeRectangles.begin());
+	}
+}
+*/
+
+Rect GuillotineBinPack::Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, 
+	GuillotineSplitHeuristic splitMethod)
+{
+	// Find where to put the new rectangle.
+	int freeNodeIndex = 0;
+	Rect newRect = FindPositionForNewNode(width, height, rectChoice, &freeNodeIndex);
+
+	// Abort if we didn't have enough space in the bin.
+	if (newRect.height == 0)
+		return newRect;
+
+	// Remove the space that was just consumed by the new rectangle.
+	SplitFreeRectByHeuristic(freeRectangles[freeNodeIndex], newRect, splitMethod);
+	freeRectangles.Delete(freeNodeIndex);
+
+	// Perform a Rectangle Merge step if desired.
+	if (merge)
+		MergeFreeList();
+
+	// Remember the new used rectangle.
+	usedRectangles.Push(newRect);
+
+	// Check that we're really producing correct packings here.
+	assert(disjointRects.Add(newRect) == true);
+
+	return newRect;
+}
+
+/// Computes the ratio of used surface area to the total bin area.
+float GuillotineBinPack::Occupancy() const
+{
+	///\todo The occupancy rate could be cached/tracked incrementally instead
+	///      of looping through the list of packed rectangles here.
+	unsigned long usedSurfaceArea = 0;
+	for(unsigned i = 0; i < usedRectangles.Size(); ++i)
+		usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height;
+
+	return (float)usedSurfaceArea / (binWidth * binHeight);
+}
+
+/// Returns the heuristic score value for placing a rectangle of size width*height into freeRect. Does not try to rotate.
+int GuillotineBinPack::ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice)
+{
+	switch(rectChoice)
+	{
+	case RectBestAreaFit: return ScoreBestAreaFit(width, height, freeRect);
+	case RectBestShortSideFit: return ScoreBestShortSideFit(width, height, freeRect);
+	case RectBestLongSideFit: return ScoreBestLongSideFit(width, height, freeRect);
+	case RectWorstAreaFit: return ScoreWorstAreaFit(width, height, freeRect);
+	case RectWorstShortSideFit: return ScoreWorstShortSideFit(width, height, freeRect);
+	case RectWorstLongSideFit: return ScoreWorstLongSideFit(width, height, freeRect);
+	default: assert(false); return INT_MAX;
+	}
+}
+
+int GuillotineBinPack::ScoreBestAreaFit(int width, int height, const Rect &freeRect)
+{
+	return freeRect.width * freeRect.height - width * height;
+}
+
+int GuillotineBinPack::ScoreBestShortSideFit(int width, int height, const Rect &freeRect)
+{
+	int leftoverHoriz = abs(freeRect.width - width);
+	int leftoverVert = abs(freeRect.height - height);
+	int leftover = min(leftoverHoriz, leftoverVert);
+	return leftover;
+}
+
+int GuillotineBinPack::ScoreBestLongSideFit(int width, int height, const Rect &freeRect)
+{
+	int leftoverHoriz = abs(freeRect.width - width);
+	int leftoverVert = abs(freeRect.height - height);
+	int leftover = max(leftoverHoriz, leftoverVert);
+	return leftover;
+}
+
+int GuillotineBinPack::ScoreWorstAreaFit(int width, int height, const Rect &freeRect)
+{
+	return -ScoreBestAreaFit(width, height, freeRect);
+}
+
+int GuillotineBinPack::ScoreWorstShortSideFit(int width, int height, const Rect &freeRect)
+{
+	return -ScoreBestShortSideFit(width, height, freeRect);
+}
+
+int GuillotineBinPack::ScoreWorstLongSideFit(int width, int height, const Rect &freeRect)
+{
+	return -ScoreBestLongSideFit(width, height, freeRect);
+}
+
+Rect GuillotineBinPack::FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex)
+{
+	Rect bestNode;
+	memset(&bestNode, 0, sizeof(Rect));
+
+	int bestScore = INT_MAX;
+
+	/// Try each free rectangle to find the best one for placement.
+	for(unsigned i = 0; i < freeRectangles.Size(); ++i)
+	{
+		// If this is a perfect fit upright, choose it immediately.
+		if (width == freeRectangles[i].width && height == freeRectangles[i].height)
+		{
+			bestNode.x = freeRectangles[i].x;
+			bestNode.y = freeRectangles[i].y;
+			bestNode.width = width;
+			bestNode.height = height;
+			bestScore = INT_MIN;
+			*nodeIndex = i;
+			assert(disjointRects.Disjoint(bestNode));
+			break;
+		}
+		// If this is a perfect fit sideways, choose it.
+/*		else if (height == freeRectangles[i].width && width == freeRectangles[i].height)
+		{
+			bestNode.x = freeRectangles[i].x;
+			bestNode.y = freeRectangles[i].y;
+			bestNode.width = height;
+			bestNode.height = width;
+			bestScore = INT_MIN;
+			*nodeIndex = i;
+			assert(disjointRects.Disjoint(bestNode));
+			break;
+		}
+*/		// Does the rectangle fit upright?
+		else if (width <= freeRectangles[i].width && height <= freeRectangles[i].height)
+		{
+			int score = ScoreByHeuristic(width, height, freeRectangles[i], rectChoice);
+
+			if (score < bestScore)
+			{
+				bestNode.x = freeRectangles[i].x;
+				bestNode.y = freeRectangles[i].y;
+				bestNode.width = width;
+				bestNode.height = height;
+				bestScore = score;
+				*nodeIndex = i;
+				assert(disjointRects.Disjoint(bestNode));
+			}
+		}
+		// Does the rectangle fit sideways?
+/*		else if (height <= freeRectangles[i].width && width <= freeRectangles[i].height)
+		{
+			int score = ScoreByHeuristic(height, width, freeRectangles[i], rectChoice);
+
+			if (score < bestScore)
+			{
+				bestNode.x = freeRectangles[i].x;
+				bestNode.y = freeRectangles[i].y;
+				bestNode.width = height;
+				bestNode.height = width;
+				bestScore = score;
+				*nodeIndex = i;
+				assert(disjointRects.Disjoint(bestNode));
+			}
+		}
+*/	}
+	return bestNode;
+}
+
+void GuillotineBinPack::SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method)
+{
+	// Compute the lengths of the leftover area.
+	const int w = freeRect.width - placedRect.width;
+	const int h = freeRect.height - placedRect.height;
+
+	// Placing placedRect into freeRect results in an L-shaped free area, which must be split into
+	// two disjoint rectangles. This can be achieved with by splitting the L-shape using a single line.
+	// We have two choices: horizontal or vertical.	
+
+	// Use the given heuristic to decide which choice to make.
+
+	bool splitHorizontal;
+	switch(method)
+	{
+	case SplitShorterLeftoverAxis:
+		// Split along the shorter leftover axis.
+		splitHorizontal = (w <= h);
+		break;
+	case SplitLongerLeftoverAxis:
+		// Split along the longer leftover axis.
+		splitHorizontal = (w > h);
+		break;
+	case SplitMinimizeArea:
+		// Maximize the larger area == minimize the smaller area.
+		// Tries to make the single bigger rectangle.
+		splitHorizontal = (placedRect.width * h > w * placedRect.height);
+		break;
+	case SplitMaximizeArea:
+		// Maximize the smaller area == minimize the larger area.
+		// Tries to make the rectangles more even-sized.
+		splitHorizontal = (placedRect.width * h <= w * placedRect.height);
+		break;
+	case SplitShorterAxis:
+		// Split along the shorter total axis.
+		splitHorizontal = (freeRect.width <= freeRect.height);
+		break;
+	case SplitLongerAxis:
+		// Split along the longer total axis.
+		splitHorizontal = (freeRect.width > freeRect.height);
+		break;
+	default:
+		splitHorizontal = true;
+		assert(false);
+	}
+
+	// Perform the actual split.
+	SplitFreeRectAlongAxis(freeRect, placedRect, splitHorizontal);
+}
+
+/// This function will add the two generated rectangles into the freeRectangles array. The caller is expected to
+/// remove the original rectangle from the freeRectangles array after that.
+void GuillotineBinPack::SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal)
+{
+	// Form the two new rectangles.
+	Rect bottom;
+	bottom.x = freeRect.x;
+	bottom.y = freeRect.y + placedRect.height;
+	bottom.height = freeRect.height - placedRect.height;
+
+	Rect right;
+	right.x = freeRect.x + placedRect.width;
+	right.y = freeRect.y;
+	right.width = freeRect.width - placedRect.width;
+
+	if (splitHorizontal)
+	{
+		bottom.width = freeRect.width;
+		right.height = placedRect.height;
+	}
+	else // Split vertically
+	{
+		bottom.width = placedRect.width;
+		right.height = freeRect.height;
+	}
+
+	// Add the new rectangles into the free rectangle pool if they weren't degenerate.
+	if (bottom.width > 0 && bottom.height > 0)
+		freeRectangles.Push(bottom);
+	if (right.width > 0 && right.height > 0)
+		freeRectangles.Push(right);
+
+	assert(disjointRects.Disjoint(bottom));
+	assert(disjointRects.Disjoint(right));
+}
+
+void GuillotineBinPack::MergeFreeList()
+{
+#ifdef _DEBUG
+	DisjointRectCollection test;
+	for(unsigned i = 0; i < freeRectangles.Size(); ++i)
+		assert(test.Add(freeRectangles[i]) == true);
+#endif
+
+	// Do a Theta(n^2) loop to see if any pair of free rectangles could me merged into one.
+	// Note that we miss any opportunities to merge three rectangles into one. (should call this function again to detect that)
+	for(unsigned i = 0; i < freeRectangles.Size(); ++i)
+		for(unsigned j = i+1; j < freeRectangles.Size(); ++j)
+		{
+			if (freeRectangles[i].width == freeRectangles[j].width && freeRectangles[i].x == freeRectangles[j].x)
+			{
+				if (freeRectangles[i].y == freeRectangles[j].y + freeRectangles[j].height)
+				{
+					freeRectangles[i].y -= freeRectangles[j].height;
+					freeRectangles[i].height += freeRectangles[j].height;
+					freeRectangles.Delete(j);
+					--j;
+				}
+				else if (freeRectangles[i].y + freeRectangles[i].height == freeRectangles[j].y)
+				{
+					freeRectangles[i].height += freeRectangles[j].height;
+					freeRectangles.Delete(j);
+					--j;
+				}
+			}
+			else if (freeRectangles[i].height == freeRectangles[j].height && freeRectangles[i].y == freeRectangles[j].y)
+			{
+				if (freeRectangles[i].x == freeRectangles[j].x + freeRectangles[j].width)
+				{
+					freeRectangles[i].x -= freeRectangles[j].width;
+					freeRectangles[i].width += freeRectangles[j].width;
+					freeRectangles.Delete(j);
+					--j;
+				}
+				else if (freeRectangles[i].x + freeRectangles[i].width == freeRectangles[j].x)
+				{
+					freeRectangles[i].width += freeRectangles[j].width;
+					freeRectangles.Delete(j);
+					--j;
+				}
+			}
+		}
+
+#ifdef _DEBUG
+	test.Clear();
+	for(unsigned i = 0; i < freeRectangles.Size(); ++i)
+		assert(test.Add(freeRectangles[i]) == true);
+#endif
+}
diff --git a/src/GuillotineBinPack.h b/src/GuillotineBinPack.h
new file mode 100644
index 000000000..54cd77a9e
--- /dev/null
+++ b/src/GuillotineBinPack.h
@@ -0,0 +1,135 @@
+/** @file GuillotineBinPack.h
+	@author Jukka Jyl�nki
+
+	@brief Implements different bin packer algorithms that use the GUILLOTINE data structure.
+
+	This work is released to Public Domain, do whatever you want with it.
+*/
+#pragma once
+
+#include "tarray.h"
+
+#include "Rect.h"
+
+/** GuillotineBinPack implements different variants of bin packer algorithms that use the GUILLOTINE data structure
+	to keep track of the free space of the bin where rectangles may be placed. */
+class GuillotineBinPack
+{
+public:
+	/// The initial bin size will be (0,0). Call Init to set the bin size.
+	GuillotineBinPack();
+
+	/// Initializes a new bin of the given size.
+	GuillotineBinPack(int width, int height);
+
+	/// (Re)initializes the packer to an empty bin of width x height units. Call whenever
+	/// you need to restart with a new bin.
+	void Init(int width, int height);
+
+	/// Specifies the different choice heuristics that can be used when deciding which of the free subrectangles
+	/// to place the to-be-packed rectangle into.
+	enum FreeRectChoiceHeuristic
+	{
+		RectBestAreaFit, ///< -BAF
+		RectBestShortSideFit, ///< -BSSF
+		RectBestLongSideFit, ///< -BLSF
+		RectWorstAreaFit, ///< -WAF
+		RectWorstShortSideFit, ///< -WSSF
+		RectWorstLongSideFit ///< -WLSF
+	};
+
+	/// Specifies the different choice heuristics that can be used when the packer needs to decide whether to
+	/// subdivide the remaining free space in horizontal or vertical direction.
+	enum GuillotineSplitHeuristic
+	{
+		SplitShorterLeftoverAxis, ///< -SLAS
+		SplitLongerLeftoverAxis, ///< -LLAS
+		SplitMinimizeArea, ///< -MINAS, Try to make a single big rectangle at the expense of making the other small.
+		SplitMaximizeArea, ///< -MAXAS, Try to make both remaining rectangles as even-sized as possible.
+		SplitShorterAxis, ///< -SAS
+		SplitLongerAxis ///< -LAS
+	};
+
+	/// Inserts a single rectangle into the bin. The packer might rotate the rectangle, in which case the returned
+	/// struct will have the width and height values swapped.
+	/// @param merge If true, performs free Rectangle Merge procedure after packing the new rectangle. This procedure
+	///		tries to defragment the list of disjoint free rectangles to improve packing performance, but also takes up 
+	///		some extra time.
+	/// @param rectChoice The free rectangle choice heuristic rule to use.
+	/// @param splitMethod The free rectangle split heuristic rule to use.
+	Rect Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
+
+	/// Inserts a list of rectangles into the bin.
+	/// @param rects The list of rectangles to add. This list will be destroyed in the packing process.
+	/// @param dst The outputted list of rectangles. Note that the indices will not correspond to the input indices.
+	/// @param merge If true, performs Rectangle Merge operations during the packing process.
+	/// @param rectChoice The free rectangle choice heuristic rule to use.
+	/// @param splitMethod The free rectangle split heuristic rule to use.
+	void Insert(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge, 
+		FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
+
+// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice.
+//	void InsertMaxFitting(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge, 
+//		FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
+
+	/// Computes the ratio of used/total surface area. 0.00 means no space is yet used, 1.00 means the whole bin is used.
+	float Occupancy() const;
+
+	/// Returns the internal list of disjoint rectangles that track the free area of the bin. You may alter this vector
+	/// any way desired, as long as the end result still is a list of disjoint rectangles.
+	TArray<Rect> &GetFreeRectangles() { return freeRectangles; }
+
+	/// Returns the list of packed rectangles. You may alter this vector at will, for example, you can move a Rect from
+	/// this list to the Free Rectangles list to free up space on-the-fly, but notice that this causes fragmentation.
+	TArray<Rect> &GetUsedRectangles() { return usedRectangles; }
+
+	/// Performs a Rectangle Merge operation. This procedure looks for adjacent free rectangles and merges them if they
+	/// can be represented with a single rectangle. Takes up Theta(|freeRectangles|^2) time.
+	void MergeFreeList();
+
+#ifdef _DEBUG
+	void DelDisjoint(const Rect &r) { disjointRects.Del(r); }
+#endif
+
+private:
+	int binWidth;
+	int binHeight;
+
+	/// Stores a list of all the rectangles that we have packed so far. This is used only to compute the Occupancy ratio,
+	/// so if you want to have the packer consume less memory, this can be removed.
+	TArray<Rect> usedRectangles;
+
+	/// Stores a list of rectangles that represents the free area of the bin. This rectangles in this list are disjoint.
+	TArray<Rect> freeRectangles;
+
+#ifdef _DEBUG
+	/// Used to track that the packer produces proper packings.
+	DisjointRectCollection disjointRects;
+#endif
+
+	/// Goes through the list of free rectangles and finds the best one to place a rectangle of given size into.
+	/// Running time is Theta(|freeRectangles|).
+	/// @param nodeIndex [out] The index of the free rectangle in the freeRectangles array into which the new
+	///		rect was placed.
+	/// @return A Rect structure that represents the placement of the new rect into the best free rectangle.
+	Rect FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex);
+
+	static int ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice);
+	// The following functions compute (penalty) score values if a rect of the given size was placed into the 
+	// given free rectangle. In these score values, smaller is better.
+
+	static int ScoreBestAreaFit(int width, int height, const Rect &freeRect);
+	static int ScoreBestShortSideFit(int width, int height, const Rect &freeRect);
+	static int ScoreBestLongSideFit(int width, int height, const Rect &freeRect);
+
+	static int ScoreWorstAreaFit(int width, int height, const Rect &freeRect);
+	static int ScoreWorstShortSideFit(int width, int height, const Rect &freeRect);
+	static int ScoreWorstLongSideFit(int width, int height, const Rect &freeRect);
+
+	/// Splits the given L-shaped free rectangle into two new free rectangles after placedRect has been placed into it.
+	/// Determines the split axis by using the given heuristic.
+	void SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method);
+
+	/// Splits the given L-shaped free rectangle into two new free rectangles along the given fixed split axis.
+	void SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal);
+};
diff --git a/src/Rect.h b/src/Rect.h
new file mode 100644
index 000000000..8b7ba1e2a
--- /dev/null
+++ b/src/Rect.h
@@ -0,0 +1,94 @@
+/** @file Rect.h
+	@author Jukka Jyl�nki
+
+	This work is released to Public Domain, do whatever you want with it.
+*/
+#pragma once
+
+#include <vector>
+
+struct RectSize
+{
+	int width;
+	int height;
+};
+
+struct Rect
+{
+	int x;
+	int y;
+	int width;
+	int height;
+};
+
+/// Performs a lexicographic compare on (rect short side, rect long side).
+/// @return -1 if the smaller side of a is shorter than the smaller side of b, 1 if the other way around.
+///   If they are equal, the larger side length is used as a tie-breaker.
+///   If the rectangles are of same size, returns 0.
+int CompareRectShortSide(const Rect &a, const Rect &b);
+
+/// Performs a lexicographic compare on (x, y, width, height).
+int NodeSortCmp(const Rect &a, const Rect &b);
+
+/// Returns true if a is contained in b.
+bool IsContainedIn(const Rect &a, const Rect &b);
+
+#ifdef _DEBUG
+class DisjointRectCollection
+{
+public:
+	TArray<Rect> rects;
+
+	bool Add(const Rect &r)
+	{
+		// Degenerate rectangles are ignored.
+		if (r.width == 0 || r.height == 0)
+			return true;
+
+		if (!Disjoint(r))
+			return false;
+		rects.Push(r);
+		return true;
+	}
+
+	bool Del(const Rect &r)
+	{
+		for(unsigned i = 0; i < rects.Size(); ++i)
+		{
+			if(r.x == rects[i].x && r.y == rects[i].y && r.width == rects[i].width && r.height == rects[i].height)
+			{
+				rects.Delete(i);
+				return true;
+			}
+		}
+		return false;
+	}
+
+	void Clear()
+	{
+		rects.Clear();
+	}
+
+	bool Disjoint(const Rect &r) const
+	{
+		// Degenerate rectangles are ignored.
+		if (r.width == 0 || r.height == 0)
+			return true;
+
+		for(unsigned i = 0; i < rects.Size(); ++i)
+			if (!Disjoint(rects[i], r))
+				return false;
+		return true;
+	}
+
+	static bool Disjoint(const Rect &a, const Rect &b)
+	{
+		if (a.x + a.width <= b.x ||
+			b.x + b.width <= a.x ||
+			a.y + a.height <= b.y ||
+			b.y + b.height <= a.y)
+			return true;
+		return false;
+	}
+};
+#endif
diff --git a/src/SkylineBinPack.cpp b/src/SkylineBinPack.cpp
new file mode 100644
index 000000000..578a248a5
--- /dev/null
+++ b/src/SkylineBinPack.cpp
@@ -0,0 +1,329 @@
+/** @file SkylineBinPack.cpp
+	@author Jukka Jyl�nki
+
+	@brief Implements different bin packer algorithms that use the SKYLINE data structure.
+
+	This work is released to Public Domain, do whatever you want with it.
+*/
+
+#include <cassert>
+
+#include "SkylineBinPack.h"
+
+using namespace std;
+
+SkylineBinPack::SkylineBinPack()
+:binWidth(0),
+binHeight(0)
+{
+}
+
+SkylineBinPack::SkylineBinPack(int width, int height, bool useWasteMap)
+{
+	Init(width, height, useWasteMap);
+}
+
+void SkylineBinPack::Init(int width, int height, bool useWasteMap_)
+{
+	binWidth = width;
+	binHeight = height;
+
+	useWasteMap = useWasteMap_;
+
+#ifdef _DEBUG
+	disjointRects.Clear();
+#endif
+
+	usedSurfaceArea = 0;
+	skyLine.Clear();
+	SkylineNode node;
+	node.x = 0;
+	node.y = 0;
+	node.width = binWidth;
+	skyLine.Push(node);
+
+	if (useWasteMap)
+	{
+		wasteMap.Init(width, height);
+		wasteMap.GetFreeRectangles().Clear();
+	}
+}
+
+void SkylineBinPack::Insert(TArray<RectSize> &rects, TArray<Rect> &dst)
+{
+	dst.Clear();
+
+	while(rects.Size() > 0)
+	{
+		Rect bestNode;
+		int bestScore1 = INT_MAX;
+		int bestScore2 = INT_MAX;
+		int bestSkylineIndex = -1;
+		int bestRectIndex = -1;
+		for(unsigned i = 0; i < rects.Size(); ++i)
+		{
+			Rect newNode;
+			int score1;
+			int score2;
+			int index;
+			newNode = FindPositionForNewNodeMinWaste(rects[i].width, rects[i].height, score2, score1, index);
+			assert(disjointRects.Disjoint(newNode));
+			if (newNode.height != 0)
+			{
+				if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2))
+				{
+					bestNode = newNode;
+					bestScore1 = score1;
+					bestScore2 = score2;
+					bestSkylineIndex = index;
+					bestRectIndex = i;
+				}
+			}
+		}
+
+		if (bestRectIndex == -1)
+			return;
+
+		// Perform the actual packing.
+		assert(disjointRects.Disjoint(bestNode));
+#ifdef _DEBUG
+		disjointRects.Add(bestNode);
+#endif
+		AddSkylineLevel(bestSkylineIndex, bestNode);
+		usedSurfaceArea += rects[bestRectIndex].width * rects[bestRectIndex].height;
+		rects.Delete(bestRectIndex);
+		dst.Push(bestNode);
+	}
+}
+
+Rect SkylineBinPack::Insert(int width, int height)
+{
+	// First try to pack this rectangle into the waste map, if it fits.
+	Rect node = wasteMap.Insert(width, height, true, GuillotineBinPack::RectBestShortSideFit, 
+		GuillotineBinPack::SplitMaximizeArea);
+	assert(disjointRects.Disjoint(node));
+
+	if (node.height != 0)
+	{
+		Rect newNode;
+		newNode.x = node.x;
+		newNode.y = node.y;
+		newNode.width = node.width;
+		newNode.height = node.height;
+		usedSurfaceArea += width * height;
+		assert(disjointRects.Disjoint(newNode));
+#ifdef _DEBUG
+		disjointRects.Add(newNode);
+#endif
+		return newNode;
+	}
+	
+	return InsertMinWaste(width, height);
+}
+
+bool SkylineBinPack::RectangleFits(int skylineNodeIndex, int width, int height, int &y) const
+{
+	int x = skyLine[skylineNodeIndex].x;
+	if (x + width > binWidth)
+		return false;
+	int widthLeft = width;
+	int i = skylineNodeIndex;
+	y = skyLine[skylineNodeIndex].y;
+	while(widthLeft > 0)
+	{
+		y = max(y, skyLine[i].y);
+		if (y + height > binHeight)
+			return false;
+		widthLeft -= skyLine[i].width;
+		++i;
+		assert(i < (int)skyLine.Size() || widthLeft <= 0);
+	}
+	return true;
+}
+
+int SkylineBinPack::ComputeWastedArea(int skylineNodeIndex, int width, int height, int y) const
+{
+	int wastedArea = 0;
+	const int rectLeft = skyLine[skylineNodeIndex].x;
+	const int rectRight = rectLeft + width;
+	for(; skylineNodeIndex < (int)skyLine.Size() && skyLine[skylineNodeIndex].x < rectRight; ++skylineNodeIndex)
+	{
+		if (skyLine[skylineNodeIndex].x >= rectRight || skyLine[skylineNodeIndex].x + skyLine[skylineNodeIndex].width <= rectLeft)
+			break;
+
+		int leftSide = skyLine[skylineNodeIndex].x;
+		int rightSide = min(rectRight, leftSide + skyLine[skylineNodeIndex].width);
+		assert(y >= skyLine[skylineNodeIndex].y);
+		wastedArea += (rightSide - leftSide) * (y - skyLine[skylineNodeIndex].y);
+	}
+	return wastedArea;
+}
+
+bool SkylineBinPack::RectangleFits(int skylineNodeIndex, int width, int height, int &y, int &wastedArea) const
+{
+	bool fits = RectangleFits(skylineNodeIndex, width, height, y);
+	if (fits)
+		wastedArea = ComputeWastedArea(skylineNodeIndex, width, height, y);
+	
+	return fits;
+}
+
+void SkylineBinPack::AddWasteMapArea(int skylineNodeIndex, int width, int height, int y)
+{
+	int wastedArea = 0;
+	const int rectLeft = skyLine[skylineNodeIndex].x;
+	const int rectRight = rectLeft + width;
+	for(; skylineNodeIndex < (int)skyLine.Size() && skyLine[skylineNodeIndex].x < rectRight; ++skylineNodeIndex)
+	{
+		if (skyLine[skylineNodeIndex].x >= rectRight || skyLine[skylineNodeIndex].x + skyLine[skylineNodeIndex].width <= rectLeft)
+			break;
+
+		int leftSide = skyLine[skylineNodeIndex].x;
+		int rightSide = min(rectRight, leftSide + skyLine[skylineNodeIndex].width);
+		assert(y >= skyLine[skylineNodeIndex].y);
+
+		Rect waste;
+		waste.x = leftSide;
+		waste.y = skyLine[skylineNodeIndex].y;
+		waste.width = rightSide - leftSide;
+		waste.height = y - skyLine[skylineNodeIndex].y;
+
+		assert(disjointRects.Disjoint(waste));
+		wasteMap.GetFreeRectangles().Push(waste);
+	}
+}
+
+void SkylineBinPack::AddWaste(const Rect &waste)
+{
+	wasteMap.GetFreeRectangles().Push(waste);
+#ifdef _DEBUG
+	disjointRects.Del(waste);
+	wasteMap.DelDisjoint(waste);
+#endif
+}
+
+void SkylineBinPack::AddSkylineLevel(int skylineNodeIndex, const Rect &rect)
+{
+	// First track all wasted areas and mark them into the waste map if we're using one.
+	if (useWasteMap)
+		AddWasteMapArea(skylineNodeIndex, rect.width, rect.height, rect.y);
+
+	SkylineNode newNode;
+	newNode.x = rect.x;
+	newNode.y = rect.y + rect.height;
+	newNode.width = rect.width;
+	skyLine.Insert(skylineNodeIndex, newNode);
+
+	assert(newNode.x + newNode.width <= binWidth);
+	assert(newNode.y <= binHeight);
+
+	for(unsigned i = skylineNodeIndex+1; i < skyLine.Size(); ++i)
+	{
+		assert(skyLine[i-1].x <= skyLine[i].x);
+
+		if (skyLine[i].x < skyLine[i-1].x + skyLine[i-1].width)
+		{
+			int shrink = skyLine[i-1].x + skyLine[i-1].width - skyLine[i].x;
+
+			skyLine[i].x += shrink;
+			skyLine[i].width -= shrink;
+
+			if (skyLine[i].width <= 0)
+			{
+				skyLine.Delete(i);
+				--i;
+			}
+			else
+				break;
+		}
+		else
+			break;
+	}
+	MergeSkylines();
+}
+
+void SkylineBinPack::MergeSkylines()
+{
+	for(unsigned i = 0; i < skyLine.Size()-1; ++i)
+		if (skyLine[i].y == skyLine[i+1].y)
+		{
+			skyLine[i].width += skyLine[i+1].width;
+			skyLine.Delete(i+1);
+			--i;
+		}
+}
+
+Rect SkylineBinPack::InsertMinWaste(int width, int height)
+{
+	int bestHeight;
+	int bestWastedArea;
+	int bestIndex;
+	Rect newNode = FindPositionForNewNodeMinWaste(width, height, bestHeight, bestWastedArea, bestIndex);
+
+	if (bestIndex != -1)
+	{
+		assert(disjointRects.Disjoint(newNode));
+		// Perform the actual packing.
+		AddSkylineLevel(bestIndex, newNode);
+
+		usedSurfaceArea += width * height;
+#ifdef _DEBUG
+		disjointRects.Add(newNode);
+#endif
+	}
+	else
+		memset(&newNode, 0, sizeof(newNode));
+
+	return newNode;
+}
+
+Rect SkylineBinPack::FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const
+{
+	bestHeight = INT_MAX;
+	bestWastedArea = INT_MAX;
+	bestIndex = -1;
+	Rect newNode;
+	memset(&newNode, 0, sizeof(newNode));
+	for(unsigned i = 0; i < skyLine.Size(); ++i)
+	{
+		int y;
+		int wastedArea;
+
+		if (RectangleFits(i, width, height, y, wastedArea))
+		{
+			if (wastedArea < bestWastedArea || (wastedArea == bestWastedArea && y + height < bestHeight))
+			{
+				bestHeight = y + height;
+				bestWastedArea = wastedArea;
+				bestIndex = i;
+				newNode.x = skyLine[i].x;
+				newNode.y = y;
+				newNode.width = width;
+				newNode.height = height;
+				assert(disjointRects.Disjoint(newNode));
+			}
+		}
+/*		if (RectangleFits(i, height, width, y, wastedArea))
+		{
+			if (wastedArea < bestWastedArea || (wastedArea == bestWastedArea && y + width < bestHeight))
+			{
+				bestHeight = y + width;
+				bestWastedArea = wastedArea;
+				bestIndex = i;
+				newNode.x = skyLine[i].x;
+				newNode.y = y;
+				newNode.width = height;
+				newNode.height = width;
+				assert(disjointRects.Disjoint(newNode));
+			}
+		}*/
+	}
+
+	return newNode;
+}
+
+/// Computes the ratio of used surface area.
+float SkylineBinPack::Occupancy() const
+{
+	return (float)usedSurfaceArea / (binWidth * binHeight);
+}
diff --git a/src/SkylineBinPack.h b/src/SkylineBinPack.h
new file mode 100644
index 000000000..f93ddd29d
--- /dev/null
+++ b/src/SkylineBinPack.h
@@ -0,0 +1,88 @@
+/** @file SkylineBinPack.h
+	@author Jukka Jyl�nki
+
+	@brief Implements different bin packer algorithms that use the SKYLINE data structure.
+
+	This work is released to Public Domain, do whatever you want with it.
+*/
+#pragma once
+
+#include "tarray.h"
+
+#include "Rect.h"
+#include "GuillotineBinPack.h"
+
+/** Implements bin packing algorithms that use the SKYLINE data structure to store the bin contents. Uses
+	GuillotineBinPack as the waste map. */
+class SkylineBinPack
+{
+public:
+	/// Instantiates a bin of size (0,0). Call Init to create a new bin.
+	SkylineBinPack();
+
+	/// Instantiates a bin of the given size.
+	SkylineBinPack(int binWidth, int binHeight, bool useWasteMap);
+
+	/// (Re)initializes the packer to an empty bin of width x height units. Call whenever
+	/// you need to restart with a new bin.
+	void Init(int binWidth, int binHeight, bool useWasteMap);
+
+	/// Inserts the given list of rectangles in an offline/batch mode, possibly rotated.
+	/// @param rects The list of rectangles to insert. This vector will be destroyed in the process.
+	/// @param dst [out] This list will contain the packed rectangles. The indices will not correspond to that of rects.
+	/// @param method The rectangle placement rule to use when packing.
+	void Insert(TArray<RectSize> &rects, TArray<Rect> &dst);
+
+	/// Inserts a single rectangle into the bin, possibly rotated.
+	Rect Insert(int width, int height);
+
+	/// Adds a rectangle to the waste list. It must have been previously returned by
+	/// Insert or the results are undefined.
+	void AddWaste(const Rect &rect);
+
+	/// Computes the ratio of used surface area to the total bin area.
+	float Occupancy() const;
+
+private:
+	int binWidth;
+	int binHeight;
+
+#ifdef _DEBUG
+	DisjointRectCollection disjointRects;
+#endif
+
+	/// Represents a single level (a horizontal line) of the skyline/horizon/envelope.
+	struct SkylineNode
+	{
+		/// The starting x-coordinate (leftmost).
+		int x;
+
+		/// The y-coordinate of the skyline level line.
+		int y;
+
+		/// The line width. The ending coordinate (inclusive) will be x+width-1.
+		int width;
+	};
+
+	TArray<SkylineNode> skyLine;
+
+	unsigned long usedSurfaceArea;
+
+	/// If true, we use the GuillotineBinPack structure to recover wasted areas into a waste map.
+	bool useWasteMap;
+	GuillotineBinPack wasteMap;
+
+	Rect InsertMinWaste(int width, int height);
+	Rect FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const;
+
+	bool RectangleFits(int skylineNodeIndex, int width, int height, int &y) const;
+	bool RectangleFits(int skylineNodeIndex, int width, int height, int &y, int &wastedArea) const;
+	int ComputeWastedArea(int skylineNodeIndex, int width, int height, int y) const;
+
+	void AddWasteMapArea(int skylineNodeIndex, int width, int height, int y);
+
+	void AddSkylineLevel(int skylineNodeIndex, const Rect &rect);
+
+	/// Merges all skyline nodes that are at the same level.
+	void MergeSkylines();
+};
diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp
index 273fcde44..1641df365 100644
--- a/src/win32/fb_d3d9.cpp
+++ b/src/win32/fb_d3d9.cpp
@@ -72,6 +72,7 @@
 #include "v_palette.h"
 #include "w_wad.h"
 #include "r_data/colormaps.h"
+#include "SkylineBinPack.h"
 
 // MACROS ------------------------------------------------------------------
 
@@ -92,7 +93,7 @@ struct D3DFB::PackedTexture
 {
 	D3DFB::Atlas *Owner;
 
-	PackedTexture *Next, **Prev;
+	PackedTexture **Prev, *Next;
 
 	// Pixels this image covers
 	RECT Area;
@@ -109,18 +110,14 @@ struct D3DFB::Atlas
 	Atlas(D3DFB *fb, int width, int height, D3DFORMAT format);
 	~Atlas();
 
-	PackedTexture *GetBestFit(int width, int height, int &area);
-	void AllocateImage(PackedTexture *box, int width, int height);
-	PackedTexture *AllocateBox();
-	void AddEmptyBox(int left, int top, int right, int bottom);
+	PackedTexture *AllocateImage(const Rect &rect, bool padded);
 	void FreeBox(PackedTexture *box);
 
+	SkylineBinPack Packer;
 	Atlas *Next;
 	IDirect3DTexture9 *Tex;
 	D3DFORMAT Format;
 	PackedTexture *UsedList;	// Boxes that contain images
-	PackedTexture *EmptyList;	// Boxes that contain empty space
-	PackedTexture *FreeList;	// Boxes that are just waiting to be used
 	int Width, Height;
 	bool OneUse;
 };
@@ -1871,7 +1868,8 @@ void D3DFB::DrawPackedTextures(int packnum)
 			continue;
 		}
 
-		AddColorOnlyQuad(x-1, y-1-LBOffsetI, 258, 258, D3DCOLOR_XRGB(255,255,0));
+		AddColorOnlyRect(x-1, y-1-LBOffsetI, 258, 258, D3DCOLOR_XRGB(255,255,0));
+		AddColorOnlyQuad(x, y-LBOffsetI, 256, 256, D3DCOLOR_ARGB(180,0,0,0));
 
 		CheckQuadBatch();
 
@@ -1881,12 +1879,12 @@ void D3DFB::DrawPackedTextures(int packnum)
 		quad->Group1 = 0;
 		if (pack->Format == D3DFMT_L8/* && !tex->IsGray*/)
 		{
-			quad->Flags = BQF_WrapUV | BQF_GamePalette | BQF_DisableAlphaTest;
+			quad->Flags = BQF_WrapUV | BQF_GamePalette/* | BQF_DisableAlphaTest*/;
 			quad->ShaderNum = BQS_PalTex;
 		}
 		else
 		{
-			quad->Flags = BQF_WrapUV | BQF_DisableAlphaTest;
+			quad->Flags = BQF_WrapUV/* | BQF_DisableAlphaTest*/;
 			quad->ShaderNum = BQS_Plain;
 		}
 		quad->Palette = NULL;
@@ -1946,16 +1944,6 @@ void D3DFB::DrawPackedTextures(int packnum)
 		VertexPos += 4;
 		IndexPos += 6;
 
-		// Draw entries in the empty list.
-		PackedTexture *box;
-		int emptynum;
-		for (box = pack->EmptyList, emptynum = 0; box != NULL; box = box->Next, emptynum++)
-		{
-			AddColorOnlyQuad(x + box->Area.left, y + box->Area.top - LBOffsetI,
-				box->Area.right - box->Area.left, box->Area.bottom - box->Area.top,
-				empty_colors[emptynum & 7]);
-		}
-
 		x += 256 + 8;
 		if (x > Width - 256)
 		{
@@ -1981,54 +1969,43 @@ void D3DFB::DrawPackedTextures(int packnum)
 
 D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3DFORMAT format)
 {
-	Atlas *bestpack;
-	PackedTexture *bestbox;
-	int area;
+	Atlas *pack;
+	Rect box;
+	bool padded;
 
 	// check for 254 to account for padding
 	if (w > 254 || h > 254 || wrapping)
 	{ // Create a new texture atlas.
-		bestpack = new Atlas(this, w, h, format);
-		bestpack->OneUse = true;
-		bestbox = bestpack->GetBestFit(w, h, area);
-		bestbox->Padded = false;
+		pack = new Atlas(this, w, h, format);
+		pack->OneUse = true;
+		box = pack->Packer.Insert(w, h);
+		padded = false;
 	}
 	else
 	{ // Try to find space in an existing texture atlas.
 		w += 2; // Add padding
 		h += 2;
-		int bestarea = INT_MAX;
-		int bestareaever = w * h;
-		bestpack = NULL;
-		bestbox = NULL;
-		for (Atlas *pack = Atlases; pack != NULL; pack = pack->Next)
+		for (pack = Atlases; pack != NULL; pack = pack->Next)
 		{
+			// Use the first atlas it fits in.
 			if (pack->Format == format)
 			{
-				PackedTexture *box = pack->GetBestFit(w, h, area);
-				if (area == bestareaever)
-				{ // An exact fit! Use it!
-					bestpack = pack;
-					bestbox = box;
-					break;
-				}
-				if (area < bestarea)
+				box = pack->Packer.Insert(w, h);
+				if (box.width != 0)
 				{
-					bestarea = area;
-					bestpack = pack;
-					bestbox = box;
+					break;
 				}
 			}
 		}
-		if (bestpack == NULL)
+		if (pack == NULL)
 		{ // Create a new texture atlas.
-			bestpack = new Atlas(this, 256, 256, format);
-			bestbox = bestpack->GetBestFit(w, h, bestarea);
+			pack = new Atlas(this, 256, 256, format);
+			box = pack->Packer.Insert(w, h);
 		}
-		bestbox->Padded = true;
+		padded = true;
 	}
-	bestpack->AllocateImage(bestbox, w, h);
-	return bestbox;
+	assert(box.width != 0 && box.height != 0);
+	return pack->AllocateImage(box, padded);
 }
 
 //==========================================================================
@@ -2038,18 +2015,23 @@ D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3D
 //==========================================================================
 
 D3DFB::Atlas::Atlas(D3DFB *fb, int w, int h, D3DFORMAT format)
+	: Packer(w, h, true)
 {
 	Tex = NULL;
 	Format = format;
 	UsedList = NULL;
-	EmptyList = NULL;
-	FreeList = NULL;
 	OneUse = false;
 	Width = 0;
 	Height = 0;
+	Next = NULL;
 
-	Next = fb->Atlases;
-	fb->Atlases = this;
+	// Attach to the end of the atlas list
+	Atlas **prev = &fb->Atlases;
+	while (*prev != NULL)
+	{
+		prev = &((*prev)->Next);
+	}
+	*prev = this;
 
 #if 1
 	if (FAILED(fb->D3DDevice->CreateTexture(w, h, 1, 0, format, D3DPOOL_MANAGED, &Tex, NULL)))
@@ -2066,9 +2048,6 @@ D3DFB::Atlas::Atlas(D3DFB *fb, int w, int h, D3DFORMAT format)
 	}
 	Width = w;
 	Height = h;
-
-	// The whole texture is initially empty.
-	AddEmptyBox(0, 0, w, h);
 }
 
 //==========================================================================
@@ -2087,53 +2066,6 @@ D3DFB::Atlas::~Atlas()
 		next = box->Next;
 		delete box;
 	}
-	for (box = EmptyList; box != NULL; box = next)
-	{
-		next = box->Next;
-		delete box;
-	}
-	for (box = FreeList; box != NULL; box = next)
-	{
-		next = box->Next;
-		delete box;
-	}
-}
-
-//==========================================================================
-//
-// Atlas :: GetBestFit
-//
-// Returns the empty box that provides the best fit for the requested
-// dimensions, or NULL if none of them are large enough.
-//
-//==========================================================================
-
-D3DFB::PackedTexture *D3DFB::Atlas::GetBestFit(int w, int h, int &area)
-{
-	PackedTexture *box;
-	int smallestarea = INT_MAX;
-	PackedTexture *smallestbox = NULL;
-
-	for (box = EmptyList; box != NULL; box = box->Next)
-	{
-		int boxw = box->Area.right - box->Area.left;
-		int boxh = box->Area.bottom - box->Area.top;
-		if (boxw >= w && boxh >= h)
-		{
-			int boxarea = boxw * boxh;
-			if (boxarea < smallestarea)
-			{
-				smallestarea = boxarea;
-				smallestbox = box;
-				if (boxw == w && boxh == h)
-				{ // An exact fit! Use it!
-					break;
-				}
-			}
-		}
-	}
-	area = smallestarea;
-	return smallestbox;
 }
 
 //==========================================================================
@@ -2148,24 +2080,22 @@ D3DFB::PackedTexture *D3DFB::Atlas::GetBestFit(int w, int h, int &area)
 //
 //==========================================================================
 
-void D3DFB::Atlas::AllocateImage(D3DFB::PackedTexture *box, int w, int h)
+D3DFB::PackedTexture *D3DFB::Atlas::AllocateImage(const Rect &rect, bool padded)
 {
-	RECT start = box->Area;
+	PackedTexture *box = new PackedTexture;
 
-	box->Area.right = box->Area.left + w;
-	box->Area.bottom = box->Area.top + h;
+	box->Owner = this;
+	box->Area.left = rect.x;
+	box->Area.top = rect.y;
+	box->Area.right = rect.x + rect.width;
+	box->Area.bottom = rect.y + rect.height;
 
-	box->Left = float(box->Area.left + box->Padded) / Width;
-	box->Right = float(box->Area.right - box->Padded) / Width;
-	box->Top = float(box->Area.top + box->Padded) / Height;
-	box->Bottom = float(box->Area.bottom - box->Padded) / Height;
+	box->Left = float(box->Area.left + padded) / Width;
+	box->Right = float(box->Area.right - padded) / Width;
+	box->Top = float(box->Area.top + padded) / Height;
+	box->Bottom = float(box->Area.bottom - padded) / Height;
 
-	// Remove it from the empty list.
-	*(box->Prev) = box->Next;
-	if (box->Next != NULL)
-	{
-		box->Next->Prev = box->Prev;
-	}
+	box->Padded = padded;
 
 	// Add it to the used list.
 	box->Next = UsedList;
@@ -2176,96 +2106,6 @@ void D3DFB::Atlas::AllocateImage(D3DFB::PackedTexture *box, int w, int h)
 	UsedList = box;
 	box->Prev = &UsedList;
 
-	// If we didn't use the whole box, split the remainder into the empty list.
-	if (box->Area.bottom + 7 < start.bottom && box->Area.right + 7 < start.right)
-	{
-		// Split like this:
-		//   +---+------+
-		//   |###|      |
-		//   +---+------+
-		//   |          |
-		//   |          |
-		//   +----------+
-		if (box->Area.bottom < start.bottom)
-		{
-			AddEmptyBox(start.left, box->Area.bottom, start.right, start.bottom);
-		}
-		if (box->Area.right < start.right)
-		{
-			AddEmptyBox(box->Area.right, start.top, start.right, box->Area.bottom);
-		}
-	}
-	else
-	{
-		// Split like this:
-		//   +---+------+
-		//   |###|      |
-		//   +---+      |
-		//   |   |      |
-		//   |   |      |
-		//   +---+------+
-		if (box->Area.bottom < start.bottom)
-		{
-			AddEmptyBox(start.left, box->Area.bottom, box->Area.right, start.bottom);
-		}
-		if (box->Area.right < start.right)
-		{
-			AddEmptyBox(box->Area.right, start.top, start.right, start.bottom);
-		}
-	}
-}
-
-//==========================================================================
-//
-// Atlas :: AddEmptyBox
-//
-// Adds a box with the specified dimensions to the empty list.
-//
-//==========================================================================
-
-void D3DFB::Atlas::AddEmptyBox(int left, int top, int right, int bottom)
-{
-	PackedTexture *box = AllocateBox();
-	box->Area.left = left;
-	box->Area.top = top;
-	box->Area.right = right;
-	box->Area.bottom = bottom;
-	box->Next = EmptyList;
-	if (box->Next != NULL)
-	{
-		box->Next->Prev = &box->Next;
-	}
-	box->Prev = &EmptyList;
-	EmptyList = box;
-}
-
-//==========================================================================
-//
-// Atlas :: AllocateBox
-//
-// Returns a new PackedTexture box, either by retrieving one off the free
-// list or by creating a new one. The box is not linked into a list.
-//
-//==========================================================================
-
-D3DFB::PackedTexture *D3DFB::Atlas::AllocateBox()
-{
-	PackedTexture *box;
-
-	if (FreeList != NULL)
-	{
-		box = FreeList;
-		FreeList = box->Next;
-		if (box->Next != NULL)
-		{
-			box->Next->Prev = &FreeList;
-		}
-	}
-	else
-	{
-		box = new PackedTexture;
-		box->Owner = this;
-	}
 	return box;
 }
 
@@ -2273,10 +2113,9 @@ D3DFB::PackedTexture *D3DFB::Atlas::AllocateBox()
 //
 // Atlas :: FreeBox
 //
-// Removes a box from its current list and adds it to the empty list,
-// updating EmptyArea. If there are no boxes left in the used list, then
-// the empty list is replaced with a single box, so the texture can be
-// subdivided again.
+// Removes a box from the used list and deletes it. Space is returned to the
+// waste list. Once all boxes for this atlas are freed, the entire bin
+// packer is reinitialized for maximum efficiency.
 //
 //==========================================================================
 
@@ -2287,47 +2126,16 @@ void D3DFB::Atlas::FreeBox(D3DFB::PackedTexture *box)
 	{
 		box->Next->Prev = box->Prev;
 	}
-	box->Next = EmptyList;
-	box->Prev = &EmptyList;
-	if (EmptyList != NULL)
-	{
-		EmptyList->Prev = &box->Next;
-	}
-	EmptyList = box;
+	Rect waste;
+	waste.x = box->Area.left;
+	waste.y = box->Area.top;
+	waste.width = box->Area.right - box->Area.left;
+	waste.height = box->Area.bottom - box->Area.top;
+	box->Owner->Packer.AddWaste(waste);
+	delete box;
 	if (UsedList == NULL)
-	{ // No more space in use! Move all but this into the free list.
-		if (box->Next != NULL)
-		{
-			D3DFB::PackedTexture *lastbox;
-
-			// Find the last box in the free list.
-			lastbox = FreeList;
-			if (lastbox != NULL)
-			{
-				while (lastbox->Next != NULL)
-				{
-					lastbox = lastbox->Next;
-				}
-			}
-			// Chain the empty list to the end of the free list.
-			if (lastbox != NULL)
-			{
-				lastbox->Next = box->Next;
-				box->Next->Prev = &lastbox->Next;
-			}
-			else
-			{
-				FreeList = box->Next;
-				box->Next->Prev = &FreeList;
-			}
-			box->Next = NULL;
-		}
-		// Now this is the only box in the empty list, so it should
-		// contain the whole texture.
-		box->Area.left = 0;
-		box->Area.top = 0;
-		box->Area.right = Width;
-		box->Area.bottom = Height;
+	{
+		Packer.Init(Width, Height, true);
 	}
 }
 
@@ -2412,6 +2220,7 @@ bool D3DTex::CheckWrapping(bool wrapping)
 
 bool D3DTex::Create(D3DFB *fb, bool wrapping)
 {
+	assert(Box == NULL);
 	if (Box != NULL)
 	{
 		Box->Owner->FreeBox(Box);
@@ -3441,6 +3250,22 @@ void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR
 	IndexPos += 6;
 }
 
+//==========================================================================
+//
+// D3DFB :: AddColorOnlyRect
+//
+// Like AddColorOnlyQuad, except it's hollow.
+//
+//==========================================================================
+
+void D3DFB::AddColorOnlyRect(int left, int top, int width, int height, D3DCOLOR color)
+{
+	AddColorOnlyQuad(left, top, width - 1, 1, color);					// top
+	AddColorOnlyQuad(left + width - 1, top, 1, height - 1, color);		// right
+	AddColorOnlyQuad(left + 1, top + height - 1, width - 1, 1, color);	// bottom
+	AddColorOnlyQuad(left, top + 1, 1, height - 1, color);				// left
+}
+
 //==========================================================================
 //
 // D3DFB :: CheckQuadBatch
diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h
index a64b1dd82..8b0c88f45 100644
--- a/src/win32/win32iface.h
+++ b/src/win32/win32iface.h
@@ -375,6 +375,7 @@ private:
 	static void SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1);
 	void DoWindowedGamma();
 	void AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color);
+	void AddColorOnlyRect(int left, int top, int width, int height, D3DCOLOR color);
 	void CheckQuadBatch(int numtris=2, int numverts=4);
 	void BeginQuadBatch();
 	void EndQuadBatch();
diff --git a/zdoom.vcproj b/zdoom.vcproj
index a2587840f..597b56f2f 100644
--- a/zdoom.vcproj
+++ b/zdoom.vcproj
@@ -666,6 +666,10 @@
 				RelativePath=".\src\gitinfo.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\src\GuillotineBinPack.cpp"
+				>
+			</File>
 			<File
 				RelativePath=".\src\hu_scores.cpp"
 				>
@@ -1026,6 +1030,10 @@
 				RelativePath=".\src\skins.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\src\SkylineBinPack.cpp"
+				>
+			</File>
 			<File
 				RelativePath=".\src\st_stuff.cpp"
 				>
@@ -1311,6 +1319,10 @@
 				RelativePath=".\src\gstrings.h"
 				>
 			</File>
+			<File
+				RelativePath=".\src\GuillotineBinPack.h"
+				>
+			</File>
 			<File
 				RelativePath=".\src\hu_stuff.h"
 				>
@@ -1491,6 +1503,10 @@
 				RelativePath=".\src\po_man.h"
 				>
 			</File>
+			<File
+				RelativePath=".\src\Rect.h"
+				>
+			</File>
 			<File
 				RelativePath=".\src\s_playlist.h"
 				>
@@ -1515,6 +1531,10 @@
 				RelativePath=".\src\skins.h"
 				>
 			</File>
+			<File
+				RelativePath=".\src\SkylineBinPack.h"
+				>
+			</File>
 			<File
 				RelativePath=".\src\st_start.h"
 				>

From 786caaf36b31d27d717a292ca6ef27fc1bde615d Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Fri, 8 Jan 2016 22:41:23 -0600
Subject: [PATCH 6/6] Execute disconnect scripts immediately before the player
 is destroyed.

- Disconnect scripts were previously run at some point after the player
  left. Now they are run immediately before destroying the player. Since
  the player hasn't actually been destroyed yet, the player also gets to
  be the script's activator. This gives you a chance to scrape whatever data
  you want from the player before they're history. Note that if you do
  anything to make the script wait, the script's activator will become the
  world, as it was before.
---
 src/b_game.cpp | 2 +-
 src/g_game.cpp | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/b_game.cpp b/src/b_game.cpp
index e136f3f29..5f852d043 100644
--- a/src/b_game.cpp
+++ b/src/b_game.cpp
@@ -423,8 +423,8 @@ void FCajunMaster::RemoveAllBots (bool fromlist)
 					}
 				}
 			}
+			FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, players[i].mo, true, i, true);
 			ClearPlayer (i, !fromlist);
-			FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, i);
 		}
 	}
 
diff --git a/src/g_game.cpp b/src/g_game.cpp
index 8101ca23d..64e746cef 100644
--- a/src/g_game.cpp
+++ b/src/g_game.cpp
@@ -1713,6 +1713,8 @@ void G_DoPlayerPop(int playernum)
 
 	// [RH] Make the player disappear
 	FBehavior::StaticStopMyScripts(players[playernum].mo);
+	// [RH] Let the scripts know the player left
+	FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, players[playernum].mo, true, playernum, true);
 	if (players[playernum].mo != NULL)
 	{
 		P_DisconnectEffect(players[playernum].mo);
@@ -1726,8 +1728,6 @@ void G_DoPlayerPop(int playernum)
 		players[playernum].mo = NULL;
 		players[playernum].camera = NULL;
 	}
-	// [RH] Let the scripts know the player left
-	FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, NULL, true, playernum);
 }
 
 void G_ScreenShot (char *filename)