mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
529 lines
13 KiB
C++
529 lines
13 KiB
C++
// Copyright (C) 2007 Id Software, Inc.
|
|
//
|
|
|
|
#include "precompiled.h"
|
|
#include "BSE_Envelope.h"
|
|
#include "BSE_Particle.h"
|
|
#include "BSE.h"
|
|
|
|
|
|
namespace {
|
|
rvParticle* AllocateParticleArray(int particleType, int count) {
|
|
if (count <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
switch (particleType) {
|
|
case PTYPE_LINE:
|
|
return static_cast<rvParticle*>(new rvLineParticle[count]);
|
|
case PTYPE_ORIENTED:
|
|
return static_cast<rvParticle*>(new rvOrientedParticle[count]);
|
|
case PTYPE_DECAL:
|
|
return static_cast<rvParticle*>(new rvDecalParticle[count]);
|
|
case PTYPE_MODEL:
|
|
return static_cast<rvParticle*>(new rvModelParticle[count]);
|
|
case PTYPE_LIGHT:
|
|
return static_cast<rvParticle*>(new rvLightParticle[count]);
|
|
case PTYPE_ELECTRICITY:
|
|
return static_cast<rvParticle*>(new rvElectricityParticle[count]);
|
|
case PTYPE_LINKED:
|
|
return static_cast<rvParticle*>(new rvLinkedParticle[count]);
|
|
case PTYPE_ORIENTEDLINKED:
|
|
return static_cast<rvParticle*>(new sdOrientedLinkedParticle[count]);
|
|
case PTYPE_DEBRIS:
|
|
return static_cast<rvParticle*>(new rvDebrisParticle[count]);
|
|
default:
|
|
return static_cast<rvParticle*>(new rvSpriteParticle[count]);
|
|
}
|
|
}
|
|
|
|
ID_INLINE void InsertByEndTime(rvParticle*& head, rvParticle* particle) {
|
|
rvParticle* prev = NULL;
|
|
rvParticle* cur = head;
|
|
while (cur && particle->GetEndTime() > cur->GetEndTime()) {
|
|
prev = cur;
|
|
cur = cur->GetNext();
|
|
}
|
|
particle->SetNext(cur);
|
|
if (prev) {
|
|
prev->SetNext(particle);
|
|
}
|
|
else {
|
|
head = particle;
|
|
}
|
|
}
|
|
|
|
ID_INLINE int GetSegmentParticleCap() {
|
|
return idMath::ClampInt(0, MAX_PARTICLES, bse_maxParticles.GetInteger());
|
|
}
|
|
|
|
ID_INLINE bool BSESpawnTraceEnabled() {
|
|
return cvarSystem && cvarSystem->GetCVarInteger("bse_frameCounters") >= 2;
|
|
}
|
|
|
|
ID_INLINE void BSE_BoundTriSurf(srfTriangles_t* tri) {
|
|
if (!tri) {
|
|
return;
|
|
}
|
|
if (tri->numVerts <= 0 || !tri->verts) {
|
|
tri->bounds.Clear();
|
|
return;
|
|
}
|
|
tri->bounds.Clear();
|
|
for (int i = 0; i < tri->numVerts; ++i) {
|
|
tri->bounds.AddPoint(tri->verts[i].xyz);
|
|
}
|
|
}
|
|
|
|
void BSESpawnTrace(const char* fmt, ...) {
|
|
static int spawnTraceCount = 0;
|
|
if (spawnTraceCount >= 256) {
|
|
return;
|
|
}
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
common->VPrintf(fmt, ap);
|
|
va_end(ap);
|
|
++spawnTraceCount;
|
|
}
|
|
}
|
|
|
|
void rvSegment::PlayEffect(rvBSE* effect, rvSegmentTemplate* st, float depthOffset) {
|
|
if (!effect || !st || st->mNumEffects <= 0 || !game) {
|
|
return;
|
|
}
|
|
|
|
const int index = rvRandom::irand(0, st->mNumEffects - 1);
|
|
const rvDeclEffect* nested = st->mEffects[index];
|
|
if (!nested) {
|
|
return;
|
|
}
|
|
|
|
game->PlayEffect(
|
|
nested,
|
|
effect->GetCurrentOrigin(),
|
|
effect->GetCurrentAxis(),
|
|
false,
|
|
effect->GetHasEndOrigin() ? effect->GetCurrentEndOrigin() : vec3_origin,
|
|
false,
|
|
false,
|
|
EC_IGNORE,
|
|
vec4_one);
|
|
}
|
|
|
|
rvParticle* rvSegment::InitParticleArray(rvBSE* effect) {
|
|
if (!effect) {
|
|
return NULL;
|
|
}
|
|
|
|
if (mParticles) {
|
|
switch (mParticleType) {
|
|
case PTYPE_LINE:
|
|
delete[] static_cast<rvLineParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_ORIENTED:
|
|
delete[] static_cast<rvOrientedParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_DECAL:
|
|
delete[] static_cast<rvDecalParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_MODEL:
|
|
delete[] static_cast<rvModelParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_LIGHT:
|
|
delete[] static_cast<rvLightParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_ELECTRICITY:
|
|
delete[] static_cast<rvElectricityParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_LINKED:
|
|
delete[] static_cast<rvLinkedParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_ORIENTEDLINKED:
|
|
delete[] static_cast<sdOrientedLinkedParticle*>(mParticles);
|
|
break;
|
|
case PTYPE_DEBRIS:
|
|
delete[] static_cast<rvDebrisParticle*>(mParticles);
|
|
break;
|
|
default:
|
|
delete[] static_cast<rvSpriteParticle*>(mParticles);
|
|
break;
|
|
}
|
|
}
|
|
mParticles = NULL;
|
|
mFreeHead = NULL;
|
|
mUsedHead = NULL;
|
|
|
|
const rvSegmentTemplate* st = GetSegmentTemplate();
|
|
const int requestedCount = effect->GetLooping() ? mLoopParticleCount : mParticleCount;
|
|
int particleCount = requestedCount;
|
|
const int particleCap = GetSegmentParticleCap();
|
|
if (particleCount > particleCap) {
|
|
common->Warning("^4BSE:^1 '%s' exceeded particle cap (%d > %d), clamping", effect->GetDeclName(), particleCount, particleCap);
|
|
}
|
|
particleCount = idMath::ClampInt(0, particleCap, particleCount);
|
|
if (particleCount <= 0) {
|
|
if (BSESpawnTraceEnabled()) {
|
|
BSESpawnTrace(
|
|
"BSE spawn init: decl=%s segType=%d pType=%d handle=%d particleCount=%d loopCount=%d (clamped<=0)\n",
|
|
effect ? effect->GetDeclName() : "<null>",
|
|
st ? st->GetType() : -1,
|
|
st ? st->GetParticleTemplate()->GetType() : -1,
|
|
mSegmentTemplateHandle,
|
|
mParticleCount,
|
|
mLoopParticleCount);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
mParticles = AllocateParticleArray(mParticleType, particleCount);
|
|
if (!mParticles) {
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < particleCount - 1; ++i) {
|
|
rvParticle* cur = mParticles->GetArrayEntry(i);
|
|
rvParticle* next = mParticles->GetArrayEntry(i + 1);
|
|
cur->SetNext(next);
|
|
}
|
|
mParticles->GetArrayEntry(particleCount - 1)->SetNext(NULL);
|
|
|
|
mFreeHead = mParticles;
|
|
mUsedHead = NULL;
|
|
return mParticles;
|
|
}
|
|
|
|
rvParticle* rvSegment::GetFreeParticle(rvBSE* effect) {
|
|
rvSegmentTemplate* st = GetSegmentTemplate();
|
|
if (!st) {
|
|
return NULL;
|
|
}
|
|
|
|
// Decal/effect paths can request a spawn before explicit segment particle
|
|
// initialization runs. Lazily allocate from precomputed counts so authored
|
|
// spawn data (size/rotation/tint) is available instead of default fallbacks.
|
|
if (mParticles == NULL) {
|
|
InitParticleArray(effect);
|
|
}
|
|
if (mParticles == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
rvParticle* particle = NULL;
|
|
if (st->GetTemporary()) {
|
|
particle = mParticles;
|
|
if (particle) {
|
|
particle->SetNext(NULL);
|
|
}
|
|
}
|
|
else {
|
|
particle = mFreeHead;
|
|
if (!particle) {
|
|
return NULL;
|
|
}
|
|
mFreeHead = particle->GetNext();
|
|
particle->SetNext(NULL);
|
|
}
|
|
|
|
return particle;
|
|
}
|
|
|
|
rvParticle* rvSegment::SpawnParticle(rvBSE* effect, rvSegmentTemplate* st, float birthTime, const idVec3& initOffset, const idMat3& initAxis) {
|
|
if (!effect || !st) {
|
|
return NULL;
|
|
}
|
|
|
|
rvParticle* particle = GetFreeParticle(effect);
|
|
if (!particle) {
|
|
if (BSESpawnTraceEnabled()) {
|
|
BSESpawnTrace(
|
|
"BSE spawn miss: decl=%s segType=%d handle=%d birth=%.4f freeHead=null particleCount=%d loopCount=%d\n",
|
|
effect ? effect->GetDeclName() : "<null>",
|
|
st ? st->GetType() : -1,
|
|
mSegmentTemplateHandle,
|
|
birthTime,
|
|
mParticleCount,
|
|
mLoopParticleCount);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const float fraction = birthTime - effect->GetStartTime();
|
|
particle->FinishSpawn(effect, this, birthTime, fraction, initOffset, initAxis);
|
|
BSE_AddSpawned(1);
|
|
if (BSESpawnTraceEnabled()) {
|
|
BSESpawnTrace(
|
|
"BSE spawn ok: decl=%s segType=%d pType=%d handle=%d birth=%.4f end=%.4f dur=%.4f fraction=%.4f\n",
|
|
effect ? effect->GetDeclName() : "<null>",
|
|
st ? st->GetType() : -1,
|
|
st ? st->GetParticleTemplate()->GetType() : -1,
|
|
mSegmentTemplateHandle,
|
|
birthTime,
|
|
particle->GetEndTime(),
|
|
particle->GetDuration(),
|
|
fraction);
|
|
}
|
|
|
|
const int type = st->mParticleTemplate.GetType();
|
|
const bool linked = (type == PTYPE_LINKED || type == PTYPE_ORIENTEDLINKED);
|
|
if (st->GetTemporary()) {
|
|
mUsedHead = particle;
|
|
particle->SetNext(NULL);
|
|
}
|
|
else if (linked || !st->GetComplexParticle()) {
|
|
InsertByEndTime(mUsedHead, particle);
|
|
}
|
|
else {
|
|
particle->SetNext(mUsedHead);
|
|
mUsedHead = particle;
|
|
}
|
|
|
|
return particle;
|
|
}
|
|
|
|
void rvSegment::SpawnParticles(rvBSE* effect, rvSegmentTemplate* st, float birthTime, int count) {
|
|
if (!effect || !st || count <= 0) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < count; ++i) {
|
|
rvParticle* particle = GetFreeParticle(effect);
|
|
if (!particle) {
|
|
if (BSESpawnTraceEnabled()) {
|
|
BSESpawnTrace(
|
|
"BSE spawn batch miss: decl=%s segType=%d handle=%d birth=%.4f idx=%d count=%d freeHead=null particleCount=%d loopCount=%d\n",
|
|
effect ? effect->GetDeclName() : "<null>",
|
|
st ? st->GetType() : -1,
|
|
mSegmentTemplateHandle,
|
|
birthTime,
|
|
i,
|
|
count,
|
|
mParticleCount,
|
|
mLoopParticleCount);
|
|
}
|
|
break;
|
|
}
|
|
|
|
float fraction = 0.0f;
|
|
if (count > 0) {
|
|
fraction = static_cast<float>(i) / static_cast<float>(count);
|
|
}
|
|
particle->FinishSpawn(effect, this, birthTime, fraction, vec3_origin, mat3_identity);
|
|
BSE_AddSpawned(1);
|
|
if (BSESpawnTraceEnabled()) {
|
|
BSESpawnTrace(
|
|
"BSE spawn batch ok: decl=%s segType=%d pType=%d handle=%d birth=%.4f idx=%d count=%d end=%.4f dur=%.4f fraction=%.4f\n",
|
|
effect ? effect->GetDeclName() : "<null>",
|
|
st ? st->GetType() : -1,
|
|
st ? st->GetParticleTemplate()->GetType() : -1,
|
|
mSegmentTemplateHandle,
|
|
birthTime,
|
|
i,
|
|
count,
|
|
particle->GetEndTime(),
|
|
particle->GetDuration(),
|
|
fraction);
|
|
}
|
|
|
|
const int type = st->mParticleTemplate.GetType();
|
|
const bool linked = (type == PTYPE_LINKED || type == PTYPE_ORIENTEDLINKED);
|
|
if (st->GetTemporary()) {
|
|
mUsedHead = particle;
|
|
particle->SetNext(NULL);
|
|
}
|
|
else if (linked || !st->GetComplexParticle()) {
|
|
InsertByEndTime(mUsedHead, particle);
|
|
}
|
|
else {
|
|
particle->SetNext(mUsedHead);
|
|
mUsedHead = particle;
|
|
}
|
|
}
|
|
}
|
|
|
|
void rvSegment::UpdateSimpleParticles(float time) {
|
|
rvSegmentTemplate* st = GetSegmentTemplate();
|
|
if (!st) {
|
|
mActiveCount = 0;
|
|
return;
|
|
}
|
|
|
|
rvParticle* prev = NULL;
|
|
rvParticle* p = mUsedHead;
|
|
mActiveCount = 0;
|
|
|
|
while (p) {
|
|
rvParticle* next = p->GetNext();
|
|
if (p->GetEndTime() - BSE_TIME_EPSILON > time) {
|
|
mActiveCount += p->Update(&st->mParticleTemplate, time);
|
|
prev = p;
|
|
}
|
|
else {
|
|
if (prev) {
|
|
prev->SetNext(next);
|
|
}
|
|
else {
|
|
mUsedHead = next;
|
|
}
|
|
p->SetNext(mFreeHead);
|
|
p->Destroy();
|
|
mFreeHead = p;
|
|
}
|
|
p = next;
|
|
}
|
|
}
|
|
|
|
void rvSegment::UpdateGenericParticles(rvBSE* effect, rvSegmentTemplate* st, float time) {
|
|
if (!effect || !st) {
|
|
mActiveCount = 0;
|
|
return;
|
|
}
|
|
|
|
const bool infinite = st->GetInfiniteDuration();
|
|
const bool smoker = st->GetSmoker();
|
|
|
|
rvParticle* prev = NULL;
|
|
rvParticle* p = mUsedHead;
|
|
mActiveCount = 0;
|
|
|
|
while (p) {
|
|
rvParticle* next = p->GetNext();
|
|
bool remove = false;
|
|
|
|
if (infinite) {
|
|
p->RunPhysics(effect, st, time);
|
|
remove = effect->GetStopped();
|
|
}
|
|
else {
|
|
if (p->GetEndTime() - BSE_TIME_EPSILON <= time) {
|
|
p->CheckTimeoutEffect(effect, st, time);
|
|
remove = true;
|
|
}
|
|
else {
|
|
remove = p->RunPhysics(effect, st, time);
|
|
}
|
|
}
|
|
|
|
if (effect->GetStopped() && (p->GetFlags() & PTFLAG_PERSIST) == 0) {
|
|
remove = true;
|
|
}
|
|
|
|
if (smoker && st->mTrailSegmentIndex >= 0) {
|
|
rvSegment* trail = effect->GetTrailSegment(st->mTrailSegmentIndex);
|
|
if (trail) {
|
|
p->EmitSmokeParticles(effect, trail, &st->mParticleTemplate, time);
|
|
}
|
|
}
|
|
|
|
if (remove) {
|
|
if (prev) {
|
|
prev->SetNext(next);
|
|
}
|
|
else {
|
|
mUsedHead = next;
|
|
}
|
|
p->SetNext(mFreeHead);
|
|
p->Destroy();
|
|
mFreeHead = p;
|
|
}
|
|
else {
|
|
mActiveCount += p->Update(st->GetParticleTemplate(), time);
|
|
prev = p;
|
|
}
|
|
|
|
p = next;
|
|
}
|
|
}
|
|
|
|
void rvSegment::AddToParticleCount(rvBSE* effect, int count, int loopCount, float duration) {
|
|
rvSegmentTemplate* st = GetSegmentTemplate();
|
|
if (!st) {
|
|
return;
|
|
}
|
|
|
|
if (duration < st->mParticleTemplate.GetMaxDuration()) {
|
|
duration = st->mParticleTemplate.GetMaxDuration();
|
|
}
|
|
|
|
const float countDuration = duration + 0.016f;
|
|
if (mSecondsPerParticle.y > BSE_TIME_EPSILON) {
|
|
const int mult = static_cast<int>(ceilf(countDuration / mSecondsPerParticle.y)) + 1;
|
|
mLoopParticleCount += loopCount * mult;
|
|
mParticleCount += count * mult;
|
|
}
|
|
else {
|
|
mLoopParticleCount += loopCount;
|
|
mParticleCount += count;
|
|
}
|
|
|
|
const int particleCap = GetSegmentParticleCap();
|
|
mParticleCount = idMath::ClampInt(0, particleCap, mParticleCount);
|
|
mLoopParticleCount = idMath::ClampInt(0, particleCap, mLoopParticleCount);
|
|
}
|
|
|
|
void rvSegment::CalcTrailCounts(rvBSE* effect, rvSegmentTemplate* st, rvParticleTemplate* pt, float duration) {
|
|
if (!effect || !st || !pt) {
|
|
return;
|
|
}
|
|
if (st->mTrailSegmentIndex >= 0) {
|
|
rvSegment* trail = effect->GetTrailSegment(st->mTrailSegmentIndex);
|
|
if (trail) {
|
|
trail->AddToParticleCount(effect, mParticleCount, mLoopParticleCount, duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
void rvSegment::RenderMotion(rvBSE* effect, const renderEffect_s* owner, idRenderModel* model, rvParticleTemplate* pt, float time) {
|
|
if (!effect || !owner || !model || !pt || mSurfaceIndex < 0) {
|
|
return;
|
|
}
|
|
if (mSurfaceIndex + 1 >= model->NumSurfaces()) {
|
|
return;
|
|
}
|
|
|
|
srfTriangles_t* tri = model->Surface(mSurfaceIndex + 1)->geometry;
|
|
if (!tri) {
|
|
return;
|
|
}
|
|
|
|
for (rvParticle* p = mUsedHead; p; p = p->GetNext()) {
|
|
p->RenderMotion(effect, pt, tri, owner, time);
|
|
}
|
|
|
|
BSE_BoundTriSurf(tri);
|
|
}
|
|
|
|
void rvSegment::Sort(const idVec3& eyePos) {
|
|
if (!mUsedHead || !mUsedHead->GetNext()) {
|
|
return;
|
|
}
|
|
|
|
rvParticle* sorted = NULL;
|
|
rvParticle* particle = mUsedHead;
|
|
while (particle) {
|
|
rvParticle* next = particle->GetNext();
|
|
const float dist = particle->UpdateViewDist(eyePos);
|
|
|
|
if (!sorted || dist > sorted->UpdateViewDist(eyePos)) {
|
|
particle->SetNext(sorted);
|
|
sorted = particle;
|
|
}
|
|
else {
|
|
rvParticle* insertAfter = sorted;
|
|
while (insertAfter->GetNext()) {
|
|
rvParticle* compare = insertAfter->GetNext();
|
|
if (dist > compare->UpdateViewDist(eyePos)) {
|
|
break;
|
|
}
|
|
insertAfter = compare;
|
|
}
|
|
particle->SetNext(insertAfter->GetNext());
|
|
insertAfter->SetNext(particle);
|
|
}
|
|
|
|
particle = next;
|
|
}
|
|
|
|
mUsedHead = sorted;
|
|
}
|