1065 lines
25 KiB
C
1065 lines
25 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>
|
|
|
|
static const float scalecos = 0.5;
|
|
static const vec3_t bsp_origin = { 0, 0, 0 };
|
|
|
|
/* ======================================================================== */
|
|
|
|
// Solve three simultaneous equations
|
|
// mtx is modified by the function...
|
|
#define ZERO_EPSILON (0.001)
|
|
static qboolean
|
|
LU_Decompose(vec3_t mtx[3], int r[3], int c[2])
|
|
{
|
|
int i, j, k; // loop variables
|
|
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(mtx[j][k]) > max) {
|
|
max = fabs(mtx[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 = mtx[i][j];
|
|
mtx[i][j] = mtx[max_r][j];
|
|
mtx[max_r][j] = max;
|
|
}
|
|
k = r[i];
|
|
r[i] = r[max_r];
|
|
r[max_r] = k;
|
|
}
|
|
if (max_c != i) {
|
|
for (j = 0; j < 3; ++j) {
|
|
max = mtx[j][i];
|
|
mtx[j][i] = mtx[j][max_c];
|
|
mtx[j][max_c] = max;
|
|
}
|
|
k = c[i];
|
|
c[i] = c[max_c];
|
|
c[max_c] = k;
|
|
}
|
|
// Do pivot
|
|
for (j = i + 1; j < 3; ++j) {
|
|
mtx[j][i] /= mtx[i][i];
|
|
for (k = i + 1; k < 3; ++k)
|
|
mtx[j][k] -= mtx[j][i] * mtx[i][k];
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
solve3(const vec3_t mtx[3], const int r[3], const int c[3],
|
|
const vec3_t rhs, vec3_t soln)
|
|
{
|
|
vec3_t y;
|
|
|
|
// forward-substitution
|
|
y[0] = rhs[r[0]];
|
|
y[1] = rhs[r[1]] - mtx[1][0] * y[0];
|
|
y[2] = rhs[r[2]] - mtx[2][0] * y[0] - mtx[2][1] * y[1];
|
|
|
|
// back-substitution
|
|
soln[c[2]] = y[2] / mtx[2][2];
|
|
soln[c[1]] = (y[1] - mtx[1][2] * soln[c[2]]) / mtx[1][1];
|
|
soln[c[0]] = (y[0] - mtx[0][1] * soln[c[1]] - mtx[0][2] * soln[c[2]])
|
|
/ mtx[0][0];
|
|
}
|
|
|
|
/* ======================================================================== */
|
|
|
|
|
|
/*
|
|
* ============
|
|
* CastRay
|
|
* Returns the distance between the points, or -1 if blocked
|
|
* =============
|
|
*/
|
|
static vec_t
|
|
CastRay(const vec3_t p1, const vec3_t p2)
|
|
{
|
|
int i;
|
|
vec_t t;
|
|
qboolean trace;
|
|
|
|
trace = TestLine(p1, p2);
|
|
if (!trace)
|
|
return -1; /* ray was blocked */
|
|
|
|
t = 0;
|
|
for (i = 0; i < 3; i++)
|
|
t += (p2[i] - p1[i]) * (p2[i] - p1[i]);
|
|
|
|
if (t == 0)
|
|
t = 1; /* don't blow up... */
|
|
return sqrt(t);
|
|
}
|
|
|
|
/*
|
|
* ============================================================================
|
|
* 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;
|
|
* ============================================================================
|
|
*/
|
|
|
|
/* Allow space for 4x4 oversampling */
|
|
#define SINGLEMAP (18*18*4*4)
|
|
|
|
typedef struct {
|
|
vec_t *light;
|
|
|
|
dface_t *face;
|
|
vec_t facedist;
|
|
vec3_t facenormal;
|
|
|
|
/* FIXME - Comment this properly */
|
|
vec_t worldtotex[2][4]; // Copy of face->texinfo->vecs
|
|
vec3_t LU[3];
|
|
int row_p[3];
|
|
int col_p[3];
|
|
|
|
vec_t exactmid[2];
|
|
|
|
int texmins[2], texsize[2];
|
|
int lightstyles[256];
|
|
|
|
int numsurfpt;
|
|
int numlightstyles;
|
|
|
|
vec3_t surfpt[SINGLEMAP];
|
|
vec_t lightmaps[MAXLIGHTMAPS][SINGLEMAP];
|
|
vec3_t colormaps[MAXLIGHTMAPS][SINGLEMAP];
|
|
} lightinfo_t;
|
|
|
|
/*
|
|
* ================
|
|
* CalcFaceVectors
|
|
* Fills in texorg, worldtotex. and textoworld
|
|
* ================
|
|
*/
|
|
static void
|
|
CalcFaceVectors(lightinfo_t * l)
|
|
{
|
|
texinfo_t *tex;
|
|
int i, j;
|
|
|
|
/* convert from float to vec_t */
|
|
tex = &texinfo[l->face->texinfo];
|
|
for (i = 0; i < 2; i++)
|
|
for (j = 0; j < 4; j++)
|
|
l->worldtotex[i][j] = tex->vecs[i][j];
|
|
|
|
/* Prepare LU and row, column permutations */
|
|
for (i = 0; i < 3; ++i)
|
|
l->row_p[i] = l->col_p[i] = i;
|
|
VectorCopy(l->worldtotex[0], l->LU[0]);
|
|
VectorCopy(l->worldtotex[1], l->LU[1]);
|
|
VectorCopy(l->facenormal, l->LU[2]);
|
|
|
|
/* Decompose the matrix. If we can't, texture axes are invalid. */
|
|
if (!LU_Decompose(l->LU, l->row_p, l->col_p)) {
|
|
const vec_t *p = dvertexes[dedges[l->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
|
|
tex_to_world(vec_t s, vec_t t, const lightinfo_t * l, vec3_t world)
|
|
{
|
|
vec3_t rhs;
|
|
|
|
rhs[0] = s - l->worldtotex[0][3];
|
|
rhs[1] = t - l->worldtotex[1][3];
|
|
rhs[2] = l->facedist + 1; // one "unit" in front of surface
|
|
|
|
solve3(l->LU, l->row_p, l->col_p, rhs, world);
|
|
}
|
|
|
|
/*
|
|
* Functions to aid in calculation of polygon centroid
|
|
*/
|
|
static void
|
|
tri_centroid(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
|
|
tri_area(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
|
|
face_centroid(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 = tri_area(v0, v1, v2);
|
|
poly_area += area;
|
|
|
|
tri_centroid(v0, v1, v2, centroid);
|
|
VectorMA(poly_centroid, area, centroid, poly_centroid);
|
|
}
|
|
|
|
VectorScale(poly_centroid, 1.0 / poly_area, out);
|
|
}
|
|
|
|
/*
|
|
* ================
|
|
* CalcFaceExtents
|
|
* Fills in s->texmins[], s->texsize[] and sets exactmid[]
|
|
* ================
|
|
*/
|
|
static void
|
|
CalcFaceExtents(lightinfo_t * l, const vec3_t faceoffset)
|
|
{
|
|
dface_t *s;
|
|
vec_t mins[2], maxs[2], val;
|
|
vec3_t centroid;
|
|
int i, j, e;
|
|
dvertex_t *v;
|
|
texinfo_t *tex;
|
|
|
|
s = l->face;
|
|
|
|
mins[0] = mins[1] = 999999;
|
|
maxs[0] = maxs[1] = -99999;
|
|
tex = &texinfo[s->texinfo];
|
|
|
|
for (i = 0; i < s->numedges; i++) {
|
|
e = dsurfedges[s->firstedge + i];
|
|
if (e >= 0)
|
|
v = dvertexes + dedges[e].v[0];
|
|
else
|
|
v = dvertexes + dedges[-e].v[1];
|
|
|
|
for (j = 0; j < 2; j++) {
|
|
// This is world->tex with world offset...
|
|
val = (v->point[0] + faceoffset[0]) * tex->vecs[j][0]
|
|
+ (v->point[1] + faceoffset[1]) * tex->vecs[j][1]
|
|
+ (v->point[2] + faceoffset[2]) * tex->vecs[j][2]
|
|
+ tex->vecs[j][3];
|
|
if (val < mins[j])
|
|
mins[j] = val;
|
|
if (val > maxs[j])
|
|
maxs[j] = val;
|
|
}
|
|
}
|
|
|
|
face_centroid(s, centroid);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
l->exactmid[i] =
|
|
(centroid[0] + faceoffset[0]) * tex->vecs[i][0]
|
|
+ (centroid[1] + faceoffset[1]) * tex->vecs[i][1]
|
|
+ (centroid[2] + faceoffset[2]) * tex->vecs[i][2]
|
|
+ tex->vecs[i][3];
|
|
|
|
|
|
mins[i] = floor(mins[i] / 16);
|
|
maxs[i] = ceil(maxs[i] / 16);
|
|
|
|
l->texmins[i] = mins[i];
|
|
l->texsize[i] = maxs[i] - mins[i];
|
|
if (l->texsize[i] > 17)
|
|
Error("Bad surface extents");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* =================
|
|
* CalcPoints
|
|
* For each texture aligned grid point, back project onto the plane
|
|
* to get the world xyz value of the sample point
|
|
* =================
|
|
*/
|
|
static void
|
|
CalcPoints(lightinfo_t * l)
|
|
{
|
|
int i;
|
|
int s, t;
|
|
int w, h, step;
|
|
vec_t starts, startt, us, ut;
|
|
vec_t *surf;
|
|
vec_t mids, midt;
|
|
vec3_t facemid, move;
|
|
|
|
/* fill in surforg */
|
|
/* the points are biased towards the center of the surface */
|
|
/* to help avoid edge cases just inside walls */
|
|
|
|
surf = l->surfpt[0];
|
|
mids = l->exactmid[0];
|
|
midt = l->exactmid[1];
|
|
|
|
tex_to_world(mids, midt, l, facemid);
|
|
|
|
h = (l->texsize[1] + 1) * oversample;
|
|
w = (l->texsize[0] + 1) * oversample;
|
|
starts = (l->texmins[0] - 0.5 + (0.5 / oversample)) * 16;
|
|
startt = (l->texmins[1] - 0.5 + (0.5 / oversample)) * 16;
|
|
step = 16 / oversample;
|
|
|
|
l->numsurfpt = w * h;
|
|
for (t = 0; t < h; t++) {
|
|
for (s = 0; s < w; s++, surf += 3) {
|
|
us = starts + s * step;
|
|
ut = startt + t * step;
|
|
|
|
/* if a line can be traced from surf to facemid, point is good */
|
|
for (i = 0; i < 6; i++) {
|
|
tex_to_world(us, ut, l, surf);
|
|
|
|
if (CastRay(facemid, surf) != -1)
|
|
break; /* got it */
|
|
if (i & 1) { // i is odd
|
|
if (us > mids) {
|
|
us -= 8;
|
|
if (us < mids)
|
|
us = mids;
|
|
} else {
|
|
us += 8;
|
|
if (us > mids)
|
|
us = mids;
|
|
}
|
|
} else {
|
|
if (ut > midt) {
|
|
ut -= 8;
|
|
if (ut < midt)
|
|
ut = midt;
|
|
} else {
|
|
ut += 8;
|
|
if (ut > midt)
|
|
ut = midt;
|
|
}
|
|
}
|
|
|
|
/* move surf 8 pixels towards the center */
|
|
VectorSubtract(facemid, surf, move);
|
|
VectorNormalize(move);
|
|
VectorMA(surf, 8, move, surf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ============================================================================
|
|
* FACE LIGHTING
|
|
* ============================================================================
|
|
*/
|
|
|
|
static int c_culldistplane;
|
|
static int c_proper;
|
|
|
|
static vec_t
|
|
GetLightValue(const entity_t *light, vec_t distance)
|
|
{
|
|
vec_t value;
|
|
|
|
if (light->formula == LF_INFINITE || light->formula == LF_LOCALMIN)
|
|
return light->light;
|
|
|
|
value = scaledist * light->atten * distance;
|
|
switch (light->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 *light, lightinfo_t * l,
|
|
const vec3_t faceoffset, const vec3_t colors)
|
|
{
|
|
vec_t dist;
|
|
vec_t angle, spotscale;
|
|
vec_t add;
|
|
const vec_t *surf;
|
|
qboolean newmap, hit;
|
|
int mapnum;
|
|
int c;
|
|
vec_t *lightsamp;
|
|
vec3_t *colorsamp;
|
|
vec_t newlightmap[SINGLEMAP];
|
|
vec3_t newcolormap[SINGLEMAP];
|
|
|
|
dist = DotProduct(light->origin, l->facenormal) - l->facedist;
|
|
|
|
/* don't bother with lights behind the surface */
|
|
if (dist < 0)
|
|
return;
|
|
|
|
/* don't bother with light too far away */
|
|
if (dist > light->fadedist) {
|
|
c_culldistplane++;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Find the lightmap with matching style
|
|
*/
|
|
newmap = true;
|
|
for (mapnum = 0; mapnum < l->numlightstyles; mapnum++) {
|
|
if (l->lightstyles[mapnum] == light->style) {
|
|
newmap = false;
|
|
break;
|
|
}
|
|
}
|
|
if (newmap) {
|
|
memset(newlightmap, 0, sizeof(newlightmap));
|
|
memset(newcolormap, 0, sizeof(newcolormap));
|
|
lightsamp = newlightmap;
|
|
colorsamp = newcolormap;
|
|
} else {
|
|
lightsamp = l->lightmaps[mapnum];
|
|
colorsamp = l->colormaps[mapnum];
|
|
}
|
|
|
|
/*
|
|
* Check it for real
|
|
*/
|
|
hit = false;
|
|
c_proper++;
|
|
|
|
surf = l->surfpt[0];
|
|
for (c = 0; c < l->numsurfpt; c++, surf += 3) {
|
|
vec3_t ray;
|
|
|
|
VectorSubtract(light->origin, surf, ray);
|
|
dist = VectorLength(ray);
|
|
|
|
/* Quick distance check first */
|
|
if (dist > light->fadedist)
|
|
continue;
|
|
|
|
/* Check spotlight cone */
|
|
VectorScale(ray, 1.0 / dist, ray);
|
|
angle = DotProduct(ray, l->facenormal);
|
|
spotscale = 1;
|
|
if (light->spotlight) {
|
|
vec_t falloff = DotProduct(light->spotvec, ray);
|
|
if (falloff > light->spotfalloff)
|
|
continue;
|
|
if (falloff > light->spotfalloff2) {
|
|
/* Interpolate between the two spotlight falloffs */
|
|
spotscale = falloff - light->spotfalloff2;
|
|
spotscale /= light->spotfalloff - light->spotfalloff2;
|
|
spotscale = 1.0 - spotscale;
|
|
}
|
|
}
|
|
|
|
/* Test for line of sight */
|
|
if (!TestLine(light->origin, surf))
|
|
continue;
|
|
|
|
angle = (1.0 - scalecos) + scalecos * angle;
|
|
add = GetLightValue(light, dist) * angle * spotscale;
|
|
lightsamp[c] += add;
|
|
if (colored)
|
|
VectorMA(colorsamp[c], add / 255, colors, colorsamp[c]);
|
|
|
|
/* Check if we really hit, ignore tiny lights */
|
|
if (newmap && lightsamp[c] > 1)
|
|
hit = true;
|
|
}
|
|
|
|
if (newmap && hit) {
|
|
if (l->numlightstyles == MAXLIGHTMAPS) {
|
|
logprint("WARNING: Too many light styles on a face\n"
|
|
" lightmap point near (%s)\n"
|
|
" light->origin (%s)\n",
|
|
VecStr(l->surfpt[0]), VecStr(light->origin));
|
|
return;
|
|
}
|
|
|
|
/* the style has some real data now */
|
|
mapnum = l->numlightstyles++;
|
|
l->lightstyles[mapnum] = light->style;
|
|
memcpy(l->lightmaps[mapnum], newlightmap, sizeof(newlightmap));
|
|
memcpy(l->colormaps[mapnum], newcolormap, sizeof(newcolormap));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* =============
|
|
* SkyLightFace
|
|
* =============
|
|
*/
|
|
static void
|
|
SkyLightFace(lightinfo_t *l, const vec3_t faceoffset, const vec3_t colors)
|
|
{
|
|
int i, mapnum;
|
|
vec_t *surf;
|
|
vec3_t incoming;
|
|
vec_t angle;
|
|
vec_t *lightmap;
|
|
vec3_t *colormap;
|
|
|
|
/* Don't bother if surface facing away from sun */
|
|
if (DotProduct(sunvec, l->facenormal) < -ANGLE_EPSILON)
|
|
return;
|
|
|
|
/* if sunlight is set, use a style 0 light map */
|
|
for (mapnum = 0; mapnum < l->numlightstyles; mapnum++)
|
|
if (l->lightstyles[mapnum] == 0)
|
|
break;
|
|
if (mapnum == l->numlightstyles) {
|
|
if (l->numlightstyles == MAXLIGHTMAPS)
|
|
return; /* oh well, too many lightmaps... */
|
|
l->lightstyles[mapnum] = 0;
|
|
l->numlightstyles++;
|
|
}
|
|
lightmap = l->lightmaps[mapnum];
|
|
colormap = l->colormaps[mapnum];
|
|
|
|
/* Check each point... */
|
|
VectorCopy(sunvec, incoming);
|
|
VectorNormalize(incoming);
|
|
angle = DotProduct(incoming, l->facenormal);
|
|
angle = (1.0 - scalecos) + scalecos * angle;
|
|
|
|
#if 0
|
|
/* Experimental - lighting of faces parallel to sunlight*/
|
|
{
|
|
int a, b, c, j;
|
|
vec_t oldangle, offset;
|
|
vec3_t sun_vectors[5];
|
|
|
|
// Try to hit parallel surfaces?
|
|
oldangle = DotProduct(incoming, l->facenormal);
|
|
if (oldangle < ANGLE_EPSILON) {
|
|
printf("real small angle! (%f)\n", oldangle);
|
|
angle = (1.0 - scalecos) + scalecos * ANGLE_EPSILON;
|
|
}
|
|
|
|
a = fabs(sunvec[0]) > fabs(sunvec[1]) ?
|
|
(fabs(sunvec[0]) > fabs(sunvec[2]) ? 0 : 2) :
|
|
(fabs(sunvec[1]) > fabs(sunvec[2]) ? 1 : 2);
|
|
b = (a + 1) % 3;
|
|
c = (a + 2) % 3;
|
|
|
|
offset = sunvec[a] * ANGLE_EPSILON * 2.0; // approx...
|
|
for (j = 0; j < 5; ++j)
|
|
VectorCopy(sunvec, sun_vectors[j]);
|
|
sun_vectors[1][b] += offset;
|
|
sun_vectors[2][b] -= offset;
|
|
sun_vectors[3][c] += offset;
|
|
sun_vectors[4][c] -= offset;
|
|
|
|
surf = l->surfpt[0];
|
|
for (i = 0; i < l->numsurfpt; i++, surf += 3) {
|
|
for (j = 0; j < 1 || (oldangle < ANGLE_EPSILON && j < 5); ++j) {
|
|
if (TestSky(surf, sun_vectors[j])) {
|
|
lightmap[i] += angle * sunlight;
|
|
if (colored)
|
|
VectorMA(colormap[i], angle * sunlight / 255, colors,
|
|
colormap[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
surf = l->surfpt[0];
|
|
for (i = 0; i < l->numsurfpt; i++, surf += 3) {
|
|
if (TestSky(surf, sunvec)) {
|
|
lightmap[i] += angle * sunlight;
|
|
if (colored)
|
|
VectorMA(colormap[i], angle * sunlight / 255, colors,
|
|
colormap[i]);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* ============
|
|
* FixMinlight
|
|
* ============
|
|
*/
|
|
static void
|
|
FixMinlight(lightinfo_t *l)
|
|
{
|
|
int i, j, k;
|
|
vec_t *lightmap;
|
|
vec3_t *colormap;
|
|
const entity_t *entity;
|
|
|
|
/* Find a style 0 lightmap */
|
|
lightmap = NULL;
|
|
colormap = NULL;
|
|
for (i = 0; i < l->numlightstyles; i++) {
|
|
if (l->lightstyles[i] == 0) {
|
|
lightmap = l->lightmaps[i];
|
|
colormap = l->colormaps[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lightmap) {
|
|
if (l->numlightstyles == MAXLIGHTMAPS)
|
|
return; /* oh well... FIXME - should we warn? */
|
|
lightmap = l->lightmaps[l->numlightstyles];
|
|
for (i = 0; i < l->numsurfpt; i++)
|
|
lightmap[i] = worldminlight;
|
|
if (colored) {
|
|
colormap = l->colormaps[l->numlightstyles];
|
|
for (i = 0; i < l->numsurfpt; i++)
|
|
VectorScale(minlight_color, worldminlight / 255, colormap[i]);
|
|
}
|
|
l->lightstyles[l->numlightstyles++] = 0;
|
|
} else {
|
|
for (i = 0; i < l->numsurfpt; i++) {
|
|
if (lightmap[i] < worldminlight)
|
|
lightmap[i] = worldminlight;
|
|
if (colored) {
|
|
for (j = 0; j < 3; j++) {
|
|
vec_t lightval = worldminlight * minlight_color[j] / 255;
|
|
if (colormap[i][j] < lightval)
|
|
colormap[i][j] = lightval;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Cast rays for local minlight entities */
|
|
for (i = 0, entity = entities; i < num_entities; i++, entity++) {
|
|
if (entity->formula != LF_LOCALMIN)
|
|
continue;
|
|
|
|
/* Find the lightmap with correct style */
|
|
lightmap = NULL;
|
|
colormap = NULL;
|
|
for (j = 0; j < l->numlightstyles; j++) {
|
|
if (l->lightstyles[j] == 0) {
|
|
lightmap = l->lightmaps[j];
|
|
colormap = l->colormaps[j];
|
|
break;
|
|
}
|
|
}
|
|
if (!lightmap) {
|
|
if (l->numlightstyles == MAXLIGHTMAPS)
|
|
continue; /* oh well... FIXME - should we warn? */
|
|
lightmap = l->lightmaps[l->numlightstyles];
|
|
colormap = l->colormaps[l->numlightstyles];
|
|
l->numlightstyles++;
|
|
}
|
|
|
|
for (j = 0; j < l->numsurfpt; j++) {
|
|
qboolean trace = false;
|
|
if (lightmap[j] < entity->light) {
|
|
trace = TestLine(entity->origin, l->surfpt[j]);
|
|
if (!trace)
|
|
continue;
|
|
lightmap[j] = entity->light;
|
|
}
|
|
if (!colored)
|
|
continue;
|
|
for (k = 0; k < 3; k++) {
|
|
if (colormap[j][k] < minlight_color[k]) {
|
|
if (!trace)
|
|
trace = TestLine(entity->origin, l->surfpt[j]);
|
|
if (trace)
|
|
colormap[j][k] = minlight_color[k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* light is the light intensity, needed to check if +ve or -ve.
|
|
* src and dest are the source and destination color vectors (vec3_t).
|
|
* dest becomes a copy of src where
|
|
* PositiveColors zeros negative light components.
|
|
* NegativeColors zeros positive light components.
|
|
*/
|
|
static void
|
|
PositiveColors(int light, vec3_t dest, const vec3_t src)
|
|
{
|
|
int i;
|
|
|
|
if (light >= 0) {
|
|
for (i = 0; i < 3; i++)
|
|
if (src[i] < 0)
|
|
dest[i] = 0;
|
|
else
|
|
dest[i] = src[i];
|
|
} else {
|
|
for (i = 0; i < 3; i++)
|
|
if (src[i] > 0)
|
|
dest[i] = 0;
|
|
else
|
|
dest[i] = src[i];
|
|
}
|
|
}
|
|
|
|
static void
|
|
NegativeColors(int light, vec3_t dest, const vec3_t src)
|
|
{
|
|
int i;
|
|
|
|
if (light >= 0) {
|
|
for (i = 0; i < 3; i++)
|
|
if (src[i] > 0)
|
|
dest[i] = 0;
|
|
else
|
|
dest[i] = src[i];
|
|
} else {
|
|
for (i = 0; i < 3; i++)
|
|
if (src[i] < 0)
|
|
dest[i] = 0;
|
|
else
|
|
dest[i] = src[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ============
|
|
* LightFace
|
|
* ============
|
|
*/
|
|
void
|
|
LightFace(int surfnum, qboolean nolight, const vec3_t faceoffset)
|
|
{
|
|
const entity_t *entity;
|
|
dface_t *face;
|
|
lightinfo_t l;
|
|
int s, t;
|
|
int i, j, k, c;
|
|
|
|
vec_t max;
|
|
vec_t total;
|
|
int size;
|
|
int lightmapwidth;
|
|
int lightmapsize;
|
|
byte *out;
|
|
byte *lit_out = NULL;
|
|
vec_t *lightmap;
|
|
vec3_t *colormap;
|
|
vec3_t colors = { 0, 0, 0 };
|
|
|
|
int width;
|
|
vec3_t point;
|
|
|
|
face = dfaces + surfnum;
|
|
|
|
/* some surfaces don't need lightmaps */
|
|
face->lightofs = -1;
|
|
for (j = 0; j < MAXLIGHTMAPS; j++)
|
|
face->styles[j] = 255;
|
|
|
|
if (texinfo[face->texinfo].flags & TEX_SPECIAL)
|
|
return; /* non-lit texture */
|
|
|
|
memset(&l, 0, sizeof(l));
|
|
l.face = face;
|
|
|
|
/* rotate plane */
|
|
|
|
VectorCopy(dplanes[face->planenum].normal, l.facenormal);
|
|
l.facedist = dplanes[face->planenum].dist;
|
|
VectorScale(l.facenormal, l.facedist, point);
|
|
VectorAdd(point, faceoffset, point);
|
|
l.facedist = DotProduct(point, l.facenormal);
|
|
|
|
if (face->side) {
|
|
VectorSubtract(vec3_origin, l.facenormal, l.facenormal);
|
|
l.facedist = -l.facedist;
|
|
}
|
|
|
|
CalcFaceVectors(&l);
|
|
CalcFaceExtents(&l, faceoffset);
|
|
CalcPoints(&l);
|
|
|
|
lightmapwidth = l.texsize[0] + 1;
|
|
|
|
size = lightmapwidth * (l.texsize[1] + 1);
|
|
if (size > SINGLEMAP)
|
|
Error("Bad lightmap size");
|
|
|
|
for (i = 0; i < MAXLIGHTMAPS; i++)
|
|
l.lightstyles[i] = 255;
|
|
|
|
/* Under normal circumstances, the lighting procedure is:
|
|
* - cast all light entities
|
|
* - cast sky lighting
|
|
* - do minlighting.
|
|
*
|
|
* However, if nominlimit is enabled then we need to do the following:
|
|
* - cast _positive_ lights
|
|
* - cast _positive_ skylight (if any)
|
|
* - do minlighting
|
|
* - cast _negative_ lights
|
|
* - cast _negative_ sky light (if any)
|
|
*/
|
|
|
|
l.numlightstyles = 0;
|
|
if (nominlimit) {
|
|
/* cast only positive lights */
|
|
for (i = 0, entity = entities; i < num_entities; i++, entity++) {
|
|
if (entity->formula == LF_LOCALMIN)
|
|
continue;
|
|
if (colored) {
|
|
if (entity->light) {
|
|
PositiveColors(entity->light, colors, entity->lightcolor);
|
|
SingleLightFace(entity, &l, faceoffset, colors);
|
|
}
|
|
} else if (entity->light > 0) {
|
|
SingleLightFace(entity, &l, faceoffset, colors);
|
|
}
|
|
}
|
|
/* cast positive sky light */
|
|
if (sunlight) {
|
|
if (colored) {
|
|
PositiveColors(sunlight, sunlight_color, colors);
|
|
SkyLightFace(&l, faceoffset, colors);
|
|
} else if (sunlight > 0) {
|
|
SkyLightFace(&l, faceoffset, colors);
|
|
}
|
|
}
|
|
} else {
|
|
/* (!nominlimit) => cast all lights */
|
|
for (i = 0, entity = entities; i < num_entities; i++, entity++) {
|
|
if (entity->formula == LF_LOCALMIN)
|
|
continue;
|
|
if (entity->light)
|
|
SingleLightFace(entity, &l, faceoffset, entity->lightcolor);
|
|
}
|
|
/* cast sky light */
|
|
if (sunlight)
|
|
SkyLightFace(&l, faceoffset, sunlight_color);
|
|
}
|
|
|
|
/* Minimum lighting */
|
|
FixMinlight(&l);
|
|
|
|
if (nominlimit) {
|
|
/* cast only negative lights */
|
|
for (i = 0, entity = entities; i < num_entities; i++, entity++) {
|
|
if (entity->formula == LF_LOCALMIN)
|
|
continue;
|
|
if (colored) {
|
|
if (entity->light) {
|
|
NegativeColors(entity->light, colors, entity->lightcolor);
|
|
SingleLightFace(entity, &l, faceoffset, colors);
|
|
}
|
|
} else if (entity->light < 0) {
|
|
SingleLightFace(entity, &l, faceoffset, colors);
|
|
}
|
|
}
|
|
/* cast negative sky light */
|
|
if (sunlight) {
|
|
if (colored) {
|
|
NegativeColors(sunlight, colors, sunlight_color);
|
|
SkyLightFace(&l, faceoffset, colors);
|
|
} else if (sunlight < 0) {
|
|
SkyLightFace(&l, faceoffset, colors);
|
|
}
|
|
}
|
|
|
|
/* Fix any negative values */
|
|
for (i = 0; i < l.numlightstyles; i++) {
|
|
for (j = 0; j < l.numsurfpt; j++) {
|
|
if (l.lightmaps[i][j] < 0) {
|
|
l.lightmaps[i][j] = 0;
|
|
}
|
|
if (colored) {
|
|
for (k = 0; k < 3; k++) {
|
|
if (l.colormaps[i][j][k] < 0) {
|
|
l.colormaps[i][j][k] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!l.numlightstyles)
|
|
return; /* no light hitting it */
|
|
|
|
/* save out the values */
|
|
|
|
for (i = 0; i < MAXLIGHTMAPS; i++)
|
|
face->styles[i] = l.lightstyles[i];
|
|
|
|
lightmapsize = size * l.numlightstyles;
|
|
GetFileSpace(&out, &lit_out, lightmapsize);
|
|
|
|
face->lightofs = out - filebase;
|
|
|
|
/* extra filtering */
|
|
width = (l.texsize[0] + 1) * oversample;
|
|
|
|
for (i = 0; i < l.numlightstyles; i++) {
|
|
if (l.lightstyles[i] == 0xff)
|
|
Error("Wrote empty lightmap");
|
|
|
|
lightmap = l.lightmaps[i];
|
|
colormap = l.colormaps[i];
|
|
c = 0;
|
|
|
|
for (t = 0; t <= l.texsize[1]; t++) {
|
|
for (s = 0; s <= l.texsize[0]; s++, c++) {
|
|
if (oversample > 1) {
|
|
total = 0;
|
|
VectorCopy(vec3_origin, colors);
|
|
for (j = 0; j < oversample; j++) {
|
|
for (k = 0; k < oversample; k++) {
|
|
int sample = (t * oversample + j) * width;
|
|
sample += s * oversample + k;
|
|
total += lightmap[sample];
|
|
if (colored)
|
|
VectorAdd(colors, colormap[sample], colors);
|
|
}
|
|
}
|
|
total /= oversample * oversample;
|
|
VectorScale(colors, 1.0 / oversample / oversample, colors);
|
|
} else {
|
|
total = lightmap[c];
|
|
if (colored)
|
|
VectorCopy(colormap[c], colors);
|
|
}
|
|
|
|
total *= rangescale; /* scale before clamping */
|
|
if (colored) {
|
|
/* Scale back intensity, instead of capping individual
|
|
* colors
|
|
*/
|
|
VectorScale(colors, rangescale, colors);
|
|
max = 0.0;
|
|
for (j = 0; j < 3; j++)
|
|
if (colors[j] > max) {
|
|
max = colors[j];
|
|
} else if (colors[j] < 0.0f) {
|
|
Error("color %i < 0", j);
|
|
}
|
|
if (max > 255.0f)
|
|
VectorScale(colors, 255.0f / max, colors);
|
|
}
|
|
|
|
if (total > 255.0f)
|
|
total = 255.0f;
|
|
else if (total < 0) {
|
|
//Error ("light < 0");
|
|
total = 0;
|
|
}
|
|
|
|
/* Write out the lightmap in the appropriate format */
|
|
if (colored) {
|
|
*lit_out++ = colors[0];
|
|
*lit_out++ = colors[1];
|
|
*lit_out++ = colors[2];
|
|
}
|
|
*out++ = total;
|
|
}
|
|
}
|
|
}
|
|
}
|