diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 992e8bb14..1bfe9ab7c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -658,6 +658,7 @@ add_executable( zdoom WIN32
 	m_misc.cpp
 	m_png.cpp
 	m_random.cpp
+	memarena.cpp
 	md5.cpp
 	name.cpp
 	nodebuild.cpp
diff --git a/src/asm_ia32/a.asm b/src/asm_ia32/a.asm
index d68fba116..eaf747455 100644
--- a/src/asm_ia32/a.asm
+++ b/src/asm_ia32/a.asm
@@ -28,6 +28,9 @@
 %define setupmvlineasm		_setupmvlineasm
 %define mvlineasm1		_mvlineasm1
 %define mvlineasm4		_mvlineasm4
+
+%define R_SetupDrawSlab	_R_SetupDrawSlab
+%define R_DrawSlab		_R_DrawSlab
 %endif
 
 EXTERN ylookup ; near
@@ -44,9 +47,6 @@ EXTERN dc_dest
 EXTERN dc_source
 EXTERN dc_texturefrac
 
-mvlineasm4_counter:
-	dd 0
-
 	SECTION .text
 
 ALIGN 16
@@ -59,8 +59,45 @@ setvlinebpl_:
 	mov [fixchain2ma+2], eax
 	mov [fixchain2mb+2], eax
 	selfmod fixchain1a, fixchain2mb+6
+
+setdrawslabbpl:
+	mov dword [voxbpl1+2], eax
+	mov dword [voxbpl2+2], eax
+	mov dword [voxbpl3+2], eax
+	mov dword [voxbpl4+2], eax
+	mov dword [voxbpl5+2], eax
+	mov dword [voxbpl6+2], eax
+	mov dword [voxbpl7+2], eax
+	mov dword [voxbpl8+2], eax
+	selfmod voxbpl1, voxpl8+6
 	ret
 
+	SECTION .data
+	
+lastslabcolormap:
+	dd 4
+	
+	SECTION .text
+
+GLOBAL R_SetupDrawSlab
+GLOBAL @R_SetupDrawSlab@4
+R_SetupDrawSlab:
+	mov ecx, [esp+4]
+@R_SetupDrawSlab@4:
+	cmp [lastslabcolormap], ecx
+	je .done
+	mov [lastslabcolormap], ecx
+	mov dword [voxpal1+2], ecx
+	mov dword [voxpal2+2], ecx
+	mov dword [voxpal3+2], ecx
+	mov dword [voxpal4+2], ecx
+	mov dword [voxpal5+2], ecx
+	mov dword [voxpal6+2], ecx
+	mov dword [voxpal7+2], ecx
+	mov dword [voxpal8+2], ecx
+.done ret
+
+
 ; pass it log2(texheight)
 
 ALIGN 16
@@ -549,6 +586,226 @@ mvcase0:	jmp beginmvlineasm4
 
 align 16
 
+
+;*************************************************************************
+;***************************** Voxel Slabs *******************************
+;*************************************************************************
+
+GLOBAL R_DrawSlab
+R_DrawSlab:
+	push ebx
+	push ebp
+	push esi
+	push edi
+	
+	mov eax, [esp+5*4+0]
+	mov ebx, [esp+5*4+4]
+	mov ecx, [esp+5*4+8]
+	mov edx, [esp+5*4+12]
+	mov esi, [esp+5*4+16]
+	mov edi, [esp+5*4+20]
+	
+	cmp eax, 2
+	je voxbegdraw2
+	ja voxskip2
+	xor eax, eax
+voxbegdraw1:
+	mov ebp, ebx
+	shr ebp, 16
+	add ebx, edx
+	dec ecx
+	mov al, byte [esi+ebp]
+voxpal1: mov al, byte [eax+88888888h]
+	mov byte [edi], al
+voxbpl1: lea edi, [edi+88888888h]
+	jnz voxbegdraw1
+	jmp voxskipslab5
+
+voxbegdraw2:
+	mov ebp, ebx
+	shr ebp, 16
+	add ebx, edx
+	xor eax, eax
+	dec ecx
+	mov al, byte [esi+ebp]
+voxpal2: mov al, byte [eax+88888888h]
+	mov ah, al
+	mov word [edi], ax
+voxbpl2: lea edi, [edi+88888888h]
+	jnz voxbegdraw2
+	jmp voxskipslab5
+
+voxskip2:
+	cmp eax, 4
+	jne voxskip4
+	xor eax, eax
+voxbegdraw4:
+	mov ebp, ebx
+	add ebx, edx
+	shr ebp, 16
+	xor eax, eax
+	mov al, byte [esi+ebp]
+voxpal3: mov al, byte [eax+88888888h]
+	mov ah, al
+	shl eax, 8
+	mov al, ah
+	shl eax, 8
+	mov al, ah
+	mov dword [edi], eax
+voxbpl3: add edi, 88888888h
+	dec ecx
+	jnz voxbegdraw4
+	jmp voxskipslab5
+
+voxskip4:
+	add eax, edi
+
+	test edi, 1
+	jz voxskipslab1
+	cmp edi, eax
+	je voxskipslab1
+
+	push eax
+	push ebx
+	push ecx
+	push edi
+voxbegslab1:
+	mov ebp, ebx
+	add ebx, edx
+	shr ebp, 16
+	xor eax, eax
+	mov al, byte [esi+ebp]
+voxpal4: mov al, byte [eax+88888888h]
+	mov byte [edi], al
+voxbpl4: add edi, 88888888h
+	dec ecx
+	jnz voxbegslab1
+	pop edi
+	pop ecx
+	pop ebx
+	pop eax
+	inc edi
+
+voxskipslab1:
+	push eax
+	test edi, 2
+	jz voxskipslab2
+	dec eax
+	cmp edi, eax
+	jge voxskipslab2
+
+	push ebx
+	push ecx
+	push edi
+voxbegslab2:
+	mov ebp, ebx
+	add ebx, edx
+	shr ebp, 16
+	xor eax, eax
+	mov al, byte [esi+ebp]
+voxpal5: mov al, byte [eax+88888888h]
+	mov ah, al
+	mov word [edi], ax
+voxbpl5: add edi, 88888888h
+	dec ecx
+	jnz voxbegslab2
+	pop edi
+	pop ecx
+	pop ebx
+	add edi, 2
+
+voxskipslab2:
+	mov eax, [esp]
+
+	sub eax, 3
+	cmp edi, eax
+	jge voxskipslab3
+
+voxprebegslab3:
+	push ebx
+	push ecx
+	push edi
+voxbegslab3:
+	mov ebp, ebx
+	add ebx, edx
+	shr ebp, 16
+	xor eax, eax
+	mov al, byte [esi+ebp]
+voxpal6: mov al, byte [eax+88888888h]
+	mov ah, al
+	shl eax, 8
+	mov al, ah
+	shl eax, 8
+	mov al, ah
+	mov dword [edi], eax
+voxbpl6: add edi, 88888888h
+	dec ecx
+	jnz voxbegslab3
+	pop edi
+	pop ecx
+	pop ebx
+	add edi, 4
+
+	mov eax, [esp]
+
+	sub eax, 3
+	cmp edi, eax
+	jl voxprebegslab3
+
+voxskipslab3:
+	mov eax, [esp]
+
+	dec eax
+	cmp edi, eax
+	jge voxskipslab4
+
+	push ebx
+	push ecx
+	push edi
+voxbegslab4:
+	mov ebp, ebx
+	add ebx, edx
+	shr ebp, 16
+	xor eax, eax
+	mov al, byte [esi+ebp]
+voxpal7: mov al, byte [eax+88888888h]
+	mov ah, al
+	mov word [edi], ax
+voxbpl7: add edi, 88888888h
+	dec ecx
+	jnz voxbegslab4
+	pop edi
+	pop ecx
+	pop ebx
+	add edi, 2
+
+voxskipslab4:
+	pop eax
+
+	cmp edi, eax
+	je voxskipslab5
+
+voxbegslab5:
+	mov ebp, ebx
+	add ebx, edx
+	shr ebp, 16
+	xor eax, eax
+	mov al, byte [esi+ebp]
+voxpal8: mov al, byte [eax+88888888h]
+	mov byte [edi], al
+voxbpl8: add edi, 88888888h
+	dec ecx
+	jnz voxbegslab5
+
+voxskipslab5:
+	pop edi
+	pop esi
+	pop ebp
+	pop ebx
+	ret
+
+align 16
+
 %ifdef M_TARGET_MACHO
 GLOBAL _rtext_a_end
 _rtext_a_end:
diff --git a/src/asm_ia32/tmap.asm b/src/asm_ia32/tmap.asm
index cbcd9f4f1..a32b1492e 100644
--- a/src/asm_ia32/tmap.asm
+++ b/src/asm_ia32/tmap.asm
@@ -850,8 +850,8 @@ GLOBAL	R_DrawColumnHorizP_ASM
 	align 16
 
 @R_DrawColumnHorizP_ASM@0:
-R_DrawColumnHorizP_ASM:
 _R_DrawColumnHorizP_ASM:
+R_DrawColumnHorizP_ASM:
 
 ; count = dc_yh - dc_yl;
 
@@ -870,8 +870,10 @@ _R_DrawColumnHorizP_ASM:
 	inc	eax			; make 0 count mean 0 pixels
 	 and	edx,3
 	push	eax
-	 mov	esi,[dc_ctspan+edx*4]
-	lea	eax,[dc_temp+ecx*4+edx] ; eax = top of column in buffer
+	 mov	eax,[dc_temp]
+	mov	esi,[dc_ctspan+edx*4]
+	 add	eax,edx
+	lea	eax,[eax+ecx*4] ; eax = top of column in buffer
 	 mov	ebp,[dc_yh]
 	mov	[esi],ecx
 	 mov	[esi+4],ebp
@@ -1102,8 +1104,9 @@ _rt_copy1col_asm:
 	lea	esi,[eax*4]
 	inc	ebx			; ebx = count
 	mov	eax,edx
-	lea	ecx,[dc_temp+ecx+esi]	; ecx = source
+	add ecx,esi
 	mov	edi,[ylookup+esi]
+	add ecx,[dc_temp]	; ecx = source
 	mov	esi,[dc_pitch]		; esi = pitch
 	add	eax,edi			; eax = dest
 	add	eax,[dc_destorg]
@@ -1169,10 +1172,11 @@ _rt_copy4cols_asm:
 	inc	ebx			; ebx = count
 	mov	eax,ecx
 	mov	esi,[ylookup+edx*4]
-	lea	ecx,[dc_temp+edx*4]	; ecx = source
-	mov	edx,[dc_pitch]		; edx = pitch
+	mov ecx,[dc_temp]
 	add	eax,esi			; eax = dest
 	add	eax,[dc_destorg]
+	lea	ecx,[ecx+edx*4]	; ecx = source
+	mov	edx,[dc_pitch]		; edx = pitch
 
 	shr	ebx,1
 	jnc	.even
@@ -1241,7 +1245,8 @@ _rt_map1col_asm:
 	mov	esi,[dc_colormap]		; esi = colormap
 	inc	ebx				; ebx = count
 	mov	eax,edx
-	lea	ebp,[dc_temp+ecx+edi]		; ebp = source
+	lea	ebp,[ecx+edi]		; ebp = source
+	add ebp,[dc_temp]
 	mov	ecx,[ylookup+edi]
 	mov	edi,[dc_pitch]			; edi = pitch
 	add	eax,ecx				; eax = dest
@@ -1320,7 +1325,8 @@ _rt_map4cols_asm1:
 	mov	eax,ecx
 	inc	ebx			; ebx = count
 	mov	edi,[ylookup+edx]
-	lea	ebp,[dc_temp+edx]	; ebp = source
+	mov	ebp,[dc_temp]
+	add ebp,edx		; ebp = source
 	add	eax,edi			; eax = dest
 	mov	edi,[dc_pitch]		; edi = pitch
 	add	eax,[dc_destorg]
@@ -1414,7 +1420,8 @@ _rt_map4cols_asm2:
 	mov	eax,ecx
 	inc	ebx			; ebx = count
 	mov	edi,[ylookup+edx]
-	lea	ebp,[dc_temp+edx]	; ebp = source
+	mov ebp,[dc_temp]
+	add ebp,edx		; ebp = source
 	add	eax,edi			; eax = dest
 	mov	edi,[dc_pitch]		; edi = pitch
 	add	eax,[dc_destorg]
@@ -1493,10 +1500,11 @@ _rt_shaded4cols_asm:
 		add		eax,[dc_destorg]				; eax = destination
 		push	ebx
 		push	esi
+		mov		esi,[dc_temp]
 		inc		ebp								; ebp = count
 		add		eax,[esp+16]
 		push	edi
-		lea		esi,[dc_temp+ecx*4]				; esi = source
+		lea		esi,[esi+ecx*4]				; esi = source
 
 		align	16
 
@@ -1580,10 +1588,11 @@ _rt_add4cols_asm:
 		add		eax,[dc_destorg]
 		push	ebx
 		push	esi
+		mov		esi,[dc_temp]
 		push	ebp
 		inc		edi
 		add		eax,[esp+20]
-		lea		esi,[dc_temp+ecx*4]
+		lea		esi,[esi+ecx*4]
 		
 		align 16
 a4loop:
@@ -1659,10 +1668,11 @@ _rt_addclamp4cols_asm:
 		add		eax,[dc_destorg]
 		push	ebx
 		push	esi
+		mov		esi,[dc_temp]
 		push	ebp
 		inc		edi
 		add		eax,[esp+20]
-		lea		esi,[dc_temp+ecx*4]
+		lea		esi,[esi+ecx*4]
 		push	edi
 		
 		align	16
diff --git a/src/c_dispatch.h b/src/c_dispatch.h
index ac8685855..5aa3f9870 100644
--- a/src/c_dispatch.h
+++ b/src/c_dispatch.h
@@ -166,5 +166,6 @@ void ResetButtonStates ();		// Same as above, but also clear bDown
 
 extern unsigned int MakeKey (const char *s);
 extern unsigned int MakeKey (const char *s, size_t len);
+extern unsigned int SuperFastHash (const char *data, size_t len);
 
 #endif //__C_DISPATCH_H__
diff --git a/src/memarena.cpp b/src/memarena.cpp
new file mode 100644
index 000000000..c44586dee
--- /dev/null
+++ b/src/memarena.cpp
@@ -0,0 +1,376 @@
+/*
+** memarena.cpp
+** Implements memory arenas.
+**
+**---------------------------------------------------------------------------
+** Copyright 2010 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+** A memory arena is used for efficient allocation of many small objects that
+** will all be freed at once. Note that since individual destructors are not
+** called, you must not use an arena to allocate any objects that use a
+** destructor, either explicitly or implicitly (because they have members
+** with destructors).
+*/
+
+#include "doomtype.h"
+#include "m_alloc.h"
+#include "memarena.h"
+#include "c_dispatch.h"
+#include "zstring.h"
+
+#define BLOCK_SIZE			(10*1024)
+
+struct FMemArena::Block
+{
+	Block *NextBlock;
+	void *Limit;			// End of this block
+	void *Avail;			// Start of free space in this block
+
+	void Reset();
+	void *Alloc(size_t size);
+};
+
+//==========================================================================
+//
+// RoundPointer
+//
+// Rounds a pointer up to a pointer-sized boundary.
+//
+//==========================================================================
+
+static inline void *RoundPointer(void *ptr)
+{
+	return (void *)(((size_t)ptr + sizeof(void*) - 1) & ~(sizeof(void*) - 1));
+}
+
+//==========================================================================
+//
+// FMemArena Constructor
+//
+//==========================================================================
+
+FMemArena::FMemArena()
+{
+	TopBlock = NULL;
+	FreeBlocks = NULL;
+}
+
+//==========================================================================
+//
+// FMemArena Destructor
+//
+//==========================================================================
+
+FMemArena::~FMemArena()
+{
+	FreeAllBlocks();
+}
+
+//==========================================================================
+//
+// FMemArena :: Alloc
+//
+//==========================================================================
+
+void *FMemArena::Alloc(size_t size)
+{
+	Block *block;
+
+	for (block = TopBlock; block != NULL; block = block->NextBlock)
+	{
+		void *res = block->Alloc(size);
+		if (res != NULL)
+		{
+			return res;
+		}
+	}
+	block = AddBlock(size);
+	return block->Alloc(size);
+}
+
+//==========================================================================
+//
+// FMemArena :: FreeAll
+//
+// Moves all blocks to the free list. No system-level deallocation occurs.
+//
+//==========================================================================
+
+void FMemArena::FreeAll()
+{
+	for (Block *next, *block = TopBlock; block != NULL; block = next)
+	{
+		next = block->NextBlock;
+		block->Reset();
+		block->NextBlock = FreeBlocks;
+		FreeBlocks = block;
+	}
+	TopBlock = NULL;
+}
+
+//==========================================================================
+//
+// FMemArena :: FreeAllBlocks
+//
+// Frees all blocks used by this arena.
+//
+//==========================================================================
+
+void FMemArena::FreeAllBlocks()
+{
+	FreeBlockChain(TopBlock);
+	FreeBlockChain(FreeBlocks);
+}
+
+//==========================================================================
+//
+// FMemArena :: FreeBlockChain
+//
+// Frees a chain of blocks.
+//
+//==========================================================================
+
+void FMemArena::FreeBlockChain(Block *&top)
+{
+	for (Block *next, *block = top; block != NULL; block = next)
+	{
+		next = block->NextBlock;
+		M_Free(block);
+	}
+	top = NULL;
+}
+
+//==========================================================================
+//
+// FMemArena :: AddBlock
+//
+// Allocates a block large enough to hold at least <size> bytes and adds it
+// to the TopBlock chain.
+//
+//==========================================================================
+
+FMemArena::Block *FMemArena::AddBlock(size_t size)
+{
+	Block *mem, **last;
+	size += sizeof(Block);		// Account for header size
+
+	// Search for a free block to use
+	for (last = &FreeBlocks, mem = FreeBlocks; mem != NULL; last = &mem->NextBlock, mem = mem->NextBlock)
+	{
+		if ((BYTE *)mem->Limit - (BYTE *)mem >= (ptrdiff_t)size)
+		{
+			*last = mem->NextBlock;
+			break;
+		}
+	}
+	if (mem == NULL)
+	{
+		// Allocate a new block
+		if (size < BLOCK_SIZE)
+		{
+			size = BLOCK_SIZE;
+		}
+		else
+		{ // Stick some free space at the end so we can use this block for
+		  // other things.
+			size += BLOCK_SIZE/2;
+		}
+		mem = (Block *)M_Malloc(size);
+		mem->Limit = (BYTE *)mem + size;
+	}
+	mem->Reset();
+	mem->NextBlock = TopBlock;
+	TopBlock = mem;
+	return mem;
+}
+
+//==========================================================================
+//
+// FMemArena :: Block :: Reset
+//
+// Resets this block's Avail pointer.
+//
+//==========================================================================
+
+void FMemArena::Block::Reset()
+{
+	Avail = RoundPointer(this + sizeof(*this));
+}
+
+//==========================================================================
+//
+// FMemArena :: Block :: Alloc
+//
+// Allocates memory from the block if it has space. Returns NULL if not.
+//
+//==========================================================================
+
+void *FMemArena::Block::Alloc(size_t size)
+{
+	if ((char *)Avail + size > Limit)
+	{
+		return NULL;
+	}
+	void *res = Avail;
+	Avail = RoundPointer((char *)Avail + size);
+	return res;
+}
+
+//==========================================================================
+//
+// FSharedStringArena Constructor
+//
+//==========================================================================
+
+FSharedStringArena::FSharedStringArena()
+{
+	memset(Buckets, 0, sizeof(Buckets));
+}
+
+//==========================================================================
+//
+// FSharedStringArena Destructor
+//
+//==========================================================================
+
+FSharedStringArena::~FSharedStringArena()
+{
+	FreeAll();
+	// FMemArena destructor will free the blocks.
+}
+
+//==========================================================================
+//
+// FSharedStringArena :: Alloc
+//
+// Allocates a new string and initializes it with the passed string. This
+// version takes an FString as a parameter, so it won't need to allocate any
+// memory for the string text if it already exists in the arena.
+//
+//==========================================================================
+
+FString *FSharedStringArena::Alloc(const FString &source)
+{
+	unsigned int hash;
+	Node *strnode;
+
+	strnode = FindString(source, source.Len(), hash);
+	if (strnode == NULL)
+	{
+		strnode = (Node *)FMemArena::Alloc(sizeof(Node));
+		::new(&strnode->String) FString(source);
+		strnode->Hash = hash;
+		hash %= countof(Buckets);
+		strnode->Next = Buckets[hash];
+		Buckets[hash] = strnode;
+	}
+	return &strnode->String;
+}
+
+//==========================================================================
+//
+// FSharedStringArena :: Alloc
+//
+//==========================================================================
+
+FString *FSharedStringArena::Alloc(const char *source)
+{
+	return Alloc(source, strlen(source));
+}
+
+//==========================================================================
+//
+// FSharedStringArena :: Alloc
+//
+//==========================================================================
+
+FString *FSharedStringArena::Alloc(const char *source, size_t strlen)
+{
+	unsigned int hash;
+	Node *strnode;
+
+	strnode = FindString(source, strlen, hash);
+	if (strnode == NULL)
+	{
+		strnode = (Node *)FMemArena::Alloc(sizeof(Node));
+		::new(&strnode->String) FString(source, strlen);
+		strnode->Hash = hash;
+		hash %= countof(Buckets);
+		strnode->Next = Buckets[hash];
+		Buckets[hash] = strnode;
+	}
+	return &strnode->String;
+}
+
+//==========================================================================
+//
+// FSharedStringArena :: FindString
+//
+// Finds the string if it's already in the arena. Returns NULL if not.
+//
+//==========================================================================
+
+FSharedStringArena::Node *FSharedStringArena::FindString(const char *str, size_t strlen, unsigned int &hash)
+{
+	hash = SuperFastHash(str, strlen);
+
+	for (Node *node = Buckets[hash % countof(Buckets)]; node != NULL; node = node->Next)
+	{
+		if (node->Hash == hash && node->String.Len() == strlen && memcmp(&node->String[0], str, strlen) == 0)
+		{
+			return node;
+		}
+	}
+	return NULL;
+}
+
+//==========================================================================
+//
+// FSharedStringArena :: FreeAll
+//
+// In addition to moving all used blocks onto the free list, all FStrings
+// they contain will have their destructors called.
+//
+//==========================================================================
+
+void FSharedStringArena::FreeAll()
+{
+	for (Block *next, *block = TopBlock; block != NULL; block = next)
+	{
+		next = block->NextBlock;
+		void *limit = block->Avail;
+		block->Reset();
+		for (Node *string = (Node *)block->Avail; string < limit; ++string)
+		{
+			string->~Node();
+		}
+		block->NextBlock = FreeBlocks;
+		FreeBlocks = block;
+	}
+	memset(Buckets, 0, sizeof(Buckets));
+}
diff --git a/src/memarena.h b/src/memarena.h
new file mode 100644
index 000000000..e933fc072
--- /dev/null
+++ b/src/memarena.h
@@ -0,0 +1,83 @@
+/*
+** memarena.h
+**
+**---------------------------------------------------------------------------
+** Copyright 2010 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include "zstring.h"
+
+// A general purpose arena.
+class FMemArena
+{
+public:
+	FMemArena();
+	~FMemArena();
+
+	void *Alloc(size_t size);
+	void FreeAll();
+	void FreeAllBlocks();
+
+protected:
+	struct Block;
+
+	Block *AddBlock(size_t size);
+	void FreeBlockChain(Block *&top);
+
+	Block *TopBlock;
+	Block *FreeBlocks;
+};
+
+// An arena specializing in storage of FStrings. It knows how to free them,
+// but this means it also should never be used for allocating anything else.
+// Identical strings all return the same pointer.
+class FSharedStringArena : public FMemArena
+{
+public:
+	FSharedStringArena();
+	~FSharedStringArena();
+	void FreeAll();
+
+	class FString *Alloc(const FString &source);
+	class FString *Alloc(const char *source);
+	class FString *Alloc(const char *source, size_t strlen);
+
+protected:
+	struct Node
+	{
+		Node *Next;
+		FString String;
+		unsigned int Hash;
+	};
+	Node *Buckets[256];
+
+	Node *FindString(const char *str, size_t strlen, unsigned int &hash);
+private:
+	void *Alloc(size_t size) { return NULL; }	// No access to FMemArena::Alloc for outsiders.
+};
diff --git a/src/r_data.cpp b/src/r_data.cpp
index a298cc03f..8983d5e55 100644
--- a/src/r_data.cpp
+++ b/src/r_data.cpp
@@ -355,6 +355,304 @@ const BYTE *R_GetColumn (FTexture *tex, int col)
 	return tex->GetColumn (col, NULL);
 }
 
+//==========================================================================
+//
+// GetVoxelRemap
+//
+// Calculates a remap table for the voxel's palette. Results are cached so
+// passing the same palette repeatedly will not require repeated
+// recalculations.
+//
+//==========================================================================
+
+static BYTE *GetVoxelRemap(const BYTE *pal)
+{
+	static BYTE remap[256];
+	static BYTE oldpal[768];
+	static bool firsttime = true;
+
+	if (firsttime || memcmp(oldpal, pal, 768) != 0)
+	{ // Not the same palette as last time, so recalculate.
+		firsttime = false;
+		memcpy(oldpal, pal, 768);
+		for (int i = 0; i < 256; ++i)
+		{
+			// The voxel palette uses VGA colors, so we have to expand it
+			// from 6 to 8 bits per component.
+			remap[i] = BestColor((uint32 *)GPalette.BaseColors,
+				(oldpal[i*3 + 0] << 2) | (oldpal[i*3 + 0] >> 4),
+				(oldpal[i*3 + 1] << 2) | (oldpal[i*3 + 1] >> 4),
+				(oldpal[i*3 + 2] << 2) | (oldpal[i*3 + 2] >> 4));
+		}
+	}
+	return remap;
+}
+
+//==========================================================================
+//
+// CopyVoxelSlabs
+//
+// Copy all the slabs in a block of slabs.
+//
+//==========================================================================
+
+static bool CopyVoxelSlabs(kvxslab_t *dest, const kvxslab_t *src, int size)
+{
+	while (size >= 3)
+	{
+		int slabzleng = src->zleng;
+
+		if (3 + slabzleng > size)
+		{ // slab is too tall
+			return false;
+		}
+
+		dest->ztop = src->ztop;
+		dest->zleng = src->zleng;
+		dest->backfacecull = src->backfacecull;
+
+		for (int j = 0; j < slabzleng; ++j)
+		{
+			dest->col[j] = src->col[j];
+		}
+		slabzleng += 3;
+		src = (kvxslab_t *)((BYTE *)src + slabzleng);
+		dest = (kvxslab_t *)((BYTE *)dest + slabzleng);
+		size -= slabzleng;
+	}
+	return true;
+}
+
+//==========================================================================
+//
+// RemapVoxelSlabs
+//
+// Remaps all the slabs in a block of slabs.
+//
+//==========================================================================
+
+static void RemapVoxelSlabs(kvxslab_t *dest, int size, const BYTE *remap)
+{
+	while (size >= 3)
+	{
+		int slabzleng = dest->zleng;
+
+		for (int j = 0; j < slabzleng; ++j)
+		{
+			dest->col[j] = remap[dest->col[j]];
+		}
+		slabzleng += 3;
+		dest = (kvxslab_t *)((BYTE *)dest + slabzleng);
+		size -= slabzleng;
+	}
+}
+
+//==========================================================================
+//
+// R_LoadKVX
+//
+//==========================================================================
+
+FVoxel *R_LoadKVX(int lumpnum)
+{
+	const kvxslab_t *slabs[MAXVOXMIPS];
+	FVoxel *voxel = new FVoxel;
+	const BYTE *rawmip;
+	int mip, maxmipsize;
+	int i, j, n;
+
+	FMemLump lump = Wads.ReadLump(lumpnum);	// FMemLump adds an extra 0 byte to the end.
+	BYTE *rawvoxel = (BYTE *)lump.GetMem();
+	int voxelsize = (int)(lump.GetSize()-1);
+
+	// Oh, KVX, why couldn't you have a proper header? We'll just go through
+	// and collect each MIP level, doing lots of range checking, and if the
+	// last one doesn't end exactly 768 bytes before the end of the file,
+	// we'll reject it.
+
+	for (mip = 0, rawmip = rawvoxel, maxmipsize = voxelsize - 768 - 4;
+		 mip < MAXVOXMIPS;
+		 mip++)
+	{
+		int numbytes = GetInt(rawmip);
+		if (numbytes > maxmipsize || numbytes < 24)
+		{
+			break;
+		}
+		rawmip += 4;
+
+		FVoxelMipLevel *mipl = &voxel->Mips[mip];
+
+		// Load header data.
+		mipl->SizeX = GetInt(rawmip + 0);
+		mipl->SizeY = GetInt(rawmip + 4);
+		mipl->SizeZ = GetInt(rawmip + 8);
+		mipl->PivotX = GetInt(rawmip + 12);
+		mipl->PivotY = GetInt(rawmip + 16);
+		mipl->PivotZ = GetInt(rawmip + 20);
+
+		// How much space do we have for voxdata?
+		int offsetsize = (mipl->SizeX + 1) * 4 + mipl->SizeX * (mipl->SizeY + 1) * 2;
+		int voxdatasize = numbytes - 24 - offsetsize;
+		if (voxdatasize < 0)
+		{ // Clearly, not enough.
+			break;
+		}
+		if (voxdatasize == 0)
+		{ // This mip level is empty.
+			goto nextmip;
+		}
+
+		// Allocate slab data space.
+		mipl->OffsetX = new int[(numbytes - 24 + 3) / 4];
+		mipl->OffsetXY = (short *)(mipl->OffsetX + mipl->SizeX + 1);
+		mipl->SlabData = (BYTE *)(mipl->OffsetXY + mipl->SizeX * (mipl->SizeY + 1));
+
+		// Load x offsets.
+		for (i = 0, n = mipl->SizeX; i <= n; ++i)
+		{
+			// The X offsets stored in the KVX file are relative to the start of the
+			// X offsets array. Make them relative to voxdata instead.
+			mipl->OffsetX[i] = GetInt(rawmip + 24 + i * 4) - offsetsize;
+		}
+
+		// The first X offset must be 0 (since we subtracted offsetsize), according to the spec:
+		//		NOTE: xoffset[0] = (xsiz+1)*4 + xsiz*(ysiz+1)*2 (ALWAYS)
+		if (mipl->OffsetX[0] != 0)
+		{
+			break;
+		}
+		// And the final X offset must point just past the end of the voxdata.
+		if (mipl->OffsetX[mipl->SizeX] != voxdatasize)
+		{
+			break;
+		}
+
+		// Load xy offsets.
+		i = 24 + i * 4;
+		for (j = 0, n *= mipl->SizeY + 1; j < n; ++j)
+		{
+			mipl->OffsetXY[j] = GetShort(rawmip + i + j * 2);
+		}
+
+		// Ensure all offsets are within bounds.
+		for (i = 0; i < mipl->SizeX; ++i)
+		{
+			int xoff = mipl->OffsetX[i];
+			for (j = 0; j < mipl->SizeY; ++j)
+			{
+				int yoff = mipl->OffsetXY[(mipl->SizeY + 1) * i + j];
+				if (unsigned(xoff + yoff) > unsigned(voxdatasize))
+				{
+					goto bad;
+				}
+			}
+		}
+
+		// Record slab location for the end.
+		slabs[mip] = (kvxslab_t *)(rawmip + 24 + offsetsize);
+
+		// Time for the next mip Level.
+nextmip:
+		rawmip += numbytes;
+		maxmipsize -= numbytes + 4;
+	}
+	// Did we get any mip levels, and if so, does the last one leave just
+	// enough room for the palette after it?
+	if (mip == 0 || rawmip != rawvoxel + voxelsize - 768)
+	{
+bad:	delete voxel;
+		return NULL;
+	}
+
+	// Do not count empty mips at the end.
+	for (; mip > 0; --mip)
+	{
+		if (voxel->Mips[mip - 1].SlabData != NULL)
+			break;
+	}
+	voxel->NumMips = mip;
+
+	for (i = 0; i < mip; ++i)
+	{
+		if (!CopyVoxelSlabs((kvxslab_t *)voxel->Mips[i].SlabData, slabs[i], voxel->Mips[i].OffsetX[voxel->Mips[i].SizeX]))
+		{ // Invalid slabs encountered. Reject this voxel.
+			delete voxel;
+			return NULL;
+		}
+	}
+
+	voxel->LumpNum = lumpnum;
+	voxel->Palette = new BYTE[768];
+	memcpy(voxel->Palette, rawvoxel + voxelsize - 768, 768);
+
+	return voxel;
+}
+
+//==========================================================================
+//
+// FVoxelMipLevel Constructor
+//
+//==========================================================================
+
+FVoxelMipLevel::FVoxelMipLevel()
+{
+	SizeZ = SizeY = SizeX = 0;
+	PivotZ = PivotY = PivotX = 0;
+	OffsetX = NULL;
+	OffsetXY = NULL;
+	SlabData = NULL;
+}
+
+//==========================================================================
+//
+// FVoxelMipLevel Destructor
+//
+//==========================================================================
+
+FVoxelMipLevel::~FVoxelMipLevel()
+{
+	if (OffsetX != NULL)
+	{
+		delete[] OffsetX;
+	}
+}
+
+//==========================================================================
+//
+// FVoxel Constructor
+//
+//==========================================================================
+
+FVoxel::FVoxel()
+{
+	Palette = NULL;
+}
+
+FVoxel::~FVoxel()
+{
+	if (Palette != NULL) delete [] Palette;
+}
+
+//==========================================================================
+//
+// Remap the voxel to the game palette
+//
+//==========================================================================
+
+void FVoxel::Remap()
+{
+	if (Palette != NULL)
+	{
+		BYTE *remap = GetVoxelRemap(Palette);
+		for (int i = 0; i < NumMips; ++i)
+		{
+			RemapVoxelSlabs((kvxslab_t *)Mips[i].SlabData, Mips[i].OffsetX[Mips[i].SizeX], remap);
+		}
+		delete [] Palette;
+		Palette = NULL;
+	}
+}
 
 //==========================================================================
 //
@@ -389,10 +687,4 @@ CCMD (printspans)
 		Printf ("\n");
 	}
 }
-
-CCMD (picnum)
-{
-	//int picnum = TexMan.GetTexture (argv[1], FTexture::TEX_Any);
-	//Printf ("%d: %s - %s\n", picnum, TexMan[picnum]->Name, TexMan(picnum)->Name);
-}
 #endif
diff --git a/src/r_data.h b/src/r_data.h
index 487860e58..d2f25dfa9 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -144,4 +144,6 @@ extern size_t numfakecmaps;
 
 int R_FindSkin (const char *name, int pclass);	// [RH] Find a skin
 
+FVoxel *R_LoadKVX(int lumpnum);
+
 #endif
diff --git a/src/r_defs.h b/src/r_defs.h
index 9d2822c33..cf84c65ac 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -25,6 +25,7 @@
 
 #include "doomdef.h"
 #include "templates.h"
+#include "memarena.h"
 
 // Some more or less basic data types
 // we depend on.
@@ -1051,7 +1052,6 @@ struct column_t
 
 typedef BYTE lighttable_t;	// This could be wider for >8 bit display.
 
-
 // A vissprite_t is a thing
 //	that will be drawn during a refresh.
 // I.e. a sprite object that is partly visible.
@@ -1059,8 +1059,9 @@ struct vissprite_t
 {
 	short			x1, x2;
 	fixed_t			cx;				// for line side calculation
-	fixed_t			gx, gy;			// for drawseg and fake floor clipping
-	fixed_t			gz, gzt;		// global bottom / top for silhouette clipping
+	fixed_t			gx, gy, gz;		// origin in world coordinates
+	angle_t			angle;
+	fixed_t			gzb, gzt;		// global bottom / top for silhouette clipping
 	fixed_t			startfrac;		// horizontal position of x1
 	fixed_t			xscale, yscale;
 	fixed_t			xiscale;		// negative if flipped
@@ -1073,12 +1074,17 @@ struct vissprite_t
 	sector_t		*sector;		// [RH] sector this sprite is in
 	fixed_t			alpha;
 	fixed_t			floorclip;
-	FTexture		*pic;
+	union
+	{
+		FTexture	  *pic;
+		struct FVoxel *voxel;
+	};
+	BYTE			bIsVoxel:1;		// [RH] Use voxel instead of pic
+	BYTE			bSplitSprite:1;	// [RH] Sprite was split by a drawseg
+	BYTE			FakeFlatStat;	// [RH] which side of fake/floor ceiling sprite is on
 	short 			renderflags;
 	DWORD			Translation;	// [RH] for color translation
 	FRenderStyle	RenderStyle;
-	BYTE			FakeFlatStat;	// [RH] which side of fake/floor ceiling sprite is on
-	BYTE			bSplitSprite;	// [RH] Sprite was split by a drawseg
 };
 
 enum
@@ -1100,14 +1106,16 @@ enum
 //
 struct spriteframe_t
 {
+	struct FVoxelDef *Voxel;// voxel to use for this frame
 	FTextureID Texture[16];	// texture to use for view angles 0-15
-	WORD Flip;			// flip (1 = flip) to use for view angles 0-15.
+	WORD Flip;				// flip (1 = flip) to use for view angles 0-15.
 };
 
 //
 // A sprite definition:
 //	a number of animation frames.
 //
+
 struct spritedef_t
 {
 	union
@@ -1140,4 +1148,77 @@ public:
 	int			namespc;	// namespace for this skin
 };
 
+
+// [RH] Voxels from Build
+
+#define MAXVOXMIPS 5
+
+struct kvxslab_t
+{
+	BYTE		ztop;			// starting z coordinate of top of slab
+	BYTE		zleng;			// # of bytes in the color array - slab height
+	BYTE		backfacecull;	// low 6 bits tell which of 6 faces are exposed
+	BYTE		col[1/*zleng*/];// color data from top to bottom
+};
+
+struct FVoxelMipLevel
+{
+	FVoxelMipLevel();
+	~FVoxelMipLevel();
+
+	int			SizeX;
+	int			SizeY;
+	int			SizeZ;
+	fixed_t		PivotX;		// 24.8 fixed point
+	fixed_t		PivotY;		// ""
+	fixed_t		PivotZ;		// ""
+	int			*OffsetX;
+	short		*OffsetXY;
+	BYTE		*SlabData;
+};
+
+struct FVoxel
+{
+	int LumpNum;
+	int NumMips;
+	BYTE *Palette;
+	FVoxelMipLevel Mips[MAXVOXMIPS];
+
+	FVoxel();
+	~FVoxel();
+	void Remap();
+};
+
+struct FVoxelDef
+{
+	FVoxel *Voxel;
+	int PlacedSpin;			// degrees/sec to spin actors without MF_DROPPED set
+	int DroppedSpin;		// degrees/sec to spin actors with MF_DROPPED set
+	fixed_t Scale;
+	angle_t AngleOffset;	// added to actor's angle to compensate for wrong-facing voxels
+};
+
+// [RH] A c-buffer. Used for keeping track of offscreen voxel spans.
+
+struct FCoverageBuffer
+{
+	struct Span
+	{
+		Span *NextSpan;
+		short Start, Stop;
+	};
+
+	FCoverageBuffer(int size);
+	~FCoverageBuffer();
+
+	void Clear();
+	void InsertSpan(int listnum, int start, int stop);
+	Span *AllocSpan();
+
+	FMemArena SpanArena;
+	Span **Spans;	// [0..NumLists-1] span lists
+	Span *FreeSpans;
+	unsigned int NumLists;
+};
+
 #endif
