ddnet/src/game/editor/mapitems/envelope.cpp
Robert Müller ab3c6c4353 Improve envelope evaluation and validation
Call `EnvelopeEval` functions directly instead of passing them and their arguments to `CRenderTools::RenderTilemap` and `CRenderTools::RenderTileRectangle`.

Only evaluate color envelopes for tiles layers once instead of separately for the opaque and transparent passes.

Only evaluate relevant number of envelope channels instead of always evaluating all channels.

Avoid unnecessary calculations by only evaluating position envelopes for quads which are not fully transparent.

Fully ignore layer color and envelope color for entities layers, as these cannot be specified in the editor and should not be changeable.

Remove duplicate and insufficient checks for invalid envelope index before calling `EnvelopeEval`. Instead, set the correct default for all channels before calling `EnvelopeEval` and only change the result on success. Now, white color will consistently be assumed for invalid color envelopes, zero positions and rotations for invalid position envelopes, and full volume for invalid sound envelopes.

Validate number of envelope channels to prevent crashes. When loading maps containing envelopes with invalid number of channels (not equal to 1, 3 or 4), the number of channels of these envelopes is reset to 4 and an error message is displayed, so the mapper can examine all channels' data and transfer it to another envelope if necessary. Closes #7985.
2024-02-23 19:24:34 +01:00

149 lines
3.3 KiB
C++

#include "envelope.h"
#include <algorithm>
#include <chrono>
#include <limits>
using namespace std::chrono_literals;
CEnvelope::CEnvelopePointAccess::CEnvelopePointAccess(std::vector<CEnvPoint_runtime> *pvPoints)
{
m_pvPoints = pvPoints;
}
int CEnvelope::CEnvelopePointAccess::NumPoints() const
{
return m_pvPoints->size();
}
const CEnvPoint *CEnvelope::CEnvelopePointAccess::GetPoint(int Index) const
{
if(Index < 0 || (size_t)Index >= m_pvPoints->size())
return nullptr;
return &m_pvPoints->at(Index);
}
const CEnvPointBezier *CEnvelope::CEnvelopePointAccess::GetBezier(int Index) const
{
if(Index < 0 || (size_t)Index >= m_pvPoints->size())
return nullptr;
return &m_pvPoints->at(Index).m_Bezier;
}
CEnvelope::CEnvelope(EType Type) :
m_Type(Type), m_PointsAccess(&m_vPoints) {}
CEnvelope::CEnvelope(int NumChannels) :
m_PointsAccess(&m_vPoints)
{
switch(NumChannels)
{
case 1:
m_Type = EType::SOUND;
break;
case 3:
m_Type = EType::POSITION;
break;
case 4:
m_Type = EType::COLOR;
break;
default:
dbg_assert(false, "invalid number of channels for envelope");
}
}
void CEnvelope::Resort()
{
std::sort(m_vPoints.begin(), m_vPoints.end());
}
std::pair<float, float> CEnvelope::GetValueRange(int ChannelMask)
{
float Top = -std::numeric_limits<float>::infinity();
float Bottom = std::numeric_limits<float>::infinity();
CEnvPoint_runtime *pPrevPoint = nullptr;
for(auto &Point : m_vPoints)
{
for(int c = 0; c < GetChannels(); c++)
{
if(ChannelMask & (1 << c))
{
{
// value handle
const float v = fx2f(Point.m_aValues[c]);
Top = maximum(Top, v);
Bottom = minimum(Bottom, v);
}
if(Point.m_Curvetype == CURVETYPE_BEZIER)
{
// out-tangent handle
const float v = fx2f(Point.m_aValues[c] + Point.m_Bezier.m_aOutTangentDeltaY[c]);
Top = maximum(Top, v);
Bottom = minimum(Bottom, v);
}
if(pPrevPoint != nullptr && pPrevPoint->m_Curvetype == CURVETYPE_BEZIER)
{
// in-tangent handle
const float v = fx2f(Point.m_aValues[c] + Point.m_Bezier.m_aInTangentDeltaY[c]);
Top = maximum(Top, v);
Bottom = minimum(Bottom, v);
}
}
}
pPrevPoint = &Point;
}
return {Bottom, Top};
}
void CEnvelope::Eval(float Time, ColorRGBA &Result, size_t Channels)
{
Channels = minimum<size_t>(Channels, GetChannels(), CEnvPoint::MAX_CHANNELS);
CRenderTools::RenderEvalEnvelope(&m_PointsAccess, std::chrono::nanoseconds((int64_t)((double)Time * (double)std::chrono::nanoseconds(1s).count())), Result, Channels);
}
void CEnvelope::AddPoint(int Time, int v0, int v1, int v2, int v3)
{
CEnvPoint_runtime p;
p.m_Time = Time;
p.m_aValues[0] = v0;
p.m_aValues[1] = v1;
p.m_aValues[2] = v2;
p.m_aValues[3] = v3;
p.m_Curvetype = CURVETYPE_LINEAR;
for(int c = 0; c < CEnvPoint::MAX_CHANNELS; c++)
{
p.m_Bezier.m_aInTangentDeltaX[c] = 0;
p.m_Bezier.m_aInTangentDeltaY[c] = 0;
p.m_Bezier.m_aOutTangentDeltaX[c] = 0;
p.m_Bezier.m_aOutTangentDeltaY[c] = 0;
}
m_vPoints.push_back(p);
Resort();
}
float CEnvelope::EndTime() const
{
if(m_vPoints.empty())
return 0.0f;
return m_vPoints.back().m_Time / 1000.0f;
}
int CEnvelope::GetChannels() const
{
switch(m_Type)
{
case EType::POSITION:
return 3;
case EType::COLOR:
return 4;
case EType::SOUND:
return 1;
default:
dbg_assert(false, "unknown envelope type");
dbg_break();
}
}