diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 723407d86fa17529d138634c3e6b48f2338f2557..fb1bd8afb14c03f35ef3f891dad0761d3da8c88e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -423,7 +423,11 @@ if(${SRB2_CONFIG_HWRENDER})
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_light.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_main.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2load.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md3load.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_model.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_trick.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/u_list.c
 	)
 
 	set (SRB2_HWRENDER_HEADERS
@@ -437,6 +441,10 @@ if(${SRB2_CONFIG_HWRENDER})
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_light.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_main.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2load.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md3load.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_model.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/u_list.h
 	)
 
 	set(SRB2_R_OPENGL_SOURCES
diff --git a/src/Makefile b/src/Makefile
index 8e9e1816d478be81b052344a471c0b835ccb52dc..c5eb8b4a3ac2e7f69cb33b40e007125f448b88d1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -226,7 +226,8 @@ else
 	#OPTS+=-DUSE_PALETTED_TEXTURE
 	OPTS+=-DHWRENDER
 	OBJS+=$(OBJDIR)/hw_bsp.o $(OBJDIR)/hw_draw.o $(OBJDIR)/hw_light.o \
-		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o $(OBJDIR)/hw_trick.o
+		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o $(OBJDIR)/hw_trick.o \
+		 $(OBJDIR)/hw_md2load.o $(OBJDIR)/hw_md3load.o $(OBJDIR)/hw_model.o $(OBJDIR)/u_list.o
 endif
 
 ifdef NOHS
@@ -646,16 +647,18 @@ ifdef MINGW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 else
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -I/usr/X11R6/include -c $< -o $@
 endif
@@ -733,16 +736,18 @@ ifndef NOHW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
 
 $(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
 endif
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 411d847b54e90d5b310d1e340f02d403c527b919..ed66d51d29c1a124d938740734accc77c183538f 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -4708,7 +4708,12 @@ void TryRunTics(tic_t realtics)
 	if (neededtic > gametic && !resynch_local_inprogress)
 	{
 		if (advancedemo)
-			D_StartTitle();
+		{
+			if (timedemo_quit)
+				COM_ImmedExecute("quit");
+			else
+				D_StartTitle();
+		}
 		else
 			// run the count * tics
 			while (neededtic > gametic)
diff --git a/src/d_main.c b/src/d_main.c
index ff4928869a1db7a89d000152884e151221e2ccde..0a13c6d0b0108bc83acbfc1501e40c92305eaaa4 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -438,6 +438,13 @@ static void D_Display(void)
 			F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 		}
 
+		// reset counters so timedemo doesn't count the wipe duration
+		if (timingdemo)
+		{
+			framecount = 0;
+			demostarttime = I_GetTime();
+		}
+		
 		wipetypepost = -1;
 	}
 	else
@@ -541,9 +548,6 @@ void D_SRB2Loop(void)
 	if (dedicated)
 		server = true;
 
-	if (M_CheckParm("-voodoo")) // 256x256 Texture Limiter
-		COM_BufAddText("gr_voodoocompatibility on\n");
-
 	// Pushing of + parameters is now done back in D_SRB2Main, not here.
 
 	CONS_Printf("I_StartupKeyboard()...\n");
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 0929577b498b779b7a4f663a6da2e648978e2c4c..95d70a776186ee548ceb359c48a5afdd6d0bdf07 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -365,6 +365,11 @@ consvar_t cv_mute = {"mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange,
 
 consvar_t cv_sleep = {"cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
 
+char timedemo_name[256];
+boolean timedemo_csv;
+char timedemo_csv_id[256];
+boolean timedemo_quit;
+
 INT16 gametype = GT_COOP;
 boolean splitscreen = false;
 boolean circuitmap = false;
@@ -1569,11 +1574,11 @@ static void Command_Playdemo_f(void)
 
 static void Command_Timedemo_f(void)
 {
-	char name[256];
+	size_t i = 0;
 
-	if (COM_Argc() != 2)
+	if (COM_Argc() < 2)
 	{
-		CONS_Printf(M_GetText("timedemo <demoname>: time a demo\n"));
+		CONS_Printf(M_GetText("timedemo <demoname> [-csv [<trialid>]] [-quit]: time a demo\n"));
 		return;
 	}
 
@@ -1590,12 +1595,23 @@ static void Command_Timedemo_f(void)
 		G_StopMetalDemo();
 
 	// open the demo file
-	strcpy (name, COM_Argv(1));
+	strcpy (timedemo_name, COM_Argv(1));
 	// dont add .lmp so internal game demos can be played
 
-	CONS_Printf(M_GetText("Timing demo '%s'.\n"), name);
+	// print timedemo results as CSV?
+	i = COM_CheckParm("-csv");
+	timedemo_csv = (i > 0);
+	if (COM_CheckParm("-quit") != i + 1)
+		strcpy(timedemo_csv_id, COM_Argv(i + 1)); // user-defined string to identify row
+	else
+		timedemo_csv_id[0] = 0;
+
+	// exit after the timedemo?
+	timedemo_quit = (COM_CheckParm("-quit") > 0);
+
+	CONS_Printf(M_GetText("Timing demo '%s'.\n"), timedemo_name);
 
-	G_TimeDemo(name);
+	G_TimeDemo(timedemo_name);
 }
 
 // stop current demo
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index e789e5b50f467d8dceec697ab841adcf851e0bf4..0b538890ec569dc8a2062889dc9842c502b89af3 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -113,6 +113,11 @@ extern consvar_t cv_skipmapcheck;
 
 extern consvar_t cv_sleep;
 
+extern char timedemo_name[256];
+extern boolean timedemo_csv;
+extern char timedemo_csv_id[256];
+extern boolean timedemo_quit;
+
 typedef enum
 {
 	XD_NAMEANDCOLOR = 1,
diff --git a/src/doomtype.h b/src/doomtype.h
index 7acdde966835b97f6977214e8925729bdbe14108..5f60e21b51cc6aec62c1a3427a897c47d649816d 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -326,16 +326,18 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 
 /* Miscellaneous types that don't fit anywhere else (Can this be changed?) */
 
+typedef struct
+{
+	UINT8 red;
+	UINT8 green;
+	UINT8 blue;
+	UINT8 alpha;
+} byteColor_t;
+
 union FColorRGBA
 {
 	UINT32 rgba;
-	struct
-	{
-		UINT8 red;
-		UINT8 green;
-		UINT8 blue;
-		UINT8 alpha;
-	} s;
+	byteColor_t s;
 } ATTRPACK;
 typedef union FColorRGBA RGBA_t;
 
diff --git a/src/g_game.c b/src/g_game.c
index ceab3a465d4ff48f1de7714badf0634b47fde439..84feb576f0b3f24a2269f6c59a3a37f5748457f6 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -104,7 +104,7 @@ UINT32 demoIdleTime  = 3*TICRATE;
 boolean timingdemo; // if true, exit with report on completion
 boolean nodrawers; // for comparative timing purposes
 boolean noblit; // for comparative timing purposes
-static tic_t demostarttime; // for comparative timing purposes
+tic_t demostarttime; // for comparative timing purposes
 
 boolean netgame; // only true if packets are broadcast
 boolean multiplayer;
@@ -6064,7 +6064,46 @@ boolean G_CheckDemoStatus(void)
 		timingdemo = false;
 		f1 = (double)demotime;
 		f2 = (double)framecount*TICRATE;
-		CONS_Printf(M_GetText("timed %u gametics in %d realtics\n%f seconds, %f avg fps\n"), leveltime,demotime,f1/TICRATE,f2/f1);
+
+		CONS_Printf(M_GetText("timed %u gametics in %d realtics - %u frames\n%f seconds, %f avg fps\n"),
+			leveltime,demotime,(UINT32)framecount,f1/TICRATE,f2/f1);
+
+		// CSV-readable timedemo results, for external parsing
+		if (timedemo_csv)
+		{
+			FILE *f;
+			const char *csvpath = va("%s"PATHSEP"%s", srb2home, "timedemo.csv");
+			const char *header = "id,demoname,seconds,avgfps,leveltime,demotime,framecount,ticrate,rendermode,vidmode,vidwidth,vidheight,procbits\n";
+			const char *rowformat = "\"%s\",\"%s\",%f,%f,%u,%d,%u,%u,%u,%u,%u,%u,%u\n";
+			boolean headerrow = !FIL_FileExists(csvpath);
+			UINT8 procbits = 0;
+
+			// Bitness
+			if (sizeof(void*) == 4)
+				procbits = 32;
+			else if (sizeof(void*) == 8)
+				procbits = 64;
+
+			f = fopen(csvpath, "a+");
+
+			if (f)
+			{
+				if (headerrow)
+					fputs(header, f);
+				fprintf(f, rowformat,
+					timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
+				fclose(f);
+				CONS_Printf("Timedemo results saved to '%s'\n", csvpath);
+			}
+			else
+			{
+				// Just print the CSV output to console
+				CON_LogMessage(header);
+				CONS_Printf(rowformat,
+					timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
+			}
+		}
+
 		if (restorecv_vidwait != cv_vidwait.value)
 			CV_SetValue(&cv_vidwait, restorecv_vidwait);
 		D_AdvanceDemo();
diff --git a/src/g_game.h b/src/g_game.h
index 198cbc396ccb5d76b1596d3ef7d45b89ee755d98..595714432749a14d9c77af39f6e2981bc4a95f2b 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -37,6 +37,7 @@ extern boolean playeringame[MAXPLAYERS];
 
 // demoplaying back and demo recording
 extern boolean demoplayback, titledemo, demorecording, timingdemo;
+extern tic_t demostarttime;
 
 // Quit after playing a demo from cmdline.
 extern boolean singledemo;
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index f6b700f2859a29dfe058a9e823c5b683b8a19620..65f1002136742d79610deab135d92a7dace02721 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -505,43 +505,6 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 		if (blockheight < 1)
 			I_Error("3D GenerateTexture : too small");
 	}
-	else if (cv_voodoocompatibility.value)
-	{
-		if (originalwidth > 256 || originalheight > 256)
-		{
-			blockwidth = 256;
-			while (originalwidth < blockwidth)
-				blockwidth >>= 1;
-			if (blockwidth < 1)
-				I_Error("3D GenerateTexture : too small");
-
-			blockheight = 256;
-			while (originalheight < blockheight)
-				blockheight >>= 1;
-			if (blockheight < 1)
-				I_Error("3D GenerateTexture : too small");
-		}
-		else
-		{
-			//size up to nearest power of 2
-			blockwidth = 1;
-			while (blockwidth < originalwidth)
-				blockwidth <<= 1;
-			// scale down the original graphics to fit in 256
-			if (blockwidth > 256)
-				blockwidth = 256;
-				//I_Error("3D GenerateTexture : too big");
-
-			//size up to nearest power of 2
-			blockheight = 1;
-			while (blockheight < originalheight)
-				blockheight <<= 1;
-			// scale down the original graphics to fit in 256
-			if (blockheight > 256)
-				blockheight = 255;
-				//I_Error("3D GenerateTexture : too big");
-		}
-	}
 	else
 	{
 #ifdef GLIDE_API_COMPATIBILITY
@@ -770,18 +733,6 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 		newwidth = blockwidth;
 		newheight = blockheight;
 	}
-	else if (cv_voodoocompatibility.value) // Only scales down textures that exceed 256x256.
-	{
-		// no rounddown, do not size up patches, so they don't look 'scaled'
-		newwidth  = min(grPatch->width, blockwidth);
-		newheight = min(grPatch->height, blockheight);
-
-		if (newwidth > 256 || newheight > 256)
-		{
-			newwidth = blockwidth;
-			newheight = blockheight;
-		}
-	}
 	else
 	{
 		// no rounddown, do not size up patches, so they don't look 'scaled'
@@ -1258,18 +1209,6 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 			newwidth = blockwidth;
 			newheight = blockheight;
 		}
-		else if (cv_voodoocompatibility.value) // Only scales down textures that exceed 256x256.
-		{
-			// no rounddown, do not size up patches, so they don't look 'scaled'
-			newwidth  = min(SHORT(pic->width),blockwidth);
-			newheight = min(SHORT(pic->height),blockheight);
-
-			if (newwidth > 256 || newheight > 256)
-			{
-				newwidth = blockwidth;
-				newheight = blockheight;
-			}
-		}
 		else
 		{
 			// no rounddown, do not size up patches, so they don't look 'scaled'
diff --git a/src/hardware/hw_clip.c b/src/hardware/hw_clip.c
index 6d120efe72c4ca251b61dfa5c68be1b206719d31..4bdc753ec870e750c1f9fc3768d46defbc0a788a 100644
--- a/src/hardware/hw_clip.c
+++ b/src/hardware/hw_clip.c
@@ -77,8 +77,8 @@
 #include "r_opengl/r_opengl.h"
 
 #ifdef HAVE_SPHEREFRUSTRUM
-static GLdouble viewMatrix[16];
-static GLdouble projMatrix[16];
+static GLfloat viewMatrix[16];
+static GLfloat projMatrix[16];
 float frustum[6][4];
 #endif
 
@@ -380,8 +380,8 @@ void gld_FrustrumSetup(void)
 	float t;
 	float clip[16];
 
-	pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
-	pglGetDoublev(GL_MODELVIEW_MATRIX, viewMatrix);
+	pglGeFloatv(GL_PROJECTION_MATRIX, projMatrix);
+	pglGetFloatv(GL_MODELVIEW_MATRIX, viewMatrix);
 
 	clip[0]  = CALCMATRIX(0, 0, 1, 4, 2, 8, 3, 12);
 	clip[1]  = CALCMATRIX(0, 1, 1, 5, 2, 9, 3, 13);
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 5dcead77cb9c59ed4d192feadf64ecde878802ad..83d601b8745e979003ea7ac8832dafdb7527fae9 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -95,14 +95,29 @@ typedef struct
 
 //Hurdler: Transform (coords + angles)
 //BP: transform order : scale(rotation_x(rotation_y(translation(v))))
+
+// Kart features
+//#define USE_FTRANSFORM_ANGLEZ
+//#define USE_FTRANSFORM_MIRROR
+
+// Vanilla features
+#define USE_MODEL_NEXTFRAME
+
 typedef struct
 {
 	FLOAT       x,y,z;           // position
+#ifdef USE_FTRANSFORM_ANGLEZ
+	FLOAT       anglex,angley,anglez;   // aimingangle / viewangle
+#else
 	FLOAT       anglex,angley;   // aimingangle / viewangle
+#endif
 	FLOAT       scalex,scaley,scalez;
 	FLOAT       fovxangle, fovyangle;
-	INT32       splitscreen;
+	UINT8       splitscreen;
 	boolean     flip;            // screenflip
+#ifdef USE_FTRANSFORM_MIRROR
+	boolean     mirror;          // SRB2Kart: Encore Mode
+#endif
 } FTransform;
 
 // Transformed vector, as passed to HWR API
@@ -145,7 +160,7 @@ enum EPolyFlags
 	                                    // When set, pass the color constant into the FSurfaceInfo -> FlatColor
 	PF_NoTexture        = 0x00002000,   // Use the small white texture
 	PF_Corona           = 0x00004000,   // Tell the rendrer we are drawing a corona
-	PF_MD2              = 0x00008000,   // Tell the rendrer we are drawing an MD2
+	PF_Unused           = 0x00008000,   // Unused
 	PF_RemoveYWrap      = 0x00010000,   // Force clamp texture on Y
 	PF_ForceWrapX       = 0x00020000,   // Force repeat texture on X
 	PF_ForceWrapY       = 0x00040000,   // Force repeat texture on Y
@@ -203,8 +218,6 @@ enum hwdsetspecialstate
 	HWD_SET_FOG_COLOR,
 	HWD_SET_FOG_DENSITY,
 	HWD_SET_FOV,
-	HWD_SET_POLYGON_SMOOTH,
-	HWD_SET_PALETTECOLOR,
 	HWD_SET_TEXTUREFILTERMODE,
 	HWD_SET_TEXTUREANISOTROPICMODE,
 	HWD_NUMSTATE
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index e0507bc7083f9f54cebe7b3ab6952fa5a932ee03..aed1611f111176ba0ab9c907362e154421894825 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -59,20 +59,18 @@ EXPORT void HWRAPI(ClearMipMapCache) (void);
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value);
 
 //Hurdler: added for new development
-EXPORT void HWRAPI(DrawMD2) (INT32 *gl_cmd_buffer, md2_frame_t *frame, FTransform *pos, float scale);
-EXPORT void HWRAPI(DrawMD2i) (INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration, INT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color);
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 *color);
+EXPORT void HWRAPI(CreateModelVBOs) (model_t *model);
 EXPORT void HWRAPI(SetTransform) (FTransform *ptransform);
 EXPORT INT32 HWRAPI(GetTextureUsed) (void);
 EXPORT INT32 HWRAPI(GetRenderVersion) (void);
 
-#ifdef SHUFFLE
 #define SCREENVERTS 10
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
-#endif
 EXPORT void HWRAPI(FlushScreenTextures) (void);
 EXPORT void HWRAPI(StartScreenWipe) (void);
 EXPORT void HWRAPI(EndScreenWipe) (void);
-EXPORT void HWRAPI(DoScreenWipe) (float alpha);
+EXPORT void HWRAPI(DoScreenWipe) (void);
 EXPORT void HWRAPI(DrawIntermissionBG) (void);
 EXPORT void HWRAPI(MakeScreenTexture) (void);
 EXPORT void HWRAPI(MakeScreenFinalTexture) (void);
@@ -98,8 +96,8 @@ struct hwdriver_s
 	GClipRect           pfnGClipRect;
 	ClearMipMapCache    pfnClearMipMapCache;
 	SetSpecialState     pfnSetSpecialState;//Hurdler: added for backward compatibility
-	DrawMD2             pfnDrawMD2;
-	DrawMD2i            pfnDrawMD2i;
+	DrawModel           pfnDrawModel;
+	CreateModelVBOs     pfnCreateModelVBOs;
 	SetTransform        pfnSetTransform;
 	GetTextureUsed      pfnGetTextureUsed;
 	GetRenderVersion    pfnGetRenderVersion;
@@ -109,9 +107,7 @@ struct hwdriver_s
 #ifndef HAVE_SDL
 	Shutdown            pfnShutdown;
 #endif
-#ifdef SHUFFLE
 	PostImgRedraw       pfnPostImgRedraw;
-#endif
 	FlushScreenTextures pfnFlushScreenTextures;
 	StartScreenWipe     pfnStartScreenWipe;
 	EndScreenWipe       pfnEndScreenWipe;
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index a337fbfe05f64ae02cbc94cf82e1b5c8af128d09..79cc9e8e53bbc1209d55fe4088b1212bdbe148bb 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1972,7 +1972,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 	{
 		// Single sided line... Deal only with the middletexture (if one exists)
 		gr_midtexture = R_GetTextureNum(gr_sidedef->midtexture);
-		if (gr_midtexture)
+		if (gr_midtexture && gr_linedef->special != 41) // Ignore horizon line for OGL
 		{
 			{
 				fixed_t     texturevpeg;
@@ -5408,17 +5408,17 @@ static void HWR_DrawSprites(void)
 #endif
 				if (spr->mobj && spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
 				{
-					if (!cv_grmd2.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
+					if (!cv_grmodels.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
 						HWR_DrawSprite(spr);
 					else
-						HWR_DrawMD2(spr);
+						HWR_DrawModel(spr);
 				}
 				else
 				{
-					if (!cv_grmd2.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
+					if (!cv_grmodels.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
 						HWR_DrawSprite(spr);
 					else
-						HWR_DrawMD2(spr);
+						HWR_DrawModel(spr);
 				}
 		}
 	}
@@ -5546,7 +5546,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	tz = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
 
 	// thing is behind view plane?
-	if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
+	if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmodels.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
 		return;
 
 	// The above can stay as it works for cutting sprites that are too close
@@ -6298,6 +6298,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	// note: sets viewangle, viewx, viewy, viewz
 	R_SetupFrame(player);
+	framecount++; // timedemo
 
 	// copy view cam position for local use
 	dup_viewx = viewx;
@@ -6594,13 +6595,13 @@ void HWR_Startup(void)
 	// do this once
 	if (!startupdone)
 	{
-		CONS_Printf("HWR_Startup()\n");
+		CONS_Printf("HWR_Startup()...\n");
 		HWR_InitPolyPool();
 		// add console cmds & vars
 		HWR_AddEngineCommands();
 		HWR_InitTextureCache();
 
-		HWR_InitMD2();
+		HWR_InitModels();
 
 #ifdef ALAM_LIGHTING
 		HWR_InitLight();
@@ -6866,11 +6867,6 @@ static void HWR_RenderWall(wallVert3D   *wallVerts, FSurfaceInfo *pSurf, FBITFIE
 #endif
 }
 
-void HWR_SetPaletteColor(INT32 palcolor)
-{
-	HWD.pfnSetSpecialState(HWD_SET_PALETTECOLOR, palcolor);
-}
-
 INT32 HWR_GetTextureUsed(void)
 {
 	return HWD.pfnGetTextureUsed();
@@ -6917,7 +6913,6 @@ void HWR_DoPostProcessor(player_t *player)
 	if (splitscreen) // Not supported in splitscreen - someone want to add support?
 		return;
 
-#ifdef SHUFFLE
 	// Drunken vision! WooOOooo~
 	if (*type == postimg_water || *type == postimg_heat)
 	{
@@ -6960,7 +6955,6 @@ void HWR_DoPostProcessor(player_t *player)
 			HWD.pfnMakeScreenTexture();
 	}
 	// Flipping of the screen isn't done here anymore
-#endif // SHUFFLE
 }
 
 void HWR_StartScreenWipe(void)
@@ -7007,7 +7001,7 @@ void HWR_DoWipe(UINT8 wipenum, UINT8 scrnnum)
 
 	HWR_GetFadeMask(lumpnum);
 
-	HWD.pfnDoScreenWipe(HWRWipeCounter); // Still send in wipecounter since old stuff might not support multitexturing
+	HWD.pfnDoScreenWipe();
 
 	HWRWipeCounter += 0.05f; // increase opacity of end screen
 
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 31e97cc130fb9ce740194a704a8525905bdc0369..3a0a58427fbb1d2d8bed6173cb04430b32c45541 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -60,7 +60,6 @@ void HWR_AddCommands(void);
 void HWR_CorrectSWTricks(void);
 void transform(float *cx, float *cy, float *cz);
 FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf);
-void HWR_SetPaletteColor(INT32 palcolor);
 INT32 HWR_GetTextureUsed(void);
 void HWR_DoPostProcessor(player_t *player);
 void HWR_StartScreenWipe(void);
@@ -83,7 +82,8 @@ extern consvar_t cv_grcoronas;
 extern consvar_t cv_grcoronasize;
 #endif
 extern consvar_t cv_grfov;
-extern consvar_t cv_grmd2;
+extern consvar_t cv_grmodels;
+extern consvar_t cv_grmodelinterpolation;
 extern consvar_t cv_grfog;
 extern consvar_t cv_grfogcolor;
 extern consvar_t cv_grfogdensity;
@@ -94,7 +94,6 @@ extern consvar_t cv_grgammablue;
 extern consvar_t cv_grfiltermode;
 extern consvar_t cv_granisotropicmode;
 extern consvar_t cv_grcorrecttricks;
-extern consvar_t cv_voodoocompatibility;
 extern consvar_t cv_grfovchange;
 extern consvar_t cv_grsolvetjoin;
 extern consvar_t cv_grspritebillboarding;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 9401d66f1f832d32eb4af951de0eaf09baa9dd3d..e02d2ce4673080eea9b8d92690c2722b1d8b9f9c 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -35,6 +35,7 @@
 #include "hw_drv.h"
 #include "hw_light.h"
 #include "hw_md2.h"
+#include "../d_main.h"
 #include "../r_bsp.h"
 #include "../r_main.h"
 #include "../m_misc.h"
@@ -43,6 +44,7 @@
 #include "../r_things.h"
 #include "../r_draw.h"
 #include "../p_tick.h"
+#include "hw_model.h"
 
 #include "hw_main.h"
 #include "../v_video.h"
@@ -75,172 +77,6 @@
 #include "errno.h"
 #endif
 
-#define NUMVERTEXNORMALS 162
-float avertexnormals[NUMVERTEXNORMALS][3] = {
-{-0.525731f, 0.000000f, 0.850651f},
-{-0.442863f, 0.238856f, 0.864188f},
-{-0.295242f, 0.000000f, 0.955423f},
-{-0.309017f, 0.500000f, 0.809017f},
-{-0.162460f, 0.262866f, 0.951056f},
-{0.000000f, 0.000000f, 1.000000f},
-{0.000000f, 0.850651f, 0.525731f},
-{-0.147621f, 0.716567f, 0.681718f},
-{0.147621f, 0.716567f, 0.681718f},
-{0.000000f, 0.525731f, 0.850651f},
-{0.309017f, 0.500000f, 0.809017f},
-{0.525731f, 0.000000f, 0.850651f},
-{0.295242f, 0.000000f, 0.955423f},
-{0.442863f, 0.238856f, 0.864188f},
-{0.162460f, 0.262866f, 0.951056f},
-{-0.681718f, 0.147621f, 0.716567f},
-{-0.809017f, 0.309017f, 0.500000f},
-{-0.587785f, 0.425325f, 0.688191f},
-{-0.850651f, 0.525731f, 0.000000f},
-{-0.864188f, 0.442863f, 0.238856f},
-{-0.716567f, 0.681718f, 0.147621f},
-{-0.688191f, 0.587785f, 0.425325f},
-{-0.500000f, 0.809017f, 0.309017f},
-{-0.238856f, 0.864188f, 0.442863f},
-{-0.425325f, 0.688191f, 0.587785f},
-{-0.716567f, 0.681718f, -0.147621f},
-{-0.500000f, 0.809017f, -0.309017f},
-{-0.525731f, 0.850651f, 0.000000f},
-{0.000000f, 0.850651f, -0.525731f},
-{-0.238856f, 0.864188f, -0.442863f},
-{0.000000f, 0.955423f, -0.295242f},
-{-0.262866f, 0.951056f, -0.162460f},
-{0.000000f, 1.000000f, 0.000000f},
-{0.000000f, 0.955423f, 0.295242f},
-{-0.262866f, 0.951056f, 0.162460f},
-{0.238856f, 0.864188f, 0.442863f},
-{0.262866f, 0.951056f, 0.162460f},
-{0.500000f, 0.809017f, 0.309017f},
-{0.238856f, 0.864188f, -0.442863f},
-{0.262866f, 0.951056f, -0.162460f},
-{0.500000f, 0.809017f, -0.309017f},
-{0.850651f, 0.525731f, 0.000000f},
-{0.716567f, 0.681718f, 0.147621f},
-{0.716567f, 0.681718f, -0.147621f},
-{0.525731f, 0.850651f, 0.000000f},
-{0.425325f, 0.688191f, 0.587785f},
-{0.864188f, 0.442863f, 0.238856f},
-{0.688191f, 0.587785f, 0.425325f},
-{0.809017f, 0.309017f, 0.500000f},
-{0.681718f, 0.147621f, 0.716567f},
-{0.587785f, 0.425325f, 0.688191f},
-{0.955423f, 0.295242f, 0.000000f},
-{1.000000f, 0.000000f, 0.000000f},
-{0.951056f, 0.162460f, 0.262866f},
-{0.850651f, -0.525731f, 0.000000f},
-{0.955423f, -0.295242f, 0.000000f},
-{0.864188f, -0.442863f, 0.238856f},
-{0.951056f, -0.162460f, 0.262866f},
-{0.809017f, -0.309017f, 0.500000f},
-{0.681718f, -0.147621f, 0.716567f},
-{0.850651f, 0.000000f, 0.525731f},
-{0.864188f, 0.442863f, -0.238856f},
-{0.809017f, 0.309017f, -0.500000f},
-{0.951056f, 0.162460f, -0.262866f},
-{0.525731f, 0.000000f, -0.850651f},
-{0.681718f, 0.147621f, -0.716567f},
-{0.681718f, -0.147621f, -0.716567f},
-{0.850651f, 0.000000f, -0.525731f},
-{0.809017f, -0.309017f, -0.500000f},
-{0.864188f, -0.442863f, -0.238856f},
-{0.951056f, -0.162460f, -0.262866f},
-{0.147621f, 0.716567f, -0.681718f},
-{0.309017f, 0.500000f, -0.809017f},
-{0.425325f, 0.688191f, -0.587785f},
-{0.442863f, 0.238856f, -0.864188f},
-{0.587785f, 0.425325f, -0.688191f},
-{0.688191f, 0.587785f, -0.425325f},
-{-0.147621f, 0.716567f, -0.681718f},
-{-0.309017f, 0.500000f, -0.809017f},
-{0.000000f, 0.525731f, -0.850651f},
-{-0.525731f, 0.000000f, -0.850651f},
-{-0.442863f, 0.238856f, -0.864188f},
-{-0.295242f, 0.000000f, -0.955423f},
-{-0.162460f, 0.262866f, -0.951056f},
-{0.000000f, 0.000000f, -1.000000f},
-{0.295242f, 0.000000f, -0.955423f},
-{0.162460f, 0.262866f, -0.951056f},
-{-0.442863f, -0.238856f, -0.864188f},
-{-0.309017f, -0.500000f, -0.809017f},
-{-0.162460f, -0.262866f, -0.951056f},
-{0.000000f, -0.850651f, -0.525731f},
-{-0.147621f, -0.716567f, -0.681718f},
-{0.147621f, -0.716567f, -0.681718f},
-{0.000000f, -0.525731f, -0.850651f},
-{0.309017f, -0.500000f, -0.809017f},
-{0.442863f, -0.238856f, -0.864188f},
-{0.162460f, -0.262866f, -0.951056f},
-{0.238856f, -0.864188f, -0.442863f},
-{0.500000f, -0.809017f, -0.309017f},
-{0.425325f, -0.688191f, -0.587785f},
-{0.716567f, -0.681718f, -0.147621f},
-{0.688191f, -0.587785f, -0.425325f},
-{0.587785f, -0.425325f, -0.688191f},
-{0.000000f, -0.955423f, -0.295242f},
-{0.000000f, -1.000000f, 0.000000f},
-{0.262866f, -0.951056f, -0.162460f},
-{0.000000f, -0.850651f, 0.525731f},
-{0.000000f, -0.955423f, 0.295242f},
-{0.238856f, -0.864188f, 0.442863f},
-{0.262866f, -0.951056f, 0.162460f},
-{0.500000f, -0.809017f, 0.309017f},
-{0.716567f, -0.681718f, 0.147621f},
-{0.525731f, -0.850651f, 0.000000f},
-{-0.238856f, -0.864188f, -0.442863f},
-{-0.500000f, -0.809017f, -0.309017f},
-{-0.262866f, -0.951056f, -0.162460f},
-{-0.850651f, -0.525731f, 0.000000f},
-{-0.716567f, -0.681718f, -0.147621f},
-{-0.716567f, -0.681718f, 0.147621f},
-{-0.525731f, -0.850651f, 0.000000f},
-{-0.500000f, -0.809017f, 0.309017f},
-{-0.238856f, -0.864188f, 0.442863f},
-{-0.262866f, -0.951056f, 0.162460f},
-{-0.864188f, -0.442863f, 0.238856f},
-{-0.809017f, -0.309017f, 0.500000f},
-{-0.688191f, -0.587785f, 0.425325f},
-{-0.681718f, -0.147621f, 0.716567f},
-{-0.442863f, -0.238856f, 0.864188f},
-{-0.587785f, -0.425325f, 0.688191f},
-{-0.309017f, -0.500000f, 0.809017f},
-{-0.147621f, -0.716567f, 0.681718f},
-{-0.425325f, -0.688191f, 0.587785f},
-{-0.162460f, -0.262866f, 0.951056f},
-{0.442863f, -0.238856f, 0.864188f},
-{0.162460f, -0.262866f, 0.951056f},
-{0.309017f, -0.500000f, 0.809017f},
-{0.147621f, -0.716567f, 0.681718f},
-{0.000000f, -0.525731f, 0.850651f},
-{0.425325f, -0.688191f, 0.587785f},
-{0.587785f, -0.425325f, 0.688191f},
-{0.688191f, -0.587785f, 0.425325f},
-{-0.955423f, 0.295242f, 0.000000f},
-{-0.951056f, 0.162460f, 0.262866f},
-{-1.000000f, 0.000000f, 0.000000f},
-{-0.850651f, 0.000000f, 0.525731f},
-{-0.955423f, -0.295242f, 0.000000f},
-{-0.951056f, -0.162460f, 0.262866f},
-{-0.864188f, 0.442863f, -0.238856f},
-{-0.951056f, 0.162460f, -0.262866f},
-{-0.809017f, 0.309017f, -0.500000f},
-{-0.864188f, -0.442863f, -0.238856f},
-{-0.951056f, -0.162460f, -0.262866f},
-{-0.809017f, -0.309017f, -0.500000f},
-{-0.681718f, 0.147621f, -0.716567f},
-{-0.681718f, -0.147621f, -0.716567f},
-{-0.850651f, 0.000000f, -0.525731f},
-{-0.688191f, 0.587785f, -0.425325f},
-{-0.587785f, 0.425325f, -0.688191f},
-{-0.425325f, 0.688191f, -0.587785f},
-{-0.425325f, -0.688191f, -0.587785f},
-{-0.587785f, -0.425325f, -0.688191f},
-{-0.688191f, -0.587785f, -0.425325f},
-};
-
 md2_t md2_models[NUMSPRITES];
 md2_t md2_playermodels[MAXSKINS];
 
@@ -248,230 +84,25 @@ md2_t md2_playermodels[MAXSKINS];
 /*
  * free model
  */
-static void md2_freeModel (md2_model_t *model)
+#if 0
+static void md2_freeModel (model_t *model)
 {
-	if (model)
-	{
-		if (model->skins)
-			free(model->skins);
-
-		if (model->texCoords)
-			free(model->texCoords);
-
-		if (model->triangles)
-			free(model->triangles);
-
-		if (model->frames)
-		{
-			size_t i;
-
-			for (i = 0; i < model->header.numFrames; i++)
-			{
-				if (model->frames[i].vertices)
-					free(model->frames[i].vertices);
-			}
-			free(model->frames);
-		}
-
-		if (model->spr2frames)
-			free(model->spr2frames);
-
-		if (model->glCommandBuffer)
-			free(model->glCommandBuffer);
-
-		free(model);
-	}
+	UnloadModel(model);
 }
+#endif
 
 
 //
 // load model
 //
 // Hurdler: the current path is the Legacy.exe path
-static md2_model_t *md2_readModel(const char *filename)
+static model_t *md2_readModel(const char *filename)
 {
-	FILE *file;
-	md2_model_t *model;
-	UINT8 buffer[MD2_MAX_FRAMESIZE];
-	size_t i;
-
-	model = calloc(1, sizeof (*model));
-	if (model == NULL)
-		return 0;
-
 	//Filename checking fixed ~Monster Iestyn and Golden
-	file = fopen(va("%s"PATHSEP"%s", srb2home, filename), "rb");
-	if (!file)
-	{
-		free(model);
-		return 0;
-	}
-
-	// initialize model and read header
-
-	if (fread(&model->header, sizeof (model->header), 1, file) != 1
-		|| model->header.magic != MD2_IDENT
-		|| model->header.version != MD2_VERSION)
-	{
-		fclose(file);
-		free(model);
-		return 0;
-	}
-
-	model->header.numSkins = 1;
-
-#define MD2LIMITCHECK(field, max, msgname) \
-	if (field > max) \
-	{ \
-		CONS_Alert(CONS_ERROR, "md2_readModel: %s has too many " msgname " (# found: %d, maximum: %d)\n", filename, field, max); \
-		md2_freeModel (model); \
-		fclose(file); \
-		return 0; \
-	}
-
-	// Uncomment if these are actually needed
-//	MD2LIMITCHECK(model->header.numSkins,     MD2_MAX_SKINS,     "skins")
-//	MD2LIMITCHECK(model->header.numTexCoords, MD2_MAX_TEXCOORDS, "texture coordinates")
-	MD2LIMITCHECK(model->header.numTriangles, MD2_MAX_TRIANGLES, "triangles")
-	MD2LIMITCHECK(model->header.numFrames,    MD2_MAX_FRAMES,    "frames")
-	MD2LIMITCHECK(model->header.numVertices,  MD2_MAX_VERTICES,  "vertices")
-
-#undef MD2LIMITCHECK
-
-	// read skins
-	fseek(file, model->header.offsetSkins, SEEK_SET);
-	if (model->header.numSkins > 0)
-	{
-		model->skins = calloc(sizeof (md2_skin_t), model->header.numSkins);
-		if (!model->skins || model->header.numSkins !=
-			fread(model->skins, sizeof (md2_skin_t), model->header.numSkins, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	// read texture coordinates
-	fseek(file, model->header.offsetTexCoords, SEEK_SET);
-	if (model->header.numTexCoords > 0)
-	{
-		model->texCoords = calloc(sizeof (md2_textureCoordinate_t), model->header.numTexCoords);
-		if (!model->texCoords || model->header.numTexCoords !=
-			fread(model->texCoords, sizeof (md2_textureCoordinate_t), model->header.numTexCoords, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	// read triangles
-	fseek(file, model->header.offsetTriangles, SEEK_SET);
-	if (model->header.numTriangles > 0)
-	{
-		model->triangles = calloc(sizeof (md2_triangle_t), model->header.numTriangles);
-		if (!model->triangles || model->header.numTriangles !=
-			fread(model->triangles, sizeof (md2_triangle_t), model->header.numTriangles, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	// read alias frames
-	fseek(file, model->header.offsetFrames, SEEK_SET);
-	if (model->header.numFrames > 0)
-	{
-		model->frames = calloc(sizeof (md2_frame_t), model->header.numFrames);
-		if (!model->frames)
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-
-		for (i = 0; i < model->header.numFrames; i++)
-		{
-			md2_alias_frame_t *frame = (md2_alias_frame_t *)(void *)buffer;
-			size_t j;
-
-			model->frames[i].vertices = calloc(sizeof (md2_triangleVertex_t), model->header.numVertices);
-			if (!model->frames[i].vertices || model->header.frameSize !=
-				fread(frame, 1, model->header.frameSize, file))
-			{
-				md2_freeModel (model);
-				fclose(file);
-				return 0;
-			}
-
-			strcpy(model->frames[i].name, frame->name);
-			if (frame->name[0] == 'S')
-			{
-				boolean super;
-				if ((super = (fastncmp("UPER", frame->name+1, 4))) // SUPER
-					|| fastncmp("PR2_", frame->name+1, 4)) // SPR2_
-				{
-					UINT8 spr2;
-					for (spr2 = 0; spr2 < free_spr2; spr2++)
-						if (fastncmp(frame->name+5,spr2names[spr2],3)
-						&& ((frame->name[8] == spr2names[spr2][3])
-							|| (frame->name[8] == '.' && spr2names[spr2][3] == '_')))
-							break;
-
-					if (spr2 < free_spr2)
-					{
-						if (!model->spr2frames)
-						{
-							model->spr2frames = calloc(sizeof (size_t), 2*NUMPLAYERSPRITES*2);
-							if (!model->spr2frames)
-							{
-								md2_freeModel (model);
-								fclose(file);
-								return 0;
-							}
-						}
-						if (super)
-							spr2 |= FF_SPR2SUPER;
-						if (model->spr2frames[spr2*2 + 1]++ == 0) // numspr2frames
-							model->spr2frames[spr2*2] = i; // starting frame
-						CONS_Debug(DBG_RENDER, "frame %s, sprite2 %s - starting frame %s, number of frames %s\n", frame->name, spr2names[spr2 & ~FF_SPR2SUPER], sizeu1(model->spr2frames[spr2*2]), sizeu2(model->spr2frames[spr2*2 + 1]));
-					}
-				}
-			}
-			for (j = 0; j < model->header.numVertices; j++)
-			{
-				model->frames[i].vertices[j].vertex[0] = (float) ((INT32) frame->alias_vertices[j].vertex[0]) * frame->scale[0] + frame->translate[0];
-				model->frames[i].vertices[j].vertex[2] = -1* ((float) ((INT32) frame->alias_vertices[j].vertex[1]) * frame->scale[1] + frame->translate[1]);
-				model->frames[i].vertices[j].vertex[1] = (float) ((INT32) frame->alias_vertices[j].vertex[2]) * frame->scale[2] + frame->translate[2];
-				model->frames[i].vertices[j].normal[0] = avertexnormals[frame->alias_vertices[j].lightNormalIndex][0];
-				model->frames[i].vertices[j].normal[1] = avertexnormals[frame->alias_vertices[j].lightNormalIndex][1];
-				model->frames[i].vertices[j].normal[2] = avertexnormals[frame->alias_vertices[j].lightNormalIndex][2];
-			}
-		}
-	}
-
-	// read gl commands
-	fseek(file, model->header.offsetGlCommands, SEEK_SET);
-	if (model->header.numGlCommands)
-	{
-		model->glCommandBuffer = calloc(sizeof (INT32), model->header.numGlCommands);
-		if (!model->glCommandBuffer || model->header.numGlCommands !=
-			fread(model->glCommandBuffer, sizeof (INT32), model->header.numGlCommands, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	fclose(file);
-
-	return model;
+	return LoadModel(va("%s"PATHSEP"%s", srb2home, filename), PU_STATIC);
 }
 
-static inline void md2_printModelInfo (md2_model_t *model)
+static inline void md2_printModelInfo (model_t *model)
 {
 #if 0
 	INT32 i;
@@ -530,7 +161,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 #endif
 	png_FILE_p png_FILE;
 	//Filename checking fixed ~Monster Iestyn and Golden
-	char *pngfilename = va("%s"PATHSEP"md2"PATHSEP"%s", srb2home, filename);
+	char *pngfilename = va("%s"PATHSEP"models"PATHSEP"%s", srb2home, filename);
 
 	FIL_ForceExtension(pngfilename, ".png");
 	png_FILE = fopen(pngfilename, "rb");
@@ -659,7 +290,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 	INT32 ch, rep;
 	FILE *file;
 	//Filename checking fixed ~Monster Iestyn and Golden
-	char *pcxfilename = va("%s"PATHSEP"md2"PATHSEP"%s", srb2home, filename);
+	char *pcxfilename = va("%s"PATHSEP"models"PATHSEP"%s", srb2home, filename);
 
 	FIL_ForceExtension(pcxfilename, ".pcx");
 	file = fopen(pcxfilename, "rb");
@@ -720,7 +351,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 }
 
 // -----------------+
-// md2_loadTexture  : Download a pcx or png texture for MD2 models
+// md2_loadTexture  : Download a pcx or png texture for models
 // -----------------+
 static void md2_loadTexture(md2_t *model)
 {
@@ -830,7 +461,7 @@ static void md2_loadBlendTexture(md2_t *model)
 // Don't spam the console, or the OS with fopen requests!
 static boolean nomd2s = false;
 
-void HWR_InitMD2(void)
+void HWR_InitModels(void)
 {
 	size_t i;
 	INT32 s;
@@ -838,7 +469,7 @@ void HWR_InitMD2(void)
 	char name[18], filename[32];
 	float scale, offset;
 
-	CONS_Printf("InitMD2()...\n");
+	CONS_Printf("HWR_InitModels()...\n");
 	for (s = 0; s < MAXSKINS; s++)
 	{
 		md2_playermodels[s].scale = -1.0f;
@@ -858,13 +489,13 @@ void HWR_InitMD2(void)
 		md2_models[i].error = false;
 	}
 
-	// read the md2.dat file
+	// read the models.dat file
 	//Filename checking fixed ~Monster Iestyn and Golden
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt");
 
 	if (!f)
 	{
-		CONS_Printf("%s %s\n", M_GetText("Error while loading md2.dat:"), strerror(errno));
+		CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno));
 		nomd2s = true;
 		return;
 	}
@@ -872,7 +503,7 @@ void HWR_InitMD2(void)
 	{
 		if (stricmp(name, "PLAY") == 0)
 		{
-			CONS_Printf("MD2 for sprite PLAY detected in md2.dat, use a player skin instead!\n");
+			CONS_Printf("Model for sprite PLAY detected in models.dat, use a player skin instead!\n");
 			continue;
 		}
 
@@ -906,7 +537,7 @@ void HWR_InitMD2(void)
 			}
 		}
 		// no sprite/player skin name found?!?
-		CONS_Printf("Unknown sprite/player skin %s detected in md2.dat\n", name);
+		//CONS_Printf("Unknown sprite/player skin %s detected in models.dat\n", name);
 md2found:
 		// move on to next line...
 		continue;
@@ -914,7 +545,7 @@ md2found:
 	fclose(f);
 }
 
-void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
+void HWR_AddPlayerModel(int skin) // For skins that were added after startup
 {
 	FILE *f;
 	char name[18], filename[32];
@@ -923,20 +554,20 @@ void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 	if (nomd2s)
 		return;
 
-	CONS_Printf("AddPlayerMD2()...\n");
+	//CONS_Printf("HWR_AddPlayerModel()...\n");
 
-	// read the md2.dat file
+	// read the models.dat file
 	//Filename checking fixed ~Monster Iestyn and Golden
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt");
 
 	if (!f)
 	{
-		CONS_Printf("Error while loading md2.dat\n");
+		CONS_Printf("Error while loading models.dat\n");
 		nomd2s = true;
 		return;
 	}
 
-	// Check for any MD2s that match the names of player skins!
+	// Check for any model that match the names of player skins!
 	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
 	{
 		if (stricmp(name, skins[skin].name) == 0)
@@ -950,17 +581,16 @@ void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 		}
 	}
 
-	//CONS_Printf("MD2 for player skin %s not found\n", skins[skin].name);
+	//CONS_Printf("Model for player skin %s not found\n", skins[skin].name);
 	md2_playermodels[skin].notfound = true;
 playermd2found:
 	fclose(f);
 }
 
-
-void HWR_AddSpriteMD2(size_t spritenum) // For MD2s that were added after startup
+void HWR_AddSpriteModel(size_t spritenum) // For sprites that were added after startup
 {
 	FILE *f;
-	// name[18] is used to check for names in the md2.dat file that match with sprites or player skins
+	// name[18] is used to check for names in the models.dat file that match with sprites or player skins
 	// sprite names are always 4 characters long, and names is for player skins can be up to 19 characters long
 	char name[18], filename[32];
 	float scale, offset;
@@ -971,18 +601,18 @@ void HWR_AddSpriteMD2(size_t spritenum) // For MD2s that were added after startu
 	if (spritenum == SPR_PLAY) // Handled already NEWMD2: Per sprite, per-skin check
 		return;
 
-	// Read the md2.dat file
+	// Read the models.dat file
 	//Filename checking fixed ~Monster Iestyn and Golden
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt");
 
 	if (!f)
 	{
-		CONS_Printf("Error while loading md2.dat\n");
+		CONS_Printf("Error while loading models.dat\n");
 		nomd2s = true;
 		return;
 	}
 
-	// Check for any MD2s that match the names of player skins!
+	// Check for any MD2s that match the names of sprite names!
 	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
 	{
 		if (stricmp(name, sprnames[spritenum]) == 0)
@@ -1006,7 +636,6 @@ spritemd2found:
 // 0.2126 to red
 // 0.7152 to green
 // 0.0722 to blue
-// (See this same define in k_kart.c!)
 #define SETBRIGHTNESS(brightness,r,g,b) \
 	brightness = (UINT8)(((1063*((UINT16)r)/5000) + (3576*((UINT16)g)/5000) + (361*((UINT16)b)/5000)) / 3)
 
@@ -1190,39 +819,40 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT
 	Z_ChangeTag(newmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
+#define NORMALFOG 0x00000000
+#define FADEFOG 0x19000000
 
+static boolean HWR_CanInterpolateModel(mobj_t *mobj, model_t *model)
+{
+	if (cv_grmodelinterpolation.value == 2) // Always interpolate
+		return true;
+	return model->interpolate[(mobj->frame & FF_FRAMEMASK)];
+}
 
-// -----------------+
-// HWR_DrawMD2      : Draw MD2
-//                  : (monsters, bonuses, weapons, lights, ...)
-// Returns          :
-// -----------------+
-	/*
-	wait/stand
-	death
-	pain
-	walk
-	shoot/fire
-
-	die?
-	atka?
-	atkb?
-	attacka/b/c/d?
-	res?
-	run?
-	*/
-
-static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *player)
+static boolean HWR_CanInterpolateSprite2(modelspr2frames_t *spr2frame)
+{
+	if (cv_grmodelinterpolation.value == 2) // Always interpolate
+		return true;
+	return spr2frame->interpolate;
+}
+
+//
+// HWR_GetModelSprite2 (see P_GetSkinSprite2)
+// For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing.
+// For super players, does the same as above - but tries the super equivalent for each sprite2 before the non-super version.
+//
+
+static UINT8 HWR_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *player)
 {
 	UINT8 super = 0, i = 0;
 
-	if (!md2 || !skin)
+	if (!md2 || !md2->model || !md2->model->spr2frames || !skin)
 		return 0;
 
 	if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
 		return 0;
 
-	while (!(md2->model->spr2frames[spr2*2 + 1])
+	while (!md2->model->spr2frames[spr2].numframes
 		&& spr2 != SPR2_STND
 		&& ++i != 32) // recursion limiter
 	{
@@ -1263,19 +893,23 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 	return spr2;
 }
 
-#define NORMALFOG 0x00000000
-#define FADEFOG 0x19000000
-void HWR_DrawMD2(gr_vissprite_t *spr)
+//
+// HWR_DrawModel
+//
+
+void HWR_DrawModel(gr_vissprite_t *spr)
 {
 	FSurfaceInfo Surf;
 
 	char filename[64];
-	INT32 frame;
+	INT32 frame = 0;
+	INT32 nextFrame = -1;
+	UINT8 spr2 = 0;
 	FTransform p;
 	md2_t *md2;
 	UINT8 color[4];
 
-	if (!cv_grmd2.value)
+	if (!cv_grmodels.value)
 		return;
 
 	if (spr->precip)
@@ -1321,13 +955,13 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 	// Look at HWR_ProjectSprite for more
 	{
 		GLPatch_t *gpatch;
-		INT32 *buff;
 		INT32 durs = spr->mobj->state->tics;
 		INT32 tics = spr->mobj->tics;
-		md2_frame_t *curr, *next = NULL;
+		//mdlframe_t *next = NULL;
 		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
+		INT32 mod;
 		float finalscale;
 
 		// Apparently people don't like jump frames like that, so back it goes
@@ -1358,13 +992,14 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			return; // we already failed loading this before :(
 		if (!md2->model)
 		{
-			//CONS_Debug(DBG_RENDER, "Loading MD2... (%s)", sprnames[spr->mobj->sprite]);
-			sprintf(filename, "md2/%s", md2->filename);
+			//CONS_Debug(DBG_RENDER, "Loading model... (%s)", sprnames[spr->mobj->sprite]);
+			sprintf(filename, "models/%s", md2->filename);
 			md2->model = md2_readModel(filename);
 
 			if (md2->model)
 			{
 				md2_printModelInfo(md2->model);
+				HWD.pfnCreateModelVBOs(md2->model);
 			}
 			else
 			{
@@ -1441,70 +1076,69 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			tics = spr->mobj->anim_duration;
 		}
 
-#define INTERPOLERATION_LIMIT TICRATE/4
-
+		frame = (spr->mobj->frame & FF_FRAMEMASK);
 		if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY && md2->model->spr2frames)
 		{
-			UINT8 spr2 = P_GetModelSprite2(md2, spr->mobj->skin, spr->mobj->sprite2, spr->mobj->player);
-			UINT8 mod = md2->model->spr2frames[spr2*2 + 1] ? md2->model->spr2frames[spr2*2 + 1] : md2->model->header.numFrames;
-			if (mod > ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes)
+			spr2 = HWR_GetModelSprite2(md2, spr->mobj->skin, spr->mobj->sprite2, spr->mobj->player);
+			mod = md2->model->spr2frames[spr2].numframes;
+#ifndef DONTHIDEDIFFANIMLENGTH // by default, different anim length is masked by the mod
+			if (mod > (INT32)((skin_t *)spr->mobj->skin)->sprites[spr2].numframes)
 				mod = ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes;
-			//FIXME: this is not yet correct
-			frame = (spr->mobj->frame & FF_FRAMEMASK);
-			if (frame >= mod)
-				frame = 0;
-			buff = md2->model->glCommandBuffer;
-			curr = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
-			if (cv_grmd2.value == 1 && tics <= durs && tics <= INTERPOLERATION_LIMIT)
-			{
-				if (durs > INTERPOLERATION_LIMIT)
-					durs = INTERPOLERATION_LIMIT;
+#endif
+			if (!mod)
+				mod = 1;
+			frame = md2->model->spr2frames[spr2].frames[frame%mod];
+		}
+		else
+		{
+			mod = md2->model->meshes[0].numFrames;
+			if (!mod)
+				mod = 1;
+		}
+
+#ifdef USE_MODEL_NEXTFRAME
+#define INTERPOLERATION_LIMIT TICRATE/4
+		if (cv_grmodelinterpolation.value && tics <= durs && tics <= INTERPOLERATION_LIMIT)
+		{
+			if (durs > INTERPOLERATION_LIMIT)
+				durs = INTERPOLERATION_LIMIT;
 
-				if (spr->mobj->frame & FF_ANIMATE
+			if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY && md2->model->spr2frames)
+			{
+				if (HWR_CanInterpolateSprite2(&md2->model->spr2frames[spr2])
+					&& (spr->mobj->frame & FF_ANIMATE
 					|| (spr->mobj->state->nextstate != S_NULL
-					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite
-					&& (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) == spr->mobj->sprite2))
+					&& states[spr->mobj->state->nextstate].sprite == SPR_PLAY
+					&& ((P_GetSkinSprite2(spr->mobj->skin, (((spr->mobj->player && spr->mobj->player->powers[pw_super]) ? FF_SPR2SUPER : 0)|states[spr->mobj->state->nextstate].frame) & FF_FRAMEMASK, spr->mobj->player) == spr->mobj->sprite2)))))
 				{
-					if (++frame >= mod)
-						frame = 0;
+					nextFrame = (spr->mobj->frame & FF_FRAMEMASK) + 1;
+					if (nextFrame >= mod)
+						nextFrame = 0;
 					if (frame || !(spr->mobj->state->frame & FF_SPR2ENDSTATE))
-						next = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
+						nextFrame = md2->model->spr2frames[spr2].frames[nextFrame];
+					else
+						nextFrame = -1;
 				}
 			}
-		}
-		else
-		{
-			//FIXME: this is not yet correct
-			frame = (spr->mobj->frame & FF_FRAMEMASK) % md2->model->header.numFrames;
-			buff = md2->model->glCommandBuffer;
-			curr = &md2->model->frames[frame];
-			if (cv_grmd2.value == 1 && tics <= durs && tics <= INTERPOLERATION_LIMIT)
+			else if (HWR_CanInterpolateModel(spr->mobj, md2->model))
 			{
-				if (durs > INTERPOLERATION_LIMIT)
-					durs = INTERPOLERATION_LIMIT;
-
 				// frames are handled differently for states with FF_ANIMATE, so get the next frame differently for the interpolation
 				if (spr->mobj->frame & FF_ANIMATE)
 				{
-					UINT32 nextframe = (spr->mobj->frame & FF_FRAMEMASK) + 1;
-					if (nextframe >= (UINT32)spr->mobj->state->var1)
-						nextframe = (spr->mobj->state->frame & FF_FRAMEMASK);
-					nextframe %= md2->model->header.numFrames;
-					next = &md2->model->frames[nextframe];
+					nextFrame = (spr->mobj->frame & FF_FRAMEMASK) + 1;
+					if (nextFrame >= (INT32)(spr->mobj->state->var1 + (spr->mobj->state->frame & FF_FRAMEMASK)))
+						nextFrame = (spr->mobj->state->frame & FF_FRAMEMASK) % mod;
 				}
 				else
 				{
-					if (spr->mobj->state->nextstate != S_NULL
-					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite)
-					{
-						const UINT32 nextframe = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->header.numFrames;
-						next = &md2->model->frames[nextframe];
-					}
+					if (spr->mobj->state->nextstate != S_NULL && states[spr->mobj->state->nextstate].sprite != SPR_NULL
+					&& !(spr->mobj->player && (spr->mobj->state->nextstate == S_PLAY_WAIT) && spr->mobj->state == &states[S_PLAY_STND]))
+						nextFrame = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % mod;
 				}
 			}
 		}
-
 #undef INTERPOLERATION_LIMIT
+#endif
 
 		//Hurdler: it seems there is still a small problem with mobj angle
 		p.x = FIXED_TO_FLOAT(spr->mobj->x);
@@ -1524,7 +1158,13 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 
 		if (sprframe->rotate)
 		{
-			const fixed_t anglef = AngleFixed((spr->mobj->player ? spr->mobj->player->drawangle : spr->mobj->angle));
+			fixed_t anglef = AngleFixed(spr->mobj->angle);
+
+			if (spr->mobj->player)
+				anglef = AngleFixed(spr->mobj->player->drawangle);
+			else
+				anglef = AngleFixed(spr->mobj->angle);
+
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 		else
@@ -1533,6 +1173,20 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 		p.anglex = 0.0f;
+#ifdef USE_FTRANSFORM_ANGLEZ
+		// Slope rotation from Kart
+		p.anglez = 0.0f;
+		if (spr->mobj->standingslope)
+		{
+			fixed_t tempz = spr->mobj->standingslope->normal.z;
+			fixed_t tempy = spr->mobj->standingslope->normal.y;
+			fixed_t tempx = spr->mobj->standingslope->normal.x;
+			fixed_t tempangle = AngleFixed(R_PointToAngle2(0, 0, FixedSqrt(FixedMul(tempy, tempy) + FixedMul(tempz, tempz)), tempx));
+			p.anglez = FIXED_TO_FLOAT(tempangle);
+			tempangle = -AngleFixed(R_PointToAngle2(0, 0, tempz, tempy));
+			p.anglex = FIXED_TO_FLOAT(tempangle);
+		}
+#endif
 
 		color[0] = Surf.FlatColor.s.red;
 		color[1] = Surf.FlatColor.s.green;
@@ -1543,8 +1197,11 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 		finalscale *= FIXED_TO_FLOAT(spr->mobj->scale);
 
 		p.flip = atransform.flip;
+#ifdef USE_FTRANSFORM_MIRROR
+		p.mirror = atransform.mirror; // from Kart
+#endif
 
-		HWD.pfnDrawMD2i(buff, curr, durs, tics, next, &p, finalscale, flip, color);
+		HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, finalscale, flip, color);
 	}
 }
 
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 24a5639330c12fe59ed1aae43ace60d54139c0f3..a5f5fc1174b1e0bc1e9a69fa8e5b75f0d78b9234 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -22,104 +22,7 @@
 #define _HW_MD2_H_
 
 #include "hw_glob.h"
-#include "../info.h"
-
-// magic number "IDP2" or 844121161
-#define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
-// model version
-#define MD2_VERSION                     8
-
-// magic number "IDP2" or 844121161
-#define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
-// model version
-#define MD2_VERSION                     8
-
-#define MD2_MAX_TRIANGLES               8192
-#define MD2_MAX_VERTICES                4096
-#define MD2_MAX_TEXCOORDS               4096
-#define MD2_MAX_FRAMES                  512
-#define MD2_MAX_SKINS                   32
-#define MD2_MAX_FRAMESIZE               (MD2_MAX_VERTICES * 4 + 128)
-
-#if defined(_MSC_VER)
-#pragma pack(1)
-#endif
-typedef struct
-{
-	UINT32 magic;
-	UINT32 version;
-	UINT32 skinWidth;
-	UINT32 skinHeight;
-	UINT32 frameSize;
-	UINT32 numSkins;
-	UINT32 numVertices;
-	UINT32 numTexCoords;
-	UINT32 numTriangles;
-	UINT32 numGlCommands;
-	UINT32 numFrames;
-	UINT32 offsetSkins;
-	UINT32 offsetTexCoords;
-	UINT32 offsetTriangles;
-	UINT32 offsetFrames;
-	UINT32 offsetGlCommands;
-	UINT32 offsetEnd;
-} ATTRPACK md2_header_t; //NOTE: each of md2_header's members are 4 unsigned bytes
-
-typedef struct
-{
-	UINT8 vertex[3];
-	UINT8 lightNormalIndex;
-} ATTRPACK md2_alias_triangleVertex_t;
-
-typedef struct
-{
-	float vertex[3];
-	float normal[3];
-} ATTRPACK md2_triangleVertex_t;
-
-typedef struct
-{
-	INT16 vertexIndices[3];
-	INT16 textureIndices[3];
-} ATTRPACK md2_triangle_t;
-
-typedef struct
-{
-	INT16 s, t;
-} ATTRPACK md2_textureCoordinate_t;
-
-typedef struct
-{
-	float scale[3];
-	float translate[3];
-	char name[16];
-	md2_alias_triangleVertex_t alias_vertices[1];
-} ATTRPACK md2_alias_frame_t;
-
-typedef struct
-{
-	char name[16];
-	md2_triangleVertex_t *vertices;
-} ATTRPACK md2_frame_t;
-
-typedef char md2_skin_t[64];
-
-typedef struct
-{
-	float s, t;
-	INT32 vertexIndex;
-} ATTRPACK md2_glCommandVertex_t;
-
-typedef struct
-{
-	md2_header_t            header;
-	md2_skin_t              *skins;
-	md2_textureCoordinate_t *texCoords;
-	md2_triangle_t          *triangles;
-	md2_frame_t             *frames;
-	size_t                  *spr2frames; // size_t spr2frames[2*NUMPLAYERSPRITES][2];
-	INT32                   *glCommandBuffer;
-} ATTRPACK md2_model_t;
+#include "hw_model.h"
 
 #if defined(_MSC_VER)
 #pragma pack()
@@ -130,7 +33,7 @@ typedef struct
 	char        filename[32];
 	float       scale;
 	float       offset;
-	md2_model_t *model;
+	model_t     *model;
 	void        *grpatch;
 	void        *blendgrpatch;
 	boolean     notfound;
@@ -141,9 +44,9 @@ typedef struct
 extern md2_t md2_models[NUMSPRITES];
 extern md2_t md2_playermodels[MAXSKINS];
 
-void HWR_InitMD2(void);
-void HWR_DrawMD2(gr_vissprite_t *spr);
-void HWR_AddPlayerMD2(INT32 skin);
-void HWR_AddSpriteMD2(size_t spritenum);
+void HWR_InitModels(void);
+void HWR_DrawModel(gr_vissprite_t *spr);
+void HWR_AddPlayerModel(INT32 skin);
+void HWR_AddSpriteModel(size_t spritenum);
 
 #endif // _HW_MD2_H_
diff --git a/src/hardware/hw_md2load.c b/src/hardware/hw_md2load.c
new file mode 100644
index 0000000000000000000000000000000000000000..fed81e411dacc6b3f87ccb985cb49c0331381770
--- /dev/null
+++ b/src/hardware/hw_md2load.c
@@ -0,0 +1,576 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "../doomdef.h"
+#include "hw_md2load.h"
+#include "hw_model.h"
+#include "../z_zone.h"
+
+#define NUMVERTEXNORMALS 162
+
+// Quake 2 normals are indexed. Use avertexnormals[normalindex][x/y/z] and
+// you'll have your normals.
+float avertexnormals[NUMVERTEXNORMALS][3] = {
+{-0.525731f, 0.000000f, 0.850651f},
+{-0.442863f, 0.238856f, 0.864188f},
+{-0.295242f, 0.000000f, 0.955423f},
+{-0.309017f, 0.500000f, 0.809017f},
+{-0.162460f, 0.262866f, 0.951056f},
+{0.000000f, 0.000000f, 1.000000f},
+{0.000000f, 0.850651f, 0.525731f},
+{-0.147621f, 0.716567f, 0.681718f},
+{0.147621f, 0.716567f, 0.681718f},
+{0.000000f, 0.525731f, 0.850651f},
+{0.309017f, 0.500000f, 0.809017f},
+{0.525731f, 0.000000f, 0.850651f},
+{0.295242f, 0.000000f, 0.955423f},
+{0.442863f, 0.238856f, 0.864188f},
+{0.162460f, 0.262866f, 0.951056f},
+{-0.681718f, 0.147621f, 0.716567f},
+{-0.809017f, 0.309017f, 0.500000f},
+{-0.587785f, 0.425325f, 0.688191f},
+{-0.850651f, 0.525731f, 0.000000f},
+{-0.864188f, 0.442863f, 0.238856f},
+{-0.716567f, 0.681718f, 0.147621f},
+{-0.688191f, 0.587785f, 0.425325f},
+{-0.500000f, 0.809017f, 0.309017f},
+{-0.238856f, 0.864188f, 0.442863f},
+{-0.425325f, 0.688191f, 0.587785f},
+{-0.716567f, 0.681718f, -0.147621f},
+{-0.500000f, 0.809017f, -0.309017f},
+{-0.525731f, 0.850651f, 0.000000f},
+{0.000000f, 0.850651f, -0.525731f},
+{-0.238856f, 0.864188f, -0.442863f},
+{0.000000f, 0.955423f, -0.295242f},
+{-0.262866f, 0.951056f, -0.162460f},
+{0.000000f, 1.000000f, 0.000000f},
+{0.000000f, 0.955423f, 0.295242f},
+{-0.262866f, 0.951056f, 0.162460f},
+{0.238856f, 0.864188f, 0.442863f},
+{0.262866f, 0.951056f, 0.162460f},
+{0.500000f, 0.809017f, 0.309017f},
+{0.238856f, 0.864188f, -0.442863f},
+{0.262866f, 0.951056f, -0.162460f},
+{0.500000f, 0.809017f, -0.309017f},
+{0.850651f, 0.525731f, 0.000000f},
+{0.716567f, 0.681718f, 0.147621f},
+{0.716567f, 0.681718f, -0.147621f},
+{0.525731f, 0.850651f, 0.000000f},
+{0.425325f, 0.688191f, 0.587785f},
+{0.864188f, 0.442863f, 0.238856f},
+{0.688191f, 0.587785f, 0.425325f},
+{0.809017f, 0.309017f, 0.500000f},
+{0.681718f, 0.147621f, 0.716567f},
+{0.587785f, 0.425325f, 0.688191f},
+{0.955423f, 0.295242f, 0.000000f},
+{1.000000f, 0.000000f, 0.000000f},
+{0.951056f, 0.162460f, 0.262866f},
+{0.850651f, -0.525731f, 0.000000f},
+{0.955423f, -0.295242f, 0.000000f},
+{0.864188f, -0.442863f, 0.238856f},
+{0.951056f, -0.162460f, 0.262866f},
+{0.809017f, -0.309017f, 0.500000f},
+{0.681718f, -0.147621f, 0.716567f},
+{0.850651f, 0.000000f, 0.525731f},
+{0.864188f, 0.442863f, -0.238856f},
+{0.809017f, 0.309017f, -0.500000f},
+{0.951056f, 0.162460f, -0.262866f},
+{0.525731f, 0.000000f, -0.850651f},
+{0.681718f, 0.147621f, -0.716567f},
+{0.681718f, -0.147621f, -0.716567f},
+{0.850651f, 0.000000f, -0.525731f},
+{0.809017f, -0.309017f, -0.500000f},
+{0.864188f, -0.442863f, -0.238856f},
+{0.951056f, -0.162460f, -0.262866f},
+{0.147621f, 0.716567f, -0.681718f},
+{0.309017f, 0.500000f, -0.809017f},
+{0.425325f, 0.688191f, -0.587785f},
+{0.442863f, 0.238856f, -0.864188f},
+{0.587785f, 0.425325f, -0.688191f},
+{0.688191f, 0.587785f, -0.425325f},
+{-0.147621f, 0.716567f, -0.681718f},
+{-0.309017f, 0.500000f, -0.809017f},
+{0.000000f, 0.525731f, -0.850651f},
+{-0.525731f, 0.000000f, -0.850651f},
+{-0.442863f, 0.238856f, -0.864188f},
+{-0.295242f, 0.000000f, -0.955423f},
+{-0.162460f, 0.262866f, -0.951056f},
+{0.000000f, 0.000000f, -1.000000f},
+{0.295242f, 0.000000f, -0.955423f},
+{0.162460f, 0.262866f, -0.951056f},
+{-0.442863f, -0.238856f, -0.864188f},
+{-0.309017f, -0.500000f, -0.809017f},
+{-0.162460f, -0.262866f, -0.951056f},
+{0.000000f, -0.850651f, -0.525731f},
+{-0.147621f, -0.716567f, -0.681718f},
+{0.147621f, -0.716567f, -0.681718f},
+{0.000000f, -0.525731f, -0.850651f},
+{0.309017f, -0.500000f, -0.809017f},
+{0.442863f, -0.238856f, -0.864188f},
+{0.162460f, -0.262866f, -0.951056f},
+{0.238856f, -0.864188f, -0.442863f},
+{0.500000f, -0.809017f, -0.309017f},
+{0.425325f, -0.688191f, -0.587785f},
+{0.716567f, -0.681718f, -0.147621f},
+{0.688191f, -0.587785f, -0.425325f},
+{0.587785f, -0.425325f, -0.688191f},
+{0.000000f, -0.955423f, -0.295242f},
+{0.000000f, -1.000000f, 0.000000f},
+{0.262866f, -0.951056f, -0.162460f},
+{0.000000f, -0.850651f, 0.525731f},
+{0.000000f, -0.955423f, 0.295242f},
+{0.238856f, -0.864188f, 0.442863f},
+{0.262866f, -0.951056f, 0.162460f},
+{0.500000f, -0.809017f, 0.309017f},
+{0.716567f, -0.681718f, 0.147621f},
+{0.525731f, -0.850651f, 0.000000f},
+{-0.238856f, -0.864188f, -0.442863f},
+{-0.500000f, -0.809017f, -0.309017f},
+{-0.262866f, -0.951056f, -0.162460f},
+{-0.850651f, -0.525731f, 0.000000f},
+{-0.716567f, -0.681718f, -0.147621f},
+{-0.716567f, -0.681718f, 0.147621f},
+{-0.525731f, -0.850651f, 0.000000f},
+{-0.500000f, -0.809017f, 0.309017f},
+{-0.238856f, -0.864188f, 0.442863f},
+{-0.262866f, -0.951056f, 0.162460f},
+{-0.864188f, -0.442863f, 0.238856f},
+{-0.809017f, -0.309017f, 0.500000f},
+{-0.688191f, -0.587785f, 0.425325f},
+{-0.681718f, -0.147621f, 0.716567f},
+{-0.442863f, -0.238856f, 0.864188f},
+{-0.587785f, -0.425325f, 0.688191f},
+{-0.309017f, -0.500000f, 0.809017f},
+{-0.147621f, -0.716567f, 0.681718f},
+{-0.425325f, -0.688191f, 0.587785f},
+{-0.162460f, -0.262866f, 0.951056f},
+{0.442863f, -0.238856f, 0.864188f},
+{0.162460f, -0.262866f, 0.951056f},
+{0.309017f, -0.500000f, 0.809017f},
+{0.147621f, -0.716567f, 0.681718f},
+{0.000000f, -0.525731f, 0.850651f},
+{0.425325f, -0.688191f, 0.587785f},
+{0.587785f, -0.425325f, 0.688191f},
+{0.688191f, -0.587785f, 0.425325f},
+{-0.955423f, 0.295242f, 0.000000f},
+{-0.951056f, 0.162460f, 0.262866f},
+{-1.000000f, 0.000000f, 0.000000f},
+{-0.850651f, 0.000000f, 0.525731f},
+{-0.955423f, -0.295242f, 0.000000f},
+{-0.951056f, -0.162460f, 0.262866f},
+{-0.864188f, 0.442863f, -0.238856f},
+{-0.951056f, 0.162460f, -0.262866f},
+{-0.809017f, 0.309017f, -0.500000f},
+{-0.864188f, -0.442863f, -0.238856f},
+{-0.951056f, -0.162460f, -0.262866f},
+{-0.809017f, -0.309017f, -0.500000f},
+{-0.681718f, 0.147621f, -0.716567f},
+{-0.681718f, -0.147621f, -0.716567f},
+{-0.850651f, 0.000000f, -0.525731f},
+{-0.688191f, 0.587785f, -0.425325f},
+{-0.587785f, 0.425325f, -0.688191f},
+{-0.425325f, 0.688191f, -0.587785f},
+{-0.425325f, -0.688191f, -0.587785f},
+{-0.587785f, -0.425325f, -0.688191f},
+{-0.688191f, -0.587785f, -0.425325f},
+};
+
+typedef struct
+{
+	int ident;        // A "magic number" that's used to identify the .md2 file
+	int version;      // The version of the file, always 8
+	int skinwidth;    // Width of the skin(s) in pixels
+	int skinheight;   // Height of the skin(s) in pixels
+	int framesize;    // Size of each frame in bytes
+	int numSkins;     // Number of skins with the model
+	int numXYZ;       // Number of vertices in each frame
+	int numST;        // Number of texture coordinates in each frame.
+	int numTris;      // Number of triangles in each frame
+	int numGLcmds;    // Number of dwords (4 bytes) in the gl command list.
+	int numFrames;    // Number of frames
+	int offsetSkins;  // Offset, in bytes from the start of the file, to the list of skin names.
+	int offsetST;     // Offset, in bytes from the start of the file, to the list of texture coordinates
+	int offsetTris;   // Offset, in bytes from the start of the file, to the list of triangles
+	int offsetFrames; // Offset, in bytes from the start of the file, to the list of frames
+	int offsetGLcmds; // Offset, in bytes from the start of the file, to the list of gl commands
+	int offsetEnd;    // Offset, in bytes from the start of the file, to the end of the file (filesize)
+} md2header_t;
+
+typedef struct
+{
+	unsigned short meshIndex[3]; // indices into the array of vertices in each frames
+	unsigned short stIndex[3];   // indices into the array of texture coordinates
+} md2triangle_t;
+
+typedef struct
+{
+	short s;
+	short t;
+} md2texcoord_t;
+
+typedef struct
+{
+	unsigned char v[3];             // Scaled vertices. You'll need to multiply them with scale[x] to make them normal.
+	unsigned char lightNormalIndex; // Index to the array of normals
+} md2vertex_t;
+
+typedef struct
+{
+	float scale[3];      // Used by the v member in the md2framePoint structure
+	float translate[3];  // Used by the v member in the md2framePoint structure
+	char name[16];       // Name of the frame
+} md2frame_t;
+
+// Load the model
+model_t *MD2_LoadModel(const char *fileName, int ztag, boolean useFloat)
+{
+	FILE *f;
+
+	model_t *retModel = NULL;
+	md2header_t *header;
+
+	size_t fileLen;
+	int i, j;
+	size_t namelen;
+	char *texturefilename;
+	const char *texPos;
+
+	char *buffer;
+
+	const float WUNITS = 1.0f;
+	float dataScale = WUNITS;
+
+	md2triangle_t *tris;
+	md2texcoord_t *texcoords;
+	md2frame_t *frames;
+	char *fname = NULL;
+	int foffset = 0;
+
+	int t;
+
+	// MD2 currently does not work with tinyframes, so force useFloat = true
+	//
+	// <SSNTails>
+	// the UV coordinates in MD2 are not compatible with glDrawElements like MD3 is. So they need to be loaded as full float.
+	//
+	// MD2 is intended to be draw in triangle strips and fans
+	// not very compatible with a modern GL implementation, either
+	// so the idea would be to full float expand it, and put it in a vertex buffer object
+	// I'm sure there's a way to convert the UVs to 'tinyframes', but maybe that's a job for someone else.
+	// You'd have to decompress the model, then recompress, reindexing the triangles and weeding out duplicate coordinates
+	// I already have the decompression work done
+
+	useFloat = true;
+
+	f = fopen(fileName, "rb");
+
+	if (!f)
+		return NULL;
+
+	retModel = (model_t*)Z_Calloc(sizeof(model_t), ztag, 0);
+
+	//size_t fileLen;
+
+	//int i, j;
+
+	//size_t namelen;
+	//char *texturefilename;
+	texPos = strchr(fileName, '/');
+
+	if (texPos)
+	{
+		texPos++;
+		namelen = strlen(texPos) + 1;
+		texturefilename = (char*)Z_Malloc(namelen, PU_CACHE, 0);
+		strcpy(texturefilename, texPos);
+	}
+	else
+	{
+		namelen = strlen(fileName) + 1;
+		texturefilename = (char*)Z_Malloc(namelen, PU_CACHE, 0);
+		strcpy(texturefilename, fileName);
+	}
+
+	texturefilename[namelen - 2] = 'z';
+	texturefilename[namelen - 3] = 'u';
+	texturefilename[namelen - 4] = 'b';
+
+	// find length of file
+	fseek(f, 0, SEEK_END);
+	fileLen = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	// read in file
+	buffer = malloc(fileLen);
+	if (fread(buffer, fileLen, 1, f)) { } // squash ignored fread error
+	fclose(f);
+
+	// get pointer to file header
+	header = (md2header_t*)buffer;
+
+	retModel->numMeshes = 1; // MD2 only has one mesh
+	retModel->meshes = (mesh_t*)Z_Calloc(sizeof(mesh_t) * retModel->numMeshes, ztag, 0);
+	retModel->meshes[0].numFrames = header->numFrames;
+	// const float WUNITS = 1.0f;
+	// float dataScale = WUNITS;
+
+	// Tris and ST are simple structures that can be straight-copied
+	tris = (md2triangle_t*)&buffer[header->offsetTris];
+	texcoords = (md2texcoord_t*)&buffer[header->offsetST];
+	frames = (md2frame_t*)&buffer[header->offsetFrames];
+
+	retModel->framenames = (char*)Z_Calloc(header->numFrames*16, ztag, 0);
+	fname = retModel->framenames;
+	for (i = 0; i < header->numFrames; i++)
+	{
+		md2frame_t *fr = (md2frame_t*)&buffer[header->offsetFrames + foffset];
+		memcpy(fname, fr->name, 16);
+		foffset += sizeof(md2frame_t) + (sizeof(md2vertex_t) * header->numXYZ);
+		fname += 16;
+	}
+
+	// Read in textures
+	retModel->numMaterials = header->numSkins;
+
+	if (retModel->numMaterials <= 0) // Always at least one skin, duh
+		retModel->numMaterials = 1;
+
+	retModel->materials = (material_t*)Z_Calloc(sizeof(material_t)*retModel->numMaterials, ztag, 0);
+
+	// int t;
+	for (t = 0; t < retModel->numMaterials; t++)
+	{
+		retModel->materials[t].ambient[0] = 0.8f;
+		retModel->materials[t].ambient[1] = 0.8f;
+		retModel->materials[t].ambient[2] = 0.8f;
+		retModel->materials[t].ambient[3] = 1.0f;
+		retModel->materials[t].diffuse[0] = 0.8f;
+		retModel->materials[t].diffuse[1] = 0.8f;
+		retModel->materials[t].diffuse[2] = 0.8f;
+		retModel->materials[t].diffuse[3] = 1.0f;
+		retModel->materials[t].emissive[0] = 0.0f;
+		retModel->materials[t].emissive[1] = 0.0f;
+		retModel->materials[t].emissive[2] = 0.0f;
+		retModel->materials[t].emissive[3] = 1.0f;
+		retModel->materials[t].specular[0] = 0.0f;
+		retModel->materials[t].specular[1] = 0.0f;
+		retModel->materials[t].specular[2] = 0.0f;
+		retModel->materials[t].specular[3] = 1.0f;
+		retModel->materials[t].shininess = 0.0f;
+		retModel->materials[t].spheremap = false;
+
+		/*		retModel->materials[t].texture = Texture::ReadTexture((char*)texturefilename, ZT_TEXTURE);
+
+				if (!systemSucks)
+				{
+					// Check for a normal map...??
+					char openfilename[1024];
+					char normalMapName[1024];
+					strcpy(normalMapName, texturefilename);
+					size_t len = strlen(normalMapName);
+					char *ptr = &normalMapName[len];
+					ptr--; // z
+					ptr--; // u
+					ptr--; // b
+					ptr--; // .
+					*ptr++ = '_';
+					*ptr++ = 'n';
+					*ptr++ = '.';
+					*ptr++ = 'b';
+					*ptr++ = 'u';
+					*ptr++ = 'z';
+					*ptr++ = '\0';
+
+					sprintf(openfilename, "%s/%s", "textures", normalMapName);
+					// Convert backslashes to forward slashes
+					for (int k = 0; k < 1024; k++)
+					{
+						if (openfilename[k] == '\0')
+							break;
+
+						if (openfilename[k] == '\\')
+							openfilename[k] = '/';
+					}
+
+					Resource::resource_t *res = Resource::Open(openfilename);
+					if (res)
+					{
+						Resource::Close(res);
+						retModel->materials[t].lightmap = Texture::ReadTexture(normalMapName, ZT_TEXTURE);
+					}
+				}*/
+	}
+
+	retModel->meshes[0].numTriangles = header->numTris;
+
+	if (!useFloat) // Decompress to MD3 'tinyframe' space
+	{
+		char *ptr;
+
+		md2triangle_t *trisPtr;
+		unsigned short *indexptr;
+		float *uvptr;
+
+		dataScale = 0.015624f; // 1 / 64.0f
+		retModel->meshes[0].tinyframes = (tinyframe_t*)Z_Calloc(sizeof(tinyframe_t)*header->numFrames, ztag, 0);
+		retModel->meshes[0].numVertices = header->numXYZ;
+		retModel->meshes[0].uvs = (float*)Z_Malloc(sizeof(float) * 2 * retModel->meshes[0].numVertices, ztag, 0);
+
+		ptr = (char*)frames;
+		for (i = 0; i < header->numFrames; i++, ptr += header->framesize)
+		{
+			short *vertptr;
+			char *normptr;
+			// char *tanptr;
+
+			md2vertex_t *vertex;
+
+			md2frame_t *framePtr = (md2frame_t*)ptr;
+			retModel->meshes[0].tinyframes[i].vertices = (short*)Z_Malloc(sizeof(short) * 3 * header->numXYZ, ztag, 0);
+			retModel->meshes[0].tinyframes[i].normals = (char*)Z_Malloc(sizeof(char) * 3 * header->numXYZ, ztag, 0);
+
+			//			if (retModel->materials[0].lightmap)
+			//				retModel->meshes[0].tinyframes[i].tangents = (char*)malloc(sizeof(char));//(char*)Z_Malloc(sizeof(char)*3*header->numVerts, ztag);
+			retModel->meshes[0].indices = (unsigned short*)Z_Malloc(sizeof(unsigned short) * 3 * header->numTris, ztag, 0);
+
+			vertptr = retModel->meshes[0].tinyframes[i].vertices;
+			normptr = retModel->meshes[0].tinyframes[i].normals;
+
+			//			tanptr = retModel->meshes[0].tinyframes[i].tangents;
+			retModel->meshes[0].tinyframes[i].material = &retModel->materials[0];
+
+			framePtr++; // Advance to vertex list
+			vertex = (md2vertex_t*)framePtr;
+			framePtr--;
+			for (j = 0; j < header->numXYZ; j++, vertex++)
+			{
+				*vertptr = (short)(((vertex->v[0] * framePtr->scale[0]) + framePtr->translate[0]) / dataScale);
+				vertptr++;
+				*vertptr = (short)(((vertex->v[2] * framePtr->scale[2]) + framePtr->translate[2]) / dataScale);
+				vertptr++;
+				*vertptr = -1.0f * (short)(((vertex->v[1] * framePtr->scale[1]) + framePtr->translate[1]) / dataScale);
+				vertptr++;
+
+				// Normal
+				*normptr++ = (char)(avertexnormals[vertex->lightNormalIndex][0] * 127);
+				*normptr++ = (char)(avertexnormals[vertex->lightNormalIndex][2] * 127);
+				*normptr++ = (char)(avertexnormals[vertex->lightNormalIndex][1] * 127);
+			}
+		}
+
+		// This doesn't need to be done every frame!
+		trisPtr = tris;
+		indexptr = retModel->meshes[0].indices;
+		uvptr = (float*)retModel->meshes[0].uvs;
+		for (j = 0; j < header->numTris; j++, trisPtr++)
+		{
+			*indexptr = trisPtr->meshIndex[0];
+			indexptr++;
+			*indexptr = trisPtr->meshIndex[1];
+			indexptr++;
+			*indexptr = trisPtr->meshIndex[2];
+			indexptr++;
+
+			uvptr[trisPtr->meshIndex[0] * 2] = texcoords[trisPtr->stIndex[0]].s / (float)header->skinwidth;
+			uvptr[trisPtr->meshIndex[0] * 2 + 1] = (texcoords[trisPtr->stIndex[0]].t / (float)header->skinheight);
+			uvptr[trisPtr->meshIndex[1] * 2] = texcoords[trisPtr->stIndex[1]].s / (float)header->skinwidth;
+			uvptr[trisPtr->meshIndex[1] * 2 + 1] = (texcoords[trisPtr->stIndex[1]].t / (float)header->skinheight);
+			uvptr[trisPtr->meshIndex[2] * 2] = texcoords[trisPtr->stIndex[2]].s / (float)header->skinwidth;
+			uvptr[trisPtr->meshIndex[2] * 2 + 1] = (texcoords[trisPtr->stIndex[2]].t / (float)header->skinheight);
+		}
+	}
+	else // Full float loading method
+	{
+		md2triangle_t *trisPtr;
+		float *uvptr;
+
+		char *ptr;
+
+		retModel->meshes[0].numVertices = header->numTris * 3;
+		retModel->meshes[0].frames = (mdlframe_t*)Z_Calloc(sizeof(mdlframe_t)*header->numFrames, ztag, 0);
+		retModel->meshes[0].uvs = (float*)Z_Malloc(sizeof(float) * 2 * retModel->meshes[0].numVertices, ztag, 0);
+
+		trisPtr = tris;
+		uvptr = retModel->meshes[0].uvs;
+		for (i = 0; i < retModel->meshes[0].numTriangles; i++, trisPtr++)
+		{
+			*uvptr++ = texcoords[trisPtr->stIndex[0]].s / (float)header->skinwidth;
+			*uvptr++ = (texcoords[trisPtr->stIndex[0]].t / (float)header->skinheight);
+			*uvptr++ = texcoords[trisPtr->stIndex[1]].s / (float)header->skinwidth;
+			*uvptr++ = (texcoords[trisPtr->stIndex[1]].t / (float)header->skinheight);
+			*uvptr++ = texcoords[trisPtr->stIndex[2]].s / (float)header->skinwidth;
+			*uvptr++ = (texcoords[trisPtr->stIndex[2]].t / (float)header->skinheight);
+		}
+
+		ptr = (char*)frames;
+		for (i = 0; i < header->numFrames; i++, ptr += header->framesize)
+		{
+			float *vertptr, *normptr;
+
+			md2vertex_t *vertex;
+
+			md2frame_t *framePtr = (md2frame_t*)ptr;
+			retModel->meshes[0].frames[i].normals = (float*)Z_Malloc(sizeof(float) * 3 * header->numTris * 3, ztag, 0);
+			retModel->meshes[0].frames[i].vertices = (float*)Z_Malloc(sizeof(float) * 3 * header->numTris * 3, ztag, 0);
+			//			if (retModel->materials[0].lightmap)
+			//				retModel->meshes[0].frames[i].tangents = (float*)malloc(sizeof(float));//(float*)Z_Malloc(sizeof(float)*3*header->numTris*3, ztag);
+			//float *vertptr, *normptr;
+			normptr = (float*)retModel->meshes[0].frames[i].normals;
+			vertptr = (float*)retModel->meshes[0].frames[i].vertices;
+			trisPtr = tris;
+
+			retModel->meshes[0].frames[i].material = &retModel->materials[0];
+
+			framePtr++; // Advance to vertex list
+			vertex = (md2vertex_t*)framePtr;
+			framePtr--;
+			for (j = 0; j < header->numTris; j++, trisPtr++)
+			{
+				*vertptr = ((vertex[trisPtr->meshIndex[0]].v[0] * framePtr->scale[0]) + framePtr->translate[0]) * WUNITS;
+				vertptr++;
+				*vertptr = ((vertex[trisPtr->meshIndex[0]].v[2] * framePtr->scale[2]) + framePtr->translate[2]) * WUNITS;
+				vertptr++;
+				*vertptr = -1.0f * ((vertex[trisPtr->meshIndex[0]].v[1] * framePtr->scale[1]) + framePtr->translate[1]) * WUNITS;
+				vertptr++;
+
+				*vertptr = ((vertex[trisPtr->meshIndex[1]].v[0] * framePtr->scale[0]) + framePtr->translate[0]) * WUNITS;
+				vertptr++;
+				*vertptr = ((vertex[trisPtr->meshIndex[1]].v[2] * framePtr->scale[2]) + framePtr->translate[2]) * WUNITS;
+				vertptr++;
+				*vertptr = -1.0f * ((vertex[trisPtr->meshIndex[1]].v[1] * framePtr->scale[1]) + framePtr->translate[1]) * WUNITS;
+				vertptr++;
+
+				*vertptr = ((vertex[trisPtr->meshIndex[2]].v[0] * framePtr->scale[0]) + framePtr->translate[0]) * WUNITS;
+				vertptr++;
+				*vertptr = ((vertex[trisPtr->meshIndex[2]].v[2] * framePtr->scale[2]) + framePtr->translate[2]) * WUNITS;
+				vertptr++;
+				*vertptr = -1.0f * ((vertex[trisPtr->meshIndex[2]].v[1] * framePtr->scale[1]) + framePtr->translate[1]) * WUNITS;
+				vertptr++;
+
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[0]].lightNormalIndex][0];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[0]].lightNormalIndex][2];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[0]].lightNormalIndex][1];
+
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[1]].lightNormalIndex][0];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[1]].lightNormalIndex][2];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[1]].lightNormalIndex][1];
+
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[2]].lightNormalIndex][0];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[2]].lightNormalIndex][2];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[2]].lightNormalIndex][1];
+			}
+		}
+	}
+
+	free(buffer);
+	return retModel;
+}
diff --git a/src/hardware/hw_md2load.h b/src/hardware/hw_md2load.h
new file mode 100644
index 0000000000000000000000000000000000000000..1662d6471eca6780ebd0213595b8985e4968da20
--- /dev/null
+++ b/src/hardware/hw_md2load.h
@@ -0,0 +1,19 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _HW_MD2LOAD_H_
+#define _HW_MD2LOAD_H_
+
+#include "hw_model.h"
+#include "../doomtype.h"
+
+// Load the Model
+model_t *MD2_LoadModel(const char *fileName, int ztag, boolean useFloat);
+
+#endif
diff --git a/src/hardware/hw_md3load.c b/src/hardware/hw_md3load.c
new file mode 100644
index 0000000000000000000000000000000000000000..87931d27ba104e1f9a4109515f6497ad19bae453
--- /dev/null
+++ b/src/hardware/hw_md3load.c
@@ -0,0 +1,522 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "../doomdef.h"
+#include "hw_md3load.h"
+#include "hw_model.h"
+#include "../z_zone.h"
+
+typedef struct
+{
+	int ident;			// A "magic number" that's used to identify the .md3 file
+	int version;		// The version of the file, always 15
+	char name[64];
+	int flags;
+	int numFrames;		// Number of frames
+	int numTags;
+	int numSurfaces;
+	int numSkins;		// Number of skins with the model
+	int offsetFrames;
+	int offsetTags;
+	int offsetSurfaces;
+	int offsetEnd;		// Offset, in bytes from the start of the file, to the end of the file (filesize)
+} md3modelHeader;
+
+typedef struct
+{
+	float minBounds[3];		// First corner of the bounding box
+	float maxBounds[3];		// Second corner of the bounding box
+	float localOrigin[3];	// Local origin, usually (0, 0, 0)
+	float radius;			// Radius of bounding sphere
+	char name[16];			// Name of frame
+} md3Frame;
+
+typedef struct
+{
+	char name[64];		// Name of tag
+	float origin[3];	// Coordinates of tag
+	float axis[9];		// Orientation of tag object
+} md3Tag;
+
+typedef struct
+{
+	int ident;
+	char name[64];			// Name of this surface
+	int flags;
+	int numFrames;			// # of keyframes
+	int numShaders;			// # of shaders
+	int numVerts;			// # of vertices
+	int numTriangles;		// # of triangles
+	int offsetTriangles;	// Relative offset from start of this struct to where the list of Triangles start
+	int offsetShaders;		// Relative offset from start of this struct to where the list of Shaders start
+	int offsetST;			// Relative offset from start of this struct to where the list of tex coords start
+	int offsetXYZNormal;	// Relative offset from start of this struct to where the list of vertices start
+	int offsetEnd;			// Relative offset from start of this struct to where this surface ends
+} md3Surface;
+
+typedef struct
+{
+	char name[64]; // Name of this shader
+	int shaderIndex; // Shader index number
+} md3Shader;
+
+typedef struct
+{
+	int index[3]; // List of offset values into the list of Vertex objects that constitute the corners of the Triangle object.
+} md3Triangle;
+
+typedef struct
+{
+	float st[2];
+} md3TexCoord;
+
+typedef struct
+{
+	short x, y, z, n;
+} md3Vertex;
+
+static float latlnglookup[256][256][3];
+
+static void GetNormalFromLatLong(short latlng, float *out)
+{
+	float *lookup = latlnglookup[(unsigned char)(latlng >> 8)][(unsigned char)(latlng & 255)];
+
+	out[0] = *lookup++;
+	out[1] = *lookup++;
+	out[2] = *lookup++;
+}
+
+#if 0
+static void NormalToLatLng(float *n, short *out)
+{
+	// Special cases
+	if (0.0f == n[0] && 0.0f == n[1])
+	{
+		if (n[2] > 0.0f)
+			*out = 0;
+		else
+			*out = 128;
+	}
+	else
+	{
+		char x, y;
+
+		x = (char)(57.2957795f * (atan2(n[1], n[0])) * (255.0f / 360.0f));
+		y = (char)(57.2957795f * (acos(n[2])) * (255.0f / 360.0f));
+
+		*out = (x << 8) + y;
+	}
+}
+#endif
+
+static inline void LatLngToNormal(short n, float *out)
+{
+	const float PI = (3.1415926535897932384626433832795f);
+	float lat = (float)(n >> 8);
+	float lng = (float)(n & 255);
+
+	lat *= PI / 128.0f;
+	lng *= PI / 128.0f;
+
+	out[0] = cosf(lat) * sinf(lng);
+	out[1] = sinf(lat) * sinf(lng);
+	out[2] = cosf(lng);
+}
+
+static void LatLngInit(void)
+{
+	int i, j;
+	for (i = 0; i < 256; i++)
+	{
+		for (j = 0; j < 256; j++)
+			LatLngToNormal((short)((i << 8) + j), latlnglookup[i][j]);
+	}
+}
+
+static boolean latlnginit = false;
+
+model_t *MD3_LoadModel(const char *fileName, int ztag, boolean useFloat)
+{
+	const float WUNITS = 1.0f;
+	model_t *retModel = NULL;
+	md3Frame *frames = NULL;
+	char *fname = NULL;
+	md3modelHeader *mdh;
+	long fileLen;
+	long fileReadLen;
+	char *buffer;
+	int surfEnd;
+	int i, t;
+	int matCount;
+	FILE *f;
+
+	if (!latlnginit)
+	{
+		LatLngInit();
+		latlnginit = true;
+	}
+
+	f = fopen(fileName, "rb");
+
+	if (!f)
+		return NULL;
+
+	retModel = (model_t*)Z_Calloc(sizeof(model_t), ztag, 0);
+
+	// find length of file
+	fseek(f, 0, SEEK_END);
+	fileLen = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	// read in file
+	buffer = malloc(fileLen);
+	fileReadLen = fread(buffer, fileLen, 1, f);
+	fclose(f);
+
+	(void)fileReadLen; // intentionally ignore return value, per buildbot
+
+	// get pointer to file header
+	mdh = (md3modelHeader*)buffer;
+
+	retModel->numMeshes = mdh->numSurfaces;
+
+	retModel->numMaterials = 0;
+	surfEnd = 0;
+	for (i = 0; i < mdh->numSurfaces; i++)
+	{
+		md3Surface *mdS = (md3Surface*)&buffer[mdh->offsetSurfaces];
+		surfEnd += mdS->offsetEnd;
+
+		retModel->numMaterials += mdS->numShaders;
+	}
+
+	// Initialize materials
+	if (retModel->numMaterials <= 0) // Always at least one skin, duh
+		retModel->numMaterials = 1;
+
+	retModel->materials = (material_t*)Z_Calloc(sizeof(material_t)*retModel->numMaterials, ztag, 0);
+
+	for (t = 0; t < retModel->numMaterials; t++)
+	{
+		retModel->materials[t].ambient[0] = 0.3686f;
+		retModel->materials[t].ambient[1] = 0.3684f;
+		retModel->materials[t].ambient[2] = 0.3684f;
+		retModel->materials[t].ambient[3] = 1.0f;
+		retModel->materials[t].diffuse[0] = 0.8863f;
+		retModel->materials[t].diffuse[1] = 0.8850f;
+		retModel->materials[t].diffuse[2] = 0.8850f;
+		retModel->materials[t].diffuse[3] = 1.0f;
+		retModel->materials[t].emissive[0] = 0.0f;
+		retModel->materials[t].emissive[1] = 0.0f;
+		retModel->materials[t].emissive[2] = 0.0f;
+		retModel->materials[t].emissive[3] = 1.0f;
+		retModel->materials[t].specular[0] = 0.4902f;
+		retModel->materials[t].specular[1] = 0.4887f;
+		retModel->materials[t].specular[2] = 0.4887f;
+		retModel->materials[t].specular[3] = 1.0f;
+		retModel->materials[t].shininess = 25.0f;
+		retModel->materials[t].spheremap = false;
+	}
+
+	retModel->meshes = (mesh_t*)Z_Calloc(sizeof(mesh_t)*retModel->numMeshes, ztag, 0);
+
+	frames = (md3Frame*)&buffer[mdh->offsetFrames];
+	retModel->framenames = (char*)Z_Calloc(mdh->numFrames*16, ztag, 0);
+	fname = retModel->framenames;
+	for (i = 0; i < mdh->numFrames; i++)
+	{
+		memcpy(fname, frames->name, 16);
+		fname += 16;
+		frames++;
+	}
+
+	matCount = 0;
+	for (i = 0, surfEnd = 0; i < mdh->numSurfaces; i++)
+	{
+		int j;
+		md3Shader *mdShader;
+		md3Surface *mdS = (md3Surface*)&buffer[mdh->offsetSurfaces + surfEnd];
+		surfEnd += mdS->offsetEnd;
+
+		mdShader = (md3Shader*)((char*)mdS + mdS->offsetShaders);
+
+		for (j = 0; j < mdS->numShaders; j++, matCount++)
+		{
+			size_t len = strlen(mdShader[j].name);
+			mdShader[j].name[len-1] = 'z';
+			mdShader[j].name[len-2] = 'u';
+			mdShader[j].name[len-3] = 'b';
+
+			// Load material
+/*			retModel->materials[matCount].texture = Texture::ReadTexture(mdShader[j].name, ZT_TEXTURE);
+
+			if (!systemSucks)
+			{
+				// Check for a normal map...??
+				char openfilename[1024];
+				char normalMapName[1024];
+				strcpy(normalMapName, mdShader[j].name);
+				len = strlen(normalMapName);
+				char *ptr = &normalMapName[len];
+				ptr--; // z
+				ptr--; // u
+				ptr--; // b
+				ptr--; // .
+				*ptr++ = '_';
+				*ptr++ = 'n';
+				*ptr++ = '.';
+				*ptr++ = 'b';
+				*ptr++ = 'u';
+				*ptr++ = 'z';
+				*ptr++ = '\0';
+
+				sprintf(openfilename, "%s/%s", "textures", normalMapName);
+				// Convert backslashes to forward slashes
+				for (int k = 0; k < 1024; k++)
+				{
+					if (openfilename[k] == '\0')
+						break;
+
+					if (openfilename[k] == '\\')
+						openfilename[k] = '/';
+				}
+
+				Resource::resource_t *res = Resource::Open(openfilename);
+				if (res)
+				{
+					Resource::Close(res);
+					retModel->materials[matCount].lightmap = Texture::ReadTexture(normalMapName, ZT_TEXTURE);
+				}
+			}*/
+		}
+
+		retModel->meshes[i].numFrames = mdS->numFrames;
+		retModel->meshes[i].numTriangles = mdS->numTriangles;
+
+		if (!useFloat) // 'tinyframe' mode with indices
+		{
+			float tempNormal[3];
+			float *uvptr;
+			md3TexCoord *mdST;
+			unsigned short *indexptr;
+			md3Triangle *mdT;
+
+			retModel->meshes[i].tinyframes = (tinyframe_t*)Z_Calloc(sizeof(tinyframe_t)*mdS->numFrames, ztag, 0);
+			retModel->meshes[i].numVertices = mdS->numVerts;
+			retModel->meshes[i].uvs = (float*)Z_Malloc(sizeof(float)*2*mdS->numVerts, ztag, 0);
+			for (j = 0; j < mdS->numFrames; j++)
+			{
+				short *vertptr;
+				char *normptr;
+				// char *tanptr;
+				int k;
+				md3Vertex *mdV = (md3Vertex*)((char*)mdS + mdS->offsetXYZNormal + (mdS->numVerts*j*sizeof(md3Vertex)));
+				retModel->meshes[i].tinyframes[j].vertices = (short*)Z_Malloc(sizeof(short)*3*mdS->numVerts, ztag, 0);
+				retModel->meshes[i].tinyframes[j].normals = (char*)Z_Malloc(sizeof(char)*3*mdS->numVerts, ztag, 0);
+
+//				if (retModel->materials[0].lightmap)
+//					retModel->meshes[i].tinyframes[j].tangents = (char*)malloc(sizeof(char));//(char*)Z_Malloc(sizeof(char)*3*mdS->numVerts, ztag);
+				retModel->meshes[i].indices = (unsigned short*)Z_Malloc(sizeof(unsigned short) * 3 * mdS->numTriangles, ztag, 0);
+				vertptr = retModel->meshes[i].tinyframes[j].vertices;
+				normptr = retModel->meshes[i].tinyframes[j].normals;
+
+//				tanptr = retModel->meshes[i].tinyframes[j].tangents;
+				retModel->meshes[i].tinyframes[j].material = &retModel->materials[i];
+
+				for (k = 0; k < mdS->numVerts; k++)
+				{
+					// Vertex
+					*vertptr = mdV[k].x;
+					vertptr++;
+					*vertptr = mdV[k].z;
+					vertptr++;
+					*vertptr = 1.0f - mdV[k].y;
+					vertptr++;
+
+					// Normal
+					GetNormalFromLatLong(mdV[k].n, tempNormal);
+					*normptr = (char)(tempNormal[0] * 127);
+					normptr++;
+					*normptr = (char)(tempNormal[2] * 127);
+					normptr++;
+					*normptr = (char)(tempNormal[1] * 127);
+					normptr++;
+				}
+			}
+
+			uvptr = (float*)retModel->meshes[i].uvs;
+			mdST = (md3TexCoord*)((char*)mdS + mdS->offsetST);
+			for (j = 0; j < mdS->numVerts; j++)
+			{
+				*uvptr = mdST[j].st[0];
+				uvptr++;
+				*uvptr = mdST[j].st[1];
+				uvptr++;
+			}
+
+			indexptr = retModel->meshes[i].indices;
+			mdT = (md3Triangle*)((char*)mdS + mdS->offsetTriangles);
+			for (j = 0; j < mdS->numTriangles; j++, mdT++)
+			{
+				// Indices
+				*indexptr = (unsigned short)mdT->index[0];
+				indexptr++;
+				*indexptr = (unsigned short)mdT->index[1];
+				indexptr++;
+				*indexptr = (unsigned short)mdT->index[2];
+				indexptr++;
+			}
+		}
+		else // Traditional full-float loading method
+		{
+			float dataScale = 0.015624f * WUNITS;
+			float tempNormal[3];
+			md3TexCoord *mdST;
+			md3Triangle *mdT;
+			float *uvptr;
+			int k;
+
+			retModel->meshes[i].numVertices = mdS->numTriangles * 3;//mdS->numVerts;
+			retModel->meshes[i].frames = (mdlframe_t*)Z_Calloc(sizeof(mdlframe_t)*mdS->numFrames, ztag, 0);
+			retModel->meshes[i].uvs = (float*)Z_Malloc(sizeof(float)*2*mdS->numTriangles*3, ztag, 0);
+
+			for (j = 0; j < mdS->numFrames; j++)
+			{
+				float *vertptr;
+				float *normptr;
+				md3Vertex *mdV = (md3Vertex*)((char*)mdS + mdS->offsetXYZNormal + (mdS->numVerts*j*sizeof(md3Vertex)));
+				retModel->meshes[i].frames[j].vertices = (float*)Z_Malloc(sizeof(float)*3*mdS->numTriangles*3, ztag, 0);
+				retModel->meshes[i].frames[j].normals = (float*)Z_Malloc(sizeof(float)*3*mdS->numTriangles*3, ztag, 0);
+//				if (retModel->materials[i].lightmap)
+//					retModel->meshes[i].frames[j].tangents = (float*)malloc(sizeof(float));//(float*)Z_Malloc(sizeof(float)*3*mdS->numTriangles*3, ztag);
+				vertptr = retModel->meshes[i].frames[j].vertices;
+				normptr = retModel->meshes[i].frames[j].normals;
+				retModel->meshes[i].frames[j].material = &retModel->materials[i];
+
+				mdT = (md3Triangle*)((char*)mdS + mdS->offsetTriangles);
+
+				for (k = 0; k < mdS->numTriangles; k++)
+				{
+					// Vertex 1
+					*vertptr = mdV[mdT->index[0]].x * dataScale;
+					vertptr++;
+					*vertptr = mdV[mdT->index[0]].z * dataScale;
+					vertptr++;
+					*vertptr = 1.0f - mdV[mdT->index[0]].y * dataScale;
+					vertptr++;
+
+					GetNormalFromLatLong(mdV[mdT->index[0]].n, tempNormal);
+					*normptr = tempNormal[0];
+					normptr++;
+					*normptr = tempNormal[2];
+					normptr++;
+					*normptr = tempNormal[1];
+					normptr++;
+
+					// Vertex 2
+					*vertptr = mdV[mdT->index[1]].x * dataScale;
+					vertptr++;
+					*vertptr = mdV[mdT->index[1]].z * dataScale;
+					vertptr++;
+					*vertptr = 1.0f - mdV[mdT->index[1]].y * dataScale;
+					vertptr++;
+
+					GetNormalFromLatLong(mdV[mdT->index[1]].n, tempNormal);
+					*normptr = tempNormal[0];
+					normptr++;
+					*normptr = tempNormal[2];
+					normptr++;
+					*normptr = tempNormal[1];
+					normptr++;
+
+					// Vertex 3
+					*vertptr = mdV[mdT->index[2]].x * dataScale;
+					vertptr++;
+					*vertptr = mdV[mdT->index[2]].z * dataScale;
+					vertptr++;
+					*vertptr = 1.0f - mdV[mdT->index[2]].y * dataScale;
+					vertptr++;
+
+					GetNormalFromLatLong(mdV[mdT->index[2]].n, tempNormal);
+					*normptr = tempNormal[0];
+					normptr++;
+					*normptr = tempNormal[2];
+					normptr++;
+					*normptr = tempNormal[1];
+					normptr++;
+
+					mdT++; // Advance to next triangle
+				}
+			}
+
+			mdST = (md3TexCoord*)((char*)mdS + mdS->offsetST);
+			uvptr = (float*)retModel->meshes[i].uvs;
+			mdT = (md3Triangle*)((char*)mdS + mdS->offsetTriangles);
+
+			for (k = 0; k < mdS->numTriangles; k++)
+			{
+				*uvptr = mdST[mdT->index[0]].st[0];
+				uvptr++;
+				*uvptr = mdST[mdT->index[0]].st[1];
+				uvptr++;
+
+				*uvptr = mdST[mdT->index[1]].st[0];
+				uvptr++;
+				*uvptr = mdST[mdT->index[1]].st[1];
+				uvptr++;
+
+				*uvptr = mdST[mdT->index[2]].st[0];
+				uvptr++;
+				*uvptr = mdST[mdT->index[2]].st[1];
+				uvptr++;
+
+				mdT++; // Advance to next triangle
+			}
+		}
+	}
+	/*
+	// Tags?
+	retModel->numTags = mdh->numTags;
+	retModel->maxNumFrames = mdh->numFrames;
+	retModel->tags = (tag_t*)Z_Calloc(sizeof(tag_t) * retModel->numTags * mdh->numFrames, ztag);
+	md3Tag *mdTag = (md3Tag*)&buffer[mdh->offsetTags];
+	tag_t *curTag = retModel->tags;
+	for (i = 0; i < mdh->numFrames; i++)
+	{
+		int j;
+		for (j = 0; j < retModel->numTags; j++, mdTag++)
+		{
+			strcpys(curTag->name, mdTag->name, sizeof(curTag->name) / sizeof(char));
+			curTag->transform.m[0][0] = mdTag->axis[0];
+			curTag->transform.m[0][1] = mdTag->axis[1];
+			curTag->transform.m[0][2] = mdTag->axis[2];
+			curTag->transform.m[1][0] = mdTag->axis[3];
+			curTag->transform.m[1][1] = mdTag->axis[4];
+			curTag->transform.m[1][2] = mdTag->axis[5];
+			curTag->transform.m[2][0] = mdTag->axis[6];
+			curTag->transform.m[2][1] = mdTag->axis[7];
+			curTag->transform.m[2][2] = mdTag->axis[8];
+			curTag->transform.m[3][0] = mdTag->origin[0] * WUNITS;
+			curTag->transform.m[3][1] = mdTag->origin[1] * WUNITS;
+			curTag->transform.m[3][2] = mdTag->origin[2] * WUNITS;
+			curTag->transform.m[3][3] = 1.0f;
+
+			Matrix::Rotate(&curTag->transform, 90.0f, &Vector::Xaxis);
+			curTag++;
+		}
+	}*/
+
+
+	free(buffer);
+
+	return retModel;
+}
diff --git a/src/hardware/hw_md3load.h b/src/hardware/hw_md3load.h
new file mode 100644
index 0000000000000000000000000000000000000000..c0e0522ff6bf2e6e43c96021ab758ca64ae7b45b
--- /dev/null
+++ b/src/hardware/hw_md3load.h
@@ -0,0 +1,19 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _HW_MD3LOAD_H_
+#define _HW_MD3LOAD_H_
+
+#include "hw_model.h"
+#include "../doomtype.h"
+
+// Load the Model
+model_t *MD3_LoadModel(const char *fileName, int ztag, boolean useFloat);
+
+#endif
diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c
new file mode 100644
index 0000000000000000000000000000000000000000..ac73f8acac4b577f1ca18bd077e8bba837f320d8
--- /dev/null
+++ b/src/hardware/hw_model.c
@@ -0,0 +1,737 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include "../doomdef.h"
+#include "../doomtype.h"
+#include "../info.h"
+#include "../z_zone.h"
+#include "hw_model.h"
+#include "hw_md2load.h"
+#include "hw_md3load.h"
+#include "hw_md2.h"
+#include "u_list.h"
+#include <string.h>
+
+static float PI = (3.1415926535897932384626433832795f);
+static float U_Deg2Rad(float deg)
+{
+	return deg * ((float)PI / 180.0f);
+}
+
+vector_t vectorXaxis = { 1.0f, 0.0f, 0.0f };
+vector_t vectorYaxis = { 0.0f, 1.0f, 0.0f };
+vector_t vectorZaxis = { 0.0f, 0.0f, 1.0f };
+
+void VectorRotate(vector_t *rotVec, const vector_t *axisVec, float angle)
+{
+	float ux, uy, uz, vx, vy, vz, wx, wy, wz, sa, ca;
+
+	angle = U_Deg2Rad(angle);
+
+	// Rotate the point (x,y,z) around the vector (u,v,w)
+	ux = axisVec->x * rotVec->x;
+	uy = axisVec->x * rotVec->y;
+	uz = axisVec->x * rotVec->z;
+	vx = axisVec->y * rotVec->x;
+	vy = axisVec->y * rotVec->y;
+	vz = axisVec->y * rotVec->z;
+	wx = axisVec->z * rotVec->x;
+	wy = axisVec->z * rotVec->y;
+	wz = axisVec->z * rotVec->z;
+	sa = sinf(angle);
+	ca = cosf(angle);
+
+	rotVec->x = axisVec->x*(ux + vy + wz) + (rotVec->x*(axisVec->y*axisVec->y + axisVec->z*axisVec->z) - axisVec->x*(vy + wz))*ca + (-wy + vz)*sa;
+	rotVec->y = axisVec->y*(ux + vy + wz) + (rotVec->y*(axisVec->x*axisVec->x + axisVec->z*axisVec->z) - axisVec->y*(ux + wz))*ca + (wx - uz)*sa;
+	rotVec->z = axisVec->z*(ux + vy + wz) + (rotVec->z*(axisVec->x*axisVec->x + axisVec->y*axisVec->y) - axisVec->z*(ux + vy))*ca + (-vx + uy)*sa;
+}
+
+void UnloadModel(model_t *model)
+{
+	// Wouldn't it be great if C just had destructors?
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (mesh->frames)
+		{
+			int j;
+			for (j = 0; j < mesh->numFrames; j++)
+			{
+				if (mesh->frames[j].normals)
+					Z_Free(mesh->frames[j].normals);
+
+				if (mesh->frames[j].tangents)
+					Z_Free(mesh->frames[j].tangents);
+
+				if (mesh->frames[j].vertices)
+					Z_Free(mesh->frames[j].vertices);
+
+				if (mesh->frames[j].colors)
+					Z_Free(mesh->frames[j].colors);
+			}
+
+			Z_Free(mesh->frames);
+		}
+		else if (mesh->tinyframes)
+		{
+			int j;
+			for (j = 0; j < mesh->numFrames; j++)
+			{
+				if (mesh->tinyframes[j].normals)
+					Z_Free(mesh->tinyframes[j].normals);
+
+				if (mesh->tinyframes[j].tangents)
+					Z_Free(mesh->tinyframes[j].tangents);
+
+				if (mesh->tinyframes[j].vertices)
+					Z_Free(mesh->tinyframes[j].vertices);
+			}
+
+			if (mesh->indices)
+				Z_Free(mesh->indices);
+
+			Z_Free(mesh->tinyframes);
+		}
+
+		if (mesh->uvs)
+			Z_Free(mesh->uvs);
+
+		if (mesh->lightuvs)
+			Z_Free(mesh->lightuvs);
+	}
+
+	if (model->meshes)
+		Z_Free(model->meshes);
+
+	if (model->tags)
+		Z_Free(model->tags);
+
+	if (model->materials)
+		Z_Free(model->materials);
+
+	DeleteVBOs(model);
+	Z_Free(model);
+}
+
+tag_t *GetTagByName(model_t *model, char *name, int frame)
+{
+	if (frame < model->maxNumFrames)
+	{
+		tag_t *iterator = &model->tags[frame * model->numTags];
+
+		int i;
+		for (i = 0; i < model->numTags; i++)
+		{
+			if (!stricmp(iterator[i].name, name))
+				return &iterator[i];
+		}
+	}
+
+	return NULL;
+}
+
+//
+// LoadModel
+//
+// Load a model and
+// convert it to the
+// internal format.
+//
+model_t *LoadModel(const char *filename, int ztag)
+{
+	model_t *model;
+
+	// What type of file?
+	const char *extension = NULL;
+	int i;
+	for (i = (int)strlen(filename)-1; i >= 0; i--)
+	{
+		if (filename[i] != '.')
+			continue;
+
+		extension = &filename[i];
+		break;
+	}
+
+	if (!extension)
+	{
+		CONS_Printf("Model %s is lacking a file extension, unable to determine type!\n", filename);
+		return NULL;
+	}
+
+	if (!strcmp(extension, ".md3"))
+	{
+		if (!(model = MD3_LoadModel(filename, ztag, false)))
+			return NULL;
+	}
+	else if (!strcmp(extension, ".md3s")) // MD3 that will be converted in memory to use full floats
+	{
+		if (!(model = MD3_LoadModel(filename, ztag, true)))
+			return NULL;
+	}
+	else if (!strcmp(extension, ".md2"))
+	{
+		if (!(model = MD2_LoadModel(filename, ztag, false)))
+			return NULL;
+	}
+	else if (!strcmp(extension, ".md2s"))
+	{
+		if (!(model = MD2_LoadModel(filename, ztag, true)))
+			return NULL;
+	}
+	else
+	{
+		CONS_Printf("Unknown model format: %s\n", extension);
+		return NULL;
+	}
+
+	model->mdlFilename = (char*)Z_Malloc(strlen(filename)+1, ztag, 0);
+	strcpy(model->mdlFilename, filename);
+
+	Optimize(model);
+	GeneratePolygonNormals(model, ztag);
+	LoadModelSprite2(model);
+	if (!model->spr2frames)
+		LoadModelInterpolationSettings(model);
+
+	// Default material properties
+	for (i = 0 ; i < model->numMaterials; i++)
+	{
+		material_t *material = &model->materials[i];
+		material->ambient[0] = 0.7686f;
+		material->ambient[1] = 0.7686f;
+		material->ambient[2] = 0.7686f;
+		material->ambient[3] = 1.0f;
+		material->diffuse[0] = 0.5863f;
+		material->diffuse[1] = 0.5863f;
+		material->diffuse[2] = 0.5863f;
+		material->diffuse[3] = 1.0f;
+		material->specular[0] = 0.4902f;
+		material->specular[1] = 0.4902f;
+		material->specular[2] = 0.4902f;
+		material->specular[3] = 1.0f;
+		material->shininess = 25.0f;
+	}
+
+	return model;
+}
+
+void HWR_ReloadModels(void)
+{
+	size_t i;
+	INT32 s;
+
+	for (s = 0; s < MAXSKINS; s++)
+	{
+		if (md2_playermodels[s].model)
+			LoadModelSprite2(md2_playermodels[s].model);
+	}
+
+	for (i = 0; i < NUMSPRITES; i++)
+	{
+		if (md2_models[i].model)
+			LoadModelInterpolationSettings(md2_models[i].model);
+	}
+}
+
+void LoadModelInterpolationSettings(model_t *model)
+{
+	INT32 i;
+	INT32 numframes = model->meshes[0].numFrames;
+	char *framename = model->framenames;
+
+	if (!framename)
+		return;
+
+	#define GET_OFFSET \
+		memcpy(&interpolation_flag, framename + offset, 2); \
+		model->interpolate[i] = (!memcmp(interpolation_flag, MODEL_INTERPOLATION_FLAG, 2));
+
+	for (i = 0; i < numframes; i++)
+	{
+		int offset = (strlen(framename) - 4);
+		char interpolation_flag[3];
+		memset(&interpolation_flag, 0x00, 3);
+
+		// find the +i on the frame name
+		// ANIM+i00
+		// so the offset is (frame name length - 4)
+		GET_OFFSET;
+
+		// maybe the frame had three digits?
+		// ANIM+i000
+		// so the offset is (frame name length - 5)
+		if (!model->interpolate[i])
+		{
+			offset--;
+			GET_OFFSET;
+		}
+
+		framename += 16;
+	}
+
+	#undef GET_OFFSET
+}
+
+void LoadModelSprite2(model_t *model)
+{
+	INT32 i;
+	modelspr2frames_t *spr2frames = NULL;
+	INT32 numframes = model->meshes[0].numFrames;
+	char *framename = model->framenames;
+
+	if (!framename)
+		return;
+
+	for (i = 0; i < numframes; i++)
+	{
+		char prefix[6];
+		char name[5];
+		char interpolation_flag[3];
+		char framechars[4];
+		UINT8 frame = 0;
+		UINT8 spr2idx;
+		boolean interpolate = false;
+
+		memset(&prefix, 0x00, 6);
+		memset(&name, 0x00, 5);
+		memset(&interpolation_flag, 0x00, 3);
+		memset(&framechars, 0x00, 4);
+
+		if (strlen(framename) >= 9)
+		{
+			boolean super;
+			char *modelframename = framename;
+			memcpy(&prefix, modelframename, 5);
+			modelframename += 5;
+			memcpy(&name, modelframename, 4);
+			modelframename += 4;
+			// Oh look
+			memcpy(&interpolation_flag, modelframename, 2);
+			if (!memcmp(interpolation_flag, MODEL_INTERPOLATION_FLAG, 2))
+			{
+				interpolate = true;
+				modelframename += 2;
+			}
+			memcpy(&framechars, modelframename, 3);
+
+			if ((super = (!memcmp(prefix, "SUPER", 5))) || (!memcmp(prefix, "SPR2_", 5)))
+			{
+				spr2idx = 0;
+				while (spr2idx < free_spr2)
+				{
+					if (!memcmp(spr2names[spr2idx], name, 4))
+					{
+						if (!spr2frames)
+							spr2frames = (modelspr2frames_t*)Z_Calloc(sizeof(modelspr2frames_t)*NUMPLAYERSPRITES*2, PU_STATIC, NULL);
+						if (super)
+							spr2idx |= FF_SPR2SUPER;
+						if (framechars[0])
+						{
+							frame = atoi(framechars);
+							if (spr2frames[spr2idx].numframes < frame+1)
+								spr2frames[spr2idx].numframes = frame+1;
+						}
+						else
+						{
+							frame = spr2frames[spr2idx].numframes;
+							spr2frames[spr2idx].numframes++;
+						}
+						spr2frames[spr2idx].frames[frame] = i;
+						spr2frames[spr2idx].interpolate = interpolate;
+						break;
+					}
+					spr2idx++;
+				}
+			}
+		}
+
+		framename += 16;
+	}
+
+	if (model->spr2frames)
+		Z_Free(model->spr2frames);
+	model->spr2frames = spr2frames;
+}
+
+//
+// GenerateVertexNormals
+//
+// Creates a new normal for a vertex using the average of all of the polygons it belongs to.
+//
+void GenerateVertexNormals(model_t *model)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		int j;
+
+		mesh_t *mesh = &model->meshes[i];
+
+		if (!mesh->frames)
+			continue;
+
+		for (j = 0; j < mesh->numFrames; j++)
+		{
+			mdlframe_t *frame = &mesh->frames[j];
+			int memTag = PU_STATIC;
+			float *newNormals = (float*)Z_Malloc(sizeof(float)*3*mesh->numTriangles*3, memTag, 0);
+			int k;
+			float *vertPtr = frame->vertices;
+			float *oldNormals;
+
+			M_Memcpy(newNormals, frame->normals, sizeof(float)*3*mesh->numTriangles*3);
+
+/*			if (!systemSucks)
+			{
+				memTag = Z_GetTag(frame->tangents);
+				float *newTangents = (float*)Z_Malloc(sizeof(float)*3*mesh->numTriangles*3, memTag);
+				M_Memcpy(newTangents, frame->tangents, sizeof(float)*3*mesh->numTriangles*3);
+			}*/
+
+			for (k = 0; k < mesh->numVertices; k++)
+			{
+				float x, y, z;
+				int vCount = 0;
+				vector_t normal;
+				int l;
+				float *testPtr = frame->vertices;
+
+				x = *vertPtr++;
+				y = *vertPtr++;
+				z = *vertPtr++;
+
+				normal.x = normal.y = normal.z = 0;
+
+				for (l = 0; l < mesh->numVertices; l++)
+				{
+					float testX, testY, testZ;
+					testX = *testPtr++;
+					testY = *testPtr++;
+					testZ = *testPtr++;
+
+					if (fabsf(x - testX) > FLT_EPSILON
+						|| fabsf(y - testY) > FLT_EPSILON
+						|| fabsf(z - testZ) > FLT_EPSILON)
+						continue;
+
+					// Found a vertex match! Add it...
+					normal.x += frame->normals[3 * l + 0];
+					normal.y += frame->normals[3 * l + 1];
+					normal.z += frame->normals[3 * l + 2];
+					vCount++;
+				}
+
+				if (vCount > 1)
+				{
+//					Vector::Normalize(&normal);
+					newNormals[3 * k + 0] = (float)normal.x;
+					newNormals[3 * k + 1] = (float)normal.y;
+					newNormals[3 * k + 2] = (float)normal.z;
+
+/*					if (!systemSucks)
+					{
+						Vector::vector_t tangent;
+						Vector::Tangent(&normal, &tangent);
+						newTangents[3 * k + 0] = tangent.x;
+						newTangents[3 * k + 1] = tangent.y;
+						newTangents[3 * k + 2] = tangent.z;
+					}*/
+				}
+			}
+
+			oldNormals = frame->normals;
+			frame->normals = newNormals;
+			Z_Free(oldNormals);
+
+/*			if (!systemSucks)
+			{
+				float *oldTangents = frame->tangents;
+				frame->tangents = newTangents;
+				Z_Free(oldTangents);
+			}*/
+		}
+	}
+}
+
+typedef struct materiallist_s
+{
+	struct materiallist_s *next;
+	struct materiallist_s *prev;
+	material_t *material;
+} materiallist_t;
+
+static boolean AddMaterialToList(materiallist_t **head, material_t *material)
+{
+	materiallist_t *node, *newMatNode;
+	for (node = *head; node; node = node->next)
+	{
+		if (node->material == material)
+			return false;
+	}
+
+	// Didn't find it, so add to the list
+	newMatNode = (materiallist_t*)Z_Malloc(sizeof(materiallist_t), PU_CACHE, 0);
+	newMatNode->material = material;
+	ListAdd(newMatNode, (listitem_t**)head);
+	return true;
+}
+
+//
+// Optimize
+//
+// Groups triangles from meshes in the model
+// Only works for models with 1 frame
+//
+void Optimize(model_t *model)
+{
+	int numMeshes = 0;
+	int i;
+	materiallist_t *matListHead = NULL;
+	int memTag;
+	mesh_t *newMeshes;
+	materiallist_t *node;
+
+	if (model->numMeshes <= 1)
+		return; // No need
+
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *curMesh = &model->meshes[i];
+
+		if (curMesh->numFrames > 1)
+			return; // Can't optimize models with > 1 frame
+
+		if (!curMesh->frames)
+			return; // Don't optimize tinyframe models (no need)
+
+		// We are condensing to 1 mesh per material, so
+		// the # of materials we use will be the new
+		// # of meshes
+		if (AddMaterialToList(&matListHead, curMesh->frames[0].material))
+			numMeshes++;
+	}
+
+	memTag = PU_STATIC;
+	newMeshes = (mesh_t*)Z_Calloc(sizeof(mesh_t) * numMeshes, memTag, 0);
+
+	i = 0;
+	for (node = matListHead; node; node = node->next)
+	{
+		material_t *curMat = node->material;
+		mesh_t *newMesh = &newMeshes[i];
+		mdlframe_t *curFrame;
+		int uvCount;
+		int vertCount;
+		int colorCount;
+
+		// Find all triangles with this material and count them
+		int numTriangles = 0;
+		int j;
+		for (j = 0; j < model->numMeshes; j++)
+		{
+			mesh_t *curMesh = &model->meshes[j];
+
+			if (curMesh->frames[0].material == curMat)
+				numTriangles += curMesh->numTriangles;
+		}
+
+		newMesh->numFrames = 1;
+		newMesh->numTriangles = numTriangles;
+		newMesh->numVertices = numTriangles * 3;
+		newMesh->uvs = (float*)Z_Malloc(sizeof(float)*2*numTriangles*3, memTag, 0);
+//		if (node->material->lightmap)
+//			newMesh->lightuvs = (float*)Z_Malloc(sizeof(float)*2*numTriangles*3, memTag, 0);
+		newMesh->frames = (mdlframe_t*)Z_Calloc(sizeof(mdlframe_t), memTag, 0);
+		curFrame = &newMesh->frames[0];
+
+		curFrame->material = curMat;
+		curFrame->normals = (float*)Z_Malloc(sizeof(float)*3*numTriangles*3, memTag, 0);
+//		if (!systemSucks)
+//			curFrame->tangents = (float*)Z_Malloc(sizeof(float)*3*numTriangles*3, memTag, 0);
+		curFrame->vertices = (float*)Z_Malloc(sizeof(float)*3*numTriangles*3, memTag, 0);
+		curFrame->colors = (char*)Z_Malloc(sizeof(char)*4*numTriangles*3, memTag, 0);
+
+		// Now traverse the meshes of the model, adding in
+		// vertices/normals/uvs that match the current material
+		uvCount = 0;
+		vertCount = 0;
+		colorCount = 0;
+		for (j = 0; j < model->numMeshes; j++)
+		{
+			mesh_t *curMesh = &model->meshes[j];
+
+			if (curMesh->frames[0].material == curMat)
+			{
+				float *dest;
+				float *src;
+				char *destByte;
+				char *srcByte;
+
+				M_Memcpy(&newMesh->uvs[uvCount],
+					curMesh->uvs,
+					sizeof(float)*2*curMesh->numTriangles*3);
+
+/*				if (node->material->lightmap)
+				{
+					M_Memcpy(&newMesh->lightuvs[uvCount],
+						curMesh->lightuvs,
+						sizeof(float)*2*curMesh->numTriangles*3);
+				}*/
+				uvCount += 2*curMesh->numTriangles*3;
+
+				dest = (float*)newMesh->frames[0].vertices;
+				src = (float*)curMesh->frames[0].vertices;
+				M_Memcpy(&dest[vertCount],
+					src,
+					sizeof(float)*3*curMesh->numTriangles*3);
+
+				dest = (float*)newMesh->frames[0].normals;
+				src = (float*)curMesh->frames[0].normals;
+				M_Memcpy(&dest[vertCount],
+					src,
+					sizeof(float)*3*curMesh->numTriangles*3);
+
+/*				if (!systemSucks)
+				{
+					dest = (float*)newMesh->frames[0].tangents;
+					src = (float*)curMesh->frames[0].tangents;
+					M_Memcpy(&dest[vertCount],
+						src,
+						sizeof(float)*3*curMesh->numTriangles*3);
+				}*/
+
+				vertCount += 3 * curMesh->numTriangles * 3;
+
+				destByte = (char*)newMesh->frames[0].colors;
+				srcByte = (char*)curMesh->frames[0].colors;
+
+				if (srcByte)
+				{
+					M_Memcpy(&destByte[colorCount],
+						srcByte,
+						sizeof(char)*4*curMesh->numTriangles*3);
+				}
+				else
+				{
+					memset(&destByte[colorCount],
+						255,
+						sizeof(char)*4*curMesh->numTriangles*3);
+				}
+
+				colorCount += 4 * curMesh->numTriangles * 3;
+			}
+		}
+
+		i++;
+	}
+
+	CONS_Printf("Model::Optimize(): Model reduced from %d to %d meshes.\n", model->numMeshes, numMeshes);
+	model->meshes = newMeshes;
+	model->numMeshes = numMeshes;
+}
+
+void GeneratePolygonNormals(model_t *model, int ztag)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		int j;
+		mesh_t *mesh = &model->meshes[i];
+
+		if (!mesh->frames)
+			continue;
+
+		for (j = 0; j < mesh->numFrames; j++)
+		{
+			int k;
+			mdlframe_t *frame = &mesh->frames[j];
+			const float *vertices = frame->vertices;
+			vector_t *polyNormals;
+
+			frame->polyNormals = (vector_t*)Z_Malloc(sizeof(vector_t) * mesh->numTriangles, ztag, 0);
+
+			polyNormals = frame->polyNormals;
+
+			for (k = 0; k < mesh->numTriangles; k++)
+			{
+//				Vector::Normal(vertices, polyNormals);
+				vertices += 3 * 3;
+				polyNormals++;
+			}
+		}
+	}
+}
+
+//
+// Reload
+//
+// Reload VBOs
+//
+#if 0
+static void Reload(void)
+{
+/*	model_t *node;
+	for (node = modelHead; node; node = node->next)
+	{
+		int i;
+		for (i = 0; i < node->numMeshes; i++)
+		{
+			mesh_t *mesh = &node->meshes[i];
+
+			if (mesh->frames)
+			{
+				int j;
+				for (j = 0; j < mesh->numFrames; j++)
+					CreateVBO(mesh, &mesh->frames[j]);
+			}
+			else if (mesh->tinyframes)
+			{
+				int j;
+				for (j = 0; j < mesh->numFrames; j++)
+					CreateVBO(mesh, &mesh->tinyframes[j]);
+			}
+		}
+	}*/
+}
+#endif
+
+void DeleteVBOs(model_t *model)
+{
+	(void)model;
+/*	for (int i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (mesh->frames)
+		{
+			for (int j = 0; j < mesh->numFrames; j++)
+			{
+				mdlframe_t *frame = &mesh->frames[j];
+				if (!frame->vboID)
+					continue;
+				bglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+			}
+		}
+		else if (mesh->tinyframes)
+		{
+			for (int j = 0; j < mesh->numFrames; j++)
+			{
+				tinyframe_t *frame = &mesh->tinyframes[j];
+				if (!frame->vboID)
+					continue;
+				bglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+			}
+		}
+	}*/
+}
diff --git a/src/hardware/hw_model.h b/src/hardware/hw_model.h
new file mode 100644
index 0000000000000000000000000000000000000000..2a5240bdefbda6320fa2411b451a5bf86408124e
--- /dev/null
+++ b/src/hardware/hw_model.h
@@ -0,0 +1,121 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _HW_MODEL_H_
+#define _HW_MODEL_H_
+
+#include "../doomtype.h"
+
+typedef struct
+{
+	float x, y, z;
+} vector_t;
+
+extern vector_t vectorXaxis;
+extern vector_t vectorYaxis;
+extern vector_t vectorZaxis;
+
+void VectorRotate(vector_t *rotVec, const vector_t *axisVec, float angle);
+
+typedef struct
+{
+	float ambient[4], diffuse[4], specular[4], emissive[4];
+	float shininess;
+	boolean spheremap;
+//	Texture::texture_t *texture;
+//	Texture::texture_t *lightmap;
+} material_t;
+
+typedef struct
+{
+	material_t *material; // Pointer to the allocated 'materials' list in model_t
+	float *vertices;
+	float *normals;
+	float *tangents;
+	char *colors;
+	unsigned int vboID;
+	vector_t *polyNormals;
+} mdlframe_t;
+
+typedef struct
+{
+	material_t *material;
+	short *vertices;
+	char *normals;
+	char *tangents;
+	unsigned int vboID;
+} tinyframe_t;
+
+// Equivalent to MD3's many 'surfaces'
+typedef struct mesh_s
+{
+	int numVertices;
+	int numTriangles;
+
+	float *uvs;
+	float *lightuvs;
+
+	int numFrames;
+	mdlframe_t *frames;
+	tinyframe_t *tinyframes;
+	unsigned short *indices;
+} mesh_t;
+
+typedef struct tag_s
+{
+	char name[64];
+//	matrix_t transform;
+} tag_t;
+
+#define MODEL_INTERPOLATION_FLAG "+i"
+
+typedef struct
+{
+	INT32 frames[256];
+	UINT8 numframes;
+	boolean interpolate;
+} modelspr2frames_t;
+
+typedef struct model_s
+{
+	int maxNumFrames;
+
+	int numMaterials;
+	material_t *materials;
+	int numMeshes;
+	mesh_t *meshes;
+	int numTags;
+	tag_t *tags;
+
+	char *mdlFilename;
+	boolean unloaded;
+
+	char *framenames;
+	boolean interpolate[256];
+	modelspr2frames_t *spr2frames;
+} model_t;
+
+extern int numModels;
+extern model_t *modelHead;
+
+void HWR_ReloadModels(void);
+
+tag_t *GetTagByName(model_t *model, char *name, int frame);
+model_t *LoadModel(const char *filename, int ztag);
+void UnloadModel(model_t *model);
+void Optimize(model_t *model);
+void LoadModelInterpolationSettings(model_t *model);
+void LoadModelSprite2(model_t *model);
+void GenerateVertexNormals(model_t *model);
+void GeneratePolygonNormals(model_t *model, int ztag);
+void CreateVBOTiny(mesh_t *mesh, tinyframe_t *frame);
+void CreateVBO(mesh_t *mesh, mdlframe_t *frame);
+void DeleteVBOs(model_t *model);
+
+#endif
diff --git a/src/hardware/r_opengl/ogl_win.c b/src/hardware/r_opengl/ogl_win.c
index eb9a31a7d7f1e909d72e88cc5bc3c166471b73ab..562afe9989e0908f046ece44abe2fcf22d2fa439 100644
--- a/src/hardware/r_opengl/ogl_win.c
+++ b/src/hardware/r_opengl/ogl_win.c
@@ -347,13 +347,6 @@ static INT32 WINAPI SetRes(viddef_t *lvid, vmode_t *pcurrentmode)
 	if (strstr(renderer, "810"))   oglflags |= GLF_NOZBUFREAD;
 	DBG_Printf("oglflags   : 0x%X\n", oglflags);
 
