4fcd615308
some tweaks to try to make webgl more robust. added r_max_gpu_bones cvar. defaults to 0 in webgl for now (64 otherwise). fixed gles2+skeletal issue that was breaking webgl (note r_max_gpu_bones needs to be set to 32 or something for it to actually use glsl bones with webgl). sound rates now internally use decimals instead of percents. because meh. allow PEXT2_MAXPLAYERS and angle deltas in nq. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5052 fc73d0e0-1445-4013-8a0c-d673dee63da5
2140 lines
51 KiB
C
2140 lines
51 KiB
C
#include "quakedef.h"
|
|
|
|
#include "pr_common.h"
|
|
|
|
#include "shader.h"
|
|
|
|
extern cvar_t r_decal_noperpendicular;
|
|
|
|
/*
|
|
Decal functions
|
|
*/
|
|
|
|
#define MAXFRAGMENTVERTS 360
|
|
int Fragment_ClipPolyToPlane(float *inverts, float *outverts, int incount, float *plane, float planedist)
|
|
{
|
|
#define C 4
|
|
float dotv[MAXFRAGMENTVERTS+1];
|
|
char keep[MAXFRAGMENTVERTS+1];
|
|
#define KEEP_KILL 0
|
|
#define KEEP_KEEP 1
|
|
#define KEEP_BORDER 2
|
|
int i;
|
|
int outcount = 0;
|
|
int clippedcount = 0;
|
|
float d, *p1, *p2, *out;
|
|
#define FRAG_EPSILON (1.0/32) //0.5
|
|
|
|
for (i = 0; i < incount; i++)
|
|
{
|
|
dotv[i] = DotProduct((inverts+i*C), plane) - planedist;
|
|
if (dotv[i]<-FRAG_EPSILON)
|
|
{
|
|
keep[i] = KEEP_KILL;
|
|
clippedcount++;
|
|
}
|
|
else if (dotv[i] > FRAG_EPSILON)
|
|
keep[i] = KEEP_KEEP;
|
|
else
|
|
keep[i] = KEEP_BORDER;
|
|
}
|
|
dotv[i] = dotv[0];
|
|
keep[i] = keep[0];
|
|
|
|
if (clippedcount == incount)
|
|
return 0; //all were clipped
|
|
if (clippedcount == 0)
|
|
{ //none were clipped
|
|
for (i = 0; i < incount; i++)
|
|
VectorCopy((inverts+i*C), (outverts+i*C));
|
|
return incount;
|
|
}
|
|
|
|
for (i = 0; i < incount; i++)
|
|
{
|
|
p1 = inverts+i*C;
|
|
if (keep[i] == KEEP_BORDER)
|
|
{
|
|
out = outverts+outcount++*C;
|
|
VectorCopy(p1, out);
|
|
continue;
|
|
}
|
|
if (keep[i] == KEEP_KEEP)
|
|
{
|
|
out = outverts+outcount++*C;
|
|
VectorCopy(p1, out);
|
|
}
|
|
if (keep[i+1] == KEEP_BORDER || keep[i] == keep[i+1])
|
|
continue;
|
|
p2 = inverts+((i+1)%incount)*C;
|
|
d = dotv[i] - dotv[i+1];
|
|
if (d)
|
|
d = dotv[i] / d;
|
|
|
|
out = outverts+outcount++*C;
|
|
VectorInterpolate(p1, d, p2, out);
|
|
}
|
|
return outcount;
|
|
}
|
|
|
|
//the plane itself must be a vec4_t, but can have other data packed between
|
|
size_t Fragment_ClipPlaneToBrush(vecV_t *points, size_t maxpoints, void *planes, size_t planestride, size_t numplanes, vec4_t face)
|
|
{
|
|
int p, a;
|
|
vec4_t verts[MAXFRAGMENTVERTS];
|
|
vec4_t verts2[MAXFRAGMENTVERTS];
|
|
vec4_t *cverts;
|
|
int flip;
|
|
// vec3_t d1, d2, n;
|
|
size_t numverts;
|
|
|
|
//generate some huge quad/poly aligned with the plane
|
|
vec3_t tmp;
|
|
vec3_t right, forward;
|
|
double t;
|
|
float *plane;
|
|
|
|
// if (face[2] != 1)
|
|
// return 0;
|
|
|
|
t = fabs(face[2]);
|
|
if (t > fabs(face[0]) && t > fabs(face[1]))
|
|
VectorSet(tmp, 1, 0, 0);
|
|
else
|
|
VectorSet(tmp, 0, 0, 1);
|
|
|
|
CrossProduct(face, tmp, right);
|
|
VectorNormalize(right);
|
|
CrossProduct(face, right, forward);
|
|
VectorNormalize(forward);
|
|
|
|
VectorScale(face, face[3], verts[0]);
|
|
VectorMA(verts[0], 32767, right, verts[0]);
|
|
VectorMA(verts[0], 32767, forward, verts[0]);
|
|
|
|
VectorScale(face, face[3], verts[1]);
|
|
VectorMA(verts[1], 32767, right, verts[1]);
|
|
VectorMA(verts[1], -32767, forward, verts[1]);
|
|
|
|
VectorScale(face, face[3], verts[2]);
|
|
VectorMA(verts[2], -32767, right, verts[2]);
|
|
VectorMA(verts[2], -32767, forward, verts[2]);
|
|
|
|
VectorScale(face, face[3], verts[3]);
|
|
VectorMA(verts[3], -32767, right, verts[3]);
|
|
VectorMA(verts[3], 32767, forward, verts[3]);
|
|
|
|
numverts = 4;
|
|
|
|
|
|
//clip the quad to the various other planes
|
|
flip = 0;
|
|
for (p = 0; p < numplanes; p++)
|
|
{
|
|
plane = (float*)((qbyte*)planes + p*planestride);
|
|
if (plane != face)
|
|
{
|
|
vec3_t norm;
|
|
flip^=1;
|
|
VectorNegate(plane, norm);
|
|
if (flip)
|
|
numverts = Fragment_ClipPolyToPlane((float*)verts, (float*)verts2, numverts, norm, -plane[3]);
|
|
else
|
|
numverts = Fragment_ClipPolyToPlane((float*)verts2, (float*)verts, numverts, norm, -plane[3]);
|
|
|
|
if (numverts < 3) //totally clipped.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (numverts > maxpoints)
|
|
return 0;
|
|
|
|
if (flip)
|
|
cverts = verts2;
|
|
else
|
|
cverts = verts;
|
|
for (p = 0; p < numverts; p++)
|
|
{
|
|
for (a = 0; a < 3; a++)
|
|
{
|
|
float f = cverts[p][a];
|
|
int rounded = floor(f + 0.5);
|
|
//if its within 1/1000th of a qu, just round it.
|
|
if (fabs(f - rounded) < 0.001)
|
|
points[p][a] = rounded;
|
|
else
|
|
points[p][a] = f;
|
|
}
|
|
}
|
|
|
|
return numverts;
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
|
|
#define MAXFRAGMENTTRIS 256
|
|
vec3_t decalfragmentverts[MAXFRAGMENTTRIS*3];
|
|
|
|
struct fragmentdecal_s
|
|
{
|
|
vec3_t center;
|
|
|
|
vec3_t normal;
|
|
// vec3_t tangent1;
|
|
// vec3_t tangent2;
|
|
|
|
vec3_t planenorm[6];
|
|
float planedist[6];
|
|
int numplanes;
|
|
|
|
vec_t radius;
|
|
|
|
//will only appear on surfaces with the matching surfaceflag
|
|
unsigned int surfflagmask;
|
|
unsigned int surfflagmatch;
|
|
|
|
void (*callback)(void *ctx, vec3_t *fte_restrict points, size_t numpoints, shader_t *shader);
|
|
void *ctx;
|
|
};
|
|
|
|
//#define SHOWCLIPS
|
|
//#define FRAGMENTASTRIANGLES //works, but produces more fragments.
|
|
|
|
#ifdef FRAGMENTASTRIANGLES
|
|
|
|
//if the triangle is clipped away, go recursive if there are tris left.
|
|
static void Fragment_ClipTriToPlane(int trinum, float *plane, float planedist, fragmentdecal_t *dec)
|
|
{
|
|
float *point[3];
|
|
float dotv[3];
|
|
|
|
vec3_t impact1, impact2;
|
|
float t;
|
|
|
|
int i, i2, i3;
|
|
int clippedverts = 0;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
point[i] = decalfragmentverts[trinum*3+i];
|
|
dotv[i] = DotProduct(point[i], plane)-planedist;
|
|
clippedverts += dotv[i] < 0;
|
|
}
|
|
|
|
//if they're all clipped away, scrap the tri
|
|
switch (clippedverts)
|
|
{
|
|
case 0:
|
|
return; //plane does not clip the triangle.
|
|
|
|
case 1: //split into 3, disregard the clipped vert
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (dotv[i] < 0)
|
|
{ //This is the vertex that's getting clipped.
|
|
|
|
if (dotv[i] > -DIST_EPSILON)
|
|
return; //it's only over the line by a tiny ammount.
|
|
|
|
i2 = (i+1)%3;
|
|
i3 = (i+2)%3;
|
|
|
|
if (dotv[i2] < DIST_EPSILON)
|
|
return;
|
|
if (dotv[i3] < DIST_EPSILON)
|
|
return;
|
|
|
|
//work out where the two lines impact the plane
|
|
t = (dotv[i]) / (dotv[i]-dotv[i2]);
|
|
VectorInterpolate(point[i], t, point[i2], impact1);
|
|
|
|
t = (dotv[i]) / (dotv[i]-dotv[i3]);
|
|
VectorInterpolate(point[i], t, point[i3], impact2);
|
|
|
|
#ifdef SHOWCLIPS
|
|
if (dec->numtris != MAXFRAGMENTTRIS)
|
|
{
|
|
VectorCopy(impact2, decalfragmentverts[dec->numtris*3+0]);
|
|
VectorCopy(decalfragmentverts[trinum*3+i], decalfragmentverts[dec->numtris*3+1]);
|
|
VectorCopy(impact1, decalfragmentverts[dec->numtris*3+2]);
|
|
dec->numtris++;
|
|
}
|
|
#endif
|
|
|
|
|
|
//shrink the tri, putting the impact into the killed vertex.
|
|
VectorCopy(impact2, point[i]);
|
|
|
|
|
|
if (dec->numtris == MAXFRAGMENTTRIS)
|
|
return; //:(
|
|
|
|
//build the second tri
|
|
VectorCopy(impact1, decalfragmentverts[dec->numtris*3+0]);
|
|
VectorCopy(decalfragmentverts[trinum*3+i2], decalfragmentverts[dec->numtris*3+1]);
|
|
VectorCopy(impact2, decalfragmentverts[dec->numtris*3+2]);
|
|
dec->numtris++;
|
|
|
|
return;
|
|
}
|
|
}
|
|
Sys_Error("Fragment_ClipTriToPlane: Clipped vertex not founc\n");
|
|
return; //can't handle it
|
|
case 2: //split into 3, disregarding both the clipped.
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (!(dotv[i] < 0))
|
|
{ //This is the vertex that's staying.
|
|
|
|
if (dotv[i] < DIST_EPSILON)
|
|
break; //only just inside
|
|
|
|
i2 = (i+1)%3;
|
|
i3 = (i+2)%3;
|
|
|
|
//work out where the two lines impact the plane
|
|
t = (dotv[i]) / (dotv[i]-dotv[i2]);
|
|
VectorInterpolate(point[i], t, point[i2], impact1);
|
|
|
|
t = (dotv[i]) / (dotv[i]-dotv[i3]);
|
|
VectorInterpolate(point[i], t, point[i3], impact2);
|
|
|
|
//shrink the tri, putting the impact into the killed vertex.
|
|
|
|
#ifdef SHOWCLIPS
|
|
if (dec->numtris != MAXFRAGMENTTRIS)
|
|
{
|
|
VectorCopy(impact1, decalfragmentverts[dec->numtris*3+0]);
|
|
VectorCopy(point[i2], decalfragmentverts[dec->numtris*3+1]);
|
|
VectorCopy(point[i3], decalfragmentverts[dec->numtris*3+2]);
|
|
dec->numtris++;
|
|
}
|
|
if (dec->numtris != MAXFRAGMENTTRIS)
|
|
{
|
|
VectorCopy(impact1, decalfragmentverts[dec->numtris*3+0]);
|
|
VectorCopy(point[i3], decalfragmentverts[dec->numtris*3+1]);
|
|
VectorCopy(impact2, decalfragmentverts[dec->numtris*3+2]);
|
|
dec->numtris++;
|
|
}
|
|
#endif
|
|
|
|
VectorCopy(impact1, point[i2]);
|
|
VectorCopy(impact2, point[i3]);
|
|
return;
|
|
}
|
|
}
|
|
case 3://scrap it
|
|
//fill the verts with the verts of the last and go recursive (due to the nature of Fragment_ClipTriangle, which doesn't actually know if we clip them away)
|
|
#ifndef SHOWCLIPS
|
|
dec->numtris--;
|
|
VectorCopy(decalfragmentverts[dec->numtris*3+0], decalfragmentverts[trinum*3+0]);
|
|
VectorCopy(decalfragmentverts[dec->numtris*3+1], decalfragmentverts[trinum*3+1]);
|
|
VectorCopy(decalfragmentverts[dec->numtris*3+2], decalfragmentverts[trinum*3+2]);
|
|
if (trinum < dec->numtris)
|
|
Fragment_ClipTriToPlane(trinum, plane, planedist, dec);
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void Fragment_ClipTriangle(fragmentdecal_t *dec, float *a, float *b, float *c)
|
|
{
|
|
//emit the triangle, and clip it's fragments.
|
|
int start, i;
|
|
|
|
int p;
|
|
|
|
if (dec->numtris == MAXFRAGMENTTRIS)
|
|
return; //:(
|
|
|
|
start = dec->numtris;
|
|
|
|
VectorCopy(a, decalfragmentverts[dec->numtris*3+0]);
|
|
VectorCopy(b, decalfragmentverts[dec->numtris*3+1]);
|
|
VectorCopy(c, decalfragmentverts[dec->numtris*3+2]);
|
|
dec->numtris++;
|
|
|
|
//clip all the fragments to all of the planes.
|
|
//This will produce a quad if the source triangle was big enough.
|
|
|
|
for (p = 0; p < 6; p++)
|
|
{
|
|
for (i = start; i < dec->numtris; i++)
|
|
Fragment_ClipTriToPlane(i, dec->planenorm[p], dec->plantdist[p], dec);
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
void Fragment_ClipPoly(fragmentdecal_t *dec, int numverts, float *inverts, shader_t *surfshader)
|
|
{
|
|
//emit the triangle, and clip it's fragments.
|
|
int p;
|
|
float verts[MAXFRAGMENTVERTS*C];
|
|
float verts2[MAXFRAGMENTVERTS*C];
|
|
float *cverts;
|
|
int flip;
|
|
vec3_t d1, d2, n;
|
|
size_t numtris;
|
|
|
|
if (numverts > MAXFRAGMENTTRIS)
|
|
return;
|
|
|
|
if (r_decal_noperpendicular.ival)
|
|
{
|
|
VectorSubtract(inverts+C*1, inverts+C*0, d1);
|
|
for (p = 2; ; p++)
|
|
{
|
|
if (p >= numverts)
|
|
return;
|
|
VectorSubtract(inverts+C*p, inverts+C*0, d2);
|
|
CrossProduct(d1, d2, n);
|
|
if (DotProduct(n,n)>.1)
|
|
break;
|
|
}
|
|
VectorNormalizeFast(n);
|
|
if (DotProduct(n, dec->normal) < 0.1)
|
|
return; //faces too far way from the normal
|
|
}
|
|
|
|
//clip to the first plane specially, so we don't have extra copys
|
|
numverts = Fragment_ClipPolyToPlane(inverts, verts, numverts, dec->planenorm[0], dec->planedist[0]);
|
|
|
|
//clip the triangle to the 6 planes.
|
|
flip = 0;
|
|
for (p = 1; p < dec->numplanes; p++)
|
|
{
|
|
flip^=1;
|
|
if (flip)
|
|
numverts = Fragment_ClipPolyToPlane(verts, verts2, numverts, dec->planenorm[p], dec->planedist[p]);
|
|
else
|
|
numverts = Fragment_ClipPolyToPlane(verts2, verts, numverts, dec->planenorm[p], dec->planedist[p]);
|
|
|
|
if (numverts < 3) //totally clipped.
|
|
return;
|
|
}
|
|
|
|
if (flip)
|
|
cverts = verts2;
|
|
else
|
|
cverts = verts;
|
|
|
|
//decompose the resultant polygon into triangles.
|
|
|
|
numtris = 0;
|
|
while(numverts-->2)
|
|
{
|
|
if (numtris == MAXFRAGMENTTRIS)
|
|
{
|
|
dec->callback(dec->ctx, decalfragmentverts, numtris, NULL);
|
|
numtris = 0;
|
|
break;
|
|
}
|
|
|
|
VectorCopy((cverts+C*0), decalfragmentverts[numtris*3+0]);
|
|
VectorCopy((cverts+C*(numverts-1)), decalfragmentverts[numtris*3+1]);
|
|
VectorCopy((cverts+C*numverts), decalfragmentverts[numtris*3+2]);
|
|
numtris++;
|
|
}
|
|
if (numtris)
|
|
dec->callback(dec->ctx, decalfragmentverts, numtris, surfshader);
|
|
}
|
|
|
|
#endif
|
|
|
|
//this could be inlined, but I'm lazy.
|
|
static void Fragment_Mesh (fragmentdecal_t *dec, mesh_t *mesh, mtexinfo_t *texinfo)
|
|
{
|
|
int i;
|
|
vecV_t verts[3];
|
|
shader_t *surfshader = texinfo->texture->shader;
|
|
|
|
if (surfshader->flags & SHADER_NOMARKS)
|
|
return;
|
|
|
|
if (dec->surfflagmask)
|
|
{
|
|
if ((texinfo->flags & dec->surfflagmask) != dec->surfflagmatch)
|
|
return;
|
|
}
|
|
|
|
/*if its a triangle fan/poly/quad then we can just submit the entire thing without generating extra fragments*/
|
|
if (mesh->istrifan)
|
|
{
|
|
Fragment_ClipPoly(dec, mesh->numvertexes, mesh->xyz_array[0], surfshader);
|
|
return;
|
|
}
|
|
|
|
//Fixme: optimise q3 patches (quad strips with bends between each strip)
|
|
|
|
/*otherwise it goes in and out in weird places*/
|
|
for (i = 0; i < mesh->numindexes; i+=3)
|
|
{
|
|
VectorCopy(mesh->xyz_array[mesh->indexes[i+0]], verts[0]);
|
|
VectorCopy(mesh->xyz_array[mesh->indexes[i+1]], verts[1]);
|
|
VectorCopy(mesh->xyz_array[mesh->indexes[i+2]], verts[2]);
|
|
Fragment_ClipPoly(dec, 3, verts[0], surfshader);
|
|
}
|
|
}
|
|
|
|
#ifdef Q1BSPS
|
|
static void Q1BSP_ClipDecalToNodes (model_t *mod, fragmentdecal_t *dec, mnode_t *node)
|
|
{
|
|
mplane_t *splitplane;
|
|
float dist;
|
|
msurface_t *surf;
|
|
int i;
|
|
|
|
if (node->contents < 0)
|
|
return;
|
|
|
|
splitplane = node->plane;
|
|
dist = DotProduct (dec->center, splitplane->normal) - splitplane->dist;
|
|
|
|
if (dist > dec->radius)
|
|
{
|
|
Q1BSP_ClipDecalToNodes (mod, dec, node->children[0]);
|
|
return;
|
|
}
|
|
if (dist < -dec->radius)
|
|
{
|
|
Q1BSP_ClipDecalToNodes (mod, dec, node->children[1]);
|
|
return;
|
|
}
|
|
|
|
// mark the polygons
|
|
surf = mod->surfaces + node->firstsurface;
|
|
if (r_decal_noperpendicular.ival)
|
|
{
|
|
for (i=0 ; i<node->numsurfaces ; i++, surf++)
|
|
{
|
|
if (surf->flags & SURF_PLANEBACK)
|
|
{
|
|
if (-DotProduct(surf->plane->normal, dec->normal) > -0.5)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (DotProduct(surf->plane->normal, dec->normal) > -0.5)
|
|
continue;
|
|
}
|
|
Fragment_Mesh(dec, surf->mesh, surf->texinfo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i=0 ; i<node->numsurfaces ; i++, surf++)
|
|
Fragment_Mesh(dec, surf->mesh, surf->texinfo);
|
|
}
|
|
|
|
Q1BSP_ClipDecalToNodes (mod, dec, node->children[0]);
|
|
Q1BSP_ClipDecalToNodes (mod, dec, node->children[1]);
|
|
}
|
|
#endif
|
|
|
|
#ifdef RTLIGHTS
|
|
extern int sh_shadowframe;
|
|
#else
|
|
static int sh_shadowframe;
|
|
#endif
|
|
#ifdef Q3BSPS
|
|
static void Q3BSP_ClipDecalToNodes (fragmentdecal_t *dec, mnode_t *node)
|
|
{
|
|
mplane_t *splitplane;
|
|
float dist;
|
|
msurface_t **msurf;
|
|
msurface_t *surf;
|
|
mleaf_t *leaf;
|
|
int i;
|
|
|
|
if (node->contents != -1)
|
|
{
|
|
leaf = (mleaf_t *)node;
|
|
// mark the polygons
|
|
msurf = leaf->firstmarksurface;
|
|
for (i=0 ; i<leaf->nummarksurfaces ; i++, msurf++)
|
|
{
|
|
surf = *msurf;
|
|
|
|
//only check each surface once. it can appear in multiple leafs.
|
|
if (surf->shadowframe == sh_shadowframe)
|
|
continue;
|
|
surf->shadowframe = sh_shadowframe;
|
|
|
|
Fragment_Mesh(dec, surf->mesh, surf->texinfo);
|
|
}
|
|
return;
|
|
}
|
|
|
|
splitplane = node->plane;
|
|
dist = DotProduct (dec->center, splitplane->normal) - splitplane->dist;
|
|
|
|
if (dist > dec->radius)
|
|
{
|
|
Q3BSP_ClipDecalToNodes (dec, node->children[0]);
|
|
return;
|
|
}
|
|
if (dist < -dec->radius)
|
|
{
|
|
Q3BSP_ClipDecalToNodes (dec, node->children[1]);
|
|
return;
|
|
}
|
|
Q3BSP_ClipDecalToNodes (dec, node->children[0]);
|
|
Q3BSP_ClipDecalToNodes (dec, node->children[1]);
|
|
}
|
|
#endif
|
|
|
|
void Mod_ClipDecal(struct model_s *mod, vec3_t center, vec3_t normal, vec3_t tangent1, vec3_t tangent2, float size, unsigned int surfflagmask, unsigned int surfflagmatch, void (*callback)(void *ctx, vec3_t *fte_restrict points, size_t numpoints, shader_t *shader), void *ctx)
|
|
{ //quad marks a full, independant quad
|
|
int p;
|
|
float r;
|
|
fragmentdecal_t dec;
|
|
|
|
VectorCopy(center, dec.center);
|
|
VectorCopy(normal, dec.normal);
|
|
dec.radius = 0;
|
|
dec.callback = callback;
|
|
dec.ctx = ctx;
|
|
dec.surfflagmask = surfflagmask;
|
|
dec.surfflagmatch = surfflagmatch;
|
|
|
|
VectorCopy(tangent1, dec.planenorm[0]);
|
|
VectorNegate(tangent1, dec.planenorm[1]);
|
|
VectorCopy(tangent2, dec.planenorm[2]);
|
|
VectorNegate(tangent2, dec.planenorm[3]);
|
|
VectorCopy(dec.normal, dec.planenorm[4]);
|
|
VectorNegate(dec.normal, dec.planenorm[5]);
|
|
for (p = 0; p < 6; p++)
|
|
{
|
|
r = sqrt(DotProduct(dec.planenorm[p], dec.planenorm[p]));
|
|
VectorScale(dec.planenorm[p], 1/r, dec.planenorm[p]);
|
|
r*= size/2;
|
|
if (r > dec.radius)
|
|
dec.radius = r;
|
|
dec.planedist[p] = -(r - DotProduct(dec.center, dec.planenorm[p]));
|
|
}
|
|
dec.numplanes = 6;
|
|
|
|
sh_shadowframe++;
|
|
|
|
if (!mod || mod->loadstate != MLS_LOADED || mod->type != mod_brush)
|
|
{
|
|
}
|
|
#ifdef Q1BSPS
|
|
else if (mod->fromgame == fg_quake || mod->fromgame == fg_halflife)
|
|
Q1BSP_ClipDecalToNodes(mod, &dec, mod->rootnode);
|
|
#endif
|
|
#ifdef Q3BSPS
|
|
else if (cl.worldmodel->fromgame == fg_quake3)
|
|
Q3BSP_ClipDecalToNodes(&dec, mod->rootnode);
|
|
#endif
|
|
|
|
#ifdef TERRAIN
|
|
if (cl.worldmodel && cl.worldmodel->terrain)
|
|
Terrain_ClipDecal(&dec, center, dec.radius, mod);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
Decal functions
|
|
|
|
============================================================================
|
|
|
|
Physics functions (common)
|
|
*/
|
|
#ifdef Q1BSPS
|
|
|
|
void Q1BSP_CheckHullNodes(hull_t *hull)
|
|
{
|
|
int num, c;
|
|
mclipnode_t *node;
|
|
for (num = hull->firstclipnode; num < hull->lastclipnode; num++)
|
|
{
|
|
node = hull->clipnodes + num;
|
|
for (c = 0; c < 2; c++)
|
|
if (node->children[c] >= 0)
|
|
if (node->children[c] < hull->firstclipnode || node->children[c] > hull->lastclipnode)
|
|
Sys_Error ("Q1BSP_CheckHull: bad node number");
|
|
|
|
}
|
|
}
|
|
|
|
|
|
static int Q1_ModelPointContents (mnode_t *node, vec3_t p)
|
|
{
|
|
float d;
|
|
mplane_t *plane;
|
|
while(node->contents >= 0)
|
|
{
|
|
plane = node->plane;
|
|
if (plane->type < 3)
|
|
d = p[plane->type] - plane->dist;
|
|
else
|
|
d = DotProduct(plane->normal, p) - plane->dist;
|
|
node = node->children[d<0];
|
|
}
|
|
return node->contents;
|
|
}
|
|
|
|
#endif//Q1BSPS
|
|
/*
|
|
==================
|
|
SV_HullPointContents
|
|
|
|
==================
|
|
*/
|
|
static int Q1_HullPointContents (hull_t *hull, int num, vec3_t p)
|
|
{
|
|
float d;
|
|
mclipnode_t *node;
|
|
mplane_t *plane;
|
|
|
|
while (num >= 0)
|
|
{
|
|
node = hull->clipnodes + num;
|
|
plane = hull->planes + node->planenum;
|
|
|
|
if (plane->type < 3)
|
|
d = p[plane->type] - plane->dist;
|
|
else
|
|
d = DotProduct (plane->normal, p) - plane->dist;
|
|
if (d < 0)
|
|
num = node->children[1];
|
|
else
|
|
num = node->children[0];
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
#define DIST_EPSILON (0.03125)
|
|
#if 1
|
|
enum
|
|
{
|
|
rht_solid,
|
|
rht_empty,
|
|
rht_impact
|
|
};
|
|
struct rhtctx_s
|
|
{
|
|
vec3_t start, end;
|
|
mclipnode_t *clipnodes;
|
|
mplane_t *planes;
|
|
};
|
|
static int Q1BSP_RecursiveHullTrace (struct rhtctx_s *ctx, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace)
|
|
{
|
|
mclipnode_t *node;
|
|
mplane_t *plane;
|
|
float t1, t2;
|
|
vec3_t mid;
|
|
int side;
|
|
float midf;
|
|
int rht;
|
|
|
|
reenter:
|
|
|
|
if (num < 0)
|
|
{
|
|
/*hit a leaf*/
|
|
if (num == Q1CONTENTS_SOLID)
|
|
{
|
|
if (trace->allsolid)
|
|
trace->startsolid = true;
|
|
return rht_solid;
|
|
}
|
|
else
|
|
{
|
|
trace->allsolid = false;
|
|
if (num == Q1CONTENTS_EMPTY)
|
|
trace->inopen = true;
|
|
else
|
|
trace->inwater = true;
|
|
return rht_empty;
|
|
}
|
|
}
|
|
|
|
/*its a node*/
|
|
|
|
/*get the node info*/
|
|
node = ctx->clipnodes + num;
|
|
plane = ctx->planes + node->planenum;
|
|
|
|
if (plane->type < 3)
|
|
{
|
|
t1 = p1[plane->type] - plane->dist;
|
|
t2 = p2[plane->type] - plane->dist;
|
|
}
|
|
else
|
|
{
|
|
t1 = DotProduct (plane->normal, p1) - plane->dist;
|
|
t2 = DotProduct (plane->normal, p2) - plane->dist;
|
|
}
|
|
|
|
/*if its completely on one side, resume on that side*/
|
|
if (t1 >= 0 && t2 >= 0)
|
|
{
|
|
//return Q1BSP_RecursiveHullTrace (hull, node->children[0], p1f, p2f, p1, p2, trace);
|
|
num = node->children[0];
|
|
goto reenter;
|
|
}
|
|
if (t1 < 0 && t2 < 0)
|
|
{
|
|
//return Q1BSP_RecursiveHullTrace (hull, node->children[1], p1f, p2f, p1, p2, trace);
|
|
num = node->children[1];
|
|
goto reenter;
|
|
}
|
|
|
|
if (plane->type < 3)
|
|
{
|
|
t1 = ctx->start[plane->type] - plane->dist;
|
|
t2 = ctx->end[plane->type] - plane->dist;
|
|
}
|
|
else
|
|
{
|
|
t1 = DotProduct (plane->normal, ctx->start) - plane->dist;
|
|
t2 = DotProduct (plane->normal, ctx->end) - plane->dist;
|
|
}
|
|
|
|
side = t1 < 0;
|
|
|
|
midf = t1 / (t1 - t2);
|
|
if (midf < p1f) midf = p1f;
|
|
if (midf > p2f) midf = p2f;
|
|
VectorInterpolate(ctx->start, midf, ctx->end, mid);
|
|
|
|
rht = Q1BSP_RecursiveHullTrace(ctx, node->children[side], p1f, midf, p1, mid, trace);
|
|
if (rht != rht_empty && !trace->allsolid)
|
|
return rht;
|
|
rht = Q1BSP_RecursiveHullTrace(ctx, node->children[side^1], midf, p2f, mid, p2, trace);
|
|
if (rht != rht_solid)
|
|
return rht;
|
|
|
|
if (side)
|
|
{
|
|
/*we impacted the back of the node, so flip the plane*/
|
|
trace->plane.dist = -plane->dist;
|
|
VectorNegate(plane->normal, trace->plane.normal);
|
|
midf = (t1 + DIST_EPSILON) / (t1 - t2);
|
|
}
|
|
else
|
|
{
|
|
/*we impacted the front of the node*/
|
|
trace->plane.dist = plane->dist;
|
|
VectorCopy(plane->normal, trace->plane.normal);
|
|
midf = (t1 - DIST_EPSILON) / (t1 - t2);
|
|
}
|
|
|
|
t1 = DotProduct (trace->plane.normal, ctx->start) - trace->plane.dist;
|
|
t2 = DotProduct (trace->plane.normal, ctx->end) - trace->plane.dist;
|
|
midf = (t1 - DIST_EPSILON) / (t1 - t2);
|
|
|
|
midf = bound(0, midf, 1);
|
|
trace->fraction = midf;
|
|
VectorCopy (mid, trace->endpos);
|
|
VectorInterpolate(ctx->start, midf, ctx->end, trace->endpos);
|
|
|
|
return rht_impact;
|
|
}
|
|
|
|
qboolean Q1BSP_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace)
|
|
{
|
|
if (VectorEquals(p1, p2))
|
|
{
|
|
/*points cannot cross planes, so do it faster*/
|
|
switch(Q1_HullPointContents(hull, num, p1))
|
|
{
|
|
case Q1CONTENTS_SOLID:
|
|
trace->startsolid = true;
|
|
break;
|
|
case Q1CONTENTS_EMPTY:
|
|
trace->allsolid = false;
|
|
trace->inopen = true;
|
|
break;
|
|
default:
|
|
trace->allsolid = false;
|
|
trace->inwater = true;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
struct rhtctx_s ctx;
|
|
VectorCopy(p1, ctx.start);
|
|
VectorCopy(p2, ctx.end);
|
|
ctx.clipnodes = hull->clipnodes;
|
|
ctx.planes = hull->planes;
|
|
return Q1BSP_RecursiveHullTrace(&ctx, num, p1f, p2f, p1, p2, trace) != rht_impact;
|
|
}
|
|
}
|
|
|
|
#else
|
|
qboolean Q1BSP_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace)
|
|
{
|
|
mclipnode_t *node;
|
|
mplane_t *plane;
|
|
float t1, t2;
|
|
float frac;
|
|
int i;
|
|
vec3_t mid;
|
|
int side;
|
|
float midf;
|
|
|
|
// check for empty
|
|
if (num < 0)
|
|
{
|
|
if (num != Q1CONTENTS_SOLID)
|
|
{
|
|
trace->allsolid = false;
|
|
if (num == Q1CONTENTS_EMPTY)
|
|
trace->inopen = true;
|
|
else
|
|
trace->inwater = true;
|
|
}
|
|
else
|
|
trace->startsolid = true;
|
|
return true; // empty
|
|
}
|
|
|
|
//
|
|
// find the point distances
|
|
//
|
|
node = hull->clipnodes + num;
|
|
plane = hull->planes + node->planenum;
|
|
|
|
if (plane->type < 3)
|
|
{
|
|
t1 = p1[plane->type] - plane->dist;
|
|
t2 = p2[plane->type] - plane->dist;
|
|
}
|
|
else
|
|
{
|
|
t1 = DotProduct (plane->normal, p1) - plane->dist;
|
|
t2 = DotProduct (plane->normal, p2) - plane->dist;
|
|
}
|
|
|
|
#if 1
|
|
if (t1 >= 0 && t2 >= 0)
|
|
return Q1BSP_RecursiveHullCheck (hull, node->children[0], p1f, p2f, p1, p2, trace);
|
|
if (t1 < 0 && t2 < 0)
|
|
return Q1BSP_RecursiveHullCheck (hull, node->children[1], p1f, p2f, p1, p2, trace);
|
|
#else
|
|
if ( (t1 >= DIST_EPSILON && t2 >= DIST_EPSILON) || (t2 > t1 && t1 >= 0) )
|
|
return Q1BSP_RecursiveHullCheck (hull, node->children[0], p1f, p2f, p1, p2, trace);
|
|
if ( (t1 <= -DIST_EPSILON && t2 <= -DIST_EPSILON) || (t2 < t1 && t1 <= 0) )
|
|
return Q1BSP_RecursiveHullCheck (hull, node->children[1], p1f, p2f, p1, p2, trace);
|
|
#endif
|
|
|
|
// put the crosspoint DIST_EPSILON pixels on the near side
|
|
if (t1 < 0)
|
|
frac = (t1 + DIST_EPSILON)/(t1-t2);
|
|
else
|
|
frac = (t1 - DIST_EPSILON)/(t1-t2);
|
|
if (frac < 0)
|
|
frac = 0;
|
|
if (frac > 1)
|
|
frac = 1;
|
|
|
|
midf = p1f + (p2f - p1f)*frac;
|
|
for (i=0 ; i<3 ; i++)
|
|
mid[i] = p1[i] + frac*(p2[i] - p1[i]);
|
|
|
|
side = (t1 < 0);
|
|
|
|
// move up to the node
|
|
if (!Q1BSP_RecursiveHullCheck (hull, node->children[side], p1f, midf, p1, mid, trace) )
|
|
return false;
|
|
|
|
#ifdef PARANOID
|
|
if (Q1BSP_RecursiveHullCheck (sv_hullmodel, mid, node->children[side])
|
|
== Q1CONTENTS_SOLID)
|
|
{
|
|
Con_Printf ("mid PointInHullSolid\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (Q1_HullPointContents (hull, node->children[side^1], mid)
|
|
!= Q1CONTENTS_SOLID)
|
|
// go past the node
|
|
return Q1BSP_RecursiveHullCheck (hull, node->children[side^1], midf, p2f, mid, p2, trace);
|
|
|
|
if (trace->allsolid)
|
|
return false; // never got out of the solid area
|
|
|
|
//==================
|
|
// the other side of the node is solid, this is the impact point
|
|
//==================
|
|
if (!side)
|
|
{
|
|
VectorCopy (plane->normal, trace->plane.normal);
|
|
trace->plane.dist = plane->dist;
|
|
}
|
|
else
|
|
{
|
|
VectorNegate (plane->normal, trace->plane.normal);
|
|
trace->plane.dist = -plane->dist;
|
|
}
|
|
|
|
while (Q1_HullPointContents (hull, hull->firstclipnode, mid)
|
|
== Q1CONTENTS_SOLID)
|
|
{ // shouldn't really happen, but does occasionally
|
|
if (!(frac < 10000000) && !(frac > -10000000))
|
|
{
|
|
trace->fraction = 0;
|
|
VectorClear (trace->endpos);
|
|
Con_Printf ("nan in traceline\n");
|
|
return false;
|
|
}
|
|
frac -= 0.1;
|
|
if (frac < 0)
|
|
{
|
|
trace->fraction = midf;
|
|
VectorCopy (mid, trace->endpos);
|
|
Con_DPrintf ("backup past 0\n");
|
|
return false;
|
|
}
|
|
midf = p1f + (p2f - p1f)*frac;
|
|
for (i=0 ; i<3 ; i++)
|
|
mid[i] = p1[i] + frac*(p2[i] - p1[i]);
|
|
}
|
|
|
|
trace->fraction = midf;
|
|
VectorCopy (mid, trace->endpos);
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#ifdef Q1BSPS
|
|
|
|
/*
|
|
the bsp tree we're walking through is the renderable hull
|
|
we need to trace a box through the world.
|
|
by its very nature, this will reach more nodes than we really want, and as we can follow a node sideways, the underlying bsp structure is no longer 100% reliable (meaning we cross planes that are entirely to one side, and follow its children too)
|
|
so all contents and solidity must come from the brushes and ONLY the brushes.
|
|
*/
|
|
struct traceinfo_s
|
|
{
|
|
unsigned int solidcontents;
|
|
trace_t trace;
|
|
|
|
qboolean capsule;
|
|
float radius;
|
|
/*set even for sphere traces (used for bbox tests)*/
|
|
vec3_t mins;
|
|
vec3_t maxs;
|
|
|
|
vec3_t start;
|
|
vec3_t end;
|
|
|
|
vec3_t up;
|
|
vec3_t capsulesize;
|
|
vec3_t extents;
|
|
};
|
|
|
|
static void Q1BSP_ClipToBrushes(struct traceinfo_s *traceinfo, mbrush_t *brush)
|
|
{
|
|
struct mbrushplane_s *plane;
|
|
struct mbrushplane_s *enterplane;
|
|
int i, j;
|
|
vec3_t ofs;
|
|
qboolean startout, endout;
|
|
float d1,d2,dist,enterdist=0;
|
|
float f, enterfrac, exitfrac;
|
|
|
|
for (; brush; brush = brush->next)
|
|
{
|
|
/*ignore if its not solid to us*/
|
|
if (!(traceinfo->solidcontents & brush->contents))
|
|
continue;
|
|
|
|
startout = false;
|
|
endout = false;
|
|
enterplane= NULL;
|
|
enterfrac = -1;
|
|
exitfrac = 10;
|
|
for (i = brush->numplanes, plane = brush->planes; i; i--, plane++)
|
|
{
|
|
/*calculate the distance based upon the shape of the object we're tracing for*/
|
|
if (traceinfo->capsule)
|
|
{
|
|
dist = DotProduct(traceinfo->up, plane->normal);
|
|
dist = dist*(traceinfo->capsulesize[(dist<0)?1:2]) - traceinfo->capsulesize[0];
|
|
dist = plane->dist - dist;
|
|
|
|
//dist = plane->dist + traceinfo->radius;
|
|
}
|
|
else
|
|
{
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
if (plane->normal[j] < 0)
|
|
ofs[j] = traceinfo->maxs[j];
|
|
else
|
|
ofs[j] = traceinfo->mins[j];
|
|
}
|
|
dist = DotProduct (ofs, plane->normal);
|
|
dist = plane->dist - dist;
|
|
}
|
|
|
|
d1 = DotProduct (traceinfo->start, plane->normal) - dist;
|
|
d2 = DotProduct (traceinfo->end, plane->normal) - dist;
|
|
|
|
if (d1 >= 0)
|
|
startout = true;
|
|
if (d2 > 0)
|
|
endout = true;
|
|
|
|
//if we're fully outside any plane, then we cannot possibly enter the brush, skip to the next one
|
|
if (d1 > 0 && d2 >= 0)
|
|
goto nextbrush;
|
|
|
|
//if we're fully inside the plane, then whatever is happening is not relevent for this plane
|
|
if (d1 < 0 && d2 <= 0)
|
|
continue;
|
|
|
|
f = d1 / (d1-d2);
|
|
if (d1 > d2)
|
|
{
|
|
//entered the brush. favour the furthest fraction to avoid extended edges (yay for convex shapes)
|
|
if (enterfrac < f)
|
|
{
|
|
enterfrac = f;
|
|
enterplane = plane;
|
|
enterdist = dist;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//left the brush, favour the nearest plane (smallest frac)
|
|
if (exitfrac > f)
|
|
{
|
|
exitfrac = f;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!startout)
|
|
{
|
|
traceinfo->trace.startsolid = true;
|
|
if (!endout)
|
|
traceinfo->trace.allsolid = true;
|
|
traceinfo->trace.contents |= brush->contents;
|
|
return;
|
|
}
|
|
if (enterfrac != -1 && enterfrac < exitfrac)
|
|
{
|
|
//impact!
|
|
if (enterfrac < traceinfo->trace.fraction)
|
|
{
|
|
traceinfo->trace.fraction = enterfrac;
|
|
traceinfo->trace.plane.dist = enterdist;
|
|
VectorCopy(enterplane->normal, traceinfo->trace.plane.normal);
|
|
traceinfo->trace.contents = brush->contents;
|
|
}
|
|
}
|
|
nextbrush:
|
|
;
|
|
}
|
|
}
|
|
static void Q1BSP_InsertBrush(mnode_t *node, mbrush_t *brush, vec3_t bmins, vec3_t bmaxs)
|
|
{
|
|
vec3_t nearp, farp;
|
|
float nd, fd;
|
|
int i;
|
|
while(1)
|
|
{
|
|
if (node->contents < 0) /*leaf, so no smaller node to put it in (I'd be surprised if it got this far)*/
|
|
{
|
|
brush->next = node->brushes;
|
|
node->brushes = brush;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (node->plane->normal[i] > 0)
|
|
{
|
|
nearp[i] = bmins[i];
|
|
farp[i] = bmaxs[i];
|
|
}
|
|
else
|
|
{
|
|
nearp[i] = bmaxs[i];
|
|
farp[i] = bmins[i];
|
|
}
|
|
}
|
|
|
|
nd = DotProduct(node->plane->normal, nearp) - node->plane->dist;
|
|
fd = DotProduct(node->plane->normal, farp) - node->plane->dist;
|
|
|
|
/*if its fully on either side, continue walking*/
|
|
if (nd < 0 && fd < 0)
|
|
node = node->children[1];
|
|
else if (nd > 0 && fd > 0)
|
|
node = node->children[0];
|
|
else
|
|
{
|
|
/*plane crosses bbox, so insert here*/
|
|
brush->next = node->brushes;
|
|
node->brushes = brush;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
static void Q1BSP_RecursiveBrushCheck (struct traceinfo_s *traceinfo, mnode_t *node, float p1f, float p2f, vec3_t p1, vec3_t p2)
|
|
{
|
|
mplane_t *plane;
|
|
float t1, t2;
|
|
float frac;
|
|
int i;
|
|
vec3_t mid;
|
|
int side;
|
|
float midf;
|
|
float offset;
|
|
|
|
if (node->brushes)
|
|
{
|
|
Q1BSP_ClipToBrushes(traceinfo, node->brushes);
|
|
}
|
|
|
|
if (traceinfo->trace.fraction < p1f)
|
|
{
|
|
//already hit something closer than this node
|
|
return;
|
|
}
|
|
|
|
if (node->contents < 0)
|
|
{
|
|
//we're in a leaf
|
|
return;
|
|
}
|
|
|
|
//
|
|
// find the point distances
|
|
//
|
|
plane = node->plane;
|
|
|
|
if (plane->type < 3)
|
|
{
|
|
t1 = p1[plane->type] - plane->dist;
|
|
t2 = p2[plane->type] - plane->dist;
|
|
if (plane->normal[plane->type] < 0)
|
|
offset = -traceinfo->mins[plane->type];
|
|
else
|
|
offset = traceinfo->maxs[plane->type];
|
|
}
|
|
else
|
|
{
|
|
t1 = DotProduct (plane->normal, p1) - plane->dist;
|
|
t2 = DotProduct (plane->normal, p2) - plane->dist;
|
|
offset = 0;
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (plane->normal[i] < 0)
|
|
offset += plane->normal[i] * -traceinfo->mins[i];
|
|
else
|
|
offset += plane->normal[i] * traceinfo->maxs[i];
|
|
}
|
|
}
|
|
|
|
/*if we're fully on one side of the trace, go only down that side*/
|
|
if (t1 >= offset && t2 >= offset)
|
|
{
|
|
Q1BSP_RecursiveBrushCheck (traceinfo, node->children[0], p1f, p2f, p1, p2);
|
|
return;
|
|
}
|
|
if (t1 < -offset && t2 < -offset)
|
|
{
|
|
Q1BSP_RecursiveBrushCheck (traceinfo, node->children[1], p1f, p2f, p1, p2);
|
|
return;
|
|
}
|
|
|
|
// put the crosspoint DIST_EPSILON pixels on the near side
|
|
if (t1 == t2)
|
|
{
|
|
side = 0;
|
|
frac = 0;
|
|
}
|
|
else if (t1 < 0)
|
|
{
|
|
frac = (t1 + DIST_EPSILON)/(t1-t2);
|
|
side = 1;
|
|
}
|
|
else
|
|
{
|
|
frac = (t1 - DIST_EPSILON)/(t1-t2);
|
|
side = 0;
|
|
}
|
|
if (frac < 0)
|
|
frac = 0;
|
|
if (frac > 1)
|
|
frac = 1;
|
|
|
|
midf = p1f + (p2f - p1f)*frac;
|
|
for (i=0 ; i<3 ; i++)
|
|
mid[i] = p1[i] + frac*(p2[i] - p1[i]);
|
|
|
|
// move up to the node
|
|
Q1BSP_RecursiveBrushCheck (traceinfo, node->children[side], p1f, midf, p1, mid);
|
|
|
|
// go past the node
|
|
Q1BSP_RecursiveBrushCheck (traceinfo, node->children[side^1], midf, p2f, mid, p2);
|
|
}
|
|
#endif //Q1BSPS
|
|
|
|
static unsigned int Q1BSP_TranslateContents(int contents)
|
|
{
|
|
switch(contents)
|
|
{
|
|
case Q1CONTENTS_EMPTY:
|
|
return FTECONTENTS_EMPTY;
|
|
case Q1CONTENTS_SOLID:
|
|
return FTECONTENTS_SOLID;
|
|
case Q1CONTENTS_WATER:
|
|
return FTECONTENTS_WATER;
|
|
case Q1CONTENTS_SLIME:
|
|
return FTECONTENTS_SLIME;
|
|
case Q1CONTENTS_LAVA:
|
|
return FTECONTENTS_LAVA;
|
|
case Q1CONTENTS_SKY:
|
|
return FTECONTENTS_SKY;
|
|
case Q1CONTENTS_LADDER:
|
|
return FTECONTENTS_LADDER;
|
|
case Q1CONTENTS_CLIP:
|
|
return FTECONTENTS_PLAYERCLIP;
|
|
case Q1CONTENTS_TRANS:
|
|
return FTECONTENTS_SOLID;
|
|
|
|
//q2 is better than nothing, right?
|
|
case Q1CONTENTS_FLOW_1:
|
|
return Q2CONTENTS_CURRENT_0;
|
|
case Q1CONTENTS_FLOW_2:
|
|
return Q2CONTENTS_CURRENT_90;
|
|
case Q1CONTENTS_FLOW_3:
|
|
return Q2CONTENTS_CURRENT_180;
|
|
case Q1CONTENTS_FLOW_4:
|
|
return Q2CONTENTS_CURRENT_270;
|
|
case Q1CONTENTS_FLOW_5:
|
|
return Q2CONTENTS_CURRENT_UP;
|
|
case Q1CONTENTS_FLOW_6:
|
|
return Q2CONTENTS_CURRENT_DOWN;
|
|
|
|
default:
|
|
Con_Printf("Q1BSP_TranslateContents: Unknown contents type - %i", contents);
|
|
return FTECONTENTS_SOLID;
|
|
}
|
|
}
|
|
|
|
int Q1BSP_HullPointContents(hull_t *hull, vec3_t p)
|
|
{
|
|
return Q1BSP_TranslateContents(Q1_HullPointContents(hull, hull->firstclipnode, p));
|
|
}
|
|
|
|
#ifdef Q1BSPS
|
|
unsigned int Q1BSP_PointContents(model_t *model, vec3_t axis[3], vec3_t point)
|
|
{
|
|
int contents;
|
|
if (axis)
|
|
{
|
|
vec3_t transformed;
|
|
transformed[0] = DotProduct(point, axis[0]);
|
|
transformed[1] = DotProduct(point, axis[1]);
|
|
transformed[2] = DotProduct(point, axis[2]);
|
|
return Q1BSP_PointContents(model, NULL, transformed);
|
|
}
|
|
else
|
|
{
|
|
if (!model->firstmodelsurface)
|
|
{
|
|
contents = Q1BSP_TranslateContents(Q1_ModelPointContents(model->nodes, point));
|
|
}
|
|
else
|
|
contents = Q1BSP_HullPointContents(&model->hulls[0], point);
|
|
}
|
|
#ifdef TERRAIN
|
|
if (model->terrain)
|
|
contents |= Heightmap_PointContents(model, NULL, point);
|
|
#endif
|
|
return contents;
|
|
}
|
|
|
|
void Q1BSP_LoadBrushes(model_t *model)
|
|
{
|
|
struct {
|
|
unsigned int ver;
|
|
unsigned int modelnum;
|
|
unsigned int numbrushes;
|
|
unsigned int numplanes;
|
|
} *permodel;
|
|
struct {
|
|
float mins[3];
|
|
float maxs[3];
|
|
signed short contents;
|
|
unsigned short numplanes;
|
|
} *perbrush;
|
|
/*
|
|
Note to implementors:
|
|
a pointy brush with angles pointier than 90 degrees will extend further than any adjacent brush, thus creating invisible walls with larger expansions.
|
|
the engine inserts 6 axial planes acording to the bbox, thus the qbsp need not write any axial planes
|
|
note that doing it this way probably isn't good if you want to query textures...
|
|
*/
|
|
struct {
|
|
vec3_t normal;
|
|
float dist;
|
|
} *perplane;
|
|
|
|
static vec3_t axis[3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
|
|
int br, pl, remainingplanes;
|
|
mbrush_t *brush;
|
|
mnode_t *rootnode;
|
|
unsigned int lumpsizeremaining;
|
|
|
|
model->engineflags &= ~MDLF_HASBRUSHES;
|
|
|
|
permodel = Q1BSPX_FindLump("BRUSHLIST", &lumpsizeremaining);
|
|
if (!permodel)
|
|
return;
|
|
|
|
while (lumpsizeremaining)
|
|
{
|
|
if (lumpsizeremaining < sizeof(*permodel))
|
|
return;
|
|
permodel->ver = LittleLong(permodel->ver);
|
|
permodel->modelnum = LittleLong(permodel->modelnum);
|
|
permodel->numbrushes = LittleLong(permodel->numbrushes);
|
|
permodel->numplanes = LittleLong(permodel->numplanes);
|
|
if (permodel->ver != 1 || lumpsizeremaining < sizeof(*permodel) + permodel->numbrushes*sizeof(*perbrush) + permodel->numplanes*sizeof(*perplane))
|
|
return;
|
|
|
|
//find the correct rootnode for the submodel (submodels are not set up yet).
|
|
rootnode = model->nodes;
|
|
if (permodel->modelnum > model->numsubmodels)
|
|
return;
|
|
rootnode += model->submodels[permodel->modelnum].headnode[0];
|
|
|
|
brush = ZG_Malloc(&model->memgroup, (sizeof(*brush) - sizeof(brush->planes[0]))*permodel->numbrushes + sizeof(brush->planes[0])*(permodel->numbrushes*6+permodel->numplanes));
|
|
remainingplanes = permodel->numplanes;
|
|
perbrush = (void*)(permodel+1);
|
|
for (br = 0; br < permodel->numbrushes; br++)
|
|
{
|
|
/*byteswap it all in place*/
|
|
perbrush->mins[0] = LittleFloat(perbrush->mins[0]);
|
|
perbrush->mins[1] = LittleFloat(perbrush->mins[1]);
|
|
perbrush->mins[2] = LittleFloat(perbrush->mins[2]);
|
|
perbrush->maxs[0] = LittleFloat(perbrush->maxs[0]);
|
|
perbrush->maxs[1] = LittleFloat(perbrush->maxs[1]);
|
|
perbrush->maxs[2] = LittleFloat(perbrush->maxs[2]);
|
|
perbrush->contents = LittleShort(perbrush->contents);
|
|
perbrush->numplanes = LittleShort(perbrush->numplanes);
|
|
|
|
/*make sure planes don't overflow*/
|
|
if (perbrush->numplanes > remainingplanes)
|
|
return;
|
|
remainingplanes-=perbrush->numplanes;
|
|
|
|
/*set up the mbrush from the file*/
|
|
brush->contents = Q1BSP_TranslateContents(perbrush->contents);
|
|
brush->numplanes = perbrush->numplanes;
|
|
for (pl = 0, perplane = (void*)(perbrush+1); pl < perbrush->numplanes; pl++, perplane++)
|
|
{
|
|
brush->planes[pl].normal[0] = LittleFloat(perplane->normal[0]);
|
|
brush->planes[pl].normal[1] = LittleFloat(perplane->normal[1]);
|
|
brush->planes[pl].normal[2] = LittleFloat(perplane->normal[2]);
|
|
brush->planes[pl].dist = LittleFloat(perplane->dist);
|
|
}
|
|
|
|
/*and add axial planes acording to the brush's bbox*/
|
|
for (pl = 0; pl < 3; pl++)
|
|
{
|
|
VectorCopy(axis[pl], brush->planes[brush->numplanes].normal);
|
|
brush->planes[brush->numplanes].dist = perbrush->maxs[pl];
|
|
brush->numplanes++;
|
|
}
|
|
for (pl = 0; pl < 3; pl++)
|
|
{
|
|
VectorNegate(axis[pl], brush->planes[brush->numplanes].normal);
|
|
brush->planes[brush->numplanes].dist = -perbrush->mins[pl];
|
|
brush->numplanes++;
|
|
}
|
|
|
|
/*link it in to the bsp tree*/
|
|
Q1BSP_InsertBrush(rootnode, brush, perbrush->mins, perbrush->maxs);
|
|
|
|
/*set up for the next brush*/
|
|
brush = (void*)&brush->planes[brush->numplanes];
|
|
perbrush = (void*)perplane;
|
|
}
|
|
/*move on to the next model*/
|
|
lumpsizeremaining -= sizeof(*permodel) + permodel->numbrushes*sizeof(*perbrush) + permodel->numplanes*sizeof(*perplane);
|
|
permodel = (void*)((char*)permodel + sizeof(*permodel) + permodel->numbrushes*sizeof(*perbrush) + permodel->numplanes*sizeof(*perplane));
|
|
}
|
|
/*parsing was successful! flag it as okay*/
|
|
model->engineflags |= MDLF_HASBRUSHES;
|
|
}
|
|
|
|
hull_t *Q1BSP_ChooseHull(model_t *model, int forcehullnum, vec3_t mins, vec3_t maxs, vec3_t offset)
|
|
{
|
|
hull_t *hull;
|
|
vec3_t size;
|
|
VectorSubtract (maxs, mins, size);
|
|
if (forcehullnum >= 1 && forcehullnum <= MAX_MAP_HULLSM && model->hulls[forcehullnum-1].available)
|
|
hull = &model->hulls[forcehullnum-1];
|
|
else
|
|
{
|
|
#ifdef HEXEN2
|
|
if (model->hulls[5].available)
|
|
{ //choose based on hexen2 sizes.
|
|
|
|
if (size[0] < 3) // Point
|
|
hull = &model->hulls[0];
|
|
else if (size[0] <= 8.1 && model->hulls[4].available)
|
|
hull = &model->hulls[4]; //Pentacles
|
|
else if (size[0] <= 32.1 && size[2] <= 28.1) // Half Player
|
|
hull = &model->hulls[3];
|
|
else if (size[0] <= 32.1) // Full Player
|
|
hull = &model->hulls[1];
|
|
else // Golumn
|
|
hull = &model->hulls[5];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (size[0] < 3 || !model->hulls[1].available)
|
|
hull = &model->hulls[0];
|
|
else if (size[0] <= 32.1 || !model->hulls[2].available)
|
|
{
|
|
if (size[2] < 54.1 && model->hulls[3].available)
|
|
hull = &model->hulls[3]; // 32x32x36 (half-life's crouch)
|
|
else if (model->hulls[1].available)
|
|
hull = &model->hulls[1];
|
|
else
|
|
hull = &model->hulls[0];
|
|
}
|
|
else
|
|
hull = &model->hulls[2];
|
|
}
|
|
}
|
|
|
|
VectorSubtract (hull->clip_mins, mins, offset);
|
|
return hull;
|
|
}
|
|
qboolean Q1BSP_Trace(model_t *model, int forcehullnum, framestate_t *framestate, vec3_t axis[3], vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int hitcontentsmask, trace_t *trace)
|
|
{
|
|
hull_t *hull;
|
|
vec3_t start_l, end_l;
|
|
vec3_t offset;
|
|
|
|
if ((model->engineflags & MDLF_HASBRUSHES))// && (size[0] || size[1] || size[2]))
|
|
{
|
|
struct traceinfo_s traceinfo;
|
|
memset (&traceinfo.trace, 0, sizeof(trace_t));
|
|
traceinfo.trace.fraction = 1;
|
|
traceinfo.trace.allsolid = false;
|
|
VectorCopy(mins, traceinfo.mins);
|
|
VectorCopy(maxs, traceinfo.maxs);
|
|
VectorCopy(start, traceinfo.start);
|
|
VectorCopy(end, traceinfo.end);
|
|
traceinfo.capsule = capsule;
|
|
|
|
if (traceinfo.capsule)
|
|
{
|
|
float ext;
|
|
traceinfo.capsulesize[0] = ((maxs[0]-mins[0]) + (maxs[1]-mins[1]))/4.0;
|
|
traceinfo.capsulesize[1] = maxs[2];
|
|
traceinfo.capsulesize[2] = mins[2];
|
|
ext = (traceinfo.capsulesize[1] > -traceinfo.capsulesize[2])?traceinfo.capsulesize[1]:-traceinfo.capsulesize[2];
|
|
traceinfo.capsulesize[1] -= traceinfo.capsulesize[0];
|
|
traceinfo.capsulesize[2] += traceinfo.capsulesize[0];
|
|
traceinfo.extents[0] = ext+1;
|
|
traceinfo.extents[1] = ext+1;
|
|
traceinfo.extents[2] = ext+1;
|
|
VectorSet(traceinfo.up, 0, 0, 1);
|
|
}
|
|
|
|
/* traceinfo.sphere = true;
|
|
traceinfo.radius = 48;
|
|
traceinfo.mins[0] = -traceinfo.radius;
|
|
traceinfo.mins[1] = -traceinfo.radius;
|
|
traceinfo.mins[2] = -traceinfo.radius;
|
|
traceinfo.maxs[0] = traceinfo.radius;
|
|
traceinfo.maxs[1] = traceinfo.radius;
|
|
traceinfo.maxs[2] = traceinfo.radius;
|
|
*/
|
|
traceinfo.solidcontents = hitcontentsmask;
|
|
Q1BSP_RecursiveBrushCheck(&traceinfo, model->rootnode, 0, 1, start, end);
|
|
memcpy(trace, &traceinfo.trace, sizeof(trace_t));
|
|
if (trace->fraction < 1)
|
|
{
|
|
float d1 = DotProduct(start, trace->plane.normal) - trace->plane.dist;
|
|
float d2 = DotProduct(end, trace->plane.normal) - trace->plane.dist;
|
|
float f = (d1 - DIST_EPSILON) / (d1 - d2);
|
|
if (f < 0)
|
|
f = 0;
|
|
trace->fraction = f;
|
|
}
|
|
VectorInterpolate(start, trace->fraction, end, trace->endpos);
|
|
return trace->fraction != 1;
|
|
}
|
|
|
|
memset (trace, 0, sizeof(trace_t));
|
|
trace->fraction = 1;
|
|
trace->allsolid = true;
|
|
|
|
hull = Q1BSP_ChooseHull(model, forcehullnum, mins, maxs, offset);
|
|
|
|
//fix for hexen2 monsters half-walking into walls.
|
|
// if (forent.flags & FL_MONSTER)
|
|
// {
|
|
// offset[0] = 0;
|
|
// offset[1] = 0;
|
|
// }
|
|
|
|
if (axis)
|
|
{
|
|
vec3_t tmp;
|
|
VectorSubtract(start, offset, tmp);
|
|
start_l[0] = DotProduct(tmp, axis[0]);
|
|
start_l[1] = DotProduct(tmp, axis[1]);
|
|
start_l[2] = DotProduct(tmp, axis[2]);
|
|
VectorSubtract(end, offset, tmp);
|
|
end_l[0] = DotProduct(tmp, axis[0]);
|
|
end_l[1] = DotProduct(tmp, axis[1]);
|
|
end_l[2] = DotProduct(tmp, axis[2]);
|
|
Q1BSP_RecursiveHullCheck(hull, hull->firstclipnode, 0, 1, start_l, end_l, trace);
|
|
|
|
if (trace->fraction == 1)
|
|
{
|
|
VectorCopy (end, trace->endpos);
|
|
}
|
|
else
|
|
{
|
|
vec3_t iaxis[3];
|
|
vec3_t norm;
|
|
Matrix3x3_RM_Invert_Simple((void *)axis, iaxis);
|
|
VectorCopy(trace->plane.normal, norm);
|
|
trace->plane.normal[0] = DotProduct(norm, iaxis[0]);
|
|
trace->plane.normal[1] = DotProduct(norm, iaxis[1]);
|
|
trace->plane.normal[2] = DotProduct(norm, iaxis[2]);
|
|
|
|
/*just interpolate it, its easier than inverse matrix rotations*/
|
|
VectorInterpolate(start, trace->fraction, end, trace->endpos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract(start, offset, start_l);
|
|
VectorSubtract(end, offset, end_l);
|
|
Q1BSP_RecursiveHullCheck(hull, hull->firstclipnode, 0, 1, start_l, end_l, trace);
|
|
|
|
if (trace->fraction == 1)
|
|
{
|
|
VectorCopy (end, trace->endpos);
|
|
}
|
|
else
|
|
{
|
|
VectorAdd (trace->endpos, offset, trace->endpos);
|
|
}
|
|
}
|
|
|
|
#ifdef TERRAIN
|
|
if (model->terrain && trace->fraction)
|
|
{
|
|
trace_t hmt;
|
|
Heightmap_Trace(model, forcehullnum, framestate, axis, start, end, mins, maxs, capsule, hitcontentsmask, &hmt);
|
|
if (hmt.fraction < trace->fraction)
|
|
*trace = hmt;
|
|
}
|
|
#endif
|
|
|
|
return trace->fraction != 1;
|
|
}
|
|
|
|
/*
|
|
Physics functions (common)
|
|
|
|
============================================================================
|
|
|
|
Rendering functions (Client only)
|
|
*/
|
|
#ifndef SERVERONLY
|
|
|
|
extern int r_dlightframecount;
|
|
|
|
//goes through the nodes marking the surfaces near the dynamic light as lit.
|
|
void Q1BSP_MarkLights (dlight_t *light, int bit, mnode_t *node)
|
|
{
|
|
mplane_t *splitplane;
|
|
float dist;
|
|
msurface_t *surf;
|
|
int i;
|
|
|
|
float l, maxdist;
|
|
int j, s, t;
|
|
vec3_t impact;
|
|
|
|
if (node->contents < 0)
|
|
return;
|
|
|
|
splitplane = node->plane;
|
|
if (splitplane->type < 3)
|
|
dist = light->origin[splitplane->type] - splitplane->dist;
|
|
else
|
|
dist = DotProduct (light->origin, splitplane->normal) - splitplane->dist;
|
|
|
|
if (dist > light->radius)
|
|
{
|
|
Q1BSP_MarkLights (light, bit, node->children[0]);
|
|
return;
|
|
}
|
|
if (dist < -light->radius)
|
|
{
|
|
Q1BSP_MarkLights (light, bit, node->children[1]);
|
|
return;
|
|
}
|
|
|
|
maxdist = light->radius*light->radius;
|
|
|
|
// mark the polygons
|
|
surf = currentmodel->surfaces + node->firstsurface;
|
|
for (i=0 ; i<node->numsurfaces ; i++, surf++)
|
|
{
|
|
//Yeah, you can blame LordHavoc for this alternate code here.
|
|
for (j=0 ; j<3 ; j++)
|
|
impact[j] = light->origin[j] - surf->plane->normal[j]*dist;
|
|
|
|
// clamp center of light to corner and check brightness
|
|
l = DotProduct (impact, surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3] - surf->texturemins[0];
|
|
s = l+0.5;if (s < 0) s = 0;else if (s > surf->extents[0]) s = surf->extents[0];
|
|
s = (l - s)*surf->texinfo->vecscale[0];
|
|
l = DotProduct (impact, surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3] - surf->texturemins[1];
|
|
t = l+0.5;if (t < 0) t = 0;else if (t > surf->extents[1]) t = surf->extents[1];
|
|
t = (l - t)*surf->texinfo->vecscale[1];
|
|
// compare to minimum light
|
|
if ((s*s+t*t+dist*dist) < maxdist)
|
|
{
|
|
if (surf->dlightframe != r_dlightframecount)
|
|
{
|
|
surf->dlightbits = bit;
|
|
surf->dlightframe = r_dlightframecount;
|
|
}
|
|
else
|
|
surf->dlightbits |= bit;
|
|
}
|
|
}
|
|
|
|
Q1BSP_MarkLights (light, bit, node->children[0]);
|
|
Q1BSP_MarkLights (light, bit, node->children[1]);
|
|
}
|
|
|
|
#endif
|
|
/*
|
|
Rendering functions (Client only)
|
|
|
|
==============================================================================
|
|
|
|
Server only functions
|
|
*/
|
|
#ifndef CLIENTONLY
|
|
|
|
//does the recursive work of Q1BSP_FatPVS
|
|
static void SV_Q1BSP_AddToFatPVS (model_t *mod, vec3_t org, mnode_t *node, qbyte *buffer, unsigned int buffersize)
|
|
{
|
|
int i;
|
|
qbyte *pvs;
|
|
mplane_t *plane;
|
|
float d;
|
|
|
|
while (1)
|
|
{
|
|
// if this is a leaf, accumulate the pvs bits
|
|
if (node->contents < 0)
|
|
{
|
|
if (node->contents != Q1CONTENTS_SOLID)
|
|
{
|
|
pvs = Q1BSP_LeafPVS (mod, (mleaf_t *)node, NULL, 0);
|
|
for (i=0; i<buffersize; i++)
|
|
buffer[i] |= pvs[i];
|
|
}
|
|
return;
|
|
}
|
|
|
|
plane = node->plane;
|
|
d = DotProduct (org, plane->normal) - plane->dist;
|
|
if (d > 8)
|
|
node = node->children[0];
|
|
else if (d < -8)
|
|
node = node->children[1];
|
|
else
|
|
{ // go down both
|
|
SV_Q1BSP_AddToFatPVS (mod, org, node->children[0], buffer, buffersize);
|
|
node = node->children[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Q1BSP_FatPVS
|
|
|
|
Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the
|
|
given point.
|
|
=============
|
|
*/
|
|
static unsigned int Q1BSP_FatPVS (model_t *mod, vec3_t org, qbyte *pvsbuffer, unsigned int buffersize, qboolean add)
|
|
{
|
|
unsigned int fatbytes = (mod->numleafs+31)>>3;
|
|
if (fatbytes > buffersize)
|
|
Sys_Error("map had too much pvs data (too many leaves)\n");;
|
|
if (!add)
|
|
Q_memset (pvsbuffer, 0, fatbytes);
|
|
SV_Q1BSP_AddToFatPVS (mod, org, mod->nodes, pvsbuffer, fatbytes);
|
|
return fatbytes;
|
|
}
|
|
|
|
#endif
|
|
static qboolean Q1BSP_EdictInFatPVS(model_t *mod, struct pvscache_s *ent, qbyte *pvs)
|
|
{
|
|
int i;
|
|
|
|
if (ent->num_leafs == MAX_ENT_LEAFS+1)
|
|
return true; //it's in too many leafs for us to cope with. Just trivially accept it.
|
|
|
|
for (i=0 ; i < ent->num_leafs ; i++)
|
|
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) ))
|
|
return true; //we might be able to see this one.
|
|
|
|
return false; //none of this ents leafs were visible, so neither is the ent.
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SV_FindTouchedLeafs
|
|
|
|
Links the edict to the right leafs so we can get it's potential visability.
|
|
===============
|
|
*/
|
|
static void Q1BSP_RFindTouchedLeafs (model_t *wm, struct pvscache_s *ent, mnode_t *node, float *mins, float *maxs)
|
|
{
|
|
mplane_t *splitplane;
|
|
mleaf_t *leaf;
|
|
int sides;
|
|
int leafnum;
|
|
|
|
// add an efrag if the node is a leaf
|
|
|
|
if (node->contents < 0)
|
|
{
|
|
//ignore solid leafs. this should include leaf 0 (which has no pvs info)
|
|
if (node->contents == Q1CONTENTS_SOLID)
|
|
return;
|
|
|
|
if (ent->num_leafs >= MAX_ENT_LEAFS)
|
|
{
|
|
ent->num_leafs = MAX_ENT_LEAFS+1; //too many. mark it as such so we can trivially accept huge mega-big brush models.
|
|
return;
|
|
}
|
|
|
|
leaf = (mleaf_t *)node;
|
|
leafnum = leaf - wm->leafs - 1;
|
|
|
|
ent->leafnums[ent->num_leafs] = leafnum;
|
|
ent->num_leafs++;
|
|
return;
|
|
}
|
|
|
|
// NODE_MIXED
|
|
|
|
splitplane = node->plane;
|
|
sides = BOX_ON_PLANE_SIDE(mins, maxs, splitplane);
|
|
|
|
// recurse down the contacted sides
|
|
if (sides & 1)
|
|
Q1BSP_RFindTouchedLeafs (wm, ent, node->children[0], mins, maxs);
|
|
|
|
if (sides & 2)
|
|
Q1BSP_RFindTouchedLeafs (wm, ent, node->children[1], mins, maxs);
|
|
}
|
|
static void Q1BSP_FindTouchedLeafs(model_t *mod, struct pvscache_s *ent, float *mins, float *maxs)
|
|
{
|
|
ent->num_leafs = 0;
|
|
if (mins && maxs)
|
|
Q1BSP_RFindTouchedLeafs (mod, ent, mod->nodes, mins, maxs);
|
|
}
|
|
|
|
|
|
/*
|
|
Server only functions
|
|
|
|
==============================================================================
|
|
|
|
PVS type stuff
|
|
*/
|
|
|
|
/*
|
|
===================
|
|
Mod_DecompressVis
|
|
===================
|
|
*/
|
|
static qbyte *Q1BSP_DecompressVis (qbyte *in, model_t *model, qbyte *decompressed, unsigned int buffersize)
|
|
{
|
|
int c;
|
|
qbyte *out;
|
|
int row;
|
|
|
|
row = (model->numclusters+7)>>3;
|
|
out = decompressed;
|
|
|
|
if (buffersize < row)
|
|
row = buffersize;
|
|
|
|
#if 0
|
|
memcpy (out, in, row);
|
|
#else
|
|
if (!in)
|
|
{ // no vis info, so make all visible
|
|
while (row)
|
|
{
|
|
*out++ = 0xff;
|
|
row--;
|
|
}
|
|
return decompressed;
|
|
}
|
|
|
|
do
|
|
{
|
|
if (*in)
|
|
{
|
|
*out++ = *in++;
|
|
continue;
|
|
}
|
|
|
|
c = in[1];
|
|
in += 2;
|
|
while (c)
|
|
{
|
|
*out++ = 0;
|
|
c--;
|
|
}
|
|
} while (out - decompressed < row);
|
|
#endif
|
|
|
|
return decompressed;
|
|
}
|
|
|
|
static FTE_ALIGN(4) qbyte mod_novis[MAX_MAP_LEAFS/8];
|
|
|
|
qbyte *Q1BSP_LeafPVS (model_t *model, mleaf_t *leaf, qbyte *buffer, unsigned int buffersize)
|
|
{
|
|
static FTE_ALIGN(4) qbyte decompressed[MAX_MAP_LEAFS/8];
|
|
|
|
if (leaf == model->leafs)
|
|
return mod_novis;
|
|
|
|
if (!buffer)
|
|
{
|
|
buffer = decompressed;
|
|
buffersize = sizeof(decompressed);
|
|
}
|
|
|
|
return Q1BSP_DecompressVis (leaf->compressed_vis, model, buffer, buffersize);
|
|
}
|
|
|
|
//pvs is 1-based. clusters are 0-based. otherwise, q1bsp has a 1:1 mapping.
|
|
static qbyte *Q1BSP_ClusterPVS (model_t *model, int cluster, qbyte *buffer, unsigned int buffersize)
|
|
{
|
|
static FTE_ALIGN(4) qbyte decompressed[MAX_MAP_LEAFS/8];
|
|
|
|
if (cluster == -1)
|
|
return mod_novis;
|
|
cluster++;
|
|
|
|
if (!buffer)
|
|
{
|
|
buffer = decompressed;
|
|
buffersize = sizeof(decompressed);
|
|
}
|
|
|
|
return Q1BSP_DecompressVis (model->leafs[cluster].compressed_vis, model, buffer, buffersize);
|
|
}
|
|
|
|
//returns the leaf number, which is used as a bit index into the pvs.
|
|
static int Q1BSP_LeafnumForPoint (model_t *model, vec3_t p)
|
|
{
|
|
mnode_t *node;
|
|
float d;
|
|
mplane_t *plane;
|
|
|
|
if (!model)
|
|
{
|
|
Sys_Error ("Mod_PointInLeaf: bad model");
|
|
}
|
|
if (!model->nodes)
|
|
return 0;
|
|
|
|
node = model->nodes;
|
|
while (1)
|
|
{
|
|
if (node->contents < 0)
|
|
return (mleaf_t *)node - model->leafs;
|
|
plane = node->plane;
|
|
d = DotProduct (p,plane->normal) - plane->dist;
|
|
if (d > 0)
|
|
node = node->children[0];
|
|
else
|
|
node = node->children[1];
|
|
}
|
|
|
|
return 0; // never reached
|
|
}
|
|
|
|
mleaf_t *Q1BSP_LeafForPoint (model_t *model, vec3_t p)
|
|
{
|
|
return model->leafs + Q1BSP_LeafnumForPoint(model, p);
|
|
}
|
|
|
|
//returns the leaf number, which is used as a direct bit index into the pvs.
|
|
//-1 for invalid
|
|
static int Q1BSP_ClusterForPoint (model_t *model, vec3_t p)
|
|
{
|
|
mnode_t *node;
|
|
float d;
|
|
mplane_t *plane;
|
|
|
|
if (!model)
|
|
{
|
|
Sys_Error ("Mod_PointInLeaf: bad model");
|
|
}
|
|
if (!model->nodes)
|
|
return -1;
|
|
|
|
node = model->nodes;
|
|
while (1)
|
|
{
|
|
if (node->contents < 0)
|
|
return ((mleaf_t *)node - model->leafs) - 1;
|
|
plane = node->plane;
|
|
d = DotProduct (p,plane->normal) - plane->dist;
|
|
if (d > 0)
|
|
node = node->children[0];
|
|
else
|
|
node = node->children[1];
|
|
}
|
|
|
|
return -1; // never reached
|
|
}
|
|
|
|
|
|
/*
|
|
PVS type stuff
|
|
|
|
==============================================================================
|
|
|
|
Init stuff
|
|
*/
|
|
|
|
|
|
void Q1BSP_Init(void)
|
|
{
|
|
memset (mod_novis, 0xff, sizeof(mod_novis));
|
|
}
|
|
|
|
//sets up the functions a server needs.
|
|
//fills in bspfuncs_t
|
|
void Q1BSP_SetModelFuncs(model_t *mod)
|
|
{
|
|
#ifndef CLIENTONLY
|
|
mod->funcs.FatPVS = Q1BSP_FatPVS;
|
|
#endif
|
|
mod->funcs.EdictInFatPVS = Q1BSP_EdictInFatPVS;
|
|
mod->funcs.FindTouchedLeafs = Q1BSP_FindTouchedLeafs;
|
|
mod->funcs.LightPointValues = NULL;
|
|
mod->funcs.StainNode = NULL;
|
|
mod->funcs.MarkLights = NULL;
|
|
|
|
mod->funcs.ClusterForPoint = Q1BSP_ClusterForPoint;
|
|
mod->funcs.ClusterPVS = Q1BSP_ClusterPVS;
|
|
mod->funcs.NativeTrace = Q1BSP_Trace;
|
|
mod->funcs.PointContents = Q1BSP_PointContents;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
Init stuff
|
|
|
|
==============================================================================
|
|
|
|
BSPX Stuff
|
|
*/
|
|
|
|
typedef struct {
|
|
char lumpname[24]; // up to 23 chars, zero-padded
|
|
int fileofs; // from file start
|
|
int filelen;
|
|
} bspx_lump_t;
|
|
typedef struct {
|
|
char id[4]; // 'BSPX'
|
|
int numlumps;
|
|
bspx_lump_t lumps[1];
|
|
} bspx_header_t;
|
|
static char *bspxbase;
|
|
static bspx_header_t *bspxheader;
|
|
//supported lumps:
|
|
//RGBLIGHTING (.lit)
|
|
//LIGHTINGDIR (.lux)
|
|
void *Q1BSPX_FindLump(char *lumpname, int *lumpsize)
|
|
{
|
|
int i;
|
|
*lumpsize = 0;
|
|
if (!bspxheader)
|
|
return NULL;
|
|
|
|
for (i = 0; i < bspxheader->numlumps; i++)
|
|
{
|
|
if (!strncmp(bspxheader->lumps[i].lumpname, lumpname, 24))
|
|
{
|
|
*lumpsize = bspxheader->lumps[i].filelen;
|
|
return bspxbase + bspxheader->lumps[i].fileofs;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
void Q1BSPX_Setup(model_t *mod, char *filebase, unsigned int filelen, lump_t *lumps, int numlumps)
|
|
{
|
|
int i;
|
|
int offs = 0;
|
|
bspx_header_t *h;
|
|
|
|
bspxbase = filebase;
|
|
bspxheader = NULL;
|
|
|
|
for (i = 0; i < numlumps; i++, lumps++)
|
|
{
|
|
if (offs < lumps->fileofs + lumps->filelen)
|
|
offs = lumps->fileofs + lumps->filelen;
|
|
}
|
|
offs = (offs + 3) & ~3;
|
|
if (offs + sizeof(*bspxheader) > filelen)
|
|
return; /*no space for it*/
|
|
h = (bspx_header_t*)(filebase + offs);
|
|
|
|
i = LittleLong(h->numlumps);
|
|
/*verify the header*/
|
|
if (*(int*)h->id != (('B'<<0)|('S'<<8)|('P'<<16)|('X'<<24)) ||
|
|
i < 0 ||
|
|
offs + sizeof(*h) + sizeof(h->lumps[0])*(i-1) > filelen)
|
|
return;
|
|
h->numlumps = i;
|
|
while(i-->0)
|
|
{
|
|
h->lumps[i].fileofs = LittleLong(h->lumps[i].fileofs);
|
|
h->lumps[i].filelen = LittleLong(h->lumps[i].filelen);
|
|
if (h->lumps[i].fileofs + h->lumps[i].filelen > filelen)
|
|
return;
|
|
}
|
|
|
|
bspxheader = h;
|
|
}
|