]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - model_shared.c
the merging of alias and bsp model rendering begins... purely cosmetic changes in...
[xonotic/darkplaces.git] / model_shared.c
index 3420ae5901766b22f1c839d70ca1e796d546f7b2..da90f78505d49fdb39e8bb207962c5e3e7680356 100644 (file)
@@ -30,144 +30,10 @@ cvar_t r_mipskins = {CVAR_SAVE, "r_mipskins", "0"};
 
 model_t *loadmodel;
 
-// LordHavoc: increased from 512 to 2048
-#define        MAX_MOD_KNOWN   2048
+// LordHavoc: was 512
+#define MAX_MOD_KNOWN (MAX_MODELS + 256)
 static model_t mod_known[MAX_MOD_KNOWN];
 
-rtexturepool_t *mod_shared_texturepool;
-rtexture_t *r_notexture;
-rtexture_t *mod_shared_detailtextures[NUM_DETAILTEXTURES];
-rtexture_t *mod_shared_distorttexture[64];
-
-void Mod_BuildDetailTextures (void)
-{
-       int i, x, y, light;
-       float vc[3], vx[3], vy[3], vn[3], lightdir[3];
-#define DETAILRESOLUTION 256
-       qbyte data[DETAILRESOLUTION][DETAILRESOLUTION][4], noise[DETAILRESOLUTION][DETAILRESOLUTION];
-       lightdir[0] = 0.5;
-       lightdir[1] = 1;
-       lightdir[2] = -0.25;
-       VectorNormalize(lightdir);
-       for (i = 0;i < NUM_DETAILTEXTURES;i++)
-       {
-               fractalnoise(&noise[0][0], DETAILRESOLUTION, DETAILRESOLUTION >> 4);
-               for (y = 0;y < DETAILRESOLUTION;y++)
-               {
-                       for (x = 0;x < DETAILRESOLUTION;x++)
-                       {
-                               vc[0] = x;
-                               vc[1] = y;
-                               vc[2] = noise[y][x] * (1.0f / 32.0f);
-                               vx[0] = x + 1;
-                               vx[1] = y;
-                               vx[2] = noise[y][(x + 1) % DETAILRESOLUTION] * (1.0f / 32.0f);
-                               vy[0] = x;
-                               vy[1] = y + 1;
-                               vy[2] = noise[(y + 1) % DETAILRESOLUTION][x] * (1.0f / 32.0f);
-                               VectorSubtract(vx, vc, vx);
-                               VectorSubtract(vy, vc, vy);
-                               CrossProduct(vx, vy, vn);
-                               VectorNormalize(vn);
-                               light = 128 - DotProduct(vn, lightdir) * 128;
-                               light = bound(0, light, 255);
-                               data[y][x][0] = data[y][x][1] = data[y][x][2] = light;
-                               data[y][x][3] = 255;
-                       }
-               }
-               mod_shared_detailtextures[i] = R_LoadTexture2D(mod_shared_texturepool, va("detailtexture%i", i), DETAILRESOLUTION, DETAILRESOLUTION, &data[0][0][0], TEXTYPE_RGBA, TEXF_MIPMAP | TEXF_PRECACHE, NULL);
-       }
-}
-
-qbyte Mod_MorphDistortTexture (double y0, double y1, double y2, double y3, double morph)
-{
-       int     value = (int)(((y1 + y3 - (y0 + y2)) * morph * morph * morph) +
-                               ((2 * (y0 - y1) + y2 - y3) * morph * morph) +
-                               ((y2 - y0) * morph) +
-                               (y1));
-
-       if (value > 255)
-               value = 255;
-       if (value < 0)
-               value = 0;
-
-       return (qbyte)value;
-}
-
-void Mod_BuildDistortTexture (void)
-{
-       int x, y, i, j;
-#define DISTORTRESOLUTION 32
-       qbyte data[5][DISTORTRESOLUTION][DISTORTRESOLUTION][2];
-
-       for (i=0; i<4; i++)
-       {
-               for (y=0; y<DISTORTRESOLUTION; y++)
-               {
-                       for (x=0; x<DISTORTRESOLUTION; x++)
-                       {
-                               data[i][y][x][0] = rand () & 255;
-                               data[i][y][x][1] = rand () & 255;
-                       }
-               }
-       }
-
-
-       for (i=0; i<4; i++)
-       {
-               for (j=0; j<16; j++)
-               {
-                       mod_shared_distorttexture[i*16+j] = NULL;
-                       if (gl_textureshader)
-                       {
-                               for (y=0; y<DISTORTRESOLUTION; y++)
-                               {
-                                       for (x=0; x<DISTORTRESOLUTION; x++)
-                                       {
-                                               data[4][y][x][0] = Mod_MorphDistortTexture (data[(i-1)&3][y][x][0], data[i][y][x][0], data[(i+1)&3][y][x][0], data[(i+2)&3][y][x][0], 0.0625*j);
-                                               data[4][y][x][1] = Mod_MorphDistortTexture (data[(i-1)&3][y][x][1], data[i][y][x][1], data[(i+1)&3][y][x][1], data[(i+2)&3][y][x][1], 0.0625*j);
-                                       }
-                               }
-                               mod_shared_distorttexture[i*16+j] = R_LoadTexture2D(mod_shared_texturepool, va("distorttexture%i", i*16+j), DISTORTRESOLUTION, DISTORTRESOLUTION, &data[4][0][0][0], TEXTYPE_DSDT, TEXF_PRECACHE, NULL);
-                       }
-               }
-       }
-
-       return;
-}
-
-texture_t r_surf_notexture;
-
-void Mod_SetupNoTexture(void)
-{
-       int x, y;
-       qbyte pix[16][16][4];
-
-       // this makes a light grey/dark grey checkerboard texture
-       for (y = 0;y < 16;y++)
-       {
-               for (x = 0;x < 16;x++)
-               {
-                       if ((y < 8) ^ (x < 8))
-                       {
-                               pix[y][x][0] = 128;
-                               pix[y][x][1] = 128;
-                               pix[y][x][2] = 128;
-                               pix[y][x][3] = 255;
-                       }
-                       else
-                       {
-                               pix[y][x][0] = 64;
-                               pix[y][x][1] = 64;
-                               pix[y][x][2] = 64;
-                               pix[y][x][3] = 255;
-                       }
-               }
-       }
-
-       r_notexture = R_LoadTexture2D(mod_shared_texturepool, "notexture", 16, 16, &pix[0][0][0], TEXTYPE_RGBA, TEXF_MIPMAP, NULL);
-}
-
 static void mod_start(void)
 {
        int i;
@@ -175,11 +41,6 @@ static void mod_start(void)
                if (mod_known[i].name[0])
                        Mod_UnloadModel(&mod_known[i]);
        Mod_LoadModels();
-
-       mod_shared_texturepool = R_AllocTexturePool();
-       Mod_SetupNoTexture();
-       Mod_BuildDetailTextures();
-       Mod_BuildDistortTexture();
 }
 
 static void mod_shutdown(void)
@@ -188,36 +49,28 @@ static void mod_shutdown(void)
        for (i = 0;i < MAX_MOD_KNOWN;i++)
                if (mod_known[i].name[0])
                        Mod_UnloadModel(&mod_known[i]);
-
-       R_FreeTexturePool(&mod_shared_texturepool);
 }
 
 static void mod_newmap(void)
 {
-       msurface_t *surf;
-       int i, surfnum, ssize, tsize;
+       msurface_t *surface;
+       int i, surfacenum, ssize, tsize;
 
-       if (!cl_stainmapsclearonload.integer)
+       if (!cl_stainmaps_clearonload.integer)
                return;
 
-       for (i=0; i<MAX_MOD_KNOWN; i++)
+       for (i = 0;i < MAX_MOD_KNOWN;i++)
        {
-               if (mod_known[i].name[0] && mod_known[i].type == mod_brushq1)
+               if (mod_known[i].name[0])
                {
-                       for (surfnum=0, surf=mod_known[i].brushq1.surfaces; surfnum<mod_known[i].brushq1.numsurfaces;surfnum++, surf++)
+                       for (surfacenum = 0, surface = mod_known[i].data_surfaces;surfacenum < mod_known[i].num_surfaces;surfacenum++, surface++)
                        {
-                               if (surf->texinfo->texture->flags & SURF_LIGHTMAP)
+                               if (surface->lightmapinfo && surface->lightmapinfo->stainsamples)
                                {
-                                       ssize = (surf->extents[0] >> 4) + 1;
-                                       tsize = (surf->extents[1] >> 4) + 1;
-                                       
-                                       if (ssize > 256 || tsize > 256)
-                                               Host_Error("Bad surface extents");
-
-                                       if (surf->stainsamples)
-                                               memset(surf->stainsamples, 255, ssize * tsize * 3);
-
-                                       surf->cached_dlight = true;
+                                       ssize = (surface->lightmapinfo->extents[0] >> 4) + 1;
+                                       tsize = (surface->lightmapinfo->extents[1] >> 4) + 1;
+                                       memset(surface->lightmapinfo->stainsamples, 255, ssize * tsize * 3);
+                                       surface->cached_dlight = true;
                                }
                        }
                }
@@ -260,12 +113,15 @@ void Mod_UnloadModel (model_t *mod)
 {
        char name[MAX_QPATH];
        qboolean isworldmodel;
+       qboolean used;
        strcpy(name, mod->name);
        isworldmodel = mod->isworldmodel;
+       used = mod->used;
        Mod_FreeModel(mod);
        strcpy(mod->name, name);
        mod->isworldmodel = isworldmodel;
-       mod->needload = true;
+       mod->used = used;
+       mod->loaded = false;
 }
 
 /*
@@ -289,31 +145,32 @@ static model_t *Mod_LoadModel(model_t *mod, qboolean crash, qboolean checkdisk,
        crc = 0;
        buf = NULL;
        if (mod->isworldmodel != isworldmodel)
-               mod->needload = true;
-       if (mod->needload || checkdisk)
+               mod->loaded = false;
+       if (!mod->loaded || checkdisk)
        {
-               if (checkdisk && !mod->needload)
+               if (checkdisk && mod->loaded)
                        Con_DPrintf("checking model %s\n", mod->name);
                buf = FS_LoadFile (mod->name, tempmempool, false);
                if (buf)
                {
                        crc = CRC_Block(buf, fs_filesize);
                        if (mod->crc != crc)
-                               mod->needload = true;
+                               mod->loaded = false;
                }
        }
-       if (!mod->needload)
+       if (mod->loaded)
                return mod; // already loaded
 
        Con_DPrintf("loading model %s\n", mod->name);
        // LordHavoc: unload the existing model in this slot (if there is one)
        Mod_UnloadModel(mod);
+
        // load the model
        mod->isworldmodel = isworldmodel;
        mod->used = true;
        mod->crc = crc;
-       // errors can prevent the corresponding mod->needload = false;
-       mod->needload = true;
+       // errors can prevent the corresponding mod->loaded = true;
+       mod->loaded = false;
 
        // default model radius and bounding box (mainly for missing models)
        mod->radius = 16;
@@ -339,6 +196,7 @@ static model_t *Mod_LoadModel(model_t *mod, qboolean crash, qboolean checkdisk,
                else if (!memcmp(buf, "IDP2", 4)) Mod_IDP2_Load(mod, buf);
                else if (!memcmp(buf, "IDP3", 4)) Mod_IDP3_Load(mod, buf);
                else if (!memcmp(buf, "IDSP", 4)) Mod_IDSP_Load(mod, buf);
+               else if (!memcmp(buf, "IDS2", 4)) Mod_IDS2_Load(mod, buf);
                else if (!memcmp(buf, "IBSP", 4)) Mod_IBSP_Load(mod, buf);
                else if (!memcmp(buf, "ZYMOTICMODEL", 12)) Mod_ZYMOTICMODEL_Load(mod, buf);
                else if (strlen(mod->name) >= 4 && !strcmp(mod->name - 4, ".map")) Mod_MAP_Load(mod, buf);
@@ -349,11 +207,11 @@ static model_t *Mod_LoadModel(model_t *mod, qboolean crash, qboolean checkdisk,
        else if (crash)
        {
                // LordHavoc: Sys_Error was *ANNOYING*
-               Con_Printf ("Mod_LoadModel: %s not found", mod->name);
+               Con_Printf ("Mod_LoadModel: %s not found\n", mod->name);
        }
 
        // no errors occurred
-       mod->needload = false;
+       mod->loaded = true;
        return mod;
 }
 
@@ -361,7 +219,7 @@ void Mod_CheckLoaded(model_t *mod)
 {
        if (mod)
        {
-               if (mod->needload)
+               if (!mod->loaded)
                        Mod_LoadModel(mod, true, true, mod->isworldmodel);
                else
                {
@@ -403,6 +261,15 @@ void Mod_PurgeUnused(void)
                                Mod_FreeModel(mod);
 }
 
+// only used during loading!
+void Mod_RemoveStaleWorldModels(model_t *skip)
+{
+       int i;
+       for (i = 0;i < MAX_MOD_KNOWN;i++)
+               if (mod_known[i].isworldmodel && skip != &mod_known[i])
+                       Mod_UnloadModel(mod_known + i);
+}
+
 void Mod_LoadModels(void)
 {
        int i;
@@ -448,7 +315,7 @@ model_t *Mod_FindName(const char *name)
        {
                mod = freemod;
                strcpy (mod->name, name);
-               mod->needload = true;
+               mod->loaded = false;
                mod->used = true;
                return mod;
        }
@@ -457,20 +324,6 @@ model_t *Mod_FindName(const char *name)
        return NULL;
 }
 
-/*
-==================
-Mod_TouchModel
-
-==================
-*/
-void Mod_TouchModel(const char *name)
-{
-       model_t *mod;
-
-       mod = Mod_FindName(name);
-       mod->used = true;
-}
-
 /*
 ==================
 Mod_ForName
@@ -533,7 +386,7 @@ int Mod_BuildVertexRemapTableFromElements(int numelements, const int *elements,
 
 #if 1
 // fast way, using an edge hash
-#define TRIANGLEEDGEHASH 1024
+#define TRIANGLEEDGEHASH 16384
 void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles)
 {
        int i, j, p, e1, e2, *n, hashindex, count, match;
@@ -648,18 +501,53 @@ void Mod_ValidateElements(const int *elements, int numtriangles, int numverts, c
                        Con_Printf("Mod_ValidateElements: out of bounds element detected at %s:%d\n", filename, fileline);
 }
 
+// warning: this is an expensive function!
+void Mod_BuildNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const int *elements, float *normal3f)
+{
+       int i, tnum;
+       float normal[3], *v;
+       const int *e;
+       // clear the vectors
+       memset(normal3f + 3 * firstvertex, 0, numvertices * sizeof(float[3]));
+       // process each vertex of each triangle and accumulate the results
+       for (tnum = 0, e = elements;tnum < numtriangles;tnum++, e += 3)
+       {
+               TriangleNormal(vertex3f + e[0] * 3, vertex3f + e[1] * 3, vertex3f + e[2] * 3, normal);
+               VectorNormalize(normal);
+               v = normal3f + e[0] * 3;
+               v[0] += normal[0];
+               v[1] += normal[1];
+               v[2] += normal[2];
+               v = normal3f + e[1] * 3;
+               v[0] += normal[0];
+               v[1] += normal[1];
+               v[2] += normal[2];
+               v = normal3f + e[2] * 3;
+               v[0] += normal[0];
+               v[1] += normal[1];
+               v[2] += normal[2];
+       }
+       // now we could divide the vectors by the number of averaged values on
+       // each vertex...  but instead normalize them
+       for (i = 0, v = normal3f + 3 * firstvertex;i < numvertices;i++, v += 3)
+               VectorNormalize(v);
+}
+
 void Mod_BuildBumpVectors(const float *v0, const float *v1, const float *v2, const float *tc0, const float *tc1, const float *tc2, float *svector3f, float *tvector3f, float *normal3f)
 {
        float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2];
        // 103 add/sub/negate/multiply (1 cycle), 3 divide (20 cycle), 3 sqrt (22 cycle), 4 compare (3 cycle?), total cycles not counting load/store/exchange roughly 241 cycles
        // 12 add, 28 subtract, 57 multiply, 3 divide, 3 sqrt, 4 compare, 50% chance of 6 negates
 
-       // 18 multiply, 19 subtract
+       // 6 multiply, 9 subtract
        VectorSubtract(v1, v0, v10);
        VectorSubtract(v2, v0, v20);
        normal3f[0] = v10[1] * v20[2] - v10[2] * v20[1];
        normal3f[1] = v10[2] * v20[0] - v10[0] * v20[2];
        normal3f[2] = v10[0] * v20[1] - v10[1] * v20[0];
+       // 1 sqrt, 1 divide, 6 multiply, 2 add, 1 compare
+       VectorNormalize(normal3f);
+       // 12 multiply, 10 subtract
        tc10[1] = tc1[1] - tc0[1];
        tc20[1] = tc2[1] - tc0[1];
        svector3f[0] = tc10[1] * v20[0] - tc20[1] * v10[0];
@@ -670,8 +558,6 @@ void Mod_BuildBumpVectors(const float *v0, const float *v1, const float *v2, con
        tvector3f[0] = tc10[0] * v20[0] - tc20[0] * v10[0];
        tvector3f[1] = tc10[0] * v20[1] - tc20[0] * v10[1];
        tvector3f[2] = tc10[0] * v20[2] - tc20[0] * v10[2];
-       // 1 sqrt, 1 divide, 6 multiply, 2 add, 1 compare
-       VectorNormalize(normal3f);
        // 12 multiply, 4 add, 6 subtract
        f = DotProduct(svector3f, normal3f);
        svector3f[0] -= f * normal3f[0];
@@ -696,19 +582,19 @@ void Mod_BuildBumpVectors(const float *v0, const float *v1, const float *v2, con
        }
 }
 
-// warning: this is an expensive function!
-void Mod_BuildTextureVectorsAndNormals(int numverts, int numtriangles, const float *vertex3f, const float *texcoord2f, const int *elements, float *svector3f, float *tvector3f, float *normal3f)
+// warning: this is a very expensive function!
+void Mod_BuildTextureVectorsAndNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const float *texcoord2f, const int *elements, float *svector3f, float *tvector3f, float *normal3f)
 {
        int i, tnum;
        float sdir[3], tdir[3], normal[3], *v;
        const int *e;
        // clear the vectors
        if (svector3f)
-               memset(svector3f, 0, numverts * sizeof(float[3]));
+               memset(svector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3]));
        if (tvector3f)
-               memset(tvector3f, 0, numverts * sizeof(float[3]));
+               memset(tvector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3]));
        if (normal3f)
-               memset(normal3f, 0, numverts * sizeof(float[3]));
+               memset(normal3f + 3 * firstvertex, 0, numvertices * sizeof(float[3]));
        // process each vertex of each triangle and accumulate the results
        for (tnum = 0, e = elements;tnum < numtriangles;tnum++, e += 3)
        {
@@ -745,18 +631,51 @@ void Mod_BuildTextureVectorsAndNormals(int numverts, int numtriangles, const flo
        // each vertex...  but instead normalize them
        // 4 assignments, 1 divide, 1 sqrt, 2 adds, 6 multiplies
        if (svector3f)
-               for (i = 0, v = svector3f;i < numverts;i++, v += 3)
+               for (i = 0, v = svector3f + 3 * firstvertex;i < numvertices;i++, v += 3)
                        VectorNormalize(v);
        // 4 assignments, 1 divide, 1 sqrt, 2 adds, 6 multiplies
        if (tvector3f)
-               for (i = 0, v = tvector3f;i < numverts;i++, v += 3)
+               for (i = 0, v = tvector3f + 3 * firstvertex;i < numvertices;i++, v += 3)
                        VectorNormalize(v);
        // 4 assignments, 1 divide, 1 sqrt, 2 adds, 6 multiplies
        if (normal3f)
-               for (i = 0, v = normal3f;i < numverts;i++, v += 3)
+               for (i = 0, v = normal3f + 3 * firstvertex;i < numvertices;i++, v += 3)
                        VectorNormalize(v);
 }
 
+surfmesh_t *Mod_AllocSurfMesh(mempool_t *mempool, int numvertices, int numtriangles, qboolean detailtexcoords, qboolean lightmapoffsets, qboolean vertexcolors, qboolean neighbors)
+{
+       surfmesh_t *mesh;
+       qbyte *data;
+       mesh = Mem_Alloc(mempool, sizeof(surfmesh_t) + numvertices * (3 + 3 + 3 + 3 + 2 + 2 + (detailtexcoords ? 2 : 0) + (vertexcolors ? 4 : 0)) * sizeof(float) + numvertices * (lightmapoffsets ? 1 : 0) * sizeof(int) + numtriangles * (3 + 3 + (neighbors ? 3 : 0)) * sizeof(int));
+       mesh->num_vertices = numvertices;
+       mesh->num_triangles = numtriangles;
+       data = (qbyte *)(mesh + 1);
+       if (mesh->num_vertices)
+       {
+               mesh->data_vertex3f = (float *)data, data += sizeof(float[3]) * mesh->num_vertices;
+               mesh->data_svector3f = (float *)data, data += sizeof(float[3]) * mesh->num_vertices;
+               mesh->data_tvector3f = (float *)data, data += sizeof(float[3]) * mesh->num_vertices;
+               mesh->data_normal3f = (float *)data, data += sizeof(float[3]) * mesh->num_vertices;
+               mesh->data_texcoordtexture2f = (float *)data, data += sizeof(float[2]) * mesh->num_vertices;
+               mesh->data_texcoordlightmap2f = (float *)data, data += sizeof(float[2]) * mesh->num_vertices;
+               if (detailtexcoords)
+                       mesh->data_texcoorddetail2f = (float *)data, data += sizeof(float[2]) * mesh->num_vertices;
+               if (vertexcolors)
+                       mesh->data_lightmapcolor4f = (float *)data, data += sizeof(float[4]) * mesh->num_vertices;
+               if (lightmapoffsets)
+                       mesh->data_lightmapoffsets = (int *)data, data += sizeof(int) * mesh->num_vertices;
+       }
+       if (mesh->num_triangles)
+       {
+               mesh->data_element3i = (int *)data, data += sizeof(int[3]) * mesh->num_triangles;
+               mesh->data_element3i = (int *)data, data += sizeof(int[3]) * mesh->num_triangles;
+               if (neighbors)
+                       mesh->data_neighbor3i = (int *)data, data += sizeof(int[3]) * mesh->num_triangles;
+       }
+       return mesh;
+}
+
 shadowmesh_t *Mod_ShadowMesh_Alloc(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable)
 {
        shadowmesh_t *newmesh;
@@ -1014,7 +933,7 @@ int Mod_LoadSkinFrame(skinframe_t *skinframe, char *basename, int textureflags,
        if (!image_loadskin(&s, basename))
                return false;
        if (usedetailtexture)
-               skinframe->detail = mod_shared_detailtextures[(detailtexturecycle++) % NUM_DETAILTEXTURES];
+               skinframe->detail = r_texture_detailtextures[(detailtexturecycle++) % NUM_DETAILTEXTURES];
        skinframe->base = R_LoadTexture2D (loadmodel->texturepool, basename, s.basepixels_width, s.basepixels_height, s.basepixels, TEXTYPE_RGBA, textureflags, NULL);
        if (s.nmappixels != NULL)
                skinframe->nmap = R_LoadTexture2D (loadmodel->texturepool, va("%s_nmap", basename), s.nmappixels_width, s.nmappixels_height, s.nmappixels, TEXTYPE_RGBA, textureflags, NULL);
@@ -1042,7 +961,7 @@ int Mod_LoadSkinFrame_Internal(skinframe_t *skinframe, char *basename, int textu
        if (!skindata)
                return false;
        if (usedetailtexture)
-               skinframe->detail = mod_shared_detailtextures[(detailtexturecycle++) % NUM_DETAILTEXTURES];
+               skinframe->detail = r_texture_detailtextures[(detailtexturecycle++) % NUM_DETAILTEXTURES];
        if (r_shadow_bumpscale_basetexture.value > 0)
        {
                temp1 = Mem_Alloc(loadmodel->mempool, width * height * 8);
@@ -1289,6 +1208,14 @@ int Mod_CountSkinFiles(skinfile_t *skinfile)
        return i;
 }
 
+void Mod_SnapVertices(int numcomponents, int numvertices, float *vertices, float snap)
+{
+       int i;
+       double isnap = 1.0 / snap;
+       for (i = 0;i < numvertices*numcomponents;i++)
+               vertices[i] = floor(vertices[i]*isnap)*snap;
+}
+
 int Mod_RemoveDegenerateTriangles(int numtriangles, const int *inelement3i, int *outelement3i, const float *vertex3f)
 {
        int i, outtriangles;