508 lines
14 KiB
C
508 lines
14 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>
|
|
|
|
typedef struct tnode_s {
|
|
vec3_t normal;
|
|
vec_t dist;
|
|
int type;
|
|
int children[2];
|
|
const dplane_t *plane;
|
|
const bsp2_dleaf_t *childleafs[2];
|
|
const bsp2_dnode_t *node;
|
|
} tnode_t;
|
|
|
|
static tnode_t *tnodes;
|
|
static tnode_t *tnode_p;
|
|
static const bsp2_t *bsp_static;
|
|
|
|
// from hmap2
|
|
#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist)
|
|
|
|
/*
|
|
==============
|
|
Light_PointInLeaf
|
|
|
|
from hmap2
|
|
==============
|
|
*/
|
|
bsp2_dleaf_t *Light_PointInLeaf( const vec3_t point )
|
|
{
|
|
int num = 0;
|
|
|
|
while( num >= 0 )
|
|
num = bsp_static->dnodes[num].children[PlaneDiff(point, &bsp_static->dplanes[bsp_static->dnodes[num].planenum]) < 0];
|
|
|
|
return bsp_static->dleafs + (-1 - num);
|
|
}
|
|
|
|
/*
|
|
==============
|
|
Light_PointContents
|
|
|
|
from hmap2
|
|
==============
|
|
*/
|
|
int Light_PointContents( const vec3_t point )
|
|
{
|
|
return Light_PointInLeaf(point)->contents;
|
|
}
|
|
|
|
/*
|
|
* ==============
|
|
* MakeTnodes
|
|
* Converts the disk node structure into the efficient tracing structure
|
|
* ==============
|
|
*/
|
|
static void
|
|
MakeTnodes_r(int nodenum, const bsp2_t *bsp)
|
|
{
|
|
tnode_t *tnode;
|
|
int i;
|
|
bsp2_dnode_t *node;
|
|
bsp2_dleaf_t *leaf;
|
|
|
|
tnode = tnode_p++;
|
|
|
|
node = bsp->dnodes + nodenum;
|
|
tnode->plane = bsp->dplanes + node->planenum;
|
|
tnode->node = node;
|
|
|
|
tnode->type = tnode->plane->type;
|
|
VectorCopy(tnode->plane->normal, tnode->normal);
|
|
tnode->dist = tnode->plane->dist;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
if (node->children[i] < 0) {
|
|
leaf = &bsp->dleafs[-node->children[i] - 1];
|
|
tnode->children[i] = leaf->contents;
|
|
tnode->childleafs[i] = leaf;
|
|
} else {
|
|
tnode->children[i] = tnode_p - tnodes;
|
|
MakeTnodes_r(node->children[i], bsp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MakeTnodes(const bsp2_t *bsp)
|
|
{
|
|
int i;
|
|
bsp_static = bsp;
|
|
tnode_p = tnodes = malloc(bsp->numnodes * sizeof(tnode_t));
|
|
for (i = 0; i < bsp->nummodels; i++)
|
|
MakeTnodes_r(bsp->dmodels[i].headnode[0], bsp);
|
|
}
|
|
|
|
/*
|
|
* ============================================================================
|
|
* FENCE TEXTURE TESTING
|
|
* ============================================================================
|
|
*/
|
|
|
|
static qboolean
|
|
PointInTri(const vec3_t v0,
|
|
const vec3_t v1,
|
|
const vec3_t v2,
|
|
const vec3_t point,
|
|
const vec3_t facenormal)
|
|
{
|
|
vec3_t temp;
|
|
vec3_t normal[3];
|
|
vec_t dist[3];
|
|
|
|
VectorSubtract (v1, v0, temp);
|
|
VectorNormalize (temp);
|
|
CrossProduct (temp, facenormal, normal[0]);
|
|
dist[0] = DotProduct (v0, normal[0]);
|
|
|
|
VectorSubtract (v2, v1, temp);
|
|
VectorNormalize (temp);
|
|
CrossProduct (temp, facenormal, normal[1]);
|
|
dist[1] = DotProduct (v1, normal[1]);
|
|
|
|
VectorSubtract (v0, v2, temp);
|
|
VectorNormalize (temp);
|
|
CrossProduct (temp, facenormal, normal[2]);
|
|
dist[2] = DotProduct (v2, normal[2]);
|
|
|
|
// check each plane
|
|
|
|
if (DotProduct (normal[0], point) - dist[0] < 0) return false;
|
|
if (DotProduct (normal[1], point) - dist[1] < 0) return false;
|
|
if (DotProduct (normal[2], point) - dist[2] < 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static qboolean
|
|
PointInFace(const bsp2_dface_t *face, const bsp2_t *bsp, const vec3_t point)
|
|
{
|
|
int i, edgenum;
|
|
dvertex_t *v0, *v1, *v2;
|
|
|
|
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];
|
|
}
|
|
|
|
vec3_t p0 = {v0->point[0], v0->point[1], v0->point[2]};
|
|
vec3_t p1 = {v1->point[0], v1->point[1], v1->point[2]};
|
|
vec3_t p2 = {v2->point[0], v2->point[1], v2->point[2]};
|
|
|
|
vec3_t facenormal;
|
|
VectorCopy(bsp->dplanes[face->planenum].normal, facenormal);
|
|
if (face->side) {
|
|
VectorSubtract(vec3_origin, facenormal, facenormal);
|
|
}
|
|
|
|
if (PointInTri(p0, p1, p2, point, facenormal)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static miptex_t *
|
|
MiptexForFace(const bsp2_dface_t *face, const bsp2_t *bsp)
|
|
{
|
|
const texinfo_t *tex;
|
|
dmiptexlump_t *miplump = bsp->dtexdata.header;
|
|
miptex_t *miptex;
|
|
tex = &bsp->texinfo[face->texinfo];
|
|
|
|
miptex = (miptex_t*)(bsp->dtexdata.base + miplump->dataofs[tex->miptex]);
|
|
return miptex;
|
|
}
|
|
|
|
vec_t fix_coord(vec_t in, int width)
|
|
{
|
|
if (in > 0)
|
|
{
|
|
return (int)in % width;
|
|
}
|
|
else
|
|
{
|
|
vec_t in_abs = fabs(in);
|
|
int in_abs_mod = (int)in_abs % width;
|
|
return width - in_abs_mod;
|
|
}
|
|
}
|
|
|
|
static int
|
|
SampleTexture(const bsp2_dface_t *face, const bsp2_t *bsp, const vec3_t point)
|
|
{
|
|
vec_t texcoord[2];
|
|
const texinfo_t *tex;
|
|
dmiptexlump_t *miplump = bsp->dtexdata.header;
|
|
miptex_t *miptex;
|
|
int x, y;
|
|
byte *data;
|
|
int sample;
|
|
|
|
tex = &bsp->texinfo[face->texinfo];
|
|
|
|
WorldToTexCoord(point, tex, texcoord);
|
|
|
|
if (miplump->dataofs[tex->miptex] == -1) {
|
|
return -1;
|
|
}
|
|
|
|
miptex = (miptex_t*)(bsp->dtexdata.base + miplump->dataofs[tex->miptex]);
|
|
|
|
x = fix_coord(texcoord[0], miptex->width);
|
|
y = fix_coord(texcoord[1], miptex->height);
|
|
|
|
data = (byte*)miptex + miptex->offsets[0];
|
|
sample = data[(miptex->width * y) + x];
|
|
|
|
return sample;
|
|
}
|
|
|
|
static qboolean
|
|
TestHitFace(bsp2_dface_t *face, const vec3_t point)
|
|
{
|
|
const dplane_t *plane;
|
|
vec_t d;
|
|
plane = &bsp_static->dplanes[face->planenum];
|
|
d = DotProduct(point, plane->normal) - plane->dist;
|
|
if (d >= -ON_EPSILON && d <= ON_EPSILON)
|
|
{
|
|
int val = PointInFace(face, bsp_static, point);
|
|
if (val)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bsp2_dface_t *
|
|
SearchNodeForHitFace(const bsp2_dnode_t *bspnode, const vec3_t point)
|
|
{
|
|
// search the faces on this node
|
|
int i;
|
|
for (i=0; i<bspnode->numfaces; i++)
|
|
{
|
|
bsp2_dface_t *face;
|
|
face = &bsp_static->dfaces[bspnode->firstface + i];
|
|
if (TestHitFace(face, point)) {
|
|
return face;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ============================================================================
|
|
* LINE TRACING
|
|
* The major lighting operation is a point to point visibility test, performed
|
|
* by recursive subdivision of the line by the BSP tree.
|
|
* ============================================================================
|
|
*/
|
|
|
|
typedef struct {
|
|
vec3_t back;
|
|
vec3_t front;
|
|
int node;
|
|
int side;
|
|
const dplane_t *plane;
|
|
} tracestack_t;
|
|
|
|
/*
|
|
* ==============
|
|
* TraceLine
|
|
* ==============
|
|
*/
|
|
#define MAX_TSTACK 128
|
|
int
|
|
TraceLine(const dmodel_t *model, const int traceflags,
|
|
const vec3_t start, const vec3_t stop, tracepoint_t *hitpoint)
|
|
{
|
|
int node, side, tracehit;
|
|
vec3_t front, back;
|
|
vec_t frontdist, backdist;
|
|
tracestack_t tracestack[MAX_TSTACK];
|
|
tracestack_t *tstack, *crossnode;
|
|
tnode_t *tnode;
|
|
const tracestack_t *const tstack_max = tracestack + MAX_TSTACK;
|
|
qboolean isbmodel;
|
|
|
|
if (traceflags <= 0)
|
|
Error("Internal error: %s - bad traceflags (%d)",
|
|
__func__, traceflags);
|
|
|
|
isbmodel = (model != tracelist[0]);
|
|
|
|
VectorCopy(start, front);
|
|
VectorCopy(stop, back);
|
|
|
|
tstack = tracestack;
|
|
node = model->headnode[0];
|
|
crossnode = NULL;
|
|
tracehit = TRACE_HIT_NONE;
|
|
|
|
while (1) {
|
|
while (node < 0) {
|
|
switch (node) {
|
|
case CONTENTS_SOLID:
|
|
if (traceflags & TRACE_HIT_SOLID)
|
|
tracehit = TRACE_HIT_SOLID;
|
|
break;
|
|
case CONTENTS_WATER:
|
|
if (traceflags & TRACE_HIT_WATER)
|
|
tracehit = TRACE_HIT_WATER;
|
|
break;
|
|
case CONTENTS_SLIME:
|
|
if (traceflags & TRACE_HIT_SLIME)
|
|
tracehit = TRACE_HIT_SLIME;
|
|
break;
|
|
case CONTENTS_LAVA:
|
|
if (traceflags & TRACE_HIT_LAVA)
|
|
tracehit = TRACE_HIT_LAVA;
|
|
break;
|
|
case CONTENTS_SKY:
|
|
if (traceflags & TRACE_HIT_SKY)
|
|
tracehit = TRACE_HIT_SKY;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (tracehit != TRACE_HIT_NONE) {
|
|
qboolean done = true;
|
|
|
|
/* If we haven't crossed, start was inside flagged contents */
|
|
if (!crossnode)
|
|
return -tracehit;
|
|
|
|
if (isbmodel && testFenceTextures) {
|
|
const bsp2_dnode_t *bspnode = tnodes[crossnode->node].node;
|
|
bsp2_dface_t *face = SearchNodeForHitFace(bspnode, crossnode->front);
|
|
|
|
if (face) {
|
|
miptex_t *mt = MiptexForFace(face, bsp_static);
|
|
if (mt && mt->name[0] == '{') {
|
|
int sample = SampleTexture(face, bsp_static, crossnode->front);
|
|
done = (sample != 255);
|
|
} else {
|
|
/* Non-fence texture was hit, we are done */
|
|
done = true;
|
|
}
|
|
} else {
|
|
/* Continue tracing until we find the face we hit */
|
|
done = false;
|
|
}
|
|
}
|
|
|
|
if (done) {
|
|
if (hitpoint) {
|
|
hitpoint->dplane = crossnode->plane;
|
|
hitpoint->side = crossnode->side;
|
|
VectorCopy(crossnode->front, hitpoint->point);
|
|
}
|
|
|
|
return tracehit;
|
|
}
|
|
}
|
|
|
|
/* If the stack is empty, no obstructions were hit */
|
|
if (tstack == tracestack)
|
|
return TRACE_HIT_NONE;
|
|
|
|
/* Pop the stack and go down the back side */
|
|
crossnode = --tstack;
|
|
VectorCopy(tstack->front, front);
|
|
VectorCopy(tstack->back, back);
|
|
node = tnodes[tstack->node].children[!tstack->side];
|
|
}
|
|
|
|
tnode = &tnodes[node];
|
|
switch (tnode->type) {
|
|
case PLANE_X:
|
|
frontdist = front[0] - tnode->dist;
|
|
backdist = back[0] - tnode->dist;
|
|
break;
|
|
case PLANE_Y:
|
|
frontdist = front[1] - tnode->dist;
|
|
backdist = back[1] - tnode->dist;
|
|
break;
|
|
case PLANE_Z:
|
|
frontdist = front[2] - tnode->dist;
|
|
backdist = back[2] - tnode->dist;
|
|
break;
|
|
default:
|
|
frontdist = DotProduct(front, tnode->normal) - tnode->dist;
|
|
backdist = DotProduct(back, tnode->normal) - tnode->dist;
|
|
break;
|
|
}
|
|
|
|
if (frontdist >= -ON_EPSILON && backdist >= -ON_EPSILON) {
|
|
node = tnode->children[0];
|
|
continue;
|
|
}
|
|
if (frontdist < ON_EPSILON && backdist < ON_EPSILON) {
|
|
node = tnode->children[1];
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* If we get here, we have a clean split with front and back on
|
|
* opposite sides. The new back is the intersection point with the
|
|
* node plane. Push the other segment onto the stack and continue.
|
|
*/
|
|
side = frontdist < 0;
|
|
tstack->node = node;
|
|
tstack->side = side;
|
|
tstack->plane = tnode->plane;
|
|
VectorCopy(back, tstack->back);
|
|
VectorSubtract(back, front, back);
|
|
VectorMA(front, frontdist / (frontdist - backdist), back, back);
|
|
VectorCopy(back, tstack->front);
|
|
crossnode = tstack++;
|
|
node = tnode->children[side];
|
|
}
|
|
}
|
|
|
|
qboolean
|
|
TestLight(const vec3_t start, const vec3_t stop, const dmodel_t *self)
|
|
{
|
|
const dmodel_t *const *model;
|
|
const int traceflags = TRACE_HIT_SOLID;
|
|
int result = TRACE_HIT_NONE;
|
|
|
|
/* Check against the list of global shadow casters */
|
|
for (model = tracelist; *model; model++) {
|
|
if (*model == self)
|
|
continue;
|
|
result = TraceLine(*model, traceflags, start, stop, NULL);
|
|
if (result != TRACE_HIT_NONE)
|
|
break;
|
|
}
|
|
|
|
/* If not yet obscured, check against the self-shadow model */
|
|
if (result == TRACE_HIT_NONE && self)
|
|
result = TraceLine(self, traceflags, start, stop, NULL);
|
|
|
|
return (result == TRACE_HIT_NONE);
|
|
}
|
|
|
|
qboolean
|
|
TestSky(const vec3_t start, const vec3_t dirn, const dmodel_t *self)
|
|
{
|
|
const dmodel_t *const *model;
|
|
int traceflags = TRACE_HIT_SKY | TRACE_HIT_SOLID;
|
|
int result = TRACE_HIT_NONE;
|
|
vec3_t stop;
|
|
tracepoint_t hit;
|
|
|
|
/* Trace towards the sunlight for a sky brush */
|
|
VectorAdd(dirn, start, stop);
|
|
result = TraceLine(tracelist[0], traceflags, start, stop, &hit);
|
|
if (result != TRACE_HIT_SKY)
|
|
return false;
|
|
|
|
/* If good, check it isn't shadowed by another model */
|
|
traceflags = TRACE_HIT_SOLID;
|
|
for (model = tracelist + 1; *model; model++) {
|
|
if (*model == self)
|
|
continue;
|
|
result = TraceLine(*model, traceflags, start, hit.point, NULL);
|
|
if (result != TRACE_HIT_NONE)
|
|
return false;
|
|
}
|
|
|
|
/* Check for self-shadowing */
|
|
if (self) {
|
|
result = TraceLine(self, traceflags, start, hit.point, NULL);
|
|
if (result != TRACE_HIT_NONE)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|