/* =========================================================================== Copyright (C) 2023 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code 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. OpenMoHAA source code 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 GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // DESCRIPTION: // Volumetric smoke A.K.A VSS sources #include "cg_local.h" #include "cg_commands.h" #include "memarchiver.h" const char *cg_vsstypes[] = { "default", "gun", "bulletimpact", "bulletdirtimpact", "heavy", "steam", "mist", "smokegrenade", "grenade", "fire", "greasefire", "debris" }; #define MAX_VSS_SORTS 16384 cvssource_t *vss_sorttable[MAX_VSS_SORTS]; static int lastVSSFrameTime; static constexpr float MAX_VSS_COORDS = 8096.0; static constexpr float MAX_VSS_WIND_DIST = 512; static constexpr float MAX_VSS_WIND_DIST_SQUARED = MAX_VSS_WIND_DIST * MAX_VSS_WIND_DIST; extern cvar_t *cg_detail; extern cvar_t *cg_effectdetail; cvar_t *vss_draw; cvar_t *vss_physics_fps; cvar_t *vss_repulsion_fps; cvar_t *vss_maxcount; cvar_t *vss_color; cvar_t *vss_showsources; cvar_t *vss_wind_x; cvar_t *vss_wind_y; cvar_t *vss_wind_z; cvar_t *vss_wind_strength; cvar_t *vss_movement_dampen; cvar_t *vss_maxvisible; cvar_t *vss_gridsize; cvar_t *vss_default_r; cvar_t *vss_default_g; cvar_t *vss_default_b; cvar_t *vss_lighting_fps; void VSS_ClampAlphaLife(cvssource_t *pSource, int maxlife); void VSS_AddRepulsion(cvssource_t *pA, cvssource_t *pB) { vec3_t vPush; float fDist, fForce, f; VectorSubtract(pA->newOrigin, pB->newOrigin, vPush); if (!vPush[0] && !vPush[1] && !vPush[2]) { VectorSet(vPush, crandom(), crandom(), crandom()); VectorAdd(pA->repulsion, vPush, pA->repulsion); VectorSubtract(pB->repulsion, vPush, pB->repulsion); return; } fDist = VectorNormalize(vPush); f = fDist - pB->newRadius; if (f > 0.0f) { f *= pA->ooRadius; if (f > 1.49f) { f = 0.0f; } else { f = f * (f * 0.0161f + -0.3104f) + 1.2887f; } if (f < 0.0) { f *= 1.1f; } fForce = f; } else { fForce = 1.0; } f = fDist - pA->newRadius; if (f > 0.0) { f *= pB->ooRadius; if (f > 1.49f) { f = 0.0f; } else { f = f * (f * 0.0161f + -0.3104f) + 1.2887f; } if (f < 0.0) { f *= 1.1f; } fForce += f; } else { fForce += 1.0f; } if (fForce <= -0.05f || fForce >= 0.05f) { fForce = (pA->newRadius + pB->newRadius) * 0.03f * fForce; VectorScale(vPush, fForce, vPush); VectorAdd(pA->repulsion, vPush, pA->repulsion); VectorSubtract(pB->repulsion, vPush, pB->repulsion); } } cvssource_t *ClientGameCommandManager::AllocateVSSSource() { cvssource_t *pNew; if (!m_free_vsssources) { FreeVSSSource(m_active_vsssources.prev); } pNew = m_free_vsssources; m_free_vsssources = m_free_vsssources->next; memset(pNew, 0, sizeof(cvssource_t)); pNew->next = m_active_vsssources.next; pNew->prev = &m_active_vsssources; m_active_vsssources.next->prev = pNew; m_active_vsssources.next = pNew; return pNew; } void ClientGameCommandManager::FreeVSSSource(cvssource_t *p) { if (!p->prev) { cgi.Error(ERR_DROP, "CCM::FreeVSSSource: not active"); } p->prev->next = p->next; p->next->prev = p->prev; p->next = m_free_vsssources; m_free_vsssources = p; } void ClientGameCommandManager::ResetVSSSources() { int i; cvssource_t *p; cvssource_t *next; vss_maxvisible = cgi.Cvar_Get("vss_maxvisible", "1024", CVAR_ARCHIVE | CVAR_LATCH); if (m_iAllocatedvsssources && m_iAllocatedvsssources == vss_maxvisible->integer) { // free existing vss sources for (p = m_active_vsssources.prev; p != &m_active_vsssources; p = next) { next = p->prev; FreeVSSSource(p); } return; } if (m_iAllocatedvsssources) { cgi.Free(m_vsssources); } if (vss_maxvisible->integer >= 128) { m_iAllocatedvsssources = vss_maxvisible->integer; } else { m_iAllocatedvsssources = 128; } m_vsssources = (cvssource_t *)cgi.Malloc(sizeof(cvssource_t) * m_iAllocatedvsssources); memset(m_vsssources, 0, sizeof(cvssource_t) * m_iAllocatedvsssources); m_active_vsssources.next = &m_active_vsssources; m_active_vsssources.prev = &m_active_vsssources; m_free_vsssources = m_vsssources; for (i = 0; i < m_iAllocatedvsssources - 1; ++i) { m_vsssources[i].next = &m_vsssources[i + 1]; } m_vsssources[m_iAllocatedvsssources - 1].next = NULL; } void ClientGameCommandManager::ResetVSSSources(Event *ev) { ResetVSSSources(); } void CG_ResetVSSSources() { commandManager.ResetVSSSources(); lastVSSFrameTime = cg.time; } void CG_ArchiveVSSGlobals(MemArchiver& archiver) { archiver.ArchiveTime(&lastVSSFrameTime); } void ClientGameCommandManager::InitializeVSSSources() { int i; vss_maxvisible = cgi.Cvar_Get("vss_maxvisible", "1024", CVAR_ARCHIVE | CVAR_LATCH); if (m_iAllocatedvsssources && m_iAllocatedvsssources == vss_maxvisible->integer) { // already allocated return; } if (m_iAllocatedvsssources) { cgi.Free(m_vsssources); } if (vss_maxvisible->integer >= 128) { m_iAllocatedvsssources = vss_maxvisible->integer; } else { m_iAllocatedvsssources = 128; } m_vsssources = (cvssource_t *)cgi.Malloc(sizeof(cvssource_t) * m_iAllocatedvsssources); memset(m_vsssources, 0, sizeof(sizeof(cvssource_t) * m_iAllocatedvsssources)); m_active_vsssources.next = &m_active_vsssources; m_active_vsssources.prev = &m_active_vsssources; m_free_vsssources = m_vsssources; for (i = 0; i < m_iAllocatedvsssources - 1; ++i) { m_vsssources[i].next = &m_vsssources[i + 1]; } m_vsssources[m_iAllocatedvsssources - 1].next = NULL; } void ClientGameCommandManager::InitializeVSSCvars() { vss_draw = cgi.Cvar_Get("vss_draw", "0", CVAR_ARCHIVE); vss_physics_fps = cgi.Cvar_Get("vss_physics_fps", "8", 0); vss_repulsion_fps = cgi.Cvar_Get("vss_repulsion_fps", "4", 0); vss_maxcount = cgi.Cvar_Get("vss_maxcount", "22", CVAR_ARCHIVE); vss_color = cgi.Cvar_Get("vss_color", "1", 0); vss_showsources = cgi.Cvar_Get("vss_showsources", "1", 0); vss_wind_x = cgi.Cvar_Get("vss_wind_x", "8", 0); vss_wind_y = cgi.Cvar_Get("vss_wind_y", "4", 0); vss_wind_z = cgi.Cvar_Get("vss_wind_z", "2", 0); vss_wind_strength = cgi.Cvar_Get("vss_wind_strength", "8", 0); vss_movement_dampen = cgi.Cvar_Get("vss_movement_dampen", "4", 0); vss_maxvisible = cgi.Cvar_Get("vss_maxvisible", "1024", 33); vss_gridsize = cgi.Cvar_Get("vss_gridsize", "12", 0); vss_default_r = cgi.Cvar_Get("vss_default_r", "0.5", 0); vss_default_g = cgi.Cvar_Get("vss_default_g", "0.45", 0); vss_default_b = cgi.Cvar_Get("vss_default_b", "0.4", 0); vss_lighting_fps = cgi.Cvar_Get("vss_lighting_fps", "15", 0); } qboolean VSS_SourcePhysics(cvssource_t *pSource, float ftime) { int i; int iSmokeType; float fWind; vec3_t vVel, vDelta; trace_t trace; entityState_t *pEntState; fWind = 0.0; if ((pSource->flags2 & (T2_ACCEL | T2_MOVE)) != 0) { VectorMA(pSource->velocity, ftime, pSource->repulsion, pSource->velocity); } pSource->lastOrigin = pSource->newOrigin; if (pSource->flags & T_COLLISION) { trace.allsolid = qfalse; CG_ClipMoveToEntities( pSource->newOrigin, vec3_origin, vec3_origin, pSource->newOrigin, -1, MASK_VOLUMETRIC_SMOKE, &trace, qfalse ); if (trace.allsolid) { vec3_t vMins, vMaxs; pEntState = &cg_entities[trace.entityNum].currentState; IntegerToBoundingBox(pEntState->solid, vMins, vMaxs); for (i = 0; i < 3; i++) { vDelta[i] = pSource->newOrigin[i] - ((vMins[i] + vMaxs[i]) * 0.5 + pEntState->origin[i]); } VectorNormalizeFast(vDelta); pSource->velocity = Vector(vDelta) * 16.0; } } if (pSource->flags2 & (T2_ACCEL | T2_MOVE)) { VectorMA(pSource->newOrigin, ftime, pSource->velocity, pSource->newOrigin); } if (pSource->flags & T_COLLISION) { CG_Trace( &trace, pSource->lastOrigin, vec3_origin, vec3_origin, pSource->newOrigin, -1, MASK_VOLUMETRIC_SMOKE, qfalse, qfalse, "Collision" ); if (trace.fraction != 1.0) { float fDot; vec3_t vNorm; VectorCopy(trace.plane.normal, vNorm); VectorAdd(trace.endpos, trace.plane.normal, pSource->newOrigin); fDot = DotProduct(vNorm, pSource->velocity); VectorMA(pSource->velocity, fDot, vNorm, pSource->velocity); if (vNorm[2] > 0.7) { VectorMA(pSource->velocity, ftime * -0.2, pSource->velocity, pSource->velocity); } iSmokeType = abs(pSource->smokeType); if (iSmokeType >= 3 && iSmokeType <= 4) { if (vNorm[2] > 0.7) { pSource->newDensity -= ftime * 0.08; } } } } if (pSource->newOrigin[0] < -MAX_VSS_COORDS || pSource->newOrigin[0] > MAX_VSS_COORDS || pSource->newOrigin[1] < -MAX_VSS_COORDS || pSource->newOrigin[1] > MAX_VSS_COORDS || pSource->newOrigin[2] < -MAX_VSS_COORDS || pSource->newOrigin[2] > MAX_VSS_COORDS) { return qfalse; } iSmokeType = abs(pSource->smokeType); if (pSource->flags2 & (T2_ACCEL | T2_MOVE)) { VectorCopy(pSource->velocity, vVel); for (i = 0; i < 3; i++) { switch (i) { case 0: fWind = vss_wind_x->value; break; case 1: fWind = vss_wind_y->value; break; case 2: fWind = vss_wind_z->value; break; } if (fWind < 0.0) { if (vVel[i] > fWind) { vVel[i] -= ftime * vss_wind_strength->value; if (vVel[i] > fWind) { vVel[i] = fWind; } } else { vVel[i] += ftime * vss_movement_dampen->value; if (vVel[i] < fWind) { vVel[i] = fWind; } } } else if (vVel[i] > fWind) { vVel[i] -= ftime * vss_movement_dampen->value; if (vVel[i] < fWind) { vVel[i] = fWind; } } else { vVel[i] += ftime * vss_movement_dampen->value; if (vVel[i] > fWind) { vVel[i] = fWind; } } } switch (iSmokeType) { case 3: if (vVel[2] > -8.0) { vVel[2] -= ftime * 8.0; } break; case 4: if (vVel[2] > -5.0) { vVel[2] -= ftime * 3.0; } break; case 5: if (vVel[2] < 256.0) { vVel[2] += ftime * 40.0; } break; case 6: if (vVel[2] > -25.0) { vVel[2] -= ftime * 10.0; } break; case 7: if (vVel[2] > -10.0) { vVel[2] -= ftime * 4.0; } break; case 9: case 10: if (pSource->typeInfo > 8.0) { if (vVel[2] < pSource->typeInfo) { vVel[2] += ftime * pSource->typeInfo; } pSource->typeInfo -= ftime * pSource->typeInfo * 0.04; if (pSource->typeInfo < 10.0) { pSource->typeInfo = 10.0; } } break; case 11: if (vVel[2] > -800.0) { vVel[2] -= ftime * 300.0; } break; } fWind = VectorLengthSquared(vVel); if (fWind > MAX_VSS_WIND_DIST_SQUARED) { VectorNormalizeFast(vVel); VectorScale(vVel, MAX_VSS_WIND_DIST, vVel); } pSource->velocity = vVel; } pSource->lastRadius = pSource->newRadius; switch (iSmokeType) { case 1: pSource->newRadius += ftime * 1.2 * pSource->scaleMult; break; case 2: pSource->newRadius += ftime * 0.7 * pSource->scaleMult; break; case 3: if ((double)pSource->lifeTime >= 1.0) { pSource->newRadius += ftime * 0.7 * pSource->scaleMult; } else { pSource->newRadius += ftime * 1.2 * pSource->scaleMult; } break; case 4: pSource->newRadius += ftime * 1.2 * pSource->scaleMult; break; case 5: pSource->newRadius += ftime * 5.0 * pSource->scaleMult; break; case 6: pSource->newRadius += ftime * 0.8 * pSource->scaleMult; break; case 7: if (pSource->newRadius >= 24.0) { pSource->newRadius += ftime * 0.4 * pSource->scaleMult; } else { pSource->newRadius += ftime * 1.6 * pSource->scaleMult; } break; case 8: if (pSource->newRadius >= 24.0) { pSource->newRadius += ftime * 0.4 * pSource->scaleMult; } else { pSource->newRadius += ftime * 1.6 * pSource->scaleMult; } break; case 9: case 10: if (pSource->newRadius >= 16.0) { pSource->newRadius += ftime * 0.4 * pSource->scaleMult; } else { pSource->newRadius += ftime * 0.8 * pSource->scaleMult; } break; case 11: pSource->newRadius += ftime * 0.8 * pSource->scaleMult; break; default: pSource->newRadius += ftime * 1.2 * pSource->scaleMult; break; } if (pSource->newRadius < 1.0) { pSource->newRadius = 1.0; } else if (pSource->newRadius > 32.0) { pSource->newRadius = 32.0; } pSource->ooRadius = 1.0 / pSource->newRadius; pSource->lastDensity = pSource->newDensity; if (pSource->smokeType >= 0) { switch (iSmokeType) { case 1: pSource->newDensity -= ftime * 0.07 * pSource->fadeMult; break; case 2: pSource->newDensity -= ftime * 0.075 * pSource->fadeMult; break; case 3: if (pSource->newDensity > 0.6) { pSource->newDensity -= ftime * 0.05 * pSource->fadeMult; } else { pSource->newDensity -= ftime * 0.4 * pSource->fadeMult; } break; case 4: pSource->newDensity -= ftime * 0.0080000004 * pSource->fadeMult; break; case 5: pSource->newDensity -= ftime * 0.75 * pSource->fadeMult; break; case 6: pSource->newDensity -= ftime * 0.016000001 * pSource->fadeMult; break; case 7: pSource->newDensity -= ftime * 0.0049999999 * pSource->fadeMult; break; case 8: if (pSource->newDensity > 0.7) { pSource->newDensity -= ftime * 0.025 * pSource->fadeMult; } else { pSource->newDensity -= ftime * 0.38 * pSource->fadeMult; } break; case 11: pSource->newDensity = pSource->newDensity - ftime * 0.125 * pSource->fadeMult; break; default: if (pSource->newDensity > 0.4) { pSource->newDensity -= ftime * 0.01 * pSource->fadeMult; } else { pSource->newDensity -= ftime * 0.0075 * pSource->fadeMult; } break; } if (pSource->newDensity <= 0.06) { return 0; } } else { switch (iSmokeType) { case 3: VSS_ClampAlphaLife(pSource, 150); break; case 4: VSS_ClampAlphaLife(pSource, 200); break; case 5: case 11: VSS_ClampAlphaLife(pSource, 50); break; case 6: case 8: VSS_ClampAlphaLife(pSource, 600); break; case 7: VSS_ClampAlphaLife(pSource, 800); break; case 9: case 10: VSS_ClampAlphaLife(pSource, 1500); break; default: VSS_ClampAlphaLife(pSource, 100); break; } } VectorCopy(pSource->newColor, pSource->lastColor); if (iSmokeType == 1) { for (i = 0; i < 3; ++i) { pSource->newColor[i] -= ftime * 0.05f * pSource->fadeMult; if (pSource->newColor[i] < 0.0f) { pSource->newColor[i] = 0.0f; } } } else if (iSmokeType == 9) { for (i = 0; i < 3; ++i) { if (pSource->newColor[i] < 0.9f) { pSource->newColor[i] += ftime * 0.02f * pSource->fadeMult; if (pSource->newColor[i] > 0.9f) { pSource->newColor[i] = 0.9f; } } } } return qtrue; } qboolean VSS_LerpSource(cvssource_t *pCurrent, cvssourcestate_t *pState, float fLerpFrac, float fLightingFrac) { int i; if (pCurrent->flags & (T_HARDLINK | T_PARENTLINK)) { Vector parentOrigin; for (i = 0; i < 3; i++) { pState->origin[i] = (pCurrent->newOrigin[i] - pCurrent->lastOrigin[i]) * fLerpFrac + pCurrent->lastOrigin[i]; } if (!cg_entities[pCurrent->parent].currentValid) { return qfalse; } refEntity_t *e = cgi.R_GetRenderEntity(pCurrent->parent); if (!e) { return qfalse; } parentOrigin = e->origin; VectorAdd(pState->origin, parentOrigin, pState->origin); } else if (pCurrent->flags2 & (T2_ACCEL | T2_MOVE)) { for (i = 0; i < 3; i++) { pState->origin[i] = (pCurrent->newOrigin[i] - pCurrent->lastOrigin[i]) * fLerpFrac + pCurrent->lastOrigin[i]; } } if (vss_color->integer) { for (i = 0; i < 3; ++i) { pState->color[i] = (pCurrent->newColor[i] - pCurrent->lastColor[i]) * fLerpFrac + pCurrent->lastColor[i]; } } if (vss_lighting_fps->integer) { for (i = 0; i < 3; ++i) { pState->color[i] = ((pCurrent->newLighting[i] - pCurrent->lastLighting[i]) * fLightingFrac + pCurrent->lastLighting[i]) * pState->color[i]; } } pState->density = (pCurrent->newDensity - pCurrent->lastDensity) * fLerpFrac + pCurrent->lastDensity; pState->radius = (pCurrent->newRadius - pCurrent->lastRadius) * fLerpFrac + pCurrent->lastRadius; return qtrue; } void ClientGameCommandManager::SpawnVSSSource(int count, int timealive) { int i; int iSmokeLeft, iSmokeType; float fSmokeTypeDataValue = 0.0; float fFadeMult = 0.0, fScaleMult = 0.0; float fCountScale; float fDensity, fRadius; float fAngle = 0.0, fAngleStep = 0.0; Vector vNewForward; str sSmokeName; cvssource_t *pSource; if (m_spawnthing->cgd.alpha <= 0.0) { return; } fDensity = this->m_spawnthing->cgd.alpha; fRadius = this->m_spawnthing->cgd.scale * vss_maxcount->value * 0.1; if (fRadius > 32.0) { fRadius = 32.0; } if (m_spawnthing->cgd.flags & T_CIRCLE) { fAngle = 0.0; fAngleStep = 360.0 / (count / vss_maxcount->value); } iSmokeType = 0; sSmokeName = m_spawnthing->GetModel(); for (i = 0; i < 12; ++i) { if (sSmokeName == cg_vsstypes[i]) { iSmokeType = i; if (i < 9 || i > 10) { fSmokeTypeDataValue = m_spawnthing->cgd.accel[0]; } else { fSmokeTypeDataValue = m_spawnthing->cgd.accel[0]; if (fSmokeTypeDataValue == 0.0) { fSmokeTypeDataValue = 24.0; } } break; } } iSmokeType = -iSmokeType; fFadeMult = m_spawnthing->cgd.accel[1]; if (fFadeMult < 0.0001) { fFadeMult = 1.0; } fScaleMult = m_spawnthing->cgd.accel[2]; if (!fScaleMult) { fScaleMult = 1.0; } fCountScale = m_spawnthing->cgd.life / 1000; if (count * fCountScale < vss_maxcount->value) { iSmokeLeft = count * fCountScale; } else { iSmokeLeft = (int)(fCountScale * count * cg_effectdetail->value); if (iSmokeLeft < vss_maxcount->value) { iSmokeLeft = (int)vss_maxcount->value; } } while (iSmokeLeft > 0) { pSource = AllocateVSSSource(); if (!pSource) { cgi.DPrintf("Out of VSS Sources\n"); return; } pSource->startAlpha = (random() * 0.15 + 0.85) * fDensity; pSource->newDensity = 0.0; if (m_spawnthing->cgd.flags & T_RANDSCALE) { pSource->newRadius = RandomizeRange(m_spawnthing->cgd.scalemin, m_spawnthing->cgd.scalemax); if (pSource->newRadius > 32.0) { pSource->newRadius = 32.0; } } else { pSource->newRadius = fRadius; } if (iSmokeLeft < vss_maxcount->value) { pSource->newRadius = iSmokeLeft / vss_maxcount->value * pSource->newRadius; } if (vss_color->value) { float fRandom = random() * 0.3 + 0.7; pSource->newColor[0] = fRandom * m_spawnthing->cgd.color[0]; pSource->newColor[1] = fRandom * m_spawnthing->cgd.color[1]; pSource->newColor[2] = fRandom * m_spawnthing->cgd.color[2]; } pSource->parent = m_spawnthing->cgd.parent; pSource->flags = m_spawnthing->cgd.flags; pSource->flags2 = m_spawnthing->cgd.flags2; pSource->smokeType = iSmokeType; pSource->typeInfo = fSmokeTypeDataValue; pSource->fadeMult = fFadeMult; pSource->scaleMult = fScaleMult; pSource->roll = anglemod(fAngle); if (random() < 0.5) { pSource->flags |= T_RANDOMROLL; } VectorCopy(m_spawnthing->axis[0], vNewForward); if (m_spawnthing->cgd.flags & T_SPHERE) { VectorCopy(m_spawnthing->cgd.origin, pSource->newOrigin); do { vNewForward = Vector(crandom(), crandom(), crandom()); } while (Vector::Dot(vNewForward, vNewForward) < 1.0); } else if (m_spawnthing->cgd.flags & T_CIRCLE) { if (m_spawnthing->sphereRadius != 0.0) { Vector dst, end; end = m_spawnthing->axis[0]; RotatePointAroundVector(dst, m_spawnthing->axis[2], end, fAngle); VectorAdd(m_spawnthing->cgd.origin, dst, pSource->newOrigin); VectorSubtract(pSource->newOrigin, m_spawnthing->cgd.origin, vNewForward); VectorNormalizeFast(vNewForward); fAngle += fAngleStep; } } else if (m_spawnthing->cgd.flags & T_INWARDSPHERE) { Vector dir, end; do { dir = Vector(crandom(), crandom(), crandom()); } while (Vector::Dot(dir, dir) < 1.0); end = m_spawnthing->cgd.origin + dir * m_spawnthing->sphereRadius; VectorCopy(end, pSource->newOrigin); vNewForward = dir * -1.0; } else if (m_spawnthing->cgd.flags2 & T2_CONE) { float fHeight, fRadius; float fAngle; float sina, cosa; fHeight = random(); fRadius = random(); if (fHeight < fRadius) { float fTemp = fHeight; fHeight = fRadius; fRadius = fTemp; } fHeight *= m_spawnthing->coneHeight; fRadius *= m_spawnthing->sphereRadius; fAngle = random() * 6.2831855; cosa = cos(fAngle); sina = sin(fAngle); VectorMA(m_spawnthing->cgd.origin, fHeight, m_spawnthing->axis[0], pSource->newOrigin); VectorMA(m_spawnthing->cgd.origin, fRadius * cosa, m_spawnthing->axis[1], pSource->newOrigin); VectorMA(m_spawnthing->cgd.origin, fRadius * sina, m_spawnthing->axis[2], pSource->newOrigin); } else if (m_spawnthing->sphereRadius) { Vector dir, end; do { dir = Vector(crandom(), crandom(), crandom()); } while (Vector::Dot(dir, dir) < 1.0); dir.normalize(); end = m_spawnthing->cgd.origin + dir * m_spawnthing->sphereRadius; VectorCopy(end, pSource->newOrigin); vNewForward = dir; } else { VectorCopy(m_spawnthing->cgd.origin, pSource->newOrigin); } for (i = 0; i < 3; i++) { pSource->newOrigin[i] += random() * m_spawnthing->origin_offset_base[i] + m_spawnthing->origin_offset_amplitude[i]; } VectorCopy(pSource->newOrigin, pSource->lastOrigin); if (timealive > 0) { pSource->lifeTime = timealive; } else { pSource->lifeTime = 0; } if (m_spawnthing->forwardVelocity) { for (i = 0; i < 3; ++i) { pSource->velocity[i] = vNewForward[i] * m_spawnthing->forwardVelocity; } } for (i = 0; i < 3; ++i) { float fVel = m_spawnthing->randvel_base[i] + random() * m_spawnthing->randvel_amplitude[i]; if (m_spawnthing->cgd.flags & T_RANDVELAXIS) { pSource->velocity += Vector(m_spawnthing->tag_axis[i]) * fVel; } else { pSource->velocity[i] += fVel; } } for (i = 0; i < 3; ++i) { float fDist = m_spawnthing->axis_offset_base[i] + random() * m_spawnthing->axis_offset_amplitude[i]; if (pSource->flags2 & T2_PARALLEL) { pSource->newOrigin += Vector(m_spawnthing->axis[i]) * fDist; } else { pSource->newOrigin += Vector(m_spawnthing->tag_axis[i]) * fDist; } } pSource->newOrigin += pSource->velocity * (pSource->lifeTime / 1000.0); if (vss_lighting_fps->integer) { cgi.R_GetLightingForSmoke(pSource->newLighting, pSource->newOrigin); } iSmokeLeft -= vss_maxcount->value; } } void VSS_CalcRepulsionForces(cvssource_t *pActiveSources) { cvssource_t *pCurrent; cvssource_t *pComp; qboolean bXUp, bXDown; qboolean bYUp, bYDown; qboolean bZDown; int i; int iIndex; int iX, iY, iZ; int iMinX, iMinY, iMinZ; int iMaxX, iMaxY, iMaxZ; float fOfs; cvssource_t *pSTLatch; pCurrent = pActiveSources->prev; if (pCurrent == pActiveSources) { return; } memset(vss_sorttable, 0, sizeof(vss_sorttable)); for (pCurrent = pActiveSources->prev; pCurrent != pActiveSources; pCurrent = pCurrent->prev) { VectorClear(pCurrent->repulsion); iIndex = ((int)floor(pCurrent->newOrigin[0] + 8192.0 + 0.5) / 96) % 32; iIndex |= (((int)floor(pCurrent->newOrigin[1] + 8192.0 + 0.5) / 96) % 32) << 5; iIndex |= (((int)floor(pCurrent->newOrigin[2] + 8192.0 + 0.5) / 96) % 16) << 10; pCurrent->stnext = vss_sorttable[iIndex]; vss_sorttable[iIndex] = pCurrent; pCurrent->stindex = iIndex; } for (pCurrent = pActiveSources->prev; pCurrent != pActiveSources; pCurrent = pCurrent->prev) { if (vss_sorttable[pCurrent->stindex] == pCurrent) { pSTLatch = (cvssource_t *)-1; pComp = pCurrent->stnext; } else { pSTLatch = 0; pComp = vss_sorttable[pCurrent->stindex]; } for(; pComp; pComp = pComp->stnext) { VSS_AddRepulsion(pCurrent, pComp); if (!pSTLatch && pComp->stnext == pCurrent) { pSTLatch = pComp; // skip current pComp = pComp->stnext; } } iX = ((int)floor(pCurrent->newOrigin[0] + 8192.0 + 0.5) / 96) % 32; iY = (((int)floor(pCurrent->newOrigin[1] + 8192.0 + 0.5) / 96) % 32) << 5; iZ = (((int)floor(pCurrent->newOrigin[2] + 8192.0 + 0.5) / 96) % 16) << 10; fOfs = pCurrent->newRadius + 1.49 + 48.0; iMaxX = ((int)floor(pCurrent->newOrigin[0] + fOfs + 8192.0 + 0.5) / 96) % 32; iMaxY = (((int)floor(pCurrent->newOrigin[1] + fOfs + 8192.0 + 0.5) / 96) % 32) << 5; iMaxZ = (((int)floor(pCurrent->newOrigin[2] + fOfs + 8192.0 + 0.5) / 96) % 16) << 10; iMinX = ((int)floor(pCurrent->newOrigin[0] - fOfs + 8192.0 + 0.5) / 96) % 32; iMinY = (((int)floor(pCurrent->newOrigin[1] - fOfs + 8192.0 + 0.5) / 96) % 32) << 5; iMinZ = (((int)floor(pCurrent->newOrigin[2] - fOfs + 8192.0 + 0.5) / 96) % 16) << 10; bXUp = (iMaxX | (pCurrent->stindex & 0xFFFFFFE0)) != pCurrent->stindex; bXDown = (iMinX | (pCurrent->stindex & 0xFFFFFFE0)) != pCurrent->stindex; bYUp = (iMaxY | (pCurrent->stindex & 0xFFFFFC1F)) != pCurrent->stindex; bYDown = (iMinY | (pCurrent->stindex & 0xFFFFFC1F)) != pCurrent->stindex; iIndex = iMinZ | (pCurrent->stindex & 0xFFFFC3FF); bZDown = iIndex != pCurrent->stindex; if (iIndex == pCurrent->stindex) { iIndex = iMaxY | (pCurrent->stindex & 0xFFFFFC1F); i = 9; } else { i = 0; } for (; i < (bZDown ? 26 : 17); i++) { switch (i) { case 0: iIndex = iMaxZ | (pCurrent->stindex & 0xFFFFC3FF); break; case 1: iIndex = iMaxX | (iIndex & 0xFFFFFFE0); if (bXUp) { break; } continue; case 2: iIndex = iMaxY | (iIndex & 0xFFFFFC1F); if (bXUp && bYUp) { break; } continue; case 3: iIndex = iMinY | (iIndex & 0xFFFFFC1F); if (bXUp && bYDown) { break; } continue; case 4: iIndex = iMinY | (iIndex & 0xFFFFFFE0); if (bYDown) { break; } continue; case 5: iIndex = iMinX | (iIndex & 0xFFFFFFE0); if (bXDown && bYDown) { break; } continue; case 6: iIndex = iY | (iIndex & 0xFFFFFC1F); if (bXDown) { break; } continue; case 7: iIndex = iMaxY | (iIndex & 0xFFFFFC1F); if (bXDown && bYUp) { break; } continue; case 8: iIndex = iX | (iIndex & 0xFFFFFFE0); if (bYUp) { break; } continue; case 9: iIndex = iZ | (iIndex & 0xFFFFFFC3); if (bYUp) { break; } continue; case 10: iIndex = iMaxX | (iIndex & 0xFFFFFFE0); if (bXUp && bYUp) { break; } continue; case 11: iIndex = iMinX | (iIndex & 0xFFFFFFE0); if (bXDown && bYUp) { break; } continue; case 12: iIndex = iY | (iIndex & 0xFFFFFC1F); if (bXDown) { break; } continue; case 13: iIndex = iMinY | (iIndex & 0xFFFFFC1F); if (bXDown && bYDown) { break; } continue; case 14: iIndex = iX | (iIndex & 0xFFFFFFE0); if (bYDown) { break; } continue; case 15: iIndex = iMaxX | (iIndex & 0xFFFFFFE0); if (bXUp && bYDown) { break; } continue; case 16: iIndex = iY | (iIndex & 0xFFFFFC1F); if (bXUp) { break; } continue; case 17: iIndex = iMinZ | (iIndex & 0xFFFFFCC3); if (bXUp) { break; } continue; case 18: iIndex = iMaxY | (iIndex & 0xFFFFFC1F); if (bXUp && bYUp) { break; } continue; case 19: iIndex = iMinY | (iIndex & 0xFFFFFC1F); if (bXUp && bYDown) { break; } continue; case 20: iIndex = iX | (iIndex & 0xFFFFFFE0); if (bYDown) { break; } continue; case 21: iIndex = iMinX | (iIndex & 0xFFFFFFE0); if (bXDown && bYDown) { break; } continue; case 22: iIndex = iY | (iIndex & 0xFFFFFC1F); if (bXDown) { break; } continue; case 23: iIndex = iMaxY | (iIndex & 0xFFFFFC1F); if (bXDown && bYUp) { break; } continue; case 24: iIndex = iX | (iIndex & 0xFFFFFFE0); if (bYUp) { break; } continue; case 25: iIndex = iY | (iIndex & 0xFFFFFC1F); break; default: assert(0); // This can't happen break; } for (pComp = vss_sorttable[iIndex]; pComp; pComp = pComp->stnext) { VSS_AddRepulsion(pCurrent, pComp); } } if (pSTLatch == (cvssource_t *)-1) { vss_sorttable[pCurrent->stindex] = pCurrent->stnext; } else { pSTLatch->stnext = pCurrent->stnext; } } } void CG_AddVSSSources() { commandManager.AddVSSSources(); } void ClientGameCommandManager::AddVSSSources() { int i, j; int frameTime; int physics_rate, lighting_rate; int mstime; float fLerpFrac, fLightingFrac; vec3_t vAng; cvssource_t *pCurrent; cvssource_t *pComp; cvssourcestate_t state; int hModel, hModel2; refEntity_t newEnt; hModel = 0; hModel2 = 0; if (vss_showsources->integer) { // load sources hModel = cgi.R_RegisterModel("VSSSource.spr"); hModel2 = cgi.R_RegisterModel("VSSSource2.spr"); memset(&newEnt, 0, sizeof(newEnt)); memset(vAng, 0, sizeof(vAng)); AnglesToAxis(vAng, newEnt.axis); newEnt.renderfx = 0; newEnt.reType = RT_SPRITE; newEnt.shaderTime = 0.0; newEnt.frameInfo[0].index = 0; newEnt.frameInfo[0].weight = 1.0; newEnt.frameInfo[0].time = 0.0; newEnt.actionWeight = 1.0; } if (lastVSSFrameTime) { if (cg.time < lastVSSFrameTime || cg.time - lastVSSFrameTime > 500) { for (pCurrent = m_active_vsssources.prev; pCurrent != &m_active_vsssources; pCurrent = pCurrent->prev) { pCurrent->lastPhysicsTime = cg.time; pCurrent->lastLightingTime = cg.time; } m_iLastVSSRepulsionTime = cg.time; lastVSSFrameTime = cg.time; return; } frameTime = cg.time - lastVSSFrameTime; } else { frameTime = 0; } if (paused->integer) { lastVSSFrameTime = 0; } else { lastVSSFrameTime = cg.time; } if (lastVSSFrameTime) { if (cg.time >= m_iLastVSSRepulsionTime && cg.time - m_iLastVSSRepulsionTime <= 500) { if (cg.time - m_iLastVSSRepulsionTime >= 1000 / vss_repulsion_fps->integer) { VSS_CalcRepulsionForces(&m_active_vsssources); m_iLastVSSRepulsionTime = cg.time; } } else { m_iLastVSSRepulsionTime = cg.time; } } else { m_iLastVSSRepulsionTime = 0; } physics_rate = (int)(1000.0 / (float)vss_physics_fps->integer); lighting_rate = (int)(1000.0 / (float)vss_lighting_fps->integer); for (pCurrent = this->m_active_vsssources.prev; pCurrent != &this->m_active_vsssources; pCurrent = pComp) { pComp = pCurrent->prev; newEnt.renderfx = 0; if ((pCurrent->flags & T_DETAIL) && !cg_detail->integer) { FreeVSSSource(pCurrent); continue; } if ((pCurrent->flags2 & T2_ALWAYSDRAW) != 0) { newEnt.renderfx = RF_ALWAYSDRAW; } if (pCurrent->lastPhysicsTime) { mstime = cg.time - pCurrent->lastPhysicsTime; if (mstime > 2 * physics_rate) { mstime = physics_rate; } if (mstime >= physics_rate || (pCurrent->flags2 & T2_PHYSICS_EVERYFRAME) != 0) { if (!VSS_SourcePhysics(pCurrent, (float)mstime / 1000.0)) { FreeVSSSource(pCurrent); continue; } pCurrent->lastPhysicsTime = cg.time; } } if (pCurrent->lastLightingTime) { mstime = cg.time - pCurrent->lastLightingTime; if (mstime > 2 * lighting_rate) { mstime = lighting_rate; } if (mstime >= lighting_rate) { pCurrent->lastLighting[0] = pCurrent->newLighting[0]; pCurrent->lastLighting[1] = pCurrent->newLighting[1]; pCurrent->lastLighting[2] = pCurrent->newLighting[2]; cgi.R_GetLightingForSmoke(pCurrent->newLighting, pCurrent->newOrigin); pCurrent->lastLightingTime = cg.time; } } fLerpFrac = (float)(cg.time - pCurrent->lastPhysicsTime) / (float)physics_rate; fLerpFrac = Q_clamp_float(fLerpFrac, 0, 1); fLightingFrac = (float)(cg.time - pCurrent->lastLightingTime) / (float)lighting_rate; fLightingFrac = Q_clamp_float(fLightingFrac, 0, 1); if (lastVSSFrameTime) { pCurrent->lifeTime += frameTime; } if (!pCurrent->lastValid) { if (!VSS_SourcePhysics(pCurrent, (float)physics_rate / 1000)) { ClientGameCommandManager::FreeVSSSource(pCurrent); continue; } pCurrent->lastLighting[0] = pCurrent->newLighting[0]; pCurrent->lastLighting[1] = pCurrent->newLighting[1]; pCurrent->lastLighting[2] = pCurrent->newLighting[2]; cgi.R_GetLightingForSmoke(pCurrent->newLighting, pCurrent->newOrigin); fLerpFrac = 0.0; fLightingFrac = 0.0; pCurrent->lastPhysicsTime = cg.time; pCurrent->lastLightingTime = cg.time; pCurrent->lastValid = 1; } if (VSS_LerpSource(pCurrent, &state, fLerpFrac, fLightingFrac)) { if (vss_showsources->integer) { VectorCopy(state.origin, newEnt.origin); newEnt.scale = state.radius / 5.0; if (vss_color->integer) { newEnt.shaderRGBA[0] = (int)(state.color[0] * 255.0); newEnt.shaderRGBA[1] = (int)(state.color[1] * 255.0); newEnt.shaderRGBA[2] = (int)(state.color[2] * 255.0); } else { newEnt.shaderRGBA[0] = (int)(vss_default_r->value * 255.0); newEnt.shaderRGBA[1] = (int)(vss_default_g->value * 255.0); newEnt.shaderRGBA[2] = (int)(vss_default_b->value * 255.0); } newEnt.shaderRGBA[3] = (int)(state.density * 255.0); if (lastVSSFrameTime) { pCurrent->roll += frameTime; for (i = 0; i < 3; ++i) { j = (int)(frameTime * pCurrent->velocity[i] * 0.03); if (pCurrent->velocity[i] < 0.0) { j = -j; } if (j > frameTime) { j = (int)((float)frameTime + (float)(j - frameTime) * 0.75); if (j > 2 * frameTime) { j = 2 * frameTime; } } pCurrent->roll -= j; } if ((pCurrent->flags & T_RANDOMROLL) != 0) { newEnt.hModel = hModel; } else { newEnt.hModel = hModel2; } } else if ((pCurrent->flags & T_RANDOMROLL) != 0) { newEnt.hModel = hModel; } else { newEnt.hModel = hModel2; } newEnt.shaderTime = (float)(pCurrent->roll + cg.time - pCurrent->lifeTime) * 0.001; cgi.R_AddRefSpriteToScene(&newEnt); } } else { FreeVSSSource(pCurrent); } } if (vss_showsources->integer == 2) { i = 0; for (pCurrent = this->m_active_vsssources.prev; pCurrent != &this->m_active_vsssources; pCurrent = pCurrent->prev) { ++i; } cgi.DPrintf("VSS Sources In Use: %i\n", i); } } void VSS_ClampAlphaLife(cvssource_t *pSource, int maxlife) { if (pSource->lifeTime >= maxlife) { pSource->smokeType = -pSource->smokeType; pSource->newDensity = pSource->startAlpha; } else { pSource->newDensity = (float)pSource->lifeTime / (float)maxlife * pSource->startAlpha; } }