+static void Mod_GenerateLightmaps_CreateLights(model_t *model)
+{
+ int index;
+ int result;
+ lightmaplight_t *lightinfo;
+ float origin[3];
+ float radius;
+ float color[3];
+ mod_generatelightmaps_numlights = 0;
+ for (index = 0;;index++)
+ {
+ result = R_Shadow_GetRTLightInfo(index, origin, &radius, color);
+ if (result < 0)
+ break;
+ if (result > 0)
+ mod_generatelightmaps_numlights++;
+ }
+ if (mod_generatelightmaps_numlights > 0)
+ {
+ mod_generatelightmaps_lightinfo = (lightmaplight_t *)Mem_Alloc(tempmempool, mod_generatelightmaps_numlights * sizeof(*mod_generatelightmaps_lightinfo));
+ lightinfo = mod_generatelightmaps_lightinfo;
+ for (index = 0;;index++)
+ {
+ result = R_Shadow_GetRTLightInfo(index, lightinfo->origin, &lightinfo->radius, lightinfo->color);
+ if (result < 0)
+ break;
+ if (result > 0)
+ lightinfo++;
+ }
+ }
+ for (index = 0, lightinfo = mod_generatelightmaps_lightinfo;index < mod_generatelightmaps_numlights;index++, lightinfo++)
+ {
+ lightinfo->iradius = 1.0f / lightinfo->radius;
+ lightinfo->radius2 = lightinfo->radius * lightinfo->radius;
+ // TODO: compute svbsp
+ Mod_GenerateLightmaps_CreateLights_ComputeSVBSP(model, lightinfo);
+ }
+}
+
+static void Mod_GenerateLightmaps_DestroyLights(model_t *model)
+{
+ int i;
+ if (mod_generatelightmaps_lightinfo)
+ {
+ for (i = 0;i < mod_generatelightmaps_numlights;i++)
+ if (mod_generatelightmaps_lightinfo[i].svbsp.nodes)
+ Mem_Free(mod_generatelightmaps_lightinfo[i].svbsp.nodes);
+ Mem_Free(mod_generatelightmaps_lightinfo);
+ }
+ mod_generatelightmaps_lightinfo = NULL;
+ mod_generatelightmaps_numlights = 0;
+}
+
+static qbool Mod_GenerateLightmaps_SamplePoint_SVBSP(const svbsp_t *svbsp, const float *pos)
+{
+ const svbsp_node_t *node;
+ const svbsp_node_t *nodes = svbsp->nodes;
+ int num = 0;
+ while (num >= 0)
+ {
+ node = nodes + num;
+ num = node->children[DotProduct(node->plane, pos) < node->plane[3]];
+ }
+ return num == -1; // true if empty, false if solid (shadowed)
+}
+
+static void Mod_GenerateLightmaps_SamplePoint(const float *pos, const float *normal, float *sample, int numoffsets, const float *offsets)
+{
+ int i;
+ float relativepoint[3];
+ float color[3];
+ float offsetpos[3];
+ float dist;
+ float dist2;
+ float intensity;
+ int offsetindex;
+ int hits;
+ int tests;
+ const lightmaplight_t *lightinfo;
+ trace_t trace;
+ for (i = 0;i < 5*3;i++)
+ sample[i] = 0.0f;
+ for (i = 0, lightinfo = mod_generatelightmaps_lightinfo;i < mod_generatelightmaps_numlights;i++, lightinfo++)
+ {
+ //R_SampleRTLights(pos, sample, numoffsets, offsets);
+ VectorSubtract(lightinfo->origin, pos, relativepoint);
+ // don't accept light from behind a surface, it causes bad shading
+ if (normal && DotProduct(relativepoint, normal) <= 0)
+ continue;
+ dist2 = VectorLength2(relativepoint);
+ if (dist2 >= lightinfo->radius2)
+ continue;
+ dist = sqrt(dist2) * lightinfo->iradius;
+ intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0;
+ if (intensity <= 0)
+ continue;
+ if (cl.worldmodel && cl.worldmodel->TraceLine && numoffsets > 0)
+ {
+ hits = 0;
+ tests = 1;
+ if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, pos))
+ hits++;
+ for (offsetindex = 1;offsetindex < numoffsets;offsetindex++)
+ {
+ VectorAdd(pos, offsets + 3*offsetindex, offsetpos);
+ if (!normal)
+ {
+ // for light grid we'd better check visibility of the offset point
+ cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, pos, offsetpos, SUPERCONTENTS_SOLID, 0, MATERIALFLAGMASK_TRANSLUCENT | MATERIALFLAG_NOSHADOW);
+ if (trace.fraction < 1)
+ VectorLerp(pos, trace.fraction, offsetpos, offsetpos);
+ }
+ tests++;
+ if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, offsetpos))
+ hits++;
+ }
+ if (!hits)
+ continue;
+ // scale intensity according to how many rays succeeded
+ // we know one test is valid, half of the rest will fail...
+ //if (normal && tests > 1)
+ // intensity *= (tests - 1.0f) / tests;
+ intensity *= (float)hits / tests;
+ }
+ // scale down intensity to add to both ambient and diffuse
+ //intensity *= 0.5f;
+ VectorNormalize(relativepoint);
+ VectorScale(lightinfo->color, intensity, color);
+ VectorMA(sample , 0.5f , color, sample );
+ VectorMA(sample + 3, relativepoint[0], color, sample + 3);
+ VectorMA(sample + 6, relativepoint[1], color, sample + 6);
+ VectorMA(sample + 9, relativepoint[2], color, sample + 9);
+ // calculate a weighted average light direction as well
+ intensity *= VectorLength(color);
+ VectorMA(sample + 12, intensity, relativepoint, sample + 12);
+ }
+}
+
+static void Mod_GenerateLightmaps_LightmapSample(const float *pos, const float *normal, unsigned char *lm_bgr, unsigned char *lm_dir)
+{
+ float sample[5*3];
+ float color[3];
+ float dir[3];
+ float f;
+ Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[0], mod_generatelightmaps_offsets[0][0]);
+ //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]);
+ VectorCopy(sample + 12, dir);
+ VectorNormalize(dir);
+ //VectorAdd(dir, normal, dir);
+ //VectorNormalize(dir);
+ f = DotProduct(dir, normal);
+ f = max(0, f) * 255.0f;
+ VectorScale(sample, f, color);
+ //VectorCopy(normal, dir);
+ VectorSet(dir, (dir[0]+1.0f)*127.5f, (dir[1]+1.0f)*127.5f, (dir[2]+1.0f)*127.5f);
+ lm_bgr[0] = (unsigned char)bound(0.0f, color[2], 255.0f);
+ lm_bgr[1] = (unsigned char)bound(0.0f, color[1], 255.0f);
+ lm_bgr[2] = (unsigned char)bound(0.0f, color[0], 255.0f);
+ lm_bgr[3] = 255;
+ lm_dir[0] = (unsigned char)dir[2];
+ lm_dir[1] = (unsigned char)dir[1];
+ lm_dir[2] = (unsigned char)dir[0];
+ lm_dir[3] = 255;
+}
+
+static void Mod_GenerateLightmaps_VertexSample(const float *pos, const float *normal, float *vertex_color)
+{
+ float sample[5*3];
+ Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[1], mod_generatelightmaps_offsets[1][0]);
+ VectorCopy(sample, vertex_color);
+}
+
+static void Mod_GenerateLightmaps_GridSample(const float *pos, q3dlightgrid_t *s)
+{
+ float sample[5*3];
+ float ambient[3];
+ float diffuse[3];
+ float dir[3];
+ Mod_GenerateLightmaps_SamplePoint(pos, NULL, sample, mod_generatelightmaps_numoffsets[2], mod_generatelightmaps_offsets[2][0]);
+ // calculate the direction we'll use to reduce the sample to a directional light source
+ VectorCopy(sample + 12, dir);
+ //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]);
+ VectorNormalize(dir);
+ // extract the diffuse color along the chosen direction and scale it
+ diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]) * 127.5f;
+ diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]) * 127.5f;
+ diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]) * 127.5f;
+ // scale the ambient from 0-2 to 0-255 and subtract some of diffuse
+ VectorScale(sample, 127.5f, ambient);
+ VectorMA(ambient, -0.333f, diffuse, ambient);
+ // encode to the grid format
+ s->ambientrgb[0] = (unsigned char)bound(0.0f, ambient[0], 255.0f);
+ s->ambientrgb[1] = (unsigned char)bound(0.0f, ambient[1], 255.0f);
+ s->ambientrgb[2] = (unsigned char)bound(0.0f, ambient[2], 255.0f);
+ s->diffusergb[0] = (unsigned char)bound(0.0f, diffuse[0], 255.0f);
+ s->diffusergb[1] = (unsigned char)bound(0.0f, diffuse[1], 255.0f);
+ s->diffusergb[2] = (unsigned char)bound(0.0f, diffuse[2], 255.0f);
+ if (dir[2] >= 0.99f) {s->diffusepitch = 0;s->diffuseyaw = 0;}
+ else if (dir[2] <= -0.99f) {s->diffusepitch = 128;s->diffuseyaw = 0;}
+ else {s->diffusepitch = (unsigned char)(acos(dir[2]) * (127.5f/M_PI));s->diffuseyaw = (unsigned char)(atan2(dir[1], dir[0]) * (127.5f/M_PI));}
+}
+
+static void Mod_GenerateLightmaps_InitSampleOffsets(model_t *model)
+{
+ float radius[3];
+ float temp[3];
+ int i, j;
+ memset(mod_generatelightmaps_offsets, 0, sizeof(mod_generatelightmaps_offsets));
+ mod_generatelightmaps_numoffsets[0] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_lightmapsamples.integer);
+ mod_generatelightmaps_numoffsets[1] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_vertexsamples.integer);
+ mod_generatelightmaps_numoffsets[2] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_gridsamples.integer);
+ radius[0] = mod_generatelightmaps_lightmapradius.value;
+ radius[1] = mod_generatelightmaps_vertexradius.value;
+ radius[2] = mod_generatelightmaps_gridradius.value;
+ for (i = 0;i < 3;i++)
+ {
+ for (j = 1;j < mod_generatelightmaps_numoffsets[i];j++)
+ {
+ VectorRandom(temp);
+ VectorScale(temp, radius[i], mod_generatelightmaps_offsets[i][j]);
+ }
+ }
+}
+
+static void Mod_GenerateLightmaps_DestroyLightmaps(model_t *model)
+{
+ msurface_t *surface;
+ int surfaceindex;
+ int i;
+ for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++)
+ {
+ surface = model->data_surfaces + surfaceindex;
+ surface->lightmaptexture = NULL;
+ surface->deluxemaptexture = NULL;
+ }
+ if (model->brushq3.data_lightmaps)
+ {
+ for (i = 0;i < model->brushq3.num_mergedlightmaps;i++)
+ if (model->brushq3.data_lightmaps[i])
+ R_FreeTexture(model->brushq3.data_lightmaps[i]);
+ Mem_Free(model->brushq3.data_lightmaps);
+ model->brushq3.data_lightmaps = NULL;
+ }
+ if (model->brushq3.data_deluxemaps)
+ {
+ for (i = 0;i < model->brushq3.num_mergedlightmaps;i++)
+ if (model->brushq3.data_deluxemaps[i])
+ R_FreeTexture(model->brushq3.data_deluxemaps[i]);
+ Mem_Free(model->brushq3.data_deluxemaps);
+ model->brushq3.data_deluxemaps = NULL;
+ }
+}
+
+static void Mod_GenerateLightmaps_UnweldTriangles(model_t *model)
+{
+ msurface_t *surface;
+ int surfaceindex;
+ int vertexindex;
+ int outvertexindex;
+ int i;
+ const int *e;
+ surfmesh_t oldsurfmesh;
+ size_t size;
+ unsigned char *data;
+ oldsurfmesh = model->surfmesh;
+ model->surfmesh.num_triangles = oldsurfmesh.num_triangles;
+ model->surfmesh.num_vertices = oldsurfmesh.num_triangles * 3;
+ size = 0;
+ size += model->surfmesh.num_vertices * sizeof(float[3]);
+ size += model->surfmesh.num_vertices * sizeof(float[3]);
+ size += model->surfmesh.num_vertices * sizeof(float[3]);
+ size += model->surfmesh.num_vertices * sizeof(float[3]);
+ size += model->surfmesh.num_vertices * sizeof(float[2]);
+ size += model->surfmesh.num_vertices * sizeof(float[2]);
+ size += model->surfmesh.num_vertices * sizeof(float[4]);
+ data = (unsigned char *)Mem_Alloc(model->mempool, size);
+ model->surfmesh.data_vertex3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]);
+ model->surfmesh.data_normal3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]);
+ model->surfmesh.data_svector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]);
+ model->surfmesh.data_tvector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]);
+ model->surfmesh.data_texcoordtexture2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]);
+ model->surfmesh.data_texcoordlightmap2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]);
+ model->surfmesh.data_lightmapcolor4f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[4]);
+ if (model->surfmesh.num_vertices > 65536)
+ model->surfmesh.data_element3s = NULL;
+
+ if (model->surfmesh.data_element3i_indexbuffer && !model->surfmesh.data_element3i_indexbuffer->isdynamic)
+ R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3i_indexbuffer);
+ model->surfmesh.data_element3i_indexbuffer = NULL;
+ if (model->surfmesh.data_element3s_indexbuffer && !model->surfmesh.data_element3s_indexbuffer->isdynamic)
+ R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3s_indexbuffer);
+ model->surfmesh.data_element3s_indexbuffer = NULL;
+ if (model->surfmesh.data_vertex3f_vertexbuffer && !model->surfmesh.data_vertex3f_vertexbuffer->isdynamic)
+ R_Mesh_DestroyMeshBuffer(model->surfmesh.data_vertex3f_vertexbuffer);
+ model->surfmesh.data_vertex3f_vertexbuffer = NULL;
+ model->surfmesh.data_svector3f_vertexbuffer = NULL;
+ model->surfmesh.data_tvector3f_vertexbuffer = NULL;
+ model->surfmesh.data_normal3f_vertexbuffer = NULL;
+ model->surfmesh.data_texcoordtexture2f_vertexbuffer = NULL;
+ model->surfmesh.data_texcoordlightmap2f_vertexbuffer = NULL;
+ model->surfmesh.data_lightmapcolor4f_vertexbuffer = NULL;
+ model->surfmesh.data_skeletalindex4ub_vertexbuffer = NULL;
+ model->surfmesh.data_skeletalweight4ub_vertexbuffer = NULL;
+
+ // convert all triangles to unique vertex data
+ outvertexindex = 0;
+ for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++)
+ {
+ surface = model->data_surfaces + surfaceindex;
+ surface->num_firstvertex = outvertexindex;
+ surface->num_vertices = surface->num_triangles*3;
+ e = oldsurfmesh.data_element3i + surface->num_firsttriangle*3;
+ for (i = 0;i < surface->num_triangles*3;i++)
+ {
+ vertexindex = e[i];
+ model->surfmesh.data_vertex3f[outvertexindex*3+0] = oldsurfmesh.data_vertex3f[vertexindex*3+0];
+ model->surfmesh.data_vertex3f[outvertexindex*3+1] = oldsurfmesh.data_vertex3f[vertexindex*3+1];
+ model->surfmesh.data_vertex3f[outvertexindex*3+2] = oldsurfmesh.data_vertex3f[vertexindex*3+2];
+ model->surfmesh.data_normal3f[outvertexindex*3+0] = oldsurfmesh.data_normal3f[vertexindex*3+0];
+ model->surfmesh.data_normal3f[outvertexindex*3+1] = oldsurfmesh.data_normal3f[vertexindex*3+1];
+ model->surfmesh.data_normal3f[outvertexindex*3+2] = oldsurfmesh.data_normal3f[vertexindex*3+2];
+ model->surfmesh.data_svector3f[outvertexindex*3+0] = oldsurfmesh.data_svector3f[vertexindex*3+0];
+ model->surfmesh.data_svector3f[outvertexindex*3+1] = oldsurfmesh.data_svector3f[vertexindex*3+1];
+ model->surfmesh.data_svector3f[outvertexindex*3+2] = oldsurfmesh.data_svector3f[vertexindex*3+2];
+ model->surfmesh.data_tvector3f[outvertexindex*3+0] = oldsurfmesh.data_tvector3f[vertexindex*3+0];
+ model->surfmesh.data_tvector3f[outvertexindex*3+1] = oldsurfmesh.data_tvector3f[vertexindex*3+1];
+ model->surfmesh.data_tvector3f[outvertexindex*3+2] = oldsurfmesh.data_tvector3f[vertexindex*3+2];
+ model->surfmesh.data_texcoordtexture2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+0];
+ model->surfmesh.data_texcoordtexture2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+1];
+ if (oldsurfmesh.data_texcoordlightmap2f)
+ {
+ model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+0];
+ model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+1];
+ }
+ if (oldsurfmesh.data_lightmapcolor4f)
+ {
+ model->surfmesh.data_lightmapcolor4f[outvertexindex*4+0] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+0];
+ model->surfmesh.data_lightmapcolor4f[outvertexindex*4+1] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+1];
+ model->surfmesh.data_lightmapcolor4f[outvertexindex*4+2] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+2];
+ model->surfmesh.data_lightmapcolor4f[outvertexindex*4+3] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+3];
+ }
+ else
+ Vector4Set(model->surfmesh.data_lightmapcolor4f + 4*outvertexindex, 1, 1, 1, 1);
+ model->surfmesh.data_element3i[surface->num_firsttriangle*3+i] = outvertexindex;
+ outvertexindex++;
+ }
+ }
+ if (model->surfmesh.data_element3s)
+ for (i = 0;i < model->surfmesh.num_triangles*3;i++)
+ model->surfmesh.data_element3s[i] = model->surfmesh.data_element3i[i];
+
+ // find and update all submodels to use this new surfmesh data
+ for (i = 0;i < model->brush.numsubmodels;i++)
+ model->brush.submodels[i]->surfmesh = model->surfmesh;
+}
+
+static void Mod_GenerateLightmaps_CreateTriangleInformation(model_t *model)
+{
+ msurface_t *surface;
+ int surfaceindex;
+ int i;
+ int axis;
+ float normal[3];
+ const int *e;
+ lightmaptriangle_t *triangle;
+ // generate lightmap triangle structs
+ mod_generatelightmaps_lightmaptriangles = (lightmaptriangle_t *)Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t));
+ for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++)
+ {
+ surface = model->data_surfaces + surfaceindex;
+ e = model->surfmesh.data_element3i + surface->num_firsttriangle*3;
+ for (i = 0;i < surface->num_triangles;i++)
+ {
+ triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i];
+ triangle->triangleindex = surface->num_firsttriangle+i;
+ triangle->surfaceindex = surfaceindex;
+ VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+0], triangle->vertex[0]);
+ VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+1], triangle->vertex[1]);
+ VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+2], triangle->vertex[2]);
+ // calculate bounds of triangle
+ triangle->mins[0] = min(triangle->vertex[0][0], min(triangle->vertex[1][0], triangle->vertex[2][0]));
+ triangle->mins[1] = min(triangle->vertex[0][1], min(triangle->vertex[1][1], triangle->vertex[2][1]));
+ triangle->mins[2] = min(triangle->vertex[0][2], min(triangle->vertex[1][2], triangle->vertex[2][2]));
+ triangle->maxs[0] = max(triangle->vertex[0][0], max(triangle->vertex[1][0], triangle->vertex[2][0]));
+ triangle->maxs[1] = max(triangle->vertex[0][1], max(triangle->vertex[1][1], triangle->vertex[2][1]));
+ triangle->maxs[2] = max(triangle->vertex[0][2], max(triangle->vertex[1][2], triangle->vertex[2][2]));
+ // pick an axial projection based on the triangle normal
+ TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], normal);
+ axis = 0;
+ if (fabs(normal[1]) > fabs(normal[axis]))
+ axis = 1;
+ if (fabs(normal[2]) > fabs(normal[axis]))
+ axis = 2;
+ triangle->axis = axis;
+ }
+ }
+}
+
+static void Mod_GenerateLightmaps_DestroyTriangleInformation(model_t *model)
+{
+ if (mod_generatelightmaps_lightmaptriangles)
+ Mem_Free(mod_generatelightmaps_lightmaptriangles);
+ mod_generatelightmaps_lightmaptriangles = NULL;
+}
+
+float lmaxis[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
+
+static void Mod_GenerateLightmaps_CreateLightmaps(model_t *model)
+{
+ msurface_t *surface;
+ int surfaceindex;
+ int lightmapindex;
+ int lightmapnumber;
+ int i;
+ int j;
+ int k;
+ int x;
+ int y;
+ int axis;
+ int axis1;
+ int axis2;
+ int retry;
+ int pixeloffset;
+ float trianglenormal[3];
+ float samplecenter[3];
+ float samplenormal[3];
+ float temp[3];
+ float lmiscale[2];
+ float slopex;
+ float slopey;
+ float slopebase;
+ float lmscalepixels;
+ float lmmins;
+ float lmmaxs;
+ float lm_basescalepixels;
+ int lm_borderpixels;
+ int lm_texturesize;
+ //int lm_maxpixels;
+ const int *e;
+ lightmaptriangle_t *triangle;
+ unsigned char *lightmappixels;
+ unsigned char *deluxemappixels;
+ mod_alloclightmap_state_t lmstate;
+ char vabuf[1024];
+
+ // generate lightmap projection information for all triangles
+ if (model->texturepool == NULL)
+ model->texturepool = R_AllocTexturePool();
+ lm_basescalepixels = 1.0f / max(0.0001f, mod_generatelightmaps_unitspersample.value);
+ lm_borderpixels = mod_generatelightmaps_borderpixels.integer;
+ lm_texturesize = bound(lm_borderpixels*2+1, 64, (int)vid.maxtexturesize_2d);
+ //lm_maxpixels = lm_texturesize-(lm_borderpixels*2+1);
+ Mod_AllocLightmap_Init(&lmstate, loadmodel->mempool, lm_texturesize, lm_texturesize);
+ lightmapnumber = 0;
+ for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++)
+ {
+ surface = model->data_surfaces + surfaceindex;
+ e = model->surfmesh.data_element3i + surface->num_firsttriangle*3;
+ lmscalepixels = lm_basescalepixels;
+ for (retry = 0;retry < 30;retry++)
+ {
+ // after a couple failed attempts, degrade quality to make it fit
+ if (retry > 1)
+ lmscalepixels *= 0.5f;
+ for (i = 0;i < surface->num_triangles;i++)
+ {
+ triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i];
+ triangle->lightmapindex = lightmapnumber;
+ // calculate lightmap bounds in 3D pixel coordinates, limit size,
+ // pick two planar axes for projection
+ // lightmap coordinates here are in pixels
+ // lightmap projections are snapped to pixel grid explicitly, such
+ // that two neighboring triangles sharing an edge and projection
+ // axis will have identical sample spacing along their shared edge
+ k = 0;
+ for (j = 0;j < 3;j++)
+ {
+ if (j == triangle->axis)
+ continue;
+ lmmins = floor(triangle->mins[j]*lmscalepixels)-lm_borderpixels;
+ lmmaxs = floor(triangle->maxs[j]*lmscalepixels)+lm_borderpixels;
+ triangle->lmsize[k] = (int)(lmmaxs-lmmins);
+ triangle->lmbase[k] = lmmins/lmscalepixels;
+ triangle->lmscale[k] = lmscalepixels;
+ k++;
+ }
+ if (!Mod_AllocLightmap_Block(&lmstate, triangle->lmsize[0], triangle->lmsize[1], &triangle->lmoffset[0], &triangle->lmoffset[1]))
+ break;
+ }
+ // if all fit in this texture, we're done with this surface
+ if (i == surface->num_triangles)
+ break;
+ // if we haven't maxed out the lightmap size yet, we retry the
+ // entire surface batch...
+ if (lm_texturesize * 2 <= min(mod_generatelightmaps_texturesize.integer, (int)vid.maxtexturesize_2d))
+ {
+ lm_texturesize *= 2;
+ surfaceindex = -1;
+ lightmapnumber = 0;
+ Mod_AllocLightmap_Free(&lmstate);
+ Mod_AllocLightmap_Init(&lmstate, loadmodel->mempool, lm_texturesize, lm_texturesize);
+ break;
+ }
+ // if we have maxed out the lightmap size, and this triangle does
+ // not fit in the same texture as the rest of the surface, we have
+ // to retry the entire surface in a new texture (can only use one)
+ // with multiple retries, the lightmap quality degrades until it
+ // fits (or gives up)
+ if (surfaceindex > 0)
+ lightmapnumber++;
+ Mod_AllocLightmap_Reset(&lmstate);
+ }
+ }
+ lightmapnumber++;
+ Mod_AllocLightmap_Free(&lmstate);
+
+ // now put triangles together into lightmap textures, and do not allow
+ // triangles of a surface to go into different textures (as that would
+ // require rewriting the surface list)
+ model->brushq3.deluxemapping_modelspace = true;
+ model->brushq3.deluxemapping = true;
+ model->brushq3.num_mergedlightmaps = lightmapnumber;
+ model->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *));
+ model->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *));
+ lightmappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4);
+ deluxemappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4);
+ for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++)
+ {
+ surface = model->data_surfaces + surfaceindex;
+ e = model->surfmesh.data_element3i + surface->num_firsttriangle*3;
+ for (i = 0;i < surface->num_triangles;i++)
+ {
+ triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i];
+ TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], trianglenormal);
+ VectorNormalize(trianglenormal);
+ VectorCopy(trianglenormal, samplenormal); // FIXME: this is supposed to be interpolated per pixel from vertices
+ axis = triangle->axis;
+ axis1 = axis == 0 ? 1 : 0;
+ axis2 = axis == 2 ? 1 : 2;
+ lmiscale[0] = 1.0f / triangle->lmscale[0];
+ lmiscale[1] = 1.0f / triangle->lmscale[1];
+ if (trianglenormal[axis] < 0)
+ VectorNegate(trianglenormal, trianglenormal);
+ CrossProduct(lmaxis[axis2], trianglenormal, temp);slopex = temp[axis] / temp[axis1];
+ CrossProduct(lmaxis[axis1], trianglenormal, temp);slopey = temp[axis] / temp[axis2];
+ slopebase = triangle->vertex[0][axis] - triangle->vertex[0][axis1]*slopex - triangle->vertex[0][axis2]*slopey;
+ for (j = 0;j < 3;j++)
+ {
+ float *t2f = model->surfmesh.data_texcoordlightmap2f + e[i*3+j]*2;
+ t2f[0] = ((triangle->vertex[j][axis1] - triangle->lmbase[0]) * triangle->lmscale[0] + triangle->lmoffset[0]) / lm_texturesize;
+ t2f[1] = ((triangle->vertex[j][axis2] - triangle->lmbase[1]) * triangle->lmscale[1] + triangle->lmoffset[1]) / lm_texturesize;
+#if 0
+ samplecenter[axis1] = (t2f[0]*lm_texturesize-triangle->lmoffset[0])*lmiscale[0] + triangle->lmbase[0];
+ samplecenter[axis2] = (t2f[1]*lm_texturesize-triangle->lmoffset[1])*lmiscale[1] + triangle->lmbase[1];
+ samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase;
+ Con_Printf("%f:%f %f:%f %f:%f = %f %f\n", triangle->vertex[j][axis1], samplecenter[axis1], triangle->vertex[j][axis2], samplecenter[axis2], triangle->vertex[j][axis], samplecenter[axis], t2f[0], t2f[1]);
+#endif
+ }
+
+#if 0
+ switch (axis)
+ {
+ default:
+ case 0:
+ forward[0] = 0;
+ forward[1] = 1.0f / triangle->lmscale[0];
+ forward[2] = 0;
+ left[0] = 0;
+ left[1] = 0;
+ left[2] = 1.0f / triangle->lmscale[1];
+ up[0] = 1.0f;
+ up[1] = 0;
+ up[2] = 0;
+ origin[0] = 0;
+ origin[1] = triangle->lmbase[0];
+ origin[2] = triangle->lmbase[1];
+ break;
+ case 1:
+ forward[0] = 1.0f / triangle->lmscale[0];
+ forward[1] = 0;
+ forward[2] = 0;
+ left[0] = 0;
+ left[1] = 0;
+ left[2] = 1.0f / triangle->lmscale[1];
+ up[0] = 0;
+ up[1] = 1.0f;
+ up[2] = 0;
+ origin[0] = triangle->lmbase[0];
+ origin[1] = 0;
+ origin[2] = triangle->lmbase[1];
+ break;
+ case 2:
+ forward[0] = 1.0f / triangle->lmscale[0];
+ forward[1] = 0;
+ forward[2] = 0;
+ left[0] = 0;
+ left[1] = 1.0f / triangle->lmscale[1];
+ left[2] = 0;
+ up[0] = 0;
+ up[1] = 0;
+ up[2] = 1.0f;
+ origin[0] = triangle->lmbase[0];
+ origin[1] = triangle->lmbase[1];
+ origin[2] = 0;
+ break;
+ }
+ Matrix4x4_FromVectors(&backmatrix, forward, left, up, origin);
+#endif
+#define LM_DIST_EPSILON (1.0f / 32.0f)
+ for (y = 0;y < triangle->lmsize[1];y++)
+ {
+ pixeloffset = ((triangle->lightmapindex * lm_texturesize + y + triangle->lmoffset[1]) * lm_texturesize + triangle->lmoffset[0]) * 4;
+ for (x = 0;x < triangle->lmsize[0];x++, pixeloffset += 4)
+ {
+ samplecenter[axis1] = (x+0.5f)*lmiscale[0] + triangle->lmbase[0];
+ samplecenter[axis2] = (y+0.5f)*lmiscale[1] + triangle->lmbase[1];
+ samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase;
+ VectorMA(samplecenter, 0.125f, samplenormal, samplecenter);
+ Mod_GenerateLightmaps_LightmapSample(samplecenter, samplenormal, lightmappixels + pixeloffset, deluxemappixels + pixeloffset);
+ }
+ }
+ }
+ }
+
+ for (lightmapindex = 0;lightmapindex < model->brushq3.num_mergedlightmaps;lightmapindex++)
+ {
+ model->brushq3.data_lightmaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va(vabuf, sizeof(vabuf), "lightmap%i", lightmapindex), lm_texturesize, lm_texturesize, lightmappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL);
+ model->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va(vabuf, sizeof(vabuf), "deluxemap%i", lightmapindex), lm_texturesize, lm_texturesize, deluxemappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL);
+ }
+
+ if (lightmappixels)
+ Mem_Free(lightmappixels);
+ if (deluxemappixels)
+ Mem_Free(deluxemappixels);
+
+ for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++)
+ {
+ surface = model->data_surfaces + surfaceindex;
+ if (!surface->num_triangles)
+ continue;
+ lightmapindex = mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle].lightmapindex;
+ surface->lightmaptexture = model->brushq3.data_lightmaps[lightmapindex];
+ surface->deluxemaptexture = model->brushq3.data_deluxemaps[lightmapindex];
+ surface->lightmapinfo = NULL;
+ }
+
+ model->brush.LightPoint = Mod_GenerateLightmaps_LightPoint;
+ model->brushq1.lightdata = NULL;
+ model->brushq1.lightmapupdateflags = NULL;
+ model->brushq1.firstrender = false;
+ model->brushq1.num_lightstyles = 0;
+ model->brushq1.data_lightstyleinfo = NULL;
+ for (i = 0;i < model->brush.numsubmodels;i++)
+ {
+ model->brush.submodels[i]->brushq1.lightmapupdateflags = NULL;
+ model->brush.submodels[i]->brushq1.firstrender = false;
+ model->brush.submodels[i]->brushq1.num_lightstyles = 0;
+ model->brush.submodels[i]->brushq1.data_lightstyleinfo = NULL;
+ }
+}
+
+static void Mod_GenerateLightmaps_UpdateVertexColors(model_t *model)
+{
+ int i;
+ for (i = 0;i < model->surfmesh.num_vertices;i++)
+ Mod_GenerateLightmaps_VertexSample(model->surfmesh.data_vertex3f + 3*i, model->surfmesh.data_normal3f + 3*i, model->surfmesh.data_lightmapcolor4f + 4*i);
+}
+
+static void Mod_GenerateLightmaps_UpdateLightGrid(model_t *model)
+{
+ int x;
+ int y;
+ int z;
+ int index = 0;
+ float pos[3];
+ for (z = 0;z < model->brushq3.num_lightgrid_isize[2];z++)
+ {
+ pos[2] = (model->brushq3.num_lightgrid_imins[2] + z + 0.5f) * model->brushq3.num_lightgrid_cellsize[2];
+ for (y = 0;y < model->brushq3.num_lightgrid_isize[1];y++)
+ {
+ pos[1] = (model->brushq3.num_lightgrid_imins[1] + y + 0.5f) * model->brushq3.num_lightgrid_cellsize[1];
+ for (x = 0;x < model->brushq3.num_lightgrid_isize[0];x++, index++)
+ {
+ pos[0] = (model->brushq3.num_lightgrid_imins[0] + x + 0.5f) * model->brushq3.num_lightgrid_cellsize[0];
+ Mod_GenerateLightmaps_GridSample(pos, model->brushq3.data_lightgrid + index);
+ }
+ }
+ }
+}
+
+extern cvar_t mod_q3bsp_nolightmaps;
+static void Mod_GenerateLightmaps(model_t *model)
+{
+ //lightmaptriangle_t *lightmaptriangles = Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t));
+ model_t *oldloadmodel = loadmodel;
+ loadmodel = model;
+
+ Mod_GenerateLightmaps_InitSampleOffsets(model);
+ Mod_GenerateLightmaps_DestroyLightmaps(model);
+ Mod_GenerateLightmaps_UnweldTriangles(model);
+ Mod_GenerateLightmaps_CreateTriangleInformation(model);
+ Mod_GenerateLightmaps_CreateLights(model);
+ if(!mod_q3bsp_nolightmaps.integer)
+ Mod_GenerateLightmaps_CreateLightmaps(model);
+ Mod_GenerateLightmaps_UpdateVertexColors(model);
+ Mod_GenerateLightmaps_UpdateLightGrid(model);
+ Mod_GenerateLightmaps_DestroyLights(model);
+ Mod_GenerateLightmaps_DestroyTriangleInformation(model);
+
+ loadmodel = oldloadmodel;
+}
+
+static void Mod_GenerateLightmaps_f(cmd_state_t *cmd)
+{
+ if (Cmd_Argc(cmd) != 1)
+ {
+ Con_Printf("usage: mod_generatelightmaps\n");
+ return;
+ }
+ if (!cl.worldmodel)
+ {
+ Con_Printf("no worldmodel loaded\n");
+ return;
+ }
+ Mod_GenerateLightmaps(cl.worldmodel);
+}
+
+void Mod_Mesh_Create(model_t *mod, const char *name)
+{
+ memset(mod, 0, sizeof(*mod));
+ strlcpy(mod->name, name, sizeof(mod->name));
+ mod->mempool = Mem_AllocPool(name, 0, NULL);
+ mod->texturepool = R_AllocTexturePool();
+ mod->Draw = R_Mod_Draw;
+ mod->DrawDepth = R_Mod_DrawDepth;
+ mod->DrawDebug = R_Mod_DrawDebug;
+ mod->DrawPrepass = R_Mod_DrawPrepass;
+ mod->GetLightInfo = R_Mod_GetLightInfo;
+ mod->DrawShadowMap = R_Mod_DrawShadowMap;
+ mod->DrawLight = R_Mod_DrawLight;
+}
+
+void Mod_Mesh_Destroy(model_t *mod)
+{
+ Mod_UnloadModel(mod);
+}
+
+// resets the mesh model to have no geometry to render, ready for a new frame -
+// the mesh will be prepared for rendering later using Mod_Mesh_Finalize
+void Mod_Mesh_Reset(model_t *mod)
+{
+ mod->num_surfaces = 0;
+ mod->surfmesh.num_vertices = 0;
+ mod->surfmesh.num_triangles = 0;
+ memset(mod->surfmesh.data_vertexhash, -1, mod->surfmesh.num_vertexhashsize * sizeof(*mod->surfmesh.data_vertexhash));
+ mod->DrawSky = NULL; // will be set if a texture needs it
+ mod->DrawAddWaterPlanes = NULL; // will be set if a texture needs it
+}
+
+texture_t *Mod_Mesh_GetTexture(model_t *mod, const char *name, int defaultdrawflags, int defaulttexflags, int defaultmaterialflags)
+{
+ int i;
+ texture_t *t;
+ int drawflag = defaultdrawflags & DRAWFLAG_MASK;
+ for (i = 0, t = mod->data_textures; i < mod->num_textures; i++, t++)
+ if (!strcmp(t->name, name) && t->mesh_drawflag == drawflag && t->mesh_defaulttexflags == defaulttexflags && t->mesh_defaultmaterialflags == defaultmaterialflags)
+ return t;
+ if (mod->max_textures <= mod->num_textures)
+ {
+ texture_t *oldtextures = mod->data_textures;
+ mod->max_textures = max(mod->max_textures * 2, 1024);
+ mod->data_textures = (texture_t *)Mem_Realloc(mod->mempool, mod->data_textures, mod->max_textures * sizeof(*mod->data_textures));
+ // update the pointers
+ for (i = 0; i < mod->num_surfaces; i++)
+ mod->data_surfaces[i].texture = mod->data_textures + (mod->data_surfaces[i].texture - oldtextures);
+ }
+ t = &mod->data_textures[mod->num_textures++];
+ Mod_LoadTextureFromQ3Shader(mod->mempool, mod->name, t, name, true, true, defaulttexflags, defaultmaterialflags);
+ t->mesh_drawflag = drawflag;
+ t->mesh_defaulttexflags = defaulttexflags;
+ t->mesh_defaultmaterialflags = defaultmaterialflags;
+ switch (defaultdrawflags & DRAWFLAG_MASK)
+ {
+ case DRAWFLAG_ADDITIVE:
+ t->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED;
+ t->currentmaterialflags = t->basematerialflags;
+ break;
+ case DRAWFLAG_MODULATE:
+ t->basematerialflags |= MATERIALFLAG_CUSTOMBLEND | MATERIALFLAG_BLENDED;
+ t->currentmaterialflags = t->basematerialflags;
+ t->customblendfunc[0] = GL_DST_COLOR;
+ t->customblendfunc[1] = GL_ZERO;
+ break;
+ case DRAWFLAG_2XMODULATE:
+ t->basematerialflags |= MATERIALFLAG_CUSTOMBLEND | MATERIALFLAG_BLENDED;
+ t->currentmaterialflags = t->basematerialflags;
+ t->customblendfunc[0] = GL_DST_COLOR;
+ t->customblendfunc[1] = GL_SRC_COLOR;
+ break;
+ case DRAWFLAG_SCREEN:
+ t->basematerialflags |= MATERIALFLAG_CUSTOMBLEND | MATERIALFLAG_BLENDED;
+ t->currentmaterialflags = t->basematerialflags;
+ t->customblendfunc[0] = GL_ONE_MINUS_DST_COLOR;
+ t->customblendfunc[1] = GL_ONE;
+ break;
+ default:
+ break;
+ }
+ return t;
+}
+
+msurface_t *Mod_Mesh_AddSurface(model_t *mod, texture_t *tex, qbool batchwithprevioussurface)
+{
+ msurface_t *surf;
+ // batch if possible; primarily useful for UI rendering where bounding boxes don't matter
+ if (batchwithprevioussurface && mod->num_surfaces > 0 && mod->data_surfaces[mod->num_surfaces - 1].texture == tex)
+ return mod->data_surfaces + mod->num_surfaces - 1;
+ // create new surface
+ if (mod->max_surfaces == mod->num_surfaces)
+ {
+ mod->max_surfaces = 2 * max(mod->num_surfaces, 64);
+ mod->data_surfaces = (msurface_t *)Mem_Realloc(mod->mempool, mod->data_surfaces, mod->max_surfaces * sizeof(*mod->data_surfaces));
+ mod->modelsurfaces_sorted = (int *)Mem_Realloc(mod->mempool, mod->modelsurfaces_sorted, mod->max_surfaces * sizeof(*mod->modelsurfaces_sorted));
+ }
+ surf = mod->data_surfaces + mod->num_surfaces;
+ mod->num_surfaces++;
+ memset(surf, 0, sizeof(*surf));
+ surf->texture = tex;
+ surf->num_firsttriangle = mod->surfmesh.num_triangles;
+ surf->num_firstvertex = mod->surfmesh.num_vertices;
+ if (tex->basematerialflags & (MATERIALFLAG_SKY))
+ mod->DrawSky = R_Mod_DrawSky;
+ if (tex->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA))
+ mod->DrawAddWaterPlanes = R_Mod_DrawAddWaterPlanes;
+ return surf;
+}
+
+int Mod_Mesh_IndexForVertex(model_t *mod, msurface_t *surf, float x, float y, float z, float nx, float ny, float nz, float s, float t, float u, float v, float r, float g, float b, float a)
+{
+ int hashindex, h, vnum, mask;
+ surfmesh_t *mesh = &mod->surfmesh;
+ if (mesh->max_vertices == mesh->num_vertices)
+ {
+ mesh->max_vertices = max(mesh->num_vertices * 2, 256);
+ mesh->data_vertex3f = (float *)Mem_Realloc(mod->mempool, mesh->data_vertex3f, mesh->max_vertices * sizeof(float[3]));
+ mesh->data_svector3f = (float *)Mem_Realloc(mod->mempool, mesh->data_svector3f, mesh->max_vertices * sizeof(float[3]));
+ mesh->data_tvector3f = (float *)Mem_Realloc(mod->mempool, mesh->data_tvector3f, mesh->max_vertices * sizeof(float[3]));
+ mesh->data_normal3f = (float *)Mem_Realloc(mod->mempool, mesh->data_normal3f, mesh->max_vertices * sizeof(float[3]));
+ mesh->data_texcoordtexture2f = (float *)Mem_Realloc(mod->mempool, mesh->data_texcoordtexture2f, mesh->max_vertices * sizeof(float[2]));
+ mesh->data_texcoordlightmap2f = (float *)Mem_Realloc(mod->mempool, mesh->data_texcoordlightmap2f, mesh->max_vertices * sizeof(float[2]));
+ mesh->data_lightmapcolor4f = (float *)Mem_Realloc(mod->mempool, mesh->data_lightmapcolor4f, mesh->max_vertices * sizeof(float[4]));
+ // rebuild the hash table
+ mesh->num_vertexhashsize = 4 * mesh->max_vertices;
+ mesh->num_vertexhashsize &= ~(mesh->num_vertexhashsize - 1); // round down to pow2
+ mesh->data_vertexhash = (int *)Mem_Realloc(mod->mempool, mesh->data_vertexhash, mesh->num_vertexhashsize * sizeof(*mesh->data_vertexhash));
+ memset(mesh->data_vertexhash, -1, mesh->num_vertexhashsize * sizeof(*mesh->data_vertexhash));
+ mask = mod->surfmesh.num_vertexhashsize - 1;
+ // no need to hash the vertices for the entire model, the latest surface will suffice.
+ for (vnum = surf ? surf->num_firstvertex : 0; vnum < mesh->num_vertices; vnum++)
+ {
+ // this uses prime numbers intentionally for computing the hash
+ hashindex = (unsigned int)(mesh->data_vertex3f[vnum * 3 + 0] * 2003 + mesh->data_vertex3f[vnum * 3 + 1] * 4001 + mesh->data_vertex3f[vnum * 3 + 2] * 7919 + mesh->data_normal3f[vnum * 3 + 0] * 4097 + mesh->data_normal3f[vnum * 3 + 1] * 257 + mesh->data_normal3f[vnum * 3 + 2] * 17) & mask;
+ for (h = hashindex; mesh->data_vertexhash[h] >= 0; h = (h + 1) & mask)
+ ; // just iterate until we find the terminator
+ mesh->data_vertexhash[h] = vnum;
+ }
+ }
+ mask = mod->surfmesh.num_vertexhashsize - 1;
+ // this uses prime numbers intentionally for computing the hash
+ hashindex = (unsigned int)(x * 2003 + y * 4001 + z * 7919 + nx * 4097 + ny * 257 + nz * 17) & mask;
+ // when possible find an identical vertex within the same surface and return it
+ for(h = hashindex;(vnum = mesh->data_vertexhash[h]) >= 0;h = (h + 1) & mask)
+ {
+ if (vnum >= surf->num_firstvertex
+ && mesh->data_vertex3f[vnum * 3 + 0] == x && mesh->data_vertex3f[vnum * 3 + 1] == y && mesh->data_vertex3f[vnum * 3 + 2] == z
+ && mesh->data_normal3f[vnum * 3 + 0] == nx && mesh->data_normal3f[vnum * 3 + 1] == ny && mesh->data_normal3f[vnum * 3 + 2] == nz
+ && mesh->data_texcoordtexture2f[vnum * 2 + 0] == s && mesh->data_texcoordtexture2f[vnum * 2 + 1] == t
+ && mesh->data_texcoordlightmap2f[vnum * 2 + 0] == u && mesh->data_texcoordlightmap2f[vnum * 2 + 1] == v
+ && mesh->data_lightmapcolor4f[vnum * 4 + 0] == r && mesh->data_lightmapcolor4f[vnum * 4 + 1] == g && mesh->data_lightmapcolor4f[vnum * 4 + 2] == b && mesh->data_lightmapcolor4f[vnum * 4 + 3] == a)
+ return vnum;
+ }
+ // add the new vertex
+ vnum = mesh->num_vertices++;
+ if (surf->num_vertices > 0)
+ {
+ if (surf->mins[0] > x) surf->mins[0] = x;
+ if (surf->mins[1] > y) surf->mins[1] = y;
+ if (surf->mins[2] > z) surf->mins[2] = z;
+ if (surf->maxs[0] < x) surf->maxs[0] = x;
+ if (surf->maxs[1] < y) surf->maxs[1] = y;
+ if (surf->maxs[2] < z) surf->maxs[2] = z;
+ }
+ else
+ {
+ VectorSet(surf->mins, x, y, z);
+ VectorSet(surf->maxs, x, y, z);
+ }
+ surf->num_vertices = mesh->num_vertices - surf->num_firstvertex;
+ mesh->data_vertexhash[h] = vnum;
+ mesh->data_vertex3f[vnum * 3 + 0] = x;
+ mesh->data_vertex3f[vnum * 3 + 1] = y;
+ mesh->data_vertex3f[vnum * 3 + 2] = z;
+ mesh->data_normal3f[vnum * 3 + 0] = nx;
+ mesh->data_normal3f[vnum * 3 + 1] = ny;
+ mesh->data_normal3f[vnum * 3 + 2] = nz;
+ mesh->data_texcoordtexture2f[vnum * 2 + 0] = s;
+ mesh->data_texcoordtexture2f[vnum * 2 + 1] = t;
+ mesh->data_texcoordlightmap2f[vnum * 2 + 0] = u;
+ mesh->data_texcoordlightmap2f[vnum * 2 + 1] = v;
+ mesh->data_lightmapcolor4f[vnum * 4 + 0] = r;
+ mesh->data_lightmapcolor4f[vnum * 4 + 1] = g;
+ mesh->data_lightmapcolor4f[vnum * 4 + 2] = b;
+ mesh->data_lightmapcolor4f[vnum * 4 + 3] = a;
+ return vnum;
+}
+
+void Mod_Mesh_AddTriangle(model_t *mod, msurface_t *surf, int e0, int e1, int e2)
+{
+ surfmesh_t *mesh = &mod->surfmesh;
+ if (mesh->max_triangles == mesh->num_triangles)
+ {
+ mesh->max_triangles = 2 * max(mesh->num_triangles, 128);
+ mesh->data_element3s = (unsigned short *)Mem_Realloc(mod->mempool, mesh->data_element3s, mesh->max_triangles * sizeof(unsigned short[3]));
+ mesh->data_element3i = (int *)Mem_Realloc(mod->mempool, mesh->data_element3i, mesh->max_triangles * sizeof(int[3]));
+ }
+ mesh->data_element3s[mesh->num_triangles * 3 + 0] = e0;
+ mesh->data_element3s[mesh->num_triangles * 3 + 1] = e1;
+ mesh->data_element3s[mesh->num_triangles * 3 + 2] = e2;
+ mesh->data_element3i[mesh->num_triangles * 3 + 0] = e0;
+ mesh->data_element3i[mesh->num_triangles * 3 + 1] = e1;
+ mesh->data_element3i[mesh->num_triangles * 3 + 2] = e2;
+ mesh->num_triangles++;
+ surf->num_triangles++;
+}
+
+static void Mod_Mesh_MakeSortedSurfaces(model_t *mod)
+{
+ int i, j;
+ texture_t *tex;
+ unsigned char* included = (unsigned char *)R_FrameData_Alloc(mod->num_surfaces * sizeof(unsigned char));
+
+ // build the sorted surfaces list properly to reduce material setup
+ // this is easy because we're just sorting on texture and don't care about the order of textures
+ mod->submodelsurfaces_start = 0;
+ mod->submodelsurfaces_end = 0;
+ for (i = 0; i < mod->num_surfaces; i++)
+ included[i] = 0;
+ for (i = 0; i < mod->num_surfaces; i++)
+ {
+ if (included[i])
+ continue;
+ tex = mod->data_surfaces[i].texture;
+ // j = i is intentional
+ for (j = i; j < mod->num_surfaces; j++)
+ {
+ if (!included[j] && mod->data_surfaces[j].texture == tex)
+ {
+ included[j] = 1;
+ mod->modelsurfaces_sorted[mod->submodelsurfaces_end++] = j;
+ }
+ }
+ }
+}
+
+static void Mod_Mesh_ComputeBounds(model_t *mod)
+{
+ int i;
+ vec_t x2a, x2b, y2a, y2b, z2a, z2b, x2, y2, z2, yawradius, rotatedradius;
+
+ if (mod->surfmesh.num_vertices > 0)
+ {
+ // calculate normalmins/normalmaxs
+ VectorCopy(mod->surfmesh.data_vertex3f, mod->normalmins);
+ VectorCopy(mod->surfmesh.data_vertex3f, mod->normalmaxs);
+ for (i = 1; i < mod->surfmesh.num_vertices; i++)
+ {
+ float x = mod->surfmesh.data_vertex3f[i * 3 + 0];
+ float y = mod->surfmesh.data_vertex3f[i * 3 + 1];
+ float z = mod->surfmesh.data_vertex3f[i * 3 + 2];
+ // expand bounds to include this vertex
+ if (mod->normalmins[0] > x) mod->normalmins[0] = x;
+ if (mod->normalmins[1] > y) mod->normalmins[1] = y;
+ if (mod->normalmins[2] > z) mod->normalmins[2] = z;
+ if (mod->normalmaxs[0] < x) mod->normalmaxs[0] = x;
+ if (mod->normalmaxs[1] < y) mod->normalmaxs[1] = y;
+ if (mod->normalmaxs[2] < z) mod->normalmaxs[2] = z;
+ }
+ // calculate yawmins/yawmaxs, rotatedmins/maxs from normalmins/maxs
+ // (fast but less accurate than doing it per vertex)
+ x2a = mod->normalmins[0] * mod->normalmins[0];
+ x2b = mod->normalmaxs[0] * mod->normalmaxs[0];
+ y2a = mod->normalmins[1] * mod->normalmins[1];
+ y2b = mod->normalmaxs[1] * mod->normalmaxs[1];
+ z2a = mod->normalmins[2] * mod->normalmins[2];
+ z2b = mod->normalmaxs[2] * mod->normalmaxs[2];
+ x2 = max(x2a, x2b);
+ y2 = max(y2a, y2b);
+ z2 = max(z2a, z2b);
+ yawradius = sqrt(x2 + y2);
+ rotatedradius = sqrt(x2 + y2 + z2);
+ VectorSet(mod->yawmins, -yawradius, -yawradius, mod->normalmins[2]);
+ VectorSet(mod->yawmaxs, yawradius, yawradius, mod->normalmaxs[2]);
+ VectorSet(mod->rotatedmins, -rotatedradius, -rotatedradius, -rotatedradius);
+ VectorSet(mod->rotatedmaxs, rotatedradius, rotatedradius, rotatedradius);
+ mod->radius = rotatedradius;
+ mod->radius2 = x2 + y2 + z2;
+ }
+ else
+ {
+ VectorClear(mod->normalmins);
+ VectorClear(mod->normalmaxs);
+ VectorClear(mod->yawmins);
+ VectorClear(mod->yawmaxs);
+ VectorClear(mod->rotatedmins);
+ VectorClear(mod->rotatedmaxs);
+ mod->radius = 0;
+ mod->radius2 = 0;
+ }
+}
+
+void Mod_Mesh_Validate(model_t *mod)
+{
+ int i;
+ qbool warned = false;
+ for (i = 0; i < mod->num_surfaces; i++)
+ {
+ msurface_t *surf = mod->data_surfaces + i;
+ int *e = mod->surfmesh.data_element3i + surf->num_firsttriangle * 3;
+ int first = surf->num_firstvertex;
+ int end = surf->num_firstvertex + surf->num_vertices;
+ int j;
+ for (j = 0;j < surf->num_triangles * 3;j++)
+ {
+ if (e[j] < first || e[j] >= end)
+ {
+ if (!warned)
+ Con_DPrintf("Mod_Mesh_Validate: detected corrupt surface - debug me!\n");
+ warned = true;
+ e[j] = first;
+ }
+ }
+ }
+}
+
+static void Mod_Mesh_UploadDynamicBuffers(model_t *mod)
+{
+ mod->surfmesh.data_element3s_indexbuffer = mod->surfmesh.data_element3s ? R_BufferData_Store(mod->surfmesh.num_triangles * sizeof(short[3]), mod->surfmesh.data_element3s, R_BUFFERDATA_INDEX16, &mod->surfmesh.data_element3s_bufferoffset) : NULL;
+ mod->surfmesh.data_element3i_indexbuffer = mod->surfmesh.data_element3i ? R_BufferData_Store(mod->surfmesh.num_triangles * sizeof(int[3]), mod->surfmesh.data_element3i, R_BUFFERDATA_INDEX32, &mod->surfmesh.data_element3i_bufferoffset) : NULL;
+ mod->surfmesh.data_vertex3f_vertexbuffer = mod->surfmesh.data_vertex3f ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(float[3]), mod->surfmesh.data_vertex3f, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_vertex3f_bufferoffset) : NULL;
+ mod->surfmesh.data_svector3f_vertexbuffer = mod->surfmesh.data_svector3f ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(float[3]), mod->surfmesh.data_svector3f, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_svector3f_bufferoffset) : NULL;
+ mod->surfmesh.data_tvector3f_vertexbuffer = mod->surfmesh.data_tvector3f ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(float[3]), mod->surfmesh.data_tvector3f, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_tvector3f_bufferoffset) : NULL;
+ mod->surfmesh.data_normal3f_vertexbuffer = mod->surfmesh.data_normal3f ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(float[3]), mod->surfmesh.data_normal3f, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_normal3f_bufferoffset) : NULL;
+ mod->surfmesh.data_texcoordtexture2f_vertexbuffer = mod->surfmesh.data_texcoordtexture2f ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(float[2]), mod->surfmesh.data_texcoordtexture2f, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_texcoordtexture2f_bufferoffset) : NULL;
+ mod->surfmesh.data_texcoordlightmap2f_vertexbuffer = mod->surfmesh.data_texcoordlightmap2f ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(float[2]), mod->surfmesh.data_texcoordlightmap2f, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_texcoordlightmap2f_bufferoffset) : NULL;
+ mod->surfmesh.data_lightmapcolor4f_vertexbuffer = mod->surfmesh.data_lightmapcolor4f ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(float[4]), mod->surfmesh.data_lightmapcolor4f, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_lightmapcolor4f_bufferoffset) : NULL;
+ mod->surfmesh.data_skeletalindex4ub_vertexbuffer = mod->surfmesh.data_skeletalindex4ub ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(unsigned char[4]), mod->surfmesh.data_skeletalindex4ub, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_skeletalindex4ub_bufferoffset) : NULL;
+ mod->surfmesh.data_skeletalweight4ub_vertexbuffer = mod->surfmesh.data_skeletalweight4ub ? R_BufferData_Store(mod->surfmesh.num_vertices * sizeof(unsigned char[4]), mod->surfmesh.data_skeletalweight4ub, R_BUFFERDATA_VERTEX, &mod->surfmesh.data_skeletalweight4ub_bufferoffset) : NULL;
+}
+
+void Mod_Mesh_Finalize(model_t *mod)
+{
+ if (gl_paranoid.integer)
+ Mod_Mesh_Validate(mod);
+ Mod_Mesh_ComputeBounds(mod);
+ Mod_Mesh_MakeSortedSurfaces(mod);
+ if(!r_refdef.draw2dstage)
+ Mod_BuildTextureVectorsFromNormals(0, mod->surfmesh.num_vertices, mod->surfmesh.num_triangles, mod->surfmesh.data_vertex3f, mod->surfmesh.data_texcoordtexture2f, mod->surfmesh.data_normal3f, mod->surfmesh.data_element3i, mod->surfmesh.data_svector3f, mod->surfmesh.data_tvector3f, true);
+ Mod_Mesh_UploadDynamicBuffers(mod);
+}