/* mod_dbghulls.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 "common.h" #include "client.h" #include "mod_local.h" #include "xash3d_mathlib.h" #include "world.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.0f ) mid[j] = in->plane->dist; else if( in->plane->normal[j] == -1.0f ) mid[j] = -in->plane->dist; else if( split->normal[j] == 1.0f ) mid[j] = split->dist; else if( split->normal[j] == -1.0f ) 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.0f ) mid[j] = in->plane->dist; else if( in->plane->normal[j] == -1.0f ) mid[j] = -in->plane->dist; else if( split->normal[j] == 1.0f ) mid[j] = split->dist; else if( split->normal[j] == -1.0f ) 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; }