/** * @brief Particle tracing with caching */ static trace_t PTL_Trace (ptl_t* ptl, const AABB& aabb) { static ptlTraceCache_t ptlCache; const float epsilonPos = 3.0f; const float epsilonBBox = 1.0f; if (VectorCompareEps(ptlCache.start, ptl->origin, epsilonPos) && VectorCompareEps(ptlCache.end, ptl->s, epsilonPos) && VectorCompareEps(ptlCache.pBox.mins, aabb.mins, epsilonBBox) && VectorCompareEps(ptlCache.pBox.maxs, aabb.maxs, epsilonBBox)) { ptlCache.count++; return ptlCache.trace; } VectorCopy(ptl->origin, ptlCache.start); VectorCopy(ptl->s, ptlCache.end); ptlCache.pBox.set(aabb); ptlCache.trace = CL_Trace(Line(ptl->origin, ptl->s), aabb, nullptr, nullptr, MASK_SOLID, cl.mapMaxLevel - 1); return ptlCache.trace; }
/** * @brief Particle tracing with caching */ static inline trace_t PTL_Trace (ptl_t *ptl, const vec3_t mins, const vec3_t maxs) { static ptlTraceCache_t ptlCache; const float epsilonPos = 3.0f; const float epsilonBBox = 1.0f; if (VectorCompareEps(ptlCache.start, ptl->origin, epsilonPos) && VectorCompareEps(ptlCache.end, ptl->s, epsilonPos) && VectorCompareEps(ptlCache.mins, mins, epsilonBBox) && VectorCompareEps(ptlCache.maxs, maxs, epsilonBBox)) { ptlCache.count++; return ptlCache.trace; } VectorCopy(ptl->origin, ptlCache.start); VectorCopy(ptl->s, ptlCache.end); VectorCopy(mins, ptlCache.mins); VectorCopy(maxs, ptlCache.maxs); ptlCache.trace = CL_Trace(ptl->origin, ptl->s, AABB(mins, maxs), nullptr, nullptr, MASK_SOLID, cl.mapMaxLevel - 1); return ptlCache.trace; }
static void LE_PlayFootStepSound (le_t* le) { if (Q_strvalid(le->teamDef->footstepSound)) { S_LoadAndPlaySample(le->teamDef->footstepSound, le->origin, SOUND_ATTN_NORM, SND_VOLUME_FOOTSTEPS); return; } /* walking in water will not play the normal footstep sounds */ if (!le->pathContents[le->pathPos]) { vec3_t from, to; /* prepare trace vectors */ PosToVec(le->pos, from); VectorCopy(from, to); /* we should really hit the ground with this */ to[2] -= UNIT_HEIGHT; const trace_t trace = CL_Trace(Line(from, to), AABB::EMPTY, nullptr, nullptr, MASK_SOLID, cl_worldlevel->integer); if (trace.surface) LE_PlaySoundFileAndParticleForSurface(le, trace.surface->name); } else LE_PlaySoundFileForContents(le, le->pathContents[le->pathPos]); }
/* =============== CL_AddParticles =============== */ void CL_AddParticles (void) { cparticle_t *p, *next; float alpha; float time, time2; vec3_t org; int color; cparticle_t *active, *tail; int contents; float grav; active = NULL; tail = NULL; if (!cl_drawParticles->integer) return; // allow gravity tweaks by the server grav = Cvar_VariableValue("sv_gravity"); if (!grav) grav = 1; else grav /= 800; for (p = active_particles; p; p = next) { next = p->next; // set alpha // PMM - added INSTANT_PARTICLE handling if (p->alphavel != INSTANT_PARTICLE) { time = (cl.time - p->time) * 0.001; alpha = p->alpha + time * p->alphavel; if (alpha <= 0) { // faded out CL_FreeParticle (p); continue; } else if (alpha <= 0.3f && p->color == 240) // this is HACK central... { // do blood decals if (rand() & 4) { trace_t tr; time2 = time * time; org[0] = p->org[0] + p->vel[0] * time + p->accel[0] * time2; org[1] = p->org[1] + p->vel[1] * time + p->accel[1] * time2; org[2] = p->org[2] + p->vel[2] * time + p->accel[2] * time2; tr = CL_Trace(p->org, org, 0, MASK_SOLID); if (tr.fraction != 1.0f) { if (!VectorCompare(tr.plane.normal, vec3_origin) && !(CM_PointContents(p->org, 0) & MASK_WATER)) // no blood splatters underwater... { vec4_t color; Vector4Set(color, 1.0, 0.0, 0.0, 1.0f); RE_AddDecal(tr.endpos, tr.plane.normal, color, 16 + ((rand() % 21 * 0.05f) - 0.5f), DECAL_BLOOD + (rand() & 4), 0, frand() * 360); } } } } } else { alpha = p->alpha; } if (alpha > 1.0) alpha = 1; // set color color = p->color; // backup old position VectorCopy (p->org, p->oldOrg); // calculate new position time2 = time * time; org[0] = p->org[0] + p->vel[0] * time + p->accel[0] * time2; org[1] = p->org[1] + p->vel[1] * time + p->accel[1] * time2; org[2] = p->org[2] + p->vel[2] * time + p->accel[2] * time2; // gravity modulation //if (!p->ignoreGrav) // org[2] += time2 * -PARTICLE_GRAVITY; // collision test if (cl_particleCollision->integer) { if (p->bounceFactor) { trace_t trace; vec3_t vel; int hitTime; float time; trace = CL_Trace(p->oldOrg, org, 0, CONTENTS_SOLID); if (trace.fraction > 0 && trace.fraction < 1) { // reflect the velocity on the trace plane hitTime = cl.time - cls.rframetime + cls.rframetime * trace.fraction; time = ((float)hitTime - p->time) * 0.001; Vector3Set (vel, p->vel[0], p->vel[1], p->vel[2] + p->accel[2] * time * grav); VectorReflect (vel, trace.plane.normal, p->vel); VectorScale (p->vel, p->bounceFactor, p->vel); // check for stop, making sure that even on low FPS systems it doesn't bobble if (trace.allsolid || (trace.plane.normal[2] > 0 && (p->vel[2] < 40 || p->vel[2] < -cls.rframetime * p->vel[2]))) { VectorClear(p->vel); VectorClear(p->accel); p->bounceFactor = 0.0f; } VectorCopy (trace.endpos, org); // reset particle p->time = cl.time; VectorCopy (org, p->org); } } } // kill all particles in solid contents = CM_PointContents (org, 0); if (contents & MASK_SOLID) { CL_FreeParticle (p); continue; } // have this always below CL_FreeParticle(p); ! p->next = NULL; if (!tail) { active = tail = p; } else { tail->next = p; tail = p; } // add to scene V_AddParticle (org, color, alpha); // PMM if (p->alphavel == INSTANT_PARTICLE) { p->alphavel = 0.0; p->alpha = 0.0; } } active_particles = active; }
/* ================= RE_AddDecal Adds a single decal to the decal list ================= */ void RE_GL_AddDecal (vec3_t origin, vec3_t dir, vec4_t color, float size, int type, int flags, float angle) { int i, j, numfragments; vec3_t verts[MAX_DECAL_VERTS], shade, temp; markFragment_t *fr, fragments[MAX_FRAGMENTS_PER_DECAL]; vec3_t axis[3]; cdecal_t *d; float lightspot[3]; if (!gl_decals->value) return; // invalid decal size if (size <= 0) return; // a hack to produce decals from explosions etc if (VectorCompare(dir, vec3_origin)) { float scale = 1.5 * size; trace_t trace; vec3_t end, dirs[6] = { { 1.0, 0.0, 0.0 }, { -1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.0, 0.0, 1.0 }, { 0.0, 0.0, -1.0 } }; for (i = 0; i < 6; i++) { VectorMA(origin, scale, dirs[i], end); trace = CL_Trace(origin, end, 0, MASK_SOLID); if (trace.fraction != 1.0) RE_GL_AddDecal(origin, trace.plane.normal, color, size, type, flags, angle); } return; } // calculate orientation matrix VectorNormalize2(dir, axis[0]); PerpendicularVector(axis[1], axis[0]); RotatePointAroundVector(axis[2], axis[0], axis[1], angle); CrossProduct(axis[0], axis[2], axis[1]); // clip it against the world numfragments = R_GetClippedFragments(origin, axis, size, MAX_DECAL_VERTS, verts, MAX_FRAGMENTS_PER_DECAL, fragments); if (!numfragments) return; // no valid fragments // store out vertex data size = 0.5f / size; VectorScale(axis[1], size, axis[1]); VectorScale(axis[2], size, axis[2]); for (i = 0, fr = fragments; i < numfragments; i++, fr++) { // check if we have hit the max if (fr->numPoints > MAX_DECAL_VERTS) fr->numPoints = MAX_DECAL_VERTS; else if (fr->numPoints <= 0) continue; d = R_AllocDecal(); d->time = r_newrefdef.time; d->node = fr->node; VectorCopy(fr->surf->plane->normal, d->direction); if (!(fr->surf->flags & SURF_PLANEBACK)) VectorNegate(d->direction, d->direction); // reverse direction Vector4Set(d->color, color[0], color[1], color[2], color[3]); VectorCopy(origin, d->org); //if (flags & DF_SHADE) { R_LightPoint(origin, shade, lightspot); for (j = 0; j < 3; j++) d->color[j] = (d->color[j] * shade[j] * 0.6) + (d->color[j] * 0.4); } d->type = type; d->flags = flags; // make the decal vert d->numverts = fr->numPoints; for (j = 0; j < fr->numPoints && j < MAX_VERTS_PER_FRAGMENT; j++) { // xyz VectorCopy(verts[fr->firstPoint + j], d->verts[j]); // st VectorSubtract(d->verts[j], origin, temp); d->stcoords[j][0] = DotProduct(temp, axis[1]) + 0.5f; d->stcoords[j][1] = DotProduct(temp, axis[2]) + 0.5f; } } }
/** * @brief Updates weather for the time passed; handles particle creation/removal automatically */ void Weather::update (int milliseconds) { /* Don't play the weather particles if the user doesn't want them */ if (!Cvar_GetInteger("cl_particleweather")) { /* This makes weather look very weird if it is enabled mid-battle */ /* clearParticles(); */ return; } size_t dead = 0; /* physics: check for ttl and move live particles */ for (size_t i = 0; i < Weather::MAX_PARTICLES; i++) { Weather::particle &prt = particles[i]; if (prt.ttl < 0) { dead++; continue; } else { /** @todo creates vanishing-before-impact particles at low framerates -- should improve that somehow */ int restOfLife = prt.ttl -= milliseconds; if (restOfLife < 0) { if (fabs(prt.vz) < 0.001f || splashTime < 1) { /* either no splash or is a splash particle dying */ dead++; continue; } /* convert it into splash particle */ /* technically, we should complete the last frame of movement, but with current particle types it looks good even without it */ prt.vz = 0; prt.vx = 0; prt.vy = 0; prt.ttl += splashTime; } } /* if we got so far, particle is alive and probably needs a physics update */ const int timeAfterUpdate = prt.timeout -= milliseconds; const float moveDuration = milliseconds * 0.001f; prt.x += prt.vx * moveDuration; prt.y += prt.vy * moveDuration; prt.z += prt.vz * moveDuration; if (timeAfterUpdate > 0) { /* just move linearly */ continue; } /* alter motion vector */ /** @todo */ } /* create new ones in place of dead */ if (dead) { const float windX = cos(windDirection) * (windStrength + crand() * windTurbulence); const float windY = sin(windDirection) * (windStrength + crand() * windTurbulence); AABB weatherZone; /** < extents of zone in which weather particles will be rendered */ weatherZone = cl.mapData->mapBox; weatherZone.expandXY(256); weatherZone.maxs[2] += 512; Line prtPath; if (dead > 200) dead = 200; /* avoid creating too much particles in the single frame, it could kill the low-end hardware */ int debugCreated = 0; for (size_t i = 0; dead && i < Weather::MAX_PARTICLES && i < weatherStrength * Weather::MAX_PARTICLES; i++) { Weather::particle &prt = particles[i]; if (prt.ttl >= 0) continue; prt.x = (frand() * (weatherZone.getMaxX() - weatherZone.getMinX())) + weatherZone.getMinX(); prt.y = (frand() * (weatherZone.getMaxY() - weatherZone.getMinY())) + weatherZone.getMinY(); prt.z = weatherZone.getMaxZ(); prt.vx = windX; prt.vy = windY; prt.vz = -fallingSpeed * (frand() * 0.2f + 0.9f); float lifeTime = (weatherZone.getMaxZ() - weatherZone.getMinZ()) / -prt.vz; /* default */ VectorSet(prtPath.start, prt.x, prt.y, prt.z); VectorSet(prtPath.stop, prt.x + prt.vx * lifeTime, prt.y + prt.vy * lifeTime, prt.z + prt.vz * lifeTime); trace_t trace = CL_Trace(prtPath, AABB::EMPTY, nullptr, nullptr, MASK_SOLID, cl.mapMaxLevel - 1); /* find the collision point */ lifeTime *= trace.fraction; prt.ttl = 1000 * lifeTime; /* convert to milliseconds */ prt.timeout = prt.ttl + 1000000; /** @todo proper code for physics */ debugCreated++; dead--; } } #if 0 if (debugCreated) Com_Printf("created %i weather particles\n", debugCreated); #endif }