ericw-tools/light/ltface.c

1385 lines
38 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 (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);
}