diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a6fab34ff621af052079ac5c8acb145f7ab8f65e..85cda35f665e6a6f4bf0293f107e0e3740429107 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -399,7 +399,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
@@ -413,6 +417,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 6363ab7dc2cf907db106e9f0ffae5813044732cf..6f7927077b61f69ffd0a4bb3cd42f17d556761cb 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -279,7 +279,8 @@ ifndef DC
 endif
 	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
@@ -732,16 +733,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
@@ -893,24 +896,27 @@ 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 $@
 
 $(OBJDIR)/r_minigl.o: hardware/r_minigl/r_minigl.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/doomtype.h b/src/doomtype.h
index b44e32e46ccb1391ee5d1294d41b72dd52a117eb..dd4268cdfc4c891cd1ddc8b3e0ed50cb154cb9a4 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -366,16 +366,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/hardware/hw_defs.h b/src/hardware/hw_defs.h
index b0ac599e19ac87bf2d611c5da13a226c2d21d10b..4fd2fd1831da86352c933b2c03b0193e74515bdf 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -89,14 +89,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
@@ -108,14 +123,6 @@ typedef struct
 	FUINT       argb;           // flat-shaded color
 } FOutVector;
 
-// jimita
-typedef struct
-{
-	float       vx, vy, vz;
-	float       nx, ny, nz;
-	float       s, t;
-} FOutVectorMD2;
-
 // ==========================================================================
 //                                                               RENDER MODES
 // ==========================================================================
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index e0def221d23d90d0efd5a30c197803f2008c53f1..9cefff68de6f956c13920781204cd3a652a525e9 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -52,7 +52,8 @@ 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, INT32 duration, INT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, FSurfaceInfo *Surface);
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, FSurfaceInfo *Surface);
+EXPORT void HWRAPI(CreateModelVBOs) (model_t *model);
 EXPORT void HWRAPI(SetTransform) (FTransform *ptransform);
 EXPORT INT32 HWRAPI(GetTextureUsed) (void);
 EXPORT INT32 HWRAPI(GetRenderVersion) (void);
@@ -95,7 +96,8 @@ struct hwdriver_s
 	GClipRect           pfnGClipRect;
 	ClearMipMapCache    pfnClearMipMapCache;
 	SetSpecialState     pfnSetSpecialState;
-	DrawMD2             pfnDrawMD2;
+	DrawModel           pfnDrawModel;
+	CreateModelVBOs     pfnCreateModelVBOs;
 	SetTransform        pfnSetTransform;
 	GetTextureUsed      pfnGetTextureUsed;
 	GetRenderVersion    pfnGetRenderVersion;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 707c16fb91a16784fdfb2d1cc5857708ae0b0174..708ac4a3b60c81ef3bcdc54e1f03d16be15928cc 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -35,6 +35,9 @@
 #include "../w_wad.h"
 #include "../z_zone.h"
 #include "../r_things.h"
+#include "../r_draw.h"
+#include "../p_tick.h"
+#include "hw_model.h"
 
 #include "hw_main.h"
 #include "../v_video.h"
@@ -67,171 +70,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];
@@ -240,194 +78,24 @@ 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->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);
-			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;
@@ -882,7 +550,7 @@ void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 		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, skins[skin].name) == 0)
@@ -1195,30 +863,21 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, con
 //                  : (monsters, bonuses, weapons, lights, ...)
 // Returns          :
 // -----------------+
-	/*
-	wait/stand
-	death
-	pain
-	walk
-	shoot/fire
-
-	die?
-	atka?
-	atkb?
-	attacka/b/c/d?
-	res?
-	run?
-	*/
+
+// hw_main.c
 #define NORMALFOG 0x00000000
 #define FADEFOG 0x19000000
+
 void HWR_DrawMD2(gr_vissprite_t *spr)
 {
-	FSurfaceInfo Surf;
+	md2_t *md2;
 
 	char filename[64];
-	INT32 frame;
+	INT32 frame = 0;
+	INT32 nextFrame = -1;
+
 	FTransform p;
-	md2_t *md2;
+	FSurfaceInfo Surf;
 
 	if (!cv_grmd2.value)
 		return;
@@ -1263,10 +922,9 @@ 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) == MFE_VERTICALFLIP);
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
@@ -1307,6 +965,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			if (md2->model)
 			{
 				md2_printModelInfo(md2->model);
+				HWD.pfnCreateModelVBOs(md2->model);
 			}
 			else
 			{
@@ -1355,30 +1014,30 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 		}
 
 		//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];
+		frame = (spr->mobj->frame & FF_FRAMEMASK) % md2->model->meshes[0].numFrames;
+#ifdef USE_MODEL_NEXTFRAME
 		if (cv_grmd2.value == 1 && tics <= durs)
 		{
 			// 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 >= spr->mobj->state->var1)
+					nextFrame = (spr->mobj->state->frame & FF_FRAMEMASK);
+				nextFrame %= md2->model->meshes[0].numFrames;
+				//next = &md2->model->meshes[0].frames[nextFrame];
 			}
 			else
 			{
 				if (spr->mobj->state->nextstate != S_NULL && states[spr->mobj->state->nextstate].sprite != SPR_NULL
 					&& !(spr->mobj->player && (spr->mobj->state->nextstate == S_PLAY_TAP1 || spr->mobj->state->nextstate == S_PLAY_TAP2) && spr->mobj->state == &states[S_PLAY_STND]))
 				{
-					const UINT32 nextframe = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->header.numFrames;
-					next = &md2->model->frames[nextframe];
+					nextFrame = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->meshes[0].numFrames;
+					//next = &md2->model->meshes[0].frames[nextFrame];
 				}
 			}
 		}
+#endif
 
 		//Hurdler: it seems there is still a small problem with mobj angle
 		p.x = FIXED_TO_FLOAT(spr->mobj->x);
@@ -1398,7 +1057,14 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 
 		if (sprframe->rotate)
 		{
-			const fixed_t anglef = AngleFixed(spr->mobj->angle);
+			fixed_t anglef = AngleFixed(spr->mobj->angle);
+			// \todo adapt for 2.2 directionchar? The below code is from Kart
+#if 0
+			if (spr->mobj->player)
+				anglef = AngleFixed(spr->mobj->player->frameangle);
+			else
+				anglef = AngleFixed(spr->mobj->angle);
+#endif
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 		else
@@ -1407,13 +1073,30 @@ 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
 
 		// SRB2CBTODO: MD2 scaling support
 		finalscale *= FIXED_TO_FLOAT(spr->mobj->scale);
 
 		p.flip = atransform.flip;
+#ifdef USE_FTRANSFORM_MIRROR
+		p.mirror = atransform.mirror; // from Kart
+#endif
 
-		HWD.pfnDrawMD2(buff, curr, durs, tics, next, &p, finalscale, flip, &Surf);
+		HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, finalscale, flip, &Surf);
 	}
 }
 
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 070886431742986cfb1588e28a77f3efec6b9f46..fab8e6ad90ab0714b4a2c16016224e0e3b1e7c40 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -16,97 +16,7 @@
 #define _HW_MD2_H_
 
 #include "hw_glob.h"
-
-// 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;
-	INT32                     *glCommandBuffer;
-} ATTRPACK md2_model_t;
+#include "hw_model.h"
 
 #if defined(_MSC_VER)
 #pragma pack()
@@ -117,7 +27,7 @@ typedef struct
 	char        filename[32];
 	float       scale;
 	float       offset;
-	md2_model_t *model;
+	model_t     *model;
 	void        *grpatch;
 	void        *blendgrpatch;
 	boolean     notfound;
diff --git a/src/hardware/hw_md2load.c b/src/hardware/hw_md2load.c
new file mode 100644
index 0000000000000000000000000000000000000000..3805deffb0657651a7157b753632ad7841259098
--- /dev/null
+++ b/src/hardware/hw_md2load.c
@@ -0,0 +1,564 @@
+/*
+	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;
+
+	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];
+
+	// 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..942aac1adc5b2756c1ef500619fafc91fb2db770
--- /dev/null
+++ b/src/hardware/hw_md2load.h
@@ -0,0 +1,20 @@
+/*
+	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..53f6034c046b9c02d87f04ee67f00bf15b57e819
--- /dev/null
+++ b/src/hardware/hw_md3load.c
@@ -0,0 +1,510 @@
+/*
+	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;
+	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);
+
+	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..2c36f974414df2b1319c5a774602483a4ecd6190
--- /dev/null
+++ b/src/hardware/hw_model.c
@@ -0,0 +1,593 @@
+/*
+	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 "../z_zone.h"
+#include "../doomdef.h"
+#include "hw_model.h"
+#include "hw_md2load.h"
+#include "hw_md3load.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);
+
+	// 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;
+}
+
+//
+// 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..1803f4c5c32f2e93dfe52a440c58742c8fd8d9cf
--- /dev/null
+++ b/src/hardware/hw_model.h
@@ -0,0 +1,104 @@
+/*
+	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;
+
+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;
+} model_t;
+
+extern int numModels;
+extern model_t *modelHead;
+
+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 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/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 645d934efc6fd9a52c54962b4b494ec804dd7e1f..25852c534e5c7d6ae1453d8fadbe3f49a67f5b87 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -24,9 +24,9 @@
 #include <stdarg.h>
 #include <math.h>
 #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
 {
@@ -36,6 +36,7 @@ struct GLRGBAFloat
 	GLfloat alpha;
 };
 typedef struct GLRGBAFloat GLRGBAFloat;
+static const GLubyte white[4] = { 255, 255, 255, 255 };
 
 // ==========================================================================
 //                                                                  CONSTANTS
@@ -94,10 +95,10 @@ static GLint viewport[4];
 //			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;
@@ -203,13 +204,19 @@ FUNCPRINTF void GL_DBG_Printf(const char *format, ...)
 #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 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
@@ -297,20 +304,30 @@ 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 * 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);
@@ -363,6 +380,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 */
@@ -424,13 +445,18 @@ boolean SetupGLfunc(void)
 	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(pglDrawArrays, glDrawArrays)
+	GETOPENGLFUNC(pglDrawElements, glDrawElements)
+	GETOPENGLFUNC(pglEnableClientState, glEnableClientState)
+	GETOPENGLFUNC(pglDisableClientState, glDisableClientState)
+	GETOPENGLFUNC(pglClientActiveTexture, glClientActiveTexture)
+	if (!pglClientActiveTexture)
+		GETOPENGLFUNC(pglClientActiveTexture, glClientActiveTextureARB)
 
 	GETOPENGLFUNC(pglShadeModel, glShadeModel)
 	GETOPENGLFUNC(pglLightfv, glLightfv)
@@ -471,49 +497,8 @@ boolean GLEXT_shaders = false;
 // hw_glob.h
 INT32 gl_leveltime = 0;
 
-//#define GL_RETAINED_MODE			// Immediate mode is faster. Go fucking figure.
 #define USE_SHADERS
 
-typedef void (APIENTRY *PFNglDrawArrays) (GLenum, GLint, GLsizei);
-typedef void (APIENTRY *PFNglDrawElements) (GLenum, GLsizei, GLenum, const GLvoid*);
-typedef void (APIENTRY *PFNglVertexAttribPointer) (GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid*);
-typedef void (APIENTRY *PFNglEnableClientState) (GLenum);
-typedef void (APIENTRY *PFNglDisableClientState) (GLenum);
-typedef void (APIENTRY *PFNglBindVertexArray) (GLuint);
-typedef void (APIENTRY *PFNglGenVertexArrays) (GLsizei, GLuint*);
-typedef void (APIENTRY *PFNglGenBuffers) (GLsizei, GLuint*);
-typedef void (APIENTRY *PFNglBindBuffer) (GLenum, GLuint);
-typedef void (APIENTRY *PFNglBufferData) (GLenum, GLsizeiptr, GLvoid*, GLenum);
-typedef void (APIENTRY *PFNglBufferSubData) (GLenum, GLintptr, GLsizeiptr, GLvoid*);
-typedef void (APIENTRY *PFNglDeleteBuffers) (GLsizei, GLuint*);
-typedef void (APIENTRY *PFNglDeleteVertexArrays) (GLsizei, GLuint*);
-typedef void (APIENTRY *PFNglEnableVertexAttribArray) (GLuint);
-typedef void (APIENTRY *PFNglDisableVertexAttribArray) (GLuint);
-
-static PFNglDrawArrays pglDrawArrays;
-static PFNglDrawElements pglDrawElements;
-static PFNglVertexAttribPointer pglVertexAttribPointer;
-static PFNglEnableClientState pglEnableClientState;
-static PFNglDisableClientState pglDisableClientState;
-static PFNglBindVertexArray pglBindVertexArray;
-static PFNglGenVertexArrays pglGenVertexArrays;
-static PFNglGenBuffers pglGenBuffers;
-static PFNglBindBuffer pglBindBuffer;
-static PFNglBufferData pglBufferData;
-static PFNglBufferSubData pglBufferSubData;
-static PFNglDeleteBuffers pglDeleteBuffers;
-static PFNglDeleteVertexArrays pglDeleteVertexArrays;
-static PFNglEnableVertexAttribArray pglEnableVertexAttribArray;
-static PFNglDisableVertexAttribArray pglDisableVertexAttribArray;
-
-typedef void (APIENTRY *PFNglVertexPointer) (GLint, GLenum, GLsizei, const GLvoid*);
-typedef void (APIENTRY *PFNglNormalPointer) (GLenum, GLsizei, const GLvoid*);
-typedef void (APIENTRY *PFNglTexCoordPointer) (GLint, GLenum, GLsizei, const GLvoid*);
-
-static PFNglVertexPointer pglVertexPointer;
-static PFNglNormalPointer pglNormalPointer;
-static PFNglTexCoordPointer pglTexCoordPointer;
-
 // shaders
 #ifdef USE_SHADERS
 
@@ -667,39 +652,18 @@ static char *fragment_shaders[] = {
 	"}\0",
 };
 
-// Macro to reduce boilerplate code
-#define BUFFER_OFFSET(i) ((void*)(i))
-
 #endif	// USE_SHADERS
 
-static GLuint gl_vertexarrayobject;
-static GLuint gl_vertexbuffer;
-
 void SetupGLFunc4(void)
 {
 	pglActiveTexture = GetGLFunc("glActiveTexture");
 	pglMultiTexCoord2f = GetGLFunc("glMultiTexCoord2f");
-
-	// 4.x
-	pglDrawArrays = GetGLFunc("glDrawArrays");
-	pglDrawElements = GetGLFunc("glDrawElements");
-	pglVertexAttribPointer = GetGLFunc("glVertexAttribPointer");
-	pglEnableClientState = GetGLFunc("glEnableClientState");
-	pglDisableClientState = GetGLFunc("glDisableClientState");
-	pglBindVertexArray = GetGLFunc("glBindVertexArray");
-	pglGenVertexArrays = GetGLFunc("glGenVertexArrays");
+	pglClientActiveTexture = GetGLFunc("glClientActiveTexture");
+	pglMultiTexCoord2fv = GetGLFunc("glMultiTexCoord2fv");
 	pglGenBuffers = GetGLFunc("glGenBuffers");
 	pglBindBuffer = GetGLFunc("glBindBuffer");
 	pglBufferData = GetGLFunc("glBufferData");
-	pglBufferSubData = GetGLFunc("glBufferSubData");
 	pglDeleteBuffers = GetGLFunc("glDeleteBuffers");
-	pglDeleteVertexArrays = GetGLFunc("glDeleteVertexArrays");
-	pglEnableVertexAttribArray = GetGLFunc("glEnableVertexAttribArray");
-	pglDisableVertexAttribArray = GetGLFunc("glDisableVertexAttribArray");
-
-	pglVertexPointer = GetGLFunc("glVertexPointer");
-	pglNormalPointer = GetGLFunc("glNormalPointer");
-	pglTexCoordPointer = GetGLFunc("glTexCoordPointer");
 
 #ifdef USE_SHADERS
 	pglCreateShader = GetGLFunc("glCreateShader");
@@ -881,6 +845,12 @@ void SetModelView(GLint w, GLint h)
 // -----------------+
 void SetStates(void)
 {
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
+#endif
+
+	pglShadeModel(GL_SMOOTH);      // iterate vertice colors
+
 	pglEnable(GL_TEXTURE_2D);      // two-dimensional texturing
 	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
@@ -904,25 +874,16 @@ void SetStates(void)
 
 	pglPolygonOffset(-1.0f, -1.0f);
 
+	// Lighting for models
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	pglLightModelfv(GL_LIGHT_MODEL_AMBIENT, LightDiffuse);
+	pglEnable(GL_LIGHT0);
+#endif
+
 	// bp : when no t&l :)
 	pglLoadIdentity();
 	pglScalef(1.0f, 1.0f, -1.0f);
 	pglGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
-
-	// jimita
-#ifndef GL_RETAINED_MODE
-	if (!GLEXT_legacy)
-		I_Error("GPU does not support legacy specifications");
-#endif
-
-	pglDeleteVertexArrays(1, &gl_vertexarrayobject);
-	pglDeleteBuffers(1, &gl_vertexbuffer);
-
-	pglGenVertexArrays(1, &gl_vertexarrayobject);
-	pglGenBuffers(1, &gl_vertexbuffer);
-
-	pglBindVertexArray(gl_vertexarrayobject);
-	pglBindBuffer(GL_ARRAY_BUFFER, gl_vertexbuffer);
 }
 
 
@@ -1100,6 +1061,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
 }
 
 
@@ -1110,26 +1073,35 @@ EXPORT void HWRAPI(Draw2DLine) (F2DCoord * v1,
                                    F2DCoord * v2,
                                    RGBA_t Color)
 {
-	GLRGBAFloat c;
-
 	//GL_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;
 
-	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();
+	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;
 
+	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);
 }
 
@@ -1176,7 +1148,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;
@@ -1240,7 +1212,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
@@ -1585,9 +1557,6 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUI
 {
 	static GLRGBAFloat mix = {0,0,0,0};
 	static GLRGBAFloat fade = {0,0,0,0};
-#ifndef GL_RETAINED_MODE
-	FUINT i;
-#endif
 
 	SetBlend(PolyFlags);    //TODO: inline (#pragma..)
 
@@ -1603,7 +1572,7 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUI
 			mix.blue   = byte2float[pSurf->PolyColor.s.blue];
 			mix.alpha  = byte2float[pSurf->PolyColor.s.alpha];
 
-			pglColor4fv(&mix.red);
+			pglColor4ubv((GLubyte*)&pSurf->PolyColor.s);
 		}
 
 		// Fade color
@@ -1616,24 +1585,9 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUI
 	// jimita
 	load_shaders(pSurf, &mix, &fade);
 
-#ifdef GL_RETAINED_MODE
-	pglBufferData(GL_ARRAY_BUFFER, sizeof(FOutVector)*iNumPts, &pOutVerts[0].x, GL_STATIC_DRAW);
-
-	pglEnableClientState(GL_VERTEX_ARRAY);
-	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
-
-	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), BUFFER_OFFSET(0));
-	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), BUFFER_OFFSET(sizeof(FLOAT)*3));
+	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].sow);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, iNumPts);
-#else
-	pglBegin(GL_TRIANGLE_FAN);
-	for (i = 0; i < iNumPts; i++)
-	{
-		pglTexCoord2f(pOutVerts[i].sow, pOutVerts[i].tow);
-		pglVertex3f(pOutVerts[i].x, pOutVerts[i].y, pOutVerts[i].z);
-	}
-	pglEnd();
-#endif
 
 	if (PolyFlags & PF_RemoveYWrap)
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
@@ -1644,11 +1598,6 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUI
 	if (PolyFlags & PF_ForceWrapY)
 		Clamp2D(GL_TEXTURE_WRAP_T);
 
-#ifdef GL_RETAINED_MODE
-	pglDisableClientState(GL_VERTEX_ARRAY);
-	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
-#endif
-
 #ifdef USE_SHADERS
 	pglUseProgram(0);
 #endif
@@ -1723,17 +1672,225 @@ 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, FSurfaceInfo *Surface)
+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)
 {
-	INT32 val, count, pindex;
-	GLfloat s, t;
+	if (lerpBufferSize >= size)
+		return;
 
-	float pol = 0.0f;
-	float scalex = scale, scaley = scale, scalez = scale;
+	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);
+}
+
+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);
+}
+
+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);
+			}
+		}
+	}
+}
+
+// Macro to
+#define BUFFER_OFFSET(i) ((void*)(i))
+
+static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, FSurfaceInfo *Surface)
+{
 	static GLRGBAFloat mix = {0,0,0,0};
 	static GLRGBAFloat fade = {0,0,0,0};
 
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	GLfloat ambient[4];
+	GLfloat diffuse[4];
+#endif
+
+	float pol = 0.0f;
+	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;
@@ -1752,6 +1909,25 @@ static void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	mix.blue   = byte2float[Surface->PolyColor.s.blue];
 	mix.alpha  = byte2float[Surface->PolyColor.s.alpha];
 
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	ambient[0] = (mix.red/255.0f);
+	ambient[1] = (mix.green/255.0f);
+	ambient[2] = (mix.blue/255.0f);
+	ambient[3] = (mix.alpha/255.0f);
+
+	diffuse[0] = (mix.red/255.0f);
+	diffuse[1] = (mix.green/255.0f);
+	diffuse[2] = (mix.blue/255.0f);
+	diffuse[3] = (mix.alpha/255.0f);
+
+	if (ambient[0] > 0.75f)
+		ambient[0] = 0.75f;
+	if (ambient[1] > 0.75f)
+		ambient[1] = 0.75f;
+	if (ambient[2] > 0.75f)
+		ambient[2] = 0.75f;
+#endif
+
 	if (mix.alpha < 1)
 		SetBlend(PF_Translucent|PF_Modulated);
 	else
@@ -1762,151 +1938,158 @@ static void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	fade.blue  = byte2float[Surface->FadeColor.s.blue];
 	fade.alpha = byte2float[Surface->FadeColor.s.alpha];
 
+	// jimita
+	load_shaders(Surface, &mix, &fade);
+
 	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
 		pglCullFace(GL_FRONT);
 	else
 		pglCullFace(GL_BACK);
+#endif
 
-	pglPushMatrix();
+	pglLightfv(GL_LIGHT0, GL_POSITION, LightPos);
+	pglShadeModel(GL_SMOOTH);
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	pglEnable(GL_LIGHTING);
+	pglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
+	pglMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
+#endif
+
+	pglPushMatrix(); // should be the same as glLoadIdentity
 	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);
 
-	// jimita
-	load_shaders(Surface, &mix, &fade);
-
-	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVectorMD2), BUFFER_OFFSET(0));
-	pglNormalPointer(GL_FLOAT, sizeof(FOutVectorMD2), BUFFER_OFFSET(sizeof(float)*3));
-	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVectorMD2), BUFFER_OFFSET(sizeof(float)*6));
+	pglScalef(scalex, scaley, scalez);
 
-	val = *gl_cmd_buffer++;
+	useTinyFrames = model->meshes[0].tinyframes != NULL;
 
-	while (val != 0)
-	{
-#ifdef GL_RETAINED_MODE
-		FOutVectorMD2 *polydata;
-		int polyindex = 0;
-#endif
-		int drawarraytype;
+	if (useTinyFrames)
+		pglScalef(1 / 64.0f, 1 / 64.0f, 1 / 64.0f);
 
-		if (val < 0)
-		{
-			drawarraytype = GL_TRIANGLE_FAN;
-			count = -val;
-		}
-		else
-		{
-			drawarraytype = GL_TRIANGLE_STRIP;
-			count = val;
-		}
+	pglEnableClientState(GL_NORMAL_ARRAY);
 
-#ifndef GL_RETAINED_MODE
-		pglBegin(drawarraytype);
-#else
-		polydata = malloc(sizeof(FOutVectorMD2) * count);
-#endif
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		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;
 
-#ifdef GL_RETAINED_MODE
-			polydata[polyindex].s = s;
-			polydata[polyindex].t = t;
-#else
-			pglTexCoord2f(s, t);
-#endif
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->tinyframes[nextFrameIndex % mesh->numFrames];
 
 			if (!nextframe || fpclassify(pol) == FP_ZERO)
 			{
-#ifdef GL_RETAINED_MODE
-				polydata[polyindex].vx = frame->vertices[pindex].vertex[0]*scalex/2.0f;
-				polydata[polyindex].vy = frame->vertices[pindex].vertex[1]*scaley/2.0f;
-				polydata[polyindex].vz = frame->vertices[pindex].vertex[2]*scalez/2.0f;
-
-				polydata[polyindex].nx = frame->vertices[pindex].normal[0];
-				polydata[polyindex].ny = frame->vertices[pindex].normal[1];
-				polydata[polyindex].nz = frame->vertices[pindex].normal[2];
-
-				polyindex++;
-#else
-
-				pglNormal3f(frame->vertices[pindex].normal[0],
-				            frame->vertices[pindex].normal[1],
-				            frame->vertices[pindex].normal[2]);
-
-				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);
-#endif
+				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));
+				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];
-
-#ifdef GL_RETAINED_MODE
-				polydata[polyindex].vx = (px1 + pol * (px2 - px1));
-				polydata[polyindex].vy = (py1 + pol * (py2 - py1));
-				polydata[polyindex].vz = (pz1 + pol * (pz2 - pz1));
-
-				polydata[polyindex].nx = (nx1 + pol * (nx2 - nx1));
-				polydata[polyindex].ny = (ny1 + pol * (ny2 - ny1));
-				polydata[polyindex].nz = (nz1 + pol * (nz2 - nz1));
-
-				polyindex++;
-#else
-				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)));
-#endif
+				short *vertPtr;
+				char *normPtr;
+				int j = 0;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				AllocLerpTinyBuffer(mesh->numVertices * sizeof(short) * 3);
+				vertPtr = vertTinyBuffer;
+				normPtr = normTinyBuffer;
+
+				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;
 
-#ifdef GL_RETAINED_MODE
-		pglEnableClientState(GL_VERTEX_ARRAY);
-		pglEnableClientState(GL_NORMAL_ARRAY);
-		pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->frames[nextFrameIndex % mesh->numFrames];
 
-		pglBufferData(GL_ARRAY_BUFFER, sizeof(FOutVectorMD2)*polyindex, &polydata[0].vx, GL_DYNAMIC_DRAW);
+			if (!nextframe || pol == 0.0f)
+			{
+				// Zoom! Take advantage of just shoving the entire arrays to the GPU.
+				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);
+				pglBindBuffer(GL_ARRAY_BUFFER, 0);
+			}
+			else
+			{
+				float *vertPtr;
+				float *normPtr;
+				int j = 0;
 
-		pglDrawArrays(drawarraytype, 0, polyindex);
+				// Dangit, I soooo want to do this in a GLSL shader...
+				AllocLerpBuffer(mesh->numVertices * sizeof(float) * 3);
+				vertPtr = vertBuffer;
+				normPtr = normBuffer;
 
-		free(polydata);
-#else
-		pglEnd();
-#endif
+				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);
+			}
+		}
 	}
 
-	pglPopMatrix();
-	pglDisable(GL_CULL_FACE);
-
-	pglDisableClientState(GL_VERTEX_ARRAY);
 	pglDisableClientState(GL_NORMAL_ARRAY);
-	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+
+	pglPopMatrix(); // should be the same as glLoadIdentity
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	pglDisable(GL_LIGHTING);
+#endif
+	pglShadeModel(GL_FLAT);
+	pglDisable(GL_CULL_FACE);
+	pglDisable(GL_NORMALIZE);
 
 #ifdef USE_SHADERS
 	pglUseProgram(0);
@@ -1914,15 +2097,11 @@ static void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 }
 
 // -----------------+
-// HWRAPI DrawMD2   : Draw an MD2 model with glcommands
+// HWRAPI DrawModel : Draw a model
 // -----------------+
-EXPORT void HWRAPI(DrawMD2) (
-							INT32 *gl_cmd_buffer, md2_frame_t *frame,
-							INT32 duration, INT32 tics, md2_frame_t *nextframe,
-							FTransform *pos, float scale, UINT8 flipped,
-							FSurfaceInfo *Surface)		// jimita 17032019
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, FSurfaceInfo *Surface)
 {
-	DrawMD2Ex(gl_cmd_buffer, frame, duration, tics, nextframe, pos, scale, flipped, Surface);
+	DrawModelEx(model, frameIndex, duration, tics, nextFrameIndex, pos, scale, flipped, Surface);
 }
 
 // -----------------+
@@ -1930,14 +2109,19 @@ EXPORT void HWRAPI(DrawMD2) (
 // -----------------+
 EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 {
-	static INT32 special_splitscreen;
+	static boolean special_splitscreen;
 	pglLoadIdentity();
 	if (stransform)
 	{
-		boolean fovx90;
 		// 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
@@ -1949,8 +2133,7 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 
 		pglMatrixMode(GL_PROJECTION);
 		pglLoadIdentity();
-		fovx90 = stransform->fovxangle > 0.0f && fabsf(stransform->fovxangle - 90.0f) < 0.5f;
-		special_splitscreen = (stransform->splitscreen && fovx90);
+		special_splitscreen = (stransform->splitscreen == 1 && stransform->fovxangle == 90.0f);
 		if (special_splitscreen)
 			GLPerspective(53.13f, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
 		else
@@ -2001,7 +2184,15 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 	float xfix, yfix;
 	INT32 texsize = 2048;
 
-	// Use a power of two texture, dammit
+	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
 	if(screen_width <= 1024)
 		texsize = 1024;
 	if(screen_width <= 512)
@@ -2013,43 +2204,60 @@ 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);
+	// 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);
+
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	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);
+
+			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);
+
+			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);
 }
@@ -2136,6 +2344,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)
@@ -2144,29 +2362,21 @@ 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);
+	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);
 	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);
-
-		// 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();
+	pglColor4ubv(white);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
 	tex_downloaded = screentexture;
 }
@@ -2177,6 +2387,26 @@ 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;
@@ -2186,32 +2416,26 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
 
+	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);
+	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);
-
-		// 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();
+	pglColor4ubv(white);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
 	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
 
@@ -2223,35 +2447,23 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 
 	pglActiveTexture(GL_TEXTURE1);
 	pglEnable(GL_TEXTURE_2D);
-	pglBindTexture(GL_TEXTURE_2D, tex_downloaded);
+	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();
+
+	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);
 
 	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;
 }
 
@@ -2345,32 +2557,42 @@ 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);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, off);
 
-	pglEnd();
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
 	tex_downloaded = finalScreenTexture;
 }
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..b1bba172d2e5f30c68d151516a0b094ba5fbcacc
--- /dev/null
+++ b/src/hardware/u_list.c
@@ -0,0 +1,231 @@
+/*
+	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..538da2fdc1bb1b4bdaa3749ab0741575715b72b8 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -411,6 +411,19 @@ boolean FV3_Equal(const vector3_t *a_1, const vector3_t *a_2)
 	return false;
 }
 
+// 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;
+}
+
 fixed_t FV3_Dot(const vector3_t *a_1, const vector3_t *a_2)
 {
 	return (FixedMul(a_1->x, a_2->x) + FixedMul(a_1->y, a_2->y) + FixedMul(a_1->z, a_2->z));
@@ -519,7 +532,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 +542,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;
 }
 
 //
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 4609913b7a413eba617146772c8f8d468c4120ef..8145a6917e7c6bf738d0e136bf7f4cb424f1c4c6 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -394,9 +394,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/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index e43772179562d95f87de7cf5d3b1734840288e4d..a8b881650114ddc531e7671a440331b9af171b1b 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -227,6 +227,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" />
@@ -365,8 +369,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">
@@ -478,4 +486,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index d67ac63082800b9d77ca3985547859714c61942b..d9caf5ce5e7fbef17af96047b7bc6fc4c5d56809 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>
@@ -623,10 +635,22 @@
     </ClCompile>
     <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>
@@ -894,4 +918,4 @@
       <Filter>SDLApp</Filter>
     </Image>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 5ee984b298c09ad8fd8f03c5f783e74483a2b042..22faeb8db6ad603b8e88e8f8df6845abd3a6075e 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -88,7 +88,8 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(ClearMipMapCache);
 	GETFUNC(SetSpecialState);
 	GETFUNC(GetTextureUsed);
-	GETFUNC(DrawMD2);
+	GETFUNC(DrawModel);
+	GETFUNC(CreateModelVBOs);
 	GETFUNC(SetTransform);
 	GETFUNC(GetRenderVersion);
 	GETFUNC(PostImgRedraw);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index d0c34d93552e29dc476c19f1540c38c07e61043d..caf02049b0c66f880ea9ddc9c256cb9393ce0af0 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1485,7 +1485,8 @@ 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.pfnDrawModel        = hwSym("DrawModel",NULL);
+		HWD.pfnCreateModelVBOs  = hwSym("CreateModelVBOs",NULL);
 		HWD.pfnSetTransform     = hwSym("SetTransform",NULL);
 		HWD.pfnGetRenderVersion = hwSym("GetRenderVersion",NULL);
 		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index 774ce5cbe8c7560c592f2b6b97e3db7864221347..177d62692d9172aa8037817c451366c5932456c3 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -222,15 +222,6 @@
     <ClCompile Include="..\f_wipe.c" />
     <ClCompile Include="..\g_game.c" />
     <ClCompile Include="..\g_input.c" />
-    <ClCompile Include="..\hardware\hw3sound.c" />
-    <ClCompile Include="..\hardware\hw_bsp.c" />
-    <ClCompile Include="..\hardware\hw_cache.c" />
-    <ClCompile Include="..\hardware\hw_clip.c" />
-    <ClCompile Include="..\hardware\hw_draw.c" />
-    <ClCompile Include="..\hardware\hw_light.c" />
-    <ClCompile Include="..\hardware\hw_main.c" />
-    <ClCompile Include="..\hardware\hw_md2.c" />
-    <ClCompile Include="..\hardware\hw_trick.c" />
     <ClCompile Include="..\hu_stuff.c" />
     <ClCompile Include="..\info.c" />
     <ClCompile Include="..\i_addrinfo.c">
@@ -384,16 +375,6 @@
     <ClInclude Include="..\hardware\hw3dsdrv.h" />
     <ClInclude Include="..\hardware\hw3sound.h" />
     <ClInclude Include="..\hardware\hws_data.h" />
-    <ClInclude Include="..\hardware\hw_clip.h" />
-    <ClInclude Include="..\hardware\hw_data.h" />
-    <ClInclude Include="..\hardware\hw_defs.h" />
-    <ClInclude Include="..\hardware\hw_dll.h" />
-    <ClInclude Include="..\hardware\hw_drv.h" />
-    <ClInclude Include="..\hardware\hw_glide.h" />
-    <ClInclude Include="..\hardware\hw_glob.h" />
-    <ClInclude Include="..\hardware\hw_light.h" />
-    <ClInclude Include="..\hardware\hw_main.h" />
-    <ClInclude Include="..\hardware\hw_md2.h" />
     <ClInclude Include="..\hu_stuff.h" />
     <ClInclude Include="..\info.h" />
     <ClInclude Include="..\i_addrinfo.h" />
@@ -494,4 +475,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index d20dd672bd6df3b28734d89aa5baff5ab5478d5a..4023091279187e1c2c97d274b59d2d116031d32d 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -87,30 +87,6 @@
     <ClCompile Include="win_vid.c">
       <Filter>Win32app</Filter>
     </ClCompile>
-    <ClCompile Include="..\hardware\hw_bsp.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
-    <ClCompile Include="..\hardware\hw_cache.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
-    <ClCompile Include="..\hardware\hw_clip.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
-    <ClCompile Include="..\hardware\hw_draw.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
-    <ClCompile Include="..\hardware\hw_light.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
-    <ClCompile Include="..\hardware\hw_main.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
-    <ClCompile Include="..\hardware\hw_md2.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
-    <ClCompile Include="..\hardware\hw_trick.c">
-      <Filter>Hw_Hardware</Filter>
-    </ClCompile>
     <ClCompile Include="..\hardware\hw3sound.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
@@ -476,36 +452,6 @@
     <ClInclude Include="win_main.h">
       <Filter>Win32app</Filter>
     </ClInclude>
-    <ClInclude Include="..\hardware\hw_clip.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_data.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_defs.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_dll.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_drv.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_glide.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_glob.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_light.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_main.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
-    <ClInclude Include="..\hardware\hw_md2.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
     <ClInclude Include="..\hardware\hw3dsdrv.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
@@ -881,4 +827,4 @@
       <Filter>A_Asm</Filter>
     </CustomBuild>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>