Paranoia2/utils/p2rad/trace.cpp

1326 lines
33 KiB
C++

/***
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
****/
// trace.c
#include "qrad.h"
#include "..\..\engine\alias.h"
#include "..\..\engine\studio.h"
#include "model_trace.h"
typedef struct moveclip_s
{
vec3_t boxmins, boxmaxs; // enclose the test object along entire move
const float *start, *end;
vec3_t direction;
vec_t distance;
bool nomodels;
entity_t *ignore;
trace_t trace;
tmesh_t *mesh; // mesh trace (may be NULL)
} moveclip_t;
typedef struct tnode_s
{
int type;
vec3_t normal;
float dist;
int children[2];
word firstface;
word numfaces; // counting both sides
} tnode_t;
static tnode_t *tnode_p;
static aabb_tree_t entity_tree;
static int numsolidedicts;
static twface_t *g_world_faces[MAX_MAP_FACES]; // polygons that turned into triangles
/*
=============
SampleMiptex
fence texture testing
=============
*/
int SampleMiptex( const dface_t *face, const vec3_t point )
{
vec_t ds, dt;
byte *data;
int x, y;
dtexinfo_t *tx;
miptex_t *mt;
tx = &g_texinfo[face->texinfo];
mt = GetTextureByMiptex( tx->miptex );
if( !mt ) return CONTENTS_EMPTY;
if( mt->name[0] != '{' )
return CONTENTS_SOLID;
ds = DotProduct( point, tx->vecs[0] ) + tx->vecs[0][3];
dt = DotProduct( point, tx->vecs[1] ) + tx->vecs[1][3];
// convert ST to real pixels position
x = fix_coord( ds, mt->width - 1 );
y = fix_coord( dt, mt->height - 1 );
ASSERT( x >= 0 && y >= 0 );
data = ((byte *)mt) + mt->offsets[0];
if( data[(mt->width * y) + x] == 255 )
return CONTENTS_EMPTY;
return CONTENTS_SOLID;
}
/*
==================
MoveBounds
==================
*/
void MoveBounds( const vec3_t start, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs )
{
for( int i = 0; i < 3; i++ )
{
if( end[i] > start[i] )
{
boxmins[i] = start[i] - 1.0f;
boxmaxs[i] = end[i] + 1.0f;
}
else
{
boxmins[i] = end[i] - 1.0f;
boxmaxs[i] = start[i] + 1.0f;
}
}
}
/*
==================
CombineTraces
==================
*/
trace_t CombineTraces( trace_t *cliptrace, trace_t *trace )
{
if( trace->fraction < cliptrace->fraction )
*cliptrace = *trace;
return *cliptrace;
}
/*
==============
MakeTnode
Converts the disk node structure into the efficient tracing structure
==============
*/
void MakeTnode( tnode_t *head, int nodenum )
{
dplane_t *plane;
dnode_t *node;
tnode_t *t;
t = tnode_p++;
node = g_dnodes + nodenum;
plane = g_dplanes + node->planenum;
t->type = plane->type;
VectorCopy( plane->normal, t->normal );
t->dist = plane->dist;
t->firstface = node->firstface;
t->numfaces = node->numfaces;
for( int i = 0; i < 2; i++ )
{
if( node->children[i] < 0 )
{
t->children[i] = g_dleafs[-node->children[i] - 1].contents;
}
else
{
t->children[i] = tnode_p - head;
MakeTnode( head, node->children[i] );
}
}
}
/*
=============
CountTnodes_r
count nodes for a given model
=============
*/
static void CountTnodes_r( int &total, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
total++;
CountTnodes_r( total, g_dnodes[nodenum].children[0] );
CountTnodes_r( total, g_dnodes[nodenum].children[1] );
}
/*
=============
MakeTnodes
Loads the node structure out of a .bsp file to be used for light occlusion
=============
*/
static void MakeTnodes( entity_t *ent, dmodel_t *mod )
{
int numnodes = 0;
CountTnodes_r( numnodes, mod->headnode[0] );
if( ent->cache ) Mem_Free( ent->cache );
ent->cache = Mem_Alloc(( numnodes + 1 ) * sizeof( tnode_t ));
tnode_p = (tnode_t *)ent->cache;
MakeTnode( tnode_p, mod->headnode[0] );
ent->modtype = mod_brush;
}
//==========================================================
/*
==================
ClipMoveToTriangle
Handles selection or creation of a clipping hull, and offseting (and
eventually rotation) of the end points
==================
*/
void ClipMoveToTriangle( tface_t *face, moveclip_t *clip )
{
// compute plane intersection
float DDotN = DotProduct( clip->direction, face->normal );
// while vertex lighting is disabled we should lighting space
// under model so R_LightVec will working correctly
if( !FBitSet( clip->mesh->flags, FMESH_VERTEX_LIGHTING|FMESH_MODEL_LIGHTMAPS ))
{
if( !FBitSet( face->texture->flags, STUDIO_NF_TWOSIDE ))
{
// NOTE: probably we should normalize the n but for speed reasons i don't do it
if( DDotN >= FLT_EPSILON ) return;
}
}
// mask off zero or near zero (ray parallel to surface)
bool did_hit = (( DDotN > FLT_EPSILON ) || ( DDotN < -FLT_EPSILON ));
if( !did_hit ) return; // to prevent division by zero
float numerator = face->NdotP1 - DotProduct( clip->start, face->normal );
float isect_t = numerator / DDotN; // fraction
float frac = isect_t / clip->distance; // remap to [0..1]
// now, we have the distance to the plane. lets update our mask
did_hit = did_hit && ( frac > clip->mesh->backfrac );
did_hit = did_hit && ( frac < clip->trace.fraction );
if( !did_hit ) return;
// now, check 3 edges
float hitc1 = clip->start[face->pcoord0] + ( isect_t * clip->direction[face->pcoord0] );
float hitc2 = clip->start[face->pcoord1] + ( isect_t * clip->direction[face->pcoord1] );
// do barycentric coordinate check
float B0 = face->edge1[0] * hitc1 + face->edge1[1] * hitc2 + face->edge1[2];
did_hit = did_hit && ( B0 >= 0.0f );
float B1 = face->edge2[0] * hitc1 + face->edge2[1] * hitc2 + face->edge2[2];
did_hit = did_hit && ( B1 >= 0.0f );
float B2 = B0 + B1;
did_hit = did_hit && ( B2 <= 1.0f );
if( !did_hit ) return;
// if the triangle is transparent
if( face->texture->data )
{
float u = 1.0 - B2;
float v = B0;
float w = B1;
// assuming a triangle indexed as v0, v1, v2
// the projected edge equations are set up such that the vert opposite the first
// equation is v2, and the vert opposite the second equation is v0
// Therefore we pass them back in 1, 2, 0 order
// Also B2 is currently B1 + B0 and needs to be 1 - (B1+B0) in order to be a real
// barycentric coordinate. Compute that now and pass it to the callback
float s = w * clip->mesh->verts[face->a].st[0] + u * clip->mesh->verts[face->b].st[0] + v * clip->mesh->verts[face->c].st[0];
float t = w * clip->mesh->verts[face->a].st[1] + u * clip->mesh->verts[face->b].st[1] + v * clip->mesh->verts[face->c].st[1];
// convert ST to real pixels position
int x = fix_coord( s * face->texture->width, face->texture->width - 1 );
int y = fix_coord( t * face->texture->height, face->texture->height - 1 );
// test pixel
if( face->texture->data[(face->texture->width * y) + x] == 255 )
return;
}
if( clip->trace.fraction > frac )
{
// at this point we hit the opaque pixel
clip->trace.fraction = bound( 0.0, frac, 1.0 );
clip->trace.contents = face->contents;
}
}
//==========================================================
#define HLRAD_TRACE_FACES
/*
==================
TestLine_r
==================
*/
int TestLine_r( tnode_t *head, int node, vec_t p1f, vec_t p2f, const vec3_t start, const vec3_t stop, trace_t *trace )
{
tnode_t *tnode;
float front, back;
float frac, midf;
int r, side;
vec3_t mid;
loc0:
if( node < 0 )
{
// water, slime or lava interpret as empty
if( node == CONTENTS_SOLID )
return CONTENTS_SOLID;
if( node == CONTENTS_SKY )
return CONTENTS_SKY;
trace->fraction = 1.0f;
return CONTENTS_EMPTY;
}
tnode = &head[node];
front = PlaneDiff( start, tnode );
back = PlaneDiff( stop, tnode );
#ifdef HLRAD_TestLine_EDGE_FIX
if( front > FRAC_EPSILON / 2 && back > FRAC_EPSILON / 2 )
{
node = tnode->children[0];
goto loc0;
}
if( front < -FRAC_EPSILON / 2 && back < -FRAC_EPSILON / 2 )
{
node = tnode->children[1];
goto loc0;
}
if( fabs( front ) <= FRAC_EPSILON && fabs( back ) <= FRAC_EPSILON )
{
int r1 = TestLine_r( head, tnode->children[0], p1f, p2f, start, stop, trace );
if( r1 == CONTENTS_SOLID )
{
trace->contents = r1;
return CONTENTS_SOLID;
}
int r2 = TestLine_r( head, tnode->children[1], p1f, p2f, start, stop, trace );
if( r2 == CONTENTS_SOLID )
{
trace->contents = r2;
return CONTENTS_SOLID;
}
if( r1 == CONTENTS_SKY || r2 == CONTENTS_SKY )
{
trace->contents = r2;
return CONTENTS_SKY;
}
trace->contents = CONTENTS_EMPTY;
trace->fraction = 1.0f;
return CONTENTS_EMPTY;
}
side = (front - back) < 0;
frac = front / (front - back);
frac = bound( 0.0, frac, 1.0 );
#else
if( front >= -FRAC_EPSILON && back >= -FRAC_EPSILON )
{
node = tnode->children[0];
goto loc0;
}
if( front < FRAC_EPSILON && back < FRAC_EPSILON )
{
node = tnode->children[1];
goto loc0;
}
side = (front < 0);
frac = front / (front - back);
frac = bound( 0.0, frac, 1.0 );
#endif
VectorLerp( start, frac, stop, mid );
midf = p1f + ( p2f - p1f ) * frac;
r = TestLine_r( head, tnode->children[side], p1f, midf, start, mid, trace );
// trace back faces (in case point was inside of brush)
if( r != CONTENTS_EMPTY )
{
if( trace->surface == -1 )
trace->fraction = midf;
trace->contents = r;
return r;
}
#ifdef HLRAD_TRACE_FACES
// walk through real faces
for( int i = 0; i < tnode->numfaces; i++ )
{
twface_t *wf = g_world_faces[tnode->firstface + i];
int contents;
vec3_t delta;
if( !wf || wf->contents == CONTENTS_SKY )
continue;
VectorSubtract( mid, wf->origin, delta );
if( DotProduct( delta, delta ) >= wf->radius )
continue; // no intersection
for( int j = 0; j < wf->numedges; j++ )
{
if( PlaneDiff( mid, &wf->edges[j] ) > FRAC_EPSILON )
break; // outside the bounds
}
if( j != wf->numedges )
continue; // we are outside the bounds of the facet
// hit the surface
if( FBitSet( wf->flags, TEX_ALPHATEST ))
{
contents = SampleMiptex( wf->original, mid );
if( contents == CONTENTS_EMPTY )
{
// traced through fence
trace->contents = contents;
trace->fraction = midf;
return contents;
}
}
else contents = wf->contents; // sky or solid
if( contents != CONTENTS_EMPTY )
{
// fill the trace and out
trace->surface = tnode->firstface + i;
trace->contents = contents;
trace->fraction = midf;
return contents;
}
}
#endif
return TestLine_r( head, tnode->children[!side], midf, p2f, mid, stop, trace );
}
/*
====================
ClipToTriangles
Mins and maxs enclose the entire area swept by the move
====================
*/
static void ClipToTriangles( areanode_t *node, moveclip_t *clip )
{
link_t *l, *next;
tface_t *touch;
loc0:
// touch linked edicts
for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )
{
next = l->next;
touch = TFACE_FROM_AREA( l );
if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->absmin, touch->absmax ))
continue;
// might intersect, so do an exact clip
if( clip->trace.contents == CONTENTS_SOLID )
return;
ClipMoveToTriangle( touch, clip );
}
// recurse down both sides
if( node->axis == -1 ) return;
if( clip->boxmaxs[node->axis] > node->dist)
{
if( clip->boxmins[node->axis] < node->dist )
ClipToTriangles( node->children[1], clip );
node = node->children[0];
goto loc0;
}
else if( clip->boxmins[node->axis] < node->dist )
{
node = node->children[1];
goto loc0;
}
}
/*
===============
UnlinkEdict
just in case for pair
===============
*/
static void UnlinkEdict( entity_t *ent )
{
// not linked in anywhere
if( !ent->area.prev ) return;
RemoveLink( &ent->area );
ent->area.prev = NULL;
ent->area.next = NULL;
numsolidedicts--;
}
/*
===============
LinkEdict
===============
*/
static void LinkEdict( entity_t *ent, modtype_t modtype, const char *modname, int flags = 0 )
{
vec3_t mins, maxs;
void *filedata = NULL;
areanode_t *node;
dmodel_t *bm;
if( ent->area.prev ) UnlinkEdict( ent ); // unlink from old position
if( ent == g_entities ) return; // don't add the world
// set the origin
GetVectorForKey( ent, "origin", ent->origin );
// also check lightorigin for brushmodels
if( modtype == mod_brush )
{
bool b_light_origin = false;
bool b_model_center = false;
vec3_t light_origin;
vec3_t model_center;
char *s;
// allow models to be lit in an alternate location (pt1)
if( *( s = ValueForKey( ent, "light_origin" )))
{
entity_t *e = FindTargetEntity( s );
if( e )
{
if( *( s = ValueForKey( e, "origin" )))
{
double v1, v2, v3;
if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 )
{
VectorSet( light_origin, v1, v2, v3 );
b_light_origin = true;
}
}
}
}
// allow models to be lit in an alternate location (pt2)
if( *( s = ValueForKey( ent, "model_center" )))
{
double v1, v2, v3;
if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 )
{
VectorSet( model_center, v1, v2, v3 );
b_model_center = true;
}
}
// allow models to be lit in an alternate location (pt3)
if( b_light_origin && b_model_center )
{
VectorSubtract( light_origin, model_center, ent->origin );
}
}
if( ent->modtype == mod_unknown )
{
size_t fileLength = 0;
// init trace nodes
switch( modtype )
{
case mod_brush:
bm = ModelForEntity( ent );
if( !bm ) COM_FatalError( "LinkEdict: not a inline bmodel!\n" );
MakeTnodes( ent, bm );
break;
default:
// trying to recognize file format by it's header
if( !g_nomodelshadow )
filedata = FS_LoadFile( modname, &fileLength, false );
break;
}
if( filedata != NULL )
{
MsgDev( D_NOTE, "loading %s\n", modname );
// call the apropriate loader
switch( *(uint *)filedata )
{
case IDSTUDIOHEADER:
LoadStudio( ent, filedata, fileLength, flags );
break;
case IDALIASHEADER:
LoadAlias( ent, filedata, fileLength, flags );
break;
default:
MsgDev( D_ERROR, "%s unknown file format\n", modname );
Mem_Free( filedata, C_FILESYSTEM );
break;
}
}
}
// entity failed to load model
if( ent->modtype == mod_unknown )
return;
if( ent->modtype == mod_brush )
{
bm = ModelForEntity( ent );
// set the abs box
VectorAdd( ent->origin, bm->mins, ent->absmin );
VectorAdd( ent->origin, bm->maxs, ent->absmax );
ExpandBounds( ent->absmin, ent->absmax, 1.0 );
}
else if( ent->modtype == mod_studio )
{
StudioGetBounds( ent, mins, maxs );
VectorAdd( ent->origin, mins, ent->absmin );
VectorAdd( ent->origin, maxs, ent->absmax );
ExpandBounds( ent->absmin, ent->absmax, 1.0 );
}
else if( ent->modtype == mod_alias )
{
AliasGetBounds( ent, mins, maxs );
VectorAdd( ent->origin, mins, ent->absmin );
VectorAdd( ent->origin, maxs, ent->absmax );
ExpandBounds( ent->absmin, ent->absmax, 1.0 );
}
else
{
return;
}
// don't link into world if shadow was disabled
if(( ent->modtype == mod_studio || ent->modtype == mod_alias ) && !FBitSet( flags, FMESH_CAST_SHADOW ))
return;
#ifdef HLRAD_RAYTRACE
if( ent->modtype == mod_studio || ent->modtype == mod_alias )
{
tmesh_t *mesh = (tmesh_t *)ent->cache;
for( int i = 0; i < mesh->numfaces; i++ )
mesh->ray.AddTriangle( &mesh->faces[i] );
mesh->ray.BuildTree( mesh );
// set backfraction if specified
mesh->ray.SetBackFraction( FloatForKey( ent, "zhlt_backfrac" ));
}
#endif
// find the first node that the ent's box crosses
node = entity_tree.areanodes;
while( 1 )
{
if( node->axis == -1 ) break;
if( ent->absmin[node->axis] > node->dist )
node = node->children[0];
else if( ent->absmax[node->axis] < node->dist )
node = node->children[1];
else break; // crosses the node
}
// link it in
InsertLinkBefore( &ent->area, &node->solid_edicts );
numsolidedicts++;
}
dvertex_t *GetVertexByNumber( int vertexnum )
{
ASSERT( vertexnum >= 0 && vertexnum < g_numsurfedges );
int e = g_dsurfedges[vertexnum];
dvertex_t *v;
if( e >= 0 ) v = &g_dvertexes[g_dedges[e].v[0]];
else v = &g_dvertexes[g_dedges[-e].v[1]];
return v;
}
void GetTexCoordsForPoint( const vec3_t point, dtexinfo_t *tex, miptex_t *mt, float stcoord[2] )
{
float s, t;
// texture coordinates
s = DotProduct( point, tex->vecs[0] ) + tex->vecs[0][3];
if( mt ) s /= mt->width;
t = DotProduct( point, tex->vecs[1] ) + tex->vecs[1][3];
if( mt ) t /= mt->height;
stcoord[0] = s;
stcoord[1] = t;
}
/*
===============
BuildWorldFaces
===============
*/
void BuildWorldFaces( void )
{
// store a list of every face that uses a particular vertex
for( int facenum = 0; facenum < g_numfaces; facenum++ )
{
vec3_t delta, edgevec;
const dplane_t *faceplane;
byte *facedata;
int contents;
int i, size;
dtexinfo_t *tx;
miptex_t *mt;
twface_t *wf;
const dface_t *f;
f = &g_dfaces[facenum];
tx = &g_texinfo[f->texinfo];
mt = GetTextureByMiptex( tx->miptex );
if( mt ) contents = GetFaceContents( mt->name );
else contents = CONTENTS_SOLID;
if( IsLiquidContents( contents )/* || contents == CONTENTS_SKY*/ || FBitSet( tx->flags, TEX_NOSHADOW ))
continue; // ignore non-lightmapped surfaces
size = sizeof( twface_t ) + f->numedges * sizeof( dplane_t );
facedata = (byte *)Mem_Alloc( size );
wf = (twface_t *)facedata;
facedata += sizeof( twface_t );
wf->edges = (dplane_t *)facedata;
facedata += f->numedges * sizeof( dplane_t );
g_world_faces[facenum] = wf;
wf->numedges = f->numedges;
wf->contents = contents;
wf->flags = tx->flags;
wf->original = f;
if( mt && mt->name[0] == '{' )
SetBits( wf->flags, TEX_ALPHATEST );
faceplane = GetPlaneFromFace( facenum );
// compute face origin and plane edges
for( i = 0; i < f->numedges; i++ )
{
dplane_t *dest = &wf->edges[i];
vec3_t v0, v1;
VectorCopy( GetVertexByNumber( f->firstedge + i )->point, v0 );
VectorCopy( GetVertexByNumber( f->firstedge + (i + 1) % f->numedges )->point, v1 );
VectorSubtract( v1, v0, edgevec );
CrossProduct( faceplane->normal, edgevec, dest->normal );
VectorNormalize( dest->normal );
dest->dist = DotProduct( dest->normal, v0 );
dest->type = PlaneTypeForNormal( dest->normal );
VectorAdd( wf->origin, v0, wf->origin );
}
VectorScale( wf->origin, 1.0f / f->numedges, wf->origin );
// compute face radius
for( i = 0; i < f->numedges; i++ )
{
vec3_t v0;
VectorCopy( GetVertexByNumber( f->firstedge + i )->point, v0 );
VectorSubtract( v0, wf->origin, delta );
vec_t radius = DotProduct( delta, delta );
wf->radius = Q_max( radius, wf->radius );
}
}
}
void FreeWorldFaces( void )
{
// store a list of every face that uses a particular vertex
for( int facenum = 0; facenum < g_numfaces; facenum++ )
{
Mem_Free( g_world_faces[facenum] );
g_world_faces[facenum] = NULL;
}
}
/*
===============
InitWorldTrace
===============
*/
void InitWorldTrace( void )
{
int i;
memset( &entity_tree, 0, sizeof( entity_tree ));
CreateAreaNode( &entity_tree, 0, AREA_MIN_DEPTH, g_dmodels[0].mins, g_dmodels[0].maxs );
BuildWorldFaces(); // init world faces
MakeTnodes( g_entities, g_dmodels ); // init world nodes
// link entities into world
for( i = 1; i < g_numentities; i++ )
{
entity_t *e = &g_entities[i];
const char *c = ValueForKey( e, "classname" );
const char *t = ValueForKey( e, "model" );
int flags = 0;
if( !*t ) continue;
// drop to floor env_static's
if( !Q_strcmp( c, "env_static" ))
{
int spawnflags = IntForKey( e, "spawnflags" );
// drop static to the floor
if( FBitSet( spawnflags, BIT( 1 )))
{
vec3_t end;
// set the origin
GetVectorForKey( e, "origin", e->origin );
VectorCopy( e->origin, end );
if( PointInLeaf( end )->contents != CONTENTS_SOLID )
{
// delicate drop-to-floor
for( int j = 0; j < 256; j++ )
{
if( PointInLeaf( end )->contents == CONTENTS_SOLID )
break; // we hit the floor
end[2] -= 1.0f;
}
if( j != 256 )
{
SetVectorForKey( e, "origin", end, true );
MsgDev( D_REPORT, "%s origin adjusted to -%g units\n", t, e->origin[2] - end[2] );
ClearBits( spawnflags, BIT( 1 ));
SetKeyValue( e, "spawnflags", va( "%i", spawnflags ));
}
}
}
}
}
#ifdef HLRAD_RAYTRACE
int dispatch = 0, total = 0;
double start = I_FloatTime();
Msg( "Build KD-Trees:\n" );
StartPacifier();
for( i = 1; i < g_numentities; i++ )
{
entity_t *e = &g_entities[i];
const char *c = ValueForKey( e, "classname" );
const char *t = ValueForKey( e, "model" );
int flags = 0;
if( !*t ) continue;
// both support for quake and half-life
if( !Q_strcmp( c, "env_static" ) || !Q_strcmp( c, "misc_model" ))
{
int spawnflags = IntForKey( e, "spawnflags" );
if( !FBitSet( spawnflags, BIT( 2 )))
total++;
}
else if( BoolForKey( e, "zhlt_modelshadow" ) || BoolForKey( e, "zhlt_vertexlight" ))
{
if( BoolForKey( e, "zhlt_modelshadow" ))
total++;
}
}
#endif
// link entities into world
for( i = 1; i < g_numentities; i++ )
{
entity_t *e = &g_entities[i];
const char *c = ValueForKey( e, "classname" );
const char *t = ValueForKey( e, "model" );
int flags = 0;
if( !*t ) continue;
// both support for quake and half-life
if( !Q_strcmp( c, "env_static" ) || !Q_strcmp( c, "misc_model" ))
{
int spawnflags = IntForKey( e, "spawnflags" );
if( !FBitSet( spawnflags, BIT( 2 ))) // spawnflag 4 - disable the shadows
SetBits( flags, FMESH_CAST_SHADOW );
if( BoolForKey( e, "zhlt_modellightmap" ))
SetBits( flags, FMESH_MODEL_LIGHTMAPS );
else if( !FBitSet( spawnflags, BIT( 3 ))) // spawnflag 8 - disable the vertex lighting
SetBits( flags, FMESH_VERTEX_LIGHTING );
if( BoolForKey( e, "zhlt_selfshadow" ))
SetBits( flags, FMESH_SELF_SHADOW );
if( BoolForKey( e, "zhlt_nosmooth" ))
SetBits( flags, FMESH_DONT_SMOOTH );
LinkEdict( e, mod_unknown, t, flags );
}
else if( BoolForKey( e, "zhlt_modelshadow" ) || BoolForKey( e, "zhlt_vertexlight" ))
{
// NOTE: compatibility case for GoldSrc
if( BoolForKey( e, "zhlt_modellightmap" ))
SetBits( flags, FMESH_MODEL_LIGHTMAPS );
else if( BoolForKey( e, "zhlt_vertexlight" ))
SetBits( flags, FMESH_VERTEX_LIGHTING );
if( BoolForKey( e, "zhlt_modelshadow" ))
SetBits( flags, FMESH_CAST_SHADOW );
if( BoolForKey( e, "zhlt_selfshadow" ))
SetBits( flags, FMESH_SELF_SHADOW );
if( BoolForKey( e, "zhlt_nosmooth" ))
SetBits( flags, FMESH_DONT_SMOOTH );
LinkEdict( e, mod_unknown, t, flags );
}
else if( *t == '*' )
{
int flags = IntForKey( e, "zhlt_lightflags" );
int shadow = IntForKey( e, "_shadow" );
if( FBitSet( flags, BIT( 1 )) || shadow == 1 )
LinkEdict( e, mod_brush, t );
}
#ifdef HLRAD_RAYTRACE
if(( e->modtype == mod_studio || e->modtype == mod_alias ) && FBitSet( flags, FMESH_CAST_SHADOW ))
{
dispatch++;
UpdatePacifier( (float)dispatch / total );
}
#endif
}
#ifdef HLRAD_RAYTRACE
double end = I_FloatTime();
EndPacifier( end - start );
#endif
}
void FreeWorldTrace( void )
{
FreeWorldFaces();
}
/*
===============================================================================
LINE TESTING IN HULLS
===============================================================================
*/
/*
==================
ClipMoveToEntity
Handles selection or creation of a clipping hull, and offseting (and
eventually rotation) of the end points
==================
*/
void ClipMoveToEntity( entity_t *ent, const vec3_t start, const vec3_t end, trace_t *trace )
{
trace->contents = CONTENTS_EMPTY;
trace->fraction = 1.0f;
trace->surface = -1;
if( ent->modtype == mod_studio || ent->modtype == mod_alias )
{
moveclip_t clip;
clip.mesh = (tmesh_t *)ent->cache;
clip.trace.contents = CONTENTS_EMPTY;
clip.trace.fraction = 1.0f;
clip.trace.surface = -1;
clip.start = start;
clip.end = end;
MoveBounds( start, end, clip.boxmins, clip.boxmaxs );
VectorSubtract( end, start, clip.direction );
clip.distance = VectorNormalize( clip.direction );
// run through studio triangles
#ifdef HLRAD_RAYTRACE
clip.mesh->ray.TraceRay( start, end, &clip.trace );
#else
ClipToTriangles( clip.mesh->face_tree.areanodes, &clip );
#endif
trace->contents = clip.trace.contents;
trace->fraction = clip.trace.fraction;
}
else if( ent->modtype == mod_brush )
{
vec3_t start_l, end_l;
VectorSubtract( start, ent->origin, start_l );
VectorSubtract( end, ent->origin, end_l );
trace->fraction = 0.0f;
TestLine_r((tnode_t *)ent->cache, 0, 0.0, 1.0, start_l, end_l, trace );
}
else
{
COM_FatalError( "ClipMoveToEntity: unknown model type\n" );
}
}
/*
====================
ClipToLinks
Mins and maxs enclose the entire area swept by the move
====================
*/
static void ClipToLinks( areanode_t *node, moveclip_t *clip )
{
link_t *l, *next;
entity_t *touch;
trace_t trace;
loc0:
// touch linked edicts
for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )
{
next = l->next;
touch = ENTITY_FROM_AREA( l );
if( touch == clip->ignore )
continue;
if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->absmin, touch->absmax ))
continue;
// might intersect, so do an exact clip
if( clip->trace.contents == CONTENTS_SOLID )
return;
if( clip->nomodels && ( touch->modtype == mod_studio || touch->modtype == mod_alias ))
continue;
ClipMoveToEntity( touch, clip->start, clip->end, &trace );
clip->trace = CombineTraces( &clip->trace, &trace );
}
// recurse down both sides
if( node->axis == -1 ) return;
if( clip->boxmaxs[node->axis] > node->dist)
{
if( clip->boxmins[node->axis] < node->dist )
ClipToLinks( node->children[1], clip );
node = node->children[0];
goto loc0;
}
else if( clip->boxmins[node->axis] < node->dist )
{
node = node->children[1];
goto loc0;
}
}
/*
==================
TraceLine
==================
*/
int TestLine( int threadnum, const vec3_t start, const vec3_t end, bool nomodels, entity_t *ignoreent )
{
moveclip_t clip;
clip.trace.contents = CONTENTS_EMPTY;
clip.trace.fraction = 0.0f;
clip.trace.surface = -1;
// trace world first
TestLine_r( (tnode_t *)g_entities->cache, 0, 0.0f, 1.0f, start, end, &clip.trace );
// run through entities (bmodels, studiomodels)
if(( numsolidedicts > 0 ) && ( clip.trace.fraction != 0.0f ))
{
float trace_fraction;
vec3_t trace_endpos;
VectorLerp( start, clip.trace.fraction, end, trace_endpos );
trace_fraction = clip.trace.fraction;
clip.trace.fraction = 1.0f;
clip.nomodels = nomodels;
clip.start = start;
clip.end = trace_endpos;
clip.ignore = ignoreent;
MoveBounds( start, trace_endpos, clip.boxmins, clip.boxmaxs );
ClipToLinks( entity_tree.areanodes, &clip );
clip.trace.fraction *= trace_fraction;
}
return clip.trace.contents;
}
/*
==================
TestLine
trace world only
==================
*/
void TestLine( int threadnum, const vec3_t start, const vec3_t stop, trace_t *trace )
{
trace->contents = CONTENTS_EMPTY;
trace->fraction = 0.0f;
trace->surface = -1;
// trace world first
TestLine_r( (tnode_t *)g_entities->cache, 0, 0.0f, 1.0f, start, stop, trace );
}
typedef double point_t[3];
typedef struct
{
int point[2];
bool divided;
int child[2];
} edge_t;
typedef struct
{
int edge[3];
int dir[3];
} triangle_t;
void CopyToSkynormals( int skylevel, int numpoints, point_t *points, int numedges, edge_t *edges, int numtriangles, triangle_t *triangles )
{
double totalsize = 0;
int j, k;
ASSERT( numpoints == ( 1 << ( 2 * skylevel )) + 2 );
ASSERT( numedges == ( 1 << ( 2 * skylevel )) * 4 - 4 );
ASSERT( numtriangles == ( 1 << ( 2 * skylevel )) * 2 );
g_skynormalsizes[skylevel] = (vec_t *)Mem_Alloc( numpoints * sizeof( vec_t ));
g_skynormals[skylevel] = (vec3_t *)Mem_Alloc( numpoints * sizeof( vec3_t ));
g_numskynormals[skylevel] = numpoints;
for( j = 0; j < numpoints; j++ )
{
VectorCopy( points[j], g_skynormals[skylevel][j] );
g_skynormalsizes[skylevel][j] = 0;
}
for( j = 0; j < numtriangles; j++ )
{
double currentsize;
double tmp[3];
int pt[3];
for( k = 0; k < 3; k++ )
pt[k] = edges[triangles[j].edge[k]].point[triangles[j].dir[k]];
CrossProduct( points[pt[0]], points[pt[1]], tmp );
currentsize = DotProduct( tmp, points[pt[2]] );
ASSERT( currentsize > 0 );
g_skynormalsizes[skylevel][pt[0]] += currentsize / 3.0;
g_skynormalsizes[skylevel][pt[1]] += currentsize / 3.0;
g_skynormalsizes[skylevel][pt[2]] += currentsize / 3.0;
totalsize += currentsize;
}
for( j = 0; j < numpoints; j++ )
{
g_skynormalsizes[skylevel][j] /= totalsize;
}
}
void BuildDiffuseNormals( void )
{
int numpoints = 6;
int numedges = 12;
int numtriangles = 8;
int i, j, k;
g_numskynormals[0] = 0;
g_skynormals[0] = NULL; //don't use this
g_skynormalsizes[0] = NULL;
point_t *points = (point_t *)Mem_Alloc((( 1 << ( 2 * SKYLEVELMAX )) + 2 ) * sizeof( point_t ), C_TEMPORARY );
edge_t *edges = (edge_t *)Mem_Alloc((( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 ) * sizeof( edge_t ), C_TEMPORARY );
triangle_t *triangles = (triangle_t *)Mem_Alloc((( 1 << ( 2 * SKYLEVELMAX )) * 2 ) * sizeof( triangle_t ), C_TEMPORARY );
VectorSet( points[0], 1.0, 0.0, 0.0 );
VectorSet( points[1],-1.0, 0.0, 0.0 );
VectorSet( points[2], 0.0, 1.0, 0.0 );
VectorSet( points[3], 0.0,-1.0, 0.0 );
VectorSet( points[4], 0.0, 0.0, 1.0 );
VectorSet( points[5], 0.0, 0.0,-1.0 );
edges[0].point[0] = 0, edges[0].point[1] = 2, edges[0].divided = false;
edges[1].point[0] = 2, edges[1].point[1] = 1, edges[1].divided = false;
edges[2].point[0] = 1, edges[2].point[1] = 3, edges[2].divided = false;
edges[3].point[0] = 3, edges[3].point[1] = 0, edges[3].divided = false;
edges[4].point[0] = 2, edges[4].point[1] = 4, edges[4].divided = false;
edges[5].point[0] = 4, edges[5].point[1] = 3, edges[5].divided = false;
edges[6].point[0] = 3, edges[6].point[1] = 5, edges[6].divided = false;
edges[7].point[0] = 5, edges[7].point[1] = 2, edges[7].divided = false;
edges[8].point[0] = 4, edges[8].point[1] = 0, edges[8].divided = false;
edges[9].point[0] = 0, edges[9].point[1] = 5, edges[9].divided = false;
edges[10].point[0] = 5, edges[10].point[1] = 1, edges[10].divided = false;
edges[11].point[0] = 1, edges[11].point[1] = 4, edges[11].divided = false;
triangles[0].edge[0] = 0, triangles[0].dir[0] = 0, triangles[0].edge[1] = 4;
triangles[0].dir[1] = 0, triangles[0].edge[2] = 8, triangles[0].dir[2] = 0;
triangles[1].edge[0] = 1, triangles[1].dir[0] = 0, triangles[1].edge[1] = 11;
triangles[1].dir[1] = 0, triangles[1].edge[2] = 4, triangles[1].dir[2] = 1;
triangles[2].edge[0] = 2, triangles[2].dir[0] = 0, triangles[2].edge[1] = 5;
triangles[2].dir[1] = 1, triangles[2].edge[2] = 11, triangles[2].dir[2] = 1;
triangles[3].edge[0] = 3, triangles[3].dir[0] = 0, triangles[3].edge[1] = 8;
triangles[3].dir[1] = 1, triangles[3].edge[2] = 5, triangles[3].dir[2] = 0;
triangles[4].edge[0] = 0, triangles[4].dir[0] = 1, triangles[4].edge[1] = 9;
triangles[4].dir[1] = 0, triangles[4].edge[2] = 7, triangles[4].dir[2] = 0;
triangles[5].edge[0] = 1, triangles[5].dir[0] = 1, triangles[5].edge[1] = 7;
triangles[5].dir[1] = 1, triangles[5].edge[2] = 10, triangles[5].dir[2] = 0;
triangles[6].edge[0] = 2, triangles[6].dir[0] = 1, triangles[6].edge[1] = 10;
triangles[6].dir[1] = 1, triangles[6].edge[2] = 6, triangles[6].dir[2] = 1;
triangles[7].edge[0] = 3, triangles[7].dir[0] = 1, triangles[7].edge[1] = 6;
triangles[7].dir[1] = 0, triangles[7].edge[2] = 9, triangles[7].dir[2] = 1;
CopyToSkynormals( 1, numpoints, points, numedges, edges, numtriangles, triangles );
for( i = 1; i < SKYLEVELMAX; i++ )
{
int oldnumtriangles = numtriangles;
int oldnumedges = numedges;
for( j = 0; j < oldnumedges; j++ )
{
if( !edges[j].divided )
{
point_t mid;
double len;
int p2;
ASSERT( numpoints < ( 1 << ( 2 * SKYLEVELMAX )) + 2 );
VectorAdd( points[edges[j].point[0]], points[edges[j].point[1]], mid );
len = VectorLength( mid );
ASSERT( len > 0.2 );
VectorScale( mid, 1.0 / len, mid );
p2 = numpoints;
VectorCopy( mid, points[numpoints] );
numpoints++;
ASSERT( numedges < ( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 );
edges[j].child[0] = numedges;
edges[numedges].divided = false;
edges[numedges].point[0] = edges[j].point[0];
edges[numedges].point[1] = p2;
numedges++;
ASSERT( numedges < ( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 );
edges[j].child[1] = numedges;
edges[numedges].divided = false;
edges[numedges].point[0] = p2;
edges[numedges].point[1] = edges[j].point[1];
numedges++;
edges[j].divided = true;
}
}
for( j = 0; j < oldnumtriangles; j++ )
{
int mid[3];
for( k = 0; k < 3; k++ )
{
ASSERT( numtriangles < ( 1 << ( 2 * SKYLEVELMAX )) * 2 );
mid[k] = edges[edges[triangles[j].edge[k]].child[0]].point[1];
triangles[numtriangles].edge[0] = edges[triangles[j].edge[k]].child[1 - triangles[j].dir[k]];
triangles[numtriangles].dir[0] = triangles[j].dir[k];
triangles[numtriangles].edge[1] = edges[triangles[j].edge[(k+1)%3]].child[triangles[j].dir[(k+1)%3]];
triangles[numtriangles].dir[1] = triangles[j].dir[(k+1)%3];
triangles[numtriangles].edge[2] = numedges + k;
triangles[numtriangles].dir[2] = 1;
numtriangles++;
}
for( k = 0; k < 3; k++ )
{
ASSERT( numedges < ( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 );
triangles[j].edge[k] = numedges;
triangles[j].dir[k] = 0;
edges[numedges].divided = false;
edges[numedges].point[0] = mid[k];
edges[numedges].point[1] = mid[(k+1)%3];
numedges++;
}
}
CopyToSkynormals( i + 1, numpoints, points, numedges, edges, numtriangles, triangles );
}
Mem_Free( triangles, C_TEMPORARY );
Mem_Free( points, C_TEMPORARY );
Mem_Free( edges, C_TEMPORARY );
}
void FreeDiffuseNormals( void )
{
for( int i = 0; i < SKYLEVELMAX + 1; i++ )
{
Mem_Free( g_skynormalsizes[i] );
Mem_Free( g_skynormals[i] );
g_skynormalsizes[i] = NULL;
g_skynormals[i] = NULL;
}
}