shithub: qk1

Download patch

ref: 0f07da746048c1452a42d3a964157448967c357f
parent: 5456cc33b02aa386b4b6d5800b8b9841726daf20
author: Sigrid Solveig Haflínudóttir <[email protected]>
date: Tue Nov 14 20:45:24 EST 2023

split bsp/bsp2 loading and removing bsp-related packed structures altogether

--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,9 @@
 	mathlib.o\
 	menu.o\
 	model.o\
+	model_brush.o\
+	model_bsp.o\
+	model_bsp2.o\
 	net_loop.o\
 	net_main.o\
 	pal.o\
--- a/bspfile.h
+++ b/bspfile.h
@@ -1,5 +1,8 @@
-// upper design bounds
+#define BSPVERSION 29
+#define BSP2VERSION ('B'|'S'<<8|'P'<<16|'2'<<24)
+
 enum {
+	// upper design bounds
 	MAX_MAP_HULLS = 4,
 	MAX_MAP_MODELS = 256,
 	MAX_MAP_BRUSHES = 4096,
@@ -19,33 +22,10 @@
 	MAX_MAP_VISIBILITY = 0x100000,
 	MAX_MAP_PORTALS = 65536,
 
-	// key / value pair sizes
-	MAX_KEY = 32,
-	MAX_VALUE = 1024,
-
 	MIPLEVELS = 4,
 	MAXLIGHTMAPS = 4,
-};
 
-//=============================================================================
-
-#define BSPVERSION	29
-#define BSP2VERSION ('B'|'S'<<8|'P'<<16|'2'<<24)
-#define	TOOLVERSION	2
-
-#ifdef __plan9__
-#pragma pack on
-#else
-#pragma pack(1)
-#endif
-
-typedef struct
-{
-	int		fileofs, filelen;
-} lump_t;
-
-enum {
-	LUMP_ENTITIES,
+	LUMP_ENTITIES = 0,
 	LUMP_PLANES,
 	LUMP_TEXTURES,
 	LUMP_VERTEXES,
@@ -61,44 +41,9 @@
 	LUMP_SURFEDGES,
 	LUMP_MODELS,
 	HEADER_LUMPS,
-};
 
-typedef struct
-{
-	float		mins[3], maxs[3];
-	float		origin[3];
-	int			headnode[MAX_MAP_HULLS];
-	int			visleafs;		// not including the solid leaf 0
-	int			firstface, numfaces;
-} dmodel_t;
-
-typedef struct
-{
-	int			version;
-	lump_t		lumps[HEADER_LUMPS];
-} dheader_t;
-
-typedef struct
-{
-	int			nummiptex;
-	int			dataofs[4];		// [nummiptex]
-} dmiptexlump_t;
-
-typedef struct miptex_s
-{
-	char		name[16];
-	unsigned	width, height;
-	unsigned	offsets[MIPLEVELS];		// four mip maps stored
-} miptex_t;
-
-typedef struct
-{
-	float	point[3];
-} dvertex_t;
-
-enum {
 	// 0-2 are axial planes
-	PLANE_X,
+	PLANE_X = 0,
 	PLANE_Y,
 	PLANE_Z,
 	// 3-5 are non-axial planes snapped to the nearest
@@ -105,16 +50,7 @@
 	PLANE_ANYX,
 	PLANE_ANYY,
 	PLANE_ANYZ,
-};
 
-typedef struct
-{
-	float	normal[3];
-	float	dist;
-	int		type;		// PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate
-} dplane_t;
-
-enum {
 	CONTENTS_CURRENT_DOWN = -14,
 	CONTENTS_CURRENT_UP,
 	CONTENTS_CURRENT_270,
@@ -129,124 +65,6 @@
 	CONTENTS_WATER,
 	CONTENTS_SOLID,
 	CONTENTS_EMPTY,
-};
 
-// !!! if this is changed, it must be changed in asm_i386.h too !!!
-typedef struct
-{
-	int			planenum;
-	short		children[2];	// negative numbers are -(leafs+1), not nodes
-	short		mins[3];		// for sphere culling
-	short		maxs[3];
-	unsigned short	firstface;
-	unsigned short	numfaces;	// counting both sides
-} dnode_t;
-
-typedef struct
-{
-	int			planenum;
-	int			children[2];	// negative numbers are -(leafs+1), not nodes
-	float		mins[3];		// for sphere culling
-	float		maxs[3];
-	unsigned int	firstface;
-	unsigned int	numfaces;	// counting both sides
-} bsp2_dnode_t;
-
-typedef struct
-{
-	int			planenum;
-	short		children[2];	// negative numbers are contents
-} dclipnode_t;
-
-typedef struct
-{
-	int		planenum;
-	int		children[2];	// negative numbers are contents
-} bsp2_dclipnode_t;
-
-enum {
 	TEX_SPECIAL = 1<<0, // sky or slime, no lightmap or 256 subdivision
 };
-
-typedef struct texinfo_s
-{
-	float		vecs[2][4];		// [s/t][xyz offset]
-	int			miptex;
-	int			flags;
-} texinfo_t;
-
-// note that edge 0 is never used, because negative edge nums are used for
-// counterclockwise use of the edge in a face
-typedef struct
-{
-	unsigned short	v[2];		// vertex numbers
-} dedge_t;
-
-typedef struct
-{
-	unsigned int v[2];
-} bsp2_dedge_t;
-
-typedef struct
-{
-	short		planenum;
-	short		side;
-
-	int			firstedge;		// we must support > 64k edges
-	short		numedges;
-	short		texinfo;
-
-// lighting info
-	byte		styles[MAXLIGHTMAPS];
-	int			lightofs;		// start of [numstyles*surfsize] samples
-} dface_t;
-
-typedef struct
-{
-	int		planenum;
-	int		side;
-
-	int			firstedge;		// we must support > 64k edges
-	int		numedges;
-	int		texinfo;
-
-// lighting info
-	byte		styles[MAXLIGHTMAPS];
-	int			lightofs;		// start of [numstyles*surfsize] samples
-} bsp2_dface_t;
-
-// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas
-// all other leafs need visibility info
-typedef struct
-{
-	int			contents;
-	int			visofs;				// -1 = no visibility info
-
-	short		mins[3];			// for frustum culling
-	short		maxs[3];
-
-	unsigned short		firstmarksurface;
-	unsigned short		nummarksurfaces;
-
-	byte		ambient_level[Namb];
-} dleaf_t;
-
-typedef struct
-{
-	int			contents;
-	int			visofs;				// -1 = no visibility info
-
-	float		mins[3];			// for frustum culling
-	float		maxs[3];
-
-	unsigned int		firstmarksurface;
-	unsigned int		nummarksurfaces;
-
-	byte		ambient_level[Namb];
-} bsp2_dleaf_t;
-
-#ifdef __plan9__
-#pragma pack off
-#else
-#pragma pack(0)
-#endif
--- a/mkfile
+++ b/mkfile
@@ -41,6 +41,9 @@
 	mathlib.$O\
 	menu.$O\
 	model.$O\
+	model_brush.$O\
+	model_bsp.$O\
+	model_bsp2.$O\
 	nanosec.$O\
 	net_dgrm.$O\
 	net_loop.$O\
--- a/model.c
+++ b/model.c
@@ -1,10 +1,9 @@
 #include "quakedef.h"
 
-static model_t *loadmodel;
 static char loadname[32];	// for hunk tags
 
 void Mod_LoadSpriteModel (model_t *mod, void *buffer);
-void Mod_LoadBrushModel (model_t *mod, void *buffer);
+void Mod_LoadBrushModel (model_t *mod, void *buffer, int total);
 void Mod_LoadAliasModel (model_t *mod, void *buffer);
 model_t *Mod_LoadModel (model_t *mod, bool crash);
 
@@ -227,27 +226,19 @@
 */
 model_t *Mod_LoadModel (model_t *mod, bool crash)
 {
-	unsigned *buf;
-	byte stackbuf[1024];		// avoid dirtying the cache heap
+	byte *buf;
+	int len;
 
-	if (mod->type == mod_alias)
-	{
-		if (Cache_Check (&mod->cache))
-		{
+	if(mod->type == mod_alias){
+		if(Cache_Check(&mod->cache)){
 			mod->needload = NL_PRESENT;
 			return mod;
 		}
-	}
-	else
-	{
-		if (mod->needload == NL_PRESENT){
-			return mod;
-		}
-	}
+	}else if(mod->needload == NL_PRESENT)
+		return mod;
 
 	// because the world is so huge, load it one piece at a time
-	buf = loadstklmp(mod->name, stackbuf, sizeof stackbuf, nil);
-	if(buf == nil){
+	if((buf = loadstklmp(mod->name, nil, 0, &len)) == nil){
 		if(crash)
 			Host_Error("Mod_LoadModel: %s", lerr());
 		return nil;
@@ -255,7 +246,6 @@
 
 	// allocate a new model
 	radix(mod->name, loadname);
-	loadmodel = mod;
 
 	// fill it in
 
@@ -262,18 +252,18 @@
 	// call the apropriate loader
 	mod->needload = NL_PRESENT;
 
-	switch (LittleLong(*(unsigned *)buf))
+	switch(LittleLong(*(unsigned *)buf))
 	{
 	case IDPOLYHEADER:
-		Mod_LoadAliasModel (mod, buf);
+		Mod_LoadAliasModel(mod, buf);
 		break;
 
 	case IDSPRITEHEADER:
-		Mod_LoadSpriteModel (mod, buf);
+		Mod_LoadSpriteModel(mod, buf);
 		break;
 
 	default:
-		Mod_LoadBrushModel (mod, buf);
+		Mod_LoadBrushModel(mod, buf, len);
 		break;
 	}
 
@@ -294,894 +284,6 @@
 	mod = Mod_FindName (name);
 
 	return Mod_LoadModel (mod, crash);
-}
-
-
-/*
-===============================================================================
-
-					BRUSHMODEL LOADING
-
-===============================================================================
-*/
-
-static byte *mod_base;
-
-/*
-=================
-Mod_LoadTextures
-=================
-*/
-void Mod_LoadTextures (lump_t *l)
-{
-	int		i, j, pixels, num, max, altmax;
-	miptex_t	*mt;
-	texture_t	*tx, *tx2;
-	texture_t	*anims[10];
-	texture_t	*altanims[10];
-	dmiptexlump_t *m;
-
-	if (!l->filelen)
-	{
-		loadmodel->textures = nil;
-		return;
-	}
-	m = (dmiptexlump_t *)(mod_base + l->fileofs);
-
-	m->nummiptex = LittleLong (m->nummiptex);
-
-	loadmodel->numtextures = m->nummiptex;
-	loadmodel->textures = Hunk_Alloc(m->nummiptex * sizeof *loadmodel->textures);
-
-	for (i=0 ; i<m->nummiptex ; i++)
-	{
-		m->dataofs[i] = LittleLong(m->dataofs[i]);
-		if (m->dataofs[i] == -1)
-			continue;
-		mt = (miptex_t *)((byte *)m + m->dataofs[i]);
-		mt->width = LittleLong (mt->width);
-		mt->height = LittleLong (mt->height);
-		for (j=0 ; j<MIPLEVELS ; j++)
-			mt->offsets[j] = LittleLong (mt->offsets[j]);
-
-		if ( (mt->width & 15) || (mt->height & 15) )
-			Con_DPrintf("Texture %s is not 16 aligned", mt->name);
-		pixels = mt->width*mt->height/64*85;
-		tx = Hunk_Alloc(pixels + sizeof *tx);
-		loadmodel->textures[i] = tx;
-
-		memcpy(tx->name, mt->name, sizeof tx->name);
-		tx->width = mt->width;
-		tx->height = mt->height;
-		for (j=0 ; j<MIPLEVELS ; j++)
-			tx->offsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t);
-		// the pixels immediately follow the structures
-		memcpy(tx+1, mt+1, pixels);
-
-		if(strncmp(mt->name, "sky", 3) == 0)
-			R_InitSky (tx);
-	}
-
-	// sequence the animations
-	for (i=0 ; i<m->nummiptex ; i++)
-	{
-		tx = loadmodel->textures[i];
-		if (!tx || tx->name[0] != '+')
-			continue;
-		if (tx->anim_next)
-			continue;	// allready sequenced
-
-		// find the number of frames in the animation
-		memset(anims, 0, sizeof anims);
-		memset(altanims, 0, sizeof altanims);
-
-		max = tx->name[1];
-		altmax = 0;
-		if (max >= 'a' && max <= 'z')
-			max -= 'a' - 'A';
-		if (max >= '0' && max <= '9')
-		{
-			max -= '0';
-			altmax = 0;
-			anims[max] = tx;
-			max++;
-		}
-		else if (max >= 'A' && max <= 'J')
-		{
-			altmax = max - 'A';
-			max = 0;
-			altanims[altmax] = tx;
-			altmax++;
-		}
-		else
-			Host_Error("Bad animating texture %s", tx->name);
-
-		for (j=i+1 ; j<m->nummiptex ; j++)
-		{
-			tx2 = loadmodel->textures[j];
-			if (!tx2 || tx2->name[0] != '+')
-				continue;
-			if(strcmp(tx2->name+2, tx->name+2) != 0)
-				continue;
-
-			num = tx2->name[1];
-			if (num >= 'a' && num <= 'z')
-				num -= 'a' - 'A';
-			if (num >= '0' && num <= '9')
-			{
-				num -= '0';
-				anims[num] = tx2;
-				if (num+1 > max)
-					max = num + 1;
-			}
-			else if (num >= 'A' && num <= 'J')
-			{
-				num = num - 'A';
-				altanims[num] = tx2;
-				if (num+1 > altmax)
-					altmax = num+1;
-			}
-			else
-				Host_Error("Bad animating texture %s", tx->name);
-		}
-
-#define	ANIM_CYCLE	2
-		// link them all together
-		for (j=0 ; j<max ; j++)
-		{
-			tx2 = anims[j];
-			if (!tx2)
-				Host_Error("Missing frame %d of %s", j, tx->name);
-			tx2->anim_total = max * ANIM_CYCLE;
-			tx2->anim_min = j * ANIM_CYCLE;
-			tx2->anim_max = (j+1) * ANIM_CYCLE;
-			tx2->anim_next = anims[ (j+1)%max ];
-			if (altmax)
-				tx2->alternate_anims = altanims[0];
-		}
-		for (j=0 ; j<altmax ; j++)
-		{
-			tx2 = altanims[j];
-			if (!tx2)
-				Host_Error("Missing frame %d of %s", j, tx->name);
-			tx2->anim_total = altmax * ANIM_CYCLE;
-			tx2->anim_min = j * ANIM_CYCLE;
-			tx2->anim_max = (j+1) * ANIM_CYCLE;
-			tx2->anim_next = altanims[ (j+1)%altmax ];
-			if (max)
-				tx2->alternate_anims = anims[0];
-		}
-	}
-}
-
-/*
-=================
-Mod_LoadLighting
-=================
-*/
-void Mod_LoadLighting (lump_t *l)
-{
-	if (!l->filelen)
-	{
-		loadmodel->lightdata = nil;
-		return;
-	}
-	loadmodel->lightdata = Hunk_Alloc(l->filelen);
-	memcpy(loadmodel->lightdata, mod_base + l->fileofs, l->filelen);
-}
-
-
-/*
-=================
-Mod_LoadVisibility
-=================
-*/
-void Mod_LoadVisibility (lump_t *l)
-{
-	if (!l->filelen)
-	{
-		loadmodel->visdata = nil;
-		return;
-	}
-	loadmodel->visdata = Hunk_Alloc(l->filelen);
-	memcpy(loadmodel->visdata, mod_base + l->fileofs, l->filelen);
-}
-
-
-/*
-=================
-Mod_LoadEntities
-=================
-*/
-void Mod_LoadEntities (lump_t *l)
-{
-	if (!l->filelen)
-	{
-		loadmodel->entities = nil;
-		return;
-	}
-	loadmodel->entities = Hunk_Alloc(l->filelen);
-	memcpy(loadmodel->entities, mod_base + l->fileofs, l->filelen);
-}
-
-
-/*
-=================
-Mod_LoadVertexes
-=================
-*/
-void Mod_LoadVertexes (lump_t *l)
-{
-	dvertex_t	*in;
-	mvertex_t	*out;
-	int			i, count;
-
-	in = (void *)(mod_base + l->fileofs);
-	if (l->filelen % sizeof(*in))
-		Host_Error("Mod_LoadVertexes: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sizeof(*in);
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->vertexes = out;
-	loadmodel->numvertexes = count;
-
-	for ( i=0 ; i<count ; i++, in++, out++)
-	{
-		out->position[0] = LittleFloat (in->point[0]);
-		out->position[1] = LittleFloat (in->point[1]);
-		out->position[2] = LittleFloat (in->point[2]);
-	}
-}
-
-/*
-=================
-Mod_LoadSubmodels
-=================
-*/
-void Mod_LoadSubmodels (lump_t *l)
-{
-	dmodel_t	*in;
-	dmodel_t	*out;
-	int			i, j, count;
-
-	in = (void *)(mod_base + l->fileofs);
-	if (l->filelen % sizeof(*in))
-		Host_Error("Mod_LoadSubmodels: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sizeof(*in);
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->submodels = out;
-	loadmodel->numsubmodels = count;
-
-	for ( i=0 ; i<count ; i++, in++, out++)
-	{
-		for (j=0 ; j<3 ; j++)
-		{	// spread the mins / maxs by a pixel
-			out->mins[j] = LittleFloat (in->mins[j]) - 1;
-			out->maxs[j] = LittleFloat (in->maxs[j]) + 1;
-			out->origin[j] = LittleFloat (in->origin[j]);
-		}
-		for (j=0 ; j<MAX_MAP_HULLS ; j++)
-			out->headnode[j] = LittleLong (in->headnode[j]);
-		out->visleafs = LittleLong (in->visleafs);
-		out->firstface = LittleLong (in->firstface);
-		out->numfaces = LittleLong (in->numfaces);
-	}
-}
-
-/*
-=================
-Mod_LoadEdges
-=================
-*/
-void Mod_LoadEdges (lump_t *l, int ver)
-{
-	byte *in;
-	medge_t *out;
-	int 	i, count, sz;
-
-	in = (void *)(mod_base + l->fileofs);
-	sz = ver == BSP2VERSION ? sizeof(bsp2_dedge_t) : sizeof(dedge_t);
-	if (l->filelen % sz)
-		Host_Error("Mod_LoadEdges: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sz;
-	out = Hunk_Alloc((count+1) * sizeof *out);
-
-	loadmodel->edges = out;
-	loadmodel->numedges = count;
-
-	for ( i=0 ; i<count ; i++, out++)
-	{
-		out->v[0] = ver == BSP2VERSION ? le32u(in) : le16u(in);
-		out->v[1] = ver == BSP2VERSION ? le32u(in) : le16u(in);
-	}
-}
-
-/*
-=================
-Mod_LoadTexinfo
-=================
-*/
-void Mod_LoadTexinfo (lump_t *l)
-{
-	texinfo_t *in;
-	mtexinfo_t *out;
-	int 	i, j, count;
-	int		miptex;
-	float	len1, len2;
-
-	in = (void *)(mod_base + l->fileofs);
-	if (l->filelen % sizeof(*in))
-		Host_Error("Mod_LoadTexInfo: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sizeof(*in);
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->texinfo = out;
-	loadmodel->numtexinfo = count;
-
-	for ( i=0 ; i<count ; i++, in++, out++)
-	{
-		for (j=0 ; j<4 ; j++)
-			out->vecs[0][j] = LittleFloat (in->vecs[0][j]);
-		for (j=0 ; j<4 ; j++)
-			out->vecs[1][j] = LittleFloat (in->vecs[1][j]);
-		len1 = Length (out->vecs[0]);
-		len2 = Length (out->vecs[1]);
-		len1 = (len1 + len2)/2;
-		if (len1 < 0.32)
-			out->mipadjust = 4;
-		else if (len1 < 0.49)
-			out->mipadjust = 3;
-		else if (len1 < 0.99)
-			out->mipadjust = 2;
-		else
-			out->mipadjust = 1;
-		/*
-		if (len1 + len2 < 0.001)
-			out->mipadjust = 1;		// don't crash
-		else
-			out->mipadjust = 1 / floor( (len1+len2)/2 + 0.1 );
-		*/
-
-		miptex = LittleLong (in->miptex);
-		out->flags = LittleLong (in->flags);
-
-		if (!loadmodel->textures)
-		{
-			out->texture = r_notexture_mip;	// checkerboard texture
-			out->flags = 0;
-		}
-		else
-		{
-			if (miptex >= loadmodel->numtextures)
-				Host_Error("miptex >= loadmodel->numtextures");
-			out->texture = loadmodel->textures[miptex];
-			if (!out->texture)
-			{
-				out->texture = r_notexture_mip; // texture not found
-				out->flags = 0;
-			}
-		}
-	}
-}
-
-/*
-================
-CalcSurfaceExtents
-
-Fills in s->texturemins[] and s->extents[]
-================
-*/
-void CalcSurfaceExtents (msurface_t *s)
-{
-	double	mins[2], maxs[2], val;
-	int		i,j, e;
-	mvertex_t	*v;
-	mtexinfo_t	*tex;
-	int		bmins[2], bmaxs[2];
-
-	mins[0] = mins[1] = Q_MAXFLOAT;
-	maxs[0] = maxs[1] = -Q_MAXFLOAT;
-	tex = s->texinfo;
-
-	for (i=0 ; i<s->numedges ; i++)
-	{
-		e = loadmodel->surfedges[s->firstedge+i];
-		if (e >= 0)
-			v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
-		else
-			v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
-
-		for (j=0 ; j<2 ; j++)
-		{
-			val = (double)v->position[0] * (double)tex->vecs[j][0] +
-				(double)v->position[1] * (double)tex->vecs[j][1] +
-				(double)v->position[2] * (double)tex->vecs[j][2] +
-				(double)tex->vecs[j][3];
-			if (val < mins[j])
-				mins[j] = val;
-			if (val > maxs[j])
-				maxs[j] = val;
-		}
-	}
-
-	for (i=0 ; i<2 ; i++)
-	{
-		bmins[i] = floor(mins[i]/16.0);
-		bmaxs[i] = ceil(maxs[i]/16.0);
-
-		s->texturemins[i] = bmins[i] * 16;
-		s->extents[i] = (bmaxs[i] - bmins[i]) * 16;
-		if ( !(tex->flags & TEX_SPECIAL) && s->extents[i] > 2000)
-			Host_Error("Bad surface: texture=%s flags=%ux extents[%d]=%d",
-				tex->texture->name,
-				tex->flags,
-				i,
-				s->extents[i]
-			);
-	}
-}
-
-
-/*
-=================
-Mod_LoadFaces
-=================
-*/
-void Mod_LoadFaces (lump_t *l, int ver)
-{
-	byte		*in;
-	msurface_t 	*out;
-	int			i, count, surfnum, sz;
-
-	in = (void *)(mod_base + l->fileofs);
-	sz = ver == BSP2VERSION ? sizeof(bsp2_dface_t) : sizeof(dface_t);
-	if (l->filelen % sz)
-		Host_Error("Mod_LoadFaces: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sz;
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->surfaces = out;
-	loadmodel->numsurfaces = count;
-
-	for ( surfnum=0 ; surfnum<count ; surfnum++, out++)
-	{
-		out->plane = loadmodel->planes + (ver == BSP2VERSION ? le32u(in) : le16u(in));
-		out->flags = (ver == BSP2VERSION ? le32u(in) : le16u(in)) ? SURF_PLANEBACK : 0;
-		out->firstedge = le32u(in);
-		out->numedges = ver == BSP2VERSION ? le32u(in) : le16u(in);
-		out->texinfo = loadmodel->texinfo + (ver == BSP2VERSION ? le32u(in) : le16u(in));
-
-		CalcSurfaceExtents (out);
-
-		// lighting info
-
-		memmove(out->styles, in, MAXLIGHTMAPS);
-		in += MAXLIGHTMAPS;
-		out->samples = (i = le32(in)) < 0 ? nil : loadmodel->lightdata + i;
-
-		// set the drawing flags flag
-
-		if(strncmp(out->texinfo->texture->name, "sky", 3) == 0)
-			out->flags |= SURF_DRAWSKY | SURF_DRAWTILED;
-		else if(out->texinfo->texture->name[0] == '*'){	// turbulent
-			out->flags |= SURF_DRAWTURB | SURF_DRAWTILED | SURF_TRANS;
-			for (i=0 ; i<2 ; i++){
-				out->extents[i] = 16384;
-				out->texturemins[i] = -8192;
-			}
-			if(strstr(out->texinfo->texture->name, "lava") != nil)
-				out->flags |= SURF_LAVA;
-			if(strstr(out->texinfo->texture->name, "slime") != nil)
-				out->flags |= SURF_SLIME;
-			if(strstr(out->texinfo->texture->name, "tele") != nil){
-				out->flags |= SURF_TELE;
-				out->flags &= ~SURF_TRANS;
-			}
-		}else if(out->texinfo->texture->name[0] == '{')
-			out->flags |= SURF_TRANS | SURF_FENCE;
-	}
-}
-
-
-/*
-=================
-Mod_SetParent
-=================
-*/
-void Mod_SetParent (mnode_t *node, mnode_t *parent)
-{
-	node->parent = parent;
-	if (node->contents < 0)
-		return;
-	Mod_SetParent (node->children[0], node);
-	Mod_SetParent (node->children[1], node);
-}
-
-/*
-=================
-Mod_LoadNodes
-=================
-*/
-void Mod_LoadNodes (lump_t *l, int ver)
-{
-	int			i, j, count, p, sz;
-	byte		*in;
-	mnode_t 	*out;
-
-	in = (void *)(mod_base + l->fileofs);
-	sz = ver == BSP2VERSION ? sizeof(bsp2_dnode_t) : sizeof(dnode_t);
-	if (l->filelen % sz)
-		Host_Error("Mod_LoadNodes: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sz;
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->nodes = out;
-	loadmodel->numnodes = count;
-
-	for ( i=0 ; i<count ; i++, out++)
-	{
-		out->plane = loadmodel->planes + le32u(in);
-
-		for (j=0 ; j<2 ; j++){
-			p = ver == BSP2VERSION ? le32(in) : le16u(in);
-			if(p >= 0 && p < count)
-				out->children[j] = loadmodel->nodes + p;
-			else{
-				p = ver == BSP2VERSION ? -1-p : 0xffff-p;
-				if(p >= 0 && p < loadmodel->numleafs)
-					out->children[j] = (mnode_t *)(loadmodel->leafs + p);
-				else{
-					Con_Printf("Mod_LoadNodes: invalid node child\n");
-					out->children[j] = (mnode_t *)loadmodel->leafs;
-				}
-			}
-		}
-
-		for(j=0 ; j<3 ; j++)
-			out->minmaxs[0+j] = ver == BSP2VERSION ? f32(in) : le16(in);
-		for(j=0 ; j<3 ; j++)
-			out->minmaxs[3+j] = ver == BSP2VERSION ? f32(in) : le16(in);
-
-		out->firstsurface = ver == BSP2VERSION ? le32u(in) : le16u(in);
-		out->numsurfaces = ver == BSP2VERSION ? le32u(in) : le16u(in);
-	}
-
-	Mod_SetParent (loadmodel->nodes, nil);	// sets nodes and leafs
-}
-
-/*
-=================
-Mod_LoadLeafs
-=================
-*/
-void Mod_LoadLeafs (lump_t *l, int ver)
-{
-	byte	 	*in;
-	mleaf_t 	*out;
-	int			i, j, count, p, sz;
-
-	in = (void *)(mod_base + l->fileofs);
-	sz = ver == BSP2VERSION ? sizeof(bsp2_dleaf_t) : sizeof(dleaf_t);
-	if (l->filelen % sz)
-		Host_Error("Mod_LoadLeafs: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sz;
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->leafs = out;
-	loadmodel->numleafs = count;
-
-	for ( i=0 ; i<count ; i++, out++)
-	{
-		out->contents = le32(in);
-		out->compressed_vis = (p = le32(in)) < 0 ? nil : loadmodel->visdata + p;
-
-		for(j=0 ; j<3 ; j++)
-			out->minmaxs[0+j] = ver == BSP2VERSION ? f32(in) : le16(in);
-		for(j=0 ; j<3 ; j++)
-			out->minmaxs[3+j] = ver == BSP2VERSION ? f32(in) : le16(in);
-
-		out->firstmarksurface = loadmodel->marksurfaces + (ver == BSP2VERSION ? le32u(in) : le16u(in));
-		out->nummarksurfaces = ver == BSP2VERSION ? le32u(in) : le16u(in);
-
-		memmove(out->ambient_sound_level, in, 4);
-		in += 4;
-	}
-}
-
-/*
-=================
-Mod_LoadClipnodes
-=================
-*/
-void Mod_LoadClipnodes (lump_t *l, int ver)
-{
-	byte *in;
-	mclipnode_t	*out;
-	int			i, count, sz;
-	hull_t		*hull;
-
-	in = (void *)(mod_base + l->fileofs);
-	sz = ver == BSP2VERSION ? sizeof(bsp2_dclipnode_t) : sizeof(dclipnode_t);
-	if (l->filelen % sz)
-		Host_Error("Mod_LoadClipnodes: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sz;
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->clipnodes = out;
-	loadmodel->numclipnodes = count;
-
-	hull = &loadmodel->hulls[1];
-	hull->clipnodes = out;
-	hull->firstclipnode = 0;
-	hull->lastclipnode = count-1;
-	hull->planes = loadmodel->planes;
-	hull->clip_mins[0] = -16;
-	hull->clip_mins[1] = -16;
-	hull->clip_mins[2] = -24;
-	hull->clip_maxs[0] = 16;
-	hull->clip_maxs[1] = 16;
-	hull->clip_maxs[2] = 32;
-
-	hull = &loadmodel->hulls[2];
-	hull->clipnodes = out;
-	hull->firstclipnode = 0;
-	hull->lastclipnode = count-1;
-	hull->planes = loadmodel->planes;
-	hull->clip_mins[0] = -32;
-	hull->clip_mins[1] = -32;
-	hull->clip_mins[2] = -24;
-	hull->clip_maxs[0] = 32;
-	hull->clip_maxs[1] = 32;
-	hull->clip_maxs[2] = 64;
-
-	for (i=0 ; i<count ; i++, out++)
-	{
-		out->planenum = le32u(in);
-		out->children[0] = ver == BSP2VERSION ? le32u(in) : le16u(in);
-		out->children[1] = ver == BSP2VERSION ? le32u(in) : le16u(in);
-		if(out->children[0] >= count)
-			out->children[0] -= 0x10000;
-		if(out->children[1] >= count)
-			out->children[1] -= 0x10000;
-	}
-}
-
-/*
-=================
-Mod_MakeHull0
-
-Deplicate the drawing hull structure as a clipping hull
-=================
-*/
-void Mod_MakeHull0 (void)
-{
-	mnode_t		*in, *child;
-	mclipnode_t *out;
-	int			i, j, count;
-	hull_t		*hull;
-
-	hull = &loadmodel->hulls[0];
-
-	in = loadmodel->nodes;
-	count = loadmodel->numnodes;
-	out = Hunk_Alloc(count * sizeof *out);
-
-	hull->clipnodes = out;
-	hull->firstclipnode = 0;
-	hull->lastclipnode = count-1;
-	hull->planes = loadmodel->planes;
-
-	for (i=0 ; i<count ; i++, out++, in++)
-	{
-		out->planenum = in->plane - loadmodel->planes;
-		for (j=0 ; j<2 ; j++)
-		{
-			child = in->children[j];
-			if (child->contents < 0)
-				out->children[j] = child->contents;
-			else
-				out->children[j] = child - loadmodel->nodes;
-		}
-	}
-}
-
-/*
-=================
-Mod_LoadMarksurfaces
-=================
-*/
-void Mod_LoadMarksurfaces (lump_t *l, int ver)
-{
-	int		i, j, count, sz;
-	byte		*in;
-	msurface_t **out;
-
-	in = (void *)(mod_base + l->fileofs);
-	sz = ver == BSP2VERSION ? sizeof(u32int) : sizeof(u16int);
-	if (l->filelen % sz)
-		Host_Error("Mod_LoadMarksurfaces: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sz;
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->marksurfaces = out;
-	loadmodel->nummarksurfaces = count;
-
-	for ( i=0 ; i<count ; i++)
-	{
-		j = ver == BSP2VERSION ? le32u(in) : le16u(in);
-		if(j < 0 || j >= loadmodel->numsurfaces)
-			Host_Error("Mod_ParseMarksurfaces: bad surface number");
-		out[i] = loadmodel->surfaces + j;
-	}
-}
-
-/*
-=================
-Mod_LoadSurfedges
-=================
-*/
-void Mod_LoadSurfedges (lump_t *l)
-{
-	int		i, count;
-	int		*in, *out;
-
-	in = (void *)(mod_base + l->fileofs);
-	if (l->filelen % sizeof(*in))
-		Host_Error("Mod_LoadSurfedges: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sizeof(*in);
-	out = Hunk_Alloc(count * sizeof *out);
-
-	loadmodel->surfedges = out;
-	loadmodel->numsurfedges = count;
-
-	for ( i=0 ; i<count ; i++)
-		out[i] = LittleLong (in[i]);
-}
-
-/*
-=================
-Mod_LoadPlanes
-=================
-*/
-void Mod_LoadPlanes (lump_t *l)
-{
-	int			i, j;
-	mplane_t	*out;
-	dplane_t 	*in;
-	int			count;
-	int			bits;
-
-	in = (void *)(mod_base + l->fileofs);
-	if (l->filelen % sizeof(*in))
-		Host_Error("Mod_LoadPlanes: funny lump size in %s", loadmodel->name);
-	count = l->filelen / sizeof(*in);
-	out = Hunk_Alloc(count * 2 * sizeof *out);
-
-	loadmodel->planes = out;
-	loadmodel->numplanes = count;
-
-	for ( i=0 ; i<count ; i++, in++, out++)
-	{
-		bits = 0;
-		for (j=0 ; j<3 ; j++)
-		{
-			out->normal[j] = LittleFloat (in->normal[j]);
-			if (out->normal[j] < 0)
-				bits |= 1<<j;
-		}
-
-		out->dist = LittleFloat (in->dist);
-		out->type = LittleLong (in->type);
-		out->signbits = bits;
-	}
-}
-
-/*
-=================
-RadiusFromBounds
-=================
-*/
-float RadiusFromBounds (vec3_t mins, vec3_t maxs)
-{
-	int		i;
-	vec3_t	corner;
-
-	for (i=0 ; i<3 ; i++)
-	{
-		corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]);
-	}
-
-	return Length (corner);
-}
-
-/*
-=================
-Mod_LoadBrushModel
-=================
-*/
-void Mod_LoadBrushModel (model_t *mod, void *buffer)
-{
-	int			i, j, ver;
-	dheader_t	*header;
-	dmodel_t 	*bm;
-
-	loadmodel->type = mod_brush;
-
-	header = (dheader_t *)buffer;
-
-	ver = LittleLong (header->version);
-	if (ver != BSPVERSION && ver != BSP2VERSION)
-		Host_Error("Mod_LoadBrushModel: %s has wrong version number (%ux should be %ux or %ux)", mod->name, ver, BSPVERSION, BSP2VERSION);
-
-	// swap all the lumps
-	mod_base = (byte *)header;
-
-	for (i=0 ; i<(int)sizeof(dheader_t)/4 ; i++)
-		((int *)header)[i] = LittleLong ( ((int *)header)[i]);
-
-	// load into heap
-
-	Mod_LoadVertexes (&header->lumps[LUMP_VERTEXES]);
-	Mod_LoadEdges (&header->lumps[LUMP_EDGES], ver);
-	Mod_LoadSurfedges (&header->lumps[LUMP_SURFEDGES]);
-	Mod_LoadTextures (&header->lumps[LUMP_TEXTURES]);
-	Mod_LoadLighting (&header->lumps[LUMP_LIGHTING]);
-	Mod_LoadPlanes (&header->lumps[LUMP_PLANES]);
-	Mod_LoadTexinfo (&header->lumps[LUMP_TEXINFO]);
-	Mod_LoadFaces (&header->lumps[LUMP_FACES], ver);
-	Mod_LoadMarksurfaces (&header->lumps[LUMP_MARKSURFACES], ver);
-	Mod_LoadVisibility (&header->lumps[LUMP_VISIBILITY]);
-	Mod_LoadLeafs (&header->lumps[LUMP_LEAFS], ver);
-	Mod_LoadNodes (&header->lumps[LUMP_NODES], ver);
-	Mod_LoadClipnodes (&header->lumps[LUMP_CLIPNODES], ver);
-	Mod_LoadEntities (&header->lumps[LUMP_ENTITIES]);
-	Mod_LoadSubmodels (&header->lumps[LUMP_MODELS]);
-
-	Mod_MakeHull0 ();
-
-	mod->numframes = 2;		// regular and alternate animation
-	mod->flags = 0;
-
-	// set up the submodels (FIXME: this is confusing)
-	for (i=0 ; i<mod->numsubmodels ; i++)
-	{
-		bm = &mod->submodels[i];
-
-		mod->hulls[0].firstclipnode = bm->headnode[0];
-		for (j=1 ; j<MAX_MAP_HULLS ; j++)
-		{
-			mod->hulls[j].firstclipnode = bm->headnode[j];
-			mod->hulls[j].lastclipnode = mod->numclipnodes-1;
-		}
-
-		mod->firstmodelsurface = bm->firstface;
-		mod->nummodelsurfaces = bm->numfaces;
-		mod->blend = false;
-		for(j = bm->firstface; j < bm->firstface+bm->numfaces; j++){
-			if(loadmodel->surfaces[j].flags & SURF_TRANS){
-				mod->blend = true;
-				break;
-			}
-		}
-
-		VectorCopy (bm->maxs, mod->maxs);
-		VectorCopy (bm->mins, mod->mins);
-		mod->radius = RadiusFromBounds (mod->mins, mod->maxs);
-
-		mod->numleafs = bm->visleafs;
-
-		if (i < mod->numsubmodels-1)
-		{	// duplicate the basic information
-			char	name[16];
-
-			snprint(name, sizeof(name), "*%d", i+1);
-			loadmodel = Mod_FindName (name);
-			*loadmodel = *mod;
-			strcpy (loadmodel->name, name);
-			mod = loadmodel;
-		}
-	}
 }
 
 /*
--- a/model.h
+++ b/model.h
@@ -273,17 +273,32 @@
 // Whole model
 //
 
-typedef enum {mod_brush, mod_sprite, mod_alias} modtype_t;
+typedef enum {
+	mod_brush,
+	mod_sprite,
+	mod_alias,
+} modtype_t;
 
-#define	EF_ROCKET	1			// leave a trail
-#define	EF_GRENADE	2			// leave a trail
-#define	EF_GIB		4			// leave a trail
-#define	EF_ROTATE	8			// rotate (bonus items)
-#define	EF_TRACER	16			// green split trail
-#define	EF_ZOMGIB	32			// small blood trail
-#define	EF_TRACER2	64			// orange split trail + rotate
-#define	EF_TRACER3	128			// purple trail
+enum {
+	EF_ROCKET = 1<<0, // leave a trail
+	EF_GRENADE = 1<<1, // leave a trail
+	EF_GIB = 1<<2, // leave a trail
+	EF_ROTATE = 1<<3, // rotate (bonus items)
+	EF_TRACER = 1<<4, // green split trail
+	EF_ZOMGIB = 1<<5, // small blood trail
+	EF_TRACER2 = 1<<6, // orange split trail + rotate
+	EF_TRACER3 = 1<<7, // purple trail
+};
 
+typedef struct
+{
+	float		mins[3], maxs[3];
+	float		origin[3];
+	int			headnode[MAX_MAP_HULLS];
+	int			visleafs;		// not including the solid leaf 0
+	int			firstface, numfaces;
+} submodel_t;
+
 typedef struct model_s
 {
 	char		name[Npath];
@@ -308,7 +323,7 @@
 	int			firstmodelsurface, nummodelsurfaces;
 
 	int			numsubmodels;
-	dmodel_t	*submodels;
+	submodel_t	*submodels;
 
 	int			numplanes;
 	mplane_t	*planes;
--- /dev/null
+++ b/model_brush.c
@@ -1,0 +1,170 @@
+#include "quakedef.h"
+
+void BSP_MakeHull0(model_t *mod);
+model_t *Mod_FindName (char *name);
+
+int BSP_LoadClipnodes(model_t *mod, byte *in, int sz);
+int BSP_LoadEdges(model_t *mod, byte *in, int sz);
+int BSP_LoadEntities(model_t *mod, byte *in, int sz);
+int BSP_LoadFaces(model_t *mod, byte *in, int sz);
+int BSP_LoadLeafs(model_t *mod, byte *in, int sz);
+int BSP_LoadLighting(model_t *mod, byte *in, int sz);
+int BSP_LoadMarksurfaces(model_t *mod, byte *in, int sz);
+int BSP_LoadNodes(model_t *mod, byte *in, int sz);
+int BSP_LoadPlanes(model_t *mod, byte *in, int sz);
+int BSP_LoadSubmodels(model_t *mod, byte *in, int sz);
+int BSP_LoadSurfedges(model_t *mod, byte *in, int sz);
+int BSP_LoadTexinfo(model_t *mod, byte *in, int sz);
+int BSP_LoadTextures(model_t *mod, byte *in, int sz);
+int BSP_LoadVertexes(model_t *mod, byte *in, int sz);
+int BSP_LoadVisibility(model_t *mod, byte *in, int sz);
+
+int BSP2_LoadClipnodes(model_t *mod, byte *in, int sz);
+int BSP2_LoadEdges(model_t *mod, byte *in, int sz);
+int BSP2_LoadFaces(model_t *mod, byte *in, int sz);
+int BSP2_LoadLeafs(model_t *mod, byte *in, int sz);
+int BSP2_LoadMarksurfaces(model_t *mod, byte *in, int sz);
+int BSP2_LoadNodes(model_t *mod, byte *in, int sz);
+
+static float
+RadiusFromBounds(vec3_t mins, vec3_t maxs)
+{
+	int i;
+	vec3_t corner;
+	float fmin, fmax;
+
+	for(i=0 ; i<3 ; i++){
+		fmin = fabs(mins[i]);
+		fmax = fabs(maxs[i]);
+		corner[i] = max(fmin, fmax);
+	}
+
+	return Length(corner);
+}
+
+void
+Mod_LoadBrushModel(model_t *mod, void *buffer, int total)
+{
+	int i, j, ver, off, sz;
+	model_t *submod;
+	byte *in, *in0;
+	submodel_t *bm;
+	char name[16];
+	int (*loadf[HEADER_LUMPS])(model_t *, byte *, int) = {
+		[LUMP_VERTEXES] = BSP_LoadVertexes,
+		[LUMP_EDGES] = nil,
+		[LUMP_SURFEDGES] = BSP_LoadSurfedges,
+		[LUMP_TEXTURES] = BSP_LoadTextures,
+		[LUMP_LIGHTING] = BSP_LoadLighting,
+		[LUMP_PLANES] = BSP_LoadPlanes,
+		[LUMP_TEXINFO] = BSP_LoadTexinfo,
+		[LUMP_FACES] = nil,
+		[LUMP_MARKSURFACES] = nil,
+		[LUMP_VISIBILITY] = BSP_LoadVisibility,
+		[LUMP_LEAFS] = nil,
+		[LUMP_NODES] = nil,
+		[LUMP_CLIPNODES] = nil,
+		[LUMP_ENTITIES] = BSP_LoadEntities,
+		[LUMP_MODELS] = BSP_LoadSubmodels,
+	};
+	static const int order[HEADER_LUMPS] = {
+		LUMP_VERTEXES,
+		LUMP_EDGES,
+		LUMP_SURFEDGES,
+		LUMP_TEXTURES,
+		LUMP_LIGHTING,
+		LUMP_PLANES,
+		LUMP_TEXINFO,
+		LUMP_FACES,
+		LUMP_MARKSURFACES,
+		LUMP_VISIBILITY,
+		LUMP_LEAFS,
+		LUMP_NODES,
+		LUMP_CLIPNODES,
+		LUMP_ENTITIES,
+		LUMP_MODELS,
+	};
+
+	in0 = in = buffer;
+	ver = le32(in);
+	if(ver == BSPVERSION){
+		loadf[LUMP_EDGES] = BSP_LoadEdges;
+		loadf[LUMP_FACES] = BSP_LoadFaces;
+		loadf[LUMP_MARKSURFACES] = BSP_LoadMarksurfaces;
+		loadf[LUMP_LEAFS] = BSP_LoadLeafs;
+		loadf[LUMP_NODES] = BSP_LoadNodes;
+		loadf[LUMP_CLIPNODES] = BSP_LoadClipnodes;
+	}else if(ver == BSP2VERSION){
+		loadf[LUMP_EDGES] = BSP2_LoadEdges;
+		loadf[LUMP_FACES] = BSP2_LoadFaces;
+		loadf[LUMP_MARKSURFACES] = BSP2_LoadMarksurfaces;
+		loadf[LUMP_LEAFS] = BSP2_LoadLeafs;
+		loadf[LUMP_NODES] = BSP2_LoadNodes;
+		loadf[LUMP_CLIPNODES] = BSP2_LoadClipnodes;
+	}else{
+		werrstr("unsupported version: %ux", ver);
+		goto err;
+	}
+
+	if(total < 4+nelem(loadf)*2*4){
+		werrstr("truncated: total=%d", total);
+		goto err;
+	}
+
+	mod->type = mod_brush;
+
+	for(i = 0; i < nelem(loadf); i++){
+		in = in0+4+2*4*order[i];
+		off = le32(in);
+		sz = le32(in);
+		if(off < 0 || sz < 0 || off+sz > total){
+			werrstr("invalid lump off=%d sz=%d total=%d", off, sz, total);
+			goto err;
+		}
+		if(loadf[order[i]](mod, in0+off, sz) != 0)
+			goto err;
+	}
+
+	BSP_MakeHull0(mod);
+
+	mod->numframes = 2;		// regular and alternate animation
+	mod->flags = 0;
+
+	// set up the submodels (FIXME: this is confusing)
+	for(i = 0; i < mod->numsubmodels; i++){
+		bm = &mod->submodels[i];
+
+		mod->hulls[0].firstclipnode = bm->headnode[0];
+		for(j = 1; j < MAX_MAP_HULLS; j++){
+			mod->hulls[j].firstclipnode = bm->headnode[j];
+			mod->hulls[j].lastclipnode = mod->numclipnodes-1;
+		}
+
+		mod->firstmodelsurface = bm->firstface;
+		mod->nummodelsurfaces = bm->numfaces;
+		mod->blend = false;
+		for(j = bm->firstface; j < bm->firstface+bm->numfaces; j++){
+			if(mod->surfaces[j].flags & SURF_TRANS){
+				mod->blend = true;
+				break;
+			}
+		}
+
+		VectorCopy(bm->maxs, mod->maxs);
+		VectorCopy(bm->mins, mod->mins);
+		mod->radius = RadiusFromBounds(mod->mins, mod->maxs);
+		mod->numleafs = bm->visleafs;
+
+		if(i < mod->numsubmodels-1){
+			snprint(name, sizeof(name), "*%d", i+1);
+			submod = Mod_FindName(name);
+			*submod = *mod;
+			strcpy(submod->name, name);
+			mod = submod;
+		}
+	}
+
+	return;
+err:
+	Host_Error("Mod_LoadBrushModel: %s: %s", mod->name, lerr());
+}
--- /dev/null
+++ b/model_bsp.c
@@ -1,0 +1,641 @@
+#include "quakedef.h"
+
+void
+BSP_SetParent(mnode_t *node, mnode_t *parent)
+{
+	node->parent = parent;
+	if(node->contents < 0)
+		return;
+	BSP_SetParent(node->children[0], node);
+	BSP_SetParent(node->children[1], node);
+}
+
+int
+BSP_CalcSurfaceExtents(model_t *mod, msurface_t *s)
+{
+	float mins[2], maxs[2], val;
+	int i,j, e;
+	mvertex_t *v;
+	mtexinfo_t *tex;
+	int bmins[2], bmaxs[2];
+
+	mins[0] = mins[1] = Q_MAXFLOAT;
+	maxs[0] = maxs[1] = -Q_MAXFLOAT;
+	tex = s->texinfo;
+
+	for(i = 0; i < s->numedges; i++){
+		e = mod->surfedges[s->firstedge+i];
+		if(e >= 0)
+			v = &mod->vertexes[mod->edges[e].v[0]];
+		else
+			v = &mod->vertexes[mod->edges[-e].v[1]];
+
+		for(j = 0; j < 2; j++){
+			val = (double)v->position[0] * (double)tex->vecs[j][0] +
+				(double)v->position[1] * (double)tex->vecs[j][1] +
+				(double)v->position[2] * (double)tex->vecs[j][2] +
+				(double)tex->vecs[j][3];
+			if(val < mins[j])
+				mins[j] = val;
+			if(val > maxs[j])
+				maxs[j] = val;
+		}
+	}
+
+	for(i = 0; i < 2; i++){
+		bmins[i] = floor(mins[i]/16.0);
+		bmaxs[i] = ceil(maxs[i]/16.0);
+
+		s->texturemins[i] = bmins[i] * 16;
+		s->extents[i] = (bmaxs[i] - bmins[i]) * 16;
+		if((tex->flags & TEX_SPECIAL) == 0 && s->extents[i] > 2000){
+			werrstr("BSP_CalcSurfaceExtents: bad surface: texture=%s flags=%ux extents[%d]=%d",
+				tex->texture->name,
+				tex->flags,
+				i,
+				s->extents[i]
+			);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int
+BSP_LoadTextures(model_t *mod, byte *in, int sz)
+{
+	int off, i, j, pixels, num, max, altmax, w, h;
+	byte *p, *in0;
+	texture_t *tx, *tx2;
+	texture_t *anims[10];
+	texture_t *altanims[10];
+	static const int elsz = 16+2*4+4*4;
+
+	if(sz < 1){
+		mod->textures = nil;
+		return 0;
+	}
+	if(sz < 4 || (sz % 4) != 0){
+		werrstr("funny lump size");
+		goto err;
+	}
+
+	in0 = in;
+	mod->numtextures = le32(in);
+	if(mod->numtextures*4 > sz-4){
+		werrstr("overflow? %d > %d", mod->numtextures*4, sz-4);
+		goto err;
+	}
+	mod->textures = Hunk_Alloc(mod->numtextures * sizeof(*mod->textures));
+
+	for(i = 0; i < mod->numtextures; i++){
+		off = le32(in);
+		if(off == -1)
+			continue;
+		if(off < 0 || off > sz-elsz){
+			werrstr("bad offset %d (sz %d)", off, sz);
+			goto err;
+		}
+		p = in0+off+16;
+		w = le32(p);
+		h = le32(p);
+		pixels = w*h/64*85;
+		tx = Hunk_Alloc(sizeof(*tx) + pixels);
+		strncpy(tx->name, (char*)in0+off, sizeof(tx->name)-1);
+		tx->name[sizeof(tx->name)-1] = 0;
+		for(j = 0; j < MIPLEVELS; j++)
+			tx->offsets[j] = le32(p) + sizeof(texture_t) - (16+2*4+4*4);
+		mod->textures[i] = tx;
+		tx->width = w;
+		tx->height = h;
+		// the pixels immediately follow the structures
+		memcpy(tx+1, p, pixels);
+		if(strncmp(tx->name, "sky", 3) == 0)
+			R_InitSky(tx);
+	}
+
+	// sequence the animations
+	for(i = 0; i < mod->numtextures; i++){
+		tx = mod->textures[i];
+		if(!tx || tx->name[0] != '+')
+			continue;
+		if(tx->anim_next)
+			continue;	// already sequenced
+
+		// find the number of frames in the animation
+		memset(anims, 0, sizeof(anims));
+		memset(altanims, 0, sizeof(altanims));
+
+		max = tx->name[1];
+		if(max >= 'a' && max <= 'z')
+			max -= 'a' - 'A';
+		if(max >= '0' && max <= '9'){
+			max -= '0';
+			altmax = 0;
+			anims[max++] = tx;
+		}else if(max >= 'A' && max <= 'J'){
+			altmax = max - 'A';
+			max = 0;
+			altanims[altmax++] = tx;
+		}else{
+badanim:
+			werrstr("bad animating texture: %s", tx->name);
+			goto err;
+		}
+
+		for(j = i+1; j < mod->numtextures; j++){
+			tx2 = mod->textures[j];
+			if(!tx2 || tx2->name[0] != '+')
+				continue;
+			if(strcmp(tx2->name+2, tx->name+2) != 0)
+				continue;
+
+			num = tx2->name[1];
+			if(num >= 'a' && num <= 'z')
+				num -= 'a' - 'A';
+			if(num >= '0' && num <= '9'){
+				num -= '0';
+				anims[num] = tx2;
+				if(num+1 > max)
+					max = num + 1;
+			}else if(num >= 'A' && num <= 'J'){
+				num = num - 'A';
+				altanims[num] = tx2;
+				if(num+1 > altmax)
+					altmax = num+1;
+			}else{
+				goto badanim;
+			}
+		}
+
+#define	ANIM_CYCLE	2
+		// link them all together
+		for(j = 0; j < max; j++){
+			tx2 = anims[j];
+			if(!tx2){
+badframe:
+				werrstr("missing frame %d of %s", j, tx->name);
+				goto err;
+			}
+			tx2->anim_total = max * ANIM_CYCLE;
+			tx2->anim_min = j * ANIM_CYCLE;
+			tx2->anim_max = (j+1) * ANIM_CYCLE;
+			tx2->anim_next = anims[(j+1) % max];
+			if(altmax)
+				tx2->alternate_anims = altanims[0];
+		}
+		for(j = 0; j < altmax; j++){
+			tx2 = altanims[j];
+			if(!tx2)
+				goto badframe;
+			tx2->anim_total = altmax * ANIM_CYCLE;
+			tx2->anim_min = j * ANIM_CYCLE;
+			tx2->anim_max = (j+1) * ANIM_CYCLE;
+			tx2->anim_next = altanims[(j+1) % altmax];
+			if(max)
+				tx2->alternate_anims = anims[0];
+		}
+	}
+
+	return 0;
+err:
+	werrstr("BSP_LoadTextures: %s", lerr());
+	return -1;
+}
+
+int
+BSP_LoadLighting(model_t *mod, byte *in, int sz)
+{
+	if(sz == 0)
+		mod->lightdata = nil;
+	else
+		memcpy(mod->lightdata = Hunk_Alloc(sz), in, sz);
+	return 0;
+}
+
+int
+BSP_LoadVisibility(model_t *mod, byte *in, int sz)
+{
+	if(sz == 0)
+		mod->visdata = nil;
+	else
+		memcpy(mod->visdata = Hunk_Alloc(sz), in, sz);
+	return 0;
+}
+
+int
+BSP_LoadEntities(model_t *mod, byte *in, int sz)
+{
+	if(sz == 0)
+		mod->entities = nil;
+	else
+		memcpy(mod->entities = Hunk_Alloc(sz), in, sz);
+	return 0;
+}
+
+int
+BSP_LoadVertexes(model_t *mod, byte *in, int sz)
+{
+	mvertex_t *out;
+	int i;
+	static const int elsz = 3*4;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadVertexes: funny lump size");
+		return -1;
+	}
+	mod->numvertexes = sz / elsz;
+	mod->vertexes = out = Hunk_Alloc(mod->numvertexes * sizeof(*out));
+	for(i = 0; i < mod->numvertexes; i++, out++){
+		out->position[0] = f32(in);
+		out->position[1] = f32(in);
+		out->position[2] = f32(in);
+	}
+	return 0;
+}
+
+int
+BSP_LoadSubmodels(model_t *mod, byte *in, int sz)
+{
+	submodel_t *out;
+	int i, j;
+	static const int elsz = 3*4+3*4+3*4+MAX_MAP_HULLS*4+4+4+4;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadSubmodels: funny lump size");
+		return -1;
+	}
+	mod->numsubmodels = sz / elsz;
+	mod->submodels = out = Hunk_Alloc(mod->numsubmodels * sizeof(*out));
+
+	for(i = 0 ; i < mod->numsubmodels; i++, out++){
+		for(j = 0; j < 3; j++)
+			out->mins[j] = f32(in) - 1.0;
+		for(j = 0; j < 3; j++)
+			out->maxs[j] = f32(in) + 1.0;
+		for(j = 0; j < 3; j++)
+			out->origin[j] = f32(in);
+		for(j = 0; j < MAX_MAP_HULLS; j++)
+			out->headnode[j] = le32(in);
+		out->visleafs = le32(in);
+		out->firstface = le32(in);
+		out->numfaces = le32(in);
+	}
+	return 0;
+}
+
+int
+BSP_LoadEdges(model_t *mod, byte *in, int sz)
+{
+	medge_t *out;
+	int i;
+	static const int elsz = 2*2;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadEdges: funny lump size");
+		return -1;
+	}
+	mod->numedges = sz / elsz;
+	// FIXME(sigrid): why +1?
+	mod->edges = out = Hunk_Alloc((mod->numedges+1) * sizeof(*out));
+
+	for(i = 0; i < mod->numedges; i++, out++){
+		out->v[0] = le16u(in);
+		out->v[1] = le16u(in);
+	}
+	return 0;
+}
+
+int
+BSP_LoadTexinfo(model_t *mod, byte *in, int sz)
+{
+	mtexinfo_t *out;
+	int i, j, k, miptex;
+	float len1, len2;
+	static const int elsz = 2*4*4+4+4;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadTexinfo: funny lump size");
+		return -1;
+	}
+	mod->numtexinfo = sz / elsz;
+	mod->texinfo = out = Hunk_Alloc(mod->numtexinfo * sizeof(*out));
+
+	for(i = 0; i < mod->numtexinfo; i++, out++){
+		for(j = 0; j < 2; j++){
+			for(k = 0; k < 4; k++)
+				out->vecs[j][k] = f32(in);
+		}
+		len1 = Length(out->vecs[0]);
+		len2 = Length(out->vecs[1]);
+		len1 = (len1 + len2)/2;
+		if (len1 < 0.32)
+			out->mipadjust = 4;
+		else if (len1 < 0.49)
+			out->mipadjust = 3;
+		else if (len1 < 0.99)
+			out->mipadjust = 2;
+		else
+			out->mipadjust = 1;
+
+		miptex = le32(in);
+		out->flags = le32(in);
+		out->texture = nil;
+		if(mod->textures != nil){
+			if(miptex >= mod->numtextures){
+				werrstr("BSP_LoadTexinfo: miptex >= mod->numtextures");
+				return -1;
+			}
+			out->texture = mod->textures[miptex];
+		}
+		if(out->texture == nil){
+			out->texture = r_notexture_mip; // texture not found
+			out->flags = 0;
+		}
+	}
+	return 0;
+}
+
+int
+BSP_LoadFaces(model_t *mod, byte *in, int sz)
+{
+	msurface_t *out;
+	int i, surfnum;
+	static const int elsz = 2+2+4+2+2+MAXLIGHTMAPS+4;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadFaces: funny lump size");
+		return -1;
+	}
+	mod->numsurfaces = sz / elsz;
+	mod->surfaces = out = Hunk_Alloc(mod->numsurfaces * sizeof(*out));
+
+	for(surfnum = 0; surfnum < mod->numsurfaces; surfnum++, out++){
+		out->plane = mod->planes + le16u(in);
+		out->flags = le16u(in) ? SURF_PLANEBACK : 0;
+		out->firstedge = le32u(in);
+		out->numedges = le16u(in);
+		out->texinfo = mod->texinfo + le16u(in);
+
+		if(BSP_CalcSurfaceExtents(mod, out) != 0)
+			return -1;
+
+		// lighting info
+
+		memmove(out->styles, in, MAXLIGHTMAPS);
+		in += MAXLIGHTMAPS;
+		out->samples = (i = le32(in)) < 0 ? nil : mod->lightdata + i;
+
+		// set the drawing flags flag
+
+		if(strncmp(out->texinfo->texture->name, "sky", 3) == 0)
+			out->flags |= SURF_DRAWSKY | SURF_DRAWTILED;
+		else if(out->texinfo->texture->name[0] == '*'){	// turbulent
+			out->flags |= SURF_DRAWTURB | SURF_DRAWTILED | SURF_TRANS;
+			for(i = 0; i < 2; i++){
+				out->extents[i] = 16384;
+				out->texturemins[i] = -8192;
+			}
+			if(strstr(out->texinfo->texture->name, "lava") != nil)
+				out->flags |= SURF_LAVA;
+			if(strstr(out->texinfo->texture->name, "slime") != nil)
+				out->flags |= SURF_SLIME;
+			if(strstr(out->texinfo->texture->name, "tele") != nil){
+				out->flags |= SURF_TELE;
+				out->flags &= ~SURF_TRANS;
+			}
+		}else if(out->texinfo->texture->name[0] == '{')
+			out->flags |= SURF_TRANS | SURF_FENCE;
+	}
+	return 0;
+}
+
+int
+BSP_LoadNodes(model_t *mod, byte *in, int sz)
+{
+	int i, j, p;
+	mnode_t *out;
+	static const int elsz = 4+2*2+3*2+3*2+2+2;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadNodes: funny lump size");
+		return -1;
+	}
+	mod->numnodes = sz / elsz;
+	mod->nodes = out = Hunk_Alloc(mod->numnodes * sizeof(*out));
+
+	for(i = 0; i < mod->numnodes; i++, out++){
+		out->plane = mod->planes + le32u(in);
+
+		for(j = 0; j < 2; j++){
+			p = le16u(in);
+			if(p >= 0 && p < mod->numnodes)
+				out->children[j] = mod->nodes + p;
+			else{
+				p = 0xffff-p;
+				assert(mod->leafs != nil);
+				if(p >= 0 && p < mod->numleafs)
+					out->children[j] = (mnode_t*)(mod->leafs + p);
+				else{
+					Con_DPrintf("BSP_LoadNodes: invalid node child\n");
+					out->children[j] = (mnode_t*)mod->leafs;
+				}
+			}
+		}
+
+		for(j = 0; j < 6; j++)
+			out->minmaxs[j] = le16(in);
+
+		out->firstsurface = le16u(in);
+		out->numsurfaces = le16u(in);
+	}
+
+	BSP_SetParent(mod->nodes, nil); // sets nodes and leafs
+	return 0;
+}
+
+int
+BSP_LoadLeafs(model_t *mod, byte *in, int sz)
+{
+	mleaf_t *out;
+	int i, j, p;
+	static const int elsz = 4+4+3*2+3*2+2+2+Namb;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadLeafs: funny lump size");
+		return -1;
+	}
+	mod->numleafs = sz / elsz;
+	mod->leafs = out = Hunk_Alloc(mod->numleafs * sizeof(*out));
+
+	for(i = 0; i < mod->numleafs; i++, out++){
+		out->contents = le32(in);
+		out->compressed_vis = (p = le32(in)) < 0 ? nil : mod->visdata + p;
+
+		for(j = 0; j < 3; j++)
+			out->minmaxs[0+j] = le16(in);
+		for(j = 0; j < 3; j++)
+			out->minmaxs[3+j] = le16(in);
+
+		out->firstmarksurface = mod->marksurfaces + le16u(in);
+		out->nummarksurfaces = le16u(in);
+
+		memmove(out->ambient_sound_level, in, Namb);
+		in += Namb;
+	}
+	return 0;
+}
+
+int
+BSP_LoadClipnodes(model_t *mod, byte *in, int sz)
+{
+	mclipnode_t *out;
+	int i;
+	hull_t *hull;
+	static const int elsz = 4+2*2;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadClipnodes: funny lump size");
+		return -1;
+	}
+	mod->numclipnodes = sz / elsz;
+	mod->clipnodes = out = Hunk_Alloc(mod->numclipnodes * sizeof(*out));
+
+	hull = &mod->hulls[1];
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = mod->numclipnodes-1;
+	hull->planes = mod->planes;
+	hull->clip_mins[0] = -16;
+	hull->clip_mins[1] = -16;
+	hull->clip_mins[2] = -24;
+	hull->clip_maxs[0] = 16;
+	hull->clip_maxs[1] = 16;
+	hull->clip_maxs[2] = 32;
+
+	hull = &mod->hulls[2];
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = mod->numclipnodes-1;
+	hull->planes = mod->planes;
+	hull->clip_mins[0] = -32;
+	hull->clip_mins[1] = -32;
+	hull->clip_mins[2] = -24;
+	hull->clip_maxs[0] = 32;
+	hull->clip_maxs[1] = 32;
+	hull->clip_maxs[2] = 64;
+
+	for(i = 0; i < mod->numclipnodes; i++, out++){
+		out->planenum = le32u(in);
+		out->children[0] = le16u(in);
+		out->children[1] = le16u(in);
+		if(out->children[0] >= mod->numclipnodes)
+			out->children[0] -= 0x10000;
+		if(out->children[1] >= mod->numclipnodes)
+			out->children[1] -= 0x10000;
+	}
+	return 0;
+}
+
+void
+BSP_MakeHull0(model_t *mod)
+{
+	mnode_t *in, *child;
+	mclipnode_t *out;
+	int i, j;
+	hull_t *hull;
+
+	hull = &mod->hulls[0];
+
+	in = mod->nodes;
+	out = Hunk_Alloc(mod->numnodes * sizeof(*out));
+
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = mod->numnodes-1;
+	hull->planes = mod->planes;
+
+	for(i = 0; i < mod->numnodes; i++, out++, in++){
+		out->planenum = in->plane - mod->planes;
+		for(j = 0; j < 2; j++){
+			child = in->children[j];
+			if(child->contents < 0)
+				out->children[j] = child->contents;
+			else
+				out->children[j] = child - mod->nodes;
+		}
+	}
+}
+
+int
+BSP_LoadMarksurfaces(model_t *mod, byte *in, int sz)
+{
+	int i, j;
+	msurface_t **out;
+	static const int elsz = 2;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadMarksurfaces: funny lump size");
+		return -1;
+	}
+	mod->nummarksurfaces = sz / elsz;
+	mod->marksurfaces = out = Hunk_Alloc(mod->nummarksurfaces * sizeof(*out));
+
+	for(i = 0; i < mod->nummarksurfaces; i++){
+		j = le16u(in);
+		if(j < 0 || j >= mod->numsurfaces){
+			werrstr("BSP_LoadMarksurfaces: bad surface number: %d", j);
+			return -1;
+		}
+		out[i] = mod->surfaces + j;
+	}
+	return 0;
+}
+
+int
+BSP_LoadSurfedges(model_t *mod, byte *in, int sz)
+{
+	int i, *out;
+	static const int elsz = 4;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadSurfedges: funny lump size");
+		return -1;
+	}
+	mod->numsurfedges = sz / elsz;
+	mod->surfedges = out = Hunk_Alloc(mod->numsurfedges * sizeof(*out));
+
+	for(i = 0; i < mod->numsurfedges; i++)
+		out[i] = le32(in);
+	return 0;
+}
+
+int
+BSP_LoadPlanes(model_t *mod, byte *in, int sz)
+{
+	int i, j, bits;
+	mplane_t *out;
+	static const int elsz = 3*4+4+4;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadPlanes: funny lump size");
+		return -1;
+	}
+	mod->numplanes = sz / elsz;
+	// FIXME(sigrid): why " * 2"???
+	mod->planes = out = Hunk_Alloc(mod->numplanes * 2 * sizeof(*out));
+
+	for(i = 0; i < mod->numplanes; i++, out++){
+		bits = 0;
+		for(j = 0; j < 3; j++){
+			if((out->normal[j] = f32(in)) < 0)
+				bits |= 1<<j;
+		}
+
+		out->dist = f32(in);
+		out->type = le32(in);
+		out->signbits = bits;
+	}
+	return 0;
+}
--- /dev/null
+++ b/model_bsp2.c
@@ -1,0 +1,231 @@
+#include "quakedef.h"
+
+void BSP_SetParent(mnode_t *node, mnode_t *parent);
+void BSP_MakeHull0(model_t *mod);
+int BSP_CalcSurfaceExtents(model_t *mod, msurface_t *s);
+
+int
+BSP2_LoadEdges(model_t *mod, byte *in, int sz)
+{
+	medge_t *out;
+	int i;
+	static const int elsz = 2*4;
+
+	if(sz % elsz){
+		werrstr("BSP2_LoadEdges: funny lump size");
+		return -1;
+	}
+	mod->numedges = sz / elsz;
+	// FIXME(sigrid): why +1?
+	mod->edges = out = Hunk_Alloc((mod->numedges+1) * sizeof(*out));
+
+	for(i = 0; i < mod->numedges; i++, out++){
+		out->v[0] = le32u(in);
+		out->v[1] = le32u(in);
+	}
+	return 0;
+}
+
+int
+BSP2_LoadLeafs(model_t *mod, byte *in, int sz)
+{
+	mleaf_t *out;
+	int i, j, p;
+	static const int elsz = 4+4+3*4+3*4+4+4+Namb;
+
+	if(sz % elsz){
+		werrstr("BSP2_LoadLeafs: funny lump size");
+		return -1;
+	}
+	mod->numleafs = sz / elsz;
+	mod->leafs = out = Hunk_Alloc(mod->numleafs * sizeof(*out));
+
+	for(i = 0; i < mod->numleafs; i++, out++){
+		out->contents = le32(in);
+		out->compressed_vis = (p = le32(in)) < 0 ? nil : mod->visdata + p;
+
+		for(j = 0; j < 3; j++)
+			out->minmaxs[0+j] = f32(in);
+		for(j = 0; j < 3; j++)
+			out->minmaxs[3+j] = f32(in);
+
+		out->firstmarksurface = mod->marksurfaces + le32u(in);
+		out->nummarksurfaces = le32u(in);
+
+		memmove(out->ambient_sound_level, in, Namb);
+		in += Namb;
+	}
+	return 0;
+}
+
+int
+BSP2_LoadFaces(model_t *mod, byte *in, int sz)
+{
+	msurface_t *out;
+	int i, surfnum;
+	static const int elsz = 4+4+4+4+4+MAXLIGHTMAPS+4;
+
+	if(sz % elsz){
+		werrstr("BSP2_LoadFaces: funny lump size");
+		return -1;
+	}
+	mod->numsurfaces = sz / elsz;
+	mod->surfaces = out = Hunk_Alloc(mod->numsurfaces * sizeof(*out));
+
+	for(surfnum = 0; surfnum < mod->numsurfaces; surfnum++, out++){
+		out->plane = mod->planes + le32u(in);
+		out->flags = le32u(in) ? SURF_PLANEBACK : 0;
+		out->firstedge = le32u(in);
+		out->numedges = le32u(in);
+		out->texinfo = mod->texinfo + le32u(in);
+
+		if(BSP_CalcSurfaceExtents(mod, out) != 0)
+			return -1;
+
+		// lighting info
+
+		memmove(out->styles, in, MAXLIGHTMAPS);
+		in += MAXLIGHTMAPS;
+		out->samples = (i = le32(in)) < 0 ? nil : mod->lightdata + i;
+
+		// set the drawing flags flag
+
+		if(strncmp(out->texinfo->texture->name, "sky", 3) == 0)
+			out->flags |= SURF_DRAWSKY | SURF_DRAWTILED;
+		else if(out->texinfo->texture->name[0] == '*'){	// turbulent
+			out->flags |= SURF_DRAWTURB | SURF_DRAWTILED | SURF_TRANS;
+			for(i = 0; i < 2; i++){
+				out->extents[i] = 16384;
+				out->texturemins[i] = -8192;
+			}
+			if(strstr(out->texinfo->texture->name, "lava") != nil)
+				out->flags |= SURF_LAVA;
+			if(strstr(out->texinfo->texture->name, "slime") != nil)
+				out->flags |= SURF_SLIME;
+			if(strstr(out->texinfo->texture->name, "tele") != nil){
+				out->flags |= SURF_TELE;
+				out->flags &= ~SURF_TRANS;
+			}
+		}else if(out->texinfo->texture->name[0] == '{')
+			out->flags |= SURF_TRANS | SURF_FENCE;
+	}
+	return 0;
+}
+
+int
+BSP2_LoadNodes(model_t *mod, byte *in, int sz)
+{
+	int i, j, p;
+	mnode_t *out;
+	static const int elsz = 4+2*4+3*4+3*4+4+4;
+
+	if(sz % elsz){
+		werrstr("BSP_LoadNodes: funny lump size");
+		return -1;
+	}
+	mod->numnodes = sz / elsz;
+	mod->nodes = out = Hunk_Alloc(mod->numnodes * sizeof(*out));
+
+	for(i = 0; i < mod->numnodes; i++, out++){
+		out->plane = mod->planes + le32u(in);
+
+		for(j = 0; j < 2; j++){
+			p = le32(in);
+			if(p >= 0 && p < mod->numnodes)
+				out->children[j] = mod->nodes + p;
+			else{
+				p = -1-p;
+				if(p >= 0 && p < mod->numleafs)
+					out->children[j] = (mnode_t*)(mod->leafs + p);
+				else{
+					Con_DPrintf("BSP2_LoadNodes: invalid node child\n");
+					out->children[j] = (mnode_t*)mod->leafs;
+				}
+			}
+		}
+
+		for(j = 0; j < 6; j++)
+			out->minmaxs[j] = f32(in);
+
+		out->firstsurface = le32u(in);
+		out->numsurfaces = le32u(in);
+	}
+
+	BSP_SetParent(mod->nodes, nil); // sets nodes and leafs
+	return 0;
+}
+
+int
+BSP2_LoadClipnodes(model_t *mod, byte *in, int sz)
+{
+	mclipnode_t	*out;
+	int i;
+	hull_t *hull;
+	static const int elsz = 4+2*4;
+
+	if(sz % elsz){
+		werrstr("BSP2_LoadClipnodes: funny lump size");
+		return -1;
+	}
+	mod->numclipnodes = sz / elsz;
+	mod->clipnodes = out = Hunk_Alloc(mod->numclipnodes * sizeof(*out));
+
+	hull = &mod->hulls[1];
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = mod->numclipnodes-1;
+	hull->planes = mod->planes;
+	hull->clip_mins[0] = -16;
+	hull->clip_mins[1] = -16;
+	hull->clip_mins[2] = -24;
+	hull->clip_maxs[0] = 16;
+	hull->clip_maxs[1] = 16;
+	hull->clip_maxs[2] = 32;
+
+	hull = &mod->hulls[2];
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = mod->numclipnodes-1;
+	hull->planes = mod->planes;
+	hull->clip_mins[0] = -32;
+	hull->clip_mins[1] = -32;
+	hull->clip_mins[2] = -24;
+	hull->clip_maxs[0] = 32;
+	hull->clip_maxs[1] = 32;
+	hull->clip_maxs[2] = 64;
+
+	for(i = 0; i < mod->numclipnodes; i++, out++){
+		out->planenum = le32u(in);
+		out->children[0] = le32u(in);
+		out->children[1] = le32u(in);
+		if(out->children[0] >= mod->numclipnodes)
+			out->children[0] -= 0x10000;
+		if(out->children[1] >= mod->numclipnodes)
+			out->children[1] -= 0x10000;
+	}
+	return 0;
+}
+
+int
+BSP2_LoadMarksurfaces(model_t *mod, byte *in, int sz)
+{
+	int i, j;
+	msurface_t **out;
+
+	if(sz % 4){
+		werrstr("BSP2_LoadMarksurfaces: funny lump size");
+		return -1;
+	}
+	mod->nummarksurfaces = sz / 4;
+	mod->marksurfaces = out = Hunk_Alloc(mod->nummarksurfaces * sizeof(*out));
+
+	for(i = 0; i < mod->nummarksurfaces; i++){
+		j = le32u(in);
+		if(j < 0 || j >= mod->numsurfaces){
+			werrstr("BSP2_LoadMarksurfaces: bad surface number: %d", j);
+			return -1;
+		}
+		out[i] = mod->surfaces + j;
+	}
+	return 0;
+}
--- a/unix/u.h
+++ b/unix/u.h
@@ -38,7 +38,7 @@
 #define setmalloctag(p, t) do{USED(p); USED(t);}while(0)
 
 extern char lasterr[256];
-#define werrstr(fmt, ...) do{snprint(lasterr, sizeof(lasterr), fmt, __VA_ARGS__); }while(0)
+#define werrstr(fmt...) do{snprint(lasterr, sizeof(lasterr), fmt); }while(0)
 
 char *seprint(char *, char *, char *, ...);
 int nrand(int);