openmohaa/code/game/b_navgen.cpp
2016-03-27 11:49:47 +02:00

3089 lines
72 KiB
C++

//
// b_navgen.c
//
/*
Path costs - time is the cost basis
For normal paths: take dist(sSurf.origin->edge + pmoveEndSpot->tSurf.origin) and
divide by speed to get time. Adjust for duck/wade/swim. Add on time used for
the pmoves to get from edge to pmoveEndspot.
Teleporter: fixed cost of teleport delay (currently 160ms, should be a shared define)
Plats: take vertical distance and divide by plat speed to get move time; add on
any delays (this assumes plats are always at bottom waiting for us)
Trains: ???
*/
/*
FIXME for Q3MAP
When done in game, plates are seen at their low position. Once this is in Q3MAP they will
be seen in their high position requiring a bit of rework of that code. I've noted most of it.
*/
#include <stdio.h>
#include "b_local.h"
#include "mover.h"
// these defines are placeholders for when this code moves into Q3MAP and is multithreaded
#define ThreadLock()
#define ThreadUnlock()
#define INFINITE 1000000
// these defines MUST match those in bg_pmove!!!
#define MIN_WALK_NORMAL 0.7
#define STEPSIZE 18
#define DEFAULT_VIEWHEIGHT 26
#define CROUCH_VIEWHEIGHT 12
#define DUCKSCALE 0.25
#define SWIMSCALE 0.50
#define WADESCALE 0.70
// these defines MUST match the values used in the game dll
#define DEFAULT_MINS_0 -48
#define DEFAULT_MINS_1 -48
#define DEFAULT_MINS_2 0
#define DEFAULT_MAXS_0 48
#define DEFAULT_MAXS_1 48
#define DEFAULT_MAXS_2 76
#define CROUCH_MAXS_2 49
// although this is a derived value, it needs to match the game
#define MAX_JUMPHEIGHT 32
// these are used to drive the pmove based reachability function
#define MAX_PMOVES 64
#define PMOVE_MSEC 50
#define PLAYER_SPEED 320
// defines for ranges and granularity of the spot testing
#define XY_MIN -4080
#define XY_MAX 4080
#define XY_STEP 16
#define Z_MIN -4096
#define Z_MAX 4064
#define Z_STEP 1
#define MAX_SPOTS 65536
#define MAX_SPOTINDEX (((XY_MAX - XY_MIN) / XY_STEP) + 2 )
typedef struct {
unsigned flags;
int surfaceNum;
vec3_t origin;
int parm;
} nspot_t;
static nspot_t *spot;
static int spotCount;
static int sortIndex[MAX_SPOTS];
static int spotIndex[MAX_SPOTINDEX];
static int spotIndexCount;
#if 0 //Q3MAP
nsurface_t *surface;
int surfaceCount;
nneighbor_t *neighbor;
int neighborCount;
byte *route;
#endif
/*
=============
VectorToString
This is just a convenience function
for printing vectors
=============
*/
char *vtos( const vec3_t v ) {
static int index;
static char str[8][32];
char *s;
// use an array so that multiple vtos won't collide
s = str[index];
index = (index + 1)&7;
Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
return s;
}
//
// FindSpots
//
static vec3_t mins;
static vec3_t duckMaxs;
static vec3_t standMaxs;
//FIXME this should NOT be global
static gentity_t *test;
static void FindSpotsInY( int num ) {
trace_t tr;
trace_t tr2;
vec3_t origin;
vec3_t point2;
vec3_t testpoint;
qboolean roof;
float viewheight;
int cont;
float sample1;
float sample2;
int waterlevel;
//int numEnts;
//gentity_t *ents[MAX_GENTITIES];
gentity_t *ent;
qboolean n;
vec3_t absmin;
vec3_t absmax;
int spotNum;
float height;
//gentity_t *target;
//gentity_t *destTarget;
origin[0] = XY_MIN + num * XY_STEP;
for ( origin[1] = XY_MIN; origin[1] <= XY_MAX; origin[1] += XY_STEP ) {
origin[2] = Z_MAX;
tr.endpos[2] = Z_MAX + Z_STEP;
point2[0] = origin[0];
point2[1] = origin[1];
point2[2] = Z_MIN;
roof = qtrue;
height = Z_STEP;
// gi.Printf( "begin %i %i\n", (int)origin[0], (int)origin[1] );
while ( origin[2] > Z_MIN ) {
origin[2] = tr.endpos[2] - height;
height = Z_STEP;
gi.trace( &tr, origin, mins, duckMaxs, point2, ENTITYNUM_NONE, MASK_DEADSOLID, true );
// did we fall out the bottom? yes = this xy finished
if ( tr.endpos[2] <= point2[2] ) {
// gi.Printf( " done - fell out\n" );
break;
}
// were we in solid the whole time? yes = this xy finished
if ( tr.allsolid ) {
// gi.Printf( " done - all solid\n" );
break;
}
// did we end up inside a solid?
gi.trace( &tr2, tr.endpos, mins, duckMaxs, tr.endpos, ENTITYNUM_NONE, MASK_DEADSOLID, true );
if ( tr2.startsolid ) {
// gi.Printf( " in solid @ %f\n", tr.endpos[2] - 24.0 );
continue;
}
// did we land on the roof of the level? yes = move z down a step from point of contact
if ( roof ) {
// gi.Printf( " fell onto level @ %f\n", tr.endpos[2] - 24.0 );
roof = qfalse;
continue;
}
// is this too steep to stand on? yes = move z down a step from point of contact
if ( tr.plane.normal[2] < MIN_WALK_NORMAL ) {
// gi.Printf( " spot too steep @ %f\n", tr.endpos[2] - 24.0 );
continue;
}
// FIXME rule out any we want ignored here
// see if we are on a special entity
ent = &g_entities[tr.entityNum];
if ( ent->s.number != ENTITYNUM_WORLD ) {
if ( ent->entity && ent->entity->isSubclassOf( Mover ) ) {
continue;
}
#if 0
if ( Q_stricmp( ent->entname, "func_plat" ) == 0 ) {
//FIXME what do we need to do if we are
}
#endif
}
// see if we are outside the world
VectorCopy( tr.endpos, test->s.origin );
gi.linkentity( test );
n = test->linked;
gi.unlinkentity( test );
if ( n == qfalse ) {
// gi.Printf( " outside world c @ %s\n", vtos( tr.endpos ) );
continue;
}
if ( spotCount == MAX_SPOTS ) {
gi.Printf( "MAX_SPOTS exceeded\n" );
//FIXME in Q3MAP this should be an exit()
return;
}
// we found a valid standing location, add it to spot list
ThreadLock();
spotNum = spotCount++;
ThreadUnlock();
// gi.Printf( " found spot @ %f %f %f\n", tr.endpos[0], tr.endpos[1], tr.endpos[2] - 24.0);
// VectorCopy( tr.endpos, spot[spotNum].origin );
spot[spotNum].origin[0] = tr.endpos[0];
spot[spotNum].origin[1] = tr.endpos[1];
spot[spotNum].origin[2] = COORDINATE_TO_FLOAT * (int)(tr.endpos[2]*FLOAT_TO_COORDINATE);
spot[spotNum].flags = 0;
spot[spotNum].surfaceNum = -1;
// see if we are on a special entity
ent = &g_entities[tr.entityNum];
if ( ent->s.number != ENTITYNUM_WORLD ) {
#if 0
if ( Q_stricmp( ent->entname, "func_plat" ) == 0 ) {
spot[spotNum].flags |= SF_PLATLOW; // Q3MAP: SF_PLATHIGH
height = Z_STEP; // Q3MAP: ent->pos2[2] - ent->pos1[2];
spot[spotNum].parm = tr.entityNum;
}
// else if ( Q_stricmp( ent->entname, "func_train" ) == 0 ) {
// spot[spotNum].flags |= SF_TRAIN;
// }
#endif
}
// check if we must be ducked
VectorAdd( tr.endpos, mins, absmin );
gi.trace( &tr2, tr.endpos, mins, standMaxs, tr.endpos, ENTITYNUM_NONE, MASK_DEADSOLID, true );
if ( tr2.allsolid ) {
spot[spotNum].flags |= SF_DUCK;
viewheight = CROUCH_VIEWHEIGHT;
VectorAdd( tr.endpos, duckMaxs, absmax );
}
else {
viewheight = DEFAULT_VIEWHEIGHT;
VectorAdd( tr.endpos, standMaxs, absmax );
}
// check water depth and type (depends on duck state to set viewheight)
waterlevel = 0;
testpoint[0] = tr.endpos[0];
testpoint[1] = tr.endpos[1];
testpoint[2] = tr.endpos[2] + mins[2] + 1;
cont = gi.pointcontents( testpoint, 0 );
if ( cont & MASK_WATER ) {
sample2 = viewheight - mins[2];
sample1 = sample2 / 2;
if ( cont & (CONTENTS_LAVA | CONTENTS_SLIME) ) {
spot[spotNum].flags |= SF_PAIN;
}
waterlevel = 1;
testpoint[2] = tr.endpos[2] + mins[2] + sample1;
cont = gi.pointcontents( testpoint, 0 );
if (cont & MASK_WATER) {
waterlevel = 2;
testpoint[2] = tr.endpos[2] + mins[2] + sample2;
cont = gi.pointcontents( testpoint, 0 );
if (cont & MASK_WATER) {
waterlevel = 3;
}
}
}
if ( waterlevel & 1) {
spot[spotNum].flags |= SF_WATERLEVEL1;
}
if ( waterlevel & 2) {
spot[spotNum].flags |= SF_WATERLEVEL2;
}
// if water depth 3, see if there is air above us
if ( waterlevel == 3) {
if ( spot[spotNum].flags & SF_DUCK ) {
spot[spotNum].flags |= SF_WATER_NOAIR;
}
else {
gi.trace( &tr2, tr.endpos, mins, standMaxs, origin, ENTITYNUM_NONE, MASK_DEADSOLID, true );
testpoint[0] = tr2.endpos[0];
testpoint[1] = tr2.endpos[1];
testpoint[2] = tr2.endpos[2] + DEFAULT_VIEWHEIGHT;
cont = gi.pointcontents( testpoint, 0 );
if (cont & MASK_WATER) {
spot[spotNum].flags |= SF_WATER_NOAIR;
}
}
}
#if 0
// check if this spot is in a trigger we care about
numEnts = gi.EntitiesInBox( absmin, absmax, ents, MAX_GENTITIES );
for ( n = 0; n < numEnts; n++ ) {
ent = ents[n];
// if ( Q_stricmp ( ent->entname, "misc_teleporter" ) == 0 ) {
if ( Q_stricmp ( ent->entname, "trigger_teleport" ) == 0 ) {
spot[spotNum].flags |= SF_TELEPORTER;
destTarget = NULL;
destTarget = G_Find( destTarget, FOFS( targetname ), ent->target );
if ( !destTarget ) {
gi.Printf( "target_teleporter with no target\n" );
spot[spotNum].parm = 0;
}
else {
spot[spotNum].parm = destTarget->s.number;
}
}
if ( !( ent->contents & CONTENTS_TRIGGER ) ) {
continue;
}
if ( Q_stricmp ( ent->entname, "trigger_hurt" ) == 0 ) {
spot[spotNum].flags |= SF_PAIN;
}
if ( Q_stricmp ( ent->entname, "trigger_push" ) == 0 ) {
spot[spotNum].flags |= SF_PUSH;
spot[spotNum].parm = ent->s.number;
}
if ( Q_stricmp ( ent->entname, "trigger_multiple" ) == 0 ) {
target = NULL;
while ( (target = G_Find( target, FOFS( targetname ), ent->target ) ) != NULL ) {
if ( target == ent ) {
continue;
}
if ( Q_stricmp ( target->entname, "target_teleporter" ) == 0 ) {
spot[spotNum].flags |= SF_TELEPORTER;
destTarget = NULL;
destTarget = G_Find( destTarget, FOFS( targetname ), target->target );
if ( !destTarget ) {
gi.Printf( "target_teleporter with no target\n" );
spot[spotNum].parm = 0;
}
else {
spot[spotNum].parm = destTarget->s.number;
}
}
if ( Q_stricmp ( target->entname, "target_push" ) == 0 ) {
spot[spotNum].flags |= SF_PUSH;
spot[spotNum].parm = target->s.number;
}
}
}
}
#endif
}
}
}
static void FindSpots( void ) {
int n;
//gentity_t *ent;
//gentity_t *target;
//int spotNum;
spot = ( nspot_t * )gi.TagMalloc ( MAX_SPOTS * sizeof(spot[0]), TAG_GAME );
spotCount = 0;
for ( n = 0; n < MAX_SPOTS; n++ ) {
spot[n].flags = 0;
}
VectorSet( mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2 );
VectorSet( standMaxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2 );
VectorSet( duckMaxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, CROUCH_MAXS_2 );
test = G_Spawn();
test->s.eType = ET_GENERAL;
VectorCopy( mins, test->mins );
VectorCopy( duckMaxs, test->maxs );
// RunThreadsOn( (XY_MAX - XY_MIN) / XY_STEP, qtrue, FindSpotsInY );
for ( n = 0; n < ((XY_MAX - XY_MIN) / XY_STEP); n++ ) {
gi.DebugPrintf( "FindSpotsInY %d/%d\n", n, ((XY_MAX - XY_MIN) / XY_STEP) );
FindSpotsInY( n );
}
G_FreeEdict( test );
#if 0
//FIXME add spots for trigger_teleport
for ( n = 0; n < globals.num_entities; n++ ) {
ent = &g_entities[n];
if ( !ent->inuse ) {
continue;
}
if ( Q_stricmp ( ent->entname, "misc_teleporter" ) == 0 ) {
if ( Q_stricmp ( ent->entname, "trigger_teleport" ) == 0 ) {
target = NULL;
target = G_Find( target, FOFS( targetname ), ent->target );
if ( !target ) {
continue;
}
spotNum = spotCount++;
spot[spotNum].origin[0] = ent->s.origin[0];
spot[spotNum].origin[1] = ent->s.origin[1];
spot[spotNum].origin[2] = COORDINATE_TO_FLOAT * (int)(ent->s.origin[2]*FLOAT_TO_COORDINATE);
spot[spotNum].flags = SF_TELEPORTER;
spot[spotNum].surfaceNum = -1;
spot[spotNum].parm = target - &g_entities[0];
}
}
#endif
gi.Printf( " %i spots\n", spotCount );
}
//
// FindSurfaces
//
#define MAX_SURFACES 4096
static int zstart;
static int zend;
//static int surfaceSizes[10];
static int CompareSpotsByZ( const void *a, const void *b ) {
nspot_t *s1 = &spot[*(int *)a];
nspot_t *s2 = &spot[*(int *)b];
// z
if ( s1->origin[2] > s2->origin[2] ) {
return -1;
}
if ( s1->origin[2] < s2->origin[2] ) {
return 1;
}
// x
if ( s1->origin[0] < s2->origin[0] ) {
return -1;
}
if ( s1->origin[0] > s2->origin[0] ) {
return 1;
}
// y
if ( s1->origin[1] < s2->origin[1] ) {
return -1;
}
if ( s1->origin[1] > s2->origin[1] ) {
return 1;
}
return 0;
}
static void BuildSpotIndexByZ( void ) {
int n;
int i;
float z;
float cz;
for ( n = 0; n < spotCount; n++ ) {
sortIndex[n] = n;
}
for ( n = 0; n < (((XY_MAX - XY_MIN) / XY_STEP) + 2); n++ ) {
spotIndex[n] = 0;
}
qsort( sortIndex, spotCount, sizeof(sortIndex[0]), CompareSpotsByZ );
i = 0;
cz = -8192;
for ( n = 0; n < spotCount; n++ ) {
z = spot[sortIndex[n]].origin[2];
if ( z != cz ) {
spotIndex[i++] = n;
cz = z;
if ( i >= ( MAX_SPOTINDEX - 1 ) )
{
gi.Printf( "MAX_SPOTINDEX exceeded\n" );
//FIXME in Q3MAP this should be an exit()
break;
}
}
}
spotIndexCount = i;
spotIndex[i] = n;
}
static int SpotIndex( vec3_t origin, int flags ) {
int n;
// gi.Printf( " SpotIndex( %s )\n", vtos( origin ) );
for ( n = zstart; n < zend; n++ ) {
if ( spot[sortIndex[n]].origin[0] < origin[0] ) {
continue;
}
if ( spot[sortIndex[n]].origin[0] > origin[0] ) {
return -1;
}
if ( spot[sortIndex[n]].origin[1] < origin[1] ) {
continue;
}
if ( spot[sortIndex[n]].origin[1] > origin[1] ) {
return -1;
}
if ( spot[sortIndex[n]].surfaceNum != -1 ) {
return -1;
}
if ( spot[sortIndex[n]].flags != flags ) {
return -1;
}
return sortIndex[n];
}
return -1;
}
static void BuildSurface( int index ) {
int surfaceNum;
int plusx;
int minusx;
int plusy;
int minusy;
qboolean plusxLock;
qboolean minusxLock;
qboolean plusyLock;
qboolean minusyLock;
float xmin, xmax, x;
float ymin, ymax, y;
vec3_t test;
int n;
int count;
int tbm[256];
int sCount;
nsurface_t *surf;
//int surfaceNum2;
//gentity_t *ent;
//float height;
if ( surfaceCount == MAX_SURFACES ) {
gi.Printf( "MAX_SURFACES exceeded\n" );
//FIXME in Q3MAP this should be an exit()
return;
}
ThreadLock();
surfaceNum = surfaceCount++;
ThreadUnlock();
spot[index].surfaceNum = surfaceNum;
plusx = 0;
minusx = 0;
plusy = 0;
minusy = 0;
plusxLock = qfalse;
minusxLock = qfalse;
plusyLock = qfalse;
minusyLock = qfalse;
test[2] = spot[index].origin[2];
sCount = 1;
while ( !plusxLock || !minusxLock || !plusyLock || !minusyLock ) {
if ( !plusxLock ) {
plusx++;
count = 0;
test[0] = spot[index].origin[0] + plusx * XY_STEP;
ymin = spot[index].origin[1] - minusy * XY_STEP;
ymax = spot[index].origin[1] + plusy * XY_STEP;
for ( y = ymin; y <= ymax; y += XY_STEP ) {
test[1] = y;
n = SpotIndex( test, spot[index].flags );
if ( n == -1 ) {
plusx--;
plusxLock = qtrue;
break;
}
tbm[count++] = n;
}
for ( n = 0; n < count; n ++ ) {
spot[tbm[n]].surfaceNum = surfaceNum;
}
sCount += count;
}
if ( !minusxLock ) {
minusx++;
count = 0;
test[0] = spot[index].origin[0] - minusx * XY_STEP;
ymin = spot[index].origin[1] - minusy * XY_STEP;
ymax = spot[index].origin[1] + plusy * XY_STEP;
for ( y = ymin; y <= ymax; y += XY_STEP ) {
test[1] = y;
n = SpotIndex( test, spot[index].flags );
if ( n == -1 ) {
minusx--;
minusxLock = qtrue;
break;
}
tbm[count++] = n;
}
for ( n = 0; n < count; n ++ ) {
spot[tbm[n]].surfaceNum = surfaceNum;
}
sCount += count;
}
if ( !plusyLock ) {
plusy++;
count = 0;
test[1] = spot[index].origin[1] + plusy * XY_STEP;
xmin = spot[index].origin[0] - minusx * XY_STEP;
xmax = spot[index].origin[0] + plusx * XY_STEP;
for ( x = xmin; x <= xmax; x += XY_STEP ) {
test[0] = x;
n = SpotIndex( test, spot[index].flags );
if ( n == -1 ) {
plusy--;
plusyLock = qtrue;
break;
}
tbm[count++] = n;
}
for ( n = 0; n < count; n ++ ) {
spot[tbm[n]].surfaceNum = surfaceNum;
}
sCount += count;
}
if ( !minusyLock ) {
minusy++;
count = 0;
test[1] = spot[index].origin[1] - minusy * XY_STEP;
xmin = spot[index].origin[0] - minusx * XY_STEP;
xmax = spot[index].origin[0] + plusx * XY_STEP;
for ( x = xmin; x <= xmax; x += XY_STEP ) {
test[0] = x;
n = SpotIndex( test, spot[index].flags );
if ( n == -1 ) {
minusy--;
minusyLock = qtrue;
break;
}
tbm[count++] = n;
}
for ( n = 0; n < count; n ++ ) {
spot[tbm[n]].surfaceNum = surfaceNum;
}
sCount += count;
}
}
#if 0
if ( sCount <= 1 ) {
surfaceSizes[0]++;
}
else if ( sCount <= 2 ) {
surfaceSizes[1]++;
}
else if ( sCount <= 4 ) {
surfaceSizes[2]++;
}
else if ( sCount <= 8 ) {
surfaceSizes[3]++;
}
else if ( sCount <= 16 ) {
surfaceSizes[4]++;
}
else if ( sCount <= 32 ) {
surfaceSizes[5]++;
}
else if ( sCount <= 64 ) {
surfaceSizes[6]++;
}
else if ( sCount <= 128 ) {
surfaceSizes[7]++;
}
else if ( sCount <= 256 ) {
surfaceSizes[8]++;
}
else {
surfaceSizes[9]++;
}
#endif
surf = &surface[surfaceNum];
surf->flags = spot[index].flags;
surf->absmin[0] = spot[index].origin[0] - ( minusx * XY_STEP );
surf->absmin[1] = spot[index].origin[1] - ( minusy * XY_STEP );
surf->absmax[0] = spot[index].origin[0] + ( plusx * XY_STEP );
surf->absmax[1] = spot[index].origin[1] + ( plusy * XY_STEP );
surf->origin[0] = ( surf->absmin[0] + surf->absmax[0] ) * 0.5;
surf->origin[1] = ( surf->absmin[1] + surf->absmax[1] ) * 0.5;
surf->origin[2] = spot[index].origin[2];
gi.DebugPrintf( "Spot %d : origin %s\n", index, vtos( surf->origin ) );
// gi.Printf( "Surface from %s ", vtos( surf->absmin ) );
// gi.Printf( "to %s", vtos( surf->absmax) );
// gi.Printf( "at %s\n", vtos( surf->origin) );
#if 0
// if this is a plat high position, build to plat low surface
// Q#MAP: numerous changes in here
if ( spot[index].flags & SF_PLATLOW ) { //Q3MAP: SF_PLATHIGH
// grab another surface
ThreadLock();
surfaceNum2 = surfaceCount++;
ThreadUnlock();
// make a copy of the other plat surface
memcpy( &surface[surfaceNum2], &surface[surfaceNum], sizeof( nsurface_t ) );
// // switch the flag from high to low
// surface[surfaceNum2].flags &= ~SF_PLATHIGH;
// surface[surfaceNum2].flags |= SF_PLATLOW;
// switch the flag from low to high
surface[surfaceNum2].flags &= ~SF_PLATLOW;
surface[surfaceNum2].flags |= SF_PLATHIGH;
// adjust the z value
ent = &g_entities[spot[index].parm];
height = ent->pos2[2] - ent->pos1[2];
// surface[surfaceNum2].origin[2] -= height;
surface[surfaceNum2].origin[2] += height;
// set low plat surface parm to high plat surface number
// surface[surfaceNum2].parm = surfaceNum;
surface[surfaceNum].parm = surfaceNum2;
// // set high plat surface parm plat cost
// surface[surfaceNum].parm = (height / ent->speed) * 1000;
// surface[surfaceNum2].parm = (height / ent->speed) * 1000;
// set high plat surface parm to plat entity number
// surface[surfaceNum].parm = spot[index].parm;
surface[surfaceNum2].parm = spot[index].parm;
}
#endif
if ( spot[index].flags & SF_PUSH ) {
surf->parm = spot[index].parm;
}
if ( spot[index].flags & SF_TELEPORTER ) {
surf->parm = spot[index].parm;
}
}
static void FindSurfacesAtZ( int zIndex ) {
int n;
zstart = spotIndex[zIndex];
zend = spotIndex[zIndex + 1];
for ( n = zstart; n < zend; n++ ) {
if ( spot[sortIndex[n]].surfaceNum == -1 ) {
BuildSurface( sortIndex[n] );
}
}
}
static void FindSurfaces( void ) {
int n;
surface = ( nsurface_t * )gi.TagMalloc ( MAX_SURFACES * sizeof(surface[0]), TAG_GAME );
surfaceCount = 0;
// memset( surfaceSizes, 0, sizeof( surfaceSizes ) );
BuildSpotIndexByZ();
// RunThreadsOn( spotIndexCount, qtrue, FindSurfacesAtZ );
for ( n = 0; n < spotIndexCount; n++ ) {
FindSurfacesAtZ( n );
}
gi.TagFree( spot );
#if 0
gi.Printf( "%3i @ 1\n", surfaceSizes[0] );
gi.Printf( "%3i @ 2\n", surfaceSizes[1] );
gi.Printf( "%3i @ 3-4\n", surfaceSizes[2] );
gi.Printf( "%3i @ 5-8\n", surfaceSizes[3] );
gi.Printf( "%3i @ 9-16\n", surfaceSizes[4] );
gi.Printf( "%3i @ 17-32\n", surfaceSizes[5] );
gi.Printf( "%3i @ 33-64\n", surfaceSizes[6] );
gi.Printf( "%3i @ 65-128\n", surfaceSizes[7] );
gi.Printf( "%3i @ 129-256\n", surfaceSizes[8] );
gi.Printf( "%3i @ 257+\n", surfaceSizes[9] );
#endif
gi.Printf( " %i surfaces\n", surfaceCount );
}
int FindSpot( vec3_t origin, int flags, float min_height )
{
int n;
for ( n = 0; n < spotCount ; n++ )
{
if ( spot[sortIndex[n]].origin[0] < origin[0] ) {
continue;
}
if ( spot[sortIndex[n]].origin[0] > origin[0] ) {
return -1;
}
if ( spot[sortIndex[n]].origin[1] < origin[1] ) {
continue;
}
if ( spot[sortIndex[n]].origin[1] > origin[1] ) {
return -1;
}
if ( spot[sortIndex[n]].origin[2] <= min_height ) {
continue;
}
if ( spot[sortIndex[n]].surfaceNum != -1 ) {
return -1;
}
if ( spot[sortIndex[n]].flags != flags ) {
return -1;
}
return sortIndex[n];
}
return -1;
}
int FindUsedSpot( vec3_t origin, float min_height )
{
int n;
for ( n = 0; n < spotCount ; n++ )
{
if ( spot[sortIndex[n]].origin[0] < origin[0] ) {
continue;
}
if ( spot[sortIndex[n]].origin[0] > origin[0] ) {
return -1;
}
if ( spot[sortIndex[n]].origin[1] < origin[1] ) {
continue;
}
if ( spot[sortIndex[n]].origin[1] > origin[1] ) {
return -1;
}
if ( spot[sortIndex[n]].origin[2] <= min_height ) {
continue;
}
return sortIndex[n];
}
return -1;
}
int FindSpotInSurface( vec3_t origin, int surfaceNum )
{
int index;
float current_height;
current_height = Z_MIN;
while( 1 )
{
index = FindUsedSpot( origin, current_height );
if (index == -1)
return -1;
if (spot[index].surfaceNum == surfaceNum)
return index;
current_height = spot[index].origin[2];
}
return -1;
}
qboolean IsConvex( int surfaceNum, vec3_t check_point, int n )
{
float i, j;
float x1, y1;
float x2, y2;
float step;
int test_y;
float y_diff;
vec3_t test_point;
int test = 0;
if (check_point[0] < spot[n].origin[0])
{
x1 = check_point[0];
y1 = check_point[1];
x2 = spot[n].origin[0];
y2 = spot[n].origin[1];
}
else
{
x1 = spot[n].origin[0];
y1 = spot[n].origin[1];
x2 = check_point[0];
y2 = check_point[1];
}
if (x2 - x1 != 0)
step = (y2 - y1) / (x2 - x1) / XY_STEP;
else
step = 0;
j = 0;
for ( i = x1 ; i < x2 ; i += XY_STEP )
{
// Test points here
test_point[0] = i;
test_y = j + .5;
y_diff = j - test_y;
if (0) //((y_diff < .05) && (y_diff > -.05))
{
test_point[1] = y1 + ((int)j) * XY_STEP;
if (FindSpotInSurface( test_point, surfaceNum ) == -1)
//return qfalse;
test++;
else
test = 0;
}
else
{
test_point[1] = y1 + ((int)j) * XY_STEP;
if (FindSpotInSurface( test_point, surfaceNum ) == -1)
{
if (j > 0)
test_point[1] = y1 + ((int)(j + 1)) * XY_STEP;
else
test_point[1] = y1 + ((int)(j - 1)) * XY_STEP;
if (FindSpotInSurface( test_point, surfaceNum ) == -1)
//return qfalse;
test++;
else
test = 0;
}
else
test = 0;
}
if (test == 3)
return qfalse;
j += step;
}
return qtrue;
}
qboolean CanAddPoint1( int surfaceNum, int n )
{
int i, j;
float z_diff;
int index;
vec3_t check_point;
float current_height;
int has_neighbor_point;
// Make sure can walk to nearest points in this surface
has_neighbor_point = qfalse;
for ( i = -1 ; i <= 1 ; i++ )
{
check_point[0] = spot[n].origin[0] + i * XY_STEP;
for ( j = -1 ; j <= 1 ; j++ )
{
check_point[1] = spot[n].origin[1] + j * XY_STEP;
if (! ((i == 0) && (j == 0)) )
{
// Find point in same surface
current_height = Z_MIN;
while( 1 )
{
index = FindUsedSpot( check_point, current_height );
if (index == -1)
break;
if (spot[index].surfaceNum == surfaceNum)
{
has_neighbor_point = qtrue;
z_diff = spot[index].origin[2] - spot[n].origin[2];
if (z_diff < 0)
z_diff = -z_diff;
if (z_diff > STEPSIZE)
return qfalse;
break;
}
current_height = spot[index].origin[2];
}
}
}
}
// Make sure it was next to at least one other point in this surface
if (!has_neighbor_point)
return qfalse;
return qtrue;
}
qboolean CanAddPoint2( int surfaceNum, int n )
{
int i, j;
vec3_t check_point;
vec2_t absmin;
vec2_t absmax;
qboolean skip_top, skip_left, skip_right, skip_bottom;
// Setup ansolute mins and maxs correctly
if (spot[n].origin[0] < surface[surfaceNum].absmin[0])
{
absmin[0] = spot[n].origin[0];
skip_left = qtrue;
}
else
{
absmin[0] = surface[surfaceNum].absmin[0];
skip_left = qfalse;
}
if (spot[n].origin[0] > surface[surfaceNum].absmax[0])
{
absmax[0] = spot[n].origin[0];
skip_right = qtrue;
}
else
{
absmax[0] = surface[surfaceNum].absmax[0];
skip_right = qfalse;
}
if (spot[n].origin[1] < surface[surfaceNum].absmin[1])
{
absmin[1] = spot[n].origin[1];
skip_top = qtrue;
}
else
{
absmin[1] = surface[surfaceNum].absmin[1];
skip_top = qfalse;
}
if (spot[n].origin[1] > surface[surfaceNum].absmax[1])
{
absmax[1] = spot[n].origin[1];
skip_bottom = qtrue;
}
else
{
absmax[1] = surface[surfaceNum].absmax[1];
skip_bottom = qfalse;
}
// Test top and bottom points for convexness
for( i = absmin[0] ; i <= absmax[0] ; i += XY_STEP )
{
check_point[0] = i;
// Find top point in this column
if (!skip_top)
{
for( j = absmin[1] ; j <= absmax[1] ; j += XY_STEP )
{
check_point[1] = j;
if (FindSpotInSurface( check_point, surfaceNum ) != -1)
{
// Test for convexness
if (!IsConvex( surfaceNum, check_point, n ))
return qfalse;
break;
}
}
}
// Find bottom point in this column
if (!skip_bottom)
{
for( j = absmax[1] ; j >= absmin[1] ; j -= XY_STEP )
{
check_point[1] = j;
if (FindSpotInSurface( check_point, surfaceNum ) != -1)
{
// Test for convexness
if (!IsConvex( surfaceNum, check_point, n ))
return qfalse;
break;
}
}
}
}
// Test left and right points for convexness
for( j = absmin[1] ; j <= absmax[1] ; j += XY_STEP )
{
check_point[1] = j;
// Find left point in this row
if (!skip_left)
{
for( i = absmin[0] ; i <= absmax[0] ; i += XY_STEP )
{
check_point[0] = i;
if (FindSpotInSurface( check_point, surfaceNum ) != -1)
{
// Test for convexness
if (!IsConvex( surfaceNum, check_point, n ))
return qfalse;
break;
}
}
}
// Find right point in this row
if (!skip_right)
{
for( i = absmax[0] ; i >= absmin[0] ; i -= XY_STEP )
{
check_point[0] = i;
if (FindSpotInSurface( check_point, surfaceNum ) != -1)
{
// Test for convexness
if (!IsConvex( surfaceNum, check_point, n ))
return qfalse;
break;
}
}
}
}
/* for ( i = -1 ; i <= 1 ; i++ )
{
check_point[0] = spot[n].origin[0] + i * XY_STEP;
for ( j = -1 ; j <= 1 ; j++ )
{
check_point[1] = spot[n].origin[1] + j * XY_STEP;
if (! ((i == 0) && (j == 0)) )
{
// Find point in same surface
current_height = Z_MIN;
while( 1 )
{
index = FindUsedSpot( check_point, current_height );
if (index == -1)
break;
if (spot[index].surfaceNum == surfaceNum)
break;
current_height = spot[index].origin[2];
}
if (index == -1)
{
current_height = Z_MIN;
while( 1 )
{
index = FindUsedSpot( check_point, current_height );
if (index == -1)
break;
if (spot[index].surfaceNum == surfaceNum)
break;
current_height = spot[index].origin[2];
}
if (index != -1)
return qfalse;
}
}
}
} */
return qtrue;
}
void AddPointToSurface( int surfaceNum, int n )
{
spot[n].surfaceNum = surfaceNum;
if (spot[n].origin[0] < surface[surfaceNum].absmin[0])
surface[surfaceNum].absmin[0] = spot[n].origin[0];
if (spot[n].origin[0] > surface[surfaceNum].absmax[0])
surface[surfaceNum].absmax[0] = spot[n].origin[0];
if (spot[n].origin[1] < surface[surfaceNum].absmin[1])
surface[surfaceNum].absmin[1] = spot[n].origin[1];
if (spot[n].origin[1] > surface[surfaceNum].absmax[1])
surface[surfaceNum].absmax[1] = spot[n].origin[1];
}
static void BuildConvexSurface( int index )
{
int surfaceNum;
int plusx;
int minusx;
int plusy;
int minusy;
qboolean plusxLock;
qboolean minusxLock;
qboolean plusyLock;
qboolean minusyLock;
qboolean plusxhold, minusxhold, plusyhold, minusyhold;
qboolean plusxalreadyheld, minusxalreadyheld, plusyalreadyheld, minusyalreadyheld;
float xmin, xmax, x;
float ymin, ymax, y;
vec3_t test;
int n;
int count;
int tbm[256];
int sCount;
nsurface_t *surf;
qboolean valid_direction;
float current_height;
int i;
if ( surfaceCount == MAX_SURFACES )
{
gi.Printf( "MAX_SURFACES exceeded\n" );
return;
}
//if (surfaceCount > 100)
// return;
ThreadLock();
surfaceNum = surfaceCount++;
ThreadUnlock();
spot[index].surfaceNum = surfaceNum;
surface[surfaceNum].absmin[0] = spot[index].origin[0];
surface[surfaceNum].absmax[0] = spot[index].origin[0];
surface[surfaceNum].absmin[1] = spot[index].origin[1];
surface[surfaceNum].absmax[1] = spot[index].origin[1];
plusx = 0;
minusx = 0;
plusy = 0;
minusy = 0;
plusxLock = qfalse;
minusxLock = qfalse;
plusyLock = qfalse;
minusyLock = qfalse;
plusxhold = qfalse;
minusxhold = qfalse;
plusyhold = qfalse;
minusyhold = qfalse;
plusxalreadyheld = qfalse;
minusxalreadyheld = qfalse;
plusyalreadyheld = qfalse;
minusyalreadyheld = qfalse;
test[2] = spot[index].origin[2];
sCount = 1;
while ( !plusxLock || !minusxLock || !plusyLock || !minusyLock )
{
if ( ( plusxLock || plusxhold ) && ( minusxLock || minusxhold ) && ( plusyLock || plusyhold ) && ( minusyLock || minusyhold ) )
{
// Remove all of the holds
plusxhold = qfalse;
minusxhold = qfalse;
plusyhold = qfalse;
minusyhold = qfalse;
plusxalreadyheld = qtrue;
minusxalreadyheld = qtrue;
plusyalreadyheld = qtrue;
minusyalreadyheld = qtrue;
}
if ( !plusxLock && !plusxhold )
{
plusx++;
count = 0;
test[0] = spot[index].origin[0] + plusx * XY_STEP;
ymin = spot[index].origin[1] - minusy * XY_STEP;
ymax = spot[index].origin[1] + plusy * XY_STEP;
valid_direction = qfalse;
for ( y = ymin; y <= ymax; y += XY_STEP )
{
// Find this spot
test[1] = y;
current_height = Z_MIN;
n = FindSpot( test, spot[index].flags, current_height );
while( n != -1 )
{
// See if this point can be added and the surface stay convex
if ( CanAddPoint1( surfaceNum, n ) )
{
// Add this point to this surface
spot[n].surfaceNum = surfaceNum;
tbm[count] = n;
count++;
break;
}
// Get the next spot at this x, y
current_height = spot[n].origin[2];
n = FindSpot( test, spot[index].flags, current_height );
}
}
if ( plusxalreadyheld )
{
for( i = 0 ; i < count ; i++ )
{
if ( CanAddPoint2( surfaceNum, tbm[i] ) )
{
AddPointToSurface( surfaceNum, tbm[i] );
valid_direction = qtrue;
}
else
spot[tbm[i]].surfaceNum = -1;
}
}
else
{
valid_direction = qtrue;
if (count == (ymax - ymin) / XY_STEP + 1)
{
for( i = 0 ; i < count ; i++ )
AddPointToSurface( surfaceNum, tbm[i] );
}
else
{
for( i = 0 ; i < count ; i++ )
spot[tbm[i]].surfaceNum = -1;
plusxhold = qtrue;
plusx--;
}
}
if ( !valid_direction )
{
plusx--;
plusxLock = qtrue;
}
}
if ( !minusxLock && !minusxhold )
{
minusx++;
count = 0;
test[0] = spot[index].origin[0] - minusx * XY_STEP;
ymin = spot[index].origin[1] - minusy * XY_STEP;
ymax = spot[index].origin[1] + plusy * XY_STEP;
valid_direction = qfalse;
for ( y = ymin; y <= ymax; y += XY_STEP )
{
// Find this spot
test[1] = y;
current_height = Z_MIN;
n = FindSpot( test, spot[index].flags, current_height );
while( n != -1 )
{
// See if this point can be added and the surface stay convex
if ( CanAddPoint1( surfaceNum, n ) )
{
// Add this point to this surface
spot[n].surfaceNum = surfaceNum;
tbm[count] = n;
count++;
break;
}
// Get the next spot at this x, y
current_height = spot[n].origin[2];
n = FindSpot( test, spot[index].flags, current_height );
}
}
if ( minusxalreadyheld )
{
for( i = 0 ; i < count ; i++ )
{
if ( CanAddPoint2( surfaceNum, tbm[i] ) )
{
AddPointToSurface( surfaceNum, tbm[i] );
valid_direction = qtrue;
}
else
spot[tbm[i]].surfaceNum = -1;
}
}
else
{
valid_direction = qtrue;
if (count == (ymax - ymin) / XY_STEP + 1)
{
for( i = 0 ; i < count ; i++ )
AddPointToSurface( surfaceNum, tbm[i] );
}
else
{
for( i = 0 ; i < count ; i++ )
spot[tbm[i]].surfaceNum = -1;
minusxhold = qtrue;
minusx--;
}
}
if ( !valid_direction )
{
minusx--;
minusxLock = qtrue;
}
}
if ( !plusyLock && !plusyhold )
{
plusy++;
count = 0;
test[1] = spot[index].origin[1] + plusy * XY_STEP;
xmin = spot[index].origin[0] - minusx * XY_STEP;
xmax = spot[index].origin[0] + plusx * XY_STEP;
valid_direction = qfalse;
for ( x = xmin; x <= xmax; x += XY_STEP )
{
// Find this spot
test[0] = x;
current_height = Z_MIN;
n = FindSpot( test, spot[index].flags, current_height );
while( n != -1 )
{
// See if this point can be added and the surface stay convex
if ( CanAddPoint1( surfaceNum, n ) )
{
// Add this point to this surface
spot[n].surfaceNum = surfaceNum;
tbm[count] = n;
count++;
break;
}
// Get the next spot at this x, y
current_height = spot[n].origin[2];
n = FindSpot( test, spot[index].flags, current_height );
}
}
if ( plusyalreadyheld )
{
for( i = 0 ; i < count ; i++ )
{
if ( CanAddPoint2( surfaceNum, tbm[i] ) )
{
AddPointToSurface( surfaceNum, tbm[i] );
valid_direction = qtrue;
}
else
spot[tbm[i]].surfaceNum = -1;
}
}
else
{
valid_direction = qtrue;
if (count == (xmax - xmin) / XY_STEP + 1)
{
for( i = 0 ; i < count ; i++ )
AddPointToSurface( surfaceNum, tbm[i] );
}
else
{
for( i = 0 ; i < count ; i++ )
spot[tbm[i]].surfaceNum = -1;
plusyhold = qtrue;
plusy--;
}
}
if ( !valid_direction )
{
plusy--;
plusyLock = qtrue;
}
}
if ( !minusyLock && !minusyhold )
{
minusy++;
count = 0;
test[1] = spot[index].origin[1] - minusy * XY_STEP;
xmin = spot[index].origin[0] - minusx * XY_STEP;
xmax = spot[index].origin[0] + plusx * XY_STEP;
valid_direction = qfalse;
for ( x = xmin; x <= xmax; x += XY_STEP )
{
// Find this spot
test[0] = x;
current_height = Z_MIN;
n = FindSpot( test, spot[index].flags, current_height );
while( n != -1 )
{
// See if this point can be added and the surface stay convex
if ( CanAddPoint1( surfaceNum, n ) )
{
// Add this point to this surface
spot[n].surfaceNum = surfaceNum;
tbm[count] = n;
count++;
break;
}
// Get the next spot at this x, y
current_height = spot[n].origin[2];
n = FindSpot( test, spot[index].flags, current_height );
}
}
if ( minusyalreadyheld )
{
for( i = 0 ; i < count ; i++ )
{
if ( CanAddPoint2( surfaceNum, tbm[i] ) )
{
AddPointToSurface( surfaceNum, tbm[i] );
valid_direction = qtrue;
}
else
spot[tbm[i]].surfaceNum = -1;
}
}
else
{
valid_direction = qtrue;
if (count == (xmax - xmin) / XY_STEP + 1)
{
for( i = 0 ; i < count ; i++ )
AddPointToSurface( surfaceNum, tbm[i] );
}
else
{
for( i = 0 ; i < count ; i++ )
spot[tbm[i]].surfaceNum = -1;
minusyhold = qtrue;
minusy--;
}
}
if ( !valid_direction )
{
minusy--;
minusyLock = qtrue;
}
}
}
// Setup surface info
surf = &surface[surfaceNum];
surf->flags = spot[index].flags;
surf->absmin[0] = spot[index].origin[0] - ( minusx * XY_STEP );
surf->absmin[1] = spot[index].origin[1] - ( minusy * XY_STEP );
surf->absmax[0] = spot[index].origin[0] + ( plusx * XY_STEP );
surf->absmax[1] = spot[index].origin[1] + ( plusy * XY_STEP );
surf->origin[0] = ( surf->absmin[0] + surf->absmax[0] ) * 0.5;
surf->origin[1] = ( surf->absmin[1] + surf->absmax[1] ) * 0.5;
surf->origin[2] = spot[index].origin[2];
gi.DebugPrintf( "surface #%d - Spot %d : origin %s\n", surfaceNum, index, vtos( surf->origin ) );
}
static int CompareSpots( const void *a, const void *b ) {
nspot_t *s1 = &spot[*(int *)a];
nspot_t *s2 = &spot[*(int *)b];
// x
if ( s1->origin[0] < s2->origin[0] ) {
return -1;
}
if ( s1->origin[0] > s2->origin[0] ) {
return 1;
}
// y
if ( s1->origin[1] < s2->origin[1] ) {
return -1;
}
if ( s1->origin[1] > s2->origin[1] ) {
return 1;
}
// z
if ( s1->origin[2] > s2->origin[2] ) {
return -1;
}
if ( s1->origin[2] < s2->origin[2] ) {
return 1;
}
return 0;
}
static void SortSpots( void )
{
int n;
//int i;
//float z;
//float cz;
for ( n = 0; n < spotCount; n++ )
sortIndex[n] = n;
for ( n = 0; n < (((XY_MAX - XY_MIN) / XY_STEP) + 2); n++ )
spotIndex[n] = 0;
qsort( sortIndex, spotCount, sizeof(sortIndex[0]), CompareSpots );
}
static void FindConvexSurfaces( void )
{
int n;
surface = ( nsurface_t * )gi.TagMalloc ( MAX_SURFACES * sizeof(surface[0]), TAG_GAME );
surfaceCount = 0;
SortSpots();
for ( n = 0; n < spotCount; n++ )
{
if ( spot[sortIndex[n]].surfaceNum == -1 )
BuildConvexSurface( sortIndex[n] );
}
gi.TagFree( spot );
gi.Printf( " %i surfaces\n", surfaceCount );
}
//
// FindNeighbors
//
// stats on point to edge reachability testing
typedef struct {
int count;
int accept;
int reject1;
int reject2;
int reject3;
int pmoveCount;
} reachStats_t;
// stats on surface to surface testing
typedef struct {
int count;
int accept;
int reject;
int treject1;
int treject2;
int treject3;
int treject4;
int zeroNeighbors;
} surfaceStats_t;
// per source surface info
//
// The reason for the local neighbors array and MAX_NEIGHBORS_PER_SURFACE is so FindSurfaceNeighbors
// can be run on many surfaces at once when multithreaded.
#define MAX_NEIGHBORS_PER_SURFACE 256
typedef struct {
int number;
nsurface_t *surface;
int neighborCount;
nneighbor_t neighbor[MAX_NEIGHBORS_PER_SURFACE];
} sourceSurfaceInfo_t;
// per target surface info
typedef struct {
int number;
nsurface_t *surface;
qboolean edgeActive;
vec2_t edgeStart;
vec2_t edgeEnd;
int edgeFlags;
float cost;
} targetSurfaceInfo_t;
// per thread neighbor testing info block
typedef struct {
reachStats_t rStats;
surfaceStats_t sStats;
sourceSurfaceInfo_t sSurf;
targetSurfaceInfo_t tSurf;
} surfaceThreadInfo_t;
// global (per map) instances of stats; used to sum all the per thread stats
static reachStats_t reachStats;
static surfaceStats_t surfStats;
#define FLOAT_TO_VELOCITY 16.0
#define VELOCITY_TO_FLOAT (1.0/FLOAT_TO_VELOCITY)
static int SurfaceNumberAtPoint( vec3_t point ) {
vec3_t p;
int n;
p[0] = point[0];
p[1] = point[1];
p[2] = floor( point[2] );
for ( n = 0; n < surfaceCount; n++ ) {
if ( floor( surface[n].origin[2] ) != p[2] ) {
continue;
}
if ( ( surface[n].absmin[0] - 16 ) > p[0] ) {
continue;
}
if ( ( surface[n].absmax[0] + 16 ) < p[0] ) {
continue;
}
if ( ( surface[n].absmin[1] - 16 ) > p[1] ) {
continue;
}
if ( ( surface[n].absmax[1] + 16 ) < p[1] ) {
continue;
}
return n;
}
return -1;
}
static qboolean PointIsOnSurfaceNumber( vec3_t point, int n ) {
vec3_t p;
p[0] = point[0];
p[1] = point[1];
p[2] = floor( point[2] );
if ( floor( surface[n].origin[2] ) != p[2] ) {
return qfalse;
}
if ( ( surface[n].absmin[0] - 16 ) > p[0] ) {
return qfalse;
}
if ( ( surface[n].absmax[0] + 16 ) < p[0] ) {
return qfalse;
}
if ( ( surface[n].absmin[1] - 16 ) > p[1] ) {
return qfalse;
}
if ( ( surface[n].absmax[1] + 16 ) < p[1] ) {
return qfalse;
}
return qtrue;
}
static void ReachTrace(trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contents, qboolean cylinder )
{
trace_t tr;
gi.trace ( &tr, start, mins, maxs, end, passEntityNum, contents, cylinder );
//FIXME this is FUGLY
while ( tr.entityNum && ( g_entities[tr.entityNum].s.eType == ET_MOVER ) )
{
if ( Q_stricmp( g_entities[tr.entityNum].entname, "func_plat" ) == 0 )
{
*result = tr;
return;
}
gi.trace ( &tr, tr.endpos, mins, maxs, end, tr.entityNum, contents, cylinder );
if ( tr.fraction == 0.0 )
{
*result = tr;
return;
}
}
*result = tr;
return;
}
//FIXME try start running, start walking, and start motionless (special flags set if slower speed required)
static qboolean CanReach( surfaceThreadInfo_t *info, vec3_t start, vec3_t dir, int *flags ) {
pmove_t pm;
playerState_t ps;
int n;
int currentSurface;
vec3_t lastOrigin;
info->rStats.count++;
*flags = 0;
VectorNormalize( dir );
VectorCopy( start, lastOrigin );
memset( &pm, 0, sizeof( pm ) );
memset( &ps, 0, sizeof( ps ) );
pm.ps = &ps;
pm.cmd.msec = PMOVE_MSEC;
pm.cmd.buttons = 0;
pm.cmd.weapon = 0;
pm.cmd.angles[0] = 0;
pm.cmd.angles[1] = 0;
pm.cmd.angles[2] = 0;
pm.cmd.forwardmove = 127;
pm.cmd.rightmove = 0;
pm.cmd.upmove = 0;
pm.tracemask = MASK_DEADSOLID;
pm.noFootsteps = qtrue;
pm.trace = ReachTrace;
pm.pointcontents = gi.pointcontents;
ps.pm_type = PM_NORMAL;
ps.gravity = sv_gravity->value;
ps.speed = PLAYER_SPEED;
VectorCopy( start, ps.origin );
// VectorClear( ps.velocity );
VectorScale( dir, ps.speed, ps.velocity );
ps.delta_angles[0] = 0;
ps.delta_angles[1] = ANGLE2SHORT ( vectoyaw ( dir ) );
ps.delta_angles[2] = 0;
ps.groundEntityNum = 0;
ps.clientNum = 0; // FIXME? was ent->s.number;
// do we need to start ducked?
if ( info->sSurf.surface->flags & SF_DUCK ) {
ps.pm_flags |= PMF_DUCKED;
}
// does the target surface require us to be ducked
if ( info->tSurf.surface->flags & SF_DUCK ) {
ps.pm_flags |= PMF_DUCKED;
pm.cmd.upmove = -127;
}
// do we need to jump up?
if ( ( info->tSurf.surface->origin[2] - info->sSurf.surface->origin[2] ) > STEPSIZE ) {
// if we're ducked we can't jump so it is unreachable
if ( ps.pm_flags & PMF_DUCKED ) {
return qfalse;
}
pm.cmd.upmove = 127;
*flags |= NF_JUMP;
}
// junk we need to fill in so PMove behaves correctly
for( n = 0; n < MAX_STATS; n++ ) {
ps.stats[n] = 0;
}
ps.stats[STAT_HEALTH] = 100;
for( n = 0; n < PW_NUM_POWERUPS; n++ ) {
ps.powerups[n] = 0;
}
for ( n = 0; n < MAX_PMOVES; n++ ) {
Pmove( &pm );
info->rStats.pmoveCount++;
if( VectorCompare( ps.origin, lastOrigin ) != 0 ) {
info->rStats.reject1++;
return qfalse;
}
VectorCopy( ps.origin, lastOrigin );
if ( ps.groundEntityNum == -1 ) {
continue;
}
currentSurface = SurfaceNumberAtPoint( ps.origin );
// FIXME - this happens at the spot under a door
if ( currentSurface == -1 ) {
continue;
}
if ( pm.pmoveEvent == EV_FALL_FAR ) {
*flags |= NF_FALL2;
}
else if ( pm.pmoveEvent == EV_FALL_MEDIUM ) {
*flags |= NF_FALL1;
}
if ( PointIsOnSurfaceNumber( ps.origin, info->tSurf.number ) ) {
info->rStats.accept++;
return qtrue;
}
if ( !PointIsOnSurfaceNumber( ps.origin, info->sSurf.number ) ) {
info->rStats.reject2++;
return qfalse;
}
}
info->rStats.reject3++;
return qfalse;
}
static int FindDestination_Push( surfaceThreadInfo_t *info, gentity_t *pushEnt, int *flags ) {
return -1;
#if 0
vec3_t dir;
pmove_t pm;
playerState_t ps;
int n;
int currentSurface;
qboolean leftGround;
vec3_t pushVel;
vec3_t absmin;
vec3_t absmax;
int numEnts;
gentity_t *ents[MAX_GENTITIES];
*flags = 0;
VectorCopy( pushEnt->movedir, dir );
VectorCopy( dir, pushVel );
VectorNormalize( dir );
memset( &pm, 0, sizeof( pm ) );
memset( &ps, 0, sizeof( ps ) );
pm.ps = &ps;
pm.cmd.msec = PMOVE_MSEC;
pm.cmd.buttons = 0;
pm.cmd.weapon = 0;
pm.cmd.angles[0] = 0;
pm.cmd.angles[1] = 0;
pm.cmd.angles[2] = 0;
pm.cmd.forwardmove = 0;
pm.cmd.rightmove = 0;
pm.cmd.upmove = 0;
pm.tracemask = MASK_DEADSOLID;
pm.noFootsteps = qtrue;
pm.trace = ReachTrace;
pm.pointcontents = gi.pointcontents;
ps.pm_type = PM_NORMAL;
ps.gravity = g_gravity->value;
ps.speed = PLAYER_SPEED;
VectorCopy( info->sSurf.surface->origin, ps.origin );
VectorCopy( pushVel, ps.velocity );
ps.delta_angles[0] = 0;
ps.delta_angles[1] = ANGLE2SHORT ( vectoyaw ( dir ) );
ps.delta_angles[2] = 0;
ps.groundEntityNum = 0;
ps.playernum = 1;
// junk we need to fill in so PMove behaves correctly
for( n = 0; n < MAX_STATS; n++ ) {
ps.stats[n] = 0;
}
ps.stats[STAT_HEALTH] = 100;
for( n = 0; n < PW_NUM_POWERUPS; n++ ) {
ps.powerups[n] = 0;
}
leftGround = qfalse;
for ( n = 0; n < MAX_PMOVES; n++ ) {
VectorAdd( ps.origin, mins, absmin );
VectorAdd( ps.origin, standMaxs, absmax );
numEnts = gi.EntitiesInBox( absmin, absmax, ents, MAX_GENTITIES );
for ( n = 0; n < numEnts; n++ ) {
if ( ents[n] == pushEnt ) {
VectorCopy( pushVel, ps.velocity );
}
}
Pmove( &pm );
if ( ps.groundEntityNum == -1 ) {
leftGround = qtrue;
continue;
}
currentSurface = SurfaceNumberAtPoint( ps.origin );
if ( currentSurface == info->sSurf.number && !leftGround ) {
continue;
}
if ( pm.pmoveEvent == EV_FALL_FAR ) {
*flags |= NF_FALL2;
}
else if ( pm.pmoveEvent == EV_FALL_MEDIUM ) {
*flags |= NF_FALL1;
}
info->tSurf.cost = n * PMOVE_MSEC;
return currentSurface;
}
return -1;
#endif
}
// TeleportPlayer( activator, dest->s.origin, dest->s.angles );
// AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
// VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
static int FindDestination_Teleport( surfaceThreadInfo_t *info, gentity_t *destEnt, int *flags ) {
pmove_t pm;
playerState_t ps;
int n;
int currentSurface;
vec3_t lastOrigin;
vec3_t angles;
*flags = 0;
VectorCopy( destEnt->s.origin, lastOrigin );
lastOrigin[2] += 1;
AngleVectors( destEnt->s.angles, angles, NULL, NULL );
memset( &pm, 0, sizeof( pm ) );
memset( &ps, 0, sizeof( ps ) );
pm.ps = &ps;
pm.cmd.msec = PMOVE_MSEC;
pm.cmd.buttons = 0;
pm.cmd.weapon = 0;
pm.cmd.angles[0] = 0;
pm.cmd.angles[1] = 0;
pm.cmd.angles[2] = 0;
pm.cmd.forwardmove = 0;
pm.cmd.rightmove = 0;
pm.cmd.upmove = 0;
pm.tracemask = MASK_DEADSOLID;
pm.noFootsteps = qtrue;
pm.trace = ReachTrace;
pm.pointcontents = gi.pointcontents;
ps.pm_type = PM_NORMAL;
ps.gravity = sv_gravity->value;
ps.speed = PLAYER_SPEED;
VectorCopy( lastOrigin, ps.origin );
VectorScale( angles, 400, ps.velocity );
ps.delta_angles[0] = 0;
ps.delta_angles[1] = ANGLE2SHORT ( angles[YAW] );
ps.delta_angles[2] = 0;
ps.groundEntityNum = 0;
ps.clientNum = 0;
// junk we need to fill in so PMove behaves correctly
for( n = 0; n < MAX_STATS; n++ ) {
ps.stats[n] = 0;
}
ps.stats[STAT_HEALTH] = 100;
for( n = 0; n < PW_NUM_POWERUPS; n++ ) {
ps.powerups[n] = 0;
}
n = 0;
while ( 1 ) {
n++;
Pmove( &pm );
info->rStats.pmoveCount++;
if( VectorCompare( ps.origin, lastOrigin ) != 0 ) {
break;
}
VectorCopy( ps.origin, lastOrigin );
if ( ps.groundEntityNum == -1 ) {
continue;
}
currentSurface = SurfaceNumberAtPoint( ps.origin );
if ( pm.pmoveEvent == EV_FALL_FAR ) {
*flags |= NF_FALL2;
}
else if ( pm.pmoveEvent == EV_FALL_MEDIUM ) {
*flags |= NF_FALL1;
}
info->tSurf.cost = n * PMOVE_MSEC;
return currentSurface;
}
return -1;
}
static vec3_t testDir[] = { {0, 1, 0}, {-1, 0, 0}, {0, -1, 0}, {1, 0, 0}, {-0.5, 0.5, 0}, {0.5, 0.5, 0}, {-0.5, -0.5, 0}, {0.5, -0.5, 0} };
static void EdgeOpen( surfaceThreadInfo_t *info, vec3_t spot, int flags ) {
info->tSurf.edgeActive = qtrue;
info->tSurf.edgeStart[0] = spot[0];
info->tSurf.edgeStart[1] = spot[1];
info->tSurf.edgeEnd[0] = spot[0];
info->tSurf.edgeEnd[1] = spot[1];
info->tSurf.edgeFlags = flags;
}
static void EdgeClose( surfaceThreadInfo_t *info ) {
nneighbor_t *n;
vec3_t vec;
float d;
n = &info->sSurf.neighbor[info->sSurf.neighborCount];
n->surfaceNum = info->tSurf.number;
if ( info->tSurf.edgeStart[0] <= info->tSurf.edgeEnd[0] ) {
n->absmin[0] = info->tSurf.edgeStart[0];
n->absmax[0] = info->tSurf.edgeEnd[0];
}
else {
n->absmin[0] = info->tSurf.edgeEnd[0];
n->absmax[0] = info->tSurf.edgeStart[0];
}
if ( info->tSurf.edgeStart[1] <= info->tSurf.edgeEnd[1] ) {
n->absmin[1] = info->tSurf.edgeStart[1];
n->absmax[1] = info->tSurf.edgeEnd[1];
}
else {
n->absmin[1] = info->tSurf.edgeEnd[1];
n->absmax[1] = info->tSurf.edgeStart[1];
}
n->origin[0] = (n->absmin[0] + n->absmax[0]) * 0.5;
n->origin[1] = (n->absmin[1] + n->absmax[1]) * 0.5;
n->origin[2] = info->sSurf.surface->origin[2];
n->flags = info->tSurf.edgeFlags;
// calc path cost
if ( info->tSurf.cost ) {
n->cost = info->tSurf.cost;
}
else {
// take dist from source origin to point
VectorSubtract( info->sSurf.surface->origin, n->origin, vec );
d = VectorLength( vec );
// add any movement weights
if ( ( info->sSurf.surface->flags & (SF_WATERLEVEL1 | SF_WATERLEVEL2 ) ) == (SF_WATERLEVEL1 | SF_WATERLEVEL2 ) ) {
d /= SWIMSCALE;
}
else if ( info->sSurf.surface->flags & SF_DUCK ) {
d /= DUCKSCALE;
}
else if ( info->sSurf.surface->flags & (SF_WATERLEVEL1 | SF_WATERLEVEL2 ) ) {
d /= WADESCALE;
}
// save that as first segment distance
n->cost = d;
// take dist from point to target origin
VectorSubtract( info->tSurf.surface->origin, n->origin, vec );
d = VectorLength( vec );
// add any movement weights
if ( ( info->tSurf.surface->flags & (SF_WATERLEVEL1 | SF_WATERLEVEL2 ) ) == (SF_WATERLEVEL1 | SF_WATERLEVEL2 ) ) {
d /= SWIMSCALE;
}
else if ( info->tSurf.surface->flags & SF_DUCK ) {
d /= DUCKSCALE;
}
else if ( info->tSurf.surface->flags & (SF_WATERLEVEL1 | SF_WATERLEVEL2 ) ) {
d /= WADESCALE;
}
// add that to first distance
n->cost += d;
// convert distance to time
n->cost = ( n->cost / (float)PLAYER_SPEED ) * 1000.0;
// possibly add a jump cost
if ( n->flags & NF_JUMP ) {
n->cost += 250.0; //FIXME what is a good value to use here? track actual time used for transitional pmoves!
}
}
if ( info->tSurf.surface->flags & SF_DUCK ) {
n->flags |= NF_DUCK;
}
// gi.Printf( "s%i to s%i: edge %f,%f to %f,%f f=%i\n", info->sSurf.number, info->tSurf.number, edgeStart[0], edgeStart[1], edgeEnd[0], edgeEnd[1], *edgeFlags );
// gi.Printf( "s%i to s%i\n", info->sSurf.number, info->tSurf.number );
info->tSurf.edgeActive = qfalse;
info->sSurf.neighborCount++;
if ( info->sSurf.neighborCount == MAX_NEIGHBORS_PER_SURFACE ) {
gi.Printf( "MAX_NEIGHBORS_PER_SURFACE exceeded\n" );
}
}
static void EdgeUpdate( surfaceThreadInfo_t *info, qboolean reachable, vec3_t spot, int flags ) {
// gi.Printf( "s1=%i s2=%i spot=%s result=%i f=%i\n", info->sSurf.number, info->tSurf.number, vtos( spot ), reachable, flags );
if ( reachable ) {
if ( info->tSurf.edgeActive ) {
if ( flags == info->tSurf.edgeFlags ) {
// continue edge
info->tSurf.edgeEnd[0] = spot[0];
info->tSurf.edgeEnd[1] = spot[1];
}
else {
EdgeClose( info );
EdgeOpen( info, spot, flags );
}
}
else {
EdgeOpen( info, spot, flags );
}
}
else {
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
}
}
static void TestReachSurface( surfaceThreadInfo_t *info ) {
int edgeCode;
float xStart, xEnd;
float yStart, yEnd;
float x, y, z;
vec3_t spot;
int flags;
qboolean reachable;
// determine which edges of s1 are facing s2
edgeCode = 0;
if ( info->tSurf.surface->absmin[1] > info->sSurf.surface->absmax[1] ) {
edgeCode |= 1;
}
if ( info->tSurf.surface->absmax[0] < info->sSurf.surface->absmin[0] ) {
edgeCode |= 2;
}
if ( info->tSurf.surface->absmin[0] > info->sSurf.surface->absmax[0] ) {
edgeCode |= 4;
}
if ( info->tSurf.surface->absmax[1] < info->sSurf.surface->absmin[1] ) {
edgeCode |= 8;
}
if ( info->tSurf.surface->absmax[1] > info->sSurf.surface->absmax[1] && info->tSurf.surface->absmin[0] <= info->sSurf.surface->absmax[0] && info->tSurf.surface->absmax[0] >= info->sSurf.surface->absmin[0] ) {
edgeCode |= 1;
}
if ( info->tSurf.surface->absmin[0] < info->sSurf.surface->absmin[0] && info->tSurf.surface->absmin[1] <= info->sSurf.surface->absmax[1] && info->tSurf.surface->absmax[1] >= info->sSurf.surface->absmin[1] ) {
edgeCode |= 2;
}
if ( info->tSurf.surface->absmax[0] > info->sSurf.surface->absmax[0] && info->tSurf.surface->absmin[1] <= info->sSurf.surface->absmax[1] && info->tSurf.surface->absmax[1] >= info->sSurf.surface->absmin[1] ) {
edgeCode |= 4;
}
if ( info->tSurf.surface->absmin[1] < info->sSurf.surface->absmin[1] && info->tSurf.surface->absmin[0] <= info->sSurf.surface->absmax[0] && info->tSurf.surface->absmax[0] >= info->sSurf.surface->absmin[0] ) {
edgeCode |= 8;
}
if ( edgeCode == 0 ) {
gi.Printf( "bad edgeCode\n" );
return;
}
// get coordinates for corners
xStart = info->sSurf.surface->absmin[0];
xEnd = info->sSurf.surface->absmax[0];
yStart = info->sSurf.surface->absmin[1];
yEnd = info->sSurf.surface->absmax[1];
z = info->sSurf.surface->origin[2];
// run through the spots on the relevant edges
// for each spot, see if the target surface is reachable from there
info->tSurf.edgeActive = qfalse;
if ( edgeCode & 1 ) {
for ( x = xStart; x <= xEnd; x += XY_STEP ) {
VectorSet( spot, x, yEnd, z );
reachable = CanReach( info, spot, testDir[0], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
if ( edgeCode & 2 ) {
for ( y = yStart; y <= yEnd; y += XY_STEP ) {
VectorSet( spot, xStart, y, z );
reachable = CanReach( info, spot, testDir[1], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
if ( edgeCode & 4 ) {
for ( y = yStart; y <= yEnd; y += XY_STEP ) {
VectorSet( spot, xEnd, y, z );
reachable = CanReach( info, spot, testDir[3], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
if ( edgeCode & 8 ) {
for ( x = xStart; x <= xEnd; x += XY_STEP ) {
VectorSet( spot, x, yStart, z );
reachable = CanReach( info, spot, testDir[2], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
// special checks for corners
if ( ( edgeCode & 3 ) == 3 ) {
VectorSet( spot, xStart, yEnd, z );
reachable = CanReach( info, spot, testDir[4], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
if ( ( edgeCode & 5 ) == 5 ) {
VectorSet( spot, xEnd, yEnd, z );
reachable = CanReach( info, spot, testDir[5], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
if ( ( edgeCode & 10 ) == 10 ) {
VectorSet( spot, xStart, yStart, z );
reachable = CanReach( info, spot, testDir[6], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
if ( ( edgeCode & 12 ) == 12 ) {
VectorSet( spot, xEnd, yStart, z );
reachable = CanReach( info, spot, testDir[7], &flags );
EdgeUpdate( info, reachable, spot, flags );
}
if ( info->tSurf.edgeActive ) {
EdgeClose( info );
}
}
static void TestReachSurface_Push( surfaceThreadInfo_t *info, gentity_t *pushEnt ) {
int flags;
int destSurf;
//Q3MAP we will need to calculate movedir the same way the game dll does
destSurf = FindDestination_Push( info, pushEnt, &flags );
if ( destSurf == -1 ) {
gi.Printf( "pusher at %s never hit ground?\n", vtos( info->sSurf.surface->origin ) );
}
else if ( destSurf != info->sSurf.number ) {
info->tSurf.number = destSurf;
info->tSurf.surface = &surface[destSurf];
info->tSurf.edgeActive = qfalse;
EdgeUpdate( info, qtrue, info->sSurf.surface->origin, flags );
EdgeClose( info );
gi.Printf( "pusher at %s ", vtos( info->sSurf.surface->origin ) );
gi.Printf( "hits surface at %s ", vtos( info->tSurf.surface->origin ) );
gi.Printf( "(%i,%i)-(%i,%i)\n", (int)info->tSurf.surface->absmin[0], (int)info->tSurf.surface->absmin[1], (int)info->tSurf.surface->absmax[0], (int)info->tSurf.surface->absmax[1] );
}
else {
gi.Printf( WARNING "bad pusher at %s\n", vtos( info->sSurf.surface->origin ) );
}
}
static void TestReachSurface_Teleport( surfaceThreadInfo_t *info, gentity_t *teleportEnt ) {
int flags;
int destSurf;
destSurf = FindDestination_Teleport( info, teleportEnt, &flags );
if ( destSurf != -1 ) {
info->tSurf.number = destSurf;
info->tSurf.surface = &surface[destSurf];
info->tSurf.edgeActive = qfalse;
EdgeUpdate( info, qtrue, info->sSurf.surface->origin, flags );
EdgeClose( info );
}
}
static void CreatePlatNeighbor( surfaceThreadInfo_t *info ) {
#if 0
nneighbor_t *n;
gentity_t *ent;
n = &info->sSurf.neighbor[info->sSurf.neighborCount];
ent = &g_entities[info->tSurf.surface->parm];
n->surfaceNum = info->tSurf.number;
n->absmin[0] = info->sSurf.surface->absmin[0] + 8;
n->absmin[1] = info->sSurf.surface->absmin[1] + 8;
n->absmax[0] = info->sSurf.surface->absmax[0] - 8;
n->absmax[1] = info->sSurf.surface->absmax[1] - 8;
VectorCopy( info->sSurf.surface->origin, n->origin );
n->flags = NF_PLAT;
n->cost = ( ( ent->pos2[2] - ent->pos1[2] ) / (float)PLAYER_SPEED ) * 1000.0;
info->sSurf.neighborCount++;
#endif
}
static void FindSurfaceNeighbors( int surfaceNum ) {
int n;
int oldCount;
surfaceThreadInfo_t info;
gentity_t *ent;
memset( &info, 0, sizeof( info ) );
info.sSurf.number = surfaceNum;
info.sSurf.surface = &surface[surfaceNum];
info.sSurf.neighborCount = 0;
//FIXME we must handle special surface cases here - push, teleport
// they don't need to check every other surface, where player will wind
// up is predetermined (roughly at least)
// teleporter special case
if ( info.sSurf.surface->flags & SF_TELEPORTER ) {
ent = &g_entities[info.sSurf.surface->parm];
TestReachSurface_Teleport( &info, ent );
goto updateGlobals;
}
// trigger_push special case
if ( info.sSurf.surface->flags & SF_PUSH ) {
ent = &g_entities[info.sSurf.surface->parm];
TestReachSurface_Push( &info, ent );
goto updateGlobals;
}
#if 0
// Q3MAP: reverse this
// if this is a PLATHIGH surface, we need to temporarily relink the plat to the high position
if ( info.sSurf.surface->flags & SF_PLATHIGH ) {
ent = &g_entities[info.sSurf.surface->parm];
VectorCopy( ent->pos2, ent->s.origin );
VectorCopy( ent->pos2, ent->currentOrigin );
gi.linkentity( ent );
}
#endif
for ( n = 0; n < surfaceCount; n++ ) {
if ( n == info.sSurf.number ) {
info.sStats.treject1++;
continue;
}
info.tSurf.number = n;
info.tSurf.surface = &surface[n];
// plat high surfaces are a special case as a target
// only the corresponding plat low should have them as a neighbor
if ( info.tSurf.surface->flags & SF_PLATHIGH ) {
if ( info.sSurf.surface->flags & SF_PLATLOW ) {
CreatePlatNeighbor( &info );
info.sStats.accept++;
}
continue;
}
// reject if surf is too high to be reached
if ( ( info.tSurf.surface->origin[2] - info.sSurf.surface->origin[2] ) > MAX_JUMPHEIGHT ) {
info.sStats.treject2++;
continue;
}
// reject is surf XY range is a subset of our own (meaning it is underneath us and not directly reachable)
if ( ( info.tSurf.surface->absmin[0] >= info.sSurf.surface->absmin[0] ) && ( info.tSurf.surface->absmin[1] >= info.sSurf.surface->absmin[1] ) &&
( info.tSurf.surface->absmax[0] <= info.sSurf.surface->absmax[0] ) && ( info.tSurf.surface->absmax[1] <= info.sSurf.surface->absmax[1] ) ) {
info.sStats.treject3++;
continue;
}
// reject if surf origin is not in PVS
if ( !gi.inPVSIgnorePortals( info.tSurf.surface->origin, info.sSurf.surface->origin ) ) {
info.sStats.treject4++;
continue;
}
// see if it's a neighbor
info.sStats.count++;
oldCount = info.sSurf.neighborCount;
TestReachSurface( &info );
if ( info.sSurf.neighborCount != oldCount ) {
info.sStats.accept++;
}
else {
info.sStats.reject++;
}
}
#if 0
// Q3MAP: reverse this
// if this is a PLATHIGH surface, we need to restore the plat to the low position
if ( info.sSurf.surface->flags & SF_PLATHIGH ) {
ent = &g_entities[info.sSurf.surface->parm];
VectorCopy( ent->pos1, ent->s.origin );
VectorCopy( ent->pos1, ent->currentOrigin );
gi.linkentity( ent );
}
#endif
if ( neighborCount + info.sSurf.neighborCount > MAX_NEIGHBORS ) {
gi.Printf( "MAX_NEIGHBORS exceeded\n" );
return;
}
updateGlobals:
// grab the critical section lock to do global variable updates
ThreadLock();
// reserve the block of neighbors we need
surface[info.sSurf.number].neighborIndex = neighborCount;
neighborCount += info.sSurf.neighborCount;
// update global stats
surfStats.count += info.sStats.count;
surfStats.accept += info.sStats.accept;
surfStats.reject += info.sStats.reject;
surfStats.treject1 += info.sStats.treject1;
surfStats.treject2 += info.sStats.treject2;
surfStats.treject3 += info.sStats.treject3;
surfStats.treject4 += info.sStats.treject4;
if ( info.sSurf.neighborCount == 0 ) {
surfStats.zeroNeighbors++;
}
reachStats.count += info.rStats.count;
reachStats.accept += info.rStats.accept;
reachStats.reject1 += info.rStats.reject1;
reachStats.reject2 += info.rStats.reject2;
reachStats.reject3 += info.rStats.reject3;
reachStats.pmoveCount += info.rStats.pmoveCount;
// release the lock
ThreadUnlock();
// now we can fill in the block of neighbors from our cache
surface[info.sSurf.number].neighborCount = info.sSurf.neighborCount;
if ( info.sSurf.neighborCount ) {
memcpy( &neighbor[surface[info.sSurf.number].neighborIndex], info.sSurf.neighbor, info.sSurf.neighborCount * sizeof( info.sSurf.neighbor[0] ) );
}
}
static void FindNeighbors( void ) {
int n;
neighbor = ( nneighbor_t * )gi.TagMalloc ( MAX_NEIGHBORS * sizeof(neighbor[0]), TAG_GAME );
neighborCount = 0;
memset( &reachStats, 0, sizeof( reachStats ) );
memset( &surfStats, 0, sizeof( surfStats ) );
// RunThreadsOn( spotIndexCount, qtrue, FindSurfaceNeighbors );
//#if 0
for ( n = 0; n < surfaceCount; n++ ) {
gi.DebugPrintf( "FindSurfaceNeighbors %d/%d\n", n, surfaceCount );
FindSurfaceNeighbors( n );
}
//#endif
gi.Printf( " %i neighbors (avg %.2f per surf)\n", neighborCount, (float)neighborCount/(float)surfaceCount );
gi.Printf( " %i surfaces with 0 neighbors\n", surfStats.zeroNeighbors );
gi.Printf( " surface to surface testing stats:\n" );
gi.Printf( " %i combinations\n", surfaceCount * surfaceCount );
gi.Printf( " %i trivial rejects (%i %i %i %i)\n", surfStats.treject1 + surfStats.treject2 + surfStats.treject3 + surfStats.treject4, surfStats.treject1, surfStats.treject2, surfStats.treject3, surfStats.treject4 );
gi.Printf( " %i full tests (%.2f avg per surf)\n", surfStats.count, (float)surfStats.count / (float)surfaceCount );
gi.Printf( " %i accepted, %i rejected\n", surfStats.accept, surfStats.reject );
gi.Printf( " spot to surface testing stats:\n" );
gi.Printf( " %i tests (%.2f avg per surf)\n", reachStats.count, (float)reachStats.count / (float)surfaceCount );
gi.Printf( " %i accepted, %i rejected (%i %i %i)\n", reachStats.accept, reachStats.reject1 + reachStats.reject2 + reachStats.reject3, reachStats.reject1, reachStats.reject2, reachStats.reject3 );
gi.Printf( " %.2f avg pmoves per test\n", (float)reachStats.pmoveCount / (float)reachStats.count );
}
//
// InflateSurfaces
//
static void InflateSurfaces( void ) {
int n;
// inflate surfaces by 16 along both axis
// also drop the Z value by mins
for ( n = 0; n < surfaceCount; n++ ) {
surface[n].absmin[0] -= 0;
surface[n].absmin[1] -= 0;
surface[n].absmax[0] += 0;
surface[n].absmax[1] += 0;
surface[n].origin[2] = floor( surface[n].origin[2] + mins[2] );
}
// inflate neighbor edges by 16 along the fixed axis
for ( n = 0; n < neighborCount; n++ ) {
if ( neighbor[n].absmin[0] == neighbor[n].absmax[0] ) {
neighbor[n].absmin[0] -= 0;
neighbor[n].absmax[0] += 0;
}
if ( neighbor[n].absmin[1] == neighbor[n].absmax[1] ) {
neighbor[n].absmin[1] -= 0;
neighbor[n].absmax[1] += 0;
}
neighbor[n].origin[2] = floor( neighbor[n].origin[2] + mins[2] );
}
}
//
// CalculateAllRoutes
//
typedef enum {
nodestate_estimate,
nodestate_final
} nodeState_t;
typedef struct {
float cost;
nodeState_t state;
int predecessor;
} nodeInfo_t;
static nodeInfo_t *nodeInfo;
static int NeighborIndex( int sourceSurfNum, int destSurfNum ) {
int base;
int n;
if ( destSurfNum == -1 ) {
return -1;
}
base = surface[sourceSurfNum].neighborIndex;
for ( n = 0; n < surface[sourceSurfNum].neighborCount; n++ ) {
if ( neighbor[base+n].surfaceNum == destSurfNum ) {
return n;
}
}
return -1;
}
static nneighbor_t *PathEdge( int sourceSurfNum, int destSurfNum ) {
int base;
int n;
base = surface[sourceSurfNum].neighborIndex;
for ( n = 0; n < surface[sourceSurfNum].neighborCount; n++ ) {
if ( neighbor[base+n].surfaceNum == destSurfNum ) {
return &neighbor[base+n];
}
}
return NULL;
}
/*
=============
SurfaceDistance
Returns the cost of moving from sourceSurfNum to targetSurfNum, 0 if there
is no direct path.
=============
*/
static float SurfaceDistance( int sourceSurfNum, int destSurfNum ) {
nneighbor_t *n;
n = PathEdge( sourceSurfNum, destSurfNum );
if ( n ) {
return n->cost;
}
return 0;
}
/*
=============
CalculateRoutes
Takes the surfaces and neighbors tablea and uses Dijkstra's algorithm to
determine the shortest route from one surface to another.
=============
*/
static void CalculateRoutes( int rootSurfaceNum ) {
int n;
int currentNode;
int testNode;
float bestCost;
int bestNode;
float d;
// initialize the node info
for ( n = 0; n < surfaceCount; n++ ) {
nodeInfo[n].cost = INFINITE;
nodeInfo[n].state = nodestate_estimate;
nodeInfo[n].predecessor = -1;
}
// prime thing to get the loop started
currentNode = rootSurfaceNum;
nodeInfo[rootSurfaceNum].cost= 0;
// calculate the shortest path info
// we loop surfaceCount times; a new final node is determined each iteration
n = 0;
while ( currentNode != -1 ) {
bestCost = INFINITE;
bestNode = -1;
// test each node
for ( testNode = 0; testNode < surfaceCount; testNode++ ) {
// do not test it against itself
if ( testNode == currentNode ) {
continue;
}
// leave final nodes alone
if ( nodeInfo[testNode].state == nodestate_final ) {
continue;
}
if ( surface[testNode].neighborCount == 0 ) {
continue;
}
// update adjacent nodes
d = SurfaceDistance( currentNode, testNode );
if ( d != 0 ) {
// see if we can improve the current estimate at the test node
if ( nodeInfo[currentNode].cost + d < nodeInfo[testNode].cost )
{
nodeInfo[testNode].predecessor = currentNode;
nodeInfo[testNode].cost = nodeInfo[currentNode].cost + d;
}
}
// see if this is our new best estimate
if ( nodeInfo[testNode].cost < bestCost ) {
bestCost = nodeInfo[testNode].cost;
bestNode = testNode;
}
}
// mark current node as final and best node as new current node
nodeInfo[currentNode].state = nodestate_final;
currentNode = bestNode;
n++;
}
// now fill in the route info
for ( n = 0; n < surfaceCount; n++ ) {
if ( n == rootSurfaceNum ) {
route[rootSurfaceNum * surfaceCount + n] = 255;
continue;
}
testNode = n;
while ( ( testNode != -1 ) && ( nodeInfo[testNode].predecessor != rootSurfaceNum ) ) {
testNode = nodeInfo[testNode].predecessor;
}
if ( testNode == -1 ) {
route[rootSurfaceNum * surfaceCount + n] = 255;
continue;
}
route[rootSurfaceNum * surfaceCount + n] = NeighborIndex( rootSurfaceNum, testNode );
}
}
static void CalculateAllRoutes( void ) {
int n;
route = ( byte * )gi.TagMalloc ( surfaceCount * surfaceCount * sizeof( byte ), TAG_GAME );
nodeInfo = ( nodeInfo_t * )gi.TagMalloc ( surfaceCount * sizeof( nodeInfo[0] ), TAG_GAME );
for ( n = 0; n < surfaceCount; n++ ) {
CalculateRoutes( n );
}
gi.TagFree( nodeInfo );
}
//
// WriteNavigationData
//
static void WriteNavigationData( void ) {
FILE *f;
int n;
navheader_t outHeader;
nsurface_t outSurface;
nneighbor_t outNeighbor;
str filename;
str osFilename;
Swap_Init();
filename = str( "maps/" ) + level.mapname.c_str() + ".nav";
gi.Printf( "Writing %s.\n", filename.c_str() );
osFilename = gi.FS_PrepFileWrite( filename.c_str() );
f = fopen( osFilename.c_str(), "wb" );
if ( !f )
{
gi.Printf( "Open failed.\n" );
return;
}
// write version header
outHeader.id = LittleLong( NAVFILE_ID );
outHeader.version = LittleLong( NAVFILE_VERSION );
outHeader.surfaceCount = LittleLong( surfaceCount );
outHeader.neighborCount = LittleLong( neighborCount );
fwrite( &outHeader, sizeof( outHeader ), 1, f );
// write surfaces
for ( n = 0; n < surfaceCount; n++ ) {
outSurface.origin[0] = LittleFloat( surface[n].origin[0] );
outSurface.origin[1] = LittleFloat( surface[n].origin[1] );
outSurface.origin[2] = LittleFloat( surface[n].origin[2] );
outSurface.absmin[0] = LittleFloat( surface[n].absmin[0] );
outSurface.absmin[1] = LittleFloat( surface[n].absmin[1] );
outSurface.absmax[0] = LittleFloat( surface[n].absmax[0] );
outSurface.absmax[1] = LittleFloat( surface[n].absmax[1] );
outSurface.flags = LittleLong( surface[n].flags );
outSurface.neighborCount = LittleLong( surface[n].neighborCount );
outSurface.neighborIndex = LittleLong( surface[n].neighborIndex );
outSurface.parm = LittleLong( surface[n].parm );
fwrite( &outSurface, sizeof( outSurface ), 1, f );
gi.Printf( "surface%02i f=%04x n=%i@%i z=%i ", n, surface[n].flags, surface[n].neighborCount, surface[n].neighborIndex, (int)surface[n].origin[2] );
gi.Printf( "(%i,%i)-(%i,%i)\n", (int)surface[n].absmin[0], (int)surface[n].absmin[1], (int)surface[n].absmax[0], (int)surface[n].absmax[1] );
}
// write neighbors
for ( n = 0; n < neighborCount; n++ ) {
outNeighbor.origin[0] = LittleFloat( neighbor[n].origin[0] );
outNeighbor.origin[1] = LittleFloat( neighbor[n].origin[1] );
outNeighbor.origin[2] = LittleFloat( neighbor[n].origin[2] );
outNeighbor.absmin[0] = LittleFloat( neighbor[n].absmin[0] );
outNeighbor.absmin[1] = LittleFloat( neighbor[n].absmin[1] );
outNeighbor.absmax[0] = LittleFloat( neighbor[n].absmax[0] );
outNeighbor.absmax[1] = LittleFloat( neighbor[n].absmax[1] );
outNeighbor.surfaceNum = LittleLong( neighbor[n].surfaceNum );
outNeighbor.flags = LittleLong( neighbor[n].flags );
outNeighbor.cost = LittleLong( neighbor[n].cost );
outNeighbor.filler = LittleLong( neighbor[n].filler );
fwrite( &outNeighbor, sizeof( outNeighbor ), 1, f );
gi.Printf( "neighbor%03i f=%04x surface=%02i cost=%f\n", n, neighbor[n].flags, neighbor[n].surfaceNum, neighbor[n].cost );
}
// write routes
fwrite( route, surfaceCount * surfaceCount * sizeof( byte ), 1, f );
fclose( f );
}
//
// Nav_Gen_f
//
void Nav_Gen_f( void ) {
int start;
int elapsed;
int i;
gentity_t *ent;
for( i = 0; i < globals.num_entities; i++ )
{
ent = &g_entities[i];
if ( ent->entity && ent->entity->isSubclassOf( Mover ) )
{
ent->entity->unlink();
}
}
// FindSpots
gi.Printf( "FindSpots\n" );
start = gi.Milliseconds();
FindSpots();
elapsed = gi.Milliseconds() - start;
gi.Printf( " %.2f seconds elapsed\n", (float)elapsed / 1000.0 );
// FindSurfaces
gi.Printf( "FindSurfaces\n" );
start = gi.Milliseconds();
//FindSurfaces();
FindConvexSurfaces();
elapsed = gi.Milliseconds() - start;
gi.Printf( " %.2f seconds elapsed\n", (float)elapsed / 1000.0 );
// FindNeighbors
gi.Printf( "FindNeighbors\n" );
start = gi.Milliseconds();
FindNeighbors();
elapsed = gi.Milliseconds() - start;
gi.Printf( " %.2f seconds elapsed\n", (float)elapsed / 1000.0 );
// InflateSurfaces
gi.Printf( "InflateSurfaces\n" );
start = gi.Milliseconds();
InflateSurfaces();
elapsed = gi.Milliseconds() - start;
gi.Printf( " %.2f seconds elapsed\n", (float)elapsed / 1000.0 );
// CalculateAllRoutes
gi.Printf( "CalculateAllRoutes\n" );
start = gi.Milliseconds();
CalculateAllRoutes();
elapsed = gi.Milliseconds() - start;
gi.Printf( " %.2f seconds elapsed\n", (float)elapsed / 1000.0 );
// WriteNavigationData
start = gi.Milliseconds();
WriteNavigationData();
elapsed = gi.Milliseconds() - start;
gi.Printf( " %.2f seconds elapsed\n", (float)elapsed / 1000.0 );
gi.TagFree( route );
gi.TagFree( neighbor );
gi.TagFree( surface );
for( i = 0; i < globals.num_entities; i++ )
{
ent = &g_entities[i];
if ( ent->entity && ent->entity->isSubclassOf( Mover ) )
{
ent->entity->link();
}
}
}