3e0b1af72b
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5621 fc73d0e0-1445-4013-8a0c-d673dee63da5
1180 lines
34 KiB
C
1180 lines
34 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the included (GNU.txt) GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
#include "quakedef.h"
|
|
|
|
#define NUMVERTEXNORMALS 162
|
|
float r_avertexnormals[NUMVERTEXNORMALS][3] = {
|
|
#include "anorms.h"
|
|
};
|
|
|
|
|
|
#include "shader.h"
|
|
#include "com_mesh.h"
|
|
//FIXME: we're likely going to want to thread the building routine at some point.
|
|
//the alias mesh stuff will need some rework as it uses statics inside.
|
|
#define DESCSPERSHADER 8
|
|
typedef struct
|
|
{
|
|
int x, y, z; //rebuilt if changed
|
|
int key;
|
|
|
|
model_t *loadingmodel; //needs rebuilding, but wait till this is loaded.
|
|
|
|
struct
|
|
{
|
|
shader_t *shader;
|
|
mesh_t mesh;
|
|
mesh_t *pmesh;
|
|
vbo_t vbo;
|
|
} soups[64];
|
|
size_t numsoups;
|
|
} cluttersector_t;
|
|
static cluttersector_t cluttersector[3*3*3];
|
|
cvar_t r_clutter_density = CVARD("r_clutter_density", "0", "Scaler for clutter counts. 0 disables clutter completely.\nClutter requires shaders with 'fte_clutter MODEL SPACING SCALEMIN SCALEMAX ZOFS ANGLEMIN ANGLEMAX' terms");
|
|
cvar_t r_clutter_distance = CVARD("r_clutter_distance", "1024", "Distance at which clutter will become invisible."); //should be used by various shaders to fade it out by here
|
|
void R_Clutter_Init(void)
|
|
{
|
|
Cvar_Register(&r_clutter_density, "Ground Clutter");
|
|
Cvar_Register(&r_clutter_distance, "Ground Clutter");
|
|
}
|
|
typedef struct
|
|
{
|
|
model_t *loadingmodel;
|
|
struct clutter_build_ctx_soup_s
|
|
{
|
|
shader_t *shader;
|
|
vecV_t *coord;
|
|
vec2_t *texcoord;
|
|
vec4_t *colour;
|
|
vec3_t *normal;
|
|
vec3_t *sdir;
|
|
vec3_t *tdir;
|
|
index_t *idx;
|
|
size_t numverts;
|
|
size_t numidx;
|
|
size_t maxverts;
|
|
size_t maxidx;
|
|
} soups[64];
|
|
unsigned int numsoups;
|
|
float area[DESCSPERSHADER]; //here so it can overflow, so large values with small surfaces actually does something. not evenly perhaps, but not much else we can do
|
|
|
|
unsigned int x, y, z, w;
|
|
} clutter_build_ctx_t;
|
|
|
|
//to make things repeatable so that people can depend upon placement.
|
|
unsigned int R_Clutter_Random(clutter_build_ctx_t *ctx)
|
|
{ //ripped from wikipedia (originally called xorshift128)
|
|
unsigned int t = ctx->x ^ (ctx->x << 11);
|
|
ctx->x = ctx->y; ctx->y = ctx->z; ctx->z = ctx->w;
|
|
return ctx->w = ctx->w ^ (ctx->w >> 19) ^ t ^ (t >> 8);
|
|
}
|
|
float R_Clutter_FRandom(clutter_build_ctx_t *ctx)
|
|
{
|
|
unsigned int r = R_Clutter_Random(ctx);
|
|
return (r & 0xffffff) / (float)0xffffff;
|
|
}
|
|
|
|
static void R_Clutter_Insert_Soup(clutter_build_ctx_t *ctx, shader_t *shader, vecV_t *fte_restrict coord, vec2_t *fte_restrict texcoord, vec3_t *fte_restrict normal, vec3_t *fte_restrict sdir, vec3_t *fte_restrict tdir, vec4_t *fte_restrict colours, size_t numverts, index_t *fte_restrict index, size_t numidx, float scale, vec3_t origin, vec3_t axis[])
|
|
{
|
|
vec3_t diffuse, ambient, ldir;
|
|
float dot;
|
|
struct clutter_build_ctx_soup_s *soup = NULL;
|
|
size_t i;
|
|
|
|
/* useful for debugging mayhaps --eukara */
|
|
/*shader_t *os = shader;
|
|
shader = R_RegisterShader(va("clutter#replace=%s", os->name), SUF_NONE,
|
|
"{\n"
|
|
"program defaultsprite#MASK=0.666\n"
|
|
// "surfaceparm nodlight\n"
|
|
"surfaceparm noshadows\n"
|
|
// "cull disable\n"
|
|
"{\n"
|
|
"map $diffuse\n"
|
|
"rgbgen vertex\n"
|
|
"alphagen vertex\n"
|
|
// "alphafunc ge128\n"
|
|
"}\n"
|
|
"}\n"
|
|
);
|
|
*shader->defaulttextures = *os->defaulttextures;*/
|
|
|
|
|
|
for (i = 0, soup = ctx->soups; i < ctx->numsoups; i++, soup++)
|
|
{
|
|
if (soup->shader == shader)
|
|
if (soup->numverts + numverts <= MAX_INDICIES)
|
|
break;
|
|
}
|
|
if (i == ctx->numsoups)
|
|
{
|
|
if (i == sizeof(ctx->soups)/sizeof(ctx->soups[0]))
|
|
return; //too many different shaders or something
|
|
soup->shader = shader;
|
|
ctx->numsoups++;
|
|
}
|
|
|
|
//inject the indicies
|
|
if (soup->numidx + numidx > soup->maxidx)
|
|
{
|
|
soup->maxidx = (soup->numidx + numidx) * 2;
|
|
soup->idx = BZ_Realloc(soup->idx, sizeof(*soup->idx) * soup->maxidx);
|
|
}
|
|
for (i = 0; i < numidx; i++)
|
|
soup->idx[soup->numidx++] = soup->numverts+*index++;
|
|
|
|
|
|
cl.worldmodel->funcs.LightPointValues(cl.worldmodel, origin, diffuse, ambient, ldir);
|
|
VectorScale(ambient, 1/255.0, ambient);
|
|
VectorScale(diffuse, 1/255.0, diffuse);
|
|
|
|
//inject the verts
|
|
if (soup->numverts + numverts > soup->maxverts)
|
|
{
|
|
soup->maxverts = (soup->numverts + numverts) * 2;
|
|
soup->coord = BZ_Realloc(soup->coord, sizeof(*soup->coord) * soup->maxverts);
|
|
soup->texcoord = BZ_Realloc(soup->texcoord, sizeof(*soup->texcoord) * soup->maxverts);
|
|
soup->colour = BZ_Realloc(soup->colour, sizeof(*soup->colour) * soup->maxverts);
|
|
soup->normal = BZ_Realloc(soup->normal, sizeof(*soup->normal) * soup->maxverts);
|
|
soup->sdir = BZ_Realloc(soup->sdir, sizeof(*soup->sdir) * soup->maxverts);
|
|
soup->tdir = BZ_Realloc(soup->tdir, sizeof(*soup->tdir) * soup->maxverts);
|
|
}
|
|
for (i = 0; i < numverts; i++)
|
|
{
|
|
VectorMA(origin, scale*coord[i][0], axis[0], soup->coord[soup->numverts]);
|
|
VectorMA(soup->coord[soup->numverts], scale*coord[i][1], axis[1], soup->coord[soup->numverts]);
|
|
VectorMA(soup->coord[soup->numverts], scale*coord[i][2], axis[2], soup->coord[soup->numverts]);
|
|
Vector2Copy(texcoord[i], soup->texcoord[soup->numverts]);
|
|
|
|
VectorMA(vec3_origin, normal[i][0], axis[0], soup->normal[soup->numverts]);
|
|
VectorMA(soup->normal[soup->numverts], normal[i][1], axis[1], soup->normal[soup->numverts]);
|
|
VectorMA(soup->normal[soup->numverts], normal[i][2], axis[2], soup->normal[soup->numverts]);
|
|
|
|
VectorMA(vec3_origin, sdir[i][0], axis[0], soup->sdir[soup->numverts]);
|
|
VectorMA(soup->sdir[soup->numverts], sdir[i][1], axis[1], soup->sdir[soup->numverts]);
|
|
VectorMA(soup->sdir[soup->numverts], sdir[i][2], axis[2], soup->sdir[soup->numverts]);
|
|
|
|
VectorMA(vec3_origin, tdir[i][0], axis[0], soup->tdir[soup->numverts]);
|
|
VectorMA(soup->tdir[soup->numverts], tdir[i][1], axis[1], soup->tdir[soup->numverts]);
|
|
VectorMA(soup->tdir[soup->numverts], tdir[i][2], axis[2], soup->tdir[soup->numverts]);
|
|
|
|
// VectorCopy(ambient, soup->colour[soup->numverts]);
|
|
dot = DotProduct(ldir, soup->normal[soup->numverts]);
|
|
if (dot < 0)
|
|
dot = 0;
|
|
VectorMA(ambient, dot, diffuse, soup->colour[soup->numverts]);
|
|
if (colours) //most model formats don't have vertex colours
|
|
soup->colour[soup->numverts][3] = colours[i][3];
|
|
else
|
|
soup->colour[soup->numverts][3] = 1;
|
|
|
|
soup->numverts++;
|
|
}
|
|
}
|
|
static void R_Clutter_Insert_Mesh(clutter_build_ctx_t *ctx, model_t *mod, float scale, vec3_t origin, vec3_t axis[3])
|
|
{
|
|
mesh_t mesh;
|
|
galiasinfo_t *inf;
|
|
unsigned int surfnum = 0;
|
|
entity_t re;
|
|
unsigned int randanim = R_Clutter_Random(ctx);
|
|
unsigned int randskin = R_Clutter_Random(ctx);
|
|
|
|
if (!mod)
|
|
return;
|
|
|
|
if (mod->type == mod_alias)
|
|
{
|
|
//fill in the parts of the entity_t that Alias_GAliasBuildMesh needs.
|
|
memset(&re, 0, sizeof(re));
|
|
re.framestate.g[FS_REG].lerpweight[0] = 1;
|
|
re.model = mod;
|
|
|
|
inf = (galiasinfo_t*)Mod_Extradata (mod);
|
|
while(inf)
|
|
{
|
|
galiasskin_t *skins = inf->ofsskins;
|
|
if (inf->numanimations >= 1)
|
|
re.framestate.g[FS_REG].frame[0] = randanim%inf->numanimations;
|
|
else
|
|
re.framestate.g[FS_REG].frame[0] = 0;
|
|
if (skins->numframes)
|
|
{
|
|
unsigned int frame = randskin%skins->numframes;
|
|
Alias_GAliasBuildMesh(&mesh, NULL, inf, surfnum, &re, false);
|
|
surfnum++;
|
|
//fixme: if shares verts, rewind the verts and don't add more somehow, while being careful with shaders
|
|
R_Clutter_Insert_Soup(ctx, skins->frame[frame].shader, mesh.xyz_array, mesh.st_array, mesh.normals_array, mesh.snormals_array, mesh.tnormals_array, mesh.colors4f_array[0], mesh.numvertexes, mesh.indexes, mesh.numindexes, scale, origin, axis);
|
|
}
|
|
inf = inf->nextsurf;
|
|
}
|
|
Alias_FlushCache(); //it got built using an entity on the stack, make sure other stuff doesn't get hurt.
|
|
}
|
|
}
|
|
static void R_Clutter_Insert(void *vctx, vec3_t *fte_restrict points, size_t numtris, shader_t *surface)
|
|
{
|
|
struct shader_clutter_s *clut;
|
|
unsigned int obj;
|
|
clutter_build_ctx_t *ctx = vctx;
|
|
model_t *mod[DESCSPERSHADER];
|
|
if (!surface || !surface->clutter)
|
|
return; //nothing to do.
|
|
|
|
//avoid returning on error, so the randomization is dependable when content is still loading.
|
|
for (clut = surface->clutter, obj = 0; clut && obj <= DESCSPERSHADER; clut = clut->next, obj++)
|
|
{
|
|
mod[obj] = Mod_ForName(clut->modelname, MLV_WARN);
|
|
if (mod[obj]->loadstate == MLS_LOADING)
|
|
{
|
|
if (!ctx->loadingmodel)
|
|
ctx->loadingmodel = mod[obj];
|
|
mod[obj] = NULL;
|
|
}
|
|
else if (mod[obj]->type != mod_alias)
|
|
mod[obj] = NULL;
|
|
}
|
|
|
|
while(numtris-->0)
|
|
{
|
|
vec3_t xd;
|
|
vec3_t yd;
|
|
vec3_t zd;
|
|
vec3_t norm;
|
|
vec3_t axis[3];
|
|
vec3_t org, dir;
|
|
float dot;
|
|
float triarea;
|
|
// vec3_t discard;
|
|
// unsigned int subimage;
|
|
vec_t xm, ym, zm, s;
|
|
VectorSubtract(points[1], points[0], xd);
|
|
VectorSubtract(points[2], points[0], yd);
|
|
VectorSubtract(points[2], points[1], zd);
|
|
CrossProduct(yd, xd, norm);
|
|
VectorNormalize(norm);
|
|
if (norm[2] >= 0.7)
|
|
{
|
|
//determine area of triangle
|
|
xm = Length(xd);
|
|
ym = Length(yd);
|
|
zm = Length(zd);
|
|
s = (xm+ym+zm)/2;
|
|
triarea = sqrt(s*(s-xm)*(s-ym)*(s-zm));
|
|
|
|
for (clut = surface->clutter, obj = 0; clut && obj <= DESCSPERSHADER; clut = clut->next, obj++)
|
|
{
|
|
float spacing = clut->spacing / r_clutter_density.value;
|
|
if (spacing < 1)
|
|
spacing = 1;
|
|
ctx->area[obj] += triarea;
|
|
while (ctx->area[obj] >= spacing)
|
|
{
|
|
float scale = clut->scalemin + R_Clutter_FRandom(ctx) * (clut->scalemax-clut->scalemin);
|
|
ctx->area[obj] -= spacing;
|
|
|
|
//pick a random spot
|
|
xm = R_Clutter_FRandom(ctx)*R_Clutter_FRandom(ctx);
|
|
ym = R_Clutter_FRandom(ctx) * (1-xm);
|
|
VectorMA(points[0], xm, xd, org);
|
|
VectorMA(org, ym, yd, org);
|
|
|
|
//randomize the direction
|
|
dot = clut->anglemin + R_Clutter_FRandom(ctx) * (clut->anglemax-clut->anglemin);
|
|
dir[0] = cos(dot);
|
|
dir[1] = sin(dot);
|
|
dir[2] = 0;
|
|
//figure out various directions
|
|
dot = -DotProduct(dir, norm);
|
|
VectorMA(dir, dot, norm, dir);
|
|
VectorNormalize(dir);
|
|
VectorCopy(norm, axis[2]);
|
|
CrossProduct(axis[2], dir, axis[1]);
|
|
CrossProduct(axis[1], axis[2], axis[0]);
|
|
VectorMA(org, clut->zofs*scale, axis[2], org);
|
|
R_Clutter_Insert_Mesh(ctx, mod[obj], scale, org, axis);
|
|
|
|
/*
|
|
VectorMA(org, r_clutter_size.value/2, dir, vertcoord[numverts]);
|
|
VectorMA(org, -(r_clutter_size.value/2), dir, vertcoord[numverts+1]);
|
|
VectorMA(vertcoord[numverts], r_clutter_height.value, norm, vertcoord[numverts+2]);
|
|
VectorMA(vertcoord[numverts+1], r_clutter_height.value, norm, vertcoord[numverts+3]);
|
|
subimage = R_Clutter_Random(ctx);
|
|
Vector2Set(texcoord[numverts], subimage%r_clutter_atlaswidth.ival, (subimage/r_clutter_atlaswidth.ival)%r_clutter_atlasheight.ival);
|
|
texcoord[numverts][0] *= 1/r_clutter_atlaswidth.value;
|
|
texcoord[numverts][1] *= 1/r_clutter_atlasheight.value;
|
|
Vector2Set(texcoord[numverts+1], texcoord[numverts][0]+(1/r_clutter_atlaswidth.value), texcoord[numverts][1]);
|
|
Vector2Set(texcoord[numverts+2], texcoord[numverts][0] , texcoord[numverts][1]);
|
|
Vector2Set(texcoord[numverts+3], texcoord[numverts][0]+(1/r_clutter_atlaswidth.value), texcoord[numverts][1]);
|
|
texcoord[numverts+0][1]+=(1/r_clutter_atlasheight.value);
|
|
texcoord[numverts+1][1]+=(1/r_clutter_atlasheight.value);
|
|
Vector4Set(colours[numverts+0], 1, 1, 1, 1);
|
|
VectorMA(org, 1/8.0, norm, org);//push away from the surface to avoid precision issues with lighting on slopes
|
|
cl.worldmodel->funcs.LightPointValues(cl.worldmodel, org, colours[numverts+0], discard, discard);
|
|
VectorScale(colours[numverts+0], 1/512.0, colours[numverts+0]);
|
|
Vector4Copy(colours[numverts+0], colours[numverts+1]);
|
|
Vector4Copy(colours[numverts+0], colours[numverts+2]);
|
|
Vector4Copy(colours[numverts+1], colours[numverts+3]);
|
|
indexes[numidx+0] = numverts+0;
|
|
indexes[numidx+1] = numverts+2;
|
|
indexes[numidx+2] = numverts+1;
|
|
indexes[numidx+3] = numverts+2;
|
|
indexes[numidx+4] = numverts+3;
|
|
indexes[numidx+5] = numverts+1;
|
|
numverts += 4;
|
|
numidx += 6;
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
points += 3;
|
|
}
|
|
}
|
|
void R_Clutter_Emit(batch_t **batches)
|
|
{
|
|
const float cluttersize = r_clutter_distance.value;
|
|
int vx, vy, vz;
|
|
int x, y, z, key, i, j;
|
|
cluttersector_t *sect;
|
|
batch_t *b;
|
|
qboolean rebuildlimit = false;
|
|
|
|
if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED || r_clutter_density.value <= 0 || (r_refdef.flags & RDF_NOWORLDMODEL))
|
|
return;
|
|
|
|
if (qrenderer != QR_OPENGL && qrenderer != QR_VULKAN) //vbo only!
|
|
return;
|
|
|
|
//rebuild if any of the cvars changes.
|
|
key = r_clutter_density.modified + r_clutter_distance.modified;
|
|
|
|
vx = floor((r_refdef.vieworg[0] / cluttersize));
|
|
vy = floor((r_refdef.vieworg[1] / cluttersize));
|
|
vz = floor((r_refdef.vieworg[2] / cluttersize));
|
|
|
|
for (z = vz-1; z <= vz+1; z++)
|
|
for (y = vy-1; y <= vy+1; y++)
|
|
for (x = vx-1; x <= vx+1; x++)
|
|
{
|
|
int ix = x%3;
|
|
int iy = y%3;
|
|
int iz = z%3;
|
|
if (ix < 0)
|
|
ix += 3;
|
|
if (iy < 0)
|
|
iy += 3;
|
|
if (iz < 0)
|
|
iz += 3;
|
|
sect = &cluttersector[ix + (iy*3) + (iz*3*3)];
|
|
if (sect->loadingmodel && sect->loadingmodel->loadstate != MLS_LOADING)
|
|
{
|
|
sect->loadingmodel = NULL;
|
|
sect->key-=1; //rebuild even if failed, this covers multiple models.
|
|
}
|
|
if (sect->x != x || sect->y != y || sect->z != z || sect->key != key)
|
|
{
|
|
vbobctx_t vctx;
|
|
clutter_build_ctx_t cctx;
|
|
vec3_t org = {x*cluttersize+(cluttersize/2),y*cluttersize+(cluttersize/2),z*cluttersize+(cluttersize/2)};
|
|
vec3_t down = {0, 0, -1};
|
|
vec3_t forward = {1, 0, 0};
|
|
vec3_t right = {0, 1, 0};
|
|
if (r_refdef.recurse) //FIXME
|
|
continue;
|
|
if (rebuildlimit)
|
|
continue;
|
|
rebuildlimit = true;
|
|
sect->x = x;
|
|
sect->y = y;
|
|
sect->z = z;
|
|
sect->key = key;
|
|
|
|
//make sure any old state is gone
|
|
for (i = 0; i < sect->numsoups; i++)
|
|
{
|
|
BE_VBO_Destroy(§->soups[i].vbo.coord, sect->soups[i].vbo.vbomem);
|
|
BE_VBO_Destroy(§->soups[i].vbo.indicies, sect->soups[i].vbo.ebomem);
|
|
}
|
|
sect->numsoups = 0;
|
|
memset(&cctx, 0, sizeof(cctx));
|
|
cctx.x = x;
|
|
cctx.y = y;
|
|
cctx.z = z;
|
|
cctx.w = (sect-cluttersector)+1;
|
|
Mod_ClipDecal(cl.worldmodel, org, down, forward, right, cluttersize, 0, 0, R_Clutter_Insert, &cctx);
|
|
sect->loadingmodel = cctx.loadingmodel;
|
|
|
|
for (i = 0; i < cctx.numsoups; i++)
|
|
{
|
|
if (cctx.soups[i].numverts)
|
|
{
|
|
sect->soups[sect->numsoups].shader = cctx.soups[i].shader;
|
|
sect->soups[sect->numsoups].pmesh = §->soups[sect->numsoups].mesh;
|
|
|
|
BE_VBO_Begin(&vctx, (sizeof(cctx.soups[i].coord[0]) + sizeof(cctx.soups[i].texcoord[0]) + sizeof(cctx.soups[i].colour[0]) + 3*sizeof(vec3_t))*cctx.soups[i].numverts);
|
|
BE_VBO_Data(&vctx, cctx.soups[i].coord, sizeof(cctx.soups[i].coord[0])*cctx.soups[i].numverts, §->soups[sect->numsoups].vbo.coord);
|
|
BE_VBO_Data(&vctx, cctx.soups[i].texcoord, sizeof(cctx.soups[i].texcoord[0])*cctx.soups[i].numverts, §->soups[sect->numsoups].vbo.texcoord);
|
|
BE_VBO_Data(&vctx, cctx.soups[i].colour, sizeof(cctx.soups[i].colour[0])*cctx.soups[i].numverts, §->soups[sect->numsoups].vbo.colours[0]);
|
|
BE_VBO_Data(&vctx, cctx.soups[i].normal, sizeof(cctx.soups[i].normal[0])*cctx.soups[i].numverts, §->soups[sect->numsoups].vbo.normals);
|
|
BE_VBO_Data(&vctx, cctx.soups[i].sdir, sizeof(cctx.soups[i].sdir[0])*cctx.soups[i].numverts, §->soups[sect->numsoups].vbo.svector);
|
|
BE_VBO_Data(&vctx, cctx.soups[i].tdir, sizeof(cctx.soups[i].tdir[0])*cctx.soups[i].numverts, §->soups[sect->numsoups].vbo.tvector);
|
|
BE_VBO_Finish(&vctx, cctx.soups[i].idx, sizeof(cctx.soups[i].idx[0])*cctx.soups[i].numidx, §->soups[sect->numsoups].vbo.indicies, §->soups[sect->numsoups].vbo.vbomem, §->soups[sect->numsoups].vbo.ebomem);
|
|
sect->soups[sect->numsoups].vbo.colours_bytes = false;
|
|
|
|
sect->soups[sect->numsoups].mesh.numindexes = sect->soups[sect->numsoups].vbo.indexcount = cctx.soups[i].numidx;
|
|
sect->soups[sect->numsoups].mesh.numvertexes = sect->soups[sect->numsoups].vbo.vertcount = cctx.soups[i].numverts;
|
|
sect->numsoups++;
|
|
}
|
|
BZ_Free(cctx.soups[i].coord);
|
|
BZ_Free(cctx.soups[i].texcoord);
|
|
BZ_Free(cctx.soups[i].colour);
|
|
BZ_Free(cctx.soups[i].normal);
|
|
BZ_Free(cctx.soups[i].sdir);
|
|
BZ_Free(cctx.soups[i].tdir);
|
|
BZ_Free(cctx.soups[i].idx);
|
|
}
|
|
}
|
|
|
|
//emit a batch if we have grassy surfaces in this block
|
|
for (i = 0; i < sect->numsoups; i++)
|
|
{
|
|
b = BE_GetTempBatch();
|
|
if (!b)
|
|
return;
|
|
memset(b, 0, sizeof(*b));
|
|
for (j = 0; j < MAXRLIGHTMAPS; j++)
|
|
b->lightmap[j] = -1;
|
|
b->ent = &r_worldentity;
|
|
b->meshes = 1;
|
|
b->mesh = §->soups[i].pmesh;
|
|
b->vbo = §->soups[i].vbo;
|
|
b->shader = sect->soups[i].shader;
|
|
b->next = batches[b->shader->sort];
|
|
batches[b->shader->sort] = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
void R_Clutter_Purge(void)
|
|
{
|
|
size_t i, j;
|
|
cluttersector_t *sect;
|
|
if (!qrenderer)
|
|
return;
|
|
for (i = 0; i < sizeof(cluttersector)/sizeof(cluttersector[0]); i++)
|
|
{
|
|
sect = &cluttersector[i];
|
|
for (j = 0; j < sect->numsoups; j++)
|
|
{
|
|
BE_VBO_Destroy(§->soups[j].vbo.coord, sect->soups[j].vbo.vbomem);
|
|
BE_VBO_Destroy(§->soups[j].vbo.indicies, sect->soups[j].vbo.ebomem);
|
|
}
|
|
memset(sect, 0, sizeof(*sect));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
static void QDECL R_Rockettrail_Callback(struct cvar_s *var, char *oldvalue)
|
|
{
|
|
int i;
|
|
model_t *mod;
|
|
extern model_t *mod_known;
|
|
extern int mod_numknown;
|
|
|
|
if (cls.state == ca_disconnected)
|
|
return; // don't bother parsing while disconnected
|
|
|
|
for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
|
|
{
|
|
if (mod->loadstate == MLS_LOADED)
|
|
if (mod->flags & MF_ROCKET)
|
|
P_LoadedModel(mod);
|
|
}
|
|
}
|
|
|
|
static void QDECL R_Grenadetrail_Callback(struct cvar_s *var, char *oldvalue)
|
|
{
|
|
int i;
|
|
model_t *mod;
|
|
extern model_t *mod_known;
|
|
extern int mod_numknown;
|
|
|
|
if (cls.state == ca_disconnected)
|
|
return; // don't bother parsing while disconnected
|
|
|
|
for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
|
|
{
|
|
if (mod->loadstate == MLS_LOADED)
|
|
if (mod->flags & MF_GRENADE)
|
|
P_LoadedModel(mod);
|
|
}
|
|
}
|
|
|
|
extern particleengine_t pe_null;
|
|
#ifdef PSET_CLASSIC
|
|
extern particleengine_t pe_classic;
|
|
#endif
|
|
particleengine_t pe_darkplaces;
|
|
particleengine_t pe_qmb;
|
|
#ifdef PSET_SCRIPT
|
|
extern particleengine_t pe_script;
|
|
#endif
|
|
|
|
particleengine_t *particlesystem[] =
|
|
{
|
|
#ifdef PSET_SCRIPT
|
|
&pe_script,
|
|
#endif
|
|
&pe_darkplaces,
|
|
&pe_qmb,
|
|
#ifdef PSET_CLASSIC
|
|
&pe_classic,
|
|
#endif
|
|
&pe_null,
|
|
NULL,
|
|
};
|
|
|
|
static void QDECL R_ParticleSystem_Callback(struct cvar_s *var, char *oldvalue)
|
|
{
|
|
int i;
|
|
if (pe)
|
|
{
|
|
CL_ClearTEntParticleState();
|
|
CL_ClearLerpEntsParticleState();
|
|
#ifdef Q2CLIENT
|
|
CLQ2_ClearParticleState();
|
|
#endif
|
|
|
|
pe->ShutdownParticles();
|
|
}
|
|
|
|
if (!qrenderer)
|
|
{
|
|
pe = &pe_null;
|
|
}
|
|
else
|
|
{
|
|
pe = NULL;
|
|
for (i = 0; particlesystem[i]; i++)
|
|
{
|
|
if ( (particlesystem[i]->name1 && !stricmp(var->string, particlesystem[i]->name1))
|
|
|| (particlesystem[i]->name2 && !stricmp(var->string, particlesystem[i]->name2)))
|
|
{
|
|
pe = particlesystem[i];
|
|
break;
|
|
}
|
|
if (!pe)
|
|
if (particlesystem[i]->name1)
|
|
pe = particlesystem[i];
|
|
}
|
|
}
|
|
if (!pe)
|
|
Sys_Error("No particle system available. Please recompile.");
|
|
|
|
if (!pe->InitParticles())
|
|
{
|
|
Con_Printf("Particlesystem %s failed to init\n", pe->name1);
|
|
pe = &pe_null;
|
|
pe->InitParticles();
|
|
}
|
|
pe->ClearParticles();
|
|
CL_RegisterParticles();
|
|
}
|
|
|
|
cvar_t r_decal_noperpendicular = CVARD("r_decal_noperpendicular", "1", "When enabled, decals will not be generated on planes at a steep angle from clipped decal orientation.");
|
|
cvar_t r_rockettrail = CVARFC("r_rockettrail", "1", CVAR_SEMICHEAT, R_Rockettrail_Callback);
|
|
cvar_t r_grenadetrail = CVARFC("r_grenadetrail", "1", CVAR_SEMICHEAT, R_Grenadetrail_Callback);
|
|
#ifndef PSET_CLASSIC
|
|
cvar_t r_particlesystem = CVARFC("r_particlesystem", "script", CVAR_SEMICHEAT|CVAR_ARCHIVE|CVAR_NOSET, R_ParticleSystem_Callback);
|
|
cvar_t r_particledesc = CVARAF("r_particledesc", "", "r_particlesdesc", CVAR_SEMICHEAT|CVAR_ARCHIVE);
|
|
#else
|
|
cvar_t r_particlesystem = CVARFC("r_particlesystem", IFMINIMAL("classic", "script"), CVAR_SEMICHEAT|CVAR_ARCHIVE, R_ParticleSystem_Callback);
|
|
cvar_t r_particledesc = CVARAF("r_particledesc", "classic", "r_particlesdesc", CVAR_SEMICHEAT|CVAR_ARCHIVE);
|
|
#endif
|
|
extern cvar_t r_bouncysparks;
|
|
extern cvar_t r_part_rain;
|
|
extern cvar_t r_bloodstains;
|
|
extern cvar_t gl_part_flame;
|
|
cvar_t r_part_rain_quantity = CVARF("r_part_rain_quantity", "1", CVAR_ARCHIVE);
|
|
|
|
cvar_t r_particle_tracelimit = CVARFD("r_particle_tracelimit", "0x7fffffff", CVAR_ARCHIVE, "Number of traces to allow per frame for particle physics.");
|
|
cvar_t r_part_sparks = CVAR("r_part_sparks", "1");
|
|
cvar_t r_part_sparks_trifan = CVAR("r_part_sparks_trifan", "1");
|
|
cvar_t r_part_sparks_textured = CVAR("r_part_sparks_textured", "1");
|
|
cvar_t r_part_beams = CVAR("r_part_beams", "1");
|
|
cvar_t r_part_contentswitch = CVARFD("r_part_contentswitch", "1", CVAR_ARCHIVE, "Enable particle effects to change based on content (ex. water).");
|
|
cvar_t r_part_density = CVARF("r_part_density", "1", CVAR_ARCHIVE);
|
|
cvar_t r_part_classic_expgrav = CVARFD("r_part_classic_expgrav", "10", CVAR_ARCHIVE, "Scaler for how fast classic explosion particles should accelerate due to gravity. 1 for like vanilla, 10 for like zquake.");
|
|
cvar_t r_part_classic_opaque = CVARFD("r_part_classic_opaque", "0", CVAR_ARCHIVE, "Disables transparency on classic particles, for the oldskool look.");
|
|
|
|
cvar_t r_part_maxparticles = CVAR("r_part_maxparticles", "65536");
|
|
cvar_t r_part_maxdecals = CVAR("r_part_maxdecals", "8192");
|
|
|
|
|
|
particleengine_t *pe;
|
|
|
|
static struct partalias_s
|
|
{
|
|
struct partalias_s *next;
|
|
const char *from;
|
|
const char *to;
|
|
} *partaliaslist;
|
|
|
|
void P_ParticleEffect_f(void);
|
|
static void P_ParticleEffectAlias_f(void);
|
|
|
|
void P_InitParticleSystem(void)
|
|
{
|
|
char *particlecvargroupname = "Particle effects";
|
|
|
|
Cvar_Register(&r_decal_noperpendicular, particlecvargroupname); //decals might actually be used for more than just particles, but oh well.
|
|
|
|
Cvar_Register(&r_particlesystem, particlecvargroupname);
|
|
|
|
//particles
|
|
Cvar_Register(&r_particledesc, particlecvargroupname);
|
|
Cvar_Register(&r_bouncysparks, particlecvargroupname);
|
|
Cvar_Register(&r_part_rain, particlecvargroupname);
|
|
|
|
Cvar_Register(&r_part_rain_quantity, particlecvargroupname);
|
|
|
|
Cvar_Register(&r_particle_tracelimit, particlecvargroupname);
|
|
|
|
Cvar_Register(&r_part_maxparticles, particlecvargroupname);
|
|
Cvar_Register(&r_part_maxdecals, particlecvargroupname);
|
|
|
|
Cvar_Register(&r_part_sparks, particlecvargroupname);
|
|
Cvar_Register(&r_part_sparks_trifan, particlecvargroupname);
|
|
Cvar_Register(&r_part_sparks_textured, particlecvargroupname);
|
|
Cvar_Register(&r_part_beams, particlecvargroupname);
|
|
Cvar_Register(&r_part_contentswitch, particlecvargroupname);
|
|
Cvar_Register(&r_part_density, particlecvargroupname);
|
|
Cvar_Register(&r_part_classic_expgrav, particlecvargroupname);
|
|
Cvar_Register(&r_part_classic_opaque, particlecvargroupname);
|
|
|
|
Cvar_Register (&gl_part_flame, particlecvargroupname);
|
|
|
|
Cvar_Register (&r_rockettrail, particlecvargroupname);
|
|
Cvar_Register (&r_grenadetrail, particlecvargroupname);
|
|
|
|
//always registered to suck up stray r_part commands even when the scripted system is not active.
|
|
#ifdef PSET_SCRIPT
|
|
Cmd_AddCommand("r_part", P_ParticleEffect_f);
|
|
#endif
|
|
Cmd_AddCommand("r_partredirect", P_ParticleEffectAlias_f);
|
|
|
|
R_Clutter_Init();
|
|
}
|
|
|
|
void P_ShutdownParticleSystem(void)
|
|
{
|
|
struct partalias_s *l;
|
|
|
|
while (partaliaslist)
|
|
{
|
|
l = partaliaslist;
|
|
partaliaslist = l->next;
|
|
Z_Free(l);
|
|
}
|
|
}
|
|
|
|
static void P_ParticleEffectAlias_f(void)
|
|
{
|
|
struct partalias_s **link, *l;
|
|
char *from = Cmd_Argv(1);
|
|
char *to = Cmd_Argv(2);
|
|
|
|
//user wants to list all
|
|
if (!*from)
|
|
{
|
|
for (l = partaliaslist; l; l = l->next)
|
|
{
|
|
Con_Printf("%s -> %s\n", l->from, l->to);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//unlink the current value
|
|
for (link = &partaliaslist; (l=*link); link = &(*link)->next)
|
|
{
|
|
if (!Q_strcasecmp(l->from, from))
|
|
{
|
|
//they didn't specify a to, so just print out this one effect without removing it.
|
|
if (Cmd_Argc() == 2)
|
|
{
|
|
Con_Printf("particle %s is currently remapped to %s\n", l->from, l->to);
|
|
return;
|
|
}
|
|
*link = l->next;
|
|
Z_Free(l);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//create a new entry.
|
|
if (*to && Q_strcasecmp(from, to))
|
|
{
|
|
l = Z_Malloc(sizeof(*l) + strlen(from) + strlen(to) + 2);
|
|
l->from = (char*)(l + 1);
|
|
strcpy((char*)l->from, from);
|
|
l->to = l->from + strlen(l->from)+1;
|
|
strcpy((char*)l->to, to);
|
|
l->next = partaliaslist;
|
|
partaliaslist = l;
|
|
}
|
|
|
|
CL_RegisterParticles();
|
|
}
|
|
int P_FindParticleType(const char *efname)
|
|
{
|
|
struct partalias_s *l;
|
|
int recurselimit = 5;
|
|
if (!pe)
|
|
return P_INVALID;
|
|
for (l = partaliaslist; l; )
|
|
{
|
|
if (!Q_strcasecmp(l->from, efname))
|
|
{
|
|
efname = l->to;
|
|
|
|
if (recurselimit --> 0)
|
|
l = partaliaslist;
|
|
else
|
|
return P_INVALID;
|
|
}
|
|
else
|
|
l = l->next;
|
|
}
|
|
|
|
return pe->FindParticleType(efname);
|
|
}
|
|
|
|
void P_Shutdown(void)
|
|
{
|
|
if (pe)
|
|
{
|
|
CL_ClearTEntParticleState();
|
|
CL_ClearLerpEntsParticleState();
|
|
#ifdef Q2CLIENT
|
|
CLQ2_ClearParticleState();
|
|
#endif
|
|
pe->ShutdownParticles();
|
|
}
|
|
pe = NULL;
|
|
|
|
R_Clutter_Purge();
|
|
}
|
|
|
|
//traces against renderable entities
|
|
//0 says hit nothing.
|
|
//1 says hit world
|
|
//>1 says hit some entity
|
|
entity_t *TraceLineR (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)
|
|
{
|
|
trace_t trace;
|
|
float len, bestlen;
|
|
int i, j;
|
|
vec3_t delta, ts, te;
|
|
entity_t *pe;
|
|
entity_t *result=NULL;
|
|
// vec3_t axis[3];
|
|
vec3_t movemins, movemaxs;
|
|
|
|
memset (&trace, 0, sizeof(trace));
|
|
|
|
VectorSubtract(end, start, delta);
|
|
bestlen = Length(delta);
|
|
|
|
VectorCopy (end, impact);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (start[i] > end[i])
|
|
{
|
|
movemins[i] = end[i];
|
|
movemaxs[i] = start[i];
|
|
}
|
|
else
|
|
{
|
|
movemins[i] = start[i];
|
|
movemaxs[i] = end[i];
|
|
}
|
|
}
|
|
|
|
for (i=-1 ; i<cl_numvisedicts ; i++)
|
|
{
|
|
if (i == -1)
|
|
pe = &r_worldentity;
|
|
else
|
|
pe = &cl_visedicts[i];
|
|
if (pe->rtype != RT_MODEL || pe->shaderRGBAf[3] < 1 || (pe->flags & (RF_ADDITIVE|RF_NODEPTHTEST|RF_TRANSLUCENT|RF_EXTERNALMODEL)))
|
|
continue;
|
|
if (pe->model && pe->model->funcs.NativeTrace && pe->model->loadstate == MLS_LOADED)
|
|
{
|
|
//try to trivially reject the mesh.
|
|
float ext = 0;
|
|
float t;
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
t = fabs(pe->model->maxs[j]);
|
|
ext = max(ext, t);
|
|
t = fabs(pe->model->mins[j]);
|
|
ext = max(ext, t);
|
|
}
|
|
if ( movemins[0] > pe->origin[0]+ext
|
|
|| movemins[1] > pe->origin[1]+ext
|
|
|| movemins[2] > pe->origin[2]+ext
|
|
|| movemaxs[0] < pe->origin[0]-ext
|
|
|| movemaxs[1] < pe->origin[1]-ext
|
|
|| movemaxs[2] < pe->origin[2]-ext )
|
|
continue;
|
|
|
|
VectorSubtract(start, pe->origin, ts);
|
|
VectorSubtract(end, pe->origin, te);
|
|
pe->model->funcs.NativeTrace(pe->model, 0, &pe->framestate, pe->axis, ts, te, vec3_origin, vec3_origin, false, MASK_WORLDSOLID, &trace);
|
|
if (trace.fraction<1)
|
|
{
|
|
VectorSubtract(trace.endpos, ts, delta);
|
|
len = DotProduct(delta,delta);
|
|
if (len < bestlen)
|
|
{
|
|
bestlen = len;
|
|
if (normal)
|
|
VectorCopy (trace.plane.normal, normal);
|
|
VectorAdd (pe->origin, trace.endpos, impact);
|
|
}
|
|
|
|
result = pe;
|
|
}
|
|
/*if (trace.startsolid)
|
|
{
|
|
VectorNormalize(delta);
|
|
if (normal)
|
|
{
|
|
normal[0] = -delta[0];
|
|
normal[1] = -delta[1];
|
|
normal[2] = -delta[2];
|
|
}
|
|
VectorCopy (end, impact);
|
|
return NULL;
|
|
}*/
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//traces against networked entities only.
|
|
//0 says hit nothing.
|
|
//1 says hit world
|
|
//>1 says hit some entity
|
|
float CL_TraceLine (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal, int *ent)
|
|
{
|
|
trace_t trace;
|
|
float bestfrac=1;
|
|
int i;
|
|
vec3_t ts, te;
|
|
physent_t *pe;
|
|
model_t *mod;
|
|
int result=0;
|
|
vec3_t axis[3];
|
|
|
|
memset (&trace, 0, sizeof(trace));
|
|
|
|
VectorCopy (end, impact);
|
|
if (normal)
|
|
VectorClear(normal);
|
|
|
|
for (i=0 ; i < pmove.numphysent ; i++)
|
|
{
|
|
pe = &pmove.physents[i];
|
|
if (pe->nonsolid)
|
|
continue;
|
|
mod = pe->model;
|
|
if (mod && mod->loadstate == MLS_LOADED && mod->funcs.NativeTrace)
|
|
{
|
|
VectorSubtract(start, pe->origin, ts);
|
|
VectorSubtract(end, pe->origin, te);
|
|
if (pe->angles[0] || pe->angles[1] || pe->angles[2])
|
|
{
|
|
AngleVectors(pe->angles, axis[0], axis[1], axis[2]);
|
|
VectorNegate(axis[1], axis[1]);
|
|
mod->funcs.NativeTrace(mod, 0, PE_FRAMESTATE, axis, ts, te, vec3_origin, vec3_origin, false, MASK_WORLDSOLID, &trace);
|
|
}
|
|
else
|
|
mod->funcs.NativeTrace(mod, 0, PE_FRAMESTATE, NULL, ts, te, vec3_origin, vec3_origin, false, MASK_WORLDSOLID, &trace);
|
|
if (trace.fraction<1)
|
|
{
|
|
if (bestfrac > trace.fraction)
|
|
{
|
|
bestfrac = trace.fraction;
|
|
if (normal)
|
|
VectorCopy (trace.plane.normal, normal);
|
|
VectorAdd (pe->origin, trace.endpos, impact);
|
|
result = pe->info;
|
|
}
|
|
}
|
|
if (trace.startsolid)
|
|
{
|
|
if (normal)
|
|
{
|
|
VectorSubtract(start, end, normal);
|
|
VectorNormalize(normal);
|
|
}
|
|
VectorCopy (end, impact);
|
|
|
|
//hit nothing
|
|
if (ent)
|
|
*ent = 0;
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (ent)
|
|
*ent = result;
|
|
return bestfrac;
|
|
}
|
|
|
|
//handy utility...
|
|
void P_EmitEffect (vec3_t pos, vec3_t orientation[3], unsigned int modeleflags, int type, trailstate_t **tsk)
|
|
{
|
|
float count;
|
|
if (cl.paused)
|
|
return;
|
|
|
|
count = ((host_frametime>0.1)?0.1:host_frametime);
|
|
if (orientation)
|
|
{
|
|
if (modeleflags & MDLF_EMITFORWARDS)
|
|
pe->RunParticleEffectState(pos, orientation[0], count, type, tsk);
|
|
else
|
|
{
|
|
vec3_t down;
|
|
VectorNegate(orientation[2], down);
|
|
pe->RunParticleEffectState(pos, down, count, type, tsk);
|
|
}
|
|
}
|
|
else
|
|
pe->RunParticleEffectState(pos, NULL, count, type, tsk);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// P_SelectableTrail: given default/opposite effects, model pointer, and a user selection cvar
|
|
// changes model to the appropriate trail effect and default trail index
|
|
static void P_SelectableTrail(int *trailid, int *trailpalidx, cvar_t *selection, int mdleffect, int mdlcidx, int oppeffect, int oppcidx)
|
|
{
|
|
int select = (int)(selection->value);
|
|
|
|
switch (select)
|
|
{
|
|
case 0: // check for string, otherwise no trail
|
|
if (selection->string[0] == '0')
|
|
{
|
|
*trailid = P_INVALID;
|
|
*trailpalidx = -1;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
int effect = P_FindParticleType(selection->string);
|
|
|
|
if (effect >= 0)
|
|
{
|
|
*trailid = effect;
|
|
*trailpalidx = mdlcidx;
|
|
break;
|
|
}
|
|
}
|
|
// fall through to default (so semicheat will work properly)
|
|
case 1: // default model effect
|
|
default:
|
|
*trailid = mdleffect;
|
|
*trailpalidx = mdlcidx;
|
|
break;
|
|
case 2: // opposite effect
|
|
*trailid = oppeffect;
|
|
*trailpalidx= oppcidx;
|
|
break;
|
|
case 3: // alt rocket effect
|
|
*trailid = P_FindParticleType("TR_ALTROCKET");
|
|
*trailpalidx = 107;
|
|
break;
|
|
case 4: // gib
|
|
*trailid = P_FindParticleType("TR_BLOOD");
|
|
*trailpalidx = 70;
|
|
break;
|
|
case 5: // zombie gib
|
|
*trailid = P_FindParticleType("TR_SLIGHTBLOOD");
|
|
*trailpalidx = 70;
|
|
break;
|
|
case 6: // Scrag tracer
|
|
*trailid = P_FindParticleType("TR_WIZSPIKE");
|
|
*trailpalidx = 60;
|
|
break;
|
|
case 7: // Knight tracer
|
|
*trailid = P_FindParticleType("TR_KNIGHTSPIKE");
|
|
*trailpalidx = 238;
|
|
break;
|
|
case 8: // Vore tracer
|
|
*trailid = P_FindParticleType("TR_VORESPIKE");
|
|
*trailpalidx = 154;
|
|
break;
|
|
case 9: // rail trail
|
|
*trailid = P_FindParticleType("TE_RAILTRAIL");
|
|
*trailpalidx = 15;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//figure out which particle trail to use for the given model, filling in its values as required.
|
|
void P_DefaultTrail (unsigned int entityeffects, unsigned int modelflags, int *trailid, int *trailpalidx)
|
|
{
|
|
// TODO: EF_BRIGHTFIELD should probably be handled in here somewhere
|
|
// TODO: make trail default color into RGB values instead of indexes
|
|
if (!pe)
|
|
return;
|
|
|
|
if (entityeffects & EF_BRIGHTFIELD)
|
|
{
|
|
*trailid = P_FindParticleType("EF_BRIGHTFIELD");
|
|
*trailpalidx = 70;
|
|
}
|
|
else if (entityeffects & DPEF_FLAME)
|
|
{
|
|
*trailid = P_FindParticleType("EF_FLAME");
|
|
*trailpalidx = 70;
|
|
}
|
|
else if (entityeffects & DPEF_STARDUST)
|
|
{
|
|
*trailid = P_FindParticleType("EF_STARDUST");
|
|
*trailpalidx = 70;
|
|
}
|
|
else if (modelflags & MF_ROCKET)
|
|
P_SelectableTrail(trailid, trailpalidx, &r_rockettrail, P_FindParticleType("TR_ROCKET"), 109, P_FindParticleType("TR_GRENADE"), 6);
|
|
else if (modelflags & MF_GRENADE)
|
|
P_SelectableTrail(trailid, trailpalidx, &r_grenadetrail, P_FindParticleType("TR_GRENADE"), 6, P_FindParticleType("TR_ROCKET"), 109);
|
|
else if (modelflags & MF_GIB)
|
|
{
|
|
*trailid = P_FindParticleType("TR_BLOOD");
|
|
*trailpalidx = 70;
|
|
}
|
|
else if (modelflags & MF_TRACER)
|
|
{
|
|
*trailid = P_FindParticleType("TR_WIZSPIKE");
|
|
*trailpalidx = 60;
|
|
}
|
|
else if (modelflags & MF_ZOMGIB)
|
|
{
|
|
*trailid = P_FindParticleType("TR_SLIGHTBLOOD");
|
|
*trailpalidx = 70;
|
|
}
|
|
else if (modelflags & MF_TRACER2)
|
|
{
|
|
*trailid = P_FindParticleType("TR_KNIGHTSPIKE");
|
|
*trailpalidx = 238;
|
|
}
|
|
else if (modelflags & MF_TRACER3)
|
|
{
|
|
*trailid = P_FindParticleType("TR_VORESPIKE");
|
|
*trailpalidx = 154;
|
|
}
|
|
#ifdef HEXEN2
|
|
else if (modelflags & MFH2_BLOODSHOT) //these are the hexen2 ones.
|
|
{
|
|
*trailid = P_FindParticleType("tr_bloodshot");
|
|
*trailpalidx = 136;
|
|
}
|
|
else if (modelflags & MFH2_FIREBALL)
|
|
{
|
|
*trailid = P_FindParticleType("tr_fireball");
|
|
*trailpalidx = 424;
|
|
}
|
|
else if (modelflags & MFH2_ACIDBALL)
|
|
{
|
|
*trailid = P_FindParticleType("tr_acidball");
|
|
*trailpalidx = 440;
|
|
}
|
|
else if (modelflags & MFH2_ICE)
|
|
{
|
|
*trailid = P_FindParticleType("tr_ice");
|
|
*trailpalidx = 408;
|
|
}
|
|
else if (modelflags & MFH2_SPIT)
|
|
{
|
|
*trailid = P_FindParticleType("tr_spit");
|
|
*trailpalidx = 260;
|
|
}
|
|
else if (modelflags & MFH2_SPELL)
|
|
{
|
|
*trailid = P_FindParticleType("tr_spell");
|
|
*trailpalidx = 260;
|
|
}
|
|
else if (modelflags & MFH2_VORP_MISSILE)
|
|
{
|
|
*trailid = P_FindParticleType("tr_vorpmissile");
|
|
*trailpalidx = 302;
|
|
}
|
|
else if (modelflags & MFH2_SET_STAFF)
|
|
{
|
|
*trailid = P_FindParticleType("tr_setstaff");
|
|
*trailpalidx = 424;
|
|
}
|
|
else if (modelflags & MFH2_MAGICMISSILE)
|
|
{
|
|
*trailid = P_FindParticleType("tr_magicmissile");
|
|
*trailpalidx = 149;
|
|
}
|
|
else if (modelflags & MFH2_BONESHARD)
|
|
{
|
|
*trailid = P_FindParticleType("tr_boneshard");
|
|
*trailpalidx = 384;
|
|
}
|
|
else if (modelflags & MFH2_SCARAB)
|
|
{
|
|
*trailid = P_FindParticleType("tr_scarab");
|
|
*trailpalidx = 254;
|
|
}
|
|
else if (modelflags & MFH2_SPIDERBLOOD)
|
|
{
|
|
//spiders
|
|
*trailid = P_FindParticleType("TR_GREENBLOOD");
|
|
*trailpalidx = 70; //fixme
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
*trailid = P_INVALID;
|
|
*trailpalidx = -1;
|
|
}
|
|
}
|