974 lines
26 KiB
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);
|
|
}
|