diff --git a/src/r_draw.cpp b/src/r_draw.cpp
index 8fb3efe36..36c87fa89 100644
--- a/src/r_draw.cpp
+++ b/src/r_draw.cpp
@@ -1352,6 +1352,97 @@ void R_FillSpan (void)
 	memset (ylookup[ds_y] + ds_x1 + dc_destorg, ds_color, ds_x2 - ds_x1 + 1);
 }
 
+// Draw a voxel slab
+//
+// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
+// Ken Silverman's official web site: "http://www.advsys.net/ken"
+// See the included license file "BUILDLIC.TXT" for license info.
+
+// Actually, this is just R_DrawColumn with an extra width parameter.
+
+#ifndef X86_ASM
+static const BYTE *slabcolormap;
+
+extern "C" void R_SetupDrawSlab(const BYTE *colormap)
+{
+	slabcolormap = colormap;
+}
+
+extern "C" void STACK_ARGS R_DrawSlab(int dx, fixed_t v, int dy, fixed_t vi, const BYTE *vptr, BYTE *p)
+{
+	int x;
+	const BYTE *colormap = slabcolormap;
+	int pitch = dc_pitch;
+
+	assert(dx > 0);
+
+	if (dx == 1)
+	{
+		while (dy > 0)
+		{
+			*p = colormap[vptr[v >> FRACBITS]];
+			p += pitch;
+			v += vi;
+			dy--;
+		}
+	}
+	else if (dx == 2)
+	{
+		while (dy > 0)
+		{
+			BYTE color = colormap[vptr[v >> FRACBITS]];
+			p[0] = color;
+			p[1] = color;
+			p += pitch;
+			v += vi;
+			dy--;
+		}
+	}
+	else if (dx == 3)
+	{
+		while (dy > 0)
+		{
+			BYTE color = colormap[vptr[v >> FRACBITS]];
+			p[0] = color;
+			p[1] = color;
+			p[2] = color;
+			p += pitch;
+			v += vi;
+			dy--;
+		}
+	}
+	else if (dx == 4)
+	{
+		while (dy > 0)
+		{
+			BYTE color = colormap[vptr[v >> FRACBITS]];
+			p[0] = color;
+			p[1] = color;
+			p[2] = color;
+			p[3] = color;
+			p += pitch;
+			v += vi;
+			dy--;
+		}
+	}
+	else while (dy > 0)
+	{
+		BYTE color = colormap[vptr[v >> FRACBITS]];
+		// The optimizer will probably turn this into a memset call.
+		// Since dx is not likely to be large, I'm not sure that's a good thing,
+		// hence the alternatives above.
+		for (x = 0; x < dx; x++)
+		{
+			p[x] = color;
+		}
+		p += pitch;
+		v += vi;
+		dy--;
+	}
+}
+#endif
+
+
 /****************************************************/
 /****************************************************/
 
