#include "image.h"
#include "r_shadow.h"
-#define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
-#define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
-
// must match ptype_t values
particletype_t particletype[pt_total] =
{
}
particleeffectinfo_t;
-#define MAX_PARTICLEEFFECTNAME 256
char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
-#define MAX_PARTICLEEFFECTINFO 4096
particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
//static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
-#define MAX_PARTICLETEXTURES 1024
// particletexture_t is a rectangle in the particlefonttexture
typedef struct particletexture_s
{
cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
-cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
+cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
+cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
+cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
+cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
+cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
+cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
+cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
+cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
+cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
-void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
+void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
{
int arrayindex;
int argc;
}
if (argc < 1)
continue;
-#define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
+#define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
#define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
#define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
#define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
effectinfoindex++;
if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
{
- Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
+ Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
break;
}
for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
// if we run out of names, abort
if (effectnameindex == MAX_PARTICLEEFFECTNAME)
{
- Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
+ Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
break;
}
info = particleeffectinfo + effectinfoindex;
}
else if (info == NULL)
{
- Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
+ Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
break;
}
else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
- else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
+ else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
info->blendmode = particletype[info->particletype].blendmode;
info->orientation = particletype[info->particletype].orientation;
}
if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
- else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
+ else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
}
else if (!strcmp(argv[0], "orientation"))
{
else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
- else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
+ else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
}
else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
else
- Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
+ Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
#undef checkparms
#undef readints
#undef readfloats
void CL_Particles_LoadEffectInfo(void)
{
int i;
+ int filepass;
unsigned char *filedata;
fs_offset_t filesize;
+ char filename[MAX_QPATH];
memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
memset(particleeffectname, 0, sizeof(particleeffectname));
for (i = 0;i < EFFECT_TOTAL;i++)
strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
- filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
- if (filedata)
+ for (filepass = 0;;filepass++)
{
- CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
+ if (filepass == 0)
+ dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
+ else if (filepass == 1)
+ dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
+ else
+ break;
+ filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
+ if (!filedata)
+ continue;
+ CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
Mem_Free(filedata);
}
}
void CL_Particles_Init (void)
{
Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
- Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
+ Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
Cvar_RegisterVariable (&cl_particles);
Cvar_RegisterVariable (&cl_particles_quality);
Cvar_RegisterVariable (&cl_particles_quake);
Cvar_RegisterVariable (&cl_particles_blood);
Cvar_RegisterVariable (&cl_particles_blood_alpha);
+ Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
+ Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
+ Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
Cvar_RegisterVariable (&cl_particles_explosions_sparks);
Cvar_RegisterVariable (&cl_particles_explosions_shell);
Cvar_RegisterVariable (&cl_decals_visculling);
Cvar_RegisterVariable (&cl_decals_time);
Cvar_RegisterVariable (&cl_decals_fadetime);
+ Cvar_RegisterVariable (&cl_decals_newsystem);
+ Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
+ Cvar_RegisterVariable (&cl_decals_models);
+ Cvar_RegisterVariable (&cl_decals_bias);
+ Cvar_RegisterVariable (&cl_decals_max);
}
void CL_Particles_Shutdown (void)
{
}
+void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
+void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
+
// list of all 26 parameters:
// ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
// pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
}
+
return part;
}
+static void CL_ImmediateBloodStain(particle_t *part)
+{
+ vec3_t v;
+ int staintex;
+
+ // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
+ if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
+ {
+ VectorCopy(part->vel, v);
+ VectorNormalize(v);
+ staintex = part->staintexnum;
+ R_DecalSystem_SplatEntities(part->org, v, 1-((part->staincolor>>16)&255)*(1.0f/255.0f), 1-((part->staincolor>>8)&255)*(1.0f/255.0f), 1-((part->staincolor)&255)*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
+ }
+
+ // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
+ if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
+ {
+ VectorCopy(part->vel, v);
+ VectorNormalize(v);
+ staintex = tex_blooddecal[rand()&7];
+ R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
+ }
+}
+
void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
{
int l1, l2;
decal_t *decal;
+ entity_render_t *ent = &cl.entities[hitent].render;
+ unsigned char color[3];
if (!cl_decals.integer)
return;
+ if (!ent->allowdecals)
+ return;
+
+ l2 = (int)lhrandom(0.5, 256.5);
+ l1 = 256 - l2;
+ color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
+ color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
+ color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
+
+ if (cl_decals_newsystem.integer)
+ {
+ R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
+ return;
+ }
+
for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
if (cl.free_decal >= cl.max_decals)
return;
if (cl.num_decals < cl.free_decal)
cl.num_decals = cl.free_decal;
memset(decal, 0, sizeof(*decal));
+ decal->decalsequence = cl.decalsequence++;
decal->typeindex = pt_decal;
decal->texnum = texnum;
- VectorAdd(org, normal, decal->org);
+ VectorMA(org, cl_decals_bias.value, normal, decal->org);
VectorCopy(normal, decal->normal);
decal->size = size;
decal->alpha = alpha;
decal->time2 = cl.time;
- l2 = (int)lhrandom(0.5, 256.5);
- l1 = 256 - l2;
- decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
- decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
- decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
+ decal->color[0] = color[0];
+ decal->color[1] = color[1];
+ decal->color[2] = color[2];
decal->owner = hitent;
decal->clusterindex = -1000; // no vis culling unless we're sure
if (hitent)
{
vec3_t center;
matrix4x4_t tempmatrix;
+ particle_t *part;
VectorLerp(originmins, 0.5, originmaxs, center);
Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
if (effectnameindex == EFFECT_SVC_PARTICLE)
else
{
static double bloodaccumulator = 0;
+ qboolean immediatebloodstain = true;
//CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
bloodaccumulator += count * 0.333 * cl_particles_quality.value;
for (;bloodaccumulator > 0;bloodaccumulator--)
- CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
+ {
+ part = CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
+ if (immediatebloodstain && part)
+ {
+ immediatebloodstain = false;
+ CL_ImmediateBloodStain(part);
+ }
+ }
}
}
else if (effectnameindex == EFFECT_TE_SPARK)
matrix4x4_t tempmatrix;
Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
- r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
+ r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
}
}
if (ent)
ent->persistent.trail_time = len;
}
- else if (developer.integer >= 1)
- Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
+ else
+ Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
}
// this is also called on point effects with spawndlight = true and
// these parameters, only trail handling does
void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
{
- vec3_t center;
qboolean found = false;
if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
{
Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
return; // no such effect
}
- VectorLerp(originmins, 0.5, originmaxs, center);
if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
{
int effectinfoindex;
int tex, staintex;
particleeffectinfo_t *info;
vec3_t center;
- vec3_t centervelocity;
vec3_t traildir;
vec3_t trailpos;
vec3_t rvec;
vec_t traillen;
vec_t trailstep;
qboolean underwater;
+ qboolean immediatebloodstain;
+ particle_t *part;
// note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
VectorLerp(originmins, 0.5, originmaxs, center);
- VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
supercontents = CL_PointSuperContents(center);
underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
VectorSubtract(originmaxs, originmins, traildir);
// called by CL_LinkNetworkEntity
Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
- r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
+ r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
}
}
{
info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
trailstep = info->trailspacing / cl_particles_quality.value;
+ immediatebloodstain = false;
}
else
{
info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
trailstep = 0;
+ immediatebloodstain = info->particletype == pt_blood || staintex;
}
info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
for (;info->particleaccumulator >= 1;info->particleaccumulator--)
trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
}
VectorRandom(rvec);
- CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
+ part = CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
+ if (immediatebloodstain && part)
+ {
+ immediatebloodstain = false;
+ CL_ImmediateBloodStain(part);
+ }
if (trailstep)
VectorMA(trailpos, trailstep, traildir, trailpos);
}
static void R_InitBloodTextures (unsigned char *particletexturedata)
{
int i, j, k, m;
- unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
+ size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
+ unsigned char *data = Mem_Alloc(tempmempool, datasize);
// blood particles
for (i = 0;i < 8;i++)
{
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
for (k = 0;k < 24;k++)
- particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
- //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
- particletextureinvert(&data[0][0][0]);
- setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
+ particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
+ //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
+ particletextureinvert(data);
+ setuptex(tex_bloodparticle[i], data, particletexturedata);
}
// blood decals
for (i = 0;i < 8;i++)
{
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
m = 8;
for (j = 1;j < 10;j++)
for (k = min(j, m - 1);k < m;k++)
- particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
- //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
- particletextureinvert(&data[0][0][0]);
- setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
+ particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
+ //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
+ particletextureinvert(data);
+ setuptex(tex_blooddecal[i], data, particletexturedata);
}
+ Mem_Free(data);
}
//uncomment this to make engine save out particle font to a tga file when run
// we invert it again during the blendfunc to make it work...
#ifndef DUMPPARTICLEFONT
- decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
+ decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
if (decalskinframe)
{
particlefonttexture = decalskinframe->base;
#endif
{
unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
- unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
+ size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
+ unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
+ unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
+ unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
// smoke
for (i = 0;i < 8;i++)
{
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
do
{
- unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
-
- fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
- fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
+ fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
+ fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
m = 0;
for (y = 0;y < PARTICLETEXTURESIZE;y++)
{
for (x = 0;x < PARTICLETEXTURESIZE;x++)
{
dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
- d = (noise2[y][x] - 128) * 3 + 192;
+ d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
if (d > 0)
d = (int)(d * (1-(dx*dx+dy*dy)));
- d = (d * noise1[y][x]) >> 7;
+ d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
d = bound(0, d, 255);
- data[y][x][3] = (unsigned char) d;
+ data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
if (m < d)
m = d;
}
}
}
while (m < 224);
- setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
+ setuptex(tex_smoke[i], data, particletexturedata);
}
// rain splash
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
for (y = 0;y < PARTICLETEXTURESIZE;y++)
{
dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
{
dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
- data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
+ data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
}
}
- setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
+ setuptex(tex_rainsplash, data, particletexturedata);
// normal particle
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
for (y = 0;y < PARTICLETEXTURESIZE;y++)
{
dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
d = (int)(256 * (1 - (dx*dx+dy*dy)));
d = bound(0, d, 255);
- data[y][x][3] = (unsigned char) d;
+ data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
}
}
- setuptex(tex_particle, &data[0][0][0], particletexturedata);
+ setuptex(tex_particle, data, particletexturedata);
// rain
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
light[0] = 1;light[1] = 1;light[2] = 1;
VectorNormalize(light);
for (y = 0;y < PARTICLETEXTURESIZE;y++)
dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
// shrink bubble width to half
dx *= 2.0f;
- data[y][x][3] = shadebubble(dx, dy, light);
+ data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
}
}
- setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
+ setuptex(tex_raindrop, data, particletexturedata);
// bubble
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
light[0] = 1;light[1] = 1;light[2] = 1;
VectorNormalize(light);
for (y = 0;y < PARTICLETEXTURESIZE;y++)
for (x = 0;x < PARTICLETEXTURESIZE;x++)
{
dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
- data[y][x][3] = shadebubble(dx, dy, light);
+ data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
}
}
- setuptex(tex_bubble, &data[0][0][0], particletexturedata);
+ setuptex(tex_bubble, data, particletexturedata);
// Blood particles and blood decals
R_InitBloodTextures (particletexturedata);
// bullet decals
for (i = 0;i < 8;i++)
{
- memset(&data[0][0][0], 255, sizeof(data));
+ memset(data, 255, datasize);
for (k = 0;k < 12;k++)
- particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
+ particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
for (k = 0;k < 3;k++)
- particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
- //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
- particletextureinvert(&data[0][0][0]);
- setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
+ particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
+ //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
+ particletextureinvert(data);
+ setuptex(tex_bulletdecal[i], data, particletexturedata);
}
#ifdef DUMPPARTICLEFONT
Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
#endif
- decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
+ decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
particlefonttexture = decalskinframe->base;
Mem_Free(particletexturedata);
+ Mem_Free(data);
+ Mem_Free(noise1);
+ Mem_Free(noise2);
}
for (i = 0;i < MAX_PARTICLETEXTURES;i++)
{
}
#ifndef DUMPPARTICLEFONT
- particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
+ particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
if (!particletexture[tex_beam].texture)
#endif
{
#ifdef DUMPPARTICLEFONT
Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
#endif
- particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
+ particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
}
particletexture[tex_beam].s1 = 0;
particletexture[tex_beam].t1 = 0;
#define BATCHSIZE 256
unsigned short particle_elements[BATCHSIZE*6];
+float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
void R_Particles_Init (void)
{
float *v3f, *t2f, *c4f;
particletexture_t *tex;
float right[3], up[3], size, ca;
- float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
+ float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
RSurf_ActiveWorldEntity();
- r_refdef.stats.decals += numsurfaces;
+ r_refdef.stats.drawndecals += numsurfaces;
R_Mesh_ResetTextureState();
R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
R_Mesh_ColorPointer(particle_color4f, 0, 0);
- R_SetupGenericShader(true);
GL_DepthMask(false);
GL_DepthRange(0, 1);
GL_PolygonOffset(0, 0);
// now render the decals all at once
// (this assumes they all use one particle font texture!)
GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
- R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
- GL_LockArrays(0, numsurfaces*4);
+ R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
- GL_LockArrays(0, 0);
}
void R_DrawDecals (void)
float frametime;
float decalfade;
float drawdist2;
+ int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
frametime = bound(0, cl.time - cl.decals_updatetime, 1);
cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
if (!decal->typeindex)
continue;
+ if (killsequence - decal->decalsequence > 0)
+ goto killdecal;
+
if (cl.time > decal->time2 + cl_decals_time.value)
{
decal->alpha -= decalfade;
while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
cl.num_decals--;
- if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
+ if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
{
decal_t *olddecals = cl.decals;
- cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
+ cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
Mem_Free(olddecals);
}
+
+ r_refdef.stats.totaldecals = cl.num_decals;
}
void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
float ambient[3], diffuse[3], diffusenormal[3];
vec4_t colormultiplier;
- float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
RSurf_ActiveWorldEntity();
R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
R_Mesh_ColorPointer(particle_color4f, 0, 0);
- R_SetupGenericShader(true);
GL_DepthMask(false);
GL_DepthRange(0, 1);
GL_PolygonOffset(0, 0);
blendmode = p->blendmode;
- c4f[0] = p->color[0] * colormultiplier[0];
- c4f[1] = p->color[1] * colormultiplier[1];
- c4f[2] = p->color[2] * colormultiplier[2];
- c4f[3] = p->alpha * colormultiplier[3];
switch (blendmode)
{
case PBLEND_INVALID:
case PBLEND_INVMOD:
+ c4f[0] = p->color[0] * (1.0f / 256.0f);
+ c4f[1] = p->color[1] * (1.0f / 256.0f);
+ c4f[2] = p->color[2] * (1.0f / 256.0f);
+ c4f[3] = p->alpha * colormultiplier[3];
+ // additive and modulate can just fade out in fog (this is correct)
+ if (r_refdef.fogenabled)
+ c4f[3] *= RSurf_FogVertex(p->org);
+ // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
+ c4f[0] *= c4f[3];
+ c4f[1] *= c4f[3];
+ c4f[2] *= c4f[3];
+ c4f[3] = 1;
+ break;
case PBLEND_ADD:
+ c4f[0] = p->color[0] * colormultiplier[0];
+ c4f[1] = p->color[1] * colormultiplier[1];
+ c4f[2] = p->color[2] * colormultiplier[2];
+ c4f[3] = p->alpha * colormultiplier[3];
// additive and modulate can just fade out in fog (this is correct)
if (r_refdef.fogenabled)
c4f[3] *= RSurf_FogVertex(p->org);
c4f[3] = 1;
break;
case PBLEND_ALPHA:
+ c4f[0] = p->color[0] * colormultiplier[0];
+ c4f[1] = p->color[1] * colormultiplier[1];
+ c4f[2] = p->color[2] * colormultiplier[2];
+ c4f[3] = p->alpha * colormultiplier[3];
// note: lighting is not cheap!
if (particletype[p->typeindex].lighting)
{
// now render batches of particles based on blendmode and texture
blendmode = PBLEND_INVALID;
texture = NULL;
- GL_LockArrays(0, numsurfaces*4);
batchstart = 0;
batchcount = 0;
for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
if (texture != particletexture[p->texnum].texture)
{
texture = particletexture[p->texnum].texture;
- R_Mesh_TexBind(0, R_GetTexture(texture));
+ R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
}
// iterate until we find a change in settings
batchcount = surfacelistindex - batchstart;
R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
}
- GL_LockArrays(0, 0);
}
void R_DrawParticles (void)
{
- int i, a, content;
+ int i, a;
int drawparticles = r_drawparticles.integer;
float minparticledist;
particle_t *p;
- float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
+ float gravity, frametime, f, dist, oldorg[3];
float drawdist2;
int hitent;
trace_t trace;
minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
gravity = frametime * cl.movevars_gravity;
- dvel = 1+4*frametime;
- decalfade = frametime * 255 / cl_decals_fadetime.value;
update = frametime > 0;
drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
drawdist2 = drawdist2*drawdist2;
continue;
p->delayedspawn = 0;
- content = 0;
-
p->size += p->sizeincrease * frametime;
p->alpha -= p->alphafade * frametime;
if (cl_decals.integer)
{
// create a decal for the blood splat
- CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
+ CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
}
}
goto killparticle;
while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
cl.num_particles--;
- if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
+ if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
{
particle_t *oldparticles = cl.particles;
- cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
+ cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
Mem_Free(oldparticles);