-#ifdef USE_PALETTED_TEXTURE
-	if (isExtAvailable("GL_EXT_paletted_texture",gl_extensions))
-		glColorTableEXT = GetGLFunc("glColorTableEXT");
-	else
-		glColorTableEXT = NULL;
-#endif
-
 #ifdef USE_WGL_SWAP
 	if (isExtAvailable("WGL_EXT_swap_control",gl_extensions))
 		wglSwapIntervalEXT = GetGLFunc("wglSwapIntervalEXT");
@@ -582,19 +575,8 @@ EXPORT void HWRAPI(SetPalette) (RGBA_t *pal, RGBA_t *gamma)
 		myPaletteData[i].s.blue  = (UINT8)MIN((pal[i].s.blue*gamma->s.blue)/127,   255);
 		myPaletteData[i].s.alpha = pal[i].s.alpha;
 	}
-#ifdef USE_PALETTED_TEXTURE
-	if (glColorTableEXT)
-	{
-		for (i = 0; i < 256; i++)
-		{
-			palette_tex[3*i+0] = pal[i].s.red;
-			palette_tex[3*i+1] = pal[i].s.green;
-			palette_tex[3*i+2] = pal[i].s.blue;
-		}
-		glColorTableEXT(GL_TEXTURE_2D, GL_RGB8, 256, GL_RGB, GL_UNSIGNED_BYTE, palette_tex);
-	}
-#endif
-	// on a chang� de palette, il faut recharger toutes les textures
+
+	// on a palette change, you have to reload all of the textures
 	Flush();
 }
 
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index ac989613a3be866ede5596ec0f7df44ab494fa54..1a50854c7e21cb949e839c1b8fbf0b9263832074 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -29,13 +29,10 @@
 
 #include <stdarg.h>
 #include <math.h>
