ericw-tools/light/trace.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;
}