mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-25 11:19:59 +01:00
663 lines
16 KiB
C
663 lines
16 KiB
C
/*
|
|
Copyright (C) 1997-2001 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.
|
|
|
|
*/
|
|
// r_bsp.c
|
|
|
|
#include "r_local.h"
|
|
|
|
//
|
|
// current entity info
|
|
//
|
|
vec3_t r_entorigin; // the currently rendering entity in world
|
|
// coordinates
|
|
|
|
float entity_rotation[3][3];
|
|
|
|
int r_currentbkey;
|
|
|
|
typedef enum {touchessolid, drawnode, nodrawnode} solidstate_t;
|
|
|
|
#define MAX_BMODEL_VERTS 1000 // 12K
|
|
#define MAX_BMODEL_EDGES 2000 // 24K
|
|
|
|
static mvertex_t *pbverts;
|
|
static bedge_t *pbedges;
|
|
static int numbverts, numbedges;
|
|
|
|
static mvertex_t *pfrontenter, *pfrontexit;
|
|
|
|
static qboolean makeclippededge;
|
|
|
|
|
|
|
|
/*
|
|
================
|
|
R_ConcatRotations
|
|
================
|
|
*/
|
|
static void R_ConcatRotations( float in1[3][3], float in2[3][3], float out[3][3] )
|
|
{
|
|
out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0]
|
|
+ in1[0][2] * in2[2][0];
|
|
out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1]
|
|
+ in1[0][2] * in2[2][1];
|
|
out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2]
|
|
+ in1[0][2] * in2[2][2];
|
|
out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0]
|
|
+ in1[1][2] * in2[2][0];
|
|
out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1]
|
|
+ in1[1][2] * in2[2][1];
|
|
out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2]
|
|
+ in1[1][2] * in2[2][2];
|
|
out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0]
|
|
+ in1[2][2] * in2[2][0];
|
|
out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1]
|
|
+ in1[2][2] * in2[2][1];
|
|
out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2]
|
|
+ in1[2][2] * in2[2][2];
|
|
}
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
/*
|
|
================
|
|
R_EntityRotate
|
|
================
|
|
*/
|
|
static void R_EntityRotate( vec3_t vec )
|
|
{
|
|
vec3_t tvec;
|
|
|
|
VectorCopy( vec, tvec );
|
|
vec[0] = DotProduct( entity_rotation[0], tvec );
|
|
vec[1] = DotProduct( entity_rotation[1], tvec );
|
|
vec[2] = DotProduct( entity_rotation[2], tvec );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
R_RotateBmodel
|
|
================
|
|
*/
|
|
void R_RotateBmodel( void )
|
|
{
|
|
float angle, s, c, temp1[3][3], temp2[3][3], temp3[3][3];
|
|
|
|
// TODO: should use a look-up table
|
|
// TODO: should really be stored with the entity instead of being reconstructed
|
|
// TODO: could cache lazily, stored in the entity
|
|
// TODO: share work with R_SetUpAliasTransform
|
|
|
|
// yaw
|
|
angle = RI.currententity->angles[YAW];
|
|
angle = angle * M_PI_F * 2 / 360.0f;
|
|
s = sin( angle );
|
|
c = cos( angle );
|
|
|
|
temp1[0][0] = c;
|
|
temp1[0][1] = s;
|
|
temp1[0][2] = 0;
|
|
temp1[1][0] = -s;
|
|
temp1[1][1] = c;
|
|
temp1[1][2] = 0;
|
|
temp1[2][0] = 0;
|
|
temp1[2][1] = 0;
|
|
temp1[2][2] = 1;
|
|
|
|
|
|
// pitch
|
|
angle = RI.currententity->angles[PITCH];
|
|
angle = angle * M_PI_F * 2 / 360.0f;
|
|
s = sin( angle );
|
|
c = cos( angle );
|
|
|
|
temp2[0][0] = c;
|
|
temp2[0][1] = 0;
|
|
temp2[0][2] = -s;
|
|
temp2[1][0] = 0;
|
|
temp2[1][1] = 1;
|
|
temp2[1][2] = 0;
|
|
temp2[2][0] = s;
|
|
temp2[2][1] = 0;
|
|
temp2[2][2] = c;
|
|
|
|
R_ConcatRotations( temp2, temp1, temp3 );
|
|
|
|
// roll
|
|
angle = RI.currententity->angles[ROLL];
|
|
angle = angle * M_PI_F * 2 / 360.0f;
|
|
s = sin( angle );
|
|
c = cos( angle );
|
|
|
|
temp1[0][0] = 1;
|
|
temp1[0][1] = 0;
|
|
temp1[0][2] = 0;
|
|
temp1[1][0] = 0;
|
|
temp1[1][1] = c;
|
|
temp1[1][2] = s;
|
|
temp1[2][0] = 0;
|
|
temp1[2][1] = -s;
|
|
temp1[2][2] = c;
|
|
|
|
R_ConcatRotations( temp1, temp3, entity_rotation );
|
|
|
|
//
|
|
// rotate modelorg and the transformation matrix
|
|
//
|
|
R_EntityRotate( tr.modelorg );
|
|
R_EntityRotate( RI.vforward );
|
|
R_EntityRotate( RI.vright );
|
|
R_EntityRotate( RI.vup );
|
|
|
|
R_TransformFrustum();
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_RecursiveClipBPoly
|
|
================
|
|
*/
|
|
static void R_RecursiveClipBPoly( bedge_t *pedges, mnode_t *pnode, msurface_t *psurf )
|
|
{
|
|
bedge_t *psideedges[2], *pnextedge, *ptedge;
|
|
int i, side, lastside;
|
|
float dist, frac, lastdist;
|
|
mplane_t *splitplane, tplane;
|
|
mvertex_t *pvert, *plastvert, *ptvert;
|
|
mnode_t *pn;
|
|
|
|
psideedges[0] = psideedges[1] = NULL;
|
|
|
|
makeclippededge = false;
|
|
|
|
// transform the BSP plane into model space
|
|
// FIXME: cache these?
|
|
splitplane = pnode->plane;
|
|
tplane.dist = splitplane->dist
|
|
- DotProduct( r_entorigin, splitplane->normal );
|
|
tplane.normal[0] = DotProduct( entity_rotation[0], splitplane->normal );
|
|
tplane.normal[1] = DotProduct( entity_rotation[1], splitplane->normal );
|
|
tplane.normal[2] = DotProduct( entity_rotation[2], splitplane->normal );
|
|
|
|
// clip edges to BSP plane
|
|
for( ; pedges; pedges = pnextedge )
|
|
{
|
|
pnextedge = pedges->pnext;
|
|
|
|
// set the status for the last point as the previous point
|
|
// FIXME: cache this stuff somehow?
|
|
plastvert = pedges->v[0];
|
|
lastdist = DotProduct( plastvert->position, tplane.normal )
|
|
- tplane.dist;
|
|
|
|
if( lastdist > 0 )
|
|
lastside = 0;
|
|
else
|
|
lastside = 1;
|
|
|
|
pvert = pedges->v[1];
|
|
|
|
dist = DotProduct( pvert->position, tplane.normal ) - tplane.dist;
|
|
|
|
if( dist > 0 )
|
|
side = 0;
|
|
else
|
|
side = 1;
|
|
|
|
if( side != lastside )
|
|
{
|
|
// clipped
|
|
if( numbverts >= MAX_BMODEL_VERTS )
|
|
return;
|
|
|
|
// generate the clipped vertex
|
|
frac = lastdist / ( lastdist - dist );
|
|
ptvert = &pbverts[numbverts++];
|
|
ptvert->position[0] = plastvert->position[0]
|
|
+ frac * ( pvert->position[0]
|
|
- plastvert->position[0] );
|
|
ptvert->position[1] = plastvert->position[1]
|
|
+ frac * ( pvert->position[1]
|
|
- plastvert->position[1] );
|
|
ptvert->position[2] = plastvert->position[2]
|
|
+ frac * ( pvert->position[2]
|
|
- plastvert->position[2] );
|
|
|
|
// split into two edges, one on each side, and remember entering
|
|
// and exiting points
|
|
// FIXME: share the clip edge by having a winding direction flag?
|
|
if( numbedges >= ( MAX_BMODEL_EDGES - 1 ))
|
|
{
|
|
// gEngfuncs.Con_Printf ("Out of edges for bmodel\n");
|
|
return;
|
|
}
|
|
|
|
ptedge = &pbedges[numbedges];
|
|
ptedge->pnext = psideedges[lastside];
|
|
psideedges[lastside] = ptedge;
|
|
ptedge->v[0] = plastvert;
|
|
ptedge->v[1] = ptvert;
|
|
|
|
ptedge = &pbedges[numbedges + 1];
|
|
ptedge->pnext = psideedges[side];
|
|
psideedges[side] = ptedge;
|
|
ptedge->v[0] = ptvert;
|
|
ptedge->v[1] = pvert;
|
|
|
|
numbedges += 2;
|
|
|
|
if( side == 0 )
|
|
{
|
|
// entering for front, exiting for back
|
|
pfrontenter = ptvert;
|
|
makeclippededge = true;
|
|
}
|
|
else
|
|
{
|
|
pfrontexit = ptvert;
|
|
makeclippededge = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// add the edge to the appropriate side
|
|
pedges->pnext = psideedges[side];
|
|
psideedges[side] = pedges;
|
|
}
|
|
}
|
|
|
|
// if anything was clipped, reconstitute and add the edges along the clip
|
|
// plane to both sides (but in opposite directions)
|
|
if( makeclippededge )
|
|
{
|
|
if( numbedges >= ( MAX_BMODEL_EDGES - 2 ))
|
|
{
|
|
// gEngfuncs.Con_Printf ("Out of edges for bmodel\n");
|
|
return;
|
|
}
|
|
|
|
ptedge = &pbedges[numbedges];
|
|
ptedge->pnext = psideedges[0];
|
|
psideedges[0] = ptedge;
|
|
ptedge->v[0] = pfrontexit;
|
|
ptedge->v[1] = pfrontenter;
|
|
|
|
ptedge = &pbedges[numbedges + 1];
|
|
ptedge->pnext = psideedges[1];
|
|
psideedges[1] = ptedge;
|
|
ptedge->v[0] = pfrontenter;
|
|
ptedge->v[1] = pfrontexit;
|
|
|
|
numbedges += 2;
|
|
}
|
|
|
|
// draw or recurse further
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
if( psideedges[i] )
|
|
{
|
|
// draw if we've reached a non-solid leaf, done if all that's left is a
|
|
// solid leaf, and continue down the tree if it's not a leaf
|
|
pn = pnode->children[i];
|
|
|
|
// we're done with this branch if the node or leaf isn't in the PVS
|
|
if( pn->visframe == tr.visframecount )
|
|
{
|
|
if( pn->contents < 0 )
|
|
{
|
|
if( pn->contents != CONTENTS_SOLID )
|
|
{
|
|
// r_currentbkey = ((mleaf_t *)pn)->cluster;
|
|
r_currentbkey = LEAF_KEY(((mleaf_t *)pn ));
|
|
R_RenderBmodelFace( psideedges[i], psurf );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
R_RecursiveClipBPoly( psideedges[i], pnode->children[i],
|
|
psurf );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawSolidClippedSubmodelPolygons
|
|
|
|
Bmodel crosses multiple leafs
|
|
================
|
|
*/
|
|
void R_DrawSolidClippedSubmodelPolygons( model_t *pmodel, mnode_t *topnode )
|
|
{
|
|
int i, j, lindex;
|
|
vec_t dot;
|
|
msurface_t *psurf;
|
|
int numsurfaces;
|
|
mplane_t *pplane;
|
|
mvertex_t bverts[MAX_BMODEL_VERTS];
|
|
bedge_t bedges[MAX_BMODEL_EDGES], *pbedge;
|
|
medge_t *pedge, *pedges;
|
|
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
|
|
psurf = &pmodel->surfaces[pmodel->firstmodelsurface];
|
|
numsurfaces = pmodel->nummodelsurfaces;
|
|
pedges = pmodel->edges;
|
|
|
|
for( i = 0; i < numsurfaces; i++, psurf++ )
|
|
{
|
|
if( FBitSet( psurf->flags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))
|
|
{
|
|
if( psurf->plane->type != PLANE_Z && !FBitSet( RI.currententity->curstate.effects, EF_WATERSIDES ))
|
|
continue;
|
|
if( r_entorigin[2] + pmodel->mins[2] + 1.0f >= psurf->plane->dist )
|
|
continue;
|
|
}
|
|
// find which side of the node we are on
|
|
pplane = psurf->plane;
|
|
|
|
dot = DotProduct( tr.modelorg, pplane->normal ) - pplane->dist;
|
|
|
|
// draw the polygon
|
|
if(( !( psurf->flags & SURF_PLANEBACK ) && ( dot < -BACKFACE_EPSILON ))
|
|
|| (( psurf->flags & SURF_PLANEBACK ) && ( dot > BACKFACE_EPSILON )))
|
|
continue;
|
|
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
|
|
// copy the edges to bedges, flipping if necessary so always
|
|
// clockwise winding
|
|
// FIXME: if edges and vertices get caches, these assignments must move
|
|
// outside the loop, and overflow checking must be done here
|
|
pbverts = bverts;
|
|
pbedges = bedges;
|
|
numbverts = numbedges = 0;
|
|
pbedge = &bedges[numbedges];
|
|
numbedges += psurf->numedges;
|
|
|
|
for( j = 0; j < psurf->numedges; j++ )
|
|
{
|
|
lindex = pmodel->surfedges[psurf->firstedge + j];
|
|
|
|
if( lindex > 0 )
|
|
{
|
|
pedge = &pedges[lindex];
|
|
pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[0]];
|
|
pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[1]];
|
|
}
|
|
else
|
|
{
|
|
lindex = -lindex;
|
|
pedge = &pedges[lindex];
|
|
pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[1]];
|
|
pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[0]];
|
|
}
|
|
|
|
pbedge[j].pnext = &pbedge[j + 1];
|
|
}
|
|
|
|
pbedge[j - 1].pnext = NULL; // mark end of edges
|
|
|
|
// if ( !( psurf->texinfo->flags & ( SURF_TRANS66 | SURF_TRANS33 ) ) )
|
|
R_RecursiveClipBPoly( pbedge, topnode, psurf );
|
|
// else
|
|
// R_RenderBmodelFace( pbedge, psurf );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
R_DrawSubmodelPolygons
|
|
|
|
All in one leaf
|
|
================
|
|
*/
|
|
void R_DrawSubmodelPolygons( model_t *pmodel, int clipflags, mnode_t *topnode )
|
|
{
|
|
int i;
|
|
vec_t dot;
|
|
msurface_t *psurf;
|
|
int numsurfaces;
|
|
mplane_t *pplane;
|
|
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
|
|
psurf = &pmodel->surfaces[pmodel->firstmodelsurface];
|
|
numsurfaces = pmodel->nummodelsurfaces;
|
|
|
|
for( i = 0; i < numsurfaces; i++, psurf++ )
|
|
{
|
|
if( FBitSet( psurf->flags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))
|
|
{
|
|
if( psurf->plane->type != PLANE_Z && !FBitSet( RI.currententity->curstate.effects, EF_WATERSIDES ))
|
|
continue;
|
|
if( r_entorigin[2] + pmodel->mins[2] + 1.0f >= psurf->plane->dist )
|
|
continue;
|
|
}
|
|
// find which side of the node we are on
|
|
pplane = psurf->plane;
|
|
|
|
dot = DotProduct( tr.modelorg, pplane->normal ) - pplane->dist;
|
|
|
|
// draw the polygon
|
|
if((( psurf->flags & SURF_PLANEBACK ) && ( dot < -BACKFACE_EPSILON ))
|
|
|| ( !( psurf->flags & SURF_PLANEBACK ) && ( dot > BACKFACE_EPSILON )))
|
|
{
|
|
r_currentkey = LEAF_KEY(((mleaf_t *)topnode ));
|
|
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
R_RenderFace( psurf, clipflags );
|
|
}
|
|
}
|
|
}
|
|
|
|
#if XASH_LOW_MEMORY
|
|
unsigned short r_leafkeys[MAX_MAP_LEAFS];
|
|
#else
|
|
int r_leafkeys[MAX_MAP_LEAFS];
|
|
#endif
|
|
/*
|
|
================
|
|
R_RecursiveWorldNode
|
|
================
|
|
*/
|
|
static void R_RecursiveWorldNode( mnode_t *node, int clipflags )
|
|
{
|
|
int i, c, side, *pindex;
|
|
vec3_t acceptpt, rejectpt;
|
|
mplane_t *plane;
|
|
msurface_t *surf, **mark;
|
|
mleaf_t *pleaf;
|
|
double d, dot;
|
|
|
|
if( node->contents == CONTENTS_SOLID )
|
|
return; // solid
|
|
|
|
if( node->visframe != tr.visframecount )
|
|
return;
|
|
|
|
// cull the clipping planes if not trivial accept
|
|
// FIXME: the compiler is doing a lousy job of optimizing here; it could be
|
|
// twice as fast in ASM
|
|
if( clipflags )
|
|
{
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
if( !( clipflags & ( 1 << i )))
|
|
continue; // don't need to clip against it
|
|
|
|
// generate accept and reject points
|
|
// FIXME: do with fast look-ups or integer tests based on the sign bit
|
|
// of the floating point values
|
|
|
|
pindex = qfrustum.pfrustum_indexes[i];
|
|
|
|
rejectpt[0] = (float)node->minmaxs[pindex[0]];
|
|
rejectpt[1] = (float)node->minmaxs[pindex[1]];
|
|
rejectpt[2] = (float)node->minmaxs[pindex[2]];
|
|
|
|
d = DotProduct( rejectpt, qfrustum.view_clipplanes[i].normal );
|
|
d -= qfrustum.view_clipplanes[i].dist;
|
|
|
|
if( d <= 0 )
|
|
return;
|
|
|
|
acceptpt[0] = (float)node->minmaxs[pindex[3 + 0]];
|
|
acceptpt[1] = (float)node->minmaxs[pindex[3 + 1]];
|
|
acceptpt[2] = (float)node->minmaxs[pindex[3 + 2]];
|
|
|
|
d = DotProduct( acceptpt, qfrustum.view_clipplanes[i].normal );
|
|
d -= qfrustum.view_clipplanes[i].dist;
|
|
|
|
if( d >= 0 )
|
|
clipflags &= ~( 1 << i ); // node is entirely on screen
|
|
}
|
|
}
|
|
|
|
// if a leaf node, draw stuff
|
|
if( node->contents < 0 )
|
|
{
|
|
pleaf = (mleaf_t *)node;
|
|
|
|
mark = pleaf->firstmarksurface;
|
|
c = pleaf->nummarksurfaces;
|
|
|
|
if( c )
|
|
{
|
|
do
|
|
{
|
|
( *mark )->visframe = tr.framecount;
|
|
mark++;
|
|
}
|
|
while( --c );
|
|
}
|
|
|
|
// deal with model fragments in this leaf
|
|
if( pleaf->efrags )
|
|
{
|
|
gEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount );
|
|
}
|
|
|
|
|
|
// pleaf->cluster
|
|
LEAF_KEY( pleaf ) = r_currentkey;
|
|
r_currentkey++; // all bmodels in a leaf share the same key
|
|
}
|
|
else
|
|
{
|
|
// node is just a decision point, so go down the apropriate sides
|
|
|
|
// find which side of the node we are on
|
|
plane = node->plane;
|
|
|
|
switch( plane->type )
|
|
{
|
|
case PLANE_X:
|
|
dot = tr.modelorg[0] - plane->dist;
|
|
break;
|
|
case PLANE_Y:
|
|
dot = tr.modelorg[1] - plane->dist;
|
|
break;
|
|
case PLANE_Z:
|
|
dot = tr.modelorg[2] - plane->dist;
|
|
break;
|
|
default:
|
|
dot = DotProduct( tr.modelorg, plane->normal ) - plane->dist;
|
|
break;
|
|
}
|
|
|
|
if( dot >= 0 )
|
|
side = 0;
|
|
else
|
|
side = 1;
|
|
|
|
// recurse down the children, front side first
|
|
R_RecursiveWorldNode( node->children[side], clipflags );
|
|
|
|
// draw stuff
|
|
c = node->numsurfaces;
|
|
|
|
if( c )
|
|
{
|
|
surf = WORLDMODEL->surfaces + node->firstsurface;
|
|
|
|
if( dot < -BACKFACE_EPSILON )
|
|
{
|
|
do
|
|
{
|
|
if(( surf->flags & SURF_PLANEBACK )
|
|
&& ( surf->visframe == tr.framecount ))
|
|
{
|
|
R_RenderFace( surf, clipflags );
|
|
}
|
|
|
|
surf++;
|
|
}
|
|
while( --c );
|
|
}
|
|
else if( dot > BACKFACE_EPSILON )
|
|
{
|
|
do
|
|
{
|
|
if( !( surf->flags & SURF_PLANEBACK )
|
|
&& ( surf->visframe == tr.framecount ))
|
|
{
|
|
R_RenderFace( surf, clipflags );
|
|
}
|
|
|
|
surf++;
|
|
}
|
|
while( --c );
|
|
}
|
|
|
|
// all surfaces on the same node share the same sequence number
|
|
r_currentkey++;
|
|
}
|
|
|
|
// recurse down the back side
|
|
R_RecursiveWorldNode( node->children[!side], clipflags );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_RenderWorld
|
|
================
|
|
*/
|
|
void R_RenderWorld( void )
|
|
{
|
|
if( !RI.drawWorld )
|
|
return;
|
|
|
|
// auto cycle the world frame for texture animation
|
|
RI.currententity = CL_GetEntityByIndex( 0 );
|
|
// RI.currententity->frame = (int)(gp_cl->time*2);
|
|
|
|
VectorCopy( RI.vieworg, tr.modelorg );
|
|
RI.currentmodel = WORLDMODEL;
|
|
r_pcurrentvertbase = RI.currentmodel->vertexes;
|
|
|
|
R_RecursiveWorldNode( RI.currentmodel->nodes, 15 );
|
|
}
|