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.
This commit is contained in:
Robert Müller 2024-02-17 21:27:00 +01:00
parent 040d7673c6
commit ab3c6c4353
13 changed files with 150 additions and 220 deletions

View file

@ -56,18 +56,19 @@ void CMapLayers::EnvelopeUpdate()
}
}
void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser)
void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Result, size_t Channels, void *pUser)
{
CMapLayers *pThis = (CMapLayers *)pUser;
Channels = ColorRGBA();
int EnvStart, EnvNum;
pThis->m_pLayers->Map()->GetType(MAPITEMTYPE_ENVELOPE, &EnvStart, &EnvNum);
if(Env < 0 || Env >= EnvNum)
return;
const CMapItemEnvelope *pItem = (CMapItemEnvelope *)pThis->m_pLayers->Map()->GetItem(EnvStart + Env);
if(pItem->m_Channels <= 0)
return;
Channels = minimum<size_t>(Channels, pItem->m_Channels, CEnvPoint::MAX_CHANNELS);
CMapBasedEnvelopePointAccess EnvelopePoints(pThis->m_pLayers->Map());
EnvelopePoints.SetPointsRange(pItem->m_StartPoint, pItem->m_NumPoints);
@ -114,7 +115,7 @@ void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels
MinTick * TickToNanoSeconds;
}
}
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, 4, s_Time + (int64_t)TimeOffsetMillis * std::chrono::nanoseconds(1ms), Channels);
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, s_Time + (int64_t)TimeOffsetMillis * std::chrono::nanoseconds(1ms), Result, Channels);
}
else
{
@ -139,7 +140,7 @@ void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels
s_Time += CurTime - s_LastLocalTime;
s_LastLocalTime = CurTime;
}
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, 4, s_Time + std::chrono::nanoseconds(std::chrono::milliseconds(TimeOffsetMillis)), Channels);
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, s_Time + std::chrono::nanoseconds(std::chrono::milliseconds(TimeOffsetMillis)), Result, Channels);
}
}
@ -901,7 +902,7 @@ void CMapLayers::OnMapLoad()
}
}
void CMapLayers::RenderTileLayer(int LayerIndex, ColorRGBA &Color, CMapItemLayerTilemap *pTileLayer, CMapItemGroup *pGroup)
void CMapLayers::RenderTileLayer(int LayerIndex, const ColorRGBA &Color, CMapItemLayerTilemap *pTileLayer, CMapItemGroup *pGroup)
{
STileLayerVisuals &Visuals = *m_vpTileLayerVisuals[LayerIndex];
if(Visuals.m_BufferContainerIndex == -1)
@ -910,12 +911,6 @@ void CMapLayers::RenderTileLayer(int LayerIndex, ColorRGBA &Color, CMapItemLayer
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
ColorRGBA Channels(1.f, 1.f, 1.f, 1.f);
if(pTileLayer->m_ColorEnv >= 0)
{
EnvelopeEval(pTileLayer->m_ColorEnvOffset, pTileLayer->m_ColorEnv, Channels, this);
}
int BorderX0, BorderY0, BorderX1, BorderY1;
bool DrawBorder = false;
@ -985,11 +980,6 @@ void CMapLayers::RenderTileLayer(int LayerIndex, ColorRGBA &Color, CMapItemLayer
}
}
Color.x *= Channels.r;
Color.y *= Channels.g;
Color.z *= Channels.b;
Color.w *= Channels.a;
int DrawCount = s_vpIndexOffsets.size();
if(DrawCount != 0)
{
@ -1277,27 +1267,11 @@ void CMapLayers::RenderQuadLayer(int LayerIndex, CMapItemLayerQuads *pQuadLayer,
{
CQuad *pQuad = &pQuads[i];
ColorRGBA Color(1.f, 1.f, 1.f, 1.f);
if(pQuad->m_ColorEnv >= 0)
{
EnvelopeEval(pQuad->m_ColorEnvOffset, pQuad->m_ColorEnv, Color, this);
}
ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
EnvelopeEval(pQuad->m_ColorEnvOffset, pQuad->m_ColorEnv, Color, 4, this);
float OffsetX = 0;
float OffsetY = 0;
float Rot = 0;
if(pQuad->m_PosEnv >= 0)
{
ColorRGBA Channels;
EnvelopeEval(pQuad->m_PosEnvOffset, pQuad->m_PosEnv, Channels, this);
OffsetX = Channels.r;
OffsetY = Channels.g;
Rot = Channels.b / 180.0f * pi;
}
const bool IsFullyTransparent = Color.a <= 0;
bool NeedsFlush = QuadsRenderCount == gs_GraphicsMaxQuadsRenderCount || IsFullyTransparent;
const bool IsFullyTransparent = Color.a <= 0.0f;
const bool NeedsFlush = QuadsRenderCount == gs_GraphicsMaxQuadsRenderCount || IsFullyTransparent;
if(NeedsFlush)
{
@ -1314,11 +1288,14 @@ void CMapLayers::RenderQuadLayer(int LayerIndex, CMapItemLayerQuads *pQuadLayer,
if(!IsFullyTransparent)
{
ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
EnvelopeEval(pQuad->m_PosEnvOffset, pQuad->m_PosEnv, Position, 3, this);
SQuadRenderInfo &QInfo = s_vQuadRenderInfo[QuadsRenderCount++];
QInfo.m_Color = Color;
QInfo.m_Offsets.x = OffsetX;
QInfo.m_Offsets.y = OffsetY;
QInfo.m_Rotation = Rot;
QInfo.m_Offsets.x = Position.r;
QInfo.m_Offsets.y = Position.g;
QInfo.m_Rotation = Position.b / 180.0f * pi;
}
}
Graphics()->RenderQuadLayer(Visuals.m_BufferContainerIndex, s_vQuadRenderInfo.data(), QuadsRenderCount, CurQuadOffset);
@ -1638,17 +1615,23 @@ void CMapLayers::OnRender()
if(Size >= (size_t)pTMap->m_Width * pTMap->m_Height * sizeof(CTile))
{
ColorRGBA Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f);
ColorRGBA Color = IsGameLayer ? ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f) : ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f);
if(IsGameLayer && EntityOverlayVal)
Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f * EntityOverlayVal / 100.0f);
else if(!IsGameLayer && EntityOverlayVal && !(m_Type == TYPE_BACKGROUND_FORCE))
Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f * (100 - EntityOverlayVal) / 100.0f);
Color.a *= EntityOverlayVal / 100.0f;
else if(!IsGameLayer && EntityOverlayVal && m_Type != TYPE_BACKGROUND_FORCE)
Color.a *= (100 - EntityOverlayVal) / 100.0f;
if(!IsGameLayer)
{
ColorRGBA ColorEnv = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
EnvelopeEval(pTMap->m_ColorEnvOffset, pTMap->m_ColorEnv, ColorEnv, 4, this);
Color = Color.Multiply(ColorEnv);
}
if(!Graphics()->IsTileBufferingEnabled())
{
Graphics()->BlendNone();
RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_OPAQUE,
EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset);
RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_OPAQUE);
Graphics()->BlendNormal();
// draw kill tiles outside the entity clipping rectangle
@ -1657,15 +1640,12 @@ void CMapLayers::OnRender()
// slow blinking to hint that it's not a part of the map
double Seconds = time_get() / (double)time_freq();
ColorRGBA ColorHint = ColorRGBA(1.0f, 1.0f, 1.0f, 0.3 + 0.7 * (1 + std::sin(2 * (double)pi * Seconds / 3)) / 2);
RenderTools()->RenderTileRectangle(-201, -201, pTMap->m_Width + 402, pTMap->m_Height + 402,
0, TILE_DEATH, // display air inside, death outside
32.0f, Color.v4() * ColorHint.v4(), TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT,
EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset);
32.0f, Color.Multiply(ColorHint), TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT);
}
RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT,
EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset);
RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT);
}
else
{
@ -1676,9 +1656,7 @@ void CMapLayers::OnRender()
// slow blinking to hint that it's not a part of the map
double Seconds = time_get() / (double)time_freq();
ColorRGBA ColorHint = ColorRGBA(1.0f, 1.0f, 1.0f, 0.3 + 0.7 * (1.0 + std::sin(2 * (double)pi * Seconds / 3)) / 2);
ColorRGBA ColorKill(Color.x * ColorHint.x, Color.y * ColorHint.y, Color.z * ColorHint.z, Color.w * ColorHint.w);
RenderKillTileBorder(TileLayerCounter - 1, ColorKill, pTMap, pGroup);
RenderKillTileBorder(TileLayerCounter - 1, Color.Multiply(ColorHint), pTMap, pGroup);
}
RenderTileLayer(TileLayerCounter - 1, Color, pTMap, pGroup);
}
@ -1732,15 +1710,13 @@ void CMapLayers::OnRender()
if(Size >= (size_t)pTMap->m_Width * pTMap->m_Height * sizeof(CTile))
{
ColorRGBA Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f * EntityOverlayVal / 100.0f);
const ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, EntityOverlayVal / 100.0f);
if(!Graphics()->IsTileBufferingEnabled())
{
Graphics()->BlendNone();
RenderTools()->RenderTilemap(pFrontTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_OPAQUE,
EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset);
RenderTools()->RenderTilemap(pFrontTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_OPAQUE);
Graphics()->BlendNormal();
RenderTools()->RenderTilemap(pFrontTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT,
EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset);
RenderTools()->RenderTilemap(pFrontTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT);
}
else
{
@ -1759,7 +1735,7 @@ void CMapLayers::OnRender()
if(Size >= (size_t)pTMap->m_Width * pTMap->m_Height * sizeof(CSwitchTile))
{
ColorRGBA Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f * EntityOverlayVal / 100.0f);
const ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, EntityOverlayVal / 100.0f);
if(!Graphics()->IsTileBufferingEnabled())
{
Graphics()->BlendNone();
@ -1792,7 +1768,7 @@ void CMapLayers::OnRender()
if(Size >= (size_t)pTMap->m_Width * pTMap->m_Height * sizeof(CTeleTile))
{
ColorRGBA Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f * EntityOverlayVal / 100.0f);
const ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, EntityOverlayVal / 100.0f);
if(!Graphics()->IsTileBufferingEnabled())
{
Graphics()->BlendNone();
@ -1823,7 +1799,7 @@ void CMapLayers::OnRender()
if(Size >= (size_t)pTMap->m_Width * pTMap->m_Height * sizeof(CSpeedupTile))
{
ColorRGBA Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f * EntityOverlayVal / 100.0f);
const ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, EntityOverlayVal / 100.0f);
if(!Graphics()->IsTileBufferingEnabled())
{
Graphics()->BlendNone();
@ -1862,7 +1838,7 @@ void CMapLayers::OnRender()
if(Size >= (size_t)pTMap->m_Width * pTMap->m_Height * sizeof(CTuneTile))
{
ColorRGBA Color = ColorRGBA(pTMap->m_Color.r / 255.0f, pTMap->m_Color.g / 255.0f, pTMap->m_Color.b / 255.0f, pTMap->m_Color.a / 255.0f * EntityOverlayVal / 100.0f);
const ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, EntityOverlayVal / 100.0f);
if(!Graphics()->IsTileBufferingEnabled())
{
Graphics()->BlendNone();

View file

@ -152,14 +152,14 @@ public:
virtual void OnRender() override;
virtual void OnMapLoad() override;
void RenderTileLayer(int LayerIndex, ColorRGBA &Color, CMapItemLayerTilemap *pTileLayer, CMapItemGroup *pGroup);
void RenderTileLayer(int LayerIndex, const ColorRGBA &Color, CMapItemLayerTilemap *pTileLayer, CMapItemGroup *pGroup);
void RenderTileBorder(int LayerIndex, const ColorRGBA &Color, CMapItemLayerTilemap *pTileLayer, CMapItemGroup *pGroup, int BorderX0, int BorderY0, int BorderX1, int BorderY1);
void RenderKillTileBorder(int LayerIndex, const ColorRGBA &Color, CMapItemLayerTilemap *pTileLayer, CMapItemGroup *pGroup);
void RenderQuadLayer(int LayerIndex, CMapItemLayerQuads *pQuadLayer, CMapItemGroup *pGroup, bool ForceRender = false);
void EnvelopeUpdate();
static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser);
static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Result, size_t Channels, void *pUser);
};
#endif

View file

@ -209,18 +209,11 @@ void CMapSounds::OnRender()
if(!Voice.m_Voice.IsValid())
continue;
float OffsetX = 0, OffsetY = 0;
ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
CMapLayers::EnvelopeEval(Voice.m_pSource->m_PosEnvOffset, Voice.m_pSource->m_PosEnv, Position, 2, &m_pClient->m_MapLayersBackground);
if(Voice.m_pSource->m_PosEnv >= 0)
{
ColorRGBA Channels;
CMapLayers::EnvelopeEval(Voice.m_pSource->m_PosEnvOffset, Voice.m_pSource->m_PosEnv, Channels, &m_pClient->m_MapLayersBackground);
OffsetX = Channels.r;
OffsetY = Channels.g;
}
float x = fx2f(Voice.m_pSource->m_Position.x) + OffsetX;
float y = fx2f(Voice.m_pSource->m_Position.y) + OffsetY;
float x = fx2f(Voice.m_pSource->m_Position.x) + Position.r;
float y = fx2f(Voice.m_pSource->m_Position.y) + Position.g;
x += Center.x * (1.0f - pGroup->m_ParallaxX / 100.0f);
y += Center.y * (1.0f - pGroup->m_ParallaxY / 100.0f);
@ -230,13 +223,11 @@ void CMapSounds::OnRender()
Sound()->SetVoiceLocation(Voice.m_Voice, x, y);
if(Voice.m_pSource->m_SoundEnv >= 0)
ColorRGBA Volume = ColorRGBA(1.0f, 0.0f, 0.0f, 0.0f);
CMapLayers::EnvelopeEval(Voice.m_pSource->m_SoundEnvOffset, Voice.m_pSource->m_SoundEnv, Volume, 1, &m_pClient->m_MapLayersBackground);
if(Volume.r < 1.0f)
{
ColorRGBA Channels;
CMapLayers::EnvelopeEval(Voice.m_pSource->m_SoundEnvOffset, Voice.m_pSource->m_SoundEnv, Channels, &m_pClient->m_MapLayersBackground);
float Volume = clamp(Channels.r, 0.0f, 1.0f);
Sound()->SetVoiceVolume(Voice.m_Voice, Volume);
Sound()->SetVoiceVolume(Voice.m_Voice, Volume.r);
}
}
}

View file

@ -117,7 +117,7 @@ public:
const CEnvPointBezier *GetBezier(int Index) const override;
};
typedef void (*ENVELOPE_EVAL)(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser);
typedef void (*ENVELOPE_EVAL)(int TimeOffsetMillis, int Env, ColorRGBA &Result, size_t Channels, void *pUser);
class CRenderTools
{
@ -162,14 +162,14 @@ public:
void RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f) const;
// map render methods (render_map.cpp)
static void RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result);
static void RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result, size_t Channels);
void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser) const;
void ForceRenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha = 1.0f) const;
void RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const;
void RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const;
// render a rectangle made of IndexIn tiles, over a background made of IndexOut tiles
// the rectangle include all tiles in [RectX, RectX+RectW-1] x [RectY, RectY+RectH-1]
void RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const;
void RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags) const;
// helpers
void CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight);

View file

@ -237,22 +237,21 @@ static float SolveBezier(float x, float p0, float p1, float p2, float p3)
}
}
void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result)
void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result, size_t Channels)
{
const int NumPoints = pPoints->NumPoints();
if(NumPoints == 0)
{
Result = ColorRGBA();
return;
}
if(NumPoints == 1)
{
const CEnvPoint *pFirstPoint = pPoints->GetPoint(0);
Result.r = fx2f(pFirstPoint->m_aValues[0]);
Result.g = fx2f(pFirstPoint->m_aValues[1]);
Result.b = fx2f(pFirstPoint->m_aValues[2]);
Result.a = fx2f(pFirstPoint->m_aValues[3]);
for(size_t c = 0; c < Channels; c++)
{
Result[c] = fx2f(pFirstPoint->m_aValues[c]);
}
return;
}
@ -298,7 +297,7 @@ void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int C
const CEnvPointBezier *pNextPointBezier = pPoints->GetBezier(i + 1);
if(pCurrentPointBezier == nullptr || pNextPointBezier == nullptr)
break; // fallback to linear
for(int c = 0; c < Channels; c++)
for(size_t c = 0; c < Channels; c++)
{
// monotonic 2d cubic bezier curve
const vec2 p0 = vec2(pCurrentPoint->m_Time / 1000.0f, fx2f(pCurrentPoint->m_aValues[c]));
@ -326,7 +325,7 @@ void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int C
break;
}
for(int c = 0; c < Channels; c++)
for(size_t c = 0; c < Channels; c++)
{
const float v0 = fx2f(pCurrentPoint->m_aValues[c]);
const float v1 = fx2f(pNextPoint->m_aValues[c]);
@ -337,13 +336,13 @@ void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int C
}
}
Result.r = fx2f(pLastPoint->m_aValues[0]);
Result.g = fx2f(pLastPoint->m_aValues[1]);
Result.b = fx2f(pLastPoint->m_aValues[2]);
Result.a = fx2f(pLastPoint->m_aValues[3]);
for(size_t c = 0; c < Channels; c++)
{
Result[c] = fx2f(pLastPoint->m_aValues[c]);
}
}
static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation)
static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation)
{
int x = pPoint->x - pCenter->x;
int y = pPoint->y - pCenter->y;
@ -367,13 +366,10 @@ void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags
{
CQuad *pQuad = &pQuads[i];
ColorRGBA Color(1.f, 1.f, 1.f, 1.f);
if(pQuad->m_ColorEnv >= 0)
{
pfnEval(pQuad->m_ColorEnvOffset, pQuad->m_ColorEnv, Color, pUser);
}
ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
pfnEval(pQuad->m_ColorEnvOffset, pQuad->m_ColorEnv, Color, 4, pUser);
if(Color.a <= 0)
if(Color.a <= 0.0f)
continue;
bool Opaque = false;
@ -392,19 +388,10 @@ void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags
fx2f(pQuad->m_aTexcoords[2].x), fx2f(pQuad->m_aTexcoords[2].y),
fx2f(pQuad->m_aTexcoords[3].x), fx2f(pQuad->m_aTexcoords[3].y));
float OffsetX = 0;
float OffsetY = 0;
float Rot = 0;
// TODO: fix this
if(pQuad->m_PosEnv >= 0)
{
ColorRGBA Channels;
pfnEval(pQuad->m_PosEnvOffset, pQuad->m_PosEnv, Channels, pUser);
OffsetX = Channels.r;
OffsetY = Channels.g;
Rot = Channels.b / 360.0f * pi * 2;
}
ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
pfnEval(pQuad->m_PosEnvOffset, pQuad->m_PosEnv, Position, 3, pUser);
const vec2 Offset = vec2(Position.r, Position.g);
const float Rotation = Position.b / 180.0f * pi;
IGraphics::CColorVertex Array[4] = {
IGraphics::CColorVertex(0, pQuad->m_aColors[0].r * Conv * Color.r, pQuad->m_aColors[0].g * Conv * Color.g, pQuad->m_aColors[0].b * Conv * Color.b, pQuad->m_aColors[0].a * Conv * Color.a * Alpha),
@ -415,26 +402,22 @@ void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags
CPoint *pPoints = pQuad->m_aPoints;
if(Rot != 0)
CPoint aRotated[4];
if(Rotation != 0.0f)
{
static CPoint aRotated[4];
aRotated[0] = pQuad->m_aPoints[0];
aRotated[1] = pQuad->m_aPoints[1];
aRotated[2] = pQuad->m_aPoints[2];
aRotated[3] = pQuad->m_aPoints[3];
for(size_t p = 0; p < std::size(aRotated); ++p)
{
aRotated[p] = pQuad->m_aPoints[p];
Rotate(&pQuad->m_aPoints[4], &aRotated[p], Rotation);
}
pPoints = aRotated;
Rotate(&pQuad->m_aPoints[4], &aRotated[0], Rot);
Rotate(&pQuad->m_aPoints[4], &aRotated[1], Rot);
Rotate(&pQuad->m_aPoints[4], &aRotated[2], Rot);
Rotate(&pQuad->m_aPoints[4], &aRotated[3], Rot);
}
IGraphics::CFreeformItem Freeform(
fx2f(pPoints[0].x) + OffsetX, fx2f(pPoints[0].y) + OffsetY,
fx2f(pPoints[1].x) + OffsetX, fx2f(pPoints[1].y) + OffsetY,
fx2f(pPoints[2].x) + OffsetX, fx2f(pPoints[2].y) + OffsetY,
fx2f(pPoints[3].x) + OffsetX, fx2f(pPoints[3].y) + OffsetY);
fx2f(pPoints[0].x) + Offset.x, fx2f(pPoints[0].y) + Offset.y,
fx2f(pPoints[1].x) + Offset.x, fx2f(pPoints[1].y) + Offset.y,
fx2f(pPoints[2].x) + Offset.x, fx2f(pPoints[2].y) + Offset.y,
fx2f(pPoints[3].x) + Offset.x, fx2f(pPoints[3].y) + Offset.y);
Graphics()->QuadsDrawFreeform(&Freeform, 1);
}
Graphics()->TrianglesEnd();
@ -442,8 +425,7 @@ void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags
void CRenderTools::RenderTileRectangle(int RectX, int RectY, int RectW, int RectH,
unsigned char IndexIn, unsigned char IndexOut,
float Scale, ColorRGBA Color, int RenderFlags,
ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const
float Scale, ColorRGBA Color, int RenderFlags) const
{
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
@ -453,17 +435,11 @@ void CRenderTools::RenderTileRectangle(int RectX, int RectY, int RectW, int Rect
float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
float FinalTilesetScale = FinalTileSize / TilePixelSize;
ColorRGBA Channels(1.f, 1.f, 1.f, 1.f);
if(ColorEnv >= 0)
{
pfnEval(ColorEnvOffset, ColorEnv, Channels, pUser);
}
if(Graphics()->HasTextureArraysSupport())
Graphics()->QuadsTex3DBegin();
else
Graphics()->QuadsBegin();
Graphics()->SetColor(Color.r * Channels.r, Color.g * Channels.g, Color.b * Channels.b, Color.a * Channels.a);
Graphics()->SetColor(Color);
int StartY = (int)(ScreenY0 / Scale) - 1;
int StartX = (int)(ScreenX0 / Scale) - 1;
@ -540,8 +516,7 @@ void CRenderTools::RenderTileRectangle(int RectX, int RectY, int RectW, int Rect
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}
void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags,
ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const
void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const
{
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
@ -551,17 +526,12 @@ void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, Color
float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
float FinalTilesetScale = FinalTileSize / TilePixelSize;
ColorRGBA Channels(1.f, 1.f, 1.f, 1.f);
if(ColorEnv >= 0)
{
pfnEval(ColorEnvOffset, ColorEnv, Channels, pUser);
}
if(Graphics()->HasTextureArraysSupport())
Graphics()->QuadsTex3DBegin();
else
Graphics()->QuadsBegin();
Graphics()->SetColor(Color.r * Channels.r, Color.g * Channels.g, Color.b * Channels.b, Color.a * Channels.a);
Graphics()->SetColor(Color);
const bool ColorOpaque = Color.a > 254.0f / 255.0f;
int StartY = (int)(ScreenY0 / Scale) - 1;
int StartX = (int)(ScreenX0 / Scale) - 1;
@ -611,7 +581,7 @@ void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, Color
unsigned char Flags = pTiles[c].m_Flags;
bool Render = false;
if(Flags & TILEFLAG_OPAQUE && Color.a * Channels.a > 254.0f / 255.0f)
if(ColorOpaque && Flags & TILEFLAG_OPAQUE)
{
if(RenderFlags & LAYERRENDERFLAG_OPAQUE)
Render = true;

View file

@ -94,20 +94,17 @@ enum
BUTTON_CONTEXT = 1,
};
void CEditor::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser)
void CEditor::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Result, size_t Channels, void *pUser)
{
CEditor *pThis = (CEditor *)pUser;
if(Env < 0 || Env >= (int)pThis->m_Map.m_vpEnvelopes.size())
{
Channels = ColorRGBA();
return;
}
std::shared_ptr<CEnvelope> pEnv = pThis->m_Map.m_vpEnvelopes[Env];
float t = pThis->m_AnimateTime;
t *= pThis->m_AnimateSpeed;
t += (TimeOffsetMillis / 1000.0f);
pEnv->Eval(t, Channels);
float Time = pThis->m_AnimateTime;
Time *= pThis->m_AnimateSpeed;
Time += (TimeOffsetMillis / 1000.0f);
pEnv->Eval(Time, Result, Channels);
}
/********************************************************
@ -2791,15 +2788,16 @@ void CEditor::DoQuadEnvelopes(const std::vector<CQuad> &vQuads, IGraphics::CText
const CPoint *pPivotPoint = &vQuads[j].m_aPoints[4];
for(size_t i = 0; i < apEnvelope[j]->m_vPoints.size() - 1; i++)
{
ColorRGBA Result;
apEnvelope[j]->Eval(apEnvelope[j]->m_vPoints[i].m_Time / 1000.0f + 0.000001f, Result);
ColorRGBA Result = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
apEnvelope[j]->Eval(apEnvelope[j]->m_vPoints[i].m_Time / 1000.0f + 0.000001f, Result, 2);
vec2 Pos0 = vec2(fx2f(pPivotPoint->x) + Result.r, fx2f(pPivotPoint->y) + Result.g);
const int Steps = 15;
for(int n = 1; n <= Steps; n++)
{
const float Time = mix(apEnvelope[j]->m_vPoints[i].m_Time, apEnvelope[j]->m_vPoints[i + 1].m_Time, (float)n / Steps);
apEnvelope[j]->Eval(Time / 1000.0f - 0.000001f, Result);
Result = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
apEnvelope[j]->Eval(Time / 1000.0f - 0.000001f, Result, 2);
vec2 Pos1 = vec2(fx2f(pPivotPoint->x) + Result.r, fx2f(pPivotPoint->y) + Result.g);
@ -6445,11 +6443,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
{
// add point
float Time = ScreenToEnvelopeX(View, UI()->MouseX());
ColorRGBA Channels;
ColorRGBA Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
if(in_range(Time, 0.0f, pEnvelope->EndTime()))
pEnvelope->Eval(Time, Channels);
else
Channels = {0, 0, 0, 0};
pEnvelope->Eval(Time, Channels, 4);
int FixedTime = std::round(Time * 1000.0f);
bool TimeFound = false;
@ -6642,12 +6638,13 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
float StepTime = (EndTime - StartTime) / static_cast<float>(Steps);
float StepSize = (EndX - StartX) / static_cast<float>(Steps);
ColorRGBA Channels;
pEnvelope->Eval(StartTime + StepTime, Channels);
ColorRGBA Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
pEnvelope->Eval(StartTime + StepTime, Channels, c + 1);
float PrevY = EnvelopeToScreenY(View, Channels[c]);
for(int i = 2; i < Steps; i++)
{
pEnvelope->Eval(StartTime + i * StepTime, Channels);
Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
pEnvelope->Eval(StartTime + i * StepTime, Channels, c + 1);
float CurrentY = EnvelopeToScreenY(View, Channels[c]);
IGraphics::CLineItem LineItem(

View file

@ -809,7 +809,7 @@ public:
int m_ShiftBy;
static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser);
static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Result, size_t Channels, void *pUser);
CLineInputBuffered<256> m_SettingsCommandInput;
CMapSettingsBackend m_MapSettingsBackend;

View file

@ -98,10 +98,10 @@ std::pair<float, float> CEnvelope::GetValueRange(int ChannelMask)
return {Bottom, Top};
}
int CEnvelope::Eval(float Time, ColorRGBA &Color)
void CEnvelope::Eval(float Time, ColorRGBA &Result, size_t Channels)
{
CRenderTools::RenderEvalEnvelope(&m_PointsAccess, GetChannels(), std::chrono::nanoseconds((int64_t)((double)Time * (double)std::chrono::nanoseconds(1s).count())), Color);
return GetChannels();
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)

View file

@ -21,7 +21,7 @@ public:
explicit CEnvelope(int NumChannels);
std::pair<float, float> GetValueRange(int ChannelMask);
int Eval(float Time, ColorRGBA &Color);
void Eval(float Time, ColorRGBA &Result, size_t Channels);
void AddPoint(int Time, int v0, int v1 = 0, int v2 = 0, int v3 = 0);
float EndTime() const;
int GetChannels() const;

View file

@ -34,41 +34,31 @@ void CLayerSounds::Render(bool Tileset)
Graphics()->SetColor(0.6f, 0.8f, 1.0f, 0.4f);
for(const auto &Source : m_vSources)
{
float OffsetX = 0;
float OffsetY = 0;
if(Source.m_PosEnv >= 0)
{
ColorRGBA Channels;
CEditor::EnvelopeEval(Source.m_PosEnvOffset, Source.m_PosEnv, Channels, m_pEditor);
OffsetX = Channels.r;
OffsetY = Channels.g;
}
ColorRGBA Offset = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
CEditor::EnvelopeEval(Source.m_PosEnvOffset, Source.m_PosEnv, Offset, 2, m_pEditor);
const vec2 Position = vec2(fx2f(Source.m_Position.x) + Offset.r, fx2f(Source.m_Position.y) + Offset.g);
const float Falloff = Source.m_Falloff / 255.0f;
switch(Source.m_Shape.m_Type)
{
case CSoundShape::SHAPE_CIRCLE:
{
m_pEditor->Graphics()->DrawCircle(fx2f(Source.m_Position.x) + OffsetX, fx2f(Source.m_Position.y) + OffsetY,
Source.m_Shape.m_Circle.m_Radius, 32);
float Falloff = ((float)Source.m_Falloff / 255.0f);
m_pEditor->Graphics()->DrawCircle(Position.x, Position.y, Source.m_Shape.m_Circle.m_Radius, 32);
if(Falloff > 0.0f)
m_pEditor->Graphics()->DrawCircle(fx2f(Source.m_Position.x) + OffsetX, fx2f(Source.m_Position.y) + OffsetY,
Source.m_Shape.m_Circle.m_Radius * Falloff, 32);
{
m_pEditor->Graphics()->DrawCircle(Position.x, Position.y, Source.m_Shape.m_Circle.m_Radius * Falloff, 32);
}
break;
}
case CSoundShape::SHAPE_RECTANGLE:
{
float Width = fx2f(Source.m_Shape.m_Rectangle.m_Width);
float Height = fx2f(Source.m_Shape.m_Rectangle.m_Height);
m_pEditor->Graphics()->DrawRectExt(fx2f(Source.m_Position.x) + OffsetX - Width / 2, fx2f(Source.m_Position.y) + OffsetY - Height / 2,
Width, Height, 0.0f, IGraphics::CORNER_NONE);
float Falloff = ((float)Source.m_Falloff / 255.0f);
const float Width = fx2f(Source.m_Shape.m_Rectangle.m_Width);
const float Height = fx2f(Source.m_Shape.m_Rectangle.m_Height);
m_pEditor->Graphics()->DrawRectExt(Position.x - Width / 2, Position.y - Height / 2, Width, Height, 0.0f, IGraphics::CORNER_NONE);
if(Falloff > 0.0f)
m_pEditor->Graphics()->DrawRectExt(fx2f(Source.m_Position.x) + OffsetX - Falloff * Width / 2, fx2f(Source.m_Position.y) + OffsetY - Falloff * Height / 2,
Width * Falloff, Height * Falloff, 0.0f, IGraphics::CORNER_NONE);
{
m_pEditor->Graphics()->DrawRectExt(Position.x - Falloff * Width / 2, Position.y - Falloff * Height / 2, Width * Falloff, Height * Falloff, 0.0f, IGraphics::CORNER_NONE);
}
break;
}
}
@ -84,18 +74,10 @@ void CLayerSounds::Render(bool Tileset)
m_pEditor->RenderTools()->SelectSprite(SPRITE_AUDIO_SOURCE);
for(const auto &Source : m_vSources)
{
float OffsetX = 0;
float OffsetY = 0;
if(Source.m_PosEnv >= 0)
{
ColorRGBA Channels;
CEditor::EnvelopeEval(Source.m_PosEnvOffset, Source.m_PosEnv, Channels, m_pEditor);
OffsetX = Channels.r;
OffsetY = Channels.g;
}
m_pEditor->RenderTools()->DrawSprite(fx2f(Source.m_Position.x) + OffsetX, fx2f(Source.m_Position.y) + OffsetY, m_pEditor->MapView()->ScaleLength(s_SourceVisualSize));
ColorRGBA Offset = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
CEditor::EnvelopeEval(Source.m_PosEnvOffset, Source.m_PosEnv, Offset, 2, m_pEditor);
const vec2 Position = vec2(fx2f(Source.m_Position.x) + Offset.r, fx2f(Source.m_Position.y) + Offset.g);
m_pEditor->RenderTools()->DrawSprite(Position.x, Position.y, m_pEditor->MapView()->ScaleLength(s_SourceVisualSize));
}
Graphics()->QuadsEnd();

View file

@ -146,13 +146,14 @@ void CLayerTiles::Render(bool Tileset)
Texture = m_pEditor->GetTuneTexture();
Graphics()->TextureSet(Texture);
ColorRGBA Color = ColorRGBA(m_Color.r / 255.0f, m_Color.g / 255.0f, m_Color.b / 255.0f, m_Color.a / 255.0f);
ColorRGBA ColorEnv = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
CEditor::EnvelopeEval(m_ColorEnvOffset, m_ColorEnv, ColorEnv, 4, m_pEditor);
const ColorRGBA Color = ColorRGBA(m_Color.r / 255.0f, m_Color.g / 255.0f, m_Color.b / 255.0f, m_Color.a / 255.0f).Multiply(ColorEnv);
Graphics()->BlendNone();
m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_OPAQUE,
CEditor::EnvelopeEval, m_pEditor, m_ColorEnv, m_ColorEnvOffset);
m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_OPAQUE);
Graphics()->BlendNormal();
m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_TRANSPARENT,
CEditor::EnvelopeEval, m_pEditor, m_ColorEnv, m_ColorEnvOffset);
m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_TRANSPARENT);
// Render DDRace Layers
if(!Tileset)

View file

@ -916,7 +916,20 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
for(int e = 0; e < EnvNum; e++)
{
CMapItemEnvelope *pItem = (CMapItemEnvelope *)DataFile.GetItem(EnvStart + e);
std::shared_ptr<CEnvelope> pEnv = std::make_shared<CEnvelope>(pItem->m_Channels);
int Channels = pItem->m_Channels;
if(Channels <= 0 || Channels == 2 || Channels > CEnvPoint::MAX_CHANNELS)
{
// Fall back to showing all channels if the number of channels is unsupported
Channels = CEnvPoint::MAX_CHANNELS;
}
if(Channels != pItem->m_Channels)
{
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "Error: Envelope %d had an invalid number of channels, %d, which was changed to %d.", e, pItem->m_Channels, Channels);
ErrorHandler(aBuf);
}
std::shared_ptr<CEnvelope> pEnv = std::make_shared<CEnvelope>(Channels);
pEnv->m_vPoints.resize(pItem->m_NumPoints);
for(int p = 0; p < pItem->m_NumPoints; p++)
{

View file

@ -1610,8 +1610,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CU
if(SelectedIndex != FirstSelectedIndex && SelectedIndex != LastSelectedIndex)
{
CEnvPoint &CurrentPoint = pEnvelope->m_vPoints[SelectedIndex];
ColorRGBA Channels;
HelperEnvelope.Eval(CurrentPoint.m_Time / 1000.0f, Channels);
ColorRGBA Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
HelperEnvelope.Eval(CurrentPoint.m_Time / 1000.0f, Channels, 1);
int PrevValue = CurrentPoint.m_aValues[c];
CurrentPoint.m_aValues[c] = f2fx(Channels.r);
vpActions.push_back(std::make_shared<CEditorActionEnvelopeEditPoint>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, PrevValue, CurrentPoint.m_aValues[c]));