diff --git a/src/r_draw.h b/src/r_draw.h
index 96e58bf8d..57dfd8712 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -53,7 +53,7 @@ extern "C" BYTE*		palookupoffse[4];
 extern "C" const BYTE*	bufplce[4];
 
 // [RH] Temporary buffer for column drawing
-extern "C" BYTE			dc_temp[MAXHEIGHT*4];
+extern "C" BYTE			*dc_temp;
 extern "C" unsigned int	dc_tspans[4][MAXHEIGHT];
 extern "C" unsigned int	*dc_ctspan[4];
 extern "C" unsigned int	horizspans[4];
@@ -178,7 +178,7 @@ extern void (STACK_ARGS *rt_map4cols)(int sx, int yl, int yh);
 void rt_draw4cols (int sx);
 
 // [RH] Preps the temporary horizontal buffer.
-void rt_initcols (void);
+void rt_initcols (BYTE *buffer=NULL);
 
 void R_DrawFogBoundary (int x1, int x2, short *uclip, short *dclip);
 
@@ -216,6 +216,9 @@ void	R_FillColumnP (void);
 void	R_FillColumnHorizP (void);
 void	R_FillSpan (void);
 
+extern "C" void			   R_SetupDrawSlab(const BYTE *colormap);
+extern "C" void STACK_ARGS R_DrawSlab(int dx, fixed_t v, int dy, fixed_t vi, const BYTE *vptr, BYTE *p);
+
 extern "C" int				ds_y;
 extern "C" int				ds_x1;
 extern "C" int				ds_x2;
diff --git a/src/r_drawt.cpp b/src/r_drawt.cpp
index fbfaf6307..c99bd3afb 100644
--- a/src/r_drawt.cpp
+++ b/src/r_drawt.cpp
@@ -52,9 +52,13 @@
 // dc_temp is the buffer R_DrawColumnHoriz writes into.
 // dc_tspans points into it.
 // dc_ctspan points into dc_tspans.
-// But what is horizspan, and what is its relation with dc_ctspan?
+// horizspan also points into dc_tspans.
 
-BYTE dc_temp[MAXHEIGHT*4];
+// dc_ctspan is advanced while drawing into dc_temp.
+// horizspan is advanced up to dc_ctspan when drawing from dc_temp to the screen.
+
+BYTE dc_tempbuff[MAXHEIGHT*4];
+BYTE *dc_temp;
 unsigned int dc_tspans[4][MAXHEIGHT];
 unsigned int *dc_ctspan[4];
 unsigned int *horizspan[4];
@@ -998,10 +1002,11 @@ void rt_draw4cols (int sx)
 
 // Before each pass through a rendering loop that uses these routines,
 // call this function to set up the span pointers.
-void rt_initcols (void)
+void rt_initcols (BYTE *buff)
 {
 	int y;
 
+	dc_temp = buff == NULL ? dc_tempbuff : buff;
 	for (y = 3; y >= 0; y--)
 		horizspan[y] = dc_ctspan[y] = &dc_tspans[y][0];
 }
@@ -1035,26 +1040,18 @@ void R_DrawColumnHorizP_C (void)
 		const BYTE *source = dc_source;
 
 		if (count & 1) {
-			*dest = source[frac>>FRACBITS];
-			dest += 4;
-			frac += fracstep;
+			*dest = source[frac>>FRACBITS]; dest += 4; frac += fracstep;
 		}
 		if (count & 2) {
-			dest[0] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[4] = source[frac>>FRACBITS];
-			frac += fracstep;
+			dest[0] = source[frac>>FRACBITS]; frac += fracstep;
+			dest[4] = source[frac>>FRACBITS]; frac += fracstep;
 			dest += 8;
 		}
 		if (count & 4) {
-			dest[0] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[4] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[8] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[12] = source[frac>>FRACBITS];
-			frac += fracstep;
+			dest[0] = source[frac>>FRACBITS]; frac += fracstep;
+			dest[4] = source[frac>>FRACBITS]; frac += fracstep;
+			dest[8] = source[frac>>FRACBITS]; frac += fracstep;
+			dest[12]= source[frac>>FRACBITS]; frac += fracstep;
 			dest += 16;
 		}
 		count >>= 3;
@@ -1062,22 +1059,14 @@ void R_DrawColumnHorizP_C (void)
 
 		do
 		{
-			dest[0] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[4] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[8] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[12] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[16] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[20] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[24] = source[frac>>FRACBITS];
-			frac += fracstep;
-			dest[28] = source[frac>>FRACBITS];
-			frac += fracstep;
+			dest[0] = source[frac>>FRACBITS]; frac += fracstep;
+			dest[4] = source[frac>>FRACBITS]; frac += fracstep;
+			dest[8] = source[frac>>FRACBITS]; frac += fracstep;
+			dest[12]= source[frac>>FRACBITS]; frac += fracstep;
+			dest[16]= source[frac>>FRACBITS]; frac += fracstep;
+			dest[20]= source[frac>>FRACBITS]; frac += fracstep;
+			dest[24]= source[frac>>FRACBITS]; frac += fracstep;
+			dest[28]= source[frac>>FRACBITS]; frac += fracstep;
 			dest += 32;
 		} while (--count);
 	}
@@ -1110,8 +1099,7 @@ void R_FillColumnHorizP (void)
 	if (!(count >>= 1))
 		return;
 	do {
-		dest[0] = color;
-		dest[4] = color;
+		dest[0] = color; dest[4] = color;
 		dest += 8;
 	} while (--count);
 }
diff --git a/src/r_main.cpp b/src/r_main.cpp
index 88aab962c..0ce3a54bd 100644
--- a/src/r_main.cpp
+++ b/src/r_main.cpp
@@ -102,7 +102,6 @@ static fixed_t MaxVisForFloor;
 static FRandom pr_torchflicker ("TorchFlicker");
 static FRandom pr_hom;
 static TArray<InterpolationViewer> PastViewers;
-static int centerxwide;
 static bool polyclipped;
 static bool r_showviewer;
 bool r_dontmaplines;
@@ -134,6 +133,7 @@ float			LastFOV;
 int				WidescreenRatio;
 
 fixed_t			GlobVis;
+fixed_t			viewingrangerecip;
 fixed_t			FocalTangent;
 fixed_t			FocalLengthX;
 fixed_t			FocalLengthY;
@@ -150,6 +150,8 @@ float			WallTMapScale2;
 extern "C" {
 int 			centerx;
 int				centery;
+int				centerxwide;
+
 }
 
 DCanvas			*RenderTarget;		// [RH] canvas to render to
@@ -158,6 +160,7 @@ fixed_t			globaluclip, globaldclip;
 fixed_t 		centerxfrac;
 fixed_t 		centeryfrac;
 fixed_t			yaspectmul;
+fixed_t			baseyaspectmul;		// yaspectmul without a forced aspect ratio
 float			iyaspectmulfloat;
 fixed_t			InvZtoScale;
 
@@ -457,6 +460,9 @@ void R_InitTextureMapping ()
 	FocalLengthY = Scale (centerxfrac, yaspectmul, hitan);
 	FocalLengthXfloat = (float)FocalLengthX / 65536.f;
 
+	// This is 1/FocalTangent before the widescreen extension of FOV.
+	viewingrangerecip = DivScale32(1, finetangent[FINEANGLES/4+(FieldOfView/2)]);
+
 	// Now generate xtoviewangle for sky texture mapping.
 	// [RH] Do not generate viewangletox, because texture mapping is no
 	// longer done with trig, so it's not needed.
@@ -599,7 +605,7 @@ void R_SetViewSize (int blocks)
 
 void R_SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight)
 {
-	int virtheight, virtwidth;
+	int virtheight, virtwidth, trueratio, virtwidth2, virtheight2;
 
 	if (windowSize >= 11)
 	{
@@ -620,7 +626,7 @@ void R_SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight)
 	}
 
 	// If the screen is approximately 16:9 or 16:10, consider it widescreen.
-	WidescreenRatio = CheckRatio (fullWidth, fullHeight);
+	WidescreenRatio = CheckRatio (fullWidth, fullHeight, &trueratio);
 
 	DrawFSHUD = (windowSize == 11);
 	
@@ -643,8 +649,18 @@ void R_SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight)
 	centerxfrac = centerx<<FRACBITS;
 	centeryfrac = centery<<FRACBITS;
 
-	virtwidth = fullWidth;
-	virtheight = fullHeight;
+	virtwidth = virtwidth2 = fullWidth;
+	virtheight = virtheight2 = fullHeight;
+
+	if (trueratio & 4)
+	{
+		virtheight2 = virtheight2 * BaseRatioSizes[trueratio][3] / 48;
+	}
+	else
+	{
+		virtwidth2 = virtwidth2 * BaseRatioSizes[trueratio][3] / 48;
+	}
+
 	if (WidescreenRatio & 4)
 	{
 		virtheight = virtheight * BaseRatioSizes[WidescreenRatio][3] / 48;
@@ -656,6 +672,7 @@ void R_SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight)
 		centerxwide = centerx * BaseRatioSizes[WidescreenRatio][3] / 48;
 	}
 
+	baseyaspectmul = Scale(320 << FRACBITS, virtheight2, r_Yaspect * virtwidth2);
 	yaspectmul = Scale ((320<<FRACBITS), virtheight, r_Yaspect * virtwidth);
 	iyaspectmulfloat = (float)virtwidth * r_Yaspect / 320.f / (float)virtheight;
 	InvZtoScale = yaspectmul * centerx;
diff --git a/src/r_main.h b/src/r_main.h
index c894a2bcf..d91450a34 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -43,6 +43,7 @@ extern fixed_t			viewsin;
 extern fixed_t			viewtancos;
 extern fixed_t			viewtansin;
 extern fixed_t			FocalTangent;
+extern fixed_t			viewingrangerecip;
 extern fixed_t			FocalLengthX, FocalLengthY;
 extern float			FocalLengthXfloat;
 extern fixed_t			InvZtoScale;
@@ -62,7 +63,7 @@ extern int				viewwindowy;
 
 
 
-extern "C" int			centerx;
+extern "C" int			centerx, centerxwide;
 extern "C" int			centery;
 
 extern fixed_t			centerxfrac;
diff --git a/src/r_things.cpp b/src/r_things.cpp
index eff99e531..5243b1c83 100644
--- a/src/r_things.cpp
+++ b/src/r_things.cpp
@@ -19,6 +19,12 @@
 // DESCRIPTION:
 //		Refresh of things, i.e. objects represented by sprites.
 //
+// This file contains some code from the Build Engine.
+//
+// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
+// Ken Silverman's official web site: "http://www.advsys.net/ken"
+// See the included license file "BUILDLIC.TXT" for license info.
+//
 //-----------------------------------------------------------------------------
 
 #include <stdio.h>
@@ -53,7 +59,6 @@
 #include "v_palette.h"
 #include "r_translate.h"
 
-
 extern fixed_t globaluclip, globaldclip;
 
 
@@ -94,6 +99,8 @@ short			screenheightarray[MAXWIDTH];
 
 CVAR (Bool, r_drawplayersprites, true, 0)	// [RH] Draw player sprites?
 
+CVAR (Bool, r_drawvoxels, true, 0)
+
 //
 // INITIALIZATION FUNCTIONS
 //
@@ -104,6 +111,13 @@ TArray<spritedef_t> sprites;
 TArray<spriteframe_t> SpriteFrames;
 DWORD			NumStdSprites;		// The first x sprites that don't belong to skins.
 
+TDeletingArray<FVoxel *> Voxels;	// used only to auto-delete voxels on exit.
+TDeletingArray<FVoxelDef *> VoxelDefs;
+
+int OffscreenBufferWidth, OffscreenBufferHeight;
+BYTE *OffscreenColorBuffer;
+FCoverageBuffer *OffscreenCoverageBuffer;
+
 struct spriteframewithrotate : public spriteframe_t
 {
 	int rotate;
@@ -112,6 +126,18 @@ sprtemp[MAX_SPRITE_FRAMES];
 int 			maxframe;
 char*			spritename;
 
+struct VoxelOptions
+{
+	VoxelOptions()
+	: DroppedSpin(0), PlacedSpin(0), Scale(FRACUNIT), AngleOffset(0)
+	{}
+
+	int			DroppedSpin;
+	int			PlacedSpin;
+	fixed_t		Scale;
+	angle_t		AngleOffset;
+};
+
 // [RH] skin globals
 FPlayerSkin		*skins;
 size_t			numskins;
@@ -290,7 +316,9 @@ static void R_InstallSprite (int num)
 	{
 		if (sprtemp[frame].rotate == -1)
 		{
-			memset (&sprtemp[frame], 0, sizeof(sprtemp[0]));
+			memset (&sprtemp[frame].Texture, 0, sizeof(sprtemp[0].Texture));
+			sprtemp[frame].Flip = 0;
+			sprtemp[frame].rotate = 0;
 		}
 	}
 	
@@ -301,6 +329,7 @@ static void R_InstallSprite (int num)
 	{
 		memcpy (SpriteFrames[framestart+frame].Texture, sprtemp[frame].Texture, sizeof(sprtemp[frame].Texture));
 		SpriteFrames[framestart+frame].Flip = sprtemp[frame].Flip;
+		SpriteFrames[framestart+frame].Voxel = sprtemp[frame].Voxel;
 	}
 
 	// Let the textures know about the rotations
@@ -338,41 +367,100 @@ void R_InitSpriteDefs ()
 	{
 		int Head, Next;
 	} *hashes;
-	unsigned int i, max;
+	struct VHasher
+	{
+		int Head, Next, Name, Spin;
+		char Frame;
+	} *vhashes;
+	unsigned int i, j, smax, vmax;
 	DWORD intname;
 
 	// Create a hash table to speed up the process
-	max = TexMan.NumTextures();
-	hashes = (Hasher *)alloca (sizeof(Hasher) * max);
-	for (i = 0; i < max; ++i)
-	{
-		hashes[i].Head = -1;
-	}
-	for (i = 0; i < max; ++i)
+	smax = TexMan.NumTextures();
+	hashes = new Hasher[smax];
+	clearbuf(hashes, sizeof(Hasher)*smax/4, -1);
+	for (i = 0; i < smax; ++i)
 	{
 		FTexture *tex = TexMan.ByIndex(i);
 		if (tex->UseType == FTexture::TEX_Sprite && strlen(tex->Name) >= 6)
 		{
-			DWORD bucket = tex->dwName % max;
+			size_t bucket = tex->dwName % smax;
 			hashes[i].Next = hashes[bucket].Head;
 			hashes[bucket].Head = i;
 		}
 	}
 
+	// Repeat, for voxels
+	vmax = Wads.GetNumLumps();
+	vhashes = new VHasher[vmax];
+	clearbuf(vhashes, sizeof(VHasher)*vmax/4, -1);
+	for (i = 0; i < vmax; ++i)
+	{
+		if (Wads.GetLumpNamespace(i) == ns_voxels)
+		{
+			char name[9];
+			size_t namelen;
+			int spin;
+			int sign;
+
+			Wads.GetLumpName(name, i);
+			name[8] = 0;
+			namelen = strlen(name);
+			if (namelen < 4)
+			{ // name is too short
+				continue;
+			}
+			if (name[4] != '\0' && name[4] != ' ' && (name[4] < 'A' || name[4] >= 'A' + MAX_SPRITE_FRAMES))
+			{ // frame char is invalid
+				continue;
+			}
+			spin = 0;
+			sign = 2;	// 2 to convert from deg/halfsec to deg/sec
+			j = 5;
+			if (j < namelen && name[j] == '-')
+			{ // a minus sign is okay, but only before any digits
+				j++;
+				sign = -2;
+			}
+			for (; j < namelen; ++j)
+			{ // the remainder to the end of the name must be digits
+				if (name[j] >= '0' && name[j] <= '9')
+				{
+					spin = spin * 10 + name[j] - '0';
+				}
+				else
+				{
+					break;
+				}
+			}
+			if (j < namelen)
+			{ // the spin part is invalid
+				continue;
+			}
+			memcpy(&vhashes[i].Name, name, 4);
+			vhashes[i].Frame = name[4];
+			vhashes[i].Spin = spin * sign;
+			size_t bucket = vhashes[i].Name % vmax;
+			vhashes[i].Next = vhashes[bucket].Head;
+			vhashes[bucket].Head = i;
+		}
+	}
+
 	// scan all the lump names for each of the names, noting the highest frame letter.
 	for (i = 0; i < sprites.Size(); ++i)
 	{
 		memset (sprtemp, 0xFF, sizeof(sprtemp));
-		for (int j = 0; j < MAX_SPRITE_FRAMES; ++j)
+		for (j = 0; j < MAX_SPRITE_FRAMES; ++j)
 		{
 			sprtemp[j].Flip = 0;
+			sprtemp[j].Voxel = NULL;
 		}
 				
 		maxframe = -1;
 		intname = sprites[i].dwName;
 
 		// scan the lumps, filling in the frames for whatever is found
-		int hash = hashes[intname % max].Head;
+		int hash = hashes[intname % smax].Head;
 		while (hash != -1)
 		{
 			FTexture *tex = TexMan[hash];
@@ -385,9 +473,345 @@ void R_InitSpriteDefs ()
 			}
 			hash = hashes[hash].Next;
 		}
+
+		// repeat, for voxels
+		hash = vhashes[intname % vmax].Head;
+		while (hash != -1)
+		{
+			VHasher *vh = &vhashes[hash];
+			if (vh->Name == intname)
+			{
+				FVoxel *vox = R_LoadKVX(hash);
+				if (vox == NULL)
+				{
+					Printf("%s is not a valid voxel file\n", Wads.GetLumpFullName(hash));
+				}
+				else
+				{
+					FVoxelDef *voxdef = new FVoxelDef;
+
+					voxdef->Voxel = vox;
+					voxdef->Scale = FRACUNIT;
+					voxdef->DroppedSpin = voxdef->PlacedSpin = vh->Spin;
+					voxdef->AngleOffset = 0;
+
+					Voxels.Push(vox);
+					VoxelDefs.Push(voxdef);
+
+					if (vh->Frame == ' ' || vh->Frame == '\0')
+					{ // voxel applies to every sprite frame
+						for (j = 0; j < MAX_SPRITE_FRAMES; ++j)
+						{
+							if (sprtemp[j].Voxel == NULL)
+							{
+								sprtemp[j].Voxel = voxdef;
+							}
+						}
+						maxframe = MAX_SPRITE_FRAMES-1;
+					}
+					else
+					{ // voxel applies to a specific frame
+						j = vh->Frame - 'A';
+						sprtemp[j].Voxel = voxdef;
+						maxframe = MAX<int>(maxframe, j);
+					}
+				}
+			}
+			hash = vh->Next;
+		}
 		
 		R_InstallSprite ((int)i);
 	}
+
+	delete[] hashes;
+	delete[] vhashes;
+}
+
+//==========================================================================
+//
+// R_ExtendSpriteFrames
+//
+// Extends a sprite so that it can hold the desired frame.
+//
+//==========================================================================
+
+static void R_ExtendSpriteFrames(spritedef_t &spr, int frame)
+{
+	unsigned int i, newstart;
+
+	if (spr.numframes >= ++frame)
+	{ // The sprite already has enough frames, so do nothing.
+		return;
+	}
+
+	if (spr.numframes == 0 || (spr.spriteframes + spr.numframes == SpriteFrames.Size()))
+	{ // Sprite's frames are at the end of the array, or it has no frames
+	  // at all, so we can tack the new frames directly on to the end
+	  // of the SpriteFrames array.
+		newstart = SpriteFrames.Reserve(frame - spr.numframes);
+	}
+	else
+	{ // We need to allocate space for all the sprite's frames and copy
+	  // the existing ones over to the new space. The old space will be
+	  // lost.
+		newstart = SpriteFrames.Reserve(frame);
+		for (i = 0; i < spr.numframes; ++i)
+		{
+			SpriteFrames[newstart + i] = SpriteFrames[spr.spriteframes + i];
+		}
+		spr.spriteframes = WORD(newstart);
+		newstart += i;
+	}
+	// Initialize all new frames to 0.
+	memset(&SpriteFrames[newstart], 0, sizeof(spriteframe_t)*(frame - spr.numframes));
+	spr.numframes = frame;
+}
+
+//==========================================================================
+//
+// VOX_ReadSpriteNames
+//
+// Reads a list of sprite names from a VOXELDEF lump.
+//
+//==========================================================================
+
+static bool VOX_ReadSpriteNames(FScanner &sc, TArray<DWORD> &vsprites)
+{
+	unsigned int i;
+
+	vsprites.Clear();
+	while (sc.GetString())
+	{
+		// A sprite name list is terminated by an '=' character.
+		if (sc.String[0] == '=')
+		{
+			if (vsprites.Size() == 0)
+			{
+				sc.ScriptMessage("No sprites specified for voxel.\n");
+			}
+			return true;
+		}
+		if (sc.StringLen != 4 && sc.StringLen != 5)
+		{
+			sc.ScriptMessage("Sprite name \"%s\" is wrong size.\n", sc.String);
+		}
+		else if (sc.StringLen == 5 && (sc.String[4] = toupper(sc.String[4]), sc.String[4] < 'A' || sc.String[4] >= 'A' + MAX_SPRITE_FRAMES))
+		{
+			sc.ScriptMessage("Sprite frame %s is invalid.\n", sc.String[4]);
+		}
+		else
+		{
+			int frame = (sc.StringLen == 4) ? 255 : sc.String[4] - 'A';
+			int spritename;
+
+			for (i = 0; i < 4; ++i)
+			{
+				sc.String[i] = toupper(sc.String[i]);
+			}
+			spritename = *(int *)sc.String;
+			for (i = 0; i < sprites.Size(); ++i)
+			{
+				if (sprites[i].dwName == spritename)
+				{
+					break;
+				}
+			}
+			if (i != sprites.Size())
+			{
+				vsprites.Push((frame << 24) | i);
+			}
+		}
+	}
+	if (vsprites.Size() != 0)
+	{
+		sc.ScriptMessage("Unexpected end of file\n");
+	}
+	return false;
+}
+
+//==========================================================================
+//
+// VOX_ReadOptions
+//
+// Reads a list of options from a VOXELDEF lump, terminated with a '}'
+// character. The leading '{' must already be consumed
+//
+//==========================================================================
+
+static void VOX_ReadOptions(FScanner &sc, VoxelOptions &opts)
+{
+	while (sc.GetToken())
+	{
+		if (sc.TokenType == '}')
+		{
+			return;
+		}
+		sc.TokenMustBe(TK_Identifier);
+		if (sc.Compare("scale"))
+		{
+			sc.MustGetToken('=');
+			sc.MustGetToken(TK_FloatConst);
+			opts.Scale = FLOAT2FIXED(sc.Float);
+		}
+		else if (sc.Compare("spin"))
+		{
+			sc.MustGetToken('=');
+			sc.MustGetToken(TK_IntConst);
+			opts.DroppedSpin = opts.PlacedSpin = sc.Number;
+		}
+		else if (sc.Compare("placedspin"))
+		{
+			sc.MustGetToken('=');
+			sc.MustGetToken(TK_IntConst);
+			opts.PlacedSpin = sc.Number;
+		}
+		else if (sc.Compare("droppedspin"))
+		{
+			sc.MustGetToken('=');
+			sc.MustGetToken(TK_IntConst);
+			opts.DroppedSpin = sc.Number;
+		}
+		else if (sc.Compare("angleoffset"))
+		{
+			sc.MustGetToken('=');
+			sc.MustGetAnyToken();
+			if (sc.TokenType == TK_IntConst)
+			{
+				sc.Float = sc.Number;
+			}
+			else
+			{
+				sc.TokenMustBe(TK_FloatConst);
+			}
+			opts.AngleOffset = angle_t(sc.Float * ANGLE_180 / 180.0);
+		}
+		else
+		{
+			sc.ScriptMessage("Unknown voxel option '%s'\n", sc.String);
+			if (sc.CheckToken('='))
+			{
+				sc.MustGetAnyToken();
+			}
+		}
+	}
+	sc.ScriptMessage("Unterminated voxel option block\n");
+}
+
+//==========================================================================
+//
+// VOX_GetVoxel
+//
+// Returns a voxel object for the given lump or NULL if it is not a valid
+// voxel. If the voxel has already been loaded, it will be reused.
+//
+//==========================================================================
+
+static FVoxel *VOX_GetVoxel(int lumpnum)
+{
+	// Is this voxel already loaded? If so, return it.
+	for (unsigned i = 0; i < Voxels.Size(); ++i)
+	{
+		if (Voxels[i]->LumpNum == lumpnum)
+		{
+			return Voxels[i];
+		}
+	}
+	FVoxel *vox = R_LoadKVX(lumpnum);
+	if (vox != NULL)
+	{
+		Voxels.Push(vox);
+	}
+	return vox;
+}
+
+//==========================================================================
+//
+// VOX_AddVoxel
+//
+// Sets a voxel for a single sprite frame.
+//
+//==========================================================================
+
+static void VOX_AddVoxel(int sprnum, int frame, FVoxelDef *def)
+{
+	R_ExtendSpriteFrames(sprites[sprnum], frame);
+	SpriteFrames[sprites[sprnum].spriteframes + frame].Voxel = def;
+}
+
+//==========================================================================
+//
+// R_InitVoxels
+//
+// Process VOXELDEF lumps for defining voxel options that cannot be
+// condensed neatly into a sprite name format.
+//
+//==========================================================================
+
+void R_InitVoxels()
+{
+	int lump, lastlump = 0;
+	
+	while ((lump = Wads.FindLump("VOXELDEF", &lastlump)) != -1)
+	{
+		FScanner sc(lump);
+		TArray<DWORD> vsprites;
+
+		while (VOX_ReadSpriteNames(sc, vsprites))
+		{
+			FVoxel *voxeldata = NULL;
+			int voxelfile;
+			VoxelOptions opts;
+
+			sc.SetCMode(true);
+			sc.MustGetToken(TK_StringConst);
+			voxelfile = Wads.CheckNumForFullName(sc.String, true, ns_voxels);
+			if (voxelfile < 0)
+			{
+				sc.ScriptMessage("Voxel \"%s\" not found.\n", sc.String);
+			}
+			else
+			{
+				voxeldata = VOX_GetVoxel(voxelfile);
+				if (voxeldata == NULL)
+				{
+					sc.ScriptMessage("\"%s\" is not a valid voxel file.\n", sc.String);
+				}
+			}
+			if (sc.CheckToken('{'))
+			{
+				VOX_ReadOptions(sc, opts);
+			}
+			sc.SetCMode(false);
+			if (voxeldata != NULL && vsprites.Size() != 0)
+			{
+				FVoxelDef *def = new FVoxelDef;
+
+				def->Voxel = voxeldata;
+				def->Scale = opts.Scale;
+				def->DroppedSpin = opts.DroppedSpin;
+				def->PlacedSpin = opts.PlacedSpin;
+				def->AngleOffset = opts.AngleOffset;
+				VoxelDefs.Push(def);
+
+				for (unsigned i = 0; i < vsprites.Size(); ++i)
+				{
+					int sprnum = int(vsprites[i] & 0xFFFFFF);
+					int frame = int(vsprites[i] >> 24);
+					if (frame == 255)
+					{ // Apply voxel to all frames.
+						for (int j = MAX_SPRITE_FRAMES - 1; j >= 0; --j)
+						{
+							VOX_AddVoxel(sprnum, j, def);
+						}
+					}
+					else
+					{ // Apply voxel to only one frame.
+						VOX_AddVoxel(sprnum, frame, def);
+					}
+				}
+			}
+		}
+	}
 }
 
 // [RH]
@@ -882,6 +1306,7 @@ void R_InitSprites ()
 	}
 
 	R_InitSpriteDefs ();
+	R_InitVoxels();		// [RH] Parse VOXELDEF
 	NumStdSprites = sprites.Size();
 	R_InitSkins ();		// [RH] Finish loading skin data
 
@@ -956,6 +1381,19 @@ void R_DeinitSprites()
 		spritesortersize = 0;
 		spritesorter = NULL;
 	}
+
+	// Free offscreen buffer
+	if (OffscreenColorBuffer != NULL)
+	{
+		delete[] OffscreenColorBuffer;
+		OffscreenColorBuffer = NULL;
+	}
+	if (OffscreenCoverageBuffer != NULL)
+	{
+		delete OffscreenCoverageBuffer;
+		OffscreenCoverageBuffer = NULL;
+	}
+	OffscreenBufferHeight = OffscreenBufferWidth = 0;
 }
 
 //
@@ -991,7 +1429,7 @@ vissprite_t *R_NewVisSprite (void)
 			*p = new vissprite_t;
 		}
 	}
-	
+
 	vissprite_p++;
 	return *(vissprite_p-1);
 }
@@ -1171,6 +1609,75 @@ void R_DrawVisSprite (vissprite_t *vis)
 	NetUpdate ();
 }
 
+void R_DrawVisVoxel(vissprite_t *spr, int minslabz, int maxslabz, short *cliptop, short *clipbot)
+{
+	ESPSResult mode;
+	int flags = 0;
+
+	// Do setup for blending.
+	dc_colormap = spr->colormap;
+	mode = R_SetPatchStyle(spr->RenderStyle, spr->alpha, spr->Translation, spr->FillColor);
+
+	if (mode == DontDraw)
+	{
+		return;
+	}
+	if (colfunc == fuzzcolfunc || colfunc == R_FillColumnP)
+	{
+		flags = DVF_OFFSCREEN | DVF_SPANSONLY;
+	}
+	else if (colfunc != basecolfunc)
+	{
+		flags = DVF_OFFSCREEN;
+	}
+	if (flags != 0)
+	{
+		R_CheckOffscreenBuffer(RenderTarget->GetWidth(), RenderTarget->GetHeight(), !!(flags & DVF_SPANSONLY));
+	}
+
+	// Render the voxel, either directly to the screen or offscreen.
+	R_DrawVoxel(spr->gx, spr->gy, spr->gz, spr->angle, spr->xscale, spr->yscale, spr->voxel, spr->colormap, cliptop, clipbot,
+		minslabz, maxslabz, flags);
+
+	// Blend the voxel, if that's what we need to do.
+	if (flags != 0)
+	{
+		for (int x = 0; x < viewwidth; ++x)
+		{
+			if (!(flags & DVF_SPANSONLY) && (x & 3) == 0)
+			{
+				rt_initcols(OffscreenColorBuffer + x * OffscreenBufferHeight);
+			}
+			for (FCoverageBuffer::Span *span = OffscreenCoverageBuffer->Spans[x]; span != NULL; span = span->NextSpan)
+			{
+				if (flags & DVF_SPANSONLY)
+				{
+					dc_x = x;
+					dc_yl = span->Start;
+					dc_yh = span->Stop - 1;
+					dc_count = span->Stop - span->Start;
+					dc_dest = ylookup[span->Start] + x + dc_destorg;
+					colfunc();
+				}
+				else
+				{
+					unsigned int **tspan = &dc_ctspan[x & 3];
+					(*tspan)[0] = span->Start;
+					(*tspan)[1] = span->Stop - 1;
+					*tspan += 2;
+				}
+			}
+			if (!(flags & DVF_SPANSONLY) && (x & 3) == 3)
+			{
+				rt_draw4cols(x - 3);
+			}
+		}
+	}
+
+	R_FinishSetPatchStyle();
+	NetUpdate();
+}
+
 //
 // R_ProjectSprite
 // Generates a vissprite for a thing if it might be visible.
@@ -1186,13 +1693,14 @@ void R_ProjectSprite (AActor *thing, int fakeside)
 	fixed_t 			tx, tx2;
 	fixed_t 			tz;
 
-	fixed_t 			xscale;
+	fixed_t 			xscale, yscale;
 	
 	int 				x1;
 	int 				x2;
 
 	FTextureID			picnum;
 	FTexture			*tex;
+	FVoxelDef			*voxel;
 	
 	WORD 				flip;
 	
@@ -1221,26 +1729,8 @@ void R_ProjectSprite (AActor *thing, int fakeside)
 
 	tz = DMulScale20 (tr_x, viewtancos, tr_y, viewtansin);
 
-	// thing is behind view plane?
-	if (tz < MINZ)
-		return;
-
-	tx = DMulScale16 (tr_x, viewsin, -tr_y, viewcos);
-
-	// [RH] Flip for mirrors
-	if (MirrorFlags & RF_XFLIP)
-	{
-		tx = -tx;
-	}
-	tx2 = tx >> 4;
-
-	// too far off the side?
-	if ((abs (tx) >> 6) > tz)
-	{
-		return;
-	}
-
-	xscale = DivScale12 (centerxfrac, tz);
+	tex = NULL;
+	voxel = NULL;
 
 	if (thing->picnum.isValid())
 	{
@@ -1306,52 +1796,53 @@ void R_ProjectSprite (AActor *thing, int fakeside)
 			picnum = sprframe->Texture[rot];
 			flip = sprframe->Flip & (1 << rot);
 			tex = TexMan[picnum];	// Do not animate the rotation
+			if (r_drawvoxels)
+			{
+				voxel = sprframe->Voxel;
+			}
 		}
 	}
-	if (tex == NULL || tex->UseType == FTexture::TEX_Null)
+	if (voxel == NULL && (tex == NULL || tex->UseType == FTexture::TEX_Null))
 	{
 		return;
 	}
 
-	// [RH] Added scaling
-	int scaled_to = tex->GetScaledTopOffset();
-	int scaled_bo = scaled_to - tex->GetScaledHeight();
-	gzt = fz + thing->scaleY * scaled_to;
-	gzb = fz + thing->scaleY * scaled_bo;
+	// thing is behind view plane?
+	if (voxel == NULL && tz < MINZ)
+		return;
 
-	// [RH] Reject sprites that are off the top or bottom of the screen
-	if (MulScale12 (globaluclip, tz) > viewz - gzb ||
-		MulScale12 (globaldclip, tz) < viewz - gzt)
+	tx = DMulScale16 (tr_x, viewsin, -tr_y, viewcos);
+
+	// [RH] Flip for mirrors
+	if (MirrorFlags & RF_XFLIP)
+	{
+		tx = -tx;
+	}
+	tx2 = tx >> 4;
+
+	// too far off the side?
+	if ((abs(tx) >> 6) > abs(tz))
 	{
 		return;
 	}
 
-	// [RH] Flip for mirrors and renderflags
-	if ((MirrorFlags ^ thing->renderflags) & RF_XFLIP)
+	if (voxel == NULL)
 	{
-		flip = !flip;
+		// [RH] Added scaling
+		int scaled_to = tex->GetScaledTopOffset();
+		int scaled_bo = scaled_to - tex->GetScaledHeight();
+		gzt = fz + thing->scaleY * scaled_to;
+		gzb = fz + thing->scaleY * scaled_bo;
+	}
+	else
+	{
+		xscale = FixedMul(thing->scaleX, voxel->Scale);
+		yscale = FixedMul(thing->scaleY, voxel->Scale);
+		gzt = fz + MulScale8(yscale, voxel->Voxel->Mips[0].PivotZ) - thing->floorclip;
+		gzb = fz + MulScale8(yscale, voxel->Voxel->Mips[0].PivotZ - (voxel->Voxel->Mips[0].SizeZ << 8));
+		if (gzt <= gzb)
+			return;
 	}
-
-	// calculate edges of the shape
-	const fixed_t thingxscalemul = DivScale16(thing->scaleX, tex->xScale);
-
-	tx -= (flip ? (tex->GetWidth() - tex->LeftOffset - 1) : tex->LeftOffset) * thingxscalemul;
-	x1 = centerx + MulScale32 (tx, xscale);
-
-	// off the right side?
-	if (x1 > WindowRight)
-		return;
-
-	tx += tex->GetWidth() * thingxscalemul;
-	x2 = centerx + MulScale32 (tx, xscale);
-
-	// off the left side or too small?
-	if (x2 < WindowLeft || x2 <= x1)
-		return;
-
-	xscale = FixedDiv(FixedMul(thing->scaleX, xscale), tex->xScale);
-	iscale = (tex->GetWidth() << FRACBITS) / (x2 - x1);
-	x2--;
 
 	// killough 3/27/98: exclude things totally separated
 	// from the viewer, by either water or fake ceilings
@@ -1380,50 +1871,129 @@ void R_ProjectSprite (AActor *thing, int fakeside)
 		}
 	}
 
-	// store information in a vissprite
-	vis = R_NewVisSprite ();
+	if (voxel == NULL)
+	{
+		xscale = DivScale12 (centerxfrac, tz);
+
+		// [RH] Reject sprites that are off the top or bottom of the screen
+		if (MulScale12 (globaluclip, tz) > viewz - gzb ||
+			MulScale12 (globaldclip, tz) < viewz - gzt)
+		{
+			return;
+		}
+
+		// [RH] Flip for mirrors and renderflags
+		if ((MirrorFlags ^ thing->renderflags) & RF_XFLIP)
+		{
+			flip = !flip;
+		}
+
+		// calculate edges of the shape
+		const fixed_t thingxscalemul = DivScale16(thing->scaleX, tex->xScale);
+
+		tx -= (flip ? (tex->GetWidth() - tex->LeftOffset - 1) : tex->LeftOffset) * thingxscalemul;
+		x1 = centerx + MulScale32 (tx, xscale);
+
+		// off the right side?
+		if (x1 > WindowRight)
+			return;
+
+		tx += tex->GetWidth() * thingxscalemul;
+		x2 = centerx + MulScale32 (tx, xscale);
+
+		// off the left side or too small?
+		if ((x2 < WindowLeft || x2 <= x1))
+			return;
+
+		xscale = FixedDiv(FixedMul(thing->scaleX, xscale), tex->xScale);
+		iscale = (tex->GetWidth() << FRACBITS) / (x2 - x1);
+		x2--;
+
+		fixed_t yscale = SafeDivScale16(thing->scaleY, tex->yScale);
+
+		// store information in a vissprite
+		vis = R_NewVisSprite();
+
+		vis->xscale = xscale;
+		vis->yscale = Scale(InvZtoScale, yscale, tz << 4);
+		vis->idepth = (unsigned)DivScale32(1, tz) >> 1;	// tz is 20.12, so idepth ought to be 12.20, but signed math makes it 13.19
+		vis->floorclip = FixedDiv (thing->floorclip, yscale);
+		vis->texturemid = (tex->TopOffset << FRACBITS) - FixedDiv (viewz - fz + thing->floorclip, yscale);
+		vis->x1 = x1 < WindowLeft ? WindowLeft : x1;
+		vis->x2 = x2 > WindowRight ? WindowRight : x2;
+		vis->angle = thing->angle;
+
+		if (flip)
+		{
+			vis->startfrac = (tex->GetWidth() << FRACBITS) - 1;
+			vis->xiscale = -iscale;
+		}
+		else
+		{
+			vis->startfrac = 0;
+			vis->xiscale = iscale;
+		}
+
+		if (vis->x1 > x1)
+			vis->startfrac += vis->xiscale * (vis->x1 - x1);
+	}
+	else
+	{
+		vis = R_NewVisSprite();
+
+		vis->xscale = xscale;
+		vis->yscale = yscale;
+		vis->x1 = WindowLeft;
+		vis->x2 = WindowRight;
+		vis->idepth = (unsigned)DivScale32(1, MAX(tz, MINZ)) >> 1;
+		vis->floorclip = thing->floorclip;
+
+		fz -= thing->floorclip;
+
+		vis->angle = thing->angle + voxel->AngleOffset;
+
+		int voxelspin = (thing->flags & MF_DROPPED) ? voxel->DroppedSpin : voxel->PlacedSpin;
+		if (voxelspin != 0)
+		{
+			double ang = double(I_FPSTime()) * voxelspin / 1000;
+			vis->angle += angle_t(ang * (4294967296.f / 360));
+		}
+
+		// These are irrelevant for voxels.
+		vis->texturemid = 0x1CEDBEEF;
+		vis->startfrac	= 0x1CEDBEEF;
+		vis->xiscale	= 0x1CEDBEEF;
+	}
 
 	// killough 3/27/98: save sector for special clipping later
 	vis->heightsec = heightsec;
 	vis->sector = thing->Sector;
 
-	fixed_t yscale = DivScale16(thing->scaleY, tex->yScale);
+	vis->cx = tx2;
+	vis->depth = tz;
+	vis->gx = fx;
+	vis->gy = fy;
+	vis->gz = fz;
+	vis->gzb = gzb;		// [RH] use gzb, not thing->z
+	vis->gzt = gzt;		// killough 3/27/98
 	vis->renderflags = thing->renderflags;
 	vis->RenderStyle = thing->RenderStyle;
 	vis->FillColor = thing->fillcolor;
-	vis->xscale = xscale;
-	vis->yscale = Scale (InvZtoScale, yscale, tz << 4);
-	vis->depth = tz;
-	vis->idepth = (DWORD)DivScale32 (1, tz) >> 1;	// tz is 20.12, so idepth ought to be 12.20, but
-	vis->cx = tx2;									// signed math makes it 13.19
-	vis->gx = fx;
-	vis->gy = fy;
-	vis->gz = gzb;		// [RH] use gzb, not thing->z
-	vis->gzt = gzt;		// killough 3/27/98
-	vis->floorclip = FixedDiv (thing->floorclip, yscale);
-	vis->texturemid = (tex->TopOffset << FRACBITS) - 
-		FixedDiv (viewz-fz+thing->floorclip, yscale);
-	vis->x1 = x1 < WindowLeft ? WindowLeft : x1;
-	vis->x2 = x2 > WindowRight ? WindowRight : x2;
 	vis->Translation = thing->Translation;		// [RH] thing translation table
 	vis->FakeFlatStat = fakeside;
 	vis->alpha = thing->alpha;
-	vis->pic = tex;
 
-	if (flip)
+	if (voxel != NULL)
 	{
-		vis->startfrac = (tex->GetWidth() << FRACBITS) - 1;
-		vis->xiscale = -iscale;
+		vis->voxel = voxel->Voxel;
+		vis->bIsVoxel = true;
 	}
 	else
 	{
-		vis->startfrac = 0;
-		vis->xiscale = iscale;
+		vis->pic = tex;
+		vis->bIsVoxel = false;
 	}
 
-	if (vis->x1 > x1)
-		vis->startfrac += vis->xiscale*(vis->x1-x1);
-
 	// The software renderer cannot invert the source without inverting the overlay
 	// too. That means if the source is inverted, we need to do the reverse of what
 	// the invert overlay flag says to do.
@@ -1477,7 +2047,7 @@ void R_ProjectSprite (AActor *thing, int fakeside)
 		else
 		{ // diminished light
 			vis->colormap = mybasecolormap->Maps + (GETPALOOKUP (
-				(fixed_t)DivScale12 (r_SpriteVisibility, tz), spriteshade) << COLORMAPSHIFT);
+				(fixed_t)DivScale12 (r_SpriteVisibility, MAX(tz, MINZ)), spriteshade) << COLORMAPSHIFT);
 		}
 	}
 }
@@ -2081,19 +2651,23 @@ void R_DrawSprite (vissprite_t *spr)
 	static short cliptop[MAXWIDTH];
 	drawseg_t *ds;
 	int i;
+	int x1, x2;
 	int r1, r2;
 	short topclip, botclip;
 	short *clip1, *clip2;
 
 	// [RH] Check for particles
-	if (spr->pic == NULL)
+	if (!spr->bIsVoxel && spr->pic == NULL)
 	{
 		R_DrawParticle (spr);
 		return;
 	}
 
+	x1 = spr->x1;
+	x2 = spr->x2;
+
 	// [RH] Quickly reject sprites with bad x ranges.
-	if (spr->x1 > spr->x2)
+	if (x1 > x2)
 		return;
 
 	// [RH] Sprites split behind a one-sided line can also be discarded.
@@ -2111,54 +2685,62 @@ void R_DrawSprite (vissprite_t *spr)
 	// [RH] rewrote this to be based on which part of the sector is really visible
 
 	fixed_t scale = MulScale19 (InvZtoScale, spr->idepth);
+	fixed_t hzb = FIXED_MIN, hzt = FIXED_MAX;
 
-	if (spr->heightsec &&
-		!(spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC))
+	if (spr->bIsVoxel && spr->floorclip != 0)
+	{
+		hzb = spr->gzb;
+	}
+
+	if (spr->heightsec && !(spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC))
 	{ // only things in specially marked sectors
 		if (spr->FakeFlatStat != FAKED_AboveCeiling)
 		{
-			fixed_t h = spr->heightsec->floorplane.ZatPoint (spr->gx, spr->gy);
-			//h = (centeryfrac - FixedMul (h-viewz, spr->yscale)) >> FRACBITS;
-			h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS;
+			fixed_t hz = spr->heightsec->floorplane.ZatPoint (spr->gx, spr->gy);
+			fixed_t h = (centeryfrac - FixedMul (hz-viewz, scale)) >> FRACBITS;
 
 			if (spr->FakeFlatStat == FAKED_BelowFloor)
 			{ // seen below floor: clip top
-				if (h > topclip)
+				if (!spr->bIsVoxel && h > topclip)
 				{
 					topclip = MIN<short> (h, viewheight);
 				}
+				hzt = MIN(hzt, hz);
 			}
 			else
 			{ // seen in the middle: clip bottom
-				if (h < botclip)
+				if (!spr->bIsVoxel && h < botclip)
 				{
 					botclip = MAX<short> (0, h);
 				}
+				hzb = MAX(hzb, hz);
 			}
 		}
 		if (spr->FakeFlatStat != FAKED_BelowFloor)
 		{
-			fixed_t h = spr->heightsec->ceilingplane.ZatPoint (spr->gx, spr->gy);
-			h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS;
+			fixed_t hz = spr->heightsec->ceilingplane.ZatPoint (spr->gx, spr->gy);
+			fixed_t h = (centeryfrac - FixedMul (hz-viewz, scale)) >> FRACBITS;
 
 			if (spr->FakeFlatStat == FAKED_AboveCeiling)
 			{ // seen above ceiling: clip bottom
-				if (h < botclip)
+				if (!spr->bIsVoxel && h < botclip)
 				{
 					botclip = MAX<short> (0, h);
 				}
+				hzb = MAX(hzb, hz);
 			}
 			else
 			{ // seen in the middle: clip top
-				if (h > topclip)
+				if (!spr->bIsVoxel && h > topclip)
 				{
 					topclip = MIN<short> (h, viewheight);
 				}
+				hzt = MIN(hzt, hz);
 			}
 		}
 	}
 	// killough 3/27/98: end special clipping for deep water / fake ceilings
-	else if (spr->floorclip)
+	else if (!spr->bIsVoxel && spr->floorclip)
 	{ // [RH] Move floorclip stuff from R_DrawVisSprite to here
 		int clip = ((centeryfrac - FixedMul (spr->texturemid -
 			(spr->pic->GetHeight() << FRACBITS) +
@@ -2191,9 +2773,14 @@ void R_DrawSprite (vissprite_t *spr)
 	}
 #endif
 
-	i = spr->x2 - spr->x1 + 1;
-	clip1 = clipbot + spr->x1;
-	clip2 = cliptop + spr->x1;
+	if (topclip >= botclip)
+	{
+		return;
+	}
+
+	i = x2 - x1 + 1;
+	clip1 = clipbot + x1;
+	clip2 = cliptop + x1;
 	do
 	{
 		*clip1++ = botclip;
@@ -2212,7 +2799,7 @@ void R_DrawSprite (vissprite_t *spr)
 	for (ds = ds_p; ds-- > firstdrawseg; )  // new -- killough
 	{
 		// determine if the drawseg obscures the sprite
-		if (ds->x1 > spr->x2 || ds->x2 < spr->x1 ||
+		if (ds->x1 > x2 || ds->x2 < x1 ||
 			(!(ds->silhouette & SIL_BOTH) && ds->maskedtexturecol == -1 &&
 			 !ds->bFogBoundary) )
 		{
@@ -2220,8 +2807,8 @@ void R_DrawSprite (vissprite_t *spr)
 			continue;
 		}
 
-		r1 = MAX<int> (ds->x1, spr->x1);
-		r2 = MIN<int> (ds->x2, spr->x2);
+		r1 = MAX<int> (ds->x1, x1);
+		r2 = MIN<int> (ds->x2, x2);
 
 		fixed_t neardepth, fardepth;
 		if (ds->sz1 < ds->sz2)
@@ -2279,9 +2866,33 @@ void R_DrawSprite (vissprite_t *spr)
 
 	// all clipping has been performed, so draw the sprite
 
-	mfloorclip = clipbot;
-	mceilingclip = cliptop;
-	R_DrawVisSprite (spr);
+	if (!spr->bIsVoxel)
+	{
+		mfloorclip = clipbot;
+		mceilingclip = cliptop;
+		R_DrawVisSprite (spr);
+	}
+	else
+	{
+		// If it is completely clipped away, don't bother drawing it.
+		if (cliptop[x2] >= clipbot[x2])
+		{
+			for (i = x1; i < x2; ++i)
+			{
+				if (cliptop[i] < clipbot[i])
+				{
+					break;
+				}
+			}
+			if (i == x2)
+			{
+				return;
+			}
+		}
+		int minvoxely = spr->gzt <= hzt ? 0 : (spr->gzt - hzt) / spr->yscale;
+		int maxvoxely = spr->gzb > hzb ? INT_MAX : (spr->gzt - hzb) / spr->yscale;
+		R_DrawVisVoxel(spr, minvoxely, maxvoxely, cliptop, clipbot);
+	}
 }
 
 //
@@ -2538,13 +3149,14 @@ void R_ProjectParticle (particle_t *particle, const sector_t *sector, int shade,
 	vis->cx = tx;
 	vis->gx = particle->x;
 	vis->gy = particle->y;
-	vis->gz = y1;
+	vis->gzb = y1;
 	vis->gzt = y2;
 	vis->x1 = x1;
 	vis->x2 = x2;
 	vis->Translation = 0;
 	vis->startfrac = particle->color;
 	vis->pic = NULL;
+	vis->bIsVoxel = false;
 	vis->renderflags = particle->trans;
 	vis->FakeFlatStat = fakeside;
 	vis->floorclip = 0;
@@ -2595,7 +3207,7 @@ void R_DrawParticle (vissprite_t *vis)
 	BYTE *dest;
 	DWORD fg;
 	BYTE color = vis->colormap[vis->startfrac];
-	int yl = vis->gz;
+	int yl = vis->gzb;
 	int ycount = vis->gzt - yl + 1;
 	int x1 = vis->x1;
 	int countbase = vis->x2 - x1 + 1;
@@ -2629,3 +3241,480 @@ void R_DrawParticle (vissprite_t *vis)
 		dest += spacing;
 	} while (--ycount);
 }
+
+static fixed_t distrecip(fixed_t y)
+{
+	y >>= 3;
+	return y == 0 ? 0 : SafeDivScale32(centerxwide, y);
+}
+
+extern fixed_t baseyaspectmul;
+
+void R_DrawVoxel(fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t dasprang,
+	fixed_t daxscale, fixed_t dayscale, FVoxel *voxobj,
+	lighttable_t *colormap, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags)
+{
+	int i, j, k, x, y, syoff, ggxstart, ggystart, nxoff;
+	fixed_t cosang, sinang, sprcosang, sprsinang;
+	int backx, backy, gxinc, gyinc;
+	int daxscalerecip, dayscalerecip, cnt, gxstart, gystart, dazscale;
+	int lx, rx, nx, ny, x1=0, y1=0, x2=0, y2=0, yplc, yinc=0;
+	int yoff, xs=0, ys=0, xe, ye, xi=0, yi=0, cbackx, cbacky, dagxinc, dagyinc;
+	kvxslab_t *voxptr, *voxend;
+	FVoxelMipLevel *mip;
+
+	const int nytooclose = centerxwide * 2100, nytoofar = 32768*32768 - 1048576;
+	const int xdimenscale = Scale(centerxwide, yaspectmul, 160);
+	const fixed_t globalposx =  viewx >> 12;
+	const fixed_t globalposy = -viewy >> 12;
+	const fixed_t globalposz = -viewz >> 8;
+
+	dasprx =  dasprx >> 12;
+	daspry = -daspry >> 12;
+	dasprz = -dasprz >> 8;
+	daxscale >>= 10;
+	dayscale >>= 10;
+
+	cosang = viewcos >> 2;
+	sinang = -viewsin >> 2;
+	sprcosang = finecosine[dasprang >> ANGLETOFINESHIFT] >> 2;
+	sprsinang = -finesine[dasprang >> ANGLETOFINESHIFT] >> 2;
+
+	R_SetupDrawSlab(colormap);
+
+	// Select mip level
+	i = abs(DMulScale8(dasprx - globalposx, viewcos, daspry - globalposy, -viewsin));
+	i = DivScale6(i, MIN(daxscale, dayscale));
+	j = FocalLengthX >> 3;
+	for (k = 0; k < voxobj->NumMips; ++k)
+	{
+		if (i < j) { break; }
+		i >>= 1;
+	}
+	if (k >= voxobj->NumMips) k = voxobj->NumMips - 1;
+
+	mip = &voxobj->Mips[k];		if (mip->SlabData == NULL) return;
+
+	minslabz >>= k;
+	maxslabz >>= k;
+
+	daxscale <<= (k+8); dayscale <<= (k+8);
+	dazscale = FixedDiv(dayscale, baseyaspectmul);
+	daxscale = FixedDiv(daxscale, yaspectmul);
+	daxscale = Scale(daxscale, xdimenscale, centerxwide << 9);
+	dayscale = Scale(dayscale, FixedMul(xdimenscale, viewingrangerecip), centerxwide << 9);
+
+	daxscalerecip = (1<<30) / daxscale;
+	dayscalerecip = (1<<30) / dayscale;
+
+	x = FixedMul(globalposx - dasprx, daxscalerecip);
+	y = FixedMul(globalposy - daspry, daxscalerecip);
+	backx = (DMulScale10(x, sprcosang, y,  sprsinang) + mip->PivotX) >> 8;
+	backy = (DMulScale10(y, sprcosang, x, -sprsinang) + mip->PivotY) >> 8;
+	cbackx = clamp(backx, 0, mip->SizeX - 1);
+	cbacky = clamp(backy, 0, mip->SizeY - 1);
+
+	sprcosang = MulScale14(daxscale, sprcosang);
+	sprsinang = MulScale14(daxscale, sprsinang);
+
+	x = (dasprx - globalposx) - DMulScale18(mip->PivotX, sprcosang, mip->PivotY, -sprsinang);
+	y = (daspry - globalposy) - DMulScale18(mip->PivotY, sprcosang, mip->PivotX,  sprsinang);
+
+	cosang = FixedMul(cosang, dayscalerecip);
+	sinang = FixedMul(sinang, dayscalerecip);
+
+	gxstart = y*cosang - x*sinang;
+	gystart = x*cosang + y*sinang;
+	gxinc = DMulScale10(sprsinang, cosang, sprcosang, -sinang);
+	gyinc = DMulScale10(sprcosang, cosang, sprsinang,  sinang);
+	if ((abs(globalposz - dasprz) >> 10) >= abs(dazscale)) return;
+
+	x = 0; y = 0; j = MAX(mip->SizeX, mip->SizeY);
+	fixed_t *ggxinc = (fixed_t *)alloca((j + 1) * sizeof(fixed_t) * 2);
+	fixed_t *ggyinc = ggxinc + (j + 1);
+	for (i = 0; i <= j; i++)
+	{
+		ggxinc[i] = x; x += gxinc;
+		ggyinc[i] = y; y += gyinc;
+	}
+
+	syoff = DivScale21(globalposz - dasprz, dazscale) + (mip->PivotZ << 7);
+	yoff = (abs(gxinc) + abs(gyinc)) >> 1;
+
+	for (cnt = 0; cnt < 8; cnt++)
+	{
+		switch (cnt)
+		{
+			case 0: xs = 0;				ys = 0;				xi =  1; yi =  1; break;
+			case 1: xs = mip->SizeX-1;	ys = 0;				xi = -1; yi =  1; break;
+			case 2: xs = 0;				ys = mip->SizeY-1;	xi =  1; yi = -1; break;
+			case 3: xs = mip->SizeX-1;	ys = mip->SizeY-1;	xi = -1; yi = -1; break;
+			case 4: xs = 0;				ys = cbacky;		xi =  1; yi =  2; break;
+			case 5: xs = mip->SizeX-1;	ys = cbacky;		xi = -1; yi =  2; break;
+			case 6: xs = cbackx;		ys = 0;				xi =  2; yi =  1; break;
+			case 7: xs = cbackx;		ys = mip->SizeY-1;	xi =  2; yi = -1; break;
+		}
+		xe = cbackx; ye = cbacky;
+		if (cnt < 4)
+		{
+			if ((xi < 0) && (xe >= xs)) continue;
+			if ((xi > 0) && (xe <= xs)) continue;
+			if ((yi < 0) && (ye >= ys)) continue;
+			if ((yi > 0) && (ye <= ys)) continue;
+		}
+		else
+		{
+			if ((xi < 0) && (xe > xs)) continue;
+			if ((xi > 0) && (xe < xs)) continue;
+			if ((yi < 0) && (ye > ys)) continue;
+			if ((yi > 0) && (ye < ys)) continue;
+			xe += xi; ye += yi;
+		}
+
+		i = ksgn(ys-backy)+ksgn(xs-backx)*3+4;
+		switch(i)
+		{
+			case 6: case 7: x1 = 0;				y1 = 0;				break;
+			case 8: case 5: x1 = gxinc;			y1 = gyinc;			break;
+			case 0: case 3: x1 = gyinc;			y1 = -gxinc;		break;
+			case 2: case 1: x1 = gxinc+gyinc;	y1 = gyinc-gxinc;	break;
+		}
+		switch(i)
+		{
+			case 2: case 5: x2 = 0;				y2 = 0;				break;
+			case 0: case 1: x2 = gxinc;			y2 = gyinc;			break;
+			case 8: case 7: x2 = gyinc;			y2 = -gxinc;		break;
+			case 6: case 3: x2 = gxinc+gyinc;	y2 = gyinc-gxinc;	break;
+		}
+		BYTE oand = (1 << int(xs<backx)) + (1 << (int(ys<backy)+2));
+		BYTE oand16 = oand + 16;
+		BYTE oand32 = oand + 32;
+
+		if (yi > 0) { dagxinc =  gxinc; dagyinc =  FixedMul(gyinc, viewingrangerecip); }
+			   else { dagxinc = -gxinc; dagyinc = -FixedMul(gyinc, viewingrangerecip); }
+
+			/* Fix for non 90 degree viewing ranges */
+		nxoff = FixedMul(x2 - x1, viewingrangerecip);
+		x1 = FixedMul(x1, viewingrangerecip);
+
+		ggxstart = gxstart + ggyinc[ys];
+		ggystart = gystart - ggxinc[ys];
+
+		for (x = xs; x != xe; x += xi)
+		{
+			BYTE *slabxoffs = &mip->SlabData[mip->OffsetX[x]];
+			short *xyoffs = &mip->OffsetXY[x * (mip->SizeY + 1)];
+
+			nx = FixedMul(ggxstart + ggxinc[x], viewingrangerecip) + x1;
+			ny = ggystart + ggyinc[x];
+			for (y = ys; y != ye; y += yi, nx += dagyinc, ny -= dagxinc)
+			{
+				if ((ny <= nytooclose) || (ny >= nytoofar)) continue;
+				voxptr = (kvxslab_t *)(slabxoffs + xyoffs[y]);
+				voxend = (kvxslab_t *)(slabxoffs + xyoffs[y+1]);
+				if (voxptr >= voxend) continue;
+
+				lx = MulScale32(nx >> 3, distrecip(ny+y1)) + centerx;
+				if (lx < 0) lx = 0;
+				rx = MulScale32((nx + nxoff) >> 3, distrecip(ny+y2)) + centerx;
+				if (rx > viewwidth) rx = viewwidth;
+				if (rx <= lx) continue;
+				rx -= lx;
+
+				fixed_t l1 = distrecip(ny-yoff);
+				fixed_t l2 = distrecip(ny+yoff);
+				for (; voxptr < voxend; voxptr = (kvxslab_t *)((BYTE *)voxptr + voxptr->zleng + 3))
+				{
+					const BYTE *col = voxptr->col;
+					int zleng = voxptr->zleng;
+					int ztop = voxptr->ztop;
+					fixed_t z1, z2;
+
+					if (ztop < minslabz)
+					{
+						int diff = minslabz - ztop;
+						ztop = minslabz;
+						col += diff;
+						zleng -= diff;
+					}
+					if (ztop + zleng > maxslabz)
+					{
+						int diff = ztop + zleng - maxslabz;
+						zleng -= diff;
+					}
+					if (zleng <= 0) continue;
+
+					j = (ztop << 15) - syoff;
+					if (j < 0)
+					{
+						k = j + (zleng << 15);
+						if (k < 0)
+						{
+							if ((voxptr->backfacecull & oand32) == 0) continue;
+							z2 = MulScale32(l2, k) + centery;					/* Below slab */
+						}
+						else
+						{
+							if ((voxptr->backfacecull & oand) == 0) continue;	/* Middle of slab */
+							z2 = MulScale32(l1, k) + centery;
+						}
+						z1 = MulScale32(l1, j) + centery;
+					}
+					else
+					{
+						if ((voxptr->backfacecull & oand16) == 0) continue;
+						z1 = MulScale32(l2, j) + centery;						/* Above slab */
+						z2 = MulScale32(l1, j + (zleng << 15)) + centery;
+					}
+
+					if (zleng == 1)
+					{
+						yplc = 0; yinc = 0;
+						if (z1 < daumost[lx]) z1 = daumost[lx];
+					}
+					else
+					{
+						if (z2-z1 >= 1024) yinc = FixedDiv(zleng, z2 - z1);
+						else if (z2 > z1) yinc = (((1 << 24) - 1) / (z2 - z1)) * zleng >> 8;
+						if (z1 < daumost[lx]) { yplc = yinc*(daumost[lx]-z1); z1 = daumost[lx]; } else yplc = 0;
+					}
+					if (z2 > dadmost[lx]) z2 = dadmost[lx];
+					z2 -= z1; if (z2 <= 0) continue;
+
+					if (!(flags & DVF_OFFSCREEN))
+					{
+						// Draw directly to the screen.
+						R_DrawSlab(rx, yplc, z2, yinc, col, ylookup[z1] + lx + dc_destorg);
+					}
+					else
+					{
+						// Record the area covered and possibly draw to an offscreen buffer.
+						dc_yl = z1;
+						dc_yh = z1 + z2 - 1;
+						dc_count = z2;
+						dc_iscale = yinc;
+						for (int x = 0; x < rx; ++x)
+						{
+							OffscreenCoverageBuffer->InsertSpan(lx + x, z1, z1 + z2);
+							if (!(flags & DVF_SPANSONLY))
+							{
+								dc_x = lx + x;
+								rt_initcols(OffscreenColorBuffer + (dc_x & ~3) * OffscreenBufferHeight);
+								dc_source = col;
+								dc_texturefrac = yplc;
+								hcolfunc_pre();
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+//==========================================================================
+//
+// FCoverageBuffer Constructor
+//
+//==========================================================================
+
+FCoverageBuffer::FCoverageBuffer(int lists)
+	: Spans(NULL), FreeSpans(NULL)
+{
+	NumLists = lists;
+	Spans = new Span *[lists];
+	memset(Spans, 0, sizeof(Span*)*lists);
+}
+
+//==========================================================================
+//
+// FCoverageBuffer Destructor
+//
+//==========================================================================
+
+FCoverageBuffer::~FCoverageBuffer()
+{
+	if (Spans != NULL)
+	{
+		delete[] Spans;
+	}
+}
+
+//==========================================================================
+//
+// FCoverageBuffer :: Clear
+//
+//==========================================================================
+
+void FCoverageBuffer::Clear()
+{
+	SpanArena.FreeAll();
+	memset(Spans, 0, sizeof(Span*)*NumLists);
+	FreeSpans = NULL;
+}
+
+//==========================================================================
+//
+// FCoverageBuffer :: InsertSpan
+//
+// start is inclusive.
+// stop is exclusive.
+//
+//==========================================================================
+
+void FCoverageBuffer::InsertSpan(int listnum, int start, int stop)
+{
+	assert(unsigned(listnum) < NumLists);
+	assert(start < stop);
+
+	Span **span_p = &Spans[listnum];
+	Span *span;
+
+	if (*span_p == NULL || (*span_p)->Start > stop)
+	{ // This list is empty or the first entry is after this one, so we can just insert the span.
+		goto addspan;
+	}
+
+	// Insert the new span in order, merging with existing ones.
+	while (*span_p != NULL)
+	{
+		if ((*span_p)->Stop < start)							// =====		(existing span)
+		{ // Span ends before this one starts.					//		  ++++	(new span)
+			span_p = &(*span_p)->NextSpan;
+			continue;
+		}
+
+		// Does the new span overlap or abut the existing one?
+		if ((*span_p)->Start <= start)
+		{
+			if ((*span_p)->Stop >= stop)						// =============
+			{ // The existing span completely covers this one.	//     +++++
+				return;
+			}
+			// Extend the existing span with the new one.		// ======
+			span = *span_p;										//     +++++++
+			span->Stop = stop;									// (or)  +++++
+
+			// Free up any spans we just covered up.
+			span_p = &(*span_p)->NextSpan;
+			while (*span_p != NULL && (*span_p)->Start <= stop && (*span_p)->Stop <= stop)
+			{
+				Span *span = *span_p;							// ======  ======
+				*span_p = span->NextSpan;						//     +++++++++++++
+				span->NextSpan = FreeSpans;
+				FreeSpans = span;
+			}
+			if (*span_p != NULL && (*span_p)->Start <= stop)	// =======         ========
+			{ // Our new span connects two existing spans.		//     ++++++++++++++
+			  // They should all be collapsed into a single span.
+				span->Stop = (*span_p)->Stop;
+				span = *span_p;
+				*span_p = span->NextSpan;
+				span->NextSpan = FreeSpans;
+				FreeSpans = span;
+			}
+			goto check;
+		}
+		else if ((*span_p)->Start <= stop)						//        =====
+		{ // The new span extends the existing span from		//    ++++
+		  // the beginning.										// (or) ++++
+			(*span_p)->Start = start;
+			goto check;
+		}
+		else													//         ======
+		{ // No overlap, so insert a new span.					// +++++
+			goto addspan;
+		}
+	}
+	// Append a new span to the end of the list.
+addspan:
+	span = AllocSpan();
+	span->NextSpan = *span_p;
+	span->Start = start;
+	span->Stop = stop;
+	*span_p = span;
+check:
+#ifdef _DEBUG
+	// Validate the span list: Spans must be in order, and there must be
+	// at least one pixel between spans.
+	for (span = Spans[listnum]; span != NULL; span = span->NextSpan)
+	{
+		assert(span->Start < span->Stop);
+		if (span->NextSpan != NULL)
+		{
+			assert(span->Stop < span->NextSpan->Start);
+		}
+	}
+#endif
+	;
+}
+
+//==========================================================================
+//
+// FCoverageBuffer :: AllocSpan
+//
+//==========================================================================
+
+FCoverageBuffer::Span *FCoverageBuffer::AllocSpan()
+{
+	Span *span;
+
+	if (FreeSpans != NULL)
+	{
+		span = FreeSpans;
+		FreeSpans = span->NextSpan;
+	}
+	else
+	{
+		span = (Span *)SpanArena.Alloc(sizeof(Span));
+	}
+	return span;
+}
+
+//==========================================================================
+//
+// R_CheckOffscreenBuffer
+//
+// Allocates the offscreen coverage buffer and optionally the offscreen
+// color buffer. If they already exist but are the wrong size, they will
+// be reallocated.
+//
+//==========================================================================
+
+void R_CheckOffscreenBuffer(int width, int height, bool spansonly)
+{
+	if (OffscreenCoverageBuffer == NULL)
+	{
+		assert(OffscreenColorBuffer == NULL && "The color buffer cannot exist without the coverage buffer");
+		OffscreenCoverageBuffer = new FCoverageBuffer(width);
+	}
+	else if (OffscreenCoverageBuffer->NumLists != width)
+	{
+		delete OffscreenCoverageBuffer;
+		OffscreenCoverageBuffer = new FCoverageBuffer(width);
+		if (OffscreenColorBuffer != NULL)
+		{
+			delete[] OffscreenColorBuffer;
+			OffscreenColorBuffer = NULL;
+		}
+	}
+	else
+	{
+		OffscreenCoverageBuffer->Clear();
+	}
+
+	if (!spansonly)
+	{
+		if (OffscreenColorBuffer == NULL)
+		{
+			OffscreenColorBuffer = new BYTE[width * height];
+		}
+		else if (OffscreenBufferWidth != width || OffscreenBufferHeight != height)
+		{
+			delete[] OffscreenColorBuffer;
+			OffscreenColorBuffer = new BYTE[width * height];
+		}
+	}
+	OffscreenBufferWidth = width;
+	OffscreenBufferHeight = height;
+}
diff --git a/src/r_things.h b/src/r_things.h
index 9ae796ce9..da31daf79 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -104,6 +104,14 @@ void R_ClearSprites ();
 void R_DrawMasked ();
 void R_DrawRemainingPlayerSprites ();
 
+void R_CheckOffscreenBuffer(int width, int height, bool spansonly);
+
+enum { DVF_OFFSCREEN = 1, DVF_SPANSONLY = 2 };
+
+void R_DrawVoxel(fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t dasprang,
+	fixed_t daxscale, fixed_t dayscale, FVoxel *voxobj,
+	lighttable_t *colormap, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags);
+
 void R_ClipVisSprite (vissprite_t *vis, int xl, int xh);
 
 
diff --git a/src/resourcefiles/file_wad.cpp b/src/resourcefiles/file_wad.cpp
index ffa9c85e8..3a6de70ea 100644
--- a/src/resourcefiles/file_wad.cpp
+++ b/src/resourcefiles/file_wad.cpp
@@ -113,6 +113,7 @@ bool FWadFile::Open(bool quiet)
 		SetNamespace("TX_START", "TX_END", ns_newtextures);
 		SetNamespace("V_START", "V_END", ns_strifevoices);
 		SetNamespace("HI_START", "HI_END", ns_hires);
+		SetNamespace("VX_START", "VX_END", ns_voxels);
 		SkinHack();
 	}
 	delete [] fileinfo;
diff --git a/src/resourcefiles/resourcefile.cpp b/src/resourcefiles/resourcefile.cpp
index c5bde5712..866605d14 100644
--- a/src/resourcefiles/resourcefile.cpp
+++ b/src/resourcefiles/resourcefile.cpp
@@ -116,6 +116,7 @@ void FResourceLump::LumpNameSetup(const char *iname)
 				!strncmp(iname, "textures/", 9)		? ns_newtextures :
 				!strncmp(iname, "hires/", 6)		? ns_hires :
 				!strncmp(iname, "sprites/", 8)		? ns_sprites :
+				!strncmp(iname, "voxels/", 7)		? ns_voxels :
 				!strncmp(iname, "colormaps/", 10)	? ns_colormaps :
 				!strncmp(iname, "acs/", 4)			? ns_acslibrary :
 				!strncmp(iname, "voices/", 7)		? ns_strifevoices :
@@ -137,7 +138,7 @@ void FResourceLump::LumpNameSetup(const char *iname)
 	// Since '\' can't be used as a file name's part inside a ZIP
 	// we have to work around this for sprites because it is a valid
 	// frame character.
-	else if (Namespace == ns_sprites)
+	else if (Namespace == ns_sprites || Namespace == ns_voxels)
 	{
 		char *c;
 
diff --git a/src/tables.h b/src/tables.h
index 72a79383d..aeb2c6991 100644
--- a/src/tables.h
+++ b/src/tables.h
@@ -60,7 +60,7 @@ extern	fixed_t 		finesine[5*FINEANGLES/4];
 // (encapsulated in a struct so that we can still use array accesses).
 struct cosine_inline
 {
-	fixed_t operator[] (unsigned int x)
+	fixed_t operator[] (unsigned int x) const
 	{
 		return finesine[x+FINEANGLES/4];
 	}
diff --git a/src/textures/buildtexture.cpp b/src/textures/buildtexture.cpp
index 2a3d1809f..836cc2daa 100644
--- a/src/textures/buildtexture.cpp
+++ b/src/textures/buildtexture.cpp
@@ -234,6 +234,7 @@ void FTextureManager::AddTiles (void *tiles)
 			rot.Texture[8] =
 			rot.Texture[9] = texnum.GetIndex() + 4;
 			rot.Flip = 0x00FC;
+			rot.Voxel = NULL;
 			tex->Rotations = SpriteFrames.Push (rot);
 		}
 		else if (rotType == 2)
@@ -247,6 +248,7 @@ void FTextureManager::AddTiles (void *tiles)
 				rot.Texture[17-j*2] = texnum.GetIndex() + j;
 			}
 			rot.Flip = 0;
+			rot.Voxel = NULL;
 			tex->Rotations = SpriteFrames.Push (rot);
 		}
 	}
diff --git a/src/v_video.cpp b/src/v_video.cpp
index 450650f53..1986a2a97 100644
--- a/src/v_video.cpp
+++ b/src/v_video.cpp
@@ -1313,6 +1313,21 @@ void DFrameBuffer::RenderView(player_t *player)
 	FCanvasTextureInfo::UpdateAll ();
 }
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+extern TDeletingArray<FVoxel *> Voxels;
+
+void DFrameBuffer::RemapVoxels()
+{
+	for (unsigned i=0; i<Voxels.Size(); i++)
+	{
+		Voxels[i]->Remap();
+	}
+}
+
 //===========================================================================
 //
 // Render the view to a savegame picture
@@ -1689,6 +1704,7 @@ void V_Init2()
 		Printf ("Resolution: %d x %d\n", SCREENWIDTH, SCREENHEIGHT);
 
 	screen->SetGamma (gamma);
+	screen->RemapVoxels();
 	FBaseCVar::ResetColors ();
 	C_NewModeAdjust();
 	M_InitVideoModesMenu();
@@ -1733,43 +1749,61 @@ CUSTOM_CVAR (Int, vid_aspect, 0, CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
 // 1: 16:9
 // 2: 16:10
 // 4: 5:4
-int CheckRatio (int width, int height)
+int CheckRatio (int width, int height, int *trueratio)
 {
+	int fakeratio = -1;
+	int ratio;
+
 	if ((vid_aspect >=1) && (vid_aspect <=4))
 	{
 		// [SP] User wants to force aspect ratio; let them.
-		return vid_aspect == 3? 0: int(vid_aspect);
+		fakeratio = vid_aspect == 3? 0: int(vid_aspect);
 	}
 	if (vid_nowidescreen)
 	{
 		if (!vid_tft)
 		{
-			return 0;
+			fakeratio = 0;
+		}
+		else
+		{
+			fakeratio = (height * 5/4 == width) ? 4 : 0;
 		}
-		return (height * 5/4 == width) ? 4 : 0;
 	}
 	// If the size is approximately 16:9, consider it so.
 	if (abs (height * 16/9 - width) < 10)
 	{
-		return 1;
+		ratio = 1;
 	}
 	// 16:10 has more variance in the pixel dimensions. Grr.
-	if (abs (height * 16/10 - width) < 60)
+	else if (abs (height * 16/10 - width) < 60)
 	{
 		// 320x200 and 640x400 are always 4:3, not 16:10
 		if ((width == 320 && height == 200) || (width == 640 && height == 400))
 		{
-			return 0;
+			ratio = 0;
+		}
+		else
+		{
+			ratio = 2;
 		}
-		return 2;
 	}
 	// Unless vid_tft is set, 1280x1024 is 4:3, not 5:4.
-	if (height * 5/4 == width && vid_tft)
+	else if (height * 5/4 == width && vid_tft)
 	{
-		return 4;
+		ratio = 4;
 	}
 	// Assume anything else is 4:3.
-	return 0;
+	else
+	{
+		ratio = 0;
+	}
+
+	if (trueratio != NULL)
+	{
+		*trueratio = ratio;
+	}
+	return (fakeratio >= 0) ? fakeratio : ratio;
 }
 
 // First column: Base width (unused)
diff --git a/src/v_video.h b/src/v_video.h
index 0fb9eaf8a..b56a4d289 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -364,6 +364,9 @@ public:
 	// Set the rect defining the area affected by blending.
 	virtual void SetBlendingRect (int x1, int y1, int x2, int y2);
 
+	// Remap voxel palette
+	virtual void RemapVoxels();
+
 	// render 3D view
 	virtual void RenderView(player_t *player);
 
@@ -500,7 +503,7 @@ void V_DrawFrame (int left, int top, int width, int height);
 extern "C" void ASM_PatchPitch (void);
 #endif
 
-int CheckRatio (int width, int height);
+int CheckRatio (int width, int height, int *trueratio=NULL);
 static inline int CheckRatio (double width, double height) { return CheckRatio(int(width), int(height)); }
 extern const int BaseRatioSizes[5][4];
 
diff --git a/src/w_wad.h b/src/w_wad.h
index dc1ea7794..76d94959d 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -62,6 +62,7 @@ typedef enum {
 	ns_bloodmisc,
 	ns_strifevoices,
 	ns_hires,
+	ns_voxels,
 
 	// These namespaces are only used to mark lumps in special subdirectories
 	// so that their contents doesn't interfere with the global namespace.
diff --git a/zdoom.vcproj b/zdoom.vcproj
index c96f7e246..29801cd05 100644
--- a/zdoom.vcproj
+++ b/zdoom.vcproj
@@ -704,6 +704,10 @@
 				RelativePath=".\src\md5.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\src\memarena.cpp"
+				>
+			</File>
 			<File
 				RelativePath=".\src\name.cpp"
 				>
@@ -1353,6 +1357,10 @@
 				RelativePath=".\src\md5.h"
 				>
 			</File>
+			<File
+				RelativePath=".\src\memarena.h"
+				>
+			</File>
 			<File
 				RelativePath=".\src\mscinlines.h"
 				>