-#ifndef SHUFFLE
-#define SHUFFLE
-#endif
 #include "r_opengl.h"
+#include "r_vbo.h"
 
 #if defined (HWRENDER) && !defined (NOROPENGL)
-// for KOS: GL_TEXTURE_ENV, glAlphaFunc, glColorMask, glPolygonOffset, glReadPixels, GL_ALPHA_TEST, GL_POLYGON_OFFSET_FILL
 
 struct GLRGBAFloat
 {
@@ -45,6 +42,7 @@ struct GLRGBAFloat
 	GLfloat alpha;
 };
 typedef struct GLRGBAFloat GLRGBAFloat;
+static const GLubyte white[4] = { 255, 255, 255, 255 };
 
 // ==========================================================================
 //                                                                  CONSTANTS
@@ -68,8 +66,10 @@ static float NEAR_CLIPPING_PLANE =   NZCLIP_PLANE;
 static  GLuint      NextTexAvail    = FIRST_TEX_AVAIL;
 static  GLuint      tex_downloaded  = 0;
 static  GLfloat     fov             = 90.0f;
+#if 0
 static  GLuint      pal_col         = 0;
 static  FRGBAFloat  const_pal_col;
+#endif
 static  FBITFIELD   CurrentPolyFlags;
 
 static  FTextureInfo*  gr_cachetail = NULL;
