openmohaa/code/cgame/cg_volumetricsmoke.cpp

1370 lines
43 KiB
C++

/*
===========================================================================
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;
}
}