r716 - in trunk: . base/maps

lordhavoc at icculus.org lordhavoc at icculus.org
Fri May 12 07:04:49 EDT 2006


Author: lordhavoc
Date: 2006-05-12 07:04:48 -0400 (Fri, 12 May 2006)
New Revision: 716

Modified:
   trunk/base/maps/test.ent
   trunk/model.c
   trunk/model.h
   trunk/nstring.c
   trunk/nstring.h
   trunk/util.c
Log:
reworked model system to use Import functions for loading each model
format and also Write now identifies file extension, this means that
util_convertobj has become util_convertmodel (able to convert any
supported format to any other supported format), and obj models now work
in the game without conversion.


Modified: trunk/base/maps/test.ent
===================================================================
--- trunk/base/maps/test.ent	2006-05-12 11:03:18 UTC (rev 715)
+++ trunk/base/maps/test.ent	2006-05-12 11:04:48 UTC (rev 716)
@@ -19,7 +19,7 @@
 	"classname" "vehicle_tank"
 }
 {
-	"origin" "0 -23 8"
+	"origin" "0 -35 6"
 	"classname" "model"
 	"model" "models/players/generic/player_generic.md5mesh"
 }

Modified: trunk/model.c
===================================================================
--- trunk/model.c	2006-05-12 11:03:18 UTC (rev 715)
+++ trunk/model.c	2006-05-12 11:04:48 UTC (rev 716)
@@ -188,11 +188,12 @@
 	Mem_Free(&hashtable);
 }
 
-void Model_Build_Begin(Model *b, Mem_Zone *memzone, Nbool isskeletal)
+void Model_Build_Begin(Model *model, const char *filename, Mem_Zone *memzone, Nbool isskeletal)
 {
-	memset(b, 0, sizeof(*b));
-	b->memzone = memzone;
-	b->isskeletal = isskeletal;
+	memset(model, 0, sizeof(*model));
+	strncpy(model->filename, filename, sizeof(model->filename) - 1);
+	model->memzone = memzone;
+	model->isskeletal = isskeletal;
 }
 
 static void Model_Build_GenerateNeighbors(Model_Mesh *mesh)
@@ -227,69 +228,85 @@
 	// TODO: how do do this?
 }
 
-void Model_Build_Compile(Model *b, Nbool generateneighbors, Nbool generateplanes, Nbool generatenormals, Nbool generatetangents, Nbool generatelightmaptexcoords, Nbool generatecollisionbrushes)
+void Model_Build_Compile(Model *model, Nbool generateneighbors, Nbool generateplanes, Nbool generatenormals, Nbool generatetangents, Nbool generatelightmaptexcoords, Nbool generatecollisionbrushes)
 {
-	NUint32 i, j;
+	NUint32 meshindex, vertexindex, weightindex, transformindex;
 	const float *v;
 	Model_Mesh *mesh;
 	Nbool initbox;
 	initbox = true;
-	VectorClear(b->basecullmins);
-	VectorClear(b->basecullmaxs);
-	for (i = 0, mesh = b->data_meshes;i < b->num_meshes;i++, mesh++)
+	VectorClear(model->basecullmins);
+	VectorClear(model->basecullmaxs);
+	// clear the culling radius value in each transform as it will be regenerated
+	if (model->num_transforms)
+		for (transformindex = 0;transformindex < model->num_transforms;transformindex++)
+			model->data_transforminfo[transformindex].radius = 0;
+	// process each mesh
+	for (meshindex = 0, mesh = model->data_meshes;meshindex < model->num_meshes;meshindex++, mesh++)
 	{
+		// load the material
+		mesh->resource_material = Resource_IndexForName(mesh->materialname, RESOURCETYPE_MATERIAL, 0, 0);
+
+		// resize triangle arrays if there is some wasted space
 		if (mesh->max_triangles > mesh->num_triangles)
 		{
 			mesh->max_triangles = mesh->num_triangles;
-			if (mesh->data_element3i) Mem_ReAlloc(b->memzone, &mesh->data_element3i, mesh->max_triangles * 3 * sizeof(*mesh->data_element3i));
-			if (mesh->data_neighbor3i) Mem_ReAlloc(b->memzone, &mesh->data_neighbor3i, mesh->max_triangles * 3 * sizeof(*mesh->data_neighbor3i));
-			if (mesh->data_plane4f) Mem_ReAlloc(b->memzone, &mesh->data_plane4f, mesh->max_triangles * 4 * sizeof(*mesh->data_plane4f));
+			if (mesh->data_element3i) Mem_ReAlloc(model->memzone, &mesh->data_element3i, mesh->max_triangles * 3 * sizeof(*mesh->data_element3i));
+			if (mesh->data_neighbor3i) Mem_ReAlloc(model->memzone, &mesh->data_neighbor3i, mesh->max_triangles * 3 * sizeof(*mesh->data_neighbor3i));
+			if (mesh->data_plane4f) Mem_ReAlloc(model->memzone, &mesh->data_plane4f, mesh->max_triangles * 4 * sizeof(*mesh->data_plane4f));
 		}
 
+		// resize vertex arrays if there is some wasted space
 		if (mesh->max_vertices > mesh->num_vertices)
 		{
 			mesh->max_vertices = mesh->num_vertices;
-			if (mesh->data_vertex3f) Mem_ReAlloc(b->memzone, &mesh->data_vertex3f, mesh->max_vertices * 3 * sizeof(*mesh->data_vertex3f));
-			if (mesh->data_svector3f) Mem_ReAlloc(b->memzone, &mesh->data_svector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_svector3f));
-			if (mesh->data_tvector3f) Mem_ReAlloc(b->memzone, &mesh->data_tvector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_tvector3f));
-			if (mesh->data_normal3f) Mem_ReAlloc(b->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
-			if (mesh->data_texcoord2f) Mem_ReAlloc(b->memzone, &mesh->data_texcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_texcoord2f));
-			if (mesh->data_lightmaptexcoord2f) Mem_ReAlloc(b->memzone, &mesh->data_lightmaptexcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_lightmaptexcoord2f));
-			if (mesh->data_weightindex4b) Mem_ReAlloc(b->memzone, &mesh->data_weightindex4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightindex4b));
-			if (mesh->data_weightvalue4b) Mem_ReAlloc(b->memzone, &mesh->data_weightvalue4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightvalue4b));
+			if (mesh->data_vertex3f) Mem_ReAlloc(model->memzone, &mesh->data_vertex3f, mesh->max_vertices * 3 * sizeof(*mesh->data_vertex3f));
+			if (mesh->data_svector3f) Mem_ReAlloc(model->memzone, &mesh->data_svector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_svector3f));
+			if (mesh->data_tvector3f) Mem_ReAlloc(model->memzone, &mesh->data_tvector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_tvector3f));
+			if (mesh->data_normal3f) Mem_ReAlloc(model->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
+			if (mesh->data_texcoord2f) Mem_ReAlloc(model->memzone, &mesh->data_texcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_texcoord2f));
+			if (mesh->data_lightmaptexcoord2f) Mem_ReAlloc(model->memzone, &mesh->data_lightmaptexcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_lightmaptexcoord2f));
+			if (mesh->data_weightindex4b) Mem_ReAlloc(model->memzone, &mesh->data_weightindex4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightindex4b));
+			if (mesh->data_weightvalue4b) Mem_ReAlloc(model->memzone, &mesh->data_weightvalue4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightvalue4b));
 		}
 
+		// generate triangle neighbors if requested
 		if (generateneighbors)
 		{
-			if (!mesh->data_neighbor3i) Mem_ReAlloc(b->memzone, &mesh->data_neighbor3i, mesh->max_triangles * 3 * sizeof(*mesh->data_neighbor3i));
+			if (!mesh->data_neighbor3i) Mem_ReAlloc(model->memzone, &mesh->data_neighbor3i, mesh->max_triangles * 3 * sizeof(*mesh->data_neighbor3i));
 			Model_Build_GenerateNeighbors(mesh);
 		}
 
+		// generate triangle planes if requested
 		if (generateplanes)
 		{
-			if (!mesh->data_plane4f) Mem_ReAlloc(b->memzone, &mesh->data_plane4f, mesh->max_triangles * 4 * sizeof(*mesh->data_plane4f));
+			if (!mesh->data_plane4f) Mem_ReAlloc(model->memzone, &mesh->data_plane4f, mesh->max_triangles * 4 * sizeof(*mesh->data_plane4f));
 			Model_Build_GeneratePlanes(mesh);
 		}
 
+		// generate vertex normals if requested
 		if (generatenormals)
 		{
-			if (!mesh->data_normal3f) Mem_ReAlloc(b->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
+			if (!mesh->data_normal3f) Mem_ReAlloc(model->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
 			Model_Build_GenerateNormals(mesh);
 		}
 
+		// generate vertex tangent vectors if requested
 		if (generatetangents)
 		{
-			if (!mesh->data_svector3f) Mem_ReAlloc(b->memzone, &mesh->data_svector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_svector3f));
-			if (!mesh->data_tvector3f) Mem_ReAlloc(b->memzone, &mesh->data_tvector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_tvector3f));
+			if (!mesh->data_svector3f) Mem_ReAlloc(model->memzone, &mesh->data_svector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_svector3f));
+			if (!mesh->data_tvector3f) Mem_ReAlloc(model->memzone, &mesh->data_tvector3f, mesh->max_vertices * 3 * sizeof(*mesh->data_tvector3f));
 			Model_Build_GenerateTangents(mesh);
 		}
 
+		// generate lightmap texcoords if requested
 		if (generatelightmaptexcoords)
 		{
-			if (!mesh->data_lightmaptexcoord2f) Mem_ReAlloc(b->memzone, &mesh->data_lightmaptexcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_lightmaptexcoord2f));
+			if (!mesh->data_lightmaptexcoord2f) Mem_ReAlloc(model->memzone, &mesh->data_lightmaptexcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_lightmaptexcoord2f));
 			Model_Build_GenerateLightmapTexCoords(mesh);
 		}
 
+		// free old triangle collision brushes
 		if (mesh->data_collisionbrushes)
 		{
 			Collision_Brush_FreeBrushesForTriangleMesh(mesh->data_collisionbrushes, mesh->max_collisionbrushes);
@@ -297,40 +314,63 @@
 			mesh->max_collisionbrushes = 0;
 			mesh->data_collisionbrushes = NULL;
 		}
+		// generate triangle collision brushes if requested
 		if (generatecollisionbrushes)
 		{
 			mesh->num_collisionbrushes = mesh->num_triangles;
 			mesh->max_collisionbrushes = mesh->num_collisionbrushes;
-			mesh->data_collisionbrushes = Collision_Brush_AllocBrushesForTriangleMesh(b->memzone, mesh->max_collisionbrushes);
+			mesh->data_collisionbrushes = Collision_Brush_AllocBrushesForTriangleMesh(model->memzone, mesh->max_collisionbrushes);
 			Collision_Brush_UpdateTriangleMeshBrushes(mesh->data_collisionbrushes, mesh->max_collisionbrushes, mesh->data_element3i, mesh->data_vertex3f);
 		}
 
-		for (j = 0, v = mesh->data_vertex3f;j < mesh->num_vertices;j++, v += 3)
+		// compute culling box for this mesh and update model box
+		// also compute culling spheres for transforms
+		VectorCopy(mesh->data_vertex3f, mesh->basecullmins);
+		VectorCopy(mesh->data_vertex3f, mesh->basecullmaxs);
+		for (vertexindex = 0, v = mesh->data_vertex3f;vertexindex < mesh->num_vertices;vertexindex++, v += 3)
 		{
-			if (initbox)
+			mesh->basecullmins[0] = Min(mesh->basecullmins[0], v[0]);
+			mesh->basecullmins[1] = Min(mesh->basecullmins[1], v[1]);
+			mesh->basecullmins[2] = Min(mesh->basecullmins[2], v[2]);
+			mesh->basecullmaxs[0] = Max(mesh->basecullmaxs[0], v[0]);
+			mesh->basecullmaxs[1] = Max(mesh->basecullmaxs[1], v[1]);
+			mesh->basecullmaxs[2] = Max(mesh->basecullmaxs[2], v[2]);
+			if (mesh->data_weightindex4b)
 			{
-				initbox = false;
-				VectorCopy(v, b->basecullmins);
-				VectorCopy(v, b->basecullmaxs);
+				for (weightindex = 0;weightindex < 4 && mesh->data_weightvalue4b[vertexindex*4+weightindex] > 0;weightindex++)
+				{
+					NUint32 index = mesh->data_weightindex4b[vertexindex*4+weightindex];
+					Nvec3 v1, v;
+					Nvec r;
+					VectorCopy(mesh->data_vertex3f + vertexindex * 3, v1);
+					Matrix4x4_Transform(&model->data_transforminfo[index].baseinversematrix, v1, v);
+					r = VectorLength(v);
+					model->data_transforminfo[index].radius = Max(model->data_transforminfo[index].radius, r);
+				}
 			}
-			else
-			{
-				b->basecullmins[0] = Min(b->basecullmins[0], v[0]);
-				b->basecullmins[1] = Min(b->basecullmins[1], v[1]);
-				b->basecullmins[2] = Min(b->basecullmins[2], v[2]);
-				b->basecullmaxs[0] = Max(b->basecullmaxs[0], v[0]);
-				b->basecullmaxs[1] = Max(b->basecullmaxs[1], v[1]);
-				b->basecullmaxs[2] = Max(b->basecullmaxs[2], v[2]);
-			}
 		}
+		if (!meshindex)
+		{
+			VectorCopy(mesh->basecullmins, model->basecullmins);
+			VectorCopy(mesh->basecullmaxs, model->basecullmaxs);
+		}
+		else
+		{
+			model->basecullmins[0] = Min(model->basecullmins[0], mesh->basecullmins[0]);
+			model->basecullmins[1] = Min(model->basecullmins[1], mesh->basecullmins[1]);
+			model->basecullmins[2] = Min(model->basecullmins[2], mesh->basecullmins[2]);
+			model->basecullmaxs[0] = Max(model->basecullmaxs[0], mesh->basecullmaxs[0]);
+			model->basecullmaxs[1] = Max(model->basecullmaxs[1], mesh->basecullmaxs[1]);
+			model->basecullmaxs[2] = Max(model->basecullmaxs[2], mesh->basecullmaxs[2]);
+		}
 	}
 }
 
-void Model_Build_Free(Model *b)
+void Model_Build_Free(Model *model)
 {
 	NUint32 i;
 	Model_Mesh *mesh;
-	for (i = 0, mesh = b->data_meshes;i < b->num_meshes;i++, mesh++)
+	for (i = 0, mesh = model->data_meshes;i < model->num_meshes;i++, mesh++)
 	{
 		if (mesh->materialname) String_Free(&mesh->materialname);
 		if (mesh->data_element3i) Mem_Free(&mesh->data_element3i);
@@ -346,41 +386,41 @@
 		if (mesh->data_weightvalue4b) Mem_Free(&mesh->data_weightvalue4b);
 		if (mesh->data_collisionbrushes) Collision_Brush_FreeBrushesForTriangleMesh(mesh->data_collisionbrushes, mesh->max_collisionbrushes);
 	}
-	if (b->data_meshes)
-		Mem_Free(&b->data_meshes);
-	memset(b, 0, sizeof(*b));
+	if (model->data_meshes)
+		Mem_Free(&model->data_meshes);
+	memset(model, 0, sizeof(*model));
 }
 
-NUint32 Model_Build_AddSurface(Model *b, Mem_Zone *zone, const char *materialname, NUint32 initialtriangles, NUint32 initialvertices)
+NUint32 Model_Build_AddSurface(Model *model, const char *materialname, NUint32 initialtriangles, NUint32 initialvertices)
 {
 	Model_Mesh *mesh;
-	if (b->num_meshes >= b->max_meshes)
+	if (model->num_meshes >= model->max_meshes)
 	{
-		b->max_meshes = Max(b->max_meshes * 2, 16);
-		Mem_ReAlloc(Global_Zone, &b->data_meshes, b->max_meshes * sizeof(*b->data_meshes));
+		model->max_meshes = Max(model->max_meshes * 2, 16);
+		Mem_ReAlloc(model->memzone, &model->data_meshes, model->max_meshes * sizeof(*model->data_meshes));
 	}
-	mesh = b->data_meshes + b->num_meshes;
-	String_Set(&mesh->materialname, zone, materialname);
+	mesh = model->data_meshes + model->num_meshes;
+	String_Set(&mesh->materialname, model->memzone, materialname);
 	mesh->max_triangles = initialtriangles;
 	mesh->max_vertices = initialvertices;
-	b->num_meshes++;
-	return b->num_meshes - 1;
+	model->num_meshes++;
+	return model->num_meshes - 1;
 }
 
-NUint32 Model_Build_AddVertex(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t)
+NUint32 Model_Build_AddVertex(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t)
 {
 	float *v;
-	Model_Mesh *mesh = b->data_meshes + meshindex;
+	Model_Mesh *mesh = model->data_meshes + meshindex;
 	if (mesh->num_vertices >= mesh->max_vertices)
 	{
 		mesh->max_vertices = Max(mesh->max_vertices * 2, 1024);
-		Mem_ReAlloc(b->memzone, &mesh->data_vertex3f, mesh->max_vertices * 3 * sizeof(*mesh->data_vertex3f));
-		Mem_ReAlloc(b->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
-		Mem_ReAlloc(b->memzone, &mesh->data_texcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_texcoord2f));
-		if (b->isskeletal)
+		Mem_ReAlloc(model->memzone, &mesh->data_vertex3f, mesh->max_vertices * 3 * sizeof(*mesh->data_vertex3f));
+		Mem_ReAlloc(model->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
+		Mem_ReAlloc(model->memzone, &mesh->data_texcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_texcoord2f));
+		if (model->isskeletal)
 		{
-			Mem_ReAlloc(b->memzone, &mesh->data_weightindex4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightindex4b));
-			Mem_ReAlloc(b->memzone, &mesh->data_weightvalue4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightvalue4b));
+			Mem_ReAlloc(model->memzone, &mesh->data_weightindex4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightindex4b));
+			Mem_ReAlloc(model->memzone, &mesh->data_weightvalue4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightvalue4b));
 		}
 	}
 	v = mesh->data_vertex3f + mesh->num_vertices * 3;
@@ -394,7 +434,7 @@
 	v = mesh->data_texcoord2f + mesh->num_vertices * 2;
 	v[0] = s;
 	v[1] = t;
-	if (b->isskeletal)
+	if (model->isskeletal)
 	{
 		NUint8 *wi = mesh->data_weightindex4b + mesh->num_vertices * 4;
 		NUint8 *wf = mesh->data_weightvalue4b + mesh->num_vertices * 4;
@@ -411,20 +451,20 @@
 	return mesh->num_vertices - 1;
 }
 
-NUint32 Model_Build_AddVertexSkeletal(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb)
+NUint32 Model_Build_AddVertexSkeletal(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb)
 {
 	float *v;
-	Model_Mesh *mesh = b->data_meshes + meshindex;
+	Model_Mesh *mesh = model->data_meshes + meshindex;
 	if (mesh->num_vertices >= mesh->max_vertices)
 	{
 		mesh->max_vertices = Max(mesh->max_vertices * 2, 1024);
-		Mem_ReAlloc(b->memzone, &mesh->data_vertex3f, mesh->max_vertices * 3 * sizeof(*mesh->data_vertex3f));
-		Mem_ReAlloc(b->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
-		Mem_ReAlloc(b->memzone, &mesh->data_texcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_texcoord2f));
-		if (b->isskeletal)
+		Mem_ReAlloc(model->memzone, &mesh->data_vertex3f, mesh->max_vertices * 3 * sizeof(*mesh->data_vertex3f));
+		Mem_ReAlloc(model->memzone, &mesh->data_normal3f, mesh->max_vertices * 3 * sizeof(*mesh->data_normal3f));
+		Mem_ReAlloc(model->memzone, &mesh->data_texcoord2f, mesh->max_vertices * 2 * sizeof(*mesh->data_texcoord2f));
+		if (model->isskeletal)
 		{
-			Mem_ReAlloc(b->memzone, &mesh->data_weightindex4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightindex4b));
-			Mem_ReAlloc(b->memzone, &mesh->data_weightvalue4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightvalue4b));
+			Mem_ReAlloc(model->memzone, &mesh->data_weightindex4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightindex4b));
+			Mem_ReAlloc(model->memzone, &mesh->data_weightvalue4b, mesh->max_vertices * 4 * sizeof(*mesh->data_weightvalue4b));
 		}
 	}
 	v = mesh->data_vertex3f + mesh->num_vertices * 3;
@@ -438,7 +478,7 @@
 	v = mesh->data_texcoord2f + mesh->num_vertices * 2;
 	v[0] = s;
 	v[1] = t;
-	if (b->isskeletal)
+	if (model->isskeletal)
 	{
 		NUint8 *wi = mesh->data_weightindex4b + mesh->num_vertices * 4;
 		NUint8 *wf = mesh->data_weightvalue4b + mesh->num_vertices * 4;
@@ -449,26 +489,26 @@
 	return mesh->num_vertices - 1;
 }
 
-NUint32 Model_Build_FindOrAddVertex(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t)
+NUint32 Model_Build_FindOrAddVertex(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t)
 {
 	NUint32 i;
 	float *v, *n, *tc;
 	NUint8 *wi, *wf;
-	Model_Mesh *mesh = b->data_meshes + meshindex;
+	Model_Mesh *mesh = model->data_meshes + meshindex;
 	for (i = 0, v = mesh->data_vertex3f, n = mesh->data_normal3f, tc = mesh->data_texcoord2f, wi = mesh->data_weightindex4b, wf = mesh->data_weightvalue4b;i < mesh->num_vertices;i++, v += 3, n += 3, tc += 2, wi += 4, wf += 4)
 		if (fabs(v[0] - x) < 0.0001 && fabs(v[1] - y) < 0.0001 && fabs(v[2] - z) < 0.0001
 		 && fabs(n[0] - nx) < 0.0001 && fabs(n[1] - ny) < 0.0001 && fabs(n[2] - nz) < 0.0001
 		 && fabs(tc[0] - s) < 0.0001 && fabs(tc[1] - t) < 0.0001)
 			return i;
-	return Model_Build_AddVertex(b, meshindex, x, y, z, nx, ny, nz, s, t);
+	return Model_Build_AddVertex(model, meshindex, x, y, z, nx, ny, nz, s, t);
 }
 
-NUint32 Model_Build_FindOrAddVertexSkeletal(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb)
+NUint32 Model_Build_FindOrAddVertexSkeletal(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb)
 {
 	NUint32 i;
 	float *v, *n, *tc;
 	NUint8 *wi, *wf;
-	Model_Mesh *mesh = b->data_meshes + meshindex;
+	Model_Mesh *mesh = model->data_meshes + meshindex;
 	for (i = 0, v = mesh->data_vertex3f, n = mesh->data_normal3f, tc = mesh->data_texcoord2f, wi = mesh->data_weightindex4b, wf = mesh->data_weightvalue4b;i < mesh->num_vertices;i++, v += 3, n += 3, tc += 2, wi += 4, wf += 4)
 		if (fabs(v[0] - x) < 0.0001 && fabs(v[1] - y) < 0.0001 && fabs(v[2] - z) < 0.0001
 		 && fabs(n[0] - nx) < 0.0001 && fabs(n[1] - ny) < 0.0001 && fabs(n[2] - nz) < 0.0001
@@ -476,17 +516,17 @@
 		 && wi[0] == wib[0] && wi[1] == wib[1] && wi[2] == wib[2] && wi[3] == wib[3]
 		 && wf[0] == wfb[0] && wf[1] == wfb[1] && wf[2] == wfb[2] && wf[3] == wfb[3])
 			return i;
-	return Model_Build_AddVertexSkeletal(b, meshindex, x, y, z, nx, ny, nz, s, t, wib, wfb);
+	return Model_Build_AddVertexSkeletal(model, meshindex, x, y, z, nx, ny, nz, s, t, wib, wfb);
 }
 
-NUint32 Model_Build_AddTriangle(Model *b, NUint32 meshindex, int e0, int e1, int e2)
+NUint32 Model_Build_AddTriangle(Model *model, NUint32 meshindex, int e0, int e1, int e2)
 {
-	Model_Mesh *mesh = b->data_meshes + meshindex;
+	Model_Mesh *mesh = model->data_meshes + meshindex;
 	NUint32 *e;
 	if (mesh->num_triangles >= mesh->max_triangles)
 	{
 		mesh->max_triangles = Max(mesh->max_triangles * 2, 1024);
-		Mem_ReAlloc(b->memzone, &mesh->data_element3i, mesh->max_triangles * 3 * sizeof(*mesh->data_element3i));
+		Mem_ReAlloc(model->memzone, &mesh->data_element3i, mesh->max_triangles * 3 * sizeof(*mesh->data_element3i));
 	}
 	e = mesh->data_element3i + mesh->num_triangles * 3;
 	e[0] = e0;
@@ -496,7 +536,7 @@
 	return mesh->num_triangles - 1;
 }
 
-void Model_Build_Write(Model *b, const char *filename)
+static void Model_Build_WriteMD5(Model *model, const char *filename)
 {
 	// FIXME: this is very unlikely to overflow but it should be made buffer size safe at some point
 	NUint32 i, num;
@@ -504,12 +544,12 @@
 	char *text;
 	Model_Mesh *mesh;
 	textmaxsize = 0x100000;
-	for (i = 0, mesh = b->data_meshes;i < b->num_meshes;i++, mesh++)
+	for (i = 0, mesh = model->data_meshes;i < model->num_meshes;i++, mesh++)
 		textmaxsize += mesh->num_vertices * 60 + mesh->num_triangles * 40;
 	textsize = 0;
 	text = Mem_Alloc(Global_Zone, textmaxsize);
-	textsize += snprintf(text + textsize, textmaxsize - textsize, "MD5Version 10\ncommandline \"\"\n\nnumJoints 1\nnumMeshes %u\n\njoints {\n\t\"ROOT\"\t-1 ( 0.000000 0.000000 0.000000 ) ( 0.000000 0.000000 0.000000 ) // \n}\n", b->num_meshes);
-	for (i = 0, mesh = b->data_meshes;i < b->num_meshes;i++, mesh++)
+	textsize += snprintf(text + textsize, textmaxsize - textsize, "MD5Version 10\ncommandline \"\"\n\nnumJoints 1\nnumMeshes %u\n\njoints {\n\t\"ROOT\"\t-1 ( 0.000000 0.000000 0.000000 ) ( 0.000000 0.000000 0.000000 ) // \n}\n", model->num_meshes);
+	for (i = 0, mesh = model->data_meshes;i < model->num_meshes;i++, mesh++)
 	{
 		if (textmaxsize < textsize + mesh->num_vertices * 100 + mesh->num_triangles * 100 + mesh->num_vertices * 100 + 1000)
 		{
@@ -531,8 +571,16 @@
 	Mem_Free(&text);
 }
 
-void Model_Build_ImportOBJ(Model *b, const char *inputname, const char *text, const char *textend)
+void Model_Build_Write(Model *model, const char *filename)
 {
+	if (String_EndsWith(filename, ".md5mesh"))
+		Model_Build_WriteMD5(model, filename);
+	else
+		Console_Printf("Model_Build_Write: %s extension not recognized\n", filename);
+}
+
+static Nbool Model_Build_Import_OBJ(Model *model, Nsize filesize, const char *filedata)
+{
 	NUint32 linenumber = 0;
 	NSint32 meshindex = -1;
 	NUint32 num_vertices = 0, max_vertices = 0;
@@ -541,6 +589,8 @@
 	float *data_vertex3f = NULL;
 	float *data_normal3f = NULL;
 	float *data_texcoord2f = NULL;
+	const char *text = filedata;
+	const char *textend = filedata + filesize;
 	while (text < textend)
 	{
 		Nbool comment = false;
@@ -586,7 +636,7 @@
 				num_vertices++;
 			}
 			else
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i parameters\n", inputname, linenumber, argv[0], 3);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i parameters\n", model->filename, linenumber, argv[0], 3);
 		}
 		else if (!String_Compare(argv[0], "vn"))
 		{
@@ -604,7 +654,7 @@
 				num_normals++;
 			}
 			else
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i parameters\n", inputname, linenumber, argv[0], 3);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i parameters\n", model->filename, linenumber, argv[0], 3);
 		}
 		else if (!String_Compare(argv[0], "vt"))
 		{
@@ -621,15 +671,15 @@
 				num_texcoords++;
 			}
 			else
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i parameters\n", inputname, linenumber, argv[0], 2);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i parameters\n", model->filename, linenumber, argv[0], 2);
 		}
 		else if (!String_Compare(argv[0], "usemtl"))
 		{
 			// usemtl mtrl/chrome
 			if (argc == 2)
-				meshindex = Model_Build_AddSurface(b, Global_Zone, argv[1], 0, 0);
+				meshindex = Model_Build_AddSurface(model, argv[1], 0, 0);
 			else
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i parameters\n", inputname, linenumber, argv[0], 1);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i parameters\n", model->filename, linenumber, argv[0], 1);
 		}
 		else if (!String_Compare(argv[0], "f"))
 		{
@@ -639,7 +689,7 @@
 			if (argc >= 4)
 			{
 				if (meshindex == -1)
-					meshindex = Model_Build_AddSurface(b, Global_Zone, "default", 0, 0);
+					meshindex = Model_Build_AddSurface(model, "default", 0, 0);
 				for (i = 0;i < argc - 1;i++)
 				{
 					NUint32 j;
@@ -670,7 +720,7 @@
 								if (k >= 0 && k < num_vertices)
 									VectorCopy(data_vertex3f + k * 3, v);
 								else
-									Console_Printf("Model_Build_ImportOBJ: %s:%i: invalid face vertex index (parameter #%i)\n", inputname, linenumber, i + 1);
+									Console_Printf("Model_Build_Import_OBJ: %s:%i: invalid face vertex index (parameter #%i)\n", model->filename, linenumber, i + 1);
 								break;
 							case 1:
 								if (k < 0)
@@ -680,7 +730,7 @@
 								if (k >= 0 && k < num_texcoords)
 									Vector2Copy(data_texcoord2f + k * 2, tc);
 								else
-									Console_Printf("Model_Build_ImportOBJ: %s:%i: invalid face texcoord index (parameter #%i)\n", inputname, linenumber, i + 1);
+									Console_Printf("Model_Build_Import_OBJ: %s:%i: invalid face texcoord index (parameter #%i)\n", model->filename, linenumber, i + 1);
 								break;
 							case 2:
 								if (k < 0)
@@ -690,7 +740,7 @@
 								if (k >= 0 && k < num_normals)
 									VectorCopy(data_normal3f + k * 3, vn);
 								else
-									Console_Printf("Model_Build_ImportOBJ: %s:%i: invalid face vertexnormal index (parameter #%i)\n", inputname, linenumber, i + 1);
+									Console_Printf("Model_Build_Import_OBJ: %s:%i: invalid face vertexnormal index (parameter #%i)\n", model->filename, linenumber, i + 1);
 								break;
 							}
 						}
@@ -707,35 +757,35 @@
 							s++;
 						}
 					}
-					indices[i] = Model_Build_FindOrAddVertex(b, meshindex, v[0], v[1], v[2], vn[0], vn[1], vn[2], tc[0], tc[1]);
+					indices[i] = Model_Build_FindOrAddVertex(model, meshindex, v[0], v[1], v[2], vn[0], vn[1], vn[2], tc[0], tc[1]);
 					// make a triangle fan if we have enough vertices
 					if (i >= 2)
-						Model_Build_AddTriangle(b, meshindex, indices[0], indices[i-1], indices[i]);
+						Model_Build_AddTriangle(model, meshindex, indices[0], indices[i], indices[i-1]);
 				}
 			}
 			else
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i or more parameters\n", inputname, linenumber, argv[0], 3);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i or more parameters\n", model->filename, linenumber, argv[0], 3);
 		}
 		else if (!String_Compare(argv[0], "o"))
 		{
 			// o cylinder1
 			if (argc != 2)
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i parameters\n", inputname, linenumber, argv[0], 1);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i parameters\n", model->filename, linenumber, argv[0], 1);
 		}
 		else if (!String_Compare(argv[0], "mtllib"))
 		{
 			// mtllib zrail2.mtl
 			if (argc != 2)
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i parameters\n", inputname, linenumber, argv[0], 1);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i parameters\n", model->filename, linenumber, argv[0], 1);
 		}
 		else if (!String_Compare(argv[0], "g"))
 		{
 			// g cylinder1_mtrl/invisible
 			if (argc != 2)
-				Console_Printf("Model_Build_ImportOBJ: %s:%i: command %s takes %i parameters\n", inputname, linenumber, argv[0], 1);
+				Console_Printf("Model_Build_Import_OBJ: %s:%i: command %s takes %i parameters\n", model->filename, linenumber, argv[0], 1);
 		}
 		else
-			Console_Printf("Model_Build_ImportOBJ: %s:%i: unknown obj command %s\n", inputname, linenumber, argv[0]);
+			Console_Printf("Model_Build_Import_OBJ: %s:%i: unknown obj command %s\n", model->filename, linenumber, argv[0]);
 	}
 	if (data_vertex3f)
 		Mem_Free(&data_vertex3f);
@@ -743,6 +793,7 @@
 		Mem_Free(&data_normal3f);
 	if (data_texcoord2f)
 		Mem_Free(&data_texcoord2f);
+	return false;
 }
 
 /* example clips of an MD5 model
@@ -782,50 +833,41 @@
 }
 Model_MD5_Weight;
 
-void Model_Load(ResourceEntry *r)
+static Nbool Model_Build_Import_MD5(Model *model, Nsize filesize, const char *filedata)
 {
 	Util_ParseC_Thread thread;
 	NUint32 meshnum, version, numjoints = 0;
 	Nbool error;
-	Nsize filesize;
-	Model *model;
-	char *filedata;
 	Nbool isskeletal = false;
 
-	model = Mem_Alloc(r->memzone, sizeof(Model));
-
-	filedata = File_LoadFile(r->name, &filesize);
-	if (!filedata)
-		return;
-
 	Util_ParseC_SetUpThread(&thread, filedata, filesize);
 
 #define Abort()		{ error = true; break; }
 #define LoadValue(var, errstr)		if (thread.type != UTIL_PARSEC_TOKENTYPE_VALUE) { \
-				Console_Printf("Model_Load: Expected value " errstr "\n"); Abort(); \
+				Console_Printf("Model_Build_Import_MD5: Expected value " errstr "\n"); Abort(); \
 			} else \
 				(var) = thread.value, Util_ParseC_Lex(&thread)
 
 #define LoadInt(var, errstr)		if (thread.type != UTIL_PARSEC_TOKENTYPE_VALUE) { \
-				Console_Printf("Model_Load: Expected integer " errstr "\n"); Abort(); \
+				Console_Printf("Model_Build_Import_MD5: Expected integer " errstr "\n"); Abort(); \
 			} else \
 				(var) = thread.value, Util_ParseC_Lex(&thread)
 
 #define LoadString(var, errstr)		if (thread.type != UTIL_PARSEC_TOKENTYPE_STRING) { \
-				Console_Printf("Model_Load: Expected string " errstr "\n"); Abort(); \
+				Console_Printf("Model_Build_Import_MD5: Expected string " errstr "\n"); Abort(); \
 			} else \
-				String_Set( &(var), r->memzone, thread.token), Util_ParseC_Lex(&thread)
+				String_Set( &(var), model->memzone, thread.token), Util_ParseC_Lex(&thread)
 
 #define ExpectKeyword(str, errstr)		if (!Util_ParseC_MatchName(&thread, (str))) { \
-				Console_Printf("Model_Load: Expected '" str "' " errstr "\n"); Abort(); }
+				Console_Printf("Model_Build_Import_MD5: Expected '" str "' " errstr "\n"); Abort(); }
 
 	version = 0;
 	meshnum = 0;
 	error = false;
 
-	VectorClear(model->basecullmins);
-	VectorClear(model->basecullmaxs);
-
+	// FIXME: this code should probably use more of the Model_Build_ functions
+	// where possible, to allow importing additional geometry into an existing
+	// model structure
 	while (!error && thread.type != UTIL_PARSEC_TOKENTYPE_EOF)
 	{
 		//MD5Version 10
@@ -834,7 +876,7 @@
 			LoadInt(version, "after 'MD5Version'");
 			if (version != 10)
 			{
-				Console_Printf("Model_Load: MD5Version %i not supported\n", version);
+				Console_Printf("Model_Build_Import_MD5: MD5Version %i not supported\n", version);
 				Abort();
 			}
 		}
@@ -849,7 +891,7 @@
 			LoadInt(numjoints, "after 'numJoints'");
 			if (numjoints < 1)
 			{
-				Console_Printf("Model_Load: numJoints %i < 1\n", numjoints);
+				Console_Printf("Model_Build_Import_MD5: numJoints %i < 1\n", numjoints);
 				Abort();
 			}
 			if (numjoints > 1)
@@ -857,7 +899,7 @@
 				isskeletal = true;
 				model->num_transforms = numjoints;
 				model->max_transforms = model->num_transforms;
-				model->data_transforminfo = Mem_Alloc(r->memzone, model->max_transforms * sizeof(*model->data_transforminfo));
+				model->data_transforminfo = Mem_Alloc(model->memzone, model->max_transforms * sizeof(*model->data_transforminfo));
 			}
 		}
 		//numMeshes 1
@@ -866,11 +908,11 @@
 			LoadInt(model->num_meshes, "after 'numMeshes'");
 			if (model->num_meshes < 1)
 			{
-				Console_Printf("Model_Load: numMeshes %i < 1\n", model->num_meshes);
+				Console_Printf("Model_Build_Import_MD5: numMeshes %i < 1\n", model->num_meshes);
 				Abort();
 			}
 			model->max_meshes = model->num_meshes;
-			model->data_meshes = Mem_Alloc(r->memzone, model->max_meshes * sizeof(Model_Mesh));
+			model->data_meshes = Mem_Alloc(model->memzone, model->max_meshes * sizeof(Model_Mesh));
 		}
 		//joints {
 		//	"root"	-1 ( 0.000000 0.000000 0.000000 ) ( -0.707107 -0.000000 -0.000000 )	//
@@ -902,7 +944,7 @@
 				if (parent >= (NSint32)jointnum)
 				{
 					String_Free(&name);
-					Console_Printf("Model_Load: joint.parent (%i) >= joint (%i)\n", parent, jointnum);
+					Console_Printf("Model_Build_Import_MD5: joint.parent (%i) >= joint (%i)\n", parent, jointnum);
 					Abort();
 				}
 				if (isskeletal)
@@ -916,7 +958,7 @@
 			String_Free(&name);
 			if (jointnum != numjoints)
 			{
-				Console_Printf("Model_Load: final jointnum (%i) != numJoints (%i)\n", jointnum, numjoints);
+				Console_Printf("Model_Build_Import_MD5: final jointnum (%i) != numJoints (%i)\n", jointnum, numjoints);
 				Abort();
 			}
 		}
@@ -933,7 +975,7 @@
 			ExpectKeyword("{", "after 'mesh'");
 			if (meshnum >= model->num_meshes)
 			{
-				Console_Printf("Model_Load: meshnum (%i) >= nummeshes (%i)\n", meshnum, model->num_meshes);
+				Console_Printf("Model_Build_Import_MD5: meshnum (%i) >= nummeshes (%i)\n", meshnum, model->num_meshes);
 				Abort();
 			}
 			mesh = model->data_meshes + meshnum;
@@ -952,22 +994,22 @@
 					LoadInt(mesh->num_vertices, "after 'numverts'");
 					if (mesh->num_vertices < 3)
 					{
-						Console_Printf("Model_Load: numverts (%i) < 3\n", mesh->num_vertices);
+						Console_Printf("Model_Build_Import_MD5: numverts (%i) < 3\n", mesh->num_vertices);
 						Abort();
 					}
 					mesh->max_vertices = mesh->num_vertices;
-					mesh->data_vertex3f = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(float[3]));
-					mesh->data_svector3f = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(float[3]));
-					mesh->data_tvector3f = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(float[3]));
-					mesh->data_normal3f = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(float[3]));
-					mesh->data_texcoord2f = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(float[2]));
-					mesh->data_lightmaptexcoord2f = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(float[2]));
+					mesh->data_vertex3f = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(float[3]));
+					mesh->data_svector3f = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(float[3]));
+					mesh->data_tvector3f = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(float[3]));
+					mesh->data_normal3f = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(float[3]));
+					mesh->data_texcoord2f = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(float[2]));
+					mesh->data_lightmaptexcoord2f = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(float[2]));
 					if (isskeletal)
 					{
-						mesh->data_weightindex4b = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(NUint8[4]));
-						mesh->data_weightvalue4b = Mem_Alloc(r->memzone, mesh->max_vertices * sizeof(NUint8[4]));
+						mesh->data_weightindex4b = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(NUint8[4]));
+						mesh->data_weightvalue4b = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(NUint8[4]));
 					}
-					md5weightrange = Mem_Alloc(Global_Zone, mesh->max_vertices * sizeof(NUint32[2]));
+					md5weightrange = Mem_Alloc(model->memzone, mesh->max_vertices * sizeof(NUint32[2]));
 				}
 				//	vert 0 ( 0.142532 0.983877 ) 0 1
 				//	vert 1 ( 0.155314 0.987100 ) 1 1
@@ -979,7 +1021,7 @@
 					LoadInt(vertexnum, "after 'vert'");
 					if (vertexnum >= mesh->num_vertices)
 					{
-						Console_Printf("Model_Load: vert (%i) >= numverts (%i)\n", vertexnum, mesh->num_vertices);
+						Console_Printf("Model_Build_Import_MD5: vert (%i) >= numverts (%i)\n", vertexnum, mesh->num_vertices);
 						Abort();
 					}
 					// (
@@ -1002,13 +1044,13 @@
 					LoadInt(mesh->num_triangles, "after 'numtris'");
 					if (mesh->num_triangles < 1)
 					{
-						Console_Printf("Model_Load: numtris (%i) < 1\n", mesh->num_triangles);
+						Console_Printf("Model_Build_Import_MD5: numtris (%i) < 1\n", mesh->num_triangles);
 						Abort();
 					}
 					mesh->max_triangles = mesh->num_triangles;
-					mesh->data_element3i = Mem_Alloc(r->memzone, mesh->max_triangles * sizeof(NUint32[3]));
-					mesh->data_neighbor3i = Mem_Alloc(r->memzone, mesh->max_triangles * sizeof(NSint32[3]));
-					mesh->data_plane4f = Mem_Alloc(r->memzone, mesh->max_triangles * sizeof(float[4]));
+					mesh->data_element3i = Mem_Alloc(model->memzone, mesh->max_triangles * sizeof(NUint32[3]));
+					mesh->data_neighbor3i = Mem_Alloc(model->memzone, mesh->max_triangles * sizeof(NSint32[3]));
+					mesh->data_plane4f = Mem_Alloc(model->memzone, mesh->max_triangles * sizeof(float[4]));
 				}
 				//	tri 0 29 30 31
 				//	tri 1 32 29 76
@@ -1019,7 +1061,7 @@
 					LoadInt(trianglenum, "after 'tri'");
 					if (trianglenum >= mesh->num_triangles)
 					{
-						Console_Printf("Model_Load: tri (%i) >= numtris (%i)\n", trianglenum, mesh->num_triangles);
+						Console_Printf("Model_Build_Import_MD5: tri (%i) >= numtris (%i)\n", trianglenum, mesh->num_triangles);
 						Abort();
 					}
 					// TODO: boundscheck elements
@@ -1035,7 +1077,7 @@
 					LoadInt(md5numweights, "after 'numweights'");
 					if (md5numweights < 1)
 					{
-						Console_Printf("Model_Load: numweights (%i) < 1\n", md5numweights);
+						Console_Printf("Model_Build_Import_MD5: numweights (%i) < 1\n", md5numweights);
 						Abort();
 					}
 					md5weights = Mem_Alloc(Global_Zone, md5numweights * sizeof(Model_MD5_Weight));
@@ -1051,7 +1093,7 @@
 					LoadInt(weightnum, "after 'weight'");
 					if (weightnum >= md5numweights)
 					{
-						Console_Printf("Model_Load: weight (%i) >= numweights (%i)\n", weightnum, md5numweights);
+						Console_Printf("Model_Build_Import_MD5: weight (%i) >= numweights (%i)\n", weightnum, md5numweights);
 						Abort();
 					}
 					md5weight = md5weights + weightnum;
@@ -1059,7 +1101,7 @@
 					LoadInt(md5weight->jointnum, "(weight jointnum)");
 					if (md5weight->jointnum >= numjoints)
 					{
-						Console_Printf("Model_Load: joint (%i) >= numjoints (%i)\n", md5weight->jointnum, numjoints);
+						Console_Printf("Model_Build_Import_MD5: joint (%i) >= numjoints (%i)\n", md5weight->jointnum, numjoints);
 						Abort();
 					}
 					// 1.000000
@@ -1147,63 +1189,6 @@
 				for (i = 0;i < mesh->num_vertices;i++)
 					VectorCopy(md5weights[md5weightrange[i*2+0]].vertex, mesh->data_vertex3f + i*3);
 			}
-			// compile the base frame of the mesh for static uses
-			Model_MeshNormals(mesh->num_triangles, mesh->data_element3i, mesh->num_vertices, mesh->data_vertex3f, mesh->data_texcoord2f, mesh->data_normal3f);
-			Model_MeshTangents(mesh->num_triangles, mesh->data_element3i, mesh->num_vertices, mesh->data_vertex3f, mesh->data_texcoord2f, mesh->data_normal3f, mesh->data_svector3f, mesh->data_tvector3f);
-			Model_MeshNeighbors(mesh->num_triangles, mesh->data_element3i, mesh->data_neighbor3i, mesh->num_vertices);
-			Model_BuildTrianglePlanes(mesh->data_vertex3f, mesh->num_triangles, mesh->data_element3i, mesh->data_plane4f);
-			// collision brushes are for the static frame
-			// TODO: load separate collision brush file for faster collisions?
-			mesh->num_collisionbrushes = mesh->num_triangles;
-			mesh->max_collisionbrushes = mesh->num_collisionbrushes;
-			mesh->data_collisionbrushes = Collision_Brush_AllocBrushesForTriangleMesh(r->memzone, mesh->max_triangles);
-			Collision_Brush_UpdateTriangleMeshBrushes(mesh->data_collisionbrushes, mesh->num_triangles, mesh->data_element3i, mesh->data_vertex3f);
-			// calculate culling shapes
-			for (i = 0;i < mesh->num_vertices;i++)
-			{
-				NUint32 k;
-				if (!i && !meshnum)
-				{
-					VectorCopy(mesh->data_vertex3f, model->basecullmins);
-					VectorCopy(mesh->data_vertex3f, model->basecullmaxs);
-				}
-				else
-				{
-					model->basecullmins[0] = Min(model->basecullmins[0], mesh->data_vertex3f[i * 3 + 0]);
-					model->basecullmins[1] = Min(model->basecullmins[1], mesh->data_vertex3f[i * 3 + 1]);
-					model->basecullmins[2] = Min(model->basecullmins[2], mesh->data_vertex3f[i * 3 + 2]);
-					model->basecullmaxs[0] = Max(model->basecullmaxs[0], mesh->data_vertex3f[i * 3 + 0]);
-					model->basecullmaxs[1] = Max(model->basecullmaxs[1], mesh->data_vertex3f[i * 3 + 1]);
-					model->basecullmaxs[2] = Max(model->basecullmaxs[2], mesh->data_vertex3f[i * 3 + 2]);
-				}
-				if (!i)
-				{
-					VectorCopy(mesh->data_vertex3f, mesh->basecullmins);
-					VectorCopy(mesh->data_vertex3f, mesh->basecullmaxs);
-				}
-				else
-				{
-					mesh->basecullmins[0] = Min(mesh->basecullmins[0], mesh->data_vertex3f[i * 3 + 0]);
-					mesh->basecullmins[1] = Min(mesh->basecullmins[1], mesh->data_vertex3f[i * 3 + 1]);
-					mesh->basecullmins[2] = Min(mesh->basecullmins[2], mesh->data_vertex3f[i * 3 + 2]);
-					mesh->basecullmaxs[0] = Max(mesh->basecullmaxs[0], mesh->data_vertex3f[i * 3 + 0]);
-					mesh->basecullmaxs[1] = Max(mesh->basecullmaxs[1], mesh->data_vertex3f[i * 3 + 1]);
-					mesh->basecullmaxs[2] = Max(mesh->basecullmaxs[2], mesh->data_vertex3f[i * 3 + 2]);
-				}
-				if (isskeletal)
-				{
-					for (k = 0;k < 4 && mesh->data_weightvalue4b[i*4+k] > 0;k++)
-					{
-						NUint32 index = mesh->data_weightindex4b[i*4+k];
-						Nvec3 v1, v;
-						Nvec r;
-						VectorCopy(mesh->data_vertex3f + i * 3, v1);
-						Matrix4x4_Transform(&model->data_transforminfo[index].baseinversematrix, v1, v);
-						r = VectorLength(v);
-						model->data_transforminfo[index].radius = Max(model->data_transforminfo[index].radius, r);
-					}
-				}
-			}
 			Mem_Free(&md5weights);
 			Mem_Free(&md5weightrange);
 			meshnum++;
@@ -1212,17 +1197,55 @@
 	}
 	Util_ParseC_CleanUpThread(&thread);
 	Mem_Free(&filedata);
-	// if there was a load error, don't point to the Model so nothing will try
-	// to use the incomplete data
-	if (!error)
-		r->data = model;
 #undef Abort
 #undef LoadInt
 #undef LoadValue
 #undef LoadString
 #undef ExpectKeyword
+	return error;
 }
 
+Nbool Model_Build_Import(Model *model, Nsize filesize, const char *filedata)
+{
+	Nbool error = false;
+	if (String_EndsWith(model->filename, ".obj"))
+		error = Model_Build_Import_OBJ(model, filesize, filedata);
+	else if (String_EndsWith(model->filename, ".md5mesh"))
+		error = Model_Build_Import_MD5(model, filesize, filedata);
+	else
+	{
+		Console_Printf("Model_Build_Import_MD5: %s extension not recognized\n", model->filename);
+		error = true;
+	}
+	return error;
+}
+
+void Model_Load(ResourceEntry *r)
+{
+	Nsize filesize;
+	Model *model;
+	Nbool error;
+	char *filedata;
+
+	filedata = File_LoadFile(r->name, &filesize);
+	if (!filedata)
+		return;
+
+	model = Mem_Alloc(r->memzone, sizeof(Model));
+	Model_Build_Begin(model, r->name, r->memzone, false);
+
+	error = Model_Build_Import(model, filesize, filedata);
+
+	// if there was a load error, don't point to the Model so nothing will try
+	// to use the incomplete data
+	if (!error)
+	{
+		Model_Build_Compile(model, true, true, true, true, true, true);
+		r->data = model;
+	}
+}
+
+
 void Model_Unload(UNUSED ResourceEntry *r)
 {
 	// freeing of the memzone will get rid of the model

Modified: trunk/model.h
===================================================================
--- trunk/model.h	2006-05-12 11:03:18 UTC (rev 715)
+++ trunk/model.h	2006-05-12 11:04:48 UTC (rev 716)
@@ -81,6 +81,7 @@
 typedef struct Model
 {
 	// set at the creation of the Model
+	char filename[64];
 	Mem_Zone *memzone;
 
 	Nbool isskeletal;
@@ -99,20 +100,20 @@
 }
 Model;
 
+Nbool Model_Build_Import(Model *model, Nsize filesize, const char *filedata);
 void Model_Load(ResourceEntry *r);
 void Model_Unload(ResourceEntry *r);
 
-void Model_Build_Begin(Model *b, Mem_Zone *memzone, Nbool isskeletal);
-void Model_Build_Compile(Model *b, Nbool generateneighbors, Nbool generateplanes, Nbool generatenormals, Nbool generatetangents, Nbool generatelightmaptexcoords, Nbool generatecollisionbrushes);
-void Model_Build_Free(Model *b);
-NUint32 Model_Build_AddSurface(Model *b, Mem_Zone *zone, const char *materialname, NUint32 initialtriangles, NUint32 initialvertices);
-NUint32 Model_Build_AddVertex(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t);
-NUint32 Model_Build_AddVertexSkeletal(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb);
-NUint32 Model_Build_FindOrAddVertex(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t);
-NUint32 Model_Build_FindOrAddVertexSkeletal(Model *b, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb);
-NUint32 Model_Build_AddTriangle(Model *b, NUint32 meshindex, int e0, int e1, int e2);
-void Model_Build_Write(Model *b, const char *filename);
-void Model_Build_ImportOBJ(Model *b, const char *inputname, const char *text, const char *textend);
+void Model_Build_Begin(Model *model, const char *filename, Mem_Zone *memzone, Nbool isskeletal);
+void Model_Build_Compile(Model *model, Nbool generateneighbors, Nbool generateplanes, Nbool generatenormals, Nbool generatetangents, Nbool generatelightmaptexcoords, Nbool generatecollisionbrushes);
+void Model_Build_Free(Model *model);
+NUint32 Model_Build_AddSurface(Model *model, const char *materialname, NUint32 initialtriangles, NUint32 initialvertices);
+NUint32 Model_Build_AddVertex(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t);
+NUint32 Model_Build_AddVertexSkeletal(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb);
+NUint32 Model_Build_FindOrAddVertex(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t);
+NUint32 Model_Build_FindOrAddVertexSkeletal(Model *model, NUint32 meshindex, float x, float y, float z, float nx, float ny, float nz, float s, float t, NUint8 *wib, NUint8 *wfb);
+NUint32 Model_Build_AddTriangle(Model *model, NUint32 meshindex, int e0, int e1, int e2);
+void Model_Build_Write(Model *model, const char *filename);
 
 // this function decodes skeletal vertex information to your supplied arrays
 // NOTE: outplane4f requires outvertex3f

Modified: trunk/nstring.c
===================================================================
--- trunk/nstring.c	2006-05-12 11:03:18 UTC (rev 715)
+++ trunk/nstring.c	2006-05-12 11:04:48 UTC (rev 716)
@@ -269,6 +269,18 @@
 	return strncasecmp(s ? s : "", t ? t : "", n);
 }
 
+Nbool String_EndsWith(const char *s, const char *t)
+{
+	Nsize i;
+	Nsize slen = String_Length(s);
+	Nsize tlen = String_Length(t);
+	if (tlen && slen >= tlen)
+		for (i = 0;i <= slen - tlen;i++)
+			if (!String_Compare(s + i, t))
+				return true;
+	return false;
+}
+
 void String_ToLower(char *s)
 {
 	if( !s )

Modified: trunk/nstring.h
===================================================================
--- trunk/nstring.h	2006-05-12 11:03:18 UTC (rev 715)
+++ trunk/nstring.h	2006-05-12 11:04:48 UTC (rev 716)
@@ -29,6 +29,7 @@
 NSint	String_ICompare(const char *s, const char *t);
 NSint String_NCompare(const char *s, const char *t, Nsize n);
 NSint String_NICompare(const char *s, const char *t, Nsize n);
+Nbool String_EndsWith(const char *s, const char *t);
 
 Nsize	String_Length(const char *s);
 

Modified: trunk/util.c
===================================================================
--- trunk/util.c	2006-05-12 11:03:18 UTC (rev 715)
+++ trunk/util.c	2006-05-12 11:04:48 UTC (rev 716)
@@ -248,7 +248,7 @@
 	const char *basename, *imagename, *materialname;
 	char *tilename;
 	Nvec terrainsize[3], texturesize[2], terrainscale[3], terrainorigin[3], texturescale[2];
-	Model b;
+	Model model;
 	basename = Shell_Callback_GetArg(1);
 	imagename = Shell_Callback_GetArg(2);
 	tilesize = (int) Shell_Callback_GetArgValue(3);
@@ -280,12 +280,14 @@
 	terrainorigin[2] = terrainsize[2] * -0.5;
 	tileswidth = width / tilesize;
 	tilesheight = height / tilesize;
-	Model_Build_Begin(&b, Global_Zone, false);
+	tilename = String_APrintf(Global_Zone, "%s.md5mesh", basename);
+	Model_Build_Begin(&model, tilename, Global_Zone, false);
+	String_Free(&tilename);
 	for (by = 0;by < height;by += tilesize)
 	{
 		for (bx = 0;bx < width;bx += tilesize)
 		{
-			NUint32 meshindex = Model_Build_AddSurface(&b, Global_Zone, materialname, 0, 0);
+			NUint32 meshindex = Model_Build_AddSurface(&model, materialname, 0, 0);
 			tilewidth = Bound(0, width - bx, tilesize+2);
 			tileheight = Bound(0, height - by, tilesize+2);
 			for (ty = 0, num = 0;ty < tileheight;ty++)
@@ -305,7 +307,7 @@
 					p = pixels + (pyi * width + pxi) * 4;
 					pz = ((p[0]+p[1]+p[2])*(1.0-pxf)+(p[4]+p[5]+p[6])*(pxf))*(1.0-pyf)+((p[w+0]+p[w+1]+p[w+2])*(1.0-pxf)+(p[w+4]+p[w+5]+p[w+6])*(pxf))*(pyf);
 					// FIXME: this SHOULD calculate a valid mesh normal!!
-					Model_Build_AddVertex(&b, meshindex, px * terrainscale[0] + terrainorigin[0], py * terrainscale[1] + terrainorigin[1], pz * terrainscale[2] + terrainorigin[2], 0, 0, 1, (px * terrainscale[0] + terrainorigin[0]) * texturescale[0], (py * terrainscale[1] + terrainorigin[1]) * texturescale[1]);
+					Model_Build_AddVertex(&model, meshindex, px * terrainscale[0] + terrainorigin[0], py * terrainscale[1] + terrainorigin[1], pz * terrainscale[2] + terrainorigin[2], 0, 0, 1, (px * terrainscale[0] + terrainorigin[0]) * texturescale[0], (py * terrainscale[1] + terrainorigin[1]) * texturescale[1]);
 				}
 			}
 			for (ty = 0, num = 0;ty < tileheight-1;ty++)
@@ -313,17 +315,15 @@
 				for (tx = 0;tx < tilewidth-1;tx++, num += 6)
 				{
 					NUint32 i = ty*tilewidth+tx;
-					Model_Build_AddTriangle(&b, meshindex, i, i+1, i+1+tilewidth);
-					Model_Build_AddTriangle(&b, meshindex, i, i+1+tilewidth, i+tilewidth);
+					Model_Build_AddTriangle(&model, meshindex, i, i+1, i+1+tilewidth);
+					Model_Build_AddTriangle(&model, meshindex, i, i+1+tilewidth, i+tilewidth);
 				}
 			}
 		}
 	}
 	Mem_Free(&pixels);
-	tilename = String_APrintf(Global_Zone, "%s.md5mesh", basename);
-	Model_Build_Write(&b, tilename);
-	String_Free(&tilename);
-	Model_Build_Free(&b);
+	Model_Build_Write(&model, model.filename);
+	Model_Build_Free(&model);
 }
 
 static Shell_SymbolDecl Util_Shell_MakeTerrain_Decl = {
@@ -336,33 +336,33 @@
 	(Shell_Symbol_Callback) Util_Shell_MakeTerrain
 };
 
-static void Util_Shell_ConvertOBJ(void)
+static void Util_Shell_ConvertModel(void)
 {
-	Nsize objtextsize;
-	Model b;
+	Nsize filesize;
+	Model model;
 	const char *inputname = Shell_Callback_GetArg(1);
 	const char *outputname = Shell_Callback_GetArg(2);
-	char *objtext = File_LoadFile(inputname, &objtextsize);
-	if (!objtext)
+	char *filedata = File_LoadFile(inputname, &filesize);
+	if (!filedata)
 	{
-		Shell_Callback_ThrowError("Could not open OBJ file\n");
+		Shell_Callback_ThrowError("Could not open model file\n");
 		// SHELLTODO
 		return;
 	}
-	Model_Build_Begin(&b, Global_Zone, false);
-	Model_Build_ImportOBJ(&b, inputname, objtext, objtext + objtextsize);
-	Model_Build_Write(&b, outputname);
-	Model_Build_Free(&b);
-	Mem_Free(&objtext);
+	Model_Build_Begin(&model, inputname, Global_Zone, false);
+	if (!Model_Build_Import(&model, filesize, filedata))
+		Model_Build_Write(&model, outputname);
+	Model_Build_Free(&model);
+	Mem_Free(&filedata);
 }
 
-static Shell_SymbolDecl Util_Shell_ConvertOBJ_Decl = {
-	"util_convertobj",
-	"Turn a .obj model into a .md5mesh model file.",
-	"Usage: util_convertobj <inputname> <outputname>\n"
-	"Example: util_convertobj maps/mymap/mymodel.obj maps/mymap/mymodel.md5mesh",
+static Shell_SymbolDecl Util_Shell_ConvertModel_Decl = {
+	"util_convertmodel",
+	"Convert a model from any supported format to any other.",
+	"Usage: util_convertmodel <inputname> <outputname>\n"
+	"Example: util_convertmodel maps/mymap/mymodel.obj maps/mymap/mymodel.md5mesh",
 	"ss",
-	(Shell_Symbol_Callback) Util_Shell_ConvertOBJ
+	(Shell_Symbol_Callback) Util_Shell_ConvertModel
 };
 
 
@@ -635,32 +635,32 @@
 	double boxsize;
 	const char *basefilename, *baseimagename;
 	char *filename;
-	Model b;
+	Model model;
 	basefilename = Shell_Callback_GetArg(1);
 	baseimagename = Shell_Callback_GetArg(2);
 	boxsize = atof(Shell_Callback_GetArg(3));
-	Model_Build_Begin(&b, 0, 0);
+	filename = String_APrintf(Global_Zone, "%s.md5mesh", basefilename);
+	Model_Build_Begin(&model, filename, Global_Zone, false);
+	String_Free(&filename);
 	for (i = 0;i < 6;i++)
 	{
 		NUint32 j;
 		NUint32 indices[4];
 		char *materialname = String_APrintf(Global_Zone, "%s_%s", baseimagename, skyboxsuffix[i]);
-		NUint32 meshindex = Model_Build_AddSurface(&b, Global_Zone, materialname, 2, 4);
+		NUint32 meshindex = Model_Build_AddSurface(&model, materialname, 2, 4);
 		String_Free(&materialname);
 		for (j = 0;j < 4;j++)
-			indices[j] = Model_Build_FindOrAddVertex(&b, meshindex, skyboxvertex[i][j][0], skyboxvertex[i][j][1], skyboxvertex[i][j][2], skyboxvertex[i][j][3], skyboxvertex[i][j][4], skyboxvertex[i][j][5], skyboxvertex[i][j][6], skyboxvertex[i][j][7]);
-		Model_Build_AddTriangle(&b, meshindex, indices[0], indices[2], indices[1]);
-		Model_Build_AddTriangle(&b, meshindex, indices[0], indices[3], indices[2]);
+			indices[j] = Model_Build_FindOrAddVertex(&model, meshindex, skyboxvertex[i][j][0], skyboxvertex[i][j][1], skyboxvertex[i][j][2], skyboxvertex[i][j][3], skyboxvertex[i][j][4], skyboxvertex[i][j][5], skyboxvertex[i][j][6], skyboxvertex[i][j][7]);
+		Model_Build_AddTriangle(&model, meshindex, indices[0], indices[2], indices[1]);
+		Model_Build_AddTriangle(&model, meshindex, indices[0], indices[3], indices[2]);
 	}
-	filename = String_APrintf(Global_Zone, "%s.md5mesh", basefilename);
-	Model_Build_Write(&b, filename);
-	String_Free(&filename);
-	Model_Build_Free(&b);
+	Model_Build_Write(&model, model.filename);
+	Model_Build_Free(&model);
 }
 
 static Shell_SymbolDecl Util_Shell_MakeSkyBox_Decl = {
 	"util_makeskybox",
-	"Make an skybox md5mesh file.",
+	"Make a skybox md5mesh file.",
 	"Usage: util_makeskybox <maps/mymap/skybox> <imagebasename> <size>\n"
 	"Example: util_makeskybox maps/mymap/sunnyday maps/mymap/sunnyday 30000\n"
 	"would produce a file named maps/mymap/sunnyday.md5mesh containing 6 meshes for maps/mymap/sunnyday_px.tga and _nx, _py, _ny, _pz, _nz, forming a box +-100000m in each direction",
@@ -672,7 +672,7 @@
 {
 	Shell_RegisterFunction(&Util_Shell_MakeTerrain_Decl);
 	Shell_RegisterFunction(&Util_Shell_MakeSkyBox_Decl);
-	Shell_RegisterFunction(&Util_Shell_ConvertOBJ_Decl);
+	Shell_RegisterFunction(&Util_Shell_ConvertModel_Decl);
 }
 
 void Util_Quit(void)




More information about the neither-commits mailing list