ericw-tools/light/ltface.c

974 lines
26 KiB
C

/* 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 <light/light.h>
#include <light/entities.h>
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 (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];
} lightsurf_t;
typedef struct {
int style;
lightsample_t samples[SINGLEMAP];
} lightmap_t;
/*
* ================
* CreateFaceTransform
* Fills in the transform matrix for converting tex coord <-> world coord
* ================
*/
static void
CreateFaceTransform(const dface_t *face, pmatrix3_t *transform)
{
const dplane_t *plane;
const texinfo_t *tex;
int i;
/* Prepare the transform matrix and init row/column permutations */
plane = &dplanes[face->planenum];
tex = &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 = dvertexes[dedges[face->firstedge].v[0]].point;
Error("Bad texture axes on face:\n"
" face point at (%5.3f, %5.3f, %5.3f)\n", p[0], p[1], p[2]);
}
}
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;
for (i = 0; i < 2; i++)
coord[i] =
world[0] * tex->vecs[i][0] +
world[1] * tex->vecs[i][1] +
world[2] * tex->vecs[i][2] + tex->vecs[i][3];
}
/*
* 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 void
FaceCentroid(const dface_t *f, vec3_t out)
{
int i, e;
dvertex_t *v0, *v1, *v2;
vec3_t centroid, poly_centroid;
vec_t area, poly_area;
VectorCopy(vec3_origin, poly_centroid);
poly_area = 0;
e = dsurfedges[f->firstedge];
if (e >= 0)
v0 = dvertexes + dedges[e].v[0];
else
v0 = dvertexes + dedges[-e].v[1];
for (i = 1; i < f->numedges - 1; i++) {
e = dsurfedges[f->firstedge + i];
if (e >= 0) {
v1 = dvertexes + dedges[e].v[0];
v2 = dvertexes + dedges[e].v[1];
} else {
v1 = dvertexes + dedges[-e].v[1];
v2 = dvertexes + dedges[-e].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);
}
/*
* ================
* CalcFaceExtents
* Fills in surf->texmins[], surf->texsize[] and sets surf->exactmid[]
* ================
*/
__attribute__((noinline))
static void
CalcFaceExtents(const dface_t *face, const vec3_t offset, 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 = &texinfo[face->texinfo];
for (i = 0; i < face->numedges; i++) {
edge = dsurfedges[face->firstedge + i];
vert = (edge >= 0) ? dedges[edge].v[0] : dedges[-edge].v[1];
dvertex = &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, 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 = dplanes + face->planenum;
const int offset = dtexdata.header->dataofs[tex->miptex];
const miptex_t *miptex = (const miptex_t *)(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 - 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 dface_t *face,
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(dplanes[face->planenum].normal, plane->normal);
plane->dist = 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, &texorg.transform);
texorg.texinfo = &texinfo[face->texinfo];
texorg.planedist = plane->dist;
/* Set up the surface points */
CalcFaceExtents(face, modelinfo->offset, lightsurf);
CalcPoints(modelinfo->model, &texorg, lightsurf);
}
static void
Lightmaps_Init(lightmap_t *lightmaps)
{
int i;
memset(lightmaps, 0, sizeof(lightmap_t) * MAXLIGHTMAPS);
for (i = 0; i < MAXLIGHTMAPS; i++)
lightmaps[i].style = 255;
}
/*
* 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");
}
}
/*
* ================
* SingleLightFace
* ================
*/
static void
SingleLightFace(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;
vec_t dist;
vec_t angle, spotscale;
vec_t add;
qboolean newmap, hit;
int i, mapnum;
lightsample_t *sample;
lightmap_t newlightmap;
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;
/*
* Find the lightmap with matching style
*/
newmap = true;
for (mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) {
if (lightmaps[mapnum].style == 255)
break;
if (lightmaps[mapnum].style == entity->style) {
newmap = false;
sample = lightmaps[mapnum].samples;
break;
}
}
if (newmap) {
memset(&newlightmap, 0, sizeof(newlightmap));
newlightmap.style = entity->style;
sample = newlightmap.samples;
}
/*
* Check it for real
*/
hit = false;
shadowself = modelinfo->shadowself ? modelinfo->model : NULL;
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;
sample->light += add;
if (colored)
VectorMA(sample->color, add / 255.0f, light->color, sample->color);
/* Check if we really hit, ignore tiny lights */
if (newmap && sample->light >= 1)
hit = true;
}
if (newmap && hit) {
if (mapnum == MAXLIGHTMAPS) {
logprint("WARNING: Too many light styles on a face\n"
" lightmap point near (%s)\n"
" entity->origin (%s)\n",
VecStr(lightsurf->points[0]), VecStr(entity->origin));
return;
}
/* the new lightmap has some real data now */
memcpy(&lightmaps[mapnum], &newlightmap, sizeof(newlightmap));
}
}
/*
* =============
* SkyLightFace
* =============
*/
static void
SkyLightFace(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, mapnum;
vec3_t incoming;
vec_t angle;
lightsample_t *sample;
/* Don't bother if surface facing away from sun */
if (DotProduct(sunvec, plane->normal) < -ANGLE_EPSILON)
return;
/* if sunlight is set, use a style 0 light map */
for (mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) {
if (lightmaps[mapnum].style == 0)
break;
if (lightmaps[mapnum].style == 255) {
lightmaps[mapnum].style = 0;
break;
}
}
if (mapnum == MAXLIGHTMAPS)
return; /* oh well, too many lightmaps... */
VectorCopy(sunvec, incoming);
VectorNormalize(incoming);
angle = DotProduct(incoming, plane->normal);
angle = (1.0 - sun_anglescale) + sun_anglescale * angle;
/* Check each point... */
shadowself = modelinfo->shadowself ? modelinfo->model : NULL;
sample = lightmaps[mapnum].samples;
surfpoint = lightsurf->points[0];
for (i = 0; i < lightsurf->numpoints; i++, sample++, surfpoint += 3) {
if (!TestSky(surfpoint, sunvec, shadowself))
continue;
sample->light += angle * light->light;
if (colored)
VectorMA(sample->color, angle * light->light / 255.0f, light->color,
sample->color);
}
}
/*
* ============
* FixMinlight
* ============
*/
static void
FixMinlight(const lightsample_t *minlight, 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;
int mapnum, i, j, k;
lightsample_t *sample;
/* Find a style 0 lightmap */
for (mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) {
if (lightmaps[mapnum].style == 0)
break;
if (lightmaps[mapnum].style == 255) {
lightmaps[mapnum].style = 0;
break;
}
}
if (mapnum == MAXLIGHTMAPS)
return; /* oh well... FIXME - should we warn? */
sample = lightmaps[mapnum].samples;
for (i = 0; i < lightsurf->numpoints; i++, sample++) {
if (addminlight)
sample->light += minlight->light;
else if (sample->light < minlight->light)
sample->light = minlight->light;
if (colored) {
for (j = 0; j < 3; j++) {
vec_t lightval = minlight->light * minlight->color[j] / 255.0f;
if (addminlight)
sample->color[j] += lightval;
else if (sample->color[j] < lightval)
sample->color[j] = lightval;
}
}
}
/* Cast rays for local minlight entities */
shadowself = modelinfo->shadowself ? modelinfo->model : NULL;
for (i = 0, entity = entities; i < num_entities; i++, entity++) {
if (entity->formula != LF_LOCALMIN)
continue;
sample = lightmaps[mapnum].samples;
surfpoint = lightsurf->points[0];
for (j = 0; j < lightsurf->numpoints; j++, sample++, surfpoint += 3) {
qboolean trace = false;
if (addminlight || sample->light < entity->light.light) {
trace = TestLight(entity->origin, surfpoint, shadowself);
if (!trace)
continue;
if (addminlight)
sample->light += entity->light.light;
else
sample->light = entity->light.light;
}
if (!colored)
continue;
for (k = 0; k < 3; k++) {
if (addminlight || sample->color[k] < entity->light.color[k]) {
if (!trace) {
trace = TestLight(entity->origin, surfpoint, shadowself);
if (!trace)
break;
}
if (addminlight)
sample->color[k] += entity->light.color[k];
else
sample->color[k] = entity->light.color[k];
}
}
}
}
}
static void
WriteLightmaps(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;
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;
if (colored)
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 (colored) {
vec_t max = 0;
VectorScale(color, rangescale, color);
for (i = 0; i < 3; i++)
if (color[i] > max)
max = color[i];
if (max > 255)
VectorScale(color, 255.0f / max, color);
}
if (light > 255)
light = 255;
else if (light < 0)
light = 0;
if (colored) {
*lit++ = color[0];
*lit++ = color[1];
*lit++ = color[2];
}
*out++ = light;
sample -= width * oversample - oversample;
}
sample += width * oversample - width;
}
}
}
/*
* ============
* LightFace
* ============
*/
void
LightFace(dface_t *face, const modelinfo_t *modelinfo)
{
int i, j, k;
const entity_t *entity;
lightsample_t *sample;
lightsurf_t lightsurf;
lightmap_t lightmaps[MAXLIGHTMAPS];
/* some surfaces don't need lightmaps */
face->lightofs = -1;
for (i = 0; i < MAXLIGHTMAPS; i++)
face->styles[i] = 255;
if (texinfo[face->texinfo].flags & TEX_SPECIAL)
return;
Lightsurf_Init(modelinfo, face, &lightsurf);
Lightmaps_Init(lightmaps);
/*
* 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 (i = 0, entity = entities; i < num_entities; i++, entity++) {
if (entity->formula == LF_LOCALMIN)
continue;
if (entity->light.light > 0)
SingleLightFace(entity, &entity->light, &lightsurf, lightmaps);
}
if (sunlight.light > 0)
SkyLightFace(&sunlight, &lightsurf, lightmaps);
/* minlight - Use the greater of global or model minlight. */
if (modelinfo->minlight.light > minlight.light)
FixMinlight(&modelinfo->minlight, &lightsurf, lightmaps);
else
FixMinlight(&minlight, &lightsurf, lightmaps);
/* negative lights */
for (i = 0, entity = entities; i < num_entities; i++, entity++) {
if (entity->formula == LF_LOCALMIN)
continue;
if (entity->light.light < 0)
SingleLightFace(entity, &entity->light, &lightsurf, lightmaps);
}
if (sunlight.light < 0)
SkyLightFace(&sunlight, &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;
if (colored) {
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);
}