Xash3D FWGS engine.

778 lines
17 KiB

/*
mod_bmodel.c - loading & handling world and brushmodels
Copyright (C) 2016 Uncle Mike
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 3 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.
*/
#include "mod_local.h"
#include "mathlib.h"
#include "world.h"
#include "gl_local.h"
#include "client.h"
#define MAX_CLIPNODE_DEPTH 256 // should never exceeds
#define list_entry( ptr, type, member ) \
((type *)((char *)(ptr) - (size_t)(&((type *)0)->member)))
// iterate over each entry in the list
#define list_for_each_entry( pos, head, member ) \
for( pos = list_entry( (head)->next, winding_t, member ); \
&pos->member != (head); \
pos = list_entry( pos->member.next, winding_t, member ))
// iterate over the list, safe for removal of entries
#define list_for_each_entry_safe( pos, n, head, member ) \
for( pos = list_entry( (head)->next, winding_t, member ), \
n = list_entry( pos->member.next, winding_t, member ); \
&pos->member != (head); \
pos = n, n = list_entry( n->member.next, winding_t, member ))
#define LIST_HEAD_INIT( name ) { &(name), &(name) }
_inline void list_add__( hullnode_t *new, hullnode_t *prev, hullnode_t *next )
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
// add the new entry after the give list entry
_inline void list_add( hullnode_t *newobj, hullnode_t *head )
{
list_add__( newobj, head, head->next );
}
// add the new entry before the given list entry (list is circular)
_inline void list_add_tail( hullnode_t *newobj, hullnode_t *head )
{
list_add__( newobj, head->prev, head );
}
_inline void list_del( hullnode_t *entry )
{
entry->next->prev = entry->prev;
entry->prev->next = entry->next;
}
static winding_t * winding_alloc( uint numpoints )
{
return (winding_t *)malloc( (int)((winding_t *)0)->p[numpoints] );
}
static void free_winding( winding_t *w )
{
// simple sentinel by Carmack
if( *(unsigned *)w == 0xDEADC0DE )
Host_Error( "free_winding: freed a freed winding\n" );
*(unsigned *)w = 0xDEADC0DE;
free( w );
}
static winding_t *winding_copy( winding_t *w )
{
winding_t *neww;
neww = winding_alloc( w->numpoints );
memcpy( neww, w, (int)((winding_t *)0)->p[w->numpoints] );
return neww;
}
static void winding_reverse( winding_t *w )
{
vec3_t point;
int i;
for( i = 0; i < w->numpoints / 2; i++ )
{
VectorCopy( w->p[i], point );
VectorCopy( w->p[w->numpoints - i - 1], w->p[i] );
VectorCopy( point, w->p[w->numpoints - i - 1] );
}
}
/*
* winding_shrink
*
* Takes an over-allocated winding and allocates a new winding with just the
* required number of points. The input winding is freed.
*/
static winding_t *winding_shrink( winding_t *w )
{
winding_t *neww = winding_alloc( w->numpoints );
memcpy( neww, w, (int)((winding_t *)0)->p[w->numpoints] );
free_winding( w );
return neww;
}
/*
====================
winding_for_plane
====================
*/
static winding_t *winding_for_plane( const mplane_t *p )
{
vec3_t org, vright, vup;
int i, axis;
vec_t max, v;
winding_t *w;
// find the major axis
max = -BOGUS_RANGE;
axis = -1;
for( i = 0; i < 3; i++ )
{
v = fabs( p->normal[i] );
if( v > max )
{
axis = i;
max = v;
}
}
VectorClear( vup );
switch( axis )
{
case 0:
case 1:
vup[2] = 1;
break;
case 2:
vup[0] = 1;
break;
default:
Host_Error( "BaseWindingForPlane: no axis found\n" );
return NULL;
}
v = DotProduct( vup, p->normal );
VectorMA( vup, -v, p->normal, vup );
VectorNormalize( vup );
VectorScale( p->normal, p->dist, org );
CrossProduct( vup, p->normal, vright );
VectorScale( vup, BOGUS_RANGE, vup );
VectorScale( vright, BOGUS_RANGE, vright );
// project a really big axis aligned box onto the plane
w = winding_alloc( 4 );
memset( w->p, 0, sizeof( vec3_t ) * 4 );
w->numpoints = 4;
w->plane = p;
VectorSubtract( org, vright, w->p[0] );
VectorAdd( w->p[0], vup, w->p[0] );
VectorAdd( org, vright, w->p[1] );
VectorAdd( w->p[1], vup, w->p[1] );
VectorAdd( org, vright, w->p[2] );
VectorSubtract( w->p[2], vup, w->p[2] );
VectorSubtract( org, vright, w->p[3] );
VectorSubtract( w->p[3], vup, w->p[3] );
return w;
}
/*
* ===========================
* Helper for for the clipping functions
* (winding_clip, winding_split)
* ===========================
*/
static void CalcSides( const winding_t *in, const mplane_t *split, int *sides, vec_t *dists, int counts[3], vec_t epsilon )
{
const vec_t *p;
int i;
counts[0] = counts[1] = counts[2] = 0;
switch( split->type )
{
case PLANE_X:
case PLANE_Y:
case PLANE_Z:
p = in->p[0] + split->type;
for( i = 0; i < in->numpoints; i++, p += 3 )
{
const vec_t dot = *p - split->dist;
dists[i] = dot;
if( dot > epsilon )
sides[i] = SIDE_FRONT;
else if( dot < -epsilon )
sides[i] = SIDE_BACK;
else sides[i] = SIDE_ON;
counts[sides[i]]++;
}
break;
default:
p = in->p[0];
for( i = 0; i < in->numpoints; i++, p += 3 )
{
const vec_t dot = DotProduct( split->normal, p ) - split->dist;
dists[i] = dot;
if( dot > epsilon )
sides[i] = SIDE_FRONT;
else if( dot < -epsilon )
sides[i] = SIDE_BACK;
else sides[i] = SIDE_ON;
counts[sides[i]]++;
}
break;
}
sides[i] = sides[0];
dists[i] = dists[0];
}
static void PushToPlaneAxis( vec_t *v, const mplane_t *p )
{
const int t = p->type % 3;
v[t] = (p->dist - p->normal[(t + 1) % 3] * v[(t + 1) % 3] - p->normal[(t + 2) % 3] * v[(t + 2) % 3]) / p->normal[t];
}
/*
==================
winding_clip
Clips the winding to the plane, returning the new winding on 'side'.
Frees the input winding.
If keepon is true, an exactly on-plane winding will be saved, otherwise
it will be clipped away.
==================
*/
static winding_t *winding_clip( winding_t *in, const mplane_t *split, qboolean keepon, int side, vec_t epsilon )
{
vec_t *dists;
int *sides;
int counts[3];
vec_t dot;
int i, j;
winding_t *neww;
vec_t *p1, *p2, *mid;
int maxpts;
dists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));
sides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));
CalcSides( in, split, sides, dists, counts, epsilon );
if( keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK] )
{
neww = in;
goto out_free;
}
if( !counts[side] )
{
free_winding( in );
neww = NULL;
goto out_free;
}
if( !counts[side ^ 1] )
{
neww = in;
goto out_free;
}
maxpts = in->numpoints + 4;
neww = winding_alloc( maxpts );
neww->numpoints = 0;
neww->plane = in->plane;
for( i = 0; i < in->numpoints; i++ )
{
p1 = in->p[i];
if( sides[i] == SIDE_ON )
{
VectorCopy( p1, neww->p[neww->numpoints] );
neww->numpoints++;
continue;
}
if( sides[i] == side )
{
VectorCopy( p1, neww->p[neww->numpoints] );
neww->numpoints++;
}
if( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] )
continue;
// generate a split point
p2 = in->p[(i + 1) % in->numpoints];
mid = neww->p[neww->numpoints++];
dot = dists[i] / (dists[i] - dists[i + 1]);
for( j = 0; j < 3; j++ )
{
// avoid round off error when possible
if( in->plane->normal[j] == 1.0 )
mid[j] = in->plane->dist;
else if( in->plane->normal[j] == -1.0 )
mid[j] = -in->plane->dist;
else if( split->normal[j] == 1.0 )
mid[j] = split->dist;
else if( split->normal[j] == -1.0 )
mid[j] = -split->dist;
else mid[j] = p1[j] + dot * (p2[j] - p1[j]);
}
if( in->plane->type < 3 )
PushToPlaneAxis( mid, in->plane );
}
// free the original winding
free_winding( in );
// Shrink the winding back to just what it needs...
neww = winding_shrink(neww);
out_free:
free( dists );
free( sides );
return neww;
}
/*
==================
winding_split
Splits a winding by a plane, producing one or two windings. The
original winding is not damaged or freed. If only on one side, the
returned winding will be the input winding. If on both sides, two
new windings will be created.
==================
*/
static void winding_split( winding_t *in, const mplane_t *split, winding_t **pfront, winding_t **pback )
{
vec_t *dists;
int *sides;
int counts[3];
vec_t dot;
int i, j;
winding_t *front, *back;
vec_t *p1, *p2, *mid;
int maxpts;
dists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));
sides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));
CalcSides(in, split, sides, dists, counts, 0.04f );
if( !counts[0] && !counts[1] )
{
// winding on the split plane - return copies on both sides
*pfront = winding_copy( in );
*pback = winding_copy( in );
goto out_free;
}
if( !counts[0] )
{
*pfront = NULL;
*pback = in;
goto out_free;
}
if( !counts[1] )
{
*pfront = in;
*pback = NULL;
goto out_free;
}
maxpts = in->numpoints + 4;
front = winding_alloc( maxpts );
front->numpoints = 0;
front->plane = in->plane;
back = winding_alloc( maxpts );
back->numpoints = 0;
back->plane = in->plane;
for( i = 0; i < in->numpoints; i++ )
{
p1 = in->p[i];
if( sides[i] == SIDE_ON )
{
VectorCopy( p1, front->p[front->numpoints] );
VectorCopy( p1, back->p[back->numpoints] );
front->numpoints++;
back->numpoints++;
continue;
}
if( sides[i] == SIDE_FRONT )
{
VectorCopy( p1, front->p[front->numpoints] );
front->numpoints++;
}
else if( sides[i] == SIDE_BACK )
{
VectorCopy( p1, back->p[back->numpoints] );
back->numpoints++;
}
if( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] )
continue;
// generate a split point
p2 = in->p[(i + 1) % in->numpoints];
mid = front->p[front->numpoints++];
dot = dists[i] / (dists[i] - dists[i + 1]);
for( j = 0; j < 3; j++ )
{
// avoid round off error when possible
if( in->plane->normal[j] == 1.0 )
mid[j] = in->plane->dist;
else if( in->plane->normal[j] == -1.0 )
mid[j] = -in->plane->dist;
else if( split->normal[j] == 1.0 )
mid[j] = split->dist;
else if( split->normal[j] == -1.0 )
mid[j] = -split->dist;
else mid[j] = p1[j] + dot * (p2[j] - p1[j]);
}
if( in->plane->type < 3 )
PushToPlaneAxis( mid, in->plane );
VectorCopy( mid, back->p[back->numpoints] );
back->numpoints++;
}
*pfront = winding_shrink( front );
*pback = winding_shrink( back );
out_free:
free( dists );
free( sides );
}
/* ------------------------------------------------------------------------- */
/*
* This is a stack of the clipnodes we have traversed
* "sides" indicates which side we went down each time
*/
static mclipnode_t *node_stack[MAX_CLIPNODE_DEPTH];
static int side_stack[MAX_CLIPNODE_DEPTH];
static uint node_stack_depth;
static void push_node( mclipnode_t *node, int side )
{
if( node_stack_depth == MAX_CLIPNODE_DEPTH )
Host_Error( "node stack overflow\n" );
node_stack[node_stack_depth] = node;
side_stack[node_stack_depth] = side;
node_stack_depth++;
}
static void pop_node( void )
{
if( !node_stack_depth )
Host_Error( "node stack underflow\n" );
node_stack_depth--;
}
static void free_hull_polys( hullnode_t *hull_polys )
{
winding_t *w, *next;
list_for_each_entry_safe( w, next, hull_polys, chain )
{
list_del( &w->chain );
free_winding( w );
}
}
static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, hull_model_t *model );
static void do_hull_recursion( hull_t *hull, mclipnode_t *node, int side, hullnode_t *polys, hull_model_t *model )
{
winding_t *w, *next;
if( node->children[side] >= 0 )
{
mclipnode_t *child = hull->clipnodes + node->children[side];
push_node( node, side );
hull_windings_r( hull, child, polys, model );
pop_node();
}
else
{
switch( node->children[side] )
{
case CONTENTS_EMPTY:
case CONTENTS_WATER:
case CONTENTS_SLIME:
case CONTENTS_LAVA:
list_for_each_entry_safe( w, next, polys, chain )
{
list_del( &w->chain );
list_add( &w->chain, &model->polys );
}
break;
case CONTENTS_SOLID:
case CONTENTS_SKY:
// throw away polys...
list_for_each_entry_safe( w, next, polys, chain )
{
if( w->pair )
w->pair->pair = NULL;
list_del( &w->chain );
free_winding( w );
model->num_polys--;
}
break;
default:
Host_Error( "bad contents: %i\n", node->children[side] );
break;
}
}
}
static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, hull_model_t *model )
{
mplane_t *plane = hull->planes + node->planenum;
hullnode_t frontlist = LIST_HEAD_INIT( frontlist );
hullnode_t backlist = LIST_HEAD_INIT( backlist );
winding_t *w, *next, *front, *back;
int i;
list_for_each_entry_safe( w, next, polys, chain )
{
// PARANIOA - PAIR CHECK
ASSERT( !w->pair || w->pair->pair == w );
list_del( &w->chain );
winding_split( w, plane, &front, &back );
if( front ) list_add( &front->chain, &frontlist );
if( back ) list_add( &back->chain, &backlist );
if( front && back )
{
if( w->pair )
{
// split the paired poly, preserve pairing
winding_t *front2, *back2;
winding_split( w->pair, plane, &front2, &back2 );
front2->pair = front;
front->pair = front2;
back2->pair = back;
back->pair = back2;
list_add( &front2->chain, &w->pair->chain );
list_add( &back2->chain, &w->pair->chain );
list_del( &w->pair->chain );
free_winding( w->pair );
model->num_polys++;
}
else
{
front->pair = NULL;
back->pair = NULL;
}
model->num_polys++;
free_winding( w );
}
}
w = winding_for_plane(plane);
for( i = 0; w && i < node_stack_depth; i++ )
{
mplane_t *p = hull->planes + node_stack[i]->planenum;
w = winding_clip( w, p, false, side_stack[i], 0.00001 );
}
if( w )
{
winding_t *tmp = winding_copy( w );
winding_reverse( tmp );
w->pair = tmp;
tmp->pair = w;
list_add( &w->chain, &frontlist );
list_add( &tmp->chain, &backlist );
// PARANIOA - PAIR CHECK
ASSERT( !w->pair || w->pair->pair == w );
model->num_polys += 2;
}
else
{
Con_Printf( S_WARN "new winding was clipped away!\n" );
}
do_hull_recursion( hull, node, 0, &frontlist, model );
do_hull_recursion( hull, node, 1, &backlist, model );
}
static void remove_paired_polys( hull_model_t *model )
{
winding_t *w, *next;
list_for_each_entry_safe( w, next, &model->polys, chain )
{
if( w->pair )
{
list_del( &w->chain );
free_winding( w );
model->num_polys--;
}
}
}
static void make_hull_windings( hull_t *hull, hull_model_t *model )
{
hullnode_t head = LIST_HEAD_INIT( head );
Con_Reportf( "%i clipnodes...\n", hull->lastclipnode - hull->firstclipnode );
node_stack_depth = 0;
model->num_polys = 0;
if( hull->planes != NULL )
{
hull_windings_r( hull, hull->clipnodes + hull->firstclipnode, &head, model );
remove_paired_polys( model );
}
Con_Reportf( "%i hull polys\n", model->num_polys );
}
void Mod_InitDebugHulls( void )
{
int i;
world.hull_models = Mem_Calloc( loadmodel->mempool, sizeof( hull_model_t ) * loadmodel->numsubmodels );
world.num_hull_models = loadmodel->numsubmodels;
// initialize list
for( i = 0; i < world.num_hull_models; i++ )
{
hullnode_t *poly = &world.hull_models[i].polys;
poly->next = poly;
poly->prev = poly;
}
}
void Mod_CreatePolygonsForHull( int hullnum )
{
model_t *mod = cl.worldmodel;
double start, end;
char name[8];
int i;
if( hullnum < 1 || hullnum > 3 )
return;
Con_Printf( "generating polygons for hull %u...\n", hullnum );
start = Sys_DoubleTime();
// rebuild hulls list
for( i = 0; i < world.num_hull_models; i++ )
{
hull_model_t *model = &world.hull_models[i];
free_hull_polys( &model->polys );
make_hull_windings( &mod->hulls[hullnum], model );
Q_snprintf( name, sizeof( name ), "*%i", i + 1 );
mod = Mod_FindName( name, false );
}
end = Sys_DoubleTime();
Con_Printf( "build time %.3f secs\n", end - start );
}
void Mod_ReleaseHullPolygons( void )
{
int i;
// release ploygons
for( i = 0; i < world.num_hull_models; i++ )
{
hull_model_t *model = &world.hull_models[i];
free_hull_polys( &model->polys );
}
world.num_hull_models = 0;
}
void R_DrawWorldHull( void )
{
hull_model_t *hull = &world.hull_models[0];
winding_t *poly;
int i;
if( FBitSet( r_showhull->flags, FCVAR_CHANGED ))
{
int val = bound( 0, (int)r_showhull->value, 3 );
if( val ) Mod_CreatePolygonsForHull( val );
ClearBits( r_showhull->flags, FCVAR_CHANGED );
}
if( !CVAR_TO_BOOL( r_showhull ))
return;
pglDisable( GL_TEXTURE_2D );
list_for_each_entry( poly, &hull->polys, chain )
{
srand((unsigned long)poly);
pglColor3f( rand() % 256 / 255.0, rand() % 256 / 255.0, rand() % 256 / 255.0 );
pglBegin( GL_POLYGON );
for( i = 0; i < poly->numpoints; i++ )
pglVertex3fv( poly->p[i] );
pglEnd();
}
pglEnable( GL_TEXTURE_2D );
}
void R_DrawModelHull( void )
{
hull_model_t *hull;
winding_t *poly;
int i;
if( !CVAR_TO_BOOL( r_showhull ))
return;
if( !RI.currentmodel || RI.currentmodel->name[0] != '*' )
return;
i = atoi( RI.currentmodel->name + 1 );
if( i < 1 || i >= world.num_hull_models )
return;
hull = &world.hull_models[i];
pglPolygonOffset( 1.0f, 2.0 );
pglEnable( GL_POLYGON_OFFSET_FILL );
pglDisable( GL_TEXTURE_2D );
list_for_each_entry( poly, &hull->polys, chain )
{
srand((unsigned long)poly);
pglColor3f( rand() % 256 / 255.0, rand() % 256 / 255.0, rand() % 256 / 255.0 );
pglBegin( GL_POLYGON );
for( i = 0; i < poly->numpoints; i++ )
pglVertex3fv( poly->p[i] );
pglEnd();
}
pglEnable( GL_TEXTURE_2D );
pglDisable( GL_POLYGON_OFFSET_FILL );
}