1073 lines
25 KiB
C
1073 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;
|
|
* ============================================================================
|
|
*/
|
|
|
|
#define SINGLEMAP (18*18*4)
|
|
|
|
typedef struct {
|
|
vec_t lightmaps[MAXLIGHTMAPS][SINGLEMAP];
|
|
int numlightstyles;
|
|
vec_t *light;
|
|
vec_t facedist;
|
|
vec3_t facenormal;
|
|
|
|
int numsurfpt;
|
|
vec3_t surfpt[SINGLEMAP];
|
|
|
|
/* 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 surfnum;
|
|
dface_t *face;
|
|
|
|
/* Colored lighting */
|
|
vec3_t lightmapcolors[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);
|
|
|
|
if (extrasamples) { /* extra filtering */
|
|
h = (l->texsize[1] + 1) * 2;
|
|
w = (l->texsize[0] + 1) * 2;
|
|
starts = (l->texmins[0] - 0.5) * 16;
|
|
startt = (l->texmins[1] - 0.5) * 16;
|
|
step = 8;
|
|
} else {
|
|
h = l->texsize[1] + 1;
|
|
w = l->texsize[0] + 1;
|
|
starts = l->texmins[0] * 16;
|
|
startt = l->texmins[1] * 16;
|
|
step = 16;
|
|
}
|
|
|
|
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;
|
|
|
|
/*
|
|
* ==============================================
|
|
* LIGHT: Attenuation formulae setup functions
|
|
* ==============================================
|
|
*/
|
|
static vec_t
|
|
scaledDistance(vec_t distance, const entity_t *light)
|
|
{
|
|
switch (light->formula) {
|
|
case LF_LINEAR:
|
|
return scaledist * light->atten * distance;
|
|
case LF_INVERSE:
|
|
case LF_INVERSE2:
|
|
case LF_INFINITE:
|
|
/* Return a small distance to prevent culling these lights, since we */
|
|
/* know these formulae won't fade to nothing. */
|
|
return (distance <= 0.0) ? -0.25 : 0.25;
|
|
default:
|
|
Error("Internal error: unknown light formula");
|
|
}
|
|
}
|
|
|
|
static vec_t
|
|
scaledLight(vec_t distance, const entity_t *light)
|
|
{
|
|
const vec_t tmp = scaledist * light->atten * distance;
|
|
|
|
switch (light->formula) {
|
|
case LF_INFINITE:
|
|
return light->light;
|
|
case LF_INVERSE:
|
|
return light->light / (tmp / LF_SCALE);
|
|
case LF_INVERSE2:
|
|
return light->light / ((tmp * tmp) / (LF_SCALE * LF_SCALE));
|
|
case LF_LINEAR:
|
|
if (light->light > 0)
|
|
return (light->light - tmp > 0) ? light->light - tmp : 0;
|
|
else
|
|
return (light->light + tmp < 0) ? light->light + tmp : 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;
|
|
vec3_t incoming;
|
|
vec_t angle;
|
|
vec_t add;
|
|
vec_t *surf;
|
|
qboolean hit;
|
|
int mapnum;
|
|
int size;
|
|
int c, i;
|
|
vec3_t rel;
|
|
vec3_t spotvec;
|
|
vec_t falloff;
|
|
vec_t *lightsamp;
|
|
|
|
/* Colored lighting */
|
|
vec3_t *lightcolorsamp;
|
|
|
|
VectorSubtract(light->origin, bsp_origin, rel);
|
|
dist = scaledDistance((DotProduct(rel, l->facenormal) - l->facedist),
|
|
light);
|
|
|
|
/* don't bother with lights behind the surface */
|
|
if (dist < 0)
|
|
return;
|
|
|
|
/* don't bother with light too far away */
|
|
if (dist > abs(light->light)) {
|
|
c_culldistplane++;
|
|
return;
|
|
}
|
|
|
|
if (light->targetent) {
|
|
VectorSubtract(light->targetent->origin, light->origin, spotvec);
|
|
VectorNormalize(spotvec);
|
|
if (!light->angle)
|
|
falloff = -cos(20 * Q_PI / 180);
|
|
else
|
|
falloff = -cos(light->angle / 2 * Q_PI / 180);
|
|
} else if (light->use_mangle) {
|
|
VectorCopy(light->mangle, spotvec);
|
|
if (!light->angle)
|
|
falloff = -cos(20 * Q_PI / 180);
|
|
else
|
|
falloff = -cos(light->angle / 2 * Q_PI / 180);
|
|
} else {
|
|
falloff = 0; /* shut up compiler warnings */
|
|
}
|
|
|
|
for (mapnum = 0; mapnum < l->numlightstyles; mapnum++)
|
|
if (l->lightstyles[mapnum] == light->style)
|
|
break;
|
|
|
|
lightsamp = l->lightmaps[mapnum];
|
|
lightcolorsamp = l->lightmapcolors[mapnum];
|
|
|
|
if (mapnum == l->numlightstyles) {
|
|
/* init a new light map */
|
|
size = (l->texsize[1] + 1) * (l->texsize[0] + 1);
|
|
for (i = 0; i < size; i++) {
|
|
if (colored) {
|
|
lightcolorsamp[i][0] = 0;
|
|
lightcolorsamp[i][1] = 0;
|
|
lightcolorsamp[i][2] = 0;
|
|
}
|
|
lightsamp[i] = 0;
|
|
}
|
|
}
|
|
|
|
/* check it for real */
|
|
hit = false;
|
|
c_proper++;
|
|
|
|
surf = l->surfpt[0];
|
|
for (c = 0; c < l->numsurfpt; c++, surf += 3) {
|
|
dist = scaledDistance(CastRay(light->origin, surf), light);
|
|
|
|
if (dist < 0)
|
|
continue; /* light doesn't reach */
|
|
|
|
VectorSubtract(light->origin, surf, incoming);
|
|
VectorNormalize(incoming);
|
|
angle = DotProduct(incoming, l->facenormal);
|
|
if (light->targetent || light->use_mangle) { /* spotlight cutoff */
|
|
if (DotProduct(spotvec, incoming) > falloff)
|
|
continue;
|
|
}
|
|
|
|
angle = (1.0 - scalecos) + scalecos * angle;
|
|
add = scaledLight(CastRay(light->origin, surf), light);
|
|
add *= angle;
|
|
lightsamp[c] += add;
|
|
|
|
if (colored) {
|
|
add /= (vec_t)255.0;
|
|
lightcolorsamp[c][0] += add * colors[0];
|
|
lightcolorsamp[c][1] += add * colors[1];
|
|
lightcolorsamp[c][2] += add * colors[2];
|
|
}
|
|
|
|
if (abs(lightsamp[c]) > 1) /* ignore really tiny lights */
|
|
hit = true;
|
|
}
|
|
|
|
if (mapnum == l->numlightstyles && hit) {
|
|
if (mapnum == MAXLIGHTMAPS - 1) {
|
|
logprint("WARNING: Too many light styles on a face\n");
|
|
logprint(" lightmap point near (%0.0f, %0.0f, %0.0f)\n",
|
|
l->surfpt[0][0], l->surfpt[0][1], l->surfpt[0][2]);
|
|
logprint(" light->origin (%0.0f, %0.0f, %0.0f)\n",
|
|
light->origin[0], light->origin[1], light->origin[2]);
|
|
return;
|
|
}
|
|
|
|
l->lightstyles[mapnum] = light->style;
|
|
l->numlightstyles++; /* the style has some real data now */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* =============
|
|
* SkyLightFace
|
|
* =============
|
|
*/
|
|
static void
|
|
SkyLightFace(lightinfo_t *l, const vec3_t faceoffset, const vec3_t colors)
|
|
{
|
|
int i, j;
|
|
vec_t *surf;
|
|
vec3_t incoming;
|
|
vec_t angle;
|
|
|
|
/* Don't bother if surface facing away from sun */
|
|
if (DotProduct(sunmangle, l->facenormal) < -ANGLE_EPSILON)
|
|
return;
|
|
|
|
/* if sunlight is set, use a style 0 light map */
|
|
for (i = 0; i < l->numlightstyles; i++)
|
|
if (l->lightstyles[i] == 0)
|
|
break;
|
|
if (i == l->numlightstyles) {
|
|
if (l->numlightstyles == MAXLIGHTMAPS)
|
|
return; /* oh well, too many lightmaps... */
|
|
l->lightstyles[i] = 0;
|
|
l->numlightstyles++;
|
|
}
|
|
|
|
/* Check each point... */
|
|
VectorCopy(sunmangle, 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, k;
|
|
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(sunmangle[0]) > fabs(sunmangle[1]) ?
|
|
(fabs(sunmangle[0]) > fabs(sunmangle[2]) ? 0 : 2) :
|
|
(fabs(sunmangle[1]) > fabs(sunmangle[2]) ? 1 : 2);
|
|
b = (a + 1) % 3;
|
|
c = (a + 2) % 3;
|
|
|
|
offset = sunmangle[a] * ANGLE_EPSILON * 2.0; // approx...
|
|
for (j = 0; j < 5; ++j)
|
|
VectorCopy(sunmangle, 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 (j = 0; j < l->numsurfpt; j++, surf += 3) {
|
|
for (k = 0; k < 1 || (oldangle < ANGLE_EPSILON && k < 5); ++k) {
|
|
if (TestSky(surf, sun_vectors[k])) {
|
|
l->lightmaps[i][j] += (angle * sunlight);
|
|
if (colored)
|
|
VectorMA(l->lightmapcolors[i][j],
|
|
angle * sunlight / 255,
|
|
colors, l->lightmapcolors[i][j]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
surf = l->surfpt[0];
|
|
for (j = 0; j < l->numsurfpt; j++, surf += 3) {
|
|
if (TestSky(surf, sunmangle)) {
|
|
l->lightmaps[i][j] += (angle * sunlight);
|
|
if (colored)
|
|
VectorMA(l->lightmapcolors[i][j], angle * sunlight / 255,
|
|
colors, l->lightmapcolors[i][j]);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* ============
|
|
* FixMinlight
|
|
* ============
|
|
*/
|
|
static void
|
|
FixMinlight(lightinfo_t * l)
|
|
{
|
|
int i, j, k;
|
|
vec_t tmp;
|
|
|
|
/* if minlight is set, there must be a style 0 light map */
|
|
for (i = 0; i < l->numlightstyles; i++) {
|
|
if (l->lightstyles[i] == 0) {
|
|
// Set the minimum light level...
|
|
break;
|
|
} else {
|
|
// Set zero minimum...
|
|
}
|
|
}
|
|
if (i == l->numlightstyles) {
|
|
if (l->numlightstyles == MAXLIGHTMAPS)
|
|
return; /* oh well.. */
|
|
for (j = 0; j < l->numsurfpt; j++)
|
|
l->lightmaps[i][j] = worldminlight;
|
|
if (colored)
|
|
for (j = 0; j < l->numsurfpt; j++)
|
|
VectorScale(minlight_color, worldminlight / (vec_t)255,
|
|
l->lightmapcolors[i][j]);
|
|
l->lightstyles[i] = 0;
|
|
l->numlightstyles++;
|
|
} else {
|
|
for (j = 0; j < l->numsurfpt; j++) {
|
|
if (l->lightmaps[i][j] < worldminlight)
|
|
l->lightmaps[i][j] = worldminlight;
|
|
if (colored) {
|
|
for (k = 0; k < 3; k++) {
|
|
tmp = (vec_t)worldminlight *minlight_color[k];
|
|
|
|
tmp /= (vec_t)255.0;
|
|
if (l->lightmapcolors[i][j][k] < tmp)
|
|
l->lightmapcolors[i][j][k] = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
int x1, x2, x3, x4;
|
|
|
|
vec_t total;
|
|
int size;
|
|
int lightmapwidth;
|
|
int lightmapsize;
|
|
byte *out;
|
|
byte *lit_out;
|
|
vec_t *light;
|
|
|
|
vec3_t *lightcolor;
|
|
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.surfnum = surfnum;
|
|
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 (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->light)
|
|
SingleLightFace(entity, &l, faceoffset, colors);
|
|
|
|
/* cast sky light */
|
|
if (sunlight)
|
|
SkyLightFace(&l, faceoffset, colors);
|
|
}
|
|
|
|
/* Minimum lighting */
|
|
FixMinlight(&l);
|
|
|
|
if (nominlimit) {
|
|
/* cast only negative lights */
|
|
for (i = 0, entity = entities; i < num_entities; i++, entity++) {
|
|
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.lightmapcolors[i][j][k] < 0) {
|
|
l.lightmapcolors[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];
|
|
|
|
/* Extra room for BSP30 lightmaps */
|
|
if (colored && bsp30)
|
|
lightmapsize = size * l.numlightstyles * 4;
|
|
else
|
|
lightmapsize = size * l.numlightstyles;
|
|
|
|
out = GetFileSpace(lightmapsize);
|
|
|
|
if (litfile)
|
|
lit_out = GetLitFileSpace(lightmapsize * 3);
|
|
else
|
|
lit_out = NULL; /* Fix compiler warning... */
|
|
|
|
face->lightofs = out - filebase;
|
|
|
|
/* extra filtering */
|
|
width = (l.texsize[0] + 1) * 2;
|
|
|
|
for (i = 0; i < l.numlightstyles; i++) {
|
|
if (l.lightstyles[i] == 0xff)
|
|
Error("Wrote empty lightmap");
|
|
|
|
light = l.lightmaps[i];
|
|
lightcolor = l.lightmapcolors[i];
|
|
c = 0;
|
|
|
|
for (t = 0; t <= l.texsize[1]; t++) {
|
|
for (s = 0; s <= l.texsize[0]; s++, c++) {
|
|
if (extrasamples) {
|
|
x1 = t * 2 * width + s * 2;
|
|
x2 = x1 + 1;
|
|
x3 = (t * 2 + 1) * width + s * 2;
|
|
x4 = x3 + 1;
|
|
|
|
/* filtered sample */
|
|
total = light[x1] + light[x2] + light[x3] + light[x4];
|
|
total *= 0.25;
|
|
|
|
/* Calculate the color */
|
|
if (colored) {
|
|
colors[0] =
|
|
lightcolor[x1][0] + lightcolor[x2][0]
|
|
+ lightcolor[x3][0] + lightcolor[x4][0];
|
|
colors[0] *= 0.25;
|
|
colors[1] =
|
|
lightcolor[x1][1] + lightcolor[x2][1]
|
|
+ lightcolor[x3][1] + lightcolor[x4][1];
|
|
colors[1] *= 0.25;
|
|
colors[2] =
|
|
lightcolor[x1][2] + lightcolor[x2][2]
|
|
+ lightcolor[x3][2] + lightcolor[x4][2];
|
|
colors[2] *= 0.25;
|
|
}
|
|
} else {
|
|
total = light[c];
|
|
if (colored)
|
|
VectorCopy(lightcolor[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 && bsp30) {
|
|
*out++ = colors[0];
|
|
*out++ = colors[1];
|
|
*out++ = colors[2];
|
|
}
|
|
if (colored && litfile) {
|
|
*lit_out++ = colors[0];
|
|
*lit_out++ = colors[1];
|
|
*lit_out++ = colors[2];
|
|
}
|
|
*out++ = total;
|
|
}
|
|
}
|
|
}
|
|
}
|