6067: Implement smooth zoom for editor r=heinrich5991 a=Robyt3

Port the smooth zoom code from the ingame camera to the editor with some minor adjustments.

The smooth zoom animation time can be adjusted with the existing `cl_smooth_zoom_time` config variable.

Closes #2525.

## Checklist

- [X] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [X] Tested in combination with possibly related configuration options: `cl_smooth_zoom_time`, `cl_limit_max_zoom_level` and `ed_zoom_target`
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2022-11-23 22:32:12 +00:00 committed by GitHub
commit d94a8ef399
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 113 additions and 47 deletions

View file

@ -969,10 +969,7 @@ void CEditor::DoToolbar(CUIRect ToolBar)
static int s_ZoomOutButton = 0;
if(DoButton_FontIcon(&s_ZoomOutButton, "-", 0, &Button, 0, "[NumPad-] Zoom out", IGraphics::CORNER_L))
{
if(m_ZoomLevel + 50 > 2000)
m_ZoomLevel = 2000;
else
m_ZoomLevel += 50;
ChangeZoom(50.0f);
}
TB_Top.VSplitLeft(25.0f, &Button, &TB_Top);
@ -981,17 +978,14 @@ void CEditor::DoToolbar(CUIRect ToolBar)
{
m_EditorOffsetX = 0;
m_EditorOffsetY = 0;
m_ZoomLevel = 100;
SetZoom(100.0f);
}
TB_Top.VSplitLeft(20.0f, &Button, &TB_Top);
static int s_ZoomInButton = 0;
if(DoButton_FontIcon(&s_ZoomInButton, "+", 0, &Button, 0, "[NumPad+] Zoom in", IGraphics::CORNER_R))
{
if(m_ZoomLevel - 50 < 10)
m_ZoomLevel = 10;
else
m_ZoomLevel -= 50;
ChangeZoom(-50.0f);
}
TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top);
@ -2401,7 +2395,7 @@ void CEditor::DoMapEditor(CUIRect View)
}
CLayerTiles *pT = static_cast<CLayerTiles *>(GetSelectedLayerType(0, LAYERTYPE_TILES));
if(m_ShowTileInfo && pT && pT->m_Visible && m_ZoomLevel <= 300)
if(m_ShowTileInfo && pT && pT->m_Visible && m_Zoom <= 300.0f)
{
GetSelectedGroup()->MapScreen();
pT->ShowInfo();
@ -5719,7 +5713,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar)
char aTimeStr[6];
str_timestamp_format(aTimeStr, sizeof(aTimeStr), "%H:%M");
str_format(aBuf, sizeof(aBuf), "X: %i, Y: %i, Z: %i, A: %.1f, G: %i %s", (int)UI()->MouseWorldX() / 32, (int)UI()->MouseWorldY() / 32, m_ZoomLevel, m_AnimateSpeed, m_GridFactor, aTimeStr);
str_format(aBuf, sizeof(aBuf), "X: %i, Y: %i, Z: %.1f, A: %.1f, G: %i %s", (int)UI()->MouseWorldX() / 32, (int)UI()->MouseWorldY() / 32, m_Zoom, m_AnimateSpeed, m_GridFactor, aTimeStr);
UI()->DoLabel(&Info, aBuf, 10.0f, TEXTALIGN_RIGHT);
static int s_CloseButton = 0;
@ -5784,14 +5778,14 @@ void CEditor::Render()
// do zooming
if(Input()->KeyPress(KEY_KP_MINUS) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)
m_ZoomLevel += 50;
ChangeZoom(50.0f);
if(Input()->KeyPress(KEY_KP_PLUS) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)
m_ZoomLevel -= 50;
ChangeZoom(-50.0f);
if(Input()->KeyPress(KEY_KP_MULTIPLY) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)
{
m_EditorOffsetX = 0;
m_EditorOffsetY = 0;
m_ZoomLevel = 100;
SetZoom(100.0f);
}
for(int i = KEY_1; i <= KEY_0; i++)
@ -6024,25 +6018,13 @@ void CEditor::Render()
if(m_Dialog == DIALOG_NONE && !m_MouseInsidePopup && UI()->MouseInside(&View))
{
// Determines in which direction to zoom.
int Zoom = 0;
if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP))
Zoom--;
if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN))
Zoom++;
if(Zoom != 0)
{
float OldLevel = m_ZoomLevel;
m_ZoomLevel = maximum(m_ZoomLevel + Zoom * 20, 10);
if(g_Config.m_ClLimitMaxZoomLevel)
m_ZoomLevel = minimum(m_ZoomLevel, 2000);
if(g_Config.m_EdZoomTarget)
ZoomMouseTarget((float)m_ZoomLevel / OldLevel);
}
ChangeZoom(20.0f);
if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP))
ChangeZoom(-20.0f);
}
m_WorldZoom = m_ZoomLevel / 100.0f;
UpdateZoom();
if(m_GuiActive)
RenderStatusbar(StatusBar);
@ -6116,8 +6098,9 @@ void CEditor::Reset(bool CreateDefault)
m_EditorOffsetX = 0.0f;
m_EditorOffsetY = 0.0f;
m_Zoom = 200.0f;
m_Zooming = false;
m_WorldZoom = 1.0f;
m_ZoomLevel = 200;
m_MouseDeltaX = 0;
m_MouseDeltaY = 0;
@ -6134,20 +6117,46 @@ void CEditor::Reset(bool CreateDefault)
int CEditor::GetLineDistance() const
{
int LineDistance = 512;
if(m_Zoom <= 100.0f)
return 16;
else if(m_Zoom <= 250.0f)
return 32;
else if(m_Zoom <= 450.0f)
return 64;
else if(m_Zoom <= 850.0f)
return 128;
else if(m_Zoom <= 1550.0f)
return 256;
else
return 512;
}
if(m_ZoomLevel <= 100)
LineDistance = 16;
else if(m_ZoomLevel <= 250)
LineDistance = 32;
else if(m_ZoomLevel <= 450)
LineDistance = 64;
else if(m_ZoomLevel <= 850)
LineDistance = 128;
else if(m_ZoomLevel <= 1550)
LineDistance = 256;
void CEditor::SetZoom(float Target)
{
Target = clamp(Target, MinZoomLevel(), MaxZoomLevel());
return LineDistance;
const float Now = Client()->LocalTime();
float Current = m_Zoom;
float Derivative = 0.0f;
if(m_Zooming)
{
const float Progress = ZoomProgress(Now);
Current = m_ZoomSmoothing.Evaluate(Progress);
Derivative = m_ZoomSmoothing.Derivative(Progress);
}
m_ZoomSmoothingTarget = Target;
m_ZoomSmoothing = CCubicBezier::With(Current, Derivative, 0.0f, m_ZoomSmoothingTarget);
m_ZoomSmoothingStart = Now;
m_ZoomSmoothingEnd = Now + g_Config.m_ClSmoothZoomTime / 1000.0f;
m_Zooming = true;
}
void CEditor::ChangeZoom(float Amount)
{
const float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom;
SetZoom(CurrentTarget + Amount);
}
void CEditor::ZoomMouseTarget(float ZoomFactor)
@ -6166,8 +6175,46 @@ void CEditor::ZoomMouseTarget(float ZoomFactor)
float Mwy = aPoints[1] + WorldHeight * (UI()->MouseY() / UI()->Screen()->h);
// adjust camera
m_WorldOffsetX += (Mwx - m_WorldOffsetX) * (1 - ZoomFactor);
m_WorldOffsetY += (Mwy - m_WorldOffsetY) * (1 - ZoomFactor);
m_WorldOffsetX += (Mwx - m_WorldOffsetX) * (1.0f - ZoomFactor);
m_WorldOffsetY += (Mwy - m_WorldOffsetY) * (1.0f - ZoomFactor);
}
void CEditor::UpdateZoom()
{
if(m_Zooming)
{
const float Time = Client()->LocalTime();
const float OldLevel = m_Zoom;
if(Time >= m_ZoomSmoothingEnd)
{
m_Zoom = m_ZoomSmoothingTarget;
m_Zooming = false;
}
else
{
m_Zoom = m_ZoomSmoothing.Evaluate(ZoomProgress(Time));
}
m_Zoom = clamp(m_Zoom, MinZoomLevel(), MaxZoomLevel());
if(g_Config.m_EdZoomTarget)
ZoomMouseTarget(m_Zoom / OldLevel);
}
m_WorldZoom = m_Zoom / 100.0f;
}
float CEditor::MinZoomLevel() const
{
return 10.0f;
}
float CEditor::MaxZoomLevel() const
{
return g_Config.m_ClLimitMaxZoomLevel ? 2000.0f : std::numeric_limits<float>::max();
}
float CEditor::ZoomProgress(float CurrentTime) const
{
return (CurrentTime - m_ZoomSmoothingStart) / (m_ZoomSmoothingEnd - m_ZoomSmoothingStart);
}
void CEditor::Goto(float X, float Y)

View file

@ -3,6 +3,7 @@
#ifndef GAME_EDITOR_EDITOR_H
#define GAME_EDITOR_EDITOR_H
#include <base/bezier.h>
#include <base/system.h>
#include <game/client/render.h>
@ -800,8 +801,10 @@ public:
m_EditorOffsetX = 0.0f;
m_EditorOffsetY = 0.0f;
m_Zoom = 200.0f;
m_Zooming = false;
m_WorldZoom = 1.0f;
m_ZoomLevel = 200;
m_LockMouse = false;
m_ShowMousePointer = true;
m_MouseDeltaX = 0;
@ -984,8 +987,16 @@ public:
float m_WorldOffsetY;
float m_EditorOffsetX;
float m_EditorOffsetY;
// Zooming
CCubicBezier m_ZoomSmoothing;
float m_ZoomSmoothingStart;
float m_ZoomSmoothingEnd;
bool m_Zooming;
float m_Zoom;
float m_ZoomSmoothingTarget;
float m_WorldZoom;
int m_ZoomLevel;
bool m_LockMouse;
bool m_ShowMousePointer;
bool m_GuiActive;
@ -1274,7 +1285,15 @@ public:
static const char *Explain(int ExplanationID, int Tile, int Layer);
int GetLineDistance() const;
// Zooming
void SetZoom(float Target);
void ChangeZoom(float Amount);
void ZoomMouseTarget(float ZoomFactor);
void UpdateZoom();
float ZoomProgress(float CurrentTime) const;
float MinZoomLevel() const;
float MaxZoomLevel() const;
static ColorHSVA ms_PickerColor;
static int ms_SVPicker;