/*** * * 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; } }