/
dieoska
/
ddnet
Обзор
Документация
Войти
/
dieoska
/
ddnet
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/game/client/components/particles.cpp
346 строк
10 KB
byfox
Removed deadcode
12 ноя 2025, 10:09
12 ноя 2025, 10:09
e8b6cb2
Код
Авторство
О чём код?
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include "particles.h" #include <base/math.h> #include <engine/demo.h> #include <engine/graphics.h> #include <generated/client_data.h> #include <game/client/gameclient.h> CParticles::CParticles() { OnReset(); m_RenderTrail.m_pParts = this; m_RenderTrailExtra.m_pParts = this; m_RenderExplosions.m_pParts = this; m_RenderExtra.m_pParts = this; m_RenderGeneral.m_pParts = this; } void CParticles::OnReset() { // reset particles for(int i = 0; i < MAX_PARTICLES; i++) { m_aParticles[i].m_PrevPart = i - 1; m_aParticles[i].m_NextPart = i + 1; } m_aParticles[0].m_PrevPart = 0; m_aParticles[MAX_PARTICLES - 1].m_NextPart = -1; m_FirstFree = 0; for(int &FirstPart : m_aFirstPart) FirstPart = -1; } void CParticles::Add(int Group, CParticle *pPart, float TimePassed) { if(Client()->State() == IClient::STATE_DEMOPLAYBACK) { const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); if(pInfo->m_Paused) return; } else { if(GameClient()->m_Snap.m_pGameInfoObj && GameClient()->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED) return; } if(m_FirstFree == -1) return; // remove from the free list int Id = m_FirstFree; m_FirstFree = m_aParticles[Id].m_NextPart; if(m_FirstFree != -1) m_aParticles[m_FirstFree].m_PrevPart = -1; // copy data m_aParticles[Id] = *pPart; // insert to the group list m_aParticles[Id].m_PrevPart = -1; m_aParticles[Id].m_NextPart = m_aFirstPart[Group]; if(m_aFirstPart[Group] != -1) m_aParticles[m_aFirstPart[Group]].m_PrevPart = Id; m_aFirstPart[Group] = Id; // set some parameters m_aParticles[Id].m_Life = TimePassed; } void CParticles::Update(float TimePassed) { if(TimePassed <= 0.0f) return; m_FrictionFraction += TimePassed; if(m_FrictionFraction > 2.0f) // safety measure m_FrictionFraction = 0; int FrictionCount = 0; while(m_FrictionFraction > 0.05f) { FrictionCount++; m_FrictionFraction -= 0.05f; } for(int &FirstPart : m_aFirstPart) { int i = FirstPart; while(i != -1) { int Next = m_aParticles[i].m_NextPart; m_aParticles[i].m_Vel.y += m_aParticles[i].m_Gravity * TimePassed; for(int f = 0; f < FrictionCount; f++) // apply friction m_aParticles[i].m_Vel *= m_aParticles[i].m_Friction; // move the point vec2 Vel = m_aParticles[i].m_Vel * TimePassed; if(m_aParticles[i].m_Collides) { Collision()->MovePoint(&m_aParticles[i].m_Pos, &Vel, random_float(0.1f, 1.0f), nullptr); } else { m_aParticles[i].m_Pos += Vel; } m_aParticles[i].m_Vel = Vel * (1.0f / TimePassed); m_aParticles[i].m_Life += TimePassed; m_aParticles[i].m_Rot += TimePassed * m_aParticles[i].m_Rotspeed; // check particle death if(m_aParticles[i].m_Life > m_aParticles[i].m_LifeSpan) { // remove it from the group list if(m_aParticles[i].m_PrevPart != -1) m_aParticles[m_aParticles[i].m_PrevPart].m_NextPart = m_aParticles[i].m_NextPart; else FirstPart = m_aParticles[i].m_NextPart; if(m_aParticles[i].m_NextPart != -1) m_aParticles[m_aParticles[i].m_NextPart].m_PrevPart = m_aParticles[i].m_PrevPart; // insert to the free list if(m_FirstFree != -1) m_aParticles[m_FirstFree].m_PrevPart = i; m_aParticles[i].m_PrevPart = -1; m_aParticles[i].m_NextPart = m_FirstFree; m_FirstFree = i; } i = Next; } } } void CParticles::OnRender() { if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) return; set_new_tick(); int64_t t = time(); if(Client()->State() == IClient::STATE_DEMOPLAYBACK) { const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); if(!pInfo->m_Paused) Update((float)((t - m_LastRenderTime) / (double)time_freq()) * pInfo->m_Speed); } else { if(GameClient()->m_Snap.m_pGameInfoObj && !(GameClient()->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)) Update((float)((t - m_LastRenderTime) / (double)time_freq())); } m_LastRenderTime = t; } void CParticles::OnInit() { Graphics()->QuadsSetRotation(0); Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); m_ParticleQuadContainerIndex = Graphics()->CreateQuadContainer(false); for(int i = 0; i <= (SPRITE_PART9 - SPRITE_PART_SLICE); ++i) { Graphics()->QuadsSetSubset(0, 0, 1, 1); Graphics()->QuadContainerAddSprite(m_ParticleQuadContainerIndex, 1.f); } Graphics()->QuadContainerUpload(m_ParticleQuadContainerIndex); m_ExtraParticleQuadContainerIndex = Graphics()->CreateQuadContainer(false); for(int i = 0; i <= (SPRITE_PART_SPARKLE - SPRITE_PART_SNOWFLAKE); ++i) { Graphics()->QuadsSetSubset(0, 0, 1, 1); Graphics()->QuadContainerAddSprite(m_ExtraParticleQuadContainerIndex, 1.f); } Graphics()->QuadContainerUpload(m_ExtraParticleQuadContainerIndex); } bool CParticles::ParticleIsVisibleOnScreen(const vec2 &CurPos, float CurSize) { float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); // for simplicity assume the worst case rotation, that increases the bounding box around the particle by its diagonal const float SqrtOf2 = std::sqrt(2); CurSize = SqrtOf2 * CurSize; // always uses the mid of the particle float SizeHalf = CurSize / 2; return CurPos.x + SizeHalf >= ScreenX0 && CurPos.x - SizeHalf <= ScreenX1 && CurPos.y + SizeHalf >= ScreenY0 && CurPos.y - SizeHalf <= ScreenY1; } void CParticles::RenderGroup(int Group) { IGraphics::CTextureHandle *aParticles = GameClient()->m_ParticlesSkin.m_aSpriteParticles; int FirstParticleOffset = SPRITE_PART_SLICE; int ParticleQuadContainerIndex = m_ParticleQuadContainerIndex; if(Group == GROUP_EXTRA || Group == GROUP_TRAIL_EXTRA) { aParticles = GameClient()->m_ExtrasSkin.m_aSpriteParticles; FirstParticleOffset = SPRITE_PART_SNOWFLAKE; ParticleQuadContainerIndex = m_ExtraParticleQuadContainerIndex; } // don't use the buffer methods here, else the old renderer gets many draw calls if(Graphics()->IsQuadContainerBufferingEnabled()) { int i = m_aFirstPart[Group]; static IGraphics::SRenderSpriteInfo s_aParticleRenderInfo[MAX_PARTICLES]; int CurParticleRenderCount = 0; // batching makes sense for stuff like ninja particles ColorRGBA LastColor; int LastQuadOffset = 0; if(i != -1) { float Alpha = m_aParticles[i].m_Color.a; if(m_aParticles[i].m_UseAlphaFading) { float a = m_aParticles[i].m_Life / m_aParticles[i].m_LifeSpan; Alpha = mix(m_aParticles[i].m_StartAlpha, m_aParticles[i].m_EndAlpha, a); } LastColor.r = m_aParticles[i].m_Color.r; LastColor.g = m_aParticles[i].m_Color.g; LastColor.b = m_aParticles[i].m_Color.b; LastColor.a = Alpha; Graphics()->SetColor( m_aParticles[i].m_Color.r, m_aParticles[i].m_Color.g, m_aParticles[i].m_Color.b, Alpha); LastQuadOffset = m_aParticles[i].m_Spr; } while(i != -1) { int QuadOffset = m_aParticles[i].m_Spr; float a = m_aParticles[i].m_Life / m_aParticles[i].m_LifeSpan; vec2 p = m_aParticles[i].m_Pos; float Size = mix(m_aParticles[i].m_StartSize, m_aParticles[i].m_EndSize, a); float Alpha = m_aParticles[i].m_Color.a; if(m_aParticles[i].m_UseAlphaFading) { Alpha = mix(m_aParticles[i].m_StartAlpha, m_aParticles[i].m_EndAlpha, a); } // the current position, respecting the size, is inside the viewport, render it, else ignore if(ParticleIsVisibleOnScreen(p, Size)) { if((size_t)CurParticleRenderCount == gs_GraphicsMaxParticlesRenderCount || LastColor.r != m_aParticles[i].m_Color.r || LastColor.g != m_aParticles[i].m_Color.g || LastColor.b != m_aParticles[i].m_Color.b || LastColor.a != Alpha || LastQuadOffset != QuadOffset) { Graphics()->TextureSet(aParticles[LastQuadOffset - FirstParticleOffset]); Graphics()->RenderQuadContainerAsSpriteMultiple(ParticleQuadContainerIndex, LastQuadOffset - FirstParticleOffset, CurParticleRenderCount, s_aParticleRenderInfo); CurParticleRenderCount = 0; LastQuadOffset = QuadOffset; Graphics()->SetColor( m_aParticles[i].m_Color.r, m_aParticles[i].m_Color.g, m_aParticles[i].m_Color.b, Alpha); LastColor.r = m_aParticles[i].m_Color.r; LastColor.g = m_aParticles[i].m_Color.g; LastColor.b = m_aParticles[i].m_Color.b; LastColor.a = Alpha; } s_aParticleRenderInfo[CurParticleRenderCount].m_Pos[0] = p.x; s_aParticleRenderInfo[CurParticleRenderCount].m_Pos[1] = p.y; s_aParticleRenderInfo[CurParticleRenderCount].m_Scale = Size; s_aParticleRenderInfo[CurParticleRenderCount].m_Rotation = m_aParticles[i].m_Rot; ++CurParticleRenderCount; } i = m_aParticles[i].m_NextPart; } Graphics()->TextureSet(aParticles[LastQuadOffset - FirstParticleOffset]); Graphics()->RenderQuadContainerAsSpriteMultiple(ParticleQuadContainerIndex, LastQuadOffset - FirstParticleOffset, CurParticleRenderCount, s_aParticleRenderInfo); } else { int i = m_aFirstPart[Group]; Graphics()->BlendNormal(); Graphics()->WrapClamp(); while(i != -1) { float a = m_aParticles[i].m_Life / m_aParticles[i].m_LifeSpan; vec2 p = m_aParticles[i].m_Pos; float Size = mix(m_aParticles[i].m_StartSize, m_aParticles[i].m_EndSize, a); float Alpha = m_aParticles[i].m_Color.a; if(m_aParticles[i].m_UseAlphaFading) { Alpha = mix(m_aParticles[i].m_StartAlpha, m_aParticles[i].m_EndAlpha, a); } // the current position, respecting the size, is inside the viewport, render it, else ignore if(ParticleIsVisibleOnScreen(p, Size)) { Graphics()->TextureSet(aParticles[m_aParticles[i].m_Spr - FirstParticleOffset]); Graphics()->QuadsBegin(); Graphics()->QuadsSetRotation(m_aParticles[i].m_Rot); Graphics()->SetColor( m_aParticles[i].m_Color.r, m_aParticles[i].m_Color.g, m_aParticles[i].m_Color.b, Alpha); IGraphics::CQuadItem QuadItem(p.x, p.y, Size, Size); Graphics()->QuadsDraw(&QuadItem, 1); Graphics()->QuadsEnd(); } i = m_aParticles[i].m_NextPart; } Graphics()->WrapNormal(); Graphics()->BlendNormal(); } }