From 5aab9969ceac977460714ad833ad496e38d57708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 25 Feb 2024 23:33:11 +0100 Subject: [PATCH 1/4] Fix first envelope line segment not being rendered in editor Rendering was started with the second line segment after one time step has already passed. --- src/game/editor/editor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 89ec0b081..382e376cf 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -6639,9 +6639,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) float StepSize = (EndX - StartX) / static_cast(Steps); ColorRGBA Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f); - pEnvelope->Eval(StartTime + StepTime, Channels, c + 1); + pEnvelope->Eval(StartTime, Channels, c + 1); float PrevY = EnvelopeToScreenY(View, Channels[c]); - for(int i = 2; i < Steps; i++) + for(int i = 1; i < Steps; i++) { Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f); pEnvelope->Eval(StartTime + i * StepTime, Channels, c + 1); From cc1d43e5a05fd811d81b32ff4533a0809a7f7fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 25 Feb 2024 23:34:55 +0100 Subject: [PATCH 2/4] Fix division by zero when envelope point times overlap Effectively skip envelope points whose start time is equal to the start time of the next envelope point. --- src/game/client/render_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index ee3952ca2..fd4b734a1 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -267,7 +267,7 @@ void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, std:: { const CEnvPoint *pCurrentPoint = pPoints->GetPoint(i); const CEnvPoint *pNextPoint = pPoints->GetPoint(i + 1); - if(TimeMillis >= pCurrentPoint->m_Time && TimeMillis <= pNextPoint->m_Time) + if(TimeMillis >= pCurrentPoint->m_Time && TimeMillis < pNextPoint->m_Time) { const float Delta = pNextPoint->m_Time - pCurrentPoint->m_Time; float a = (float)(TimeMillis - pCurrentPoint->m_Time) / Delta; From 47a8156ca8d9efc6c8bf80188f1e47f78b486fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 25 Feb 2024 23:38:37 +0100 Subject: [PATCH 3/4] Fix inaccurate evaluation of bezier curve envelopes There is no need to divide the times by 1000 when evaluating bezier curves, as all times are relative and the division adds significant inaccuracy, to the point where evaluation of bezier curves goes completely wrong in some cases. Closes #8005. --- src/game/client/render_map.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index fd4b734a1..79a14de2e 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -300,11 +300,11 @@ void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, std:: 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])); - const vec2 p3 = vec2(pNextPoint->m_Time / 1000.0f, fx2f(pNextPoint->m_aValues[c])); + const vec2 p0 = vec2(pCurrentPoint->m_Time, fx2f(pCurrentPoint->m_aValues[c])); + const vec2 p3 = vec2(pNextPoint->m_Time, fx2f(pNextPoint->m_aValues[c])); - const vec2 OutTang = vec2(pCurrentPointBezier->m_aOutTangentDeltaX[c] / 1000.0f, fx2f(pCurrentPointBezier->m_aOutTangentDeltaY[c])); - const vec2 InTang = -vec2(pNextPointBezier->m_aInTangentDeltaX[c] / 1000.0f, fx2f(pNextPointBezier->m_aInTangentDeltaY[c])); + const vec2 OutTang = vec2(pCurrentPointBezier->m_aOutTangentDeltaX[c], fx2f(pCurrentPointBezier->m_aOutTangentDeltaY[c])); + const vec2 InTang = -vec2(pNextPointBezier->m_aInTangentDeltaX[c], fx2f(pNextPointBezier->m_aInTangentDeltaY[c])); vec2 p1 = p0 + OutTang; vec2 p2 = p3 - InTang; @@ -312,7 +312,7 @@ void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, std:: ValidateFCurve(p0, p1, p2, p3); // solve x(a) = time for a - a = clamp(SolveBezier(TimeMillis / 1000.0f, p0.x, p1.x, p2.x, p3.x), 0.0f, 1.0f); + a = clamp(SolveBezier(TimeMillis, p0.x, p1.x, p2.x, p3.x), 0.0f, 1.0f); // value = y(t) Result[c] = bezier(p0.y, p1.y, p2.y, p3.y, a); From ecfc18d1294ba5ad07308b74214c9f7b22045abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 25 Feb 2024 23:57:18 +0100 Subject: [PATCH 4/4] Minor refactoring of bezier curve evaluation - Remove `ValidateFCurve` function because it's small and only used once. - Remove unnecessary checks in `SolveBezier`, as all of these conditions are already checked before the function is called. - Remove unnecessary double negation of `InTang` to improve readability. - Use `double` literals for `double` comparisons instead of `float` literals. - Fix comments. --- src/game/client/render_map.cpp | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index 79a14de2e..07e8ae3ed 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -124,13 +124,6 @@ const CEnvPointBezier *CMapBasedEnvelopePointAccess::GetBezier(int Index) const return nullptr; } -static void ValidateFCurve(const vec2 &p0, vec2 &p1, vec2 &p2, const vec2 &p3) -{ - // validate the bezier curve - p1.x = clamp(p1.x, p0.x, p3.x); - p2.x = clamp(p2.x, p0.x, p3.x); -} - static double CubicRoot(double x) { if(x == 0.0) @@ -143,11 +136,6 @@ static double CubicRoot(double x) static float SolveBezier(float x, float p0, float p1, float p2, float p3) { - // check for valid f-curve - // we only take care of monotonic bezier curves, so there has to be exactly 1 real solution - if(!(p0 <= x && x <= p3) || !(p0 <= p1 && p1 <= p3) || !(p0 <= p2 && p2 <= p3)) - return 0.0f; - const double x3 = -p0 + 3.0 * p1 - 3.0 * p2 + p3; const double x2 = 3.0 * p0 - 6.0 * p1 + 3.0 * p2; const double x1 = -3.0 * p0 + 3.0 * p1; @@ -167,7 +155,7 @@ static float SolveBezier(float x, float p0, float p1, float p2, float p3) else if(x3 == 0.0) { // quadratic - // t * t + b * t +c = 0 + // t * t + b * t + c = 0 const double b = x1 / x2; const double c = x0 / x2; @@ -179,7 +167,7 @@ static float SolveBezier(float x, float p0, float p1, float p2, float p3) const double t = (-b + SqrtD) / 2.0; - if(0.0 <= t && t <= 1.0001f) + if(0.0 <= t && t <= 1.0001) return t; return (-b - SqrtD) / 2.0; } @@ -213,24 +201,24 @@ static float SolveBezier(float x, float p0, float p1, float p2, float p3) const double s = CubicRoot(-q); const double t = 2.0 * s - sub; - if(0.0 <= t && t <= 1.0001f) + if(0.0 <= t && t <= 1.0001) return t; return (-s - sub); } else { - // Casus irreductibilis ... ,_, + // Casus irreducibilis ... ,_, const double phi = std::acos(-q / std::sqrt(-(p * p * p))) / 3.0; const double s = 2.0 * std::sqrt(-p); const double t1 = s * std::cos(phi) - sub; - if(0.0 <= t1 && t1 <= 1.0001f) + if(0.0 <= t1 && t1 <= 1.0001) return t1; const double t2 = -s * std::cos(phi + pi / 3.0) - sub; - if(0.0 <= t2 && t2 <= 1.0001f) + if(0.0 <= t2 && t2 <= 1.0001) return t2; return -s * std::cos(phi - pi / 3.0) - sub; } @@ -304,12 +292,14 @@ void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, std:: const vec2 p3 = vec2(pNextPoint->m_Time, fx2f(pNextPoint->m_aValues[c])); const vec2 OutTang = vec2(pCurrentPointBezier->m_aOutTangentDeltaX[c], fx2f(pCurrentPointBezier->m_aOutTangentDeltaY[c])); - const vec2 InTang = -vec2(pNextPointBezier->m_aInTangentDeltaX[c], fx2f(pNextPointBezier->m_aInTangentDeltaY[c])); + const vec2 InTang = vec2(pNextPointBezier->m_aInTangentDeltaX[c], fx2f(pNextPointBezier->m_aInTangentDeltaY[c])); + vec2 p1 = p0 + OutTang; - vec2 p2 = p3 - InTang; + vec2 p2 = p3 + InTang; // validate bezier curve - ValidateFCurve(p0, p1, p2, p3); + p1.x = clamp(p1.x, p0.x, p3.x); + p2.x = clamp(p2.x, p0.x, p3.x); // solve x(a) = time for a a = clamp(SolveBezier(TimeMillis, p0.x, p1.x, p2.x, p3.x), 0.0f, 1.0f);