@@ -90,26 +90,20 @@ static FTransform  md2_transform;
 const GLubyte *gl_extensions = NULL;
 
 //Hurdler: 04/10/2000: added for the kick ass coronas as Boris wanted;-)
-static GLdouble    modelMatrix[16];
-static GLdouble    projMatrix[16];
+static GLfloat    modelMatrix[16];
+static GLfloat    projMatrix[16];
 static GLint       viewport[4];
 
-
-#ifdef USE_PALETTED_TEXTURE
-	PFNGLCOLORTABLEEXTPROC  glColorTableEXT = NULL;
-	GLubyte                 palette_tex[256*3];
-#endif
-
 // Yay for arbitrary  numbers! NextTexAvail is buggy for some reason.
 // Sryder:	NextTexAvail is broken for these because palette changes or changes to the texture filter or antialiasing
 //			flush all of the stored textures, leaving them unavailable at times such as between levels
 //			These need to start at 0 and be set to their number, and be reset to 0 when deleted so that intel GPUs
 //			can know when the textures aren't there, as textures are always considered resident in their virtual memory
 // TODO:	Store them in a more normal way
-#define SCRTEX_SCREENTEXTURE 65535
-#define SCRTEX_STARTSCREENWIPE 65534
-#define SCRTEX_ENDSCREENWIPE 65533
-#define SCRTEX_FINALSCREENTEXTURE 65532
+#define SCRTEX_SCREENTEXTURE 4294967295U
+#define SCRTEX_STARTSCREENWIPE 4294967294U
+#define SCRTEX_ENDSCREENWIPE 4294967293U
+#define SCRTEX_FINALSCREENTEXTURE 4294967292U
 static GLuint screentexture = 0;
 static GLuint startScreenWipe = 0;
 static GLuint endScreenWipe = 0;
@@ -161,9 +155,6 @@ float byteasfloat(UINT8 fbyte)
 
 static I_Error_t I_Error_GL = NULL;
 
-static boolean gl13 = false; // whether we can use opengl 1.3 functions
-
-
 // -----------------+
 // DBG_Printf       : Output error messages to debug log if DEBUG_TO_FILE is defined,
 //                  : else do nothing
@@ -194,14 +185,14 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 #define pglAlphaFunc glAlphaFunc
 #define pglBlendFunc glBlendFunc
 #define pglCullFace glCullFace
-#define pglPolygonMode glPolygonMode
 #define pglPolygonOffset glPolygonOffset
 #define pglScissor glScissor
 #define pglEnable glEnable
 #define pglDisable glDisable
-#define pglGetDoublev glGetDoublev
+#define pglGetFloatv glGetFloatv
 //glGetIntegerv
 //glGetString
+#define pglHint glHint
 
 /* Depth Buffer */
 #define pglClearDepth glClearDepth
@@ -215,19 +206,26 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 #define pglPushMatrix glPushMatrix
 #define pglPopMatrix glPopMatrix
 #define pglLoadIdentity glLoadIdentity
-#define pglMultMatrixd glMultMatrixd
+#define pglMultMatrixf glMultMatrixf
 #define pglRotatef glRotatef
 #define pglScalef glScalef
 #define pglTranslatef glTranslatef
 
 /* Drawing Functions */
-#define pglBegin glBegin
-#define pglEnd glEnd
-#define pglVertex3f glVertex3f
-#define pglNormal3f glNormal3f
-#define pglColor4f glColor4f
-#define pglColor4fv glColor4fv
-#define pglTexCoord2f glTexCoord2f
+#define pglColor4ubv glColor4ubv
+#define pglVertexPointer glVertexPointer
+#define pglNormalPointer glNormalPointer
+#define pglTexCoordPointer glTexCoordPointer
+#define pglColorPointer glColorPointer
+#define pglDrawArrays glDrawArrays
+#define pglDrawElements glDrawElements
+#define pglEnableClientState glEnableClientState
+#define pglDisableClientState glDisableClientState
+#define pglClientActiveTexture glClientActiveTexture
+#define pglGenBuffers glGenBuffers
+#define pglBindBuffer glBindBuffer
+#define pglBufferData glBufferData
+#define pglDeleteBuffers glDeleteBuffers
 
 /* Lighting */
 #define pglShadeModel glShadeModel
@@ -271,8 +269,6 @@ typedef void (APIENTRY * PFNglBlendFunc) (GLenum sfactor, GLenum dfactor);
 static PFNglBlendFunc pglBlendFunc;
 typedef void (APIENTRY * PFNglCullFace) (GLenum mode);
 static PFNglCullFace pglCullFace;
-typedef void (APIENTRY * PFNglPolygonMode) (GLenum face, GLenum mode);
-static PFNglPolygonMode pglPolygonMode;
 typedef void (APIENTRY * PFNglPolygonOffset) (GLfloat factor, GLfloat units);
 static PFNglPolygonOffset pglPolygonOffset;
 typedef void (APIENTRY * PFNglScissor) (GLint x, GLint y, GLsizei width, GLsizei height);
@@ -281,8 +277,8 @@ typedef void (APIENTRY * PFNglEnable) (GLenum cap);
 static PFNglEnable pglEnable;
 typedef void (APIENTRY * PFNglDisable) (GLenum cap);
 static PFNglDisable pglDisable;
-typedef void (APIENTRY * PFNglGetDoublev) (GLenum pname, GLdouble *params);
-static PFNglGetDoublev pglGetDoublev;
+typedef void (APIENTRY * PFNglGetFloatv) (GLenum pname, GLfloat *params);
+static PFNglGetFloatv pglGetFloatv;
 //glGetIntegerv
 //glGetString
 
@@ -307,8 +303,8 @@ typedef void (APIENTRY * PFNglPopMatrix) (void);
 static PFNglPopMatrix pglPopMatrix;
 typedef void (APIENTRY * PFNglLoadIdentity) (void);
 static PFNglLoadIdentity pglLoadIdentity;
-typedef void (APIENTRY * PFNglMultMatrixd) (const GLdouble *m);
-static PFNglMultMatrixd pglMultMatrixd;
+typedef void (APIENTRY * PFNglMultMatrixf) (const GLfloat *m);
+static PFNglMultMatrixf pglMultMatrixf;
 typedef void (APIENTRY * PFNglRotatef) (GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
 static PFNglRotatef pglRotatef;
 typedef void (APIENTRY * PFNglScalef) (GLfloat x, GLfloat y, GLfloat z);
@@ -317,20 +313,33 @@ typedef void (APIENTRY * PFNglTranslatef) (GLfloat x, GLfloat y, GLfloat z);
 static PFNglTranslatef pglTranslatef;
 
 /* Drawing Functions */
-typedef void (APIENTRY * PFNglBegin) (GLenum mode);
-static PFNglBegin pglBegin;
-typedef void (APIENTRY * PFNglEnd) (void);
-static PFNglEnd pglEnd;
-typedef void (APIENTRY * PFNglVertex3f) (GLfloat x, GLfloat y, GLfloat z);
-static PFNglVertex3f pglVertex3f;
-typedef void (APIENTRY * PFNglNormal3f) (GLfloat x, GLfloat y, GLfloat z);
-static PFNglNormal3f pglNormal3f;
-typedef void (APIENTRY * PFNglColor4f) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
-static PFNglColor4f pglColor4f;
-typedef void (APIENTRY * PFNglColor4fv) (const GLfloat *v);
-static PFNglColor4fv pglColor4fv;
-typedef void (APIENTRY * PFNglTexCoord2f) (GLfloat s, GLfloat t);
-static PFNglTexCoord2f pglTexCoord2f;
+typedef void (APIENTRY * PFNglColor4ubv) (const GLubyte *v);
+static PFNglColor4ubv pglColor4ubv;
+typedef void (APIENTRY * PFNglVertexPointer) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglVertexPointer pglVertexPointer;
+typedef void (APIENTRY * PFNglNormalPointer) (GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglNormalPointer pglNormalPointer;
+typedef void (APIENTRY * PFNglTexCoordPointer) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglTexCoordPointer pglTexCoordPointer;
+typedef void (APIENTRY * PFNglColorPointer) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglColorPointer pglColorPointer;
+typedef void (APIENTRY * PFNglDrawArrays) (GLenum mode, GLint first, GLsizei count);
+static PFNglDrawArrays pglDrawArrays;
+typedef void (APIENTRY * PFNglDrawElements) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
+static PFNglDrawElements pglDrawElements;
+typedef void (APIENTRY * PFNglEnableClientState) (GLenum cap);
+static PFNglEnableClientState pglEnableClientState;
+typedef void (APIENTRY * PFNglDisableClientState) (GLenum cap);
+static PFNglDisableClientState pglDisableClientState;
+typedef void (APIENTRY * PFNglGenBuffers) (GLsizei n, GLuint *buffers);
+static PFNglGenBuffers pglGenBuffers;
+typedef void (APIENTRY * PFNglBindBuffer) (GLenum target, GLuint buffer);
+static PFNglBindBuffer pglBindBuffer;
+typedef void (APIENTRY * PFNglBufferData) (GLenum target, GLsizei size, const GLvoid *data, GLenum usage);
+static PFNglBufferData pglBufferData;
+typedef void (APIENTRY * PFNglDeleteBuffers) (GLsizei n, const GLuint *buffers);
+static PFNglDeleteBuffers pglDeleteBuffers;
+
 
 /* Lighting */
 typedef void (APIENTRY * PFNglShadeModel) (GLenum mode);
@@ -383,6 +392,10 @@ typedef void (APIENTRY *PFNglActiveTexture) (GLenum);
 static PFNglActiveTexture pglActiveTexture;
 typedef void (APIENTRY *PFNglMultiTexCoord2f) (GLenum, GLfloat, GLfloat);
 static PFNglMultiTexCoord2f pglMultiTexCoord2f;
+typedef void (APIENTRY *PFNglMultiTexCoord2fv) (GLenum target, const GLfloat *v);
+static PFNglMultiTexCoord2fv pglMultiTexCoord2fv;
+typedef void (APIENTRY *PFNglClientActiveTexture) (GLenum);
+static PFNglClientActiveTexture pglClientActiveTexture;
 
 /* 1.2 Parms */
 /* GL_CLAMP_TO_EDGE_EXT */
@@ -416,19 +429,18 @@ boolean SetupGLfunc(void)
 
 	GETOPENGLFUNC(pglClearColor, glClearColor)
 
-	GETOPENGLFUNC(pglClear , glClear)
-	GETOPENGLFUNC(pglColorMask , glColorMask)
-	GETOPENGLFUNC(pglAlphaFunc , glAlphaFunc)
-	GETOPENGLFUNC(pglBlendFunc , glBlendFunc)
-	GETOPENGLFUNC(pglCullFace , glCullFace)
-	GETOPENGLFUNC(pglPolygonMode , glPolygonMode)
-	GETOPENGLFUNC(pglPolygonOffset , glPolygonOffset)
-	GETOPENGLFUNC(pglScissor , glScissor)
-	GETOPENGLFUNC(pglEnable , glEnable)
-	GETOPENGLFUNC(pglDisable , glDisable)
-	GETOPENGLFUNC(pglGetDoublev , glGetDoublev)
-	GETOPENGLFUNC(pglGetIntegerv , glGetIntegerv)
-	GETOPENGLFUNC(pglGetString , glGetString)
+	GETOPENGLFUNC(pglClear, glClear)
+	GETOPENGLFUNC(pglColorMask, glColorMask)
+	GETOPENGLFUNC(pglAlphaFunc, glAlphaFunc)
+	GETOPENGLFUNC(pglBlendFunc, glBlendFunc)
+	GETOPENGLFUNC(pglCullFace, glCullFace)
+	GETOPENGLFUNC(pglPolygonOffset, glPolygonOffset)
+	GETOPENGLFUNC(pglScissor, glScissor)
+	GETOPENGLFUNC(pglEnable, glEnable)
+	GETOPENGLFUNC(pglDisable, glDisable)
+	GETOPENGLFUNC(pglGetFloatv, glGetFloatv)
+	GETOPENGLFUNC(pglGetIntegerv, glGetIntegerv)
+	GETOPENGLFUNC(pglGetString, glGetString)
 
 	GETOPENGLFUNC(pglClearDepth , glClearDepth)
 	GETOPENGLFUNC(pglDepthFunc , glDepthFunc)
@@ -440,18 +452,20 @@ boolean SetupGLfunc(void)
 	GETOPENGLFUNC(pglPushMatrix , glPushMatrix)
 	GETOPENGLFUNC(pglPopMatrix , glPopMatrix)
 	GETOPENGLFUNC(pglLoadIdentity , glLoadIdentity)
-	GETOPENGLFUNC(pglMultMatrixd , glMultMatrixd)
+	GETOPENGLFUNC(pglMultMatrixf , glMultMatrixf)
 	GETOPENGLFUNC(pglRotatef , glRotatef)
 	GETOPENGLFUNC(pglScalef , glScalef)
 	GETOPENGLFUNC(pglTranslatef , glTranslatef)
 
-	GETOPENGLFUNC(pglBegin , glBegin)
-	GETOPENGLFUNC(pglEnd , glEnd)
-	GETOPENGLFUNC(pglVertex3f , glVertex3f)
-	GETOPENGLFUNC(pglNormal3f , glNormal3f)
-	GETOPENGLFUNC(pglColor4f , glColor4f)
-	GETOPENGLFUNC(pglColor4fv , glColor4fv)
-	GETOPENGLFUNC(pglTexCoord2f , glTexCoord2f)
+	GETOPENGLFUNC(pglColor4ubv, glColor4ubv)
+	GETOPENGLFUNC(pglVertexPointer, glVertexPointer)
+	GETOPENGLFUNC(pglNormalPointer, glNormalPointer)
+	GETOPENGLFUNC(pglTexCoordPointer, glTexCoordPointer)
+	GETOPENGLFUNC(pglColorPointer, glColorPointer)
+	GETOPENGLFUNC(pglDrawArrays, glDrawArrays)
+	GETOPENGLFUNC(pglDrawElements, glDrawElements)
+	GETOPENGLFUNC(pglEnableClientState, glEnableClientState)
+	GETOPENGLFUNC(pglDisableClientState, glDisableClientState)
 
 	GETOPENGLFUNC(pglShadeModel , glShadeModel)
 	GETOPENGLFUNC(pglLightfv, glLightfv)
@@ -483,42 +497,18 @@ boolean SetupGLfunc(void)
 }
 
 // This has to be done after the context is created so the version number can be obtained
+// This is stupid -- even some of the oldest usable OpenGL hardware today supports 1.3-level featureset.
 boolean SetupGLFunc13(void)
 {
-	const GLubyte *version = pglGetString(GL_VERSION);
-	int glmajor, glminor;
-
-	gl13 = false;
-	// Parse the GL version
-	if (version != NULL)
-	{
-		if (sscanf((const char*)version, "%d.%d", &glmajor, &glminor) == 2)
-		{
-			// Look, we gotta prepare for the inevitable arrival of GL 2.0 code...
-			if (glmajor == 1 && glminor >= 3)
-				gl13 = true;
-			else if (glmajor > 1)
-				gl13 = true;
-		}
-	}
-
-	if (gl13)
-	{
-		pglActiveTexture = GetGLFunc("glActiveTexture");
-		pglMultiTexCoord2f = GetGLFunc("glMultiTexCoord2f");
-	}
-	else if (isExtAvailable("GL_ARB_multitexture", gl_extensions))
-	{
-		// Get the functions
-		pglActiveTexture  = GetGLFunc("glActiveTextureARB");
-		pglMultiTexCoord2f  = GetGLFunc("glMultiTexCoord2fARB");
+	pglActiveTexture = GetGLFunc("glActiveTexture");
+	pglMultiTexCoord2f = GetGLFunc("glMultiTexCoord2f");
+	pglClientActiveTexture = GetGLFunc("glClientActiveTexture");
+	pglMultiTexCoord2fv = GetGLFunc("glMultiTexCoord2fv");
+	pglGenBuffers = GetGLFunc("glGenBuffers");
+	pglBindBuffer = GetGLFunc("glBindBuffer");
+	pglBufferData = GetGLFunc("glBufferData");
+	pglDeleteBuffers = GetGLFunc("glDeleteBuffers");
 
-		gl13 = true; // This is now true, so the new fade mask stuff can be done, if OpenGL version is less than 1.3, it still uses the old fade stuff.
-		DBG_Printf("GL_ARB_multitexture support: enabled\n");
-
-	}
-	else
-		DBG_Printf("GL_ARB_multitexture support: disabled\n");
 	return true;
 }
 
@@ -535,39 +525,40 @@ static void SetNoTexture(void)
 	}
 }
 
-static void GLPerspective(GLdouble fovy, GLdouble aspect)
+static void GLPerspective(GLfloat fovy, GLfloat aspect)
 {
-	GLdouble m[4][4] =
+	GLfloat m[4][4] =
 	{
 		{ 1.0f, 0.0f, 0.0f, 0.0f},
 		{ 0.0f, 1.0f, 0.0f, 0.0f},
 		{ 0.0f, 0.0f, 1.0f,-1.0f},
 		{ 0.0f, 0.0f, 0.0f, 0.0f},
 	};
-	const GLdouble zNear = NEAR_CLIPPING_PLANE;
-	const GLdouble zFar = FAR_CLIPPING_PLANE;
-	const GLdouble radians = (GLdouble)(fovy / 2.0f * M_PIl / 180.0f);
-	const GLdouble sine = sin(radians);
-	const GLdouble deltaZ = zFar - zNear;
-	GLdouble cotangent;
+	const GLfloat zNear = NEAR_CLIPPING_PLANE;
+	const GLfloat zFar = FAR_CLIPPING_PLANE;
+	const GLfloat radians = (GLfloat)(fovy / 2.0f * M_PIl / 180.0f);
+	const GLfloat sine = sinf(radians);
+	const GLfloat deltaZ = zFar - zNear;
+	GLfloat cotangent;
 
 	if ((fabsf((float)deltaZ) < 1.0E-36f) || fpclassify(sine) == FP_ZERO || fpclassify(aspect) == FP_ZERO)
 	{
 		return;
 	}
-	cotangent = cos(radians) / sine;
+	cotangent = cosf(radians) / sine;
 
 	m[0][0] = cotangent / aspect;
 	m[1][1] = cotangent;
 	m[2][2] = -(zFar + zNear) / deltaZ;
 	m[3][2] = -2.0f * zNear * zFar / deltaZ;
-	pglMultMatrixd(&m[0][0]);
+
+	pglMultMatrixf(&m[0][0]);
 }
 
-static void GLProject(GLdouble objX, GLdouble objY, GLdouble objZ,
-                      GLdouble* winX, GLdouble* winY, GLdouble* winZ)
+static void GLProject(GLfloat objX, GLfloat objY, GLfloat objZ,
+                      GLfloat* winX, GLfloat* winY, GLfloat* winZ)
 {
-	GLdouble in[4], out[4];
+	GLfloat in[4], out[4];
 	int i;
 
 	for (i=0; i<4; i++)
@@ -634,7 +625,7 @@ void SetModelView(GLint w, GLint h)
 
 	// added for new coronas' code (without depth buffer)
 	pglGetIntegerv(GL_VIEWPORT, viewport);
-	pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
+	pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix);
 }
 
 
@@ -659,9 +650,11 @@ void SetStates(void)
 	//pglShadeModel(GL_FLAT);
 
 	pglEnable(GL_TEXTURE_2D);      // two-dimensional texturing
+
 	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
 	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+
 	//pglBlendFunc(GL_ONE, GL_ZERO); // copy pixel to frame buffer (opaque)
 	pglEnable(GL_BLEND);           // enable color blending
 
@@ -694,8 +687,6 @@ void SetStates(void)
 
 	//pglEnable(GL_CULL_FACE);
 	//pglCullFace(GL_FRONT);
-	//pglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-	//pglPolygonMode(GL_FRONT, GL_LINE);
 
 	//glFogi(GL_FOG_MODE, GL_EXP);
 	//pglHint(GL_FOG_HINT, GL_FASTEST);
@@ -711,7 +702,7 @@ void SetStates(void)
 	// bp : when no t&l :)
 	pglLoadIdentity();
 	pglScalef(1.0f, 1.0f, -1.0f);
-	pglGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
+	pglGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
 }
 
 
@@ -877,7 +868,7 @@ EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, f
 
 	// added for new coronas' code (without depth buffer)
 	pglGetIntegerv(GL_VIEWPORT, viewport);
-	pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
+	pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix);
 }
 
 
@@ -911,6 +902,8 @@ EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask,
 	SetBlend(DepthMask ? PF_Occlude | CurrentPolyFlags : CurrentPolyFlags&~PF_Occlude);
 
 	pglClear(ClearMask);
+	pglEnableClientState(GL_VERTEX_ARRAY); // We always use this one
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY); // And mostly this one, too
 }
 
 
@@ -921,26 +914,35 @@ EXPORT void HWRAPI(Draw2DLine) (F2DCoord * v1,
                                    F2DCoord * v2,
                                    RGBA_t Color)
 {
-	GLRGBAFloat c;
-
 	// DBG_Printf ("DrawLine() (%f %f %f) %d\n", v1->x, -v1->y, -v1->z, v1->argb);
+	GLfloat p[12];
+	GLfloat dx, dy;
+	GLfloat angle;
 
 	// BP: we should reflect the new state in our variable
 	//SetBlend(PF_Modulated|PF_NoTexture);
 
 	pglDisable(GL_TEXTURE_2D);
 
-	c.red   = byte2float[Color.s.red];
-	c.green = byte2float[Color.s.green];
-	c.blue  = byte2float[Color.s.blue];
-	c.alpha = byte2float[Color.s.alpha];
+	// This is the preferred, 'modern' way of rendering lines -- creating a polygon.
+	if (fabsf(v2->x - v1->x) > FLT_EPSILON)
+		angle = (float)atan((v2->y-v1->y)/(v2->x-v1->x));
+	else
+		angle = (float)N_PI_DEMI;
+	dx = (float)sin(angle) / (float)screen_width;
+	dy = (float)cos(angle) / (float)screen_height;
+
+	p[0] = v1->x - dx;  p[1] = -(v1->y + dy); p[2] = 1;
+	p[3] = v2->x - dx;  p[4] = -(v2->y + dy); p[5] = 1;
+	p[6] = v2->x + dx;  p[7] = -(v2->y - dy); p[8] = 1;
+	p[9] = v1->x + dx;  p[10] = -(v1->y - dy); p[11] = 1;
 
-	pglColor4fv(&c.red);    // is in RGBA float format
-	pglBegin(GL_LINES);
-		pglVertex3f(v1->x, -v1->y, 1.0f);
-		pglVertex3f(v2->x, -v2->y, 1.0f);
-	pglEnd();
+	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+	pglColor4ubv((GLubyte*)&Color.s);
+	pglVertexPointer(3, GL_FLOAT, 0, p);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
 	pglEnable(GL_TEXTURE_2D);
 }
 
@@ -985,7 +987,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 					break;
 				case PF_Substractive & PF_Blending:
 					// good for shadow
-					// not realy but what else ?
+					// not really but what else ?
 					pglBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
 					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
@@ -1050,7 +1052,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 			if (oglflags & GLF_NOTEXENV)
 			{
 				if (!(PolyFlags & PF_Modulated))
-					pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+					pglColor4ubv(white);
 			}
 			else
 #endif
@@ -1125,15 +1127,6 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 		w = pTexInfo->width;
 		h = pTexInfo->height;
 
-#ifdef USE_PALETTED_TEXTURE
-		if (glColorTableEXT &&
-			(pTexInfo->grInfo.format == GR_TEXFMT_P_8) &&
-			!(pTexInfo->flags & TF_CHROMAKEYED))
-		{
-			// do nothing here.
-		}
-		else
-#endif
 		if ((pTexInfo->grInfo.format == GR_TEXFMT_P_8) ||
 			(pTexInfo->grInfo.format == GR_TEXFMT_AP_88))
 		{
@@ -1233,17 +1226,6 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
 		}
 
-#ifdef USE_PALETTED_TEXTURE
-			//Hurdler: not really supported and not tested recently
-		if (glColorTableEXT &&
-			(pTexInfo->grInfo.format == GR_TEXFMT_P_8) &&
-			!(pTexInfo->flags & TF_CHROMAKEYED))
-		{
-			glColorTableEXT(GL_TEXTURE_2D, GL_RGB8, 256, GL_RGB, GL_UNSIGNED_BYTE, palette_tex);
-			pglTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, w, h, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, pTexInfo->grInfo.data);
-		}
-		else
-#endif
 		if (pTexInfo->grInfo.format == GR_TEXFMT_ALPHA_INTENSITY_88)
 		{
 			//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
@@ -1328,7 +1310,6 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 {
 	FUINT i;
 	FUINT j;
-	GLRGBAFloat c = {0,0,0,0};
 
 	if ((PolyFlags & PF_Corona) && (oglflags & GLF_NOZBUFREAD))
 		PolyFlags &= ~(PF_NoDepthTest|PF_Corona);
@@ -1337,24 +1318,7 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 
 	// If Modulated, mix the surface colour to the texture
 	if ((CurrentPolyFlags & PF_Modulated) && pSurf)
-	{
-		if (pal_col)
-		{ // hack for non-palettized mode
-			c.red   = (const_pal_col.red  +byte2float[pSurf->FlatColor.s.red])  /2.0f;
-			c.green = (const_pal_col.green+byte2float[pSurf->FlatColor.s.green])/2.0f;
-			c.blue  = (const_pal_col.blue +byte2float[pSurf->FlatColor.s.blue]) /2.0f;
-			c.alpha = byte2float[pSurf->FlatColor.s.alpha];
-		}
-		else
-		{
-			c.red   = byte2float[pSurf->FlatColor.s.red];
-			c.green = byte2float[pSurf->FlatColor.s.green];
-			c.blue  = byte2float[pSurf->FlatColor.s.blue];
-			c.alpha = byte2float[pSurf->FlatColor.s.alpha];
-		}
-
-		pglColor4fv(&c.red);    // is in RGBA float format
-	}
+		pglColor4ubv((GLubyte*)&pSurf->FlatColor.s);
 
 	// this test is added for new coronas' code (without depth buffer)
 	// I think I should do a separate function for drawing coronas, so it will be a little faster
@@ -1362,10 +1326,14 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 	{
 		//rem: all 8 (or 8.0f) values are hard coded: it can be changed to a higher value
 		GLfloat     buf[8][8];
-		GLdouble    cx, cy, cz;
-		GLdouble    px = 0.0f, py = 0.0f, pz = -1.0f;
+		GLfloat    cx, cy, cz;
+		GLfloat    px = 0.0f, py = 0.0f, pz = -1.0f;
 		GLfloat     scalef = 0.0f;
 
+		GLubyte c[4];
+
+		float alpha;
+
 		cx = (pOutVerts[0].x + pOutVerts[2].x) / 2.0f; // we should change the coronas' ...
 		cy = (pOutVerts[0].y + pOutVerts[2].y) / 2.0f; // ... code so its only done once.
 		cz = pOutVerts[0].z;
@@ -1401,21 +1369,20 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 		if (scalef < 0.05f)
 			return;
 
-		c.alpha *= scalef; // change the alpha value (it seems better than changing the size of the corona)
-		pglColor4fv(&c.red);
-	}
-	if (PolyFlags & PF_MD2)
-		return;
+		// GLubyte c[4];
+		c[0] = pSurf->FlatColor.s.red;
+		c[1] = pSurf->FlatColor.s.green;
+		c[2] = pSurf->FlatColor.s.blue;
 
-	pglBegin(GL_TRIANGLE_FAN);
-	for (i = 0; i < iNumPts; i++)
-	{
-		pglTexCoord2f(pOutVerts[i].sow, pOutVerts[i].tow);
-		//Hurdler: test code: -pOutVerts[i].z => pOutVerts[i].z
-		pglVertex3f(pOutVerts[i].x, pOutVerts[i].y, pOutVerts[i].z);
-		//pglVertex3f(pOutVerts[i].x, pOutVerts[i].y, -pOutVerts[i].z);
+		alpha = byte2float[pSurf->FlatColor.s.alpha];
+		alpha *= scalef; // change the alpha value (it seems better than changing the size of the corona)
+		c[3] = (unsigned char)(alpha * 255);
+		pglColor4ubv(c);
 	}
-	pglEnd();
+
+	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].sow);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, iNumPts);
 
 	if (PolyFlags & PF_RemoveYWrap)
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
@@ -1444,13 +1411,20 @@ typedef struct
 
 typedef struct
 {
-	int id;
+	unsigned int id;
 	int rows, columns;
 	int loopcount;
 	GLSkyLoopDef *loops;
 	vbo_vertex_t *data;
 } GLSkyVBO;
 
+static const boolean gl_ext_arb_vertex_buffer_object = true;
+
+#define NULL_VBO_VERTEX ((vbo_vertex_t*)NULL)
+#define sky_vbo_x (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->x : &vbo->data[0].x)
+#define sky_vbo_u (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->u : &vbo->data[0].u)
+#define sky_vbo_r (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->r : &vbo->data[0].r)
+
 // The texture offset to be applied to the texture coordinates in SkyVertex().
 static int rows, columns;
 static boolean yflip;
@@ -1587,20 +1561,55 @@ static void gld_BuildSky(int row_count, int col_count)
 static void RenderDome(INT32 skytexture)
 {
 	int i, j;
+	int vbosize;
 	GLSkyVBO *vbo = &sky_vbo;
 
-	pglRotatef(270.0f, 0.0f, 1.0f, 0.0f);
-
 	rows = 4;
 	columns = 4 * gl_sky_detail;
 
+	vbosize = 2 * rows * (columns * 2 + 2) + columns * 2;
+
+	// Build the sky dome! Yes!
 	if (lasttex != skytexture)
 	{
+		// delete VBO when already exists
+		if (gl_ext_arb_vertex_buffer_object)
+		{
+			if (vbo->id)
+				pglDeleteBuffers(1, &vbo->id);
+		}
+
 		lasttex = skytexture;
 		gld_BuildSky(rows, columns);
+
+		if (gl_ext_arb_vertex_buffer_object)
+		{
+			// generate a new VBO and get the associated ID
+			pglGenBuffers(1, &vbo->id);
+
+			// bind VBO in order to use
+			pglBindBuffer(GL_ARRAY_BUFFER, vbo->id);
+
+			// upload data to VBO
+			pglBufferData(GL_ARRAY_BUFFER, vbosize * sizeof(vbo->data[0]), vbo->data, GL_STATIC_DRAW);
+		}
 	}
 
+	// bind VBO in order to use
+	if (gl_ext_arb_vertex_buffer_object)
+		pglBindBuffer(GL_ARRAY_BUFFER, vbo->id);
+
+	// activate and specify pointers to arrays
+	pglVertexPointer(3, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_u);
+	pglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vbo->data[0]), sky_vbo_r);
+
+	// activate color arrays
+	pglEnableClientState(GL_COLOR_ARRAY);
+
+	// set transforms
 	pglScalef(1.0f, (float)texh / 230.0f, 1.0f);
+	pglRotatef(270.0f, 0.0f, 1.0f, 0.0f);
 
 	for (j = 0; j < 2; j++)
 	{
@@ -1610,25 +1619,20 @@ static void RenderDome(INT32 skytexture)
 
 			if (j == 0 ? loop->use_texture : !loop->use_texture)
 				continue;
-			else
-			{
-				int k;
-				pglBegin(loop->mode);
-				for (k = loop->vertexindex; k < (loop->vertexindex + loop->vertexcount); k++)
-				{
-					vbo_vertex_t *v = &vbo->data[k];
-					if (loop->use_texture)
-						pglTexCoord2f(v->u, v->v);
-					pglColor4f(v->r, v->g, v->b, v->a);
-					pglVertex3f(v->x, v->y, v->z);
-				}
-				pglEnd();
-			}
+
+			pglDrawArrays(loop->mode, loop->vertexindex, loop->vertexcount);
 		}
 	}
 
 	pglScalef(1.0f, 1.0f, 1.0f);
-	pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+	pglColor4ubv(white);
+
+	// bind with 0, so, switch back to normal pointer operation
+	if (gl_ext_arb_vertex_buffer_object)
+		pglBindBuffer(GL_ARRAY_BUFFER, 0);
+
+	// deactivate color array
+	pglDisableClientState(GL_COLOR_ARRAY);
 }
 
 EXPORT void HWRAPI(RenderSkyDome) (INT32 tex, INT32 texture_width, INT32 texture_height, FTransform transform)
@@ -1659,15 +1663,6 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 		}
 #endif
 
-		case HWD_SET_PALETTECOLOR:
-		{
-			pal_col = Value;
-			const_pal_col.blue  = byte2float[((Value>>16)&0xff)];
-			const_pal_col.green = byte2float[((Value>>8)&0xff)];
-			const_pal_col.red   = byte2float[((Value)&0xff)];
-			break;
-		}
-
 		case HWD_SET_FOG_COLOR:
 		{
 			GLfloat fogcolor[4];
@@ -1709,13 +1704,6 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 				pglDisable(GL_FOG);
 			break;
 
-		case HWD_SET_POLYGON_SMOOTH:
-			if (Value)
-				pglEnable(GL_POLYGON_SMOOTH);
-			else
-				pglDisable(GL_POLYGON_SMOOTH);
-			break;
-
 		case HWD_SET_TEXTUREFILTERMODE:
 			switch (Value)
 			{
@@ -1770,19 +1758,227 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 	}
 }
 
-static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration, INT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
+static float *vertBuffer = NULL;
+static float *normBuffer = NULL;
+static size_t lerpBufferSize = 0;
+static short *vertTinyBuffer = NULL;
+static char *normTinyBuffer = NULL;
+static size_t lerpTinyBufferSize = 0;
+
+// Static temporary buffer for doing frame interpolation
+// 'size' is the vertex size
+static void AllocLerpBuffer(size_t size)
+{
+	if (lerpBufferSize >= size)
+		return;
+
+	if (vertBuffer != NULL)
+		free(vertBuffer);
+
+	if (normBuffer != NULL)
+		free(normBuffer);
+
+	lerpBufferSize = size;
+	vertBuffer = malloc(lerpBufferSize);
+	normBuffer = malloc(lerpBufferSize);
+}
+
+// Static temporary buffer for doing frame interpolation
+// 'size' is the vertex size
+static void AllocLerpTinyBuffer(size_t size)
+{
+	if (lerpTinyBufferSize >= size)
+		return;
+
+	if (vertTinyBuffer != NULL)
+		free(vertTinyBuffer);
+
+	if (normTinyBuffer != NULL)
+		free(normTinyBuffer);
+
+	lerpTinyBufferSize = size;
+	vertTinyBuffer = malloc(lerpTinyBufferSize);
+	normTinyBuffer = malloc(lerpTinyBufferSize / 2);
+}
+
+#ifndef GL_STATIC_DRAW
+#define GL_STATIC_DRAW 0x88E4
+#endif
+
+#ifndef GL_ARRAY_BUFFER
+#define GL_ARRAY_BUFFER 0x8892
+#endif
+
+static void CreateModelVBO(mesh_t *mesh, mdlframe_t *frame)
+{
+	int bufferSize = sizeof(vbo64_t)*mesh->numTriangles * 3;
+	vbo64_t *buffer = (vbo64_t*)malloc(bufferSize);
+	vbo64_t *bufPtr = buffer;
+
+	float *vertPtr = frame->vertices;
+	float *normPtr = frame->normals;
+	float *tanPtr = frame->tangents;
+	float *uvPtr = mesh->uvs;
+	float *lightPtr = mesh->lightuvs;
+	char *colorPtr = frame->colors;
+
+	int i;
+	for (i = 0; i < mesh->numTriangles * 3; i++)
+	{
+		bufPtr->x = *vertPtr++;
+		bufPtr->y = *vertPtr++;
+		bufPtr->z = *vertPtr++;
+
+		bufPtr->nx = *normPtr++;
+		bufPtr->ny = *normPtr++;
+		bufPtr->nz = *normPtr++;
+
+		bufPtr->s0 = *uvPtr++;
+		bufPtr->t0 = *uvPtr++;
+
+		if (tanPtr != NULL)
+		{
+			bufPtr->tan0 = *tanPtr++;
+			bufPtr->tan1 = *tanPtr++;
+			bufPtr->tan2 = *tanPtr++;
+		}
+
+		if (lightPtr != NULL)
+		{
+			bufPtr->s1 = *lightPtr++;
+			bufPtr->t1 = *lightPtr++;
+		}
+
+		if (colorPtr)
+		{
+			bufPtr->r = *colorPtr++;
+			bufPtr->g = *colorPtr++;
+			bufPtr->b = *colorPtr++;
+			bufPtr->a = *colorPtr++;
+		}
+		else
+		{
+			bufPtr->r = 255;
+			bufPtr->g = 255;
+			bufPtr->b = 255;
+			bufPtr->a = 255;
+		}
+
+		bufPtr++;
+	}
+
+	pglGenBuffers(1, &frame->vboID);
+	pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+	pglBufferData(GL_ARRAY_BUFFER, bufferSize, buffer, GL_STATIC_DRAW);
+	free(buffer);
+
+	// Don't leave the array buffer bound to the model,
+	// since this is called mid-frame
+	pglBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+static void CreateModelVBOTiny(mesh_t *mesh, tinyframe_t *frame)
+{
+	int bufferSize = sizeof(vbotiny_t)*mesh->numTriangles * 3;
+	vbotiny_t *buffer = (vbotiny_t*)malloc(bufferSize);
+	vbotiny_t *bufPtr = buffer;
+
+	short *vertPtr = frame->vertices;
+	char *normPtr = frame->normals;
+	float *uvPtr = mesh->uvs;
+	char *tanPtr = frame->tangents;
+
+	int i;
+	for (i = 0; i < mesh->numVertices; i++)
+	{
+		bufPtr->x = *vertPtr++;
+		bufPtr->y = *vertPtr++;
+		bufPtr->z = *vertPtr++;
+
+		bufPtr->nx = *normPtr++;
+		bufPtr->ny = *normPtr++;
+		bufPtr->nz = *normPtr++;
+
+		bufPtr->s0 = *uvPtr++;
+		bufPtr->t0 = *uvPtr++;
+
+		if (tanPtr)
+		{
+			bufPtr->tanx = *tanPtr++;
+			bufPtr->tany = *tanPtr++;
+			bufPtr->tanz = *tanPtr++;
+		}
+
+		bufPtr++;
+	}
+
+	pglGenBuffers(1, &frame->vboID);
+	pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+	pglBufferData(GL_ARRAY_BUFFER, bufferSize, buffer, GL_STATIC_DRAW);
+	free(buffer);
+
+	// Don't leave the array buffer bound to the model,
+	// since this is called mid-frame
+	pglBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+EXPORT void HWRAPI(CreateModelVBOs) (model_t *model)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (mesh->frames)
+		{
+			int j;
+			for (j = 0; j < model->meshes[i].numFrames; j++)
+			{
+				mdlframe_t *frame = &mesh->frames[j];
+				if (frame->vboID)
+					pglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+				CreateModelVBO(mesh, frame);
+			}
+		}
+		else if (mesh->tinyframes)
+		{
+			int j;
+			for (j = 0; j < model->meshes[i].numFrames; j++)
+			{
+				tinyframe_t *frame = &mesh->tinyframes[j];
+				if (frame->vboID)
+					pglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+				CreateModelVBOTiny(mesh, frame);
+			}
+		}
+	}
+}
+
+#define BUFFER_OFFSET(i) ((char*)NULL + (i))
+
+static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
 {
-	INT32     val, count, pindex;
-	GLfloat s, t;
 	GLfloat ambient[4];
 	GLfloat diffuse[4];
 
 	float pol = 0.0f;
-	float scalex = scale, scaley = scale, scalez = scale;
+	float scalex, scaley, scalez;
+
+	boolean useTinyFrames;
+
+	int i;
 
 	// Because Otherwise, scaling the screen negatively vertically breaks the lighting
 	GLfloat LightPos[] = {0.0f, 1.0f, 0.0f, 0.0f};
 
+	// Affect input model scaling
+	scale *= 0.5f;
+	scalex = scale;
+	scaley = scale;
+	scalez = scale;
+
 	if (duration != 0 && duration != -1 && tics != -1) // don't interpolate if instantaneous or infinite in length
 	{
 		UINT32 newtime = (duration - tics); // + 1;
@@ -1816,7 +2012,21 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	}
 
 	pglEnable(GL_CULL_FACE);
+	pglEnable(GL_NORMALIZE);
 
+#ifdef USE_FTRANSFORM_MIRROR
+	// flipped is if the object is flipped
+	// pos->flip is if the screen is flipped vertically
+	// pos->mirror is if the screen is flipped horizontally
+	// XOR all the flips together to figure out what culling to use!
+	{
+		boolean reversecull = (flipped ^ pos->flip ^ pos->mirror);
+		if (reversecull)
+			pglCullFace(GL_FRONT);
+		else
+			pglCullFace(GL_BACK);
+	}
+#else
 	// pos->flip is if the screen is flipped too
 	if (flipped != pos->flip) // If either are active, but not both, invert the model's culling
 	{
@@ -1826,6 +2036,7 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	{
 		pglCullFace(GL_BACK);
 	}
+#endif
 
 	pglLightfv(GL_LIGHT0, GL_POSITION, LightPos);
 
@@ -1848,98 +2059,145 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	pglTranslatef(pos->x, pos->z, pos->y);
 	if (flipped)
 		scaley = -scaley;
-	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
+#ifdef USE_FTRANSFORM_ANGLEZ
+	pglRotatef(pos->anglez, 0.0f, 0.0f, -1.0f); // rotate by slope from Kart
+#endif
 	pglRotatef(pos->anglex, -1.0f, 0.0f, 0.0f);
+	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
+
+	pglScalef(scalex, scaley, scalez);
+
+	useTinyFrames = model->meshes[0].tinyframes != NULL;
 
-	val = *gl_cmd_buffer++;
+	if (useTinyFrames)
+		pglScalef(1 / 64.0f, 1 / 64.0f, 1 / 64.0f);
 
-	while (val != 0)
+	pglEnableClientState(GL_NORMAL_ARRAY);
+
+	for (i = 0; i < model->numMeshes; i++)
 	{
-		if (val < 0)
-		{
-			pglBegin(GL_TRIANGLE_FAN);
-			count = -val;
-		}
-		else
-		{
-			pglBegin(GL_TRIANGLE_STRIP);
-			count = val;
-		}
+		mesh_t *mesh = &model->meshes[i];
 
-		while (count--)
+		if (useTinyFrames)
 		{
-			s = *(float *) gl_cmd_buffer++;
-			t = *(float *) gl_cmd_buffer++;
-			pindex = *gl_cmd_buffer++;
+			tinyframe_t *frame = &mesh->tinyframes[frameIndex % mesh->numFrames];
+			tinyframe_t *nextframe = NULL;
 
-			pglTexCoord2f(s, t);
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->tinyframes[nextFrameIndex % mesh->numFrames];
 
 			if (!nextframe || fpclassify(pol) == FP_ZERO)
 			{
-				pglNormal3f(frame->vertices[pindex].normal[0],
-				            frame->vertices[pindex].normal[1],
-				            frame->vertices[pindex].normal[2]);
+				pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+				pglVertexPointer(3, GL_SHORT, sizeof(vbotiny_t), BUFFER_OFFSET(0));
+				pglNormalPointer(GL_BYTE, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short)*3));
+				pglTexCoordPointer(2, GL_FLOAT, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short) * 3 + sizeof(char) * 6));
 
-				pglVertex3f(frame->vertices[pindex].vertex[0]*scalex/2.0f,
-				            frame->vertices[pindex].vertex[1]*scaley/2.0f,
-				            frame->vertices[pindex].vertex[2]*scalez/2.0f);
+				pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+				pglBindBuffer(GL_ARRAY_BUFFER, 0);
 			}
 			else
 			{
-				// Interpolate
-				float px1 = frame->vertices[pindex].vertex[0]*scalex/2.0f;
-				float px2 = nextframe->vertices[pindex].vertex[0]*scalex/2.0f;
-				float py1 = frame->vertices[pindex].vertex[1]*scaley/2.0f;
-				float py2 = nextframe->vertices[pindex].vertex[1]*scaley/2.0f;
-				float pz1 = frame->vertices[pindex].vertex[2]*scalez/2.0f;
-				float pz2 = nextframe->vertices[pindex].vertex[2]*scalez/2.0f;
-				float nx1 = frame->vertices[pindex].normal[0];
-				float nx2 = nextframe->vertices[pindex].normal[0];
-				float ny1 = frame->vertices[pindex].normal[1];
-				float ny2 = nextframe->vertices[pindex].normal[1];
-				float nz1 = frame->vertices[pindex].normal[2];
-				float nz2 = nextframe->vertices[pindex].normal[2];
-
-				pglNormal3f((nx1 + pol * (nx2 - nx1)),
-				            (ny1 + pol * (ny2 - ny1)),
-				            (nz1 + pol * (nz2 - nz1)));
-				pglVertex3f((px1 + pol * (px2 - px1)),
-				            (py1 + pol * (py2 - py1)),
-				            (pz1 + pol * (pz2 - pz1)));
+				short *vertPtr;
+				char *normPtr;
+				int j;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				AllocLerpTinyBuffer(mesh->numVertices * sizeof(short) * 3);
+				vertPtr = vertTinyBuffer;
+				normPtr = normTinyBuffer;
+				j = 0;
+
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = (short)(frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j])));
+					*normPtr++ = (char)(frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j])));
+				}
+
+				pglVertexPointer(3, GL_SHORT, 0, vertTinyBuffer);
+				pglNormalPointer(GL_BYTE, 0, normTinyBuffer);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
 			}
 		}
+		else
+		{
+			mdlframe_t *frame = &mesh->frames[frameIndex % mesh->numFrames];
+			mdlframe_t *nextframe = NULL;
+
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->frames[nextFrameIndex % mesh->numFrames];
+
+			if (!nextframe || fpclassify(pol) == FP_ZERO)
+			{
+				// Zoom! Take advantage of just shoving the entire arrays to the GPU.
+/*				pglVertexPointer(3, GL_FLOAT, 0, frame->vertices);
+				pglNormalPointer(GL_FLOAT, 0, frame->normals);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);*/
+
+				pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+				pglVertexPointer(3, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(0));
+				pglNormalPointer(GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 3));
+				pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 6));
+
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+				// No tinyframes, no mesh indices
+				//pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+				pglBindBuffer(GL_ARRAY_BUFFER, 0);
+			}
+			else
+			{
+				float *vertPtr;
+				float *normPtr;
+				int j = 0;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				AllocLerpBuffer(mesh->numVertices * sizeof(float) * 3);
+				vertPtr = vertBuffer;
+				normPtr = normBuffer;
+				//int j = 0;
 
-		pglEnd();
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j]));
+					*normPtr++ = frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j]));
+				}
 
-		val = *gl_cmd_buffer++;
+				pglVertexPointer(3, GL_FLOAT, 0, vertBuffer);
+				pglNormalPointer(GL_FLOAT, 0, normBuffer);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numVertices);
+			}
+		}
 	}
+
+	pglDisableClientState(GL_NORMAL_ARRAY);
+
 	pglPopMatrix(); // should be the same as glLoadIdentity
 	if (color)
 		pglDisable(GL_LIGHTING);
 	pglShadeModel(GL_FLAT);
 	pglDisable(GL_CULL_FACE);
+	pglDisable(GL_NORMALIZE);
 }
 
 // -----------------+
 // HWRAPI DrawMD2   : Draw an MD2 model with glcommands
 // -----------------+
-EXPORT void HWRAPI(DrawMD2i) (INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration, INT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
 {
-	DrawMD2Ex(gl_cmd_buffer, frame, duration, tics,  nextframe, pos, scale, flipped, color);
+	DrawModelEx(model, frameIndex, duration, tics,  nextFrameIndex, pos, scale, flipped, color);
 }
 
-EXPORT void HWRAPI(DrawMD2) (INT32 *gl_cmd_buffer, md2_frame_t *frame, FTransform *pos, float scale)
-{
-	DrawMD2Ex(gl_cmd_buffer, frame, 0, 0,  NULL, pos, scale, false, NULL);
-}
-
-
 // -----------------+
 // SetTransform     :
 // -----------------+
 EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 {
-	static INT32 special_splitscreen;
+	static boolean special_splitscreen;
 	pglLoadIdentity();
 	if (stransform)
 	{
@@ -1947,6 +2205,12 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 		// keep a trace of the transformation for md2
 		memcpy(&md2_transform, stransform, sizeof (md2_transform));
 
+#ifdef USE_FTRANSFORM_MIRROR
+		// mirroring from Kart
+		if (stransform->mirror)
+			pglScalef(-stransform->scalex, stransform->scaley, -stransform->scalez);
+		else
+#endif
 		if (stransform->flip)
 			pglScalef(stransform->scalex, -stransform->scaley, -stransform->scalez);
 		else
@@ -1961,10 +2225,10 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 		fovx90 = stransform->fovxangle > 0.0f && fabsf(stransform->fovxangle - 90.0f) < 0.5f;
 		special_splitscreen = (stransform->splitscreen && fovx90);
 		if (special_splitscreen)
-			GLPerspective(53.13l, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
+			GLPerspective(53.13f, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
 		else
 			GLPerspective(stransform->fovxangle, ASPECT_RATIO);
-		pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
+		pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
 		pglMatrixMode(GL_MODELVIEW);
 	}
 	else
@@ -1974,15 +2238,15 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 		pglMatrixMode(GL_PROJECTION);
 		pglLoadIdentity();
 		if (special_splitscreen)
-			GLPerspective(53.13l, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
+			GLPerspective(53.13f, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
 		else
 			//Hurdler: is "fov" correct?
 			GLPerspective(fov, ASPECT_RATIO);
-		pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
+		pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
 		pglMatrixMode(GL_MODELVIEW);
 	}
 
-	pglGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
+	pglGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
 }
 
 EXPORT INT32  HWRAPI(GetTextureUsed) (void)
@@ -2003,7 +2267,6 @@ EXPORT INT32  HWRAPI(GetRenderVersion) (void)
 	return VERSION;
 }
 
-#ifdef SHUFFLE
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 {
 	INT32 x, y;
@@ -2011,6 +2274,14 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 	float xfix, yfix;
 	INT32 texsize = 2048;
 
+	const float blackBack[16] =
+	{
+		-16.0f, -16.0f, 6.0f,
+		-16.0f, 16.0f, 6.0f,
+		16.0f, 16.0f, 6.0f,
+		16.0f, -16.0f, 6.0f
+	};
+
 	// Use a power of two texture, dammit
 	if(screen_width <= 1024)
 		texsize = 1024;
@@ -2023,47 +2294,66 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 
 	pglDisable(GL_DEPTH_TEST);
 	pglDisable(GL_BLEND);
-	pglBegin(GL_QUADS);
 
-		// Draw a black square behind the screen texture,
-		// so nothing shows through the edges
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-		pglVertex3f(-16.0f, -16.0f, 6.0f);
-		pglVertex3f(-16.0f, 16.0f, 6.0f);
-		pglVertex3f(16.0f, 16.0f, 6.0f);
-		pglVertex3f(16.0f, -16.0f, 6.0f);
+	// const float blackBack[16]
+
+	// Draw a black square behind the screen texture,
+	// so nothing shows through the edges
+	pglColor4ubv(white);
 
-		for(x=0;x<SCREENVERTS-1;x++)
+	pglVertexPointer(3, GL_FLOAT, 0, blackBack);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	for(x=0;x<SCREENVERTS-1;x++)
+	{
+		for(y=0;y<SCREENVERTS-1;y++)
 		{
-			for(y=0;y<SCREENVERTS-1;y++)
-			{
-				// Used for texture coordinates
-				// Annoying magic numbers to scale the square texture to
-				// a non-square screen..
-				float_x = (float)(x/(xfix));
-				float_y = (float)(y/(yfix));
-				float_nextx = (float)(x+1)/(xfix);
-				float_nexty = (float)(y+1)/(yfix);
-
-				// Attach the squares together.
-				pglTexCoord2f( float_x, float_y);
-				pglVertex3f(points[x][y][0], points[x][y][1], 4.4f);
-
-				pglTexCoord2f( float_x, float_nexty);
-				pglVertex3f(points[x][y+1][0], points[x][y+1][1], 4.4f);
-
-				pglTexCoord2f( float_nextx, float_nexty);
-				pglVertex3f(points[x+1][y+1][0], points[x+1][y+1][1], 4.4f);
-
-				pglTexCoord2f( float_nextx, float_y);
-				pglVertex3f(points[x+1][y][0], points[x+1][y][1], 4.4f);
-			}
+			float stCoords[8];
+			float vertCoords[12];
+
+			// Used for texture coordinates
+			// Annoying magic numbers to scale the square texture to
+			// a non-square screen..
+			float_x = (float)(x/(xfix));
+			float_y = (float)(y/(yfix));
+			float_nextx = (float)(x+1)/(xfix);
+			float_nexty = (float)(y+1)/(yfix);
+
+			// float stCoords[8];
+			stCoords[0] = float_x;
+			stCoords[1] = float_y;
+			stCoords[2] = float_x;
+			stCoords[3] = float_nexty;
+			stCoords[4] = float_nextx;
+			stCoords[5] = float_nexty;
+			stCoords[6] = float_nextx;
+			stCoords[7] = float_y;
+
+			pglTexCoordPointer(2, GL_FLOAT, 0, stCoords);
+
+			// float vertCoords[12];
+			vertCoords[0] = points[x][y][0];
+			vertCoords[1] = points[x][y][1];
+			vertCoords[2] = 4.4f;
+			vertCoords[3] = points[x][y + 1][0];
+			vertCoords[4] = points[x][y + 1][1];
+			vertCoords[5] = 4.4f;
+			vertCoords[6] = points[x + 1][y + 1][0];
+			vertCoords[7] = points[x + 1][y + 1][1];
+			vertCoords[8] = 4.4f;
+			vertCoords[9] = points[x + 1][y][0];
+			vertCoords[10] = points[x + 1][y][1];
+			vertCoords[11] = 4.4f;
+
+			pglVertexPointer(3, GL_FLOAT, 0, vertCoords);
+
+			pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 		}
-	pglEnd();
+	}
+
 	pglEnable(GL_DEPTH_TEST);
 	pglEnable(GL_BLEND);
 }
-#endif //SHUFFLE
 
 // Sryder:	This needs to be called whenever the screen changes resolution in order to reset the screen textures to use
 //			a new size
@@ -2148,6 +2438,16 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 	float xfix, yfix;
 	INT32 texsize = 2048;
 
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
 	if(screen_width <= 1024)
 		texsize = 1024;
 	if(screen_width <= 512)
@@ -2156,41 +2456,56 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
 
-	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
-
-	pglBindTexture(GL_TEXTURE_2D, screentexture);
-	pglBegin(GL_QUADS);
-
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-		// Bottom left
-		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-1.0f, -1.0f, 1.0f);
+	// const float screenVerts[12]
 
-		// Top left
-		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-1.0f, 1.0f, 1.0f);
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
 
-		// Top right
-		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(1.0f, 1.0f, 1.0f);
+	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
-		// Bottom right
-		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(1.0f, -1.0f, 1.0f);
+	pglBindTexture(GL_TEXTURE_2D, screentexture);
+	pglColor4ubv(white);
 
-	pglEnd();
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
 	tex_downloaded = screentexture;
 }
 
 // Do screen fades!
-EXPORT void HWRAPI(DoScreenWipe)(float alpha)
+EXPORT void HWRAPI(DoScreenWipe)(void)
 {
 	INT32 texsize = 2048;
 	float xfix, yfix;
 
 	INT32 fademaskdownloaded = tex_downloaded; // the fade mask that has been set
 
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
+	const float defaultST[8] =
+	{
+		0.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		1.0f, 1.0f
+	};
+
 	// Use a power of two texture, dammit
 	if(screen_width <= 1024)
 		texsize = 1024;
@@ -2200,101 +2515,60 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
 
+	// const float screenVerts[12]
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
 	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
 	SetBlend(PF_Modulated|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	pglEnable(GL_TEXTURE_2D);
 
 	// Draw the original screen
 	pglBindTexture(GL_TEXTURE_2D, startScreenWipe);
-	pglBegin(GL_QUADS);
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+	pglColor4ubv(white);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-		// Bottom left
-		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-1.0f, -1.0f, 1.0f);
+	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest|PF_Clip|PF_NoZClip);
 
-		// Top left
-		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-1.0f, 1.0f, 1.0f);
+	// Draw the end screen that fades in
+	pglActiveTexture(GL_TEXTURE0);
+	pglEnable(GL_TEXTURE_2D);
+	pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
 
-		// Top right
-		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(1.0f, 1.0f, 1.0f);
+	pglActiveTexture(GL_TEXTURE1);
+	pglEnable(GL_TEXTURE_2D);
+	pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
 
-		// Bottom right
-		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(1.0f, -1.0f, 1.0f);
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
-	pglEnd();
+	// const float defaultST[8]
 
-	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	pglClientActiveTexture(GL_TEXTURE0);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglClientActiveTexture(GL_TEXTURE1);
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	pglTexCoordPointer(2, GL_FLOAT, 0, defaultST);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-	if (gl13)
-	{
-		// Draw the end screen that fades in
-		pglActiveTexture(GL_TEXTURE0);
-		pglEnable(GL_TEXTURE_2D);
-		pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-		pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
-		pglActiveTexture(GL_TEXTURE1);
-		pglEnable(GL_TEXTURE_2D);
-		pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
-
-		pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-		pglBegin(GL_QUADS);
-			pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-
-			// Bottom left
-			pglMultiTexCoord2f(GL_TEXTURE0, 0.0f, 0.0f);
-			pglMultiTexCoord2f(GL_TEXTURE1, 0.0f, 1.0f);
-			pglVertex3f(-1.0f, -1.0f, 1.0f);
-
-			// Top left
-			pglMultiTexCoord2f(GL_TEXTURE0, 0.0f, yfix);
-			pglMultiTexCoord2f(GL_TEXTURE1, 0.0f, 0.0f);
-			pglVertex3f(-1.0f, 1.0f, 1.0f);
-
-			// Top right
-			pglMultiTexCoord2f(GL_TEXTURE0, xfix, yfix);
-			pglMultiTexCoord2f(GL_TEXTURE1, 1.0f, 0.0f);
-			pglVertex3f(1.0f, 1.0f, 1.0f);
-
-			// Bottom right
-			pglMultiTexCoord2f(GL_TEXTURE0, xfix, 0.0f);
-			pglMultiTexCoord2f(GL_TEXTURE1, 1.0f, 1.0f);
-			pglVertex3f(1.0f, -1.0f, 1.0f);
-		pglEnd();
-
-		pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
-		pglActiveTexture(GL_TEXTURE0);
-		tex_downloaded = endScreenWipe;
-	}
-	else
-	{
-		// Draw the end screen that fades in
-		pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-		pglBegin(GL_QUADS);
-			pglColor4f(1.0f, 1.0f, 1.0f, alpha);
-
-			// Bottom left
-			pglTexCoord2f(0.0f, 0.0f);
-			pglVertex3f(-1.0f, -1.0f, 1.0f);
-
-			// Top left
-			pglTexCoord2f(0.0f, yfix);
-			pglVertex3f(-1.0f, 1.0f, 1.0f);
-
-			// Top right
-			pglTexCoord2f(xfix, yfix);
-			pglVertex3f(1.0f, 1.0f, 1.0f);
-
-			// Bottom right
-			pglTexCoord2f(xfix, 0.0f);
-			pglVertex3f(1.0f, -1.0f, 1.0f);
-		pglEnd();
-		tex_downloaded = endScreenWipe;
-	}
+	pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
+	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+
+	pglActiveTexture(GL_TEXTURE0);
+	pglClientActiveTexture(GL_TEXTURE0);
+	tex_downloaded = endScreenWipe;
 }
 
 
@@ -2357,7 +2631,6 @@ EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
 		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
 
 	tex_downloaded = finalScreenTexture;
-
 }
 
 EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
@@ -2368,6 +2641,9 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 	FRGBAFloat clearColour;
 	INT32 texsize = 2048;
 
+	float off[12];
+	float fix[8];
+
 	if(screen_width <= 1024)
 		texsize = 1024;
 	if(screen_width <= 512)
@@ -2389,33 +2665,43 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 		yoff = newaspect / origaspect;
 	}
 
+	// float off[12];
+	off[0] = -xoff;
+	off[1] = -yoff;
+	off[2] = 1.0f;
+	off[3] = -xoff;
+	off[4] = yoff;
+	off[5] = 1.0f;
+	off[6] = xoff;
+	off[7] = yoff;
+	off[8] = 1.0f;
+	off[9] = xoff;
+	off[10] = -yoff;
+	off[11] = 1.0f;
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
 	pglViewport(0, 0, width, height);
 
 	clearColour.red = clearColour.green = clearColour.blue = 0;
 	clearColour.alpha = 1;
 	ClearBuffer(true, false, &clearColour);
 	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
-	pglBegin(GL_QUADS);
-
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-		// Bottom left
-		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-xoff, -yoff, 1.0f);
-
-		// Top left
-		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-xoff, yoff, 1.0f);
-
-		// Top right
-		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(xoff, yoff, 1.0f);
 
-		// Bottom right
-		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(xoff, -yoff, 1.0f);
+	pglColor4ubv(white);
 
-	pglEnd();
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, off);
 
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 	tex_downloaded = finalScreenTexture;
 }
 
diff --git a/src/hardware/r_opengl/r_opengl.h b/src/hardware/r_opengl/r_opengl.h
index e6cf164bb088b72ae778bc40fa02814e7f96fcd9..1bb97c37c31aec5dcde5524b907bea8c5576f639 100644
--- a/src/hardware/r_opengl/r_opengl.h
+++ b/src/hardware/r_opengl/r_opengl.h
@@ -71,7 +71,6 @@ extern FILE             *gllogstream;
 #endif
 
 #ifndef DRIVER_STRING
-//    #define USE_PALETTED_TEXTURE
 #define DRIVER_STRING "HWRAPI Init(): SRB2 OpenGL renderer" // Tails
 #endif
 
@@ -89,10 +88,6 @@ int SetupPixelFormat(INT32 WantColorBits, INT32 WantStencilBits, INT32 WantDepth
 void SetModelView(GLint w, GLint h);
 void SetStates(void);
 FUNCMATH float byteasfloat(UINT8 fbyte);
-#ifdef USE_PALETTED_TEXTURE
-extern PFNGLCOLORTABLEEXTPROC glColorTableEXT;
-extern GLubyte                palette_tex[256*3];
-#endif
 
 #ifndef GL_EXT_texture_filter_anisotropic
 #define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
@@ -118,6 +113,10 @@ typedef void (APIENTRY * PFNglGetIntegerv) (GLenum pname, GLint *params);
 extern PFNglGetIntegerv pglGetIntegerv;
 typedef const GLubyte* (APIENTRY  * PFNglGetString) (GLenum name);
 extern PFNglGetString pglGetString;
+#if 0
+typedef void (APIENTRY * PFNglEnableClientState) (GLenum cap); // redefined in r_opengl.c
+static PFNglEnableClientState pglEnableClientState;
+#endif
 #endif
 
 // ==========================================================================
diff --git a/src/hardware/r_opengl/r_vbo.h b/src/hardware/r_opengl/r_vbo.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca1a974dabbbe6cc296e0f237d2f7dacd1d2328b
--- /dev/null
+++ b/src/hardware/r_opengl/r_vbo.h
@@ -0,0 +1,52 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+#ifndef _R_VBO_H_
+#define _R_VBO_H_
+
+typedef struct
+{
+	float x, y, z;		// Vertex
+	float nx, ny, nz;	// Normal
+	float s0, t0;		// Texcoord0
+} vbo32_t;
+
+typedef struct
+{
+	float x, y, z;	// Vertex
+	float s0, t0;	// Texcoord0
+	unsigned char r, g, b, a; // Color
+	float pad[2]; // Pad
+} vbo2d32_t;
+
+typedef struct
+{
+	float x, y; // Vertex
+	float s0, t0; // Texcoord0
+} vbofont_t;
+
+typedef struct
+{
+	short x, y, z; // Vertex
+	char nx, ny, nz; // Normal
+	char tanx, tany, tanz; // Tangent
+	float s0, t0; // Texcoord0
+} vbotiny_t;
+
+typedef struct
+{
+	float x, y, z;      // Vertex
+	float nx, ny, nz;   // Normal
+	float s0, t0;       // Texcoord0
+	float s1, t1;       // Texcoord1
+	float s2, t2;       // Texcoord2
+	float tan0, tan1, tan2; // Tangent
+	unsigned char r, g, b, a;	// Color
+} vbo64_t;
+
+#endif
diff --git a/src/hardware/u_list.c b/src/hardware/u_list.c
new file mode 100644
index 0000000000000000000000000000000000000000..dc49a74e7f7ab1a0502fbf5fb5f13cbbad888275
--- /dev/null
+++ b/src/hardware/u_list.c
@@ -0,0 +1,230 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include "u_list.h"
+#include "../z_zone.h"
+
+// Utility for managing
+// structures in a linked
+// list.
+//
+// Struct must have "next" and "prev" pointers
+// as its first two variables.
+//
+
+//
+// ListAdd
+//
+// Adds an item to the list
+//
+void ListAdd(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (*itemHead == NULL)
+	{
+		*itemHead = item;
+		(*itemHead)->prev = (*itemHead)->next = NULL;
+	}
+	else
+	{
+		listitem_t *tail;
+		tail = *itemHead;
+
+		while (tail->next != NULL)
+			tail = tail->next;
+
+		tail->next = item;
+
+		tail->next->prev = tail;
+
+		item->next = NULL;
+	}
+}
+
+//
+// ListAddFront
+//
+// Adds an item to the front of the list
+// (This is much faster)
+//
+void ListAddFront(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (*itemHead == NULL)
+	{
+		*itemHead = item;
+		(*itemHead)->prev = (*itemHead)->next = NULL;
+	}
+	else
+	{
+		(*itemHead)->prev = item;
+		item->next = (*itemHead);
+		item->prev = NULL;
+		*itemHead = item;
+	}
+}
+
+//
+// ListAddBefore
+//
+// Adds an item before the item specified in the list
+//
+void ListAddBefore(void *pItem, void *pSpot, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+	listitem_t *spot = (listitem_t*)pSpot;
+
+	listitem_t *prev = spot->prev;
+
+	if (!prev)
+		ListAddFront(pItem, itemHead);
+	else
+	{
+		item->next = spot;
+		spot->prev = item;
+		item->prev = prev;
+		prev->next = item;
+	}
+}
+
+//
+// ListAddAfter
+//
+// Adds an item after the item specified in the list
+//
+void ListAddAfter(void *pItem, void *pSpot, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+	listitem_t *spot = (listitem_t*)pSpot;
+
+	listitem_t *next = spot->next;
+
+	if (!next)
+		ListAdd(pItem, itemHead);
+	else
+	{
+		item->prev = spot;
+		spot->next = item;
+		item->next = next;
+		next->prev = item;
+	}
+}
+
+//
+// ListRemove
+//
+// Take an item out of the list and free its memory.
+//
+void ListRemove(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (item == *itemHead) // Start of list
+	{
+		*itemHead = item->next;
+
+		if (*itemHead)
+			(*itemHead)->prev = NULL;
+	}
+	else if (item->next == NULL) // end of list
+	{
+		item->prev->next = NULL;
+	}
+	else // Somewhere in between
+	{
+		item->prev->next = item->next;
+		item->next->prev = item->prev;
+	}
+
+	Z_Free (item);
+}
+
+//
+// ListRemoveAll
+//
+// Removes all items from the list, freeing their memory.
+//
+void ListRemoveAll(listitem_t **itemHead)
+{
+	listitem_t *item;
+	listitem_t *next;
+	for (item = *itemHead; item; item = next)
+	{
+		next = item->next;
+		ListRemove(item, itemHead);
+	}
+}
+
+//
+// ListRemoveNoFree
+//
+// Take an item out of the list, but don't free its memory.
+//
+void ListRemoveNoFree(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (item == *itemHead) // Start of list
+	{
+		*itemHead = item->next;
+
+		if (*itemHead)
+			(*itemHead)->prev = NULL;
+	}
+	else if (item->next == NULL) // end of list
+	{
+		item->prev->next = NULL;
+	}
+	else // Somewhere in between
+	{
+		item->prev->next = item->next;
+		item->next->prev = item->prev;
+	}
+}
+
+//
+// ListGetCount
+//
+// Counts the # of items in a list
+// Should not be used in performance-minded code
+//
+unsigned int ListGetCount(void *itemHead)
+{
+	listitem_t *item = (listitem_t*)itemHead;
+
+	unsigned int count = 0;
+	for (; item; item = item->next)
+		count++;
+
+	return count;
+}
+
+//
+// ListGetByIndex
+//
+// Gets an item in the list by its index
+// Should not be used in performance-minded code
+//
+listitem_t *ListGetByIndex(void *itemHead, unsigned int index)
+{
+	listitem_t *head = (listitem_t*)itemHead;
+	unsigned int count = 0;
+	listitem_t *node;
+	for (node = head; node; node = node->next)
+	{
+		if (count == index)
+			return node;
+
+		count++;
+	}
+
+	return NULL;
+}
diff --git a/src/hardware/u_list.h b/src/hardware/u_list.h
new file mode 100644
index 0000000000000000000000000000000000000000..7e9a3cabd3bb9e5a9ded2c2d9f1d0f34b074ec3f
--- /dev/null
+++ b/src/hardware/u_list.h
@@ -0,0 +1,29 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _U_LIST_H_
+#define _U_LIST_H_
+
+typedef struct listitem_s
+{
+	struct listitem_s *next;
+	struct listitem_s *prev;
+} listitem_t;
+
+void ListAdd(void *pItem, listitem_t **itemHead);
+void ListAddFront(void *pItem, listitem_t **itemHead);
+void ListAddBefore(void *pItem, void *pSpot, listitem_t **itemHead);
+void ListAddAfter(void *pItem, void *pSpot, listitem_t **itemHead);
+void ListRemove(void *pItem, listitem_t **itemHead);
+void ListRemoveAll(listitem_t **itemHead);
+void ListRemoveNoFree(void *pItem, listitem_t **itemHead);
+unsigned int ListGetCount(void *itemHead);
+listitem_t *ListGetByIndex(void *itemHead, unsigned int index);
+
+#endif
diff --git a/src/m_fixed.c b/src/m_fixed.c
index d45bb70bf572dabacef5f9874dfe84d3b9bdef32..5e78967396a4b9802dc29e301a14112f03b3168a 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -56,7 +56,7 @@ fixed_t FixedDiv2(fixed_t a, fixed_t b)
 	if (b == 0)
 		I_Error("FixedDiv: divide by zero");
 
-	ret = (((INT64)a * FRACUNIT) ) / b;
+	ret = (((INT64)a * FRACUNIT)) / b;
 
 	if ((ret > INT32_MAX) || (ret < INT32_MIN))
 		I_Error("FixedDiv: divide by zero");
@@ -117,7 +117,7 @@ fixed_t FixedHypot(fixed_t x, fixed_t y)
 		yx = FixedDiv(y, x); // (x/y)
 	}
 	yx2 = FixedMul(yx, yx); // (x/y)^2
-	yx1 = FixedSqrt(1*FRACUNIT + yx2); // (1 + (x/y)^2)^1/2
+	yx1 = FixedSqrt(1 * FRACUNIT + yx2); // (1 + (x/y)^2)^1/2
 	return FixedMul(ax, yx1); // |x|*((1 + (x/y)^2)^1/2)
 }
 
@@ -191,8 +191,8 @@ vector2_t *FV2_Divide(vector2_t *a_i, fixed_t a_c)
 // Vector Complex Math
 vector2_t *FV2_Midpoint(const vector2_t *a_1, const vector2_t *a_2, vector2_t *a_o)
 {
-	a_o->x = FixedDiv(a_2->x - a_1->x, 2*FRACUNIT);
-	a_o->y = FixedDiv(a_2->y - a_1->y, 2*FRACUNIT);
+	a_o->x = FixedDiv(a_2->x - a_1->x, 2 * FRACUNIT);
+	a_o->y = FixedDiv(a_2->y - a_1->y, 2 * FRACUNIT);
 	a_o->x = a_1->x + a_o->x;
 	a_o->y = a_1->y + a_o->y;
 	return a_o;
@@ -200,16 +200,16 @@ vector2_t *FV2_Midpoint(const vector2_t *a_1, const vector2_t *a_2, vector2_t *a
 
 fixed_t FV2_Distance(const vector2_t *p1, const vector2_t *p2)
 {
-	fixed_t xs = FixedMul(p2->x-p1->x,p2->x-p1->x);
-	fixed_t ys = FixedMul(p2->y-p1->y,p2->y-p1->y);
-	return FixedSqrt(xs+ys);
+	fixed_t xs = FixedMul(p2->x - p1->x, p2->x - p1->x);
+	fixed_t ys = FixedMul(p2->y - p1->y, p2->y - p1->y);
+	return FixedSqrt(xs + ys);
 }
 
 fixed_t FV2_Magnitude(const vector2_t *a_normal)
 {
-	fixed_t xs = FixedMul(a_normal->x,a_normal->x);
-	fixed_t ys = FixedMul(a_normal->y,a_normal->y);
-	return FixedSqrt(xs+ys);
+	fixed_t xs = FixedMul(a_normal->x, a_normal->x);
+	fixed_t ys = FixedMul(a_normal->y, a_normal->y);
+	return FixedSqrt(xs + ys);
 }
 
 // Also returns the magnitude
@@ -240,7 +240,7 @@ vector2_t *FV2_Negate(vector2_t *a_1)
 
 boolean FV2_Equal(const vector2_t *a_1, const vector2_t *a_2)
 {
-	fixed_t Epsilon = FRACUNIT/FRACUNIT;
+	fixed_t Epsilon = FRACUNIT / FRACUNIT;
 
 	if ((abs(a_2->x - a_1->x) > Epsilon) ||
 		(abs(a_2->y - a_1->y) > Epsilon))
@@ -261,7 +261,7 @@ fixed_t FV2_Dot(const vector2_t *a_1, const vector2_t *a_2)
 //
 // Given two points, create a vector between them.
 //
-vector2_t *FV2_Point2Vec (const vector2_t *point1, const vector2_t *point2, vector2_t *a_o)
+vector2_t *FV2_Point2Vec(const vector2_t *point1, const vector2_t *point2, vector2_t *a_o)
 {
 	a_o->x = point1->x - point2->x;
 	a_o->y = point1->y - point2->y;
@@ -344,9 +344,9 @@ vector3_t *FV3_Divide(vector3_t *a_i, fixed_t a_c)
 // Vector Complex Math
 vector3_t *FV3_Midpoint(const vector3_t *a_1, const vector3_t *a_2, vector3_t *a_o)
 {
-	a_o->x = FixedDiv(a_2->x - a_1->x, 2*FRACUNIT);
-	a_o->y = FixedDiv(a_2->y - a_1->y, 2*FRACUNIT);
-	a_o->z = FixedDiv(a_2->z - a_1->z, 2*FRACUNIT);
+	a_o->x = FixedDiv(a_2->x - a_1->x, 2 * FRACUNIT);
+	a_o->y = FixedDiv(a_2->y - a_1->y, 2 * FRACUNIT);
+	a_o->z = FixedDiv(a_2->z - a_1->z, 2 * FRACUNIT);
 	a_o->x = a_1->x + a_o->x;
 	a_o->y = a_1->y + a_o->y;
 	a_o->z = a_1->z + a_o->z;
@@ -355,18 +355,18 @@ vector3_t *FV3_Midpoint(const vector3_t *a_1, const vector3_t *a_2, vector3_t *a
 
 fixed_t FV3_Distance(const vector3_t *p1, const vector3_t *p2)
 {
-	fixed_t xs = FixedMul(p2->x-p1->x,p2->x-p1->x);
-	fixed_t ys = FixedMul(p2->y-p1->y,p2->y-p1->y);
-	fixed_t zs = FixedMul(p2->z-p1->z,p2->z-p1->z);
-	return FixedSqrt(xs+ys+zs);
+	fixed_t xs = FixedMul(p2->x - p1->x, p2->x - p1->x);
+	fixed_t ys = FixedMul(p2->y - p1->y, p2->y - p1->y);
+	fixed_t zs = FixedMul(p2->z - p1->z, p2->z - p1->z);
+	return FixedSqrt(xs + ys + zs);
 }
 
 fixed_t FV3_Magnitude(const vector3_t *a_normal)
 {
-	fixed_t xs = FixedMul(a_normal->x,a_normal->x);
-	fixed_t ys = FixedMul(a_normal->y,a_normal->y);
-	fixed_t zs = FixedMul(a_normal->z,a_normal->z);
-	return FixedSqrt(xs+ys+zs);
+	fixed_t xs = FixedMul(a_normal->x, a_normal->x);
+	fixed_t ys = FixedMul(a_normal->y, a_normal->y);
+	fixed_t zs = FixedMul(a_normal->z, a_normal->z);
+	return FixedSqrt(xs + ys + zs);
 }
 
 // Also returns the magnitude
@@ -399,7 +399,7 @@ vector3_t *FV3_Negate(vector3_t *a_1)
 
 boolean FV3_Equal(const vector3_t *a_1, const vector3_t *a_2)
 {
-	fixed_t Epsilon = FRACUNIT/FRACUNIT;
+	fixed_t Epsilon = FRACUNIT / FRACUNIT;
 
 	if ((abs(a_2->x - a_1->x) > Epsilon) ||
 		(abs(a_2->y - a_1->y) > Epsilon) ||
@@ -458,6 +458,20 @@ vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vec
 	return FV3_AddEx(&Line[0], &V, out);
 }
 
+//
+// ClosestPointOnVector
+//
+// Similar to ClosestPointOnLine, but uses a vector instead of two points.
+//
+void FV3_ClosestPointOnVector(const vector3_t *dir, const vector3_t *p, vector3_t *out)
+{
+	fixed_t t = FV3_Dot(dir, p);
+
+	// Return the point on the line closest
+	FV3_MulEx(dir, t, out);
+	return;
+}
+
 //
 // ClosestPointOnTriangle
 //
@@ -465,7 +479,7 @@ vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vec
 // the closest point on the edge of
 // the triangle is returned.
 //
-void FV3_ClosestPointOnTriangle (const vector3_t *tri, const vector3_t *point, vector3_t *result)
+void FV3_ClosestPointOnTriangle(const vector3_t *tri, const vector3_t *point, vector3_t *result)
 {
 	UINT8 i;
 	fixed_t dist, closestdist;
@@ -506,7 +520,7 @@ void FV3_ClosestPointOnTriangle (const vector3_t *tri, const vector3_t *point, v
 //
 // Given two points, create a vector between them.
 //
-vector3_t *FV3_Point2Vec (const vector3_t *point1, const vector3_t *point2, vector3_t *a_o)
+vector3_t *FV3_Point2Vec(const vector3_t *point1, const vector3_t *point2, vector3_t *a_o)
 {
 	a_o->x = point1->x - point2->x;
 	a_o->y = point1->y - point2->y;
@@ -519,7 +533,7 @@ vector3_t *FV3_Point2Vec (const vector3_t *point1, const vector3_t *point2, vect
 //
 // Calculates the normal of a polygon.
 //
-void FV3_Normal (const vector3_t *a_triangle, vector3_t *a_normal)
+fixed_t FV3_Normal(const vector3_t *a_triangle, vector3_t *a_normal)
 {
 	vector3_t a_1;
 	vector3_t a_2;
@@ -529,7 +543,28 @@ void FV3_Normal (const vector3_t *a_triangle, vector3_t *a_normal)
 
 	FV3_Cross(&a_1, &a_2, a_normal);
 
-	FV3_NormalizeEx(a_normal, a_normal);
+	return FV3_NormalizeEx(a_normal, a_normal);
+}
+
+//
+// Strength
+//
+// Measures the 'strength' of a vector in a particular direction.
+//
+fixed_t FV3_Strength(const vector3_t *a_1, const vector3_t *dir)
+{
+	vector3_t normal;
+	fixed_t dist = FV3_NormalizeEx(a_1, &normal);
+	fixed_t dot = FV3_Dot(&normal, dir);
+
+	FV3_ClosestPointOnVector(dir, a_1, &normal);
+
+	dist = FV3_Magnitude(&normal);
+
+	if (dot < 0) // Not facing same direction, so negate result.
+		dist = -dist;
+
+	return dist;
 }
 
 //
@@ -550,11 +585,11 @@ boolean FV3_IntersectedPlane(const vector3_t *a_triangle, const vector3_t *a_lin
 
 	*originDistance = FV3_PlaneDistance(a_normal, &a_triangle[0]);
 
-	distance1 = (FixedMul(a_normal->x, a_line[0].x)  + FixedMul(a_normal->y, a_line[0].y)
-				+ FixedMul(a_normal->z, a_line[0].z)) + *originDistance;
+	distance1 = (FixedMul(a_normal->x, a_line[0].x) + FixedMul(a_normal->y, a_line[0].y)
+		+ FixedMul(a_normal->z, a_line[0].z)) + *originDistance;
 
-	distance2 = (FixedMul(a_normal->x, a_line[1].x)  + FixedMul(a_normal->y, a_line[1].y)
-				+ FixedMul(a_normal->z, a_line[1].z)) + *originDistance;
+	distance2 = (FixedMul(a_normal->x, a_line[1].x) + FixedMul(a_normal->y, a_line[1].y)
+		+ FixedMul(a_normal->z, a_line[1].z)) + *originDistance;
 
 	// Positive or zero number means no intersection
 	if (FixedMul(distance1, distance2) >= 0)
@@ -575,8 +610,8 @@ boolean FV3_IntersectedPlane(const vector3_t *a_triangle, const vector3_t *a_lin
 fixed_t FV3_PlaneIntersection(const vector3_t *pOrigin, const vector3_t *pNormal, const vector3_t *rOrigin, const vector3_t *rVector)
 {
 	fixed_t d = -(FV3_Dot(pNormal, pOrigin));
-	fixed_t number = FV3_Dot(pNormal,rOrigin) + d;
-	fixed_t denom = FV3_Dot(pNormal,rVector);
+	fixed_t number = FV3_Dot(pNormal, rOrigin) + d;
+	fixed_t denom = FV3_Dot(pNormal, rVector);
 	return -FixedDiv(number, denom);
 }
 
@@ -597,11 +632,11 @@ fixed_t FV3_IntersectRaySphere(const vector3_t *rO, const vector3_t *rV, const v
 
 	c = FV3_Magnitude(&Q);
 	v = FV3_Dot(&Q, rV);
-	d = FixedMul(sR, sR) - (FixedMul(c,c) - FixedMul(v,v));
+	d = FixedMul(sR, sR) - (FixedMul(c, c) - FixedMul(v, v));
 
 	// If there was no intersection, return -1
-	if (d < 0*FRACUNIT)
-		return (-1*FRACUNIT);
+	if (d < 0 * FRACUNIT)
+		return (-1 * FRACUNIT);
 
 	// Return the distance to the [first] intersecting point
 	return (v - FixedSqrt(d));
@@ -629,9 +664,9 @@ vector3_t *FV3_IntersectionPoint(const vector3_t *vNormal, const vector3_t *vLin
 	//    Here I just chose a arbitrary point as the point to find that distance.  You notice we negate that
 	//    distance.  We negate the distance because we want to eventually go BACKWARDS from our point to the plane.
 	//    By doing this is will basically bring us back to the plane to find our intersection point.
-	Numerator = - (FixedMul(vNormal->x, vLine[0].x) +		// Use the plane equation with the normal and the line
-				   FixedMul(vNormal->y, vLine[0].y) +
-				   FixedMul(vNormal->z, vLine[0].z) + distance);
+	Numerator = -(FixedMul(vNormal->x, vLine[0].x) +		// Use the plane equation with the normal and the line
+		FixedMul(vNormal->y, vLine[0].y) +
+		FixedMul(vNormal->z, vLine[0].z) + distance);
 
 	// 3) If we take the dot product between our line vector and the normal of the polygon,
 	//    this will give us the cosine of the angle between the 2 (since they are both normalized - length 1).
@@ -643,7 +678,7 @@ vector3_t *FV3_IntersectionPoint(const vector3_t *vNormal, const vector3_t *vLin
 	// on the plane (the normal is perpendicular to the line - (Normal.Vector = 0)).
 	// In this case, we should just return any point on the line.
 
-	if( Denominator == 0*FRACUNIT) // Check so we don't divide by zero
+	if (Denominator == 0 * FRACUNIT) // Check so we don't divide by zero
 	{
 		ReturnVec->x = vLine[0].x;
 		ReturnVec->y = vLine[0].y;
@@ -686,8 +721,8 @@ vector3_t *FV3_IntersectionPoint(const vector3_t *vNormal, const vector3_t *vLin
 //
 UINT8 FV3_PointOnLineSide(const vector3_t *point, const vector3_t *line)
 {
-	fixed_t s1 = FixedMul((point->y - line[0].y),(line[1].x - line[0].x));
-	fixed_t s2 = FixedMul((point->x - line[0].x),(line[1].y - line[0].y));
+	fixed_t s1 = FixedMul((point->y - line[0].y), (line[1].x - line[0].x));
+	fixed_t s2 = FixedMul((point->x - line[0].x), (line[1].y - line[0].y));
 	return (UINT8)(s1 - s2 < 0);
 }
 
@@ -752,7 +787,7 @@ void FM_CreateObjectMatrix(matrix_t *matrix, fixed_t x, fixed_t y, fixed_t z, fi
 	matrix->m[0] = upcross.x;
 	matrix->m[1] = upcross.y;
 	matrix->m[2] = upcross.z;
-	matrix->m[3] = 0*FRACUNIT;
+	matrix->m[3] = 0 * FRACUNIT;
 
 	matrix->m[4] = upx;
 	matrix->m[5] = upy;
@@ -764,9 +799,9 @@ void FM_CreateObjectMatrix(matrix_t *matrix, fixed_t x, fixed_t y, fixed_t z, fi
 	matrix->m[10] = anglez;
 	matrix->m[11] = 0;
 
-	matrix->m[12] = x - FixedMul(upx,radius);
-	matrix->m[13] = y - FixedMul(upy,radius);
-	matrix->m[14] = z - FixedMul(upz,radius);
+	matrix->m[12] = x - FixedMul(upx, radius);
+	matrix->m[13] = y - FixedMul(upy, radius);
+	matrix->m[14] = z - FixedMul(upz, radius);
 	matrix->m[15] = FRACUNIT;
 }
 
@@ -778,20 +813,20 @@ void FM_CreateObjectMatrix(matrix_t *matrix, fixed_t x, fixed_t y, fixed_t z, fi
 void FM_MultMatrixVec3(const matrix_t *matrix, const vector3_t *vec, vector3_t *out)
 {
 #define M(row,col)  matrix->m[col * 4 + row]
-	out->x = FixedMul(vec->x,M(0, 0))
-	       + FixedMul(vec->y,M(0, 1))
-	       + FixedMul(vec->z,M(0, 2))
-	       + M(0, 3);
-
-	out->y = FixedMul(vec->x,M(1, 0))
-	       + FixedMul(vec->y,M(1, 1))
-	       + FixedMul(vec->z,M(1, 2))
-	       + M(1, 3);
-
-	out->z = FixedMul(vec->x,M(2, 0))
-	       + FixedMul(vec->y,M(2, 1))
-	       + FixedMul(vec->z,M(2, 2))
-	       + M(2, 3);
+	out->x = FixedMul(vec->x, M(0, 0))
+		+ FixedMul(vec->y, M(0, 1))
+		+ FixedMul(vec->z, M(0, 2))
+		+ M(0, 3);
+
+	out->y = FixedMul(vec->x, M(1, 0))
+		+ FixedMul(vec->y, M(1, 1))
+		+ FixedMul(vec->z, M(1, 2))
+		+ M(1, 3);
+
+	out->z = FixedMul(vec->x, M(2, 0))
+		+ FixedMul(vec->y, M(2, 1))
+		+ FixedMul(vec->z, M(2, 2))
+		+ M(2, 3);
 #undef M
 }
 
@@ -811,7 +846,7 @@ void FM_MultMatrix(matrix_t *dest, const matrix_t *multme)
 	for (i = 0; i < 4; i++)
 	{
 		for (j = 0; j < 4; j++)
-			R(i, j) = FixedMul(D(i, 0),M(0, j)) + FixedMul(D(i, 1),M(1, j)) + FixedMul(D(i, 2),M(2, j)) + FixedMul(D(i, 3),M(3, j));
+			R(i, j) = FixedMul(D(i, 0), M(0, j)) + FixedMul(D(i, 1), M(1, j)) + FixedMul(D(i, 2), M(2, j)) + FixedMul(D(i, 3), M(3, j));
 	}
 
 	M_Memcpy(dest, &result, sizeof(matrix_t));
@@ -869,8 +904,8 @@ void FM_Scale(matrix_t *dest, fixed_t x, fixed_t y, fixed_t z)
 
 static inline void M_print(INT64 a)
 {
-	const fixed_t w = (a>>FRACBITS);
-	fixed_t f = a%FRACUNIT;
+	const fixed_t w = (a >> FRACBITS);
+	fixed_t f = a % FRACUNIT;
 	fixed_t d = FRACUNIT;
 
 	if (f == 0)
@@ -878,7 +913,7 @@ static inline void M_print(INT64 a)
 		printf("%d", (fixed_t)w);
 		return;
 	}
-	else while (f != 1 && f/2 == f>>1)
+	else while (f != 1 && f / 2 == f >> 1)
 	{
 		d /= 2;
 		f /= 2;
@@ -892,7 +927,7 @@ static inline void M_print(INT64 a)
 
 FUNCMATH FUNCINLINE static inline fixed_t FixedMulC(fixed_t a, fixed_t b)
 {
-	return (fixed_t)((((INT64)a * b) ) / FRACUNIT);
+	return (fixed_t)((((INT64)a * b)) / FRACUNIT);
 }
 
 FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
@@ -902,7 +937,7 @@ FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
 	if (b == 0)
 		I_Error("FixedDiv: divide by zero");
 
-	ret = (((INT64)a * FRACUNIT) ) / b;
+	ret = (((INT64)a * FRACUNIT)) / b;
 
 	if ((ret > INT32_MAX) || (ret < INT32_MIN))
 		I_Error("FixedDiv: divide by zero");
@@ -911,7 +946,7 @@ FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
 
 FUNCMATH FUNCINLINE static inline fixed_t FixedDivC(fixed_t a, fixed_t b)
 {
-	if ((abs(a) >> (FRACBITS-2)) >= abs(b))
+	if ((abs(a) >> (FRACBITS - 2)) >= abs(b))
 		return (a^b) < 0 ? INT32_MIN : INT32_MAX;
 
 	return FixedDivC2(a, b);
@@ -938,43 +973,43 @@ int main(int argc, char** argv)
 
 #ifdef MULDIV_TEST
 	for (a = 1; a <= INT32_MAX; a += FRACUNIT)
-	for (b = 0; b <= INT32_MAX; b += FRACUNIT)
-	{
-		c = FixedMul(a, b);
-		d = FixedMulC(a, b);
-		if (c != d)
+		for (b = 0; b <= INT32_MAX; b += FRACUNIT)
 		{
-			printf("(");
-			M_print(a);
-			printf(") * (");
-			M_print(b);
-			printf(") = (");
-			M_print(c);
-			printf(") != (");
-			M_print(d);
-			printf(") \n");
-			n--;
-			printf("%d != %d\n", c, d);
+			c = FixedMul(a, b);
+			d = FixedMulC(a, b);
+			if (c != d)
+			{
+				printf("(");
+				M_print(a);
+				printf(") * (");
+				M_print(b);
+				printf(") = (");
+				M_print(c);
+				printf(") != (");
+				M_print(d);
+				printf(") \n");
+				n--;
+				printf("%d != %d\n", c, d);
+			}
+			c = FixedDiv(a, b);
+			d = FixedDivC(a, b);
+			if (c != d)
+			{
+				printf("(");
+				M_print(a);
+				printf(") / (");
+				M_print(b);
+				printf(") = (");
+				M_print(c);
+				printf(") != (");
+				M_print(d);
+				printf(")\n");
+				n--;
+				printf("%d != %d\n", c, d);
+			}
+			if (n <= 0)
+				exit(-1);
 		}
-		c = FixedDiv(a, b);
-		d = FixedDivC(a, b);
-		if (c != d)
-		{
-			printf("(");
-			M_print(a);
-			printf(") / (");
-			M_print(b);
-			printf(") = (");
-			M_print(c);
-			printf(") != (");
-			M_print(d);
-			printf(")\n");
-			n--;
-			printf("%d != %d\n", c, d);
-		}
-		if (n <= 0)
-			exit(-1);
-	}
 #endif
 
 #ifdef SQRT_TEST
@@ -982,7 +1017,7 @@ int main(int argc, char** argv)
 	{
 		c = FixedSqrt(a);
 		d = FixedSqrtC(a);
-		b = abs(c-d);
+		b = abs(c - d);
 		if (b > 1)
 		{
 			printf("sqrt(");
diff --git a/src/m_fixed.h b/src/m_fixed.h
index d8e722b13c8f0117f10098064973997cf0695df8..370633c1f6b0de4752f575bc3ab5929fd0a329cd 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -389,9 +389,11 @@ boolean FV3_Equal(const vector3_t *a_1, const vector3_t *a_2);
 fixed_t FV3_Dot(const vector3_t *a_1, const vector3_t *a_2);
 vector3_t *FV3_Cross(const vector3_t *a_1, const vector3_t *a_2, vector3_t *a_o);
 vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vector3_t *out);
+void FV3_ClosestPointOnVector(const vector3_t *dir, const vector3_t *p, vector3_t *out);
 void FV3_ClosestPointOnTriangle(const vector3_t *tri, const vector3_t *point, vector3_t *result);
 vector3_t *FV3_Point2Vec(const vector3_t *point1, const vector3_t *point2, vector3_t *a_o);
-void FV3_Normal(const vector3_t *a_triangle, vector3_t *a_normal);
+fixed_t FV3_Normal(const vector3_t *a_triangle, vector3_t *a_normal);
+fixed_t FV3_Strength(const vector3_t *a_1, const vector3_t *dir);
 fixed_t FV3_PlaneDistance(const vector3_t *a_normal, const vector3_t *a_point);
 boolean FV3_IntersectedPlane(const vector3_t *a_triangle, const vector3_t *a_line, vector3_t *a_normal, fixed_t *originDistance);
 fixed_t FV3_PlaneIntersection(const vector3_t *pOrigin, const vector3_t *pNormal, const vector3_t *rOrigin, const vector3_t *rVector);
diff --git a/src/m_menu.c b/src/m_menu.c
index f5c11b817933838f6c03ab25ccc00a9483cb594f..f3528d8e5cb33874a58203e0f832020c7696514f 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1295,18 +1295,21 @@ static menuitem_t OP_ColorOptionsMenu[] =
 #ifdef HWRENDER
 static menuitem_t OP_OpenGLOptionsMenu[] =
 {
-	{IT_STRING|IT_CVAR,         NULL, "Field of view",   &cv_grfov,            10},
-	{IT_STRING|IT_CVAR,         NULL, "Quality",         &cv_scr_depth,        20},
-	{IT_STRING|IT_CVAR,         NULL, "Texture Filter",  &cv_grfiltermode,     30},
-	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",     &cv_granisotropicmode,40},
+	{IT_STRING|IT_CVAR,         NULL, "Models",              &cv_grmodels,             10},
+	{IT_STRING|IT_CVAR,         NULL, "Model interpolation", &cv_grmodelinterpolation, 20},
+
+	{IT_STRING|IT_CVAR,         NULL, "Field of view",   &cv_grfov,            40},
+	{IT_STRING|IT_CVAR,         NULL, "Quality",         &cv_scr_depth,        50},
+	{IT_STRING|IT_CVAR,         NULL, "Texture Filter",  &cv_grfiltermode,     60},
+	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",     &cv_granisotropicmode,70},
 #if defined (_WINDOWS) && (!((defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)))
-	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,       50},
+	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,       80},
 #endif
 #ifdef ALAM_LIGHTING
-	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",     &OP_OpenGLLightingDef,     70},
+	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",     &OP_OpenGLLightingDef,     100},
 #endif
-	{IT_SUBMENU|IT_STRING,      NULL, "Fog...",          &OP_OpenGLFogDef,          80},
-	{IT_SUBMENU|IT_STRING,      NULL, "Gamma...",        &OP_OpenGLColorDef,        90},
+	{IT_SUBMENU|IT_STRING,      NULL, "Fog...",          &OP_OpenGLFogDef,          110},
+	{IT_SUBMENU|IT_STRING,      NULL, "Gamma...",        &OP_OpenGLColorDef,        120},
 };
 
 #ifdef ALAM_LIGHTING
diff --git a/src/p_setup.c b/src/p_setup.c
index 2c5305499a09e697ae3176723fdc6e77e0bdc67c..d844a29e6b0b29247a30315b9f3ad037da7d5fcd 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -75,6 +75,7 @@
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
 #include "hardware/hw_light.h"
+#include "hardware/hw_model.h"
 #endif
 
 #ifdef ESLOPE
@@ -3501,6 +3502,10 @@ boolean P_AddWadFile(const char *wadfilename)
 	if (!mapsadded)
 		CONS_Printf(M_GetText("No maps added\n"));
 
+#ifdef HWRENDER
+	HWR_ReloadModels();
+#endif // HWRENDER
+
 	// reload status bar (warning should have valid player!)
 	if (gamestate == GS_LEVEL)
 		ST_Start();
diff --git a/src/r_main.c b/src/r_main.c
index 9d9d1c39a8c6826b2597070f5fb3d986183b438c..3ed509af568565c95c67a443ab76b5625ff4c840 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1208,7 +1208,6 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_grgammared);
 	CV_RegisterVar(&cv_grfovchange);
 	CV_RegisterVar(&cv_grfog);
-	CV_RegisterVar(&cv_voodoocompatibility);
 	CV_RegisterVar(&cv_grfogcolor);
 	CV_RegisterVar(&cv_grsoftwarefog);
 #ifdef ALAM_LIGHTING
@@ -1217,7 +1216,8 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_grcoronas);
 	CV_RegisterVar(&cv_grcoronasize);
 #endif
-	CV_RegisterVar(&cv_grmd2);
+	CV_RegisterVar(&cv_grmodelinterpolation);
+	CV_RegisterVar(&cv_grmodels);
 	CV_RegisterVar(&cv_grspritebillboarding);
 	CV_RegisterVar(&cv_grskydome);
 #endif
diff --git a/src/r_things.c b/src/r_things.c
index b6833d93216310de84134e8c2b9571a6b6053bd8..404d6a6142c024c5e7b73c831551488d57912940 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -444,7 +444,7 @@ void R_AddSpriteDefs(UINT16 wadnum)
 		{
 #ifdef HWRENDER
 			if (rendermode == render_opengl)
-				HWR_AddSpriteMD2(i);
+				HWR_AddSpriteModel(i);
 #endif
 			// if a new sprite was added (not just replaced)
 			addsprites++;
@@ -2560,7 +2560,7 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 	if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
 		return 0;
 
-	while (!(skin->sprites[spr2].numframes)
+	while (!skin->sprites[spr2].numframes
 		&& spr2 != SPR2_STND
 		&& ++i < 32) // recursion limiter
 	{
@@ -3189,7 +3189,7 @@ next_token:
 
 #ifdef HWRENDER
 		if (rendermode == render_opengl)
-			HWR_AddPlayerMD2(numskins);
+			HWR_AddPlayerModel(numskins);
 #endif
 
 		numskins++;
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 72c38b3dc090061f370d6822d7050912ed2f34ca..0bdc26a127171ba73bcb3e4b692768475fe598a4 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -229,6 +229,10 @@
     <ClInclude Include="..\hardware\hw_light.h" />
     <ClInclude Include="..\hardware\hw_main.h" />
     <ClInclude Include="..\hardware\hw_md2.h" />
+    <ClInclude Include="..\hardware\hw_md2load.h" />
+    <ClInclude Include="..\hardware\hw_md3load.h" />
+    <ClInclude Include="..\hardware\hw_model.h" />
+    <ClInclude Include="..\hardware\u_list.h" />
     <ClInclude Include="..\hu_stuff.h" />
     <ClInclude Include="..\info.h" />
     <ClInclude Include="..\i_addrinfo.h" />
@@ -368,8 +372,12 @@
     <ClCompile Include="..\hardware\hw_light.c" />
     <ClCompile Include="..\hardware\hw_main.c" />
     <ClCompile Include="..\hardware\hw_md2.c" />
+    <ClCompile Include="..\hardware\hw_md2load.c" />
+    <ClCompile Include="..\hardware\hw_md3load.c" />
+    <ClCompile Include="..\hardware\hw_model.c" />
     <ClCompile Include="..\hardware\hw_trick.c" />
     <ClCompile Include="..\hardware\r_opengl\r_opengl.c" />
+    <ClCompile Include="..\hardware\u_list.c" />
     <ClCompile Include="..\hu_stuff.c" />
     <ClCompile Include="..\info.c" />
     <ClCompile Include="..\i_addrinfo.c">
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 9e442000fdc1a5ad0a2e6643cd06eb99ddd8cf11..a2110822edbbe45eb4bcb016b3bed02c33c09784 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -246,6 +246,18 @@
     <ClInclude Include="..\hardware\hw_md2.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_md2load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_md3load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_model.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\u_list.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\byteptr.h">
       <Filter>I_Interface</Filter>
     </ClInclude>
@@ -630,9 +642,21 @@
     <ClCompile Include="..\hardware\hw_md2.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_md2load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_md3load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_model.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\hardware\hw_trick.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\u_list.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\filesrch.c">
       <Filter>I_Interface</Filter>
     </ClCompile>
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 103398405f1e98518b65157859f39a981836fca1..5f040023a8032e1086ebec70f8a28891a0b741e1 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -88,13 +88,11 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(ClearMipMapCache);
 	GETFUNC(SetSpecialState);
 	GETFUNC(GetTextureUsed);
-	GETFUNC(DrawMD2);
-	GETFUNC(DrawMD2i);
+	GETFUNC(DrawModel);
+	GETFUNC(CreateModelVBOs);
 	GETFUNC(SetTransform);
 	GETFUNC(GetRenderVersion);
-#ifdef SHUFFLE
 	GETFUNC(PostImgRedraw);
-#endif //SHUFFLE
 	GETFUNC(FlushScreenTextures);
 	GETFUNC(StartScreenWipe);
 	GETFUNC(EndScreenWipe);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 57591af104922eecc821dea8d3973545b6db7775..fb0f4b2ba3eaea01e6881a77f2717672860cb21f 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1654,13 +1654,11 @@ void I_StartupGraphics(void)
 		HWD.pfnSetSpecialState  = hwSym("SetSpecialState",NULL);
 		HWD.pfnSetPalette       = hwSym("SetPalette",NULL);
 		HWD.pfnGetTextureUsed   = hwSym("GetTextureUsed",NULL);
-		HWD.pfnDrawMD2          = hwSym("DrawMD2",NULL);
-		HWD.pfnDrawMD2i         = hwSym("DrawMD2i",NULL);
+		HWD.pfnDrawModel        = hwSym("DrawModel",NULL);
+		HWD.pfnCreateModelVBOs  = hwSym("CreateModelVBOs",NULL);
 		HWD.pfnSetTransform     = hwSym("SetTransform",NULL);
 		HWD.pfnGetRenderVersion = hwSym("GetRenderVersion",NULL);
-#ifdef SHUFFLE
 		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
-#endif
 		HWD.pfnFlushScreenTextures=hwSym("FlushScreenTextures",NULL);
 		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
 		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
diff --git a/src/v_video.c b/src/v_video.c
index 34d64cb04bca5c1d33119ff767038965bf2cd665..7f99cfd326c8089b878e4624a90c9f0e295edf87 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -89,8 +89,8 @@ static void CV_Gammaxxx_ONChange(void);
 // but they won't do anything.
 static CV_PossibleValue_t grgamma_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t grsoftwarefog_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "LightPlanes"}, {0, NULL}};
+static CV_PossibleValue_t grmodelinterpolation_cons_t[] = {{0, "Off"}, {1, "Sometimes"}, {2, "Always"}, {0, NULL}};
 
-consvar_t cv_voodoocompatibility = {"gr_voodoocompatibility", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grfovchange = {"gr_fovchange", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grfog = {"gr_fog", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grfogcolor = {"gr_fogcolor", "AAAAAA", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -108,9 +108,9 @@ consvar_t cv_grcoronas = {"gr_coronas", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL,
 consvar_t cv_grcoronasize = {"gr_coronasize", "1", CV_SAVE| CV_FLOAT, 0, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
-static CV_PossibleValue_t CV_MD2[] = {{0, "Off"}, {1, "On"}, {2, "Old"}, {0, NULL}};
-// console variables in development
-consvar_t cv_grmd2 = {"gr_md2", "Off", CV_SAVE, CV_MD2, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grmodels = {"gr_models", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grmodelinterpolation = {"gr_modelinterpolation", "Sometimes", CV_SAVE, grmodelinterpolation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 consvar_t cv_grspritebillboarding = {"gr_spritebillboarding", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grskydome = {"gr_skydome", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
diff --git a/src/w_wad.c b/src/w_wad.c
index 18781070c521736ad14f910813b9cbca4289dc32..ea9310580a08dd2e6d97302023d06d96866e4f6c 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1679,12 +1679,12 @@ void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5)
 	{
 		char actualmd5text[2*MD5_LEN+1];
 		PrintMD5String(wadfiles[wadfilenum]->md5sum, actualmd5text);
-#ifdef _DEBUG
+/*#ifdef _DEBUG
 		CONS_Printf
 #else
 		I_Error
 #endif
-			(M_GetText("File is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);
+			(M_GetText("File is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);*/
 	}
 #endif
 }
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index c0fe8eda9ad5f2d73f7a0c2cd7f1a2023a02dd0e..dea71925c09796e93ae577fad417b9a2bbc3d3a2 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -231,7 +231,11 @@
     <ClCompile Include="..\hardware\hw_light.c" />
     <ClCompile Include="..\hardware\hw_main.c" />
     <ClCompile Include="..\hardware\hw_md2.c" />
+    <ClCompile Include="..\hardware\hw_md2load.c" />
+    <ClCompile Include="..\hardware\hw_md3load.c" />
+    <ClCompile Include="..\hardware\hw_model.c" />
     <ClCompile Include="..\hardware\hw_trick.c" />
+    <ClCompile Include="..\hardware\u_list.c" />
     <ClCompile Include="..\hu_stuff.c" />
     <ClCompile Include="..\info.c" />
     <ClCompile Include="..\i_addrinfo.c">
@@ -396,6 +400,10 @@
     <ClInclude Include="..\hardware\hw_light.h" />
     <ClInclude Include="..\hardware\hw_main.h" />
     <ClInclude Include="..\hardware\hw_md2.h" />
+    <ClInclude Include="..\hardware\hw_md2load.h" />
+    <ClInclude Include="..\hardware\hw_md3load.h" />
+    <ClInclude Include="..\hardware\hw_model.h" />
+    <ClInclude Include="..\hardware\u_list.h" />
     <ClInclude Include="..\hu_stuff.h" />
     <ClInclude Include="..\info.h" />
     <ClInclude Include="..\i_addrinfo.h" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index 93806e3951066fff4172cdc9f60a0315171e74f4..c7e93ddd9a257feb994a025d4ffd74b328b723f2 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -453,6 +453,18 @@
     <ClCompile Include="..\string.c">
       <Filter>M_Misc</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_md2load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_md3load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_model.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\u_list.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\hardware\hw_clip.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
@@ -513,6 +525,15 @@
     <ClInclude Include="..\hardware\hw_md2.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_md2load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_md3load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_model.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\hardware\hw3dsdrv.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
@@ -522,6 +543,9 @@
     <ClInclude Include="..\hardware\hws_data.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\u_list.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\blua\lapi.h">
       <Filter>BLUA</Filter>
     </ClInclude>
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index 4ea792a250d353596a20dfb67b006b9ada06ad7f..5378bb52f645bc10abc0c58dcf27d38f15d9b85a 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -110,8 +110,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"GClipRect@20",        &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache@0",  &hwdriver.pfnClearMipMapCache},
 	{"SetSpecialState@8",   &hwdriver.pfnSetSpecialState},
-	{"DrawMD2@16",          &hwdriver.pfnDrawMD2},
-	{"DrawMD2i@36",         &hwdriver.pfnDrawMD2i},
+	{"DrawModel@16",          &hwdriver.pfnDrawModel},
 	{"SetTransform@4",      &hwdriver.pfnSetTransform},
 	{"GetTextureUsed@0",    &hwdriver.pfnGetTextureUsed},
 	{"GetRenderVersion@0",  &hwdriver.pfnGetRenderVersion},
@@ -142,8 +141,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"GClipRect",           &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache",    &hwdriver.pfnClearMipMapCache},
 	{"SetSpecialState",     &hwdriver.pfnSetSpecialState},
-	{"DrawMD2",             &hwdriver.pfnDrawMD2},
-	{"DrawMD2i",            &hwdriver.pfnDrawMD2i},
+	{"DrawModel",           &hwdriver.pfnDrawModel},
 	{"SetTransform",        &hwdriver.pfnSetTransform},
 	{"GetTextureUsed",      &hwdriver.pfnGetTextureUsed},
 	{"GetRenderVersion",    &hwdriver.pfnGetRenderVersion},