/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #include #include static const vec3_t bsp_origin = { 0, 0, 0 }; /* ======================================================================== */ typedef struct { vec3_t data[3]; /* permuted 3x3 matrix */ int row[3]; /* row permutations */ int col[3]; /* column permutations */ } pmatrix3_t; /* * To do arbitrary transformation of texture coordinates to world * coordinates requires solving for three simultaneous equations. We * set up the LU decomposed form of the transform matrix here. */ #define ZERO_EPSILON (0.001) static qboolean PMatrix3_LU_Decompose(pmatrix3_t *matrix) { int i, j, k, tmp; vec_t max; int max_r, max_c; /* Do gauss elimination */ for (i = 0; i < 3; ++i) { max = 0; max_r = max_c = i; for (j = i; j < 3; ++j) { for (k = i; k < 3; ++k) { if (fabs(matrix->data[j][k]) > max) { max = fabs(matrix->data[j][k]); max_r = j; max_c = k; } } } /* Check for parallel planes */ if (max < ZERO_EPSILON) return false; /* Swap rows/columns if necessary */ if (max_r != i) { for (j = 0; j < 3; ++j) { max = matrix->data[i][j]; matrix->data[i][j] = matrix->data[max_r][j]; matrix->data[max_r][j] = max; } tmp = matrix->row[i]; matrix->row[i] = matrix->row[max_r]; matrix->row[max_r] = tmp; } if (max_c != i) { for (j = 0; j < 3; ++j) { max = matrix->data[j][i]; matrix->data[j][i] = matrix->data[j][max_c]; matrix->data[j][max_c] = max; } tmp = matrix->col[i]; matrix->col[i] = matrix->col[max_c]; matrix->col[max_c] = tmp; } /* Do pivot */ for (j = i + 1; j < 3; ++j) { matrix->data[j][i] /= matrix->data[i][i]; for (k = i + 1; k < 3; ++k) matrix->data[j][k] -= matrix->data[j][i] * matrix->data[i][k]; } } return true; } static void Solve3(const pmatrix3_t *matrix, const vec3_t rhs, vec3_t out) { /* Use local short names just for readability (should optimize away) */ const vec3_t *data = matrix->data; const int *r = matrix->row; const int *c = matrix->col; vec3_t tmp; /* forward-substitution */ tmp[0] = rhs[r[0]]; tmp[1] = rhs[r[1]] - data[1][0] * tmp[0]; tmp[2] = rhs[r[2]] - data[2][0] * tmp[0] - data[2][1] * tmp[1]; /* back-substitution */ out[c[2]] = tmp[2] / data[2][2]; out[c[1]] = (tmp[1] - data[1][2] * out[c[2]]) / data[1][1]; out[c[0]] = (tmp[0] - data[0][1] * out[c[1]] - data[0][2] * out[c[2]]) / data[0][0]; } /* * ============================================================================ * SAMPLE POINT DETERMINATION * void SetupBlock (bsp2_dface_t *f) Returns with surfpt[] set * * This is a little tricky because the lightmap covers more area than the face. * If done in the straightforward fashion, some of the sample points will be * inside walls or on the other side of walls, causing false shadows and light * bleeds. * * To solve this, I only consider a sample point valid if a line can be drawn * between it and the exact midpoint of the face. If invalid, it is adjusted * towards the center until it is valid. * * FIXME: This doesn't completely work; I think what we really want is to move * the light point to the nearst sample point that is on the polygon; * ============================================================================ */ typedef struct { pmatrix3_t transform; const texinfo_t *texinfo; vec_t planedist; } texorg_t; typedef struct { vec3_t normal; vec_t dist; } plane_t; /* Allow space for 4x4 oversampling */ #define SINGLEMAP (18*18*4*4) typedef struct { const modelinfo_t *modelinfo; plane_t plane; int texmins[2]; int texsize[2]; vec_t exactmid[2]; int numpoints; vec3_t points[SINGLEMAP]; /* raw ambient occlusion amount per sample point, 0-1, where 1 is fully occluded. dirtgain/dirtscale are not applied yet */ vec_t occlusion[SINGLEMAP]; } lightsurf_t; typedef struct { int style; lightsample_t samples[SINGLEMAP]; } lightmap_t; /* * Functions to aid in calculation of polygon centroid */ static void TriCentroid(const dvertex_t *v0, const dvertex_t *v1, const dvertex_t *v2, vec3_t out) { int i; for (i = 0; i < 3; i++) out[i] = (v0->point[i] + v1->point[i] + v2->point[i]) / 3.0; } static vec_t TriArea(const dvertex_t *v0, const dvertex_t *v1, const dvertex_t *v2) { int i; vec3_t edge0, edge1, cross; for (i =0; i < 3; i++) { edge0[i] = v1->point[i] - v0->point[i]; edge1[i] = v2->point[i] - v0->point[i]; } CrossProduct(edge0, edge1, cross); return VectorLength(cross) * 0.5; } static vec_t FaceArea(const bsp2_dface_t *face, const bsp2_t *bsp) { int i, edgenum; dvertex_t *v0, *v1, *v2; vec_t poly_area = 0; edgenum = bsp->dsurfedges[face->firstedge]; if (edgenum >= 0) v0 = bsp->dvertexes + bsp->dedges[edgenum].v[0]; else v0 = bsp->dvertexes + bsp->dedges[-edgenum].v[1]; for (i = 1; i < face->numedges - 1; i++) { edgenum = bsp->dsurfedges[face->firstedge + i]; if (edgenum >= 0) { v1 = bsp->dvertexes + bsp->dedges[edgenum].v[0]; v2 = bsp->dvertexes + bsp->dedges[edgenum].v[1]; } else { v1 = bsp->dvertexes + bsp->dedges[-edgenum].v[1]; v2 = bsp->dvertexes + bsp->dedges[-edgenum].v[0]; } poly_area += TriArea(v0, v1, v2); } return poly_area; } static void FaceCentroid(const bsp2_dface_t *face, const bsp2_t *bsp, vec3_t out) { int i, edgenum; dvertex_t *v0, *v1, *v2; vec3_t centroid, poly_centroid; vec_t area, poly_area; VectorCopy(vec3_origin, poly_centroid); poly_area = 0; edgenum = bsp->dsurfedges[face->firstedge]; if (edgenum >= 0) v0 = bsp->dvertexes + bsp->dedges[edgenum].v[0]; else v0 = bsp->dvertexes + bsp->dedges[-edgenum].v[1]; for (i = 1; i < face->numedges - 1; i++) { edgenum = bsp->dsurfedges[face->firstedge + i]; if (edgenum >= 0) { v1 = bsp->dvertexes + bsp->dedges[edgenum].v[0]; v2 = bsp->dvertexes + bsp->dedges[edgenum].v[1]; } else { v1 = bsp->dvertexes + bsp->dedges[-edgenum].v[1]; v2 = bsp->dvertexes + bsp->dedges[-edgenum].v[0]; } area = TriArea(v0, v1, v2); poly_area += area; TriCentroid(v0, v1, v2, centroid); VectorMA(poly_centroid, area, centroid, poly_centroid); } VectorScale(poly_centroid, 1.0 / poly_area, out); } /* * ================ * CreateFaceTransform * Fills in the transform matrix for converting tex coord <-> world coord * ================ */ static void CreateFaceTransform(const bsp2_dface_t *face, const bsp2_t *bsp, pmatrix3_t *transform) { const dplane_t *plane; const texinfo_t *tex; int i; /* Prepare the transform matrix and init row/column permutations */ plane = &bsp->dplanes[face->planenum]; tex = &bsp->texinfo[face->texinfo]; for (i = 0; i < 3; i++) { transform->data[0][i] = tex->vecs[0][i]; transform->data[1][i] = tex->vecs[1][i]; transform->data[2][i] = plane->normal[i]; transform->row[i] = transform->col[i] = i; } if (face->side) VectorSubtract(vec3_origin, transform->data[2], transform->data[2]); /* Decompose the matrix. If we can't, texture axes are invalid. */ if (!PMatrix3_LU_Decompose(transform)) { const vec_t *p = bsp->dvertexes[bsp->dedges[face->firstedge].v[0]].point; Error("Bad texture axes on face:\n" " face point at (%s)\n" " face area = %5.3f\n", VecStr(p), FaceArea(face, bsp)); } } static void TexCoordToWorld(vec_t s, vec_t t, const texorg_t *texorg, vec3_t world) { vec3_t rhs; rhs[0] = s - texorg->texinfo->vecs[0][3]; rhs[1] = t - texorg->texinfo->vecs[1][3]; rhs[2] = texorg->planedist + 1; /* one "unit" in front of surface */ Solve3(&texorg->transform, rhs, world); } static void WorldToTexCoord(const vec3_t world, const texinfo_t *tex, vec_t coord[2]) { int i; /* * The (long double) casts below are important: The original code * was written for x87 floating-point which uses 80-bit floats for * intermediate calculations. But if you compile it without the * casts for modern x86_64, the compiler will round each * intermediate result to a 32-bit float, which introduces extra * rounding error. * * This becomes a problem if the rounding error causes the light * utilities and the engine to disagree about the lightmap size * for some surfaces. * * Casting to (long double) keeps the intermediate values at at * least 64 bits of precision, probably 128. */ for (i = 0; i < 2; i++) coord[i] = (long double)world[0] * tex->vecs[i][0] + (long double)world[1] * tex->vecs[i][1] + (long double)world[2] * tex->vecs[i][2] + tex->vecs[i][3]; } #if 0 /* Debug helper - move elsewhere? */ static void PrintFaceInfo(const bsp2_dface_t *face, const bsp2_t *bsp) { const texinfo_t *tex = &bsp->texinfo[face->texinfo]; const int offset = bsp->dtexdata.header->dataofs[tex->miptex]; const miptex_t *miptex = (const miptex_t *)(bsp->dtexdata.base + offset); int i; logprint("face %d, texture %s, %d edges...\n" " vectors (%3.3f, %3.3f, %3.3f) (%3.3f)\n" " (%3.3f, %3.3f, %3.3f) (%3.3f)\n", (int)(face - bsp->dfaces), miptex->name, face->numedges, tex->vecs[0][0], tex->vecs[0][1], tex->vecs[0][2], tex->vecs[0][3], tex->vecs[1][0], tex->vecs[1][1], tex->vecs[1][2], tex->vecs[1][3]); for (i = 0; i < face->numedges; i++) { int edge = bsp->dsurfedges[face->firstedge + i]; int vert = (edge >= 0) ? bsp->dedges[edge].v[0] : bsp->dedges[-edge].v[1]; const float *point = bsp->dvertexes[vert].point; logprint("%s %3d (%3.3f, %3.3f, %3.3f) :: edge %d\n", i ? " " : " verts ", vert, point[0], point[1], point[2], edge); } } #endif /* * ================ * CalcFaceExtents * Fills in surf->texmins[], surf->texsize[] and sets surf->exactmid[] * ================ */ __attribute__((noinline)) static void CalcFaceExtents(const bsp2_dface_t *face, const vec3_t offset, const bsp2_t *bsp, lightsurf_t *surf) { vec_t mins[2], maxs[2], texcoord[2]; vec3_t worldpoint; int i, j, edge, vert; const dvertex_t *dvertex; const texinfo_t *tex; mins[0] = mins[1] = VECT_MAX; maxs[0] = maxs[1] = -VECT_MAX; tex = &bsp->texinfo[face->texinfo]; for (i = 0; i < face->numedges; i++) { edge = bsp->dsurfedges[face->firstedge + i]; vert = (edge >= 0) ? bsp->dedges[edge].v[0] : bsp->dedges[-edge].v[1]; dvertex = &bsp->dvertexes[vert]; VectorAdd(dvertex->point, offset, worldpoint); WorldToTexCoord(worldpoint, tex, texcoord); for (j = 0; j < 2; j++) { if (texcoord[j] < mins[j]) mins[j] = texcoord[j]; if (texcoord[j] > maxs[j]) maxs[j] = texcoord[j]; } } FaceCentroid(face, bsp, worldpoint); VectorAdd(worldpoint, offset, worldpoint); WorldToTexCoord(worldpoint, tex, surf->exactmid); for (i = 0; i < 2; i++) { mins[i] = floor(mins[i] / 16); maxs[i] = ceil(maxs[i] / 16); surf->texmins[i] = mins[i]; surf->texsize[i] = maxs[i] - mins[i]; if (surf->texsize[i] > 17) { const dplane_t *plane = bsp->dplanes + face->planenum; const int offset = bsp->dtexdata.header->dataofs[tex->miptex]; const miptex_t *miptex = (const miptex_t *)(bsp->dtexdata.base + offset); Error("Bad surface extents:\n" " surface %d, %s extents = %d\n" " texture %s at (%s)\n" " surface normal (%s)\n", (int)(face - bsp->dfaces), i ? "t" : "s", surf->texsize[i], miptex->name, VecStr(worldpoint), VecStrf(plane->normal)); } } } /* * Print warning for CalcPoint where the midpoint of a polygon, one * unit above the surface is covered by a solid brush. */ static void WarnBadMidpoint(const vec3_t point) { #if 0 static qboolean warned = false; if (warned) return; warned = true; logprint("WARNING: unable to lightmap surface near (%s)\n" " This is usually caused by an unintentional tiny gap between\n" " two solid brushes which doesn't leave enough room for the\n" " lightmap to fit (one world unit). Further instances of this\n" " warning during this compile will be supressed.\n", VecStr(point)); #endif } /* * ================= * CalcPoints * For each texture aligned grid point, back project onto the plane * to get the world xyz value of the sample point * ================= */ __attribute__((noinline)) static void CalcPoints(const dmodel_t *model, const texorg_t *texorg, lightsurf_t *surf) { int i; int s, t; int width, height, step; vec_t starts, startt, us, ut; vec_t *point; vec3_t midpoint, move; /* * Fill in the surface points. The points are biased towards the center of * the surface to help avoid edge cases just inside walls */ TexCoordToWorld(surf->exactmid[0], surf->exactmid[1], texorg, midpoint); width = (surf->texsize[0] + 1) * oversample; height = (surf->texsize[1] + 1) * oversample; starts = (surf->texmins[0] - 0.5 + (0.5 / oversample)) * 16; startt = (surf->texmins[1] - 0.5 + (0.5 / oversample)) * 16; step = 16 / oversample; point = surf->points[0]; surf->numpoints = width * height; for (t = 0; t < height; t++) { for (s = 0; s < width; s++, point += 3) { us = starts + s * step; ut = startt + t * step; TexCoordToWorld(us, ut, texorg, point); for (i = 0; i < 6; i++) { const int flags = TRACE_HIT_SOLID; tracepoint_t hit; int result; vec_t dist; result = TraceLine(model, flags, midpoint, point, &hit); if (result == TRACE_HIT_NONE) break; if (result != TRACE_HIT_SOLID) { WarnBadMidpoint(midpoint); break; } /* Move the point 1 unit above the obstructing surface */ dist = DotProduct(point, hit.dplane->normal) - hit.dplane->dist; dist = hit.side ? -dist - 1 : -dist + 1; VectorScale(hit.dplane->normal, dist, move); VectorAdd(point, move, point); } } } } __attribute__((noinline)) static void Lightsurf_Init(const modelinfo_t *modelinfo, const bsp2_dface_t *face, const bsp2_t *bsp, lightsurf_t *lightsurf) { plane_t *plane; vec3_t planepoint; texorg_t texorg; memset(lightsurf, 0, sizeof(*lightsurf)); lightsurf->modelinfo = modelinfo; /* Set up the plane, including model offset */ plane = &lightsurf->plane; VectorCopy(bsp->dplanes[face->planenum].normal, plane->normal); plane->dist = bsp->dplanes[face->planenum].dist; VectorScale(plane->normal, plane->dist, planepoint); VectorAdd(planepoint, modelinfo->offset, planepoint); plane->dist = DotProduct(plane->normal, planepoint); if (face->side) { VectorSubtract(vec3_origin, plane->normal, plane->normal); plane->dist = -plane->dist; } /* Set up the texorg for coordinate transformation */ CreateFaceTransform(face, bsp, &texorg.transform); texorg.texinfo = &bsp->texinfo[face->texinfo]; texorg.planedist = plane->dist; /* Set up the surface points */ CalcFaceExtents(face, modelinfo->offset, bsp, lightsurf); CalcPoints(modelinfo->model, &texorg, lightsurf); } static void Lightmaps_Init(lightmap_t *lightmaps, const int count) { int i; memset(lightmaps, 0, sizeof(lightmap_t) * count); for (i = 0; i < count; i++) lightmaps[i].style = 255; } /* * Lightmap_ForStyle * * If lightmap with given style has already been allocated, return it. * Otherwise, return the next available map. A new map is not marked as * allocated since it may not be kept if no lights hit. */ static lightmap_t * Lightmap_ForStyle(lightmap_t *lightmaps, const int style) { lightmap_t *lightmap = lightmaps; int i; for (i = 0; i < MAXLIGHTMAPS; i++, lightmap++) { if (lightmap->style == style) return lightmap; if (lightmap->style == 255) break; } memset(lightmap, 0, sizeof(*lightmap)); lightmap->style = 255; return lightmap; } /* * Lightmap_Save * * As long as we have space for the style, mark as allocated, * otherwise emit a warning. */ static void Lightmap_Save(lightmap_t *lightmaps, const lightsurf_t *lightsurf, lightmap_t *lightmap, const int style) { if (lightmap - lightmaps < MAXLIGHTMAPS) { if (lightmap->style == 255) lightmap->style = style; return; } logprint("WARNING: Too many light styles on a face\n" " lightmap point near (%s)\n", VecStr(lightsurf->points[0])); } /* * Average adjacent points on the grid to soften shadow edges */ __attribute__((noinline)) static void Lightmap_Soften(lightmap_t *lightmap, const lightsurf_t *lightsurf) { int i, samples; int s, t, starts, startt, ends, endt; const lightsample_t *src; lightsample_t *dst; lightmap_t softmap; const int width = (lightsurf->texsize[0] + 1) * oversample; const int height = (lightsurf->texsize[1] + 1) * oversample; const int fullsamples = (2 * softsamples + 1) * (2 * softsamples + 1); memset(&softmap, 0, sizeof(softmap)); dst = softmap.samples; for (i = 0; i < lightsurf->numpoints; i++, dst++) { startt = qmax((i / width) - softsamples, 0); endt = qmin((i / width) + softsamples + 1, height); starts = qmax((i % width) - softsamples, 0); ends = qmin((i % width) + softsamples + 1, width); for (t = startt; t < endt; t++) { src = &lightmap->samples[t * width + starts]; for (s = starts; s < ends; s++) { dst->light += src->light; VectorAdd(dst->color, src->color, dst->color); src++; } } /* * For cases where we are softening near the edge of the lightmap, * take extra samples from the centre point (follows old bjp tools * behaviour) */ samples = (endt - startt) * (ends - starts); if (samples < fullsamples) { const int extraweight = 2 * (fullsamples - samples); src = &lightmap->samples[i]; dst->light += src->light * extraweight; VectorMA(dst->color, extraweight, src->color, dst->color); samples += extraweight; } dst->light /= samples; VectorScale(dst->color, 1.0 / samples, dst->color); } softmap.style = lightmap->style; memcpy(lightmap, &softmap, sizeof(softmap)); } /* * ============================================================================ * FACE LIGHTING * ============================================================================ */ static vec_t GetLightValue(const lightsample_t *light, const entity_t *entity, vec_t dist) { vec_t value; if (entity->formula == LF_INFINITE || entity->formula == LF_LOCALMIN) return light->light; value = scaledist * entity->atten * dist; switch (entity->formula) { case LF_INVERSE: return light->light / (value / LF_SCALE); case LF_INVERSE2A: value += LF_SCALE; /* Fall through */ case LF_INVERSE2: return light->light / ((value * value) / (LF_SCALE * LF_SCALE)); case LF_LINEAR: if (light->light > 0) return (light->light - value > 0) ? light->light - value : 0; else return (light->light + value < 0) ? light->light + value : 0; default: Error("Internal error: unknown light formula"); } } static inline void Light_Add(lightsample_t *sample, const vec_t light, const vec3_t color) { sample->light += light; VectorMA(sample->color, light / 255.0f, color, sample->color); } static inline void Light_ClampMin(lightsample_t *sample, const vec_t light, const vec3_t color) { int i; if (sample->light < light) { sample->light = light; for (i = 0; i < 3; i++) if (sample->color[i] < color[i] * light / 255.0f) sample->color[i] = color[i] * light / 255.0f; } } /* * ============ * Dirt_GetScaleFactor * * returns scale factor for dirt/ambient occlusion * ============ */ static inline vec_t Dirt_GetScaleFactor(vec_t occlusion, const entity_t *entity) { vec_t light_dirtgain = dirtGain; vec_t light_dirtscale = dirtScale; vec_t outDirt; qboolean usedirt; /* is dirt processing disabled entirely? */ if (!dirty) return 1.0f; /* should this light be affected by dirt? */ if (entity) { if (entity->dirt == -1) { usedirt = false; } else if (entity->dirt == 1) { usedirt = true; } else { usedirt = globalDirt; } } else { /* no entity is provided, assume the caller wants dirt */ usedirt = true; } /* if not, quit */ if (!usedirt) return 1.0; /* override the global scale and gain values with the light-specific values, if present */ if (entity) { if (entity->dirtgain) light_dirtgain = entity->dirtgain; if (entity->dirtscale) light_dirtscale = entity->dirtscale; } /* early out */ if ( occlusion <= 0.0f ) { return 1.0f; } /* apply gain (does this even do much? heh) */ outDirt = pow( occlusion, light_dirtgain ); if ( outDirt > 1.0f ) { outDirt = 1.0f; } /* apply scale */ outDirt *= light_dirtscale; if ( outDirt > 1.0f ) { outDirt = 1.0f; } /* return to sender */ return 1.0f - outDirt; } /* * ================ * LightFace_Entity * ================ */ static void LightFace_Entity(const entity_t *entity, const lightsample_t *light, const lightsurf_t *lightsurf, lightmap_t *lightmaps) { const modelinfo_t *modelinfo = lightsurf->modelinfo; const plane_t *plane = &lightsurf->plane; const dmodel_t *shadowself; const vec_t *surfpoint; int i; qboolean hit; vec_t dist, add, angle, spotscale; lightsample_t *sample; lightmap_t *lightmap; dist = DotProduct(entity->origin, plane->normal) - plane->dist; /* don't bother with lights behind the surface */ if (dist < 0) return; /* don't bother with light too far away */ if (dist > entity->fadedist) return; /* * Check it for real */ hit = false; lightmap = Lightmap_ForStyle(lightmaps, entity->style); shadowself = modelinfo->shadowself ? modelinfo->model : NULL; sample = lightmap->samples; surfpoint = lightsurf->points[0]; for (i = 0; i < lightsurf->numpoints; i++, sample++, surfpoint += 3) { vec3_t ray; VectorSubtract(entity->origin, surfpoint, ray); dist = VectorLength(ray); /* Quick distance check first */ if (dist > entity->fadedist) continue; /* Check spotlight cone */ VectorScale(ray, 1.0 / dist, ray); angle = DotProduct(ray, plane->normal); spotscale = 1; if (entity->spotlight) { vec_t falloff = DotProduct(entity->spotvec, ray); if (falloff > entity->spotfalloff) continue; if (falloff > entity->spotfalloff2) { /* Interpolate between the two spotlight falloffs */ spotscale = falloff - entity->spotfalloff2; spotscale /= entity->spotfalloff - entity->spotfalloff2; spotscale = 1.0 - spotscale; } } if (!TestLight(entity->origin, surfpoint, shadowself)) continue; angle = (1.0 - entity->anglescale) + entity->anglescale * angle; add = GetLightValue(light, entity, dist) * angle * spotscale; add *= Dirt_GetScaleFactor(lightsurf->occlusion[i], entity); Light_Add(sample, add, light->color); /* Check if we really hit, ignore tiny lights */ /* ericw -- don't ignore tiny lights if the light was split up for a penumbra; the small light values are important in that case */ if (!hit && (sample->light >= 1 || entity->num_samples > 1)) hit = true; } if (hit) Lightmap_Save(lightmaps, lightsurf, lightmap, entity->style); } /* * ============= * LightFace_Sky * ============= */ static void LightFace_Sky(const sun_t *sun, const lightsurf_t *lightsurf, lightmap_t *lightmaps) { const modelinfo_t *modelinfo = lightsurf->modelinfo; const plane_t *plane = &lightsurf->plane; const dmodel_t *shadowself; const vec_t *surfpoint; int i; qboolean hit; vec3_t incoming; vec_t angle; lightsample_t *sample; lightmap_t *lightmap; /* Don't bother if surface facing away from sun */ if (DotProduct(sun->sunvec, plane->normal) < -ANGLE_EPSILON) return; /* if sunlight is set, use a style 0 light map */ lightmap = Lightmap_ForStyle(lightmaps, 0); VectorCopy(sun->sunvec, incoming); VectorNormalize(incoming); angle = DotProduct(incoming, plane->normal); angle = (1.0 - sun->anglescale) + sun->anglescale * angle; /* Check each point... */ hit = false; shadowself = modelinfo->shadowself ? modelinfo->model : NULL; sample = lightmap->samples; surfpoint = lightsurf->points[0]; for (i = 0; i < lightsurf->numpoints; i++, sample++, surfpoint += 3) { vec_t value; if (!TestSky(surfpoint, sun->sunvec, shadowself)) continue; value = angle * sun->sunlight.light; if (sun->dirt) value *= Dirt_GetScaleFactor(lightsurf->occlusion[i], NULL); Light_Add(sample, value, sun->sunlight.color); if (!hit/* && (sample->light >= 1)*/) hit = true; } if (hit) Lightmap_Save(lightmaps, lightsurf, lightmap, 0); } /* * ============ * LightFace_Min * ============ */ static void LightFace_Min(const lightsample_t *light, const lightsurf_t *lightsurf, lightmap_t *lightmaps) { const modelinfo_t *modelinfo = lightsurf->modelinfo; const dmodel_t *shadowself; const entity_t *entity; const vec_t *surfpoint; qboolean hit, trace; int i, j; lightsample_t *sample; lightmap_t *lightmap; /* Find a style 0 lightmap */ lightmap = Lightmap_ForStyle(lightmaps, 0); hit = false; sample = lightmap->samples; for (i = 0; i < lightsurf->numpoints; i++, sample++) { vec_t value = light->light; if (minlightDirt) value *= Dirt_GetScaleFactor(lightsurf->occlusion[i], NULL); if (addminlight) Light_Add(sample, value, light->color); else Light_ClampMin(sample, value, light->color); if (!hit && sample->light >= 1) hit = true; } /* Cast rays for local minlight entities */ shadowself = modelinfo->shadowself ? modelinfo->model : NULL; for (entity = entities; entity; entity = entity->next) { if (entity->formula != LF_LOCALMIN) continue; sample = lightmap->samples; surfpoint = lightsurf->points[0]; for (j = 0; j < lightsurf->numpoints; j++, sample++, surfpoint += 3) { if (addminlight || sample->light < entity->light.light) { vec_t value = entity->light.light; trace = TestLight(entity->origin, surfpoint, shadowself); if (!trace) continue; value *= Dirt_GetScaleFactor(lightsurf->occlusion[j], entity); if (addminlight) Light_Add(sample, value, entity->light.color); else Light_ClampMin(sample, value, entity->light.color); } if (!hit && sample->light >= 1) hit = true; } } if (hit) Lightmap_Save(lightmaps, lightsurf, lightmap, 0); } /* * ============= * LightFace_DirtDebug * ============= */ static void LightFace_DirtDebug(const lightsurf_t *lightsurf, lightmap_t *lightmaps) { int i; lightsample_t *sample; lightmap_t *lightmap; /* use a style 0 light map */ lightmap = Lightmap_ForStyle(lightmaps, 0); /* Overwrite each point with the dirt value for that sample... */ sample = lightmap->samples; for (i = 0; i < lightsurf->numpoints; i++, sample++) { sample->light = 255 * Dirt_GetScaleFactor(lightsurf->occlusion[i], NULL); VectorSet(sample->color, sample->light, sample->light, sample->light); } Lightmap_Save(lightmaps, lightsurf, lightmap, 0); } /* Dirtmapping borrowed from q3map2, originally by RaP7oR */ #define DIRT_CONE_ANGLE 88 /* degrees */ #define DIRT_NUM_ANGLE_STEPS 16 #define DIRT_NUM_ELEVATION_STEPS 3 #define DIRT_NUM_VECTORS ( DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS ) static vec3_t dirtVectors[ DIRT_NUM_VECTORS ]; static int numDirtVectors = 0; /* * ============ * SetupDirt * * sets up dirtmap (ambient occlusion) * ============ */ void SetupDirt( void ) { int i, j; float angle, elevation, angleStep, elevationStep; /* note it */ logprint("--- SetupDirt ---\n" ); /* calculate angular steps */ angleStep = DEG2RAD( 360.0f / DIRT_NUM_ANGLE_STEPS ); elevationStep = DEG2RAD( DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS ); /* iterate angle */ angle = 0.0f; for ( i = 0, angle = 0.0f; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep ) { /* iterate elevation */ for ( j = 0, elevation = elevationStep * 0.5f; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) { dirtVectors[ numDirtVectors ][ 0 ] = sin( elevation ) * cos( angle ); dirtVectors[ numDirtVectors ][ 1 ] = sin( elevation ) * sin( angle ); dirtVectors[ numDirtVectors ][ 2 ] = cos( elevation ); numDirtVectors++; } } /* emit some statistics */ logprint("%9d dirtmap vectors\n", numDirtVectors ); } /* * ============ * DirtTrace * * returns true if the trace from start to stop hits something solid, * or if it started in the void. * ============ */ qboolean DirtTrace(const vec3_t start, const vec3_t stop, const dmodel_t *self, vec3_t hitpoint_out) { const dmodel_t *const *model; const int traceflags = TRACE_HIT_SOLID | TRACE_HIT_SKY; int result = TRACE_HIT_NONE; tracepoint_t hitpoint; if (self) { result = TraceLine(self, traceflags, start, stop, &hitpoint); if (result == -TRACE_HIT_SOLID) { /* We started in the void, which ideally wouldn't happen, but does (say on e1m1). Return the start point as the hitpoint, which will make fully black dirt. */ VectorCopy(start, hitpoint_out); return true; } else if (result == TRACE_HIT_SOLID) { VectorCopy(hitpoint.point, hitpoint_out); return true; } } /* Check against the list of global shadow casters */ for (model = tracelist; *model; model++) { result = TraceLine(*model, traceflags, start, stop, &hitpoint); if (result == -TRACE_HIT_SOLID) { VectorCopy(start, hitpoint_out); return true; } else if (result == TRACE_HIT_SOLID) { VectorCopy(hitpoint.point, hitpoint_out); return true; } } return false; } /* * ============ * DirtForSample * ============ */ static vec_t DirtForSample(const dmodel_t *model, const vec3_t origin, const vec3_t normal){ int i; float gatherDirt, angle, elevation, ooDepth; vec3_t worldUp, myUp, myRt, temp, direction, displacement; vec3_t traceEnd, traceHitpoint; /* dummy check */ if ( !dirty ) { return 1.0f; } /* setup */ gatherDirt = 0.0f; ooDepth = 1.0f / dirtDepth; /* check if the normal is aligned to the world-up */ if ( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f ) { if ( normal[ 2 ] == 1.0f ) { VectorSet( myRt, 1.0f, 0.0f, 0.0f ); VectorSet( myUp, 0.0f, 1.0f, 0.0f ); } else if ( normal[ 2 ] == -1.0f ) { VectorSet( myRt, -1.0f, 0.0f, 0.0f ); VectorSet( myUp, 0.0f, 1.0f, 0.0f ); } } else { VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); CrossProduct( normal, worldUp, myRt ); VectorNormalize( myRt ); CrossProduct( myRt, normal, myUp ); VectorNormalize( myUp ); } /* 1 = random mode, 0 (well everything else) = non-random mode */ if ( dirtMode == 1 ) { /* iterate */ for ( i = 0; i < numDirtVectors; i++ ) { /* get random vector */ angle = Random() * DEG2RAD( 360.0f ); elevation = Random() * DEG2RAD( DIRT_CONE_ANGLE ); temp[ 0 ] = cos( angle ) * sin( elevation ); temp[ 1 ] = sin( angle ) * sin( elevation ); temp[ 2 ] = cos( elevation ); /* transform into tangent space */ direction[ 0 ] = myRt[ 0 ] * temp[ 0 ] + myUp[ 0 ] * temp[ 1 ] + normal[ 0 ] * temp[ 2 ]; direction[ 1 ] = myRt[ 1 ] * temp[ 0 ] + myUp[ 1 ] * temp[ 1 ] + normal[ 1 ] * temp[ 2 ]; direction[ 2 ] = myRt[ 2 ] * temp[ 0 ] + myUp[ 2 ] * temp[ 1 ] + normal[ 2 ] * temp[ 2 ]; /* set endpoint */ VectorMA( origin, dirtDepth, direction, traceEnd ); /* trace */ if (DirtTrace(origin, traceEnd, model, traceHitpoint)) { VectorSubtract( traceHitpoint, origin, displacement ); gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); } } } else { /* iterate through ordered vectors */ for ( i = 0; i < numDirtVectors; i++ ) { /* transform vector into tangent space */ direction[ 0 ] = myRt[ 0 ] * dirtVectors[ i ][ 0 ] + myUp[ 0 ] * dirtVectors[ i ][ 1 ] + normal[ 0 ] * dirtVectors[ i ][ 2 ]; direction[ 1 ] = myRt[ 1 ] * dirtVectors[ i ][ 0 ] + myUp[ 1 ] * dirtVectors[ i ][ 1 ] + normal[ 1 ] * dirtVectors[ i ][ 2 ]; direction[ 2 ] = myRt[ 2 ] * dirtVectors[ i ][ 0 ] + myUp[ 2 ] * dirtVectors[ i ][ 1 ] + normal[ 2 ] * dirtVectors[ i ][ 2 ]; /* set endpoint */ VectorMA( origin, dirtDepth, direction, traceEnd ); /* trace */ if (DirtTrace(origin, traceEnd, model, traceHitpoint)) { VectorSubtract( traceHitpoint, origin, displacement ); gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); } } } /* direct ray */ VectorMA( origin, dirtDepth, normal, traceEnd ); /* trace */ if (DirtTrace(origin, traceEnd, model, traceHitpoint)) { VectorSubtract( traceHitpoint, origin, displacement ); gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); } /* save gatherDirt, the rest of the scaling of the dirt value is done per-light */ return gatherDirt / ( numDirtVectors + 1 ); } /* * ============ * LightFace_CalculateDirt * ============ */ static void LightFace_CalculateDirt(lightsurf_t *lightsurf) { const modelinfo_t *modelinfo = lightsurf->modelinfo; const plane_t *plane = &lightsurf->plane; const vec_t *surfpoint; int i; /* Check each point... */ surfpoint = lightsurf->points[0]; for (i = 0; i < lightsurf->numpoints; i++, surfpoint += 3) { lightsurf->occlusion[i] = DirtForSample(modelinfo->model, surfpoint, plane->normal); } } static void WriteLightmaps(bsp2_dface_t *face, const lightsurf_t *lightsurf, const lightmap_t *lightmaps) { int numstyles, size, mapnum, width, s, t, i, j; const lightsample_t *sample; vec_t light, maxcolor; vec3_t color; byte *out, *lit; numstyles = 0; for (mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) { face->styles[mapnum] = lightmaps[mapnum].style; if (lightmaps[mapnum].style != 255) numstyles++; } if (!numstyles) return; size = (lightsurf->texsize[0] + 1) * (lightsurf->texsize[1] + 1); GetFileSpace(&out, &lit, size * numstyles); face->lightofs = out - filebase; width = (lightsurf->texsize[0] + 1) * oversample; for (mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) { if (lightmaps[mapnum].style == 255) break; sample = lightmaps[mapnum].samples; for (t = 0; t <= lightsurf->texsize[1]; t++) { for (s = 0; s <= lightsurf->texsize[0]; s++) { /* Take the average of any oversampling */ light = 0; VectorCopy(vec3_origin, color); for (i = 0; i < oversample; i++) { for (j = 0; j < oversample; j++) { light += sample->light; VectorAdd(color, sample->color, color); sample++; } sample += width - oversample; } light /= oversample * oversample; VectorScale(color, 1.0 / oversample / oversample, color); /* Scale and clamp any out-of-range samples */ light *= rangescale; if (light > 255) light = 255; else if (light < 0) light = 0; *out++ = light; maxcolor = 0; VectorScale(color, rangescale, color); for (i = 0; i < 3; i++) if (color[i] > maxcolor) maxcolor = color[i]; if (maxcolor > 255) VectorScale(color, 255.0f / maxcolor, color); *lit++ = color[0]; *lit++ = color[1]; *lit++ = color[2]; sample -= width * oversample - oversample; } sample += width * oversample - width; } } } /* * ============ * LightFace * ============ */ void LightFace(bsp2_dface_t *face, const modelinfo_t *modelinfo, const bsp2_t *bsp) { int i, j, k; const entity_t *entity; lightsample_t *sample; lightsurf_t lightsurf; sun_t *sun; /* One extra lightmap is allocated to simplify handling overflow */ lightmap_t lightmaps[MAXLIGHTMAPS + 1]; /* some surfaces don't need lightmaps */ face->lightofs = -1; for (i = 0; i < MAXLIGHTMAPS; i++) face->styles[i] = 255; if (bsp->texinfo[face->texinfo].flags & TEX_SPECIAL) return; Lightsurf_Init(modelinfo, face, bsp, &lightsurf); Lightmaps_Init(lightmaps, MAXLIGHTMAPS + 1); /* calculate dirt (ambient occlusion) but don't use it yet */ if (dirty) LightFace_CalculateDirt(&lightsurf); /* * The lighting procedure is: cast all positive lights, fix * minlight levels, then cast all negative lights. Finally, we * clamp any values that may have gone negative. */ /* positive lights */ for (entity = entities; entity; entity = entity->next) { if (entity->formula == LF_LOCALMIN) continue; if (entity->light.light > 0) LightFace_Entity(entity, &entity->light, &lightsurf, lightmaps); } for ( sun = suns; sun; sun = sun->next ) if (sun->sunlight.light > 0) LightFace_Sky (sun, &lightsurf, lightmaps); /* minlight - Use the greater of global or model minlight. */ if (modelinfo->minlight.light > minlight.light) LightFace_Min(&modelinfo->minlight, &lightsurf, lightmaps); else LightFace_Min(&minlight, &lightsurf, lightmaps); /* negative lights */ for (entity = entities; entity; entity = entity->next) { if (entity->formula == LF_LOCALMIN) continue; if (entity->light.light < 0) LightFace_Entity(entity, &entity->light, &lightsurf, lightmaps); } for ( sun = suns; sun; sun = sun->next ) if (sun->sunlight.light < 0) LightFace_Sky (sun, &lightsurf, lightmaps); /* replace lightmaps with AO for debugging */ if (dirtDebug) LightFace_DirtDebug(&lightsurf, lightmaps); /* Fix any negative values */ for (i = 0; i < MAXLIGHTMAPS; i++) { if (lightmaps[i].style == 255) break; sample = lightmaps[i].samples; for (j = 0; j < lightsurf.numpoints; j++, sample++) { if (sample->light < 0) sample->light = 0; for (k = 0; k < 3; k++) { if (sample->color[k] < 0) { sample->color[k] = 0; } } } } /* Perform post-processing if requested */ if (softsamples > 0) { for (i = 0; i < MAXLIGHTMAPS; i++) { if (lightmaps[i].style == 255) break; Lightmap_Soften(&lightmaps[i], &lightsurf); } } WriteLightmaps(face, &lightsurf, lightmaps); }