diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 90868aa97..ae4647204 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1082,7 +1082,7 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) static int s_ZoomOutButton = 0; if(DoButton_FontIcon(&s_ZoomOutButton, "-", 0, &Button, 0, "[NumPad-] Zoom out", IGraphics::CORNER_L)) { - ChangeZoom(50.0f); + m_ZoomMapView.ChangeZoom(50.0f); } TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); @@ -1091,14 +1091,14 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) { m_EditorOffsetX = 0; m_EditorOffsetY = 0; - SetZoom(100.0f); + m_ZoomMapView.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)) { - ChangeZoom(-50.0f); + m_ZoomMapView.ChangeZoom(-50.0f); } TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); @@ -2251,7 +2251,7 @@ void CEditor::DoQuadEnvelopes(const std::vector &vQuads, IGraphics::CText apEnvelope[i] = m_Map.m_vpEnvelopes[vQuads[i].m_PosEnv]; } - //Draw Lines + // Draw Lines Graphics()->TextureClear(); Graphics()->LinesBegin(); Graphics()->SetColor(80.0f / 255, 150.0f / 255, 230.f / 255, 0.5f); @@ -2260,7 +2260,7 @@ void CEditor::DoQuadEnvelopes(const std::vector &vQuads, IGraphics::CText if(!apEnvelope[j]) continue; - //QuadParams + // QuadParams const CPoint *pPivotPoint = &vQuads[j].m_aPoints[4]; for(size_t i = 0; i < apEnvelope[j]->m_vPoints.size() - 1; i++) { @@ -2286,7 +2286,7 @@ void CEditor::DoQuadEnvelopes(const std::vector &vQuads, IGraphics::CText Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); Graphics()->LinesEnd(); - //Draw Quads + // Draw Quads Graphics()->TextureSet(Texture); Graphics()->QuadsBegin(); @@ -2295,17 +2295,17 @@ void CEditor::DoQuadEnvelopes(const std::vector &vQuads, IGraphics::CText if(!apEnvelope[j]) continue; - //QuadParams + // QuadParams const CPoint *pPoints = vQuads[j].m_aPoints; for(size_t i = 0; i < apEnvelope[j]->m_vPoints.size(); i++) { - //Calc Env Position + // Calc Env Position float OffsetX = fx2f(apEnvelope[j]->m_vPoints[i].m_aValues[0]); float OffsetY = fx2f(apEnvelope[j]->m_vPoints[i].m_aValues[1]); float Rot = fx2f(apEnvelope[j]->m_vPoints[i].m_aValues[2]) / 360.0f * pi * 2; - //Set Colours + // Set Colours float Alpha = (m_SelectedQuadEnvelope == vQuads[j].m_PosEnv && m_SelectedEnvelopePoint == (int)i) ? 0.65f : 0.35f; IGraphics::CColorVertex aArray[4] = { IGraphics::CColorVertex(0, vQuads[j].m_aColors[0].r, vQuads[j].m_aColors[0].g, vQuads[j].m_aColors[0].b, Alpha), @@ -2314,7 +2314,7 @@ void CEditor::DoQuadEnvelopes(const std::vector &vQuads, IGraphics::CText IGraphics::CColorVertex(3, vQuads[j].m_aColors[3].r, vQuads[j].m_aColors[3].g, vQuads[j].m_aColors[3].b, Alpha)}; Graphics()->SetColorVertex(aArray, 4); - //Rotation + // Rotation if(Rot != 0) { static CPoint aRotated[4]; @@ -2330,14 +2330,14 @@ void CEditor::DoQuadEnvelopes(const std::vector &vQuads, IGraphics::CText Rotate(&vQuads[j].m_aPoints[4], &aRotated[3], Rot); } - //Set Texture Coords + // Set Texture Coords Graphics()->QuadsSetSubsetFree( fx2f(vQuads[j].m_aTexcoords[0].x), fx2f(vQuads[j].m_aTexcoords[0].y), fx2f(vQuads[j].m_aTexcoords[1].x), fx2f(vQuads[j].m_aTexcoords[1].y), fx2f(vQuads[j].m_aTexcoords[2].x), fx2f(vQuads[j].m_aTexcoords[2].y), fx2f(vQuads[j].m_aTexcoords[3].x), fx2f(vQuads[j].m_aTexcoords[3].y)); - //Set Quad Coords & Draw + // Set Quad Coords & Draw IGraphics::CFreeformItem Freeform( fx2f(pPoints[0].x) + OffsetX, fx2f(pPoints[0].y) + OffsetY, fx2f(pPoints[1].x) + OffsetX, fx2f(pPoints[1].y) + OffsetY, @@ -2518,7 +2518,7 @@ void CEditor::DoMapEditor(CUIRect View) } CLayerTiles *pT = static_cast(GetSelectedLayerType(0, LAYERTYPE_TILES)); - if(m_ShowTileInfo != SHOW_TILE_OFF && pT && pT->m_Visible && m_Zoom <= 300.0f) + if(m_ShowTileInfo != SHOW_TILE_OFF && pT && pT->m_Visible && m_ZoomMapView.GetZoom() <= 300.0f) { GetSelectedGroup()->MapScreen(); pT->ShowInfo(); @@ -3147,7 +3147,7 @@ void CEditor::DoMapEditor(CUIRect View) // possible screen sizes (white border) float aLastPoints[4]; - float Start = 1.0f; //9.0f/16.0f; + float Start = 1.0f; // 9.0f/16.0f; float End = 16.0f / 9.0f; const int NumSteps = 20; for(int i = 0; i <= NumSteps; i++) @@ -5601,6 +5601,128 @@ void CEditor::RemoveUnusedEnvelopes() } } +void CEditor::ZoomAdaptOffsetX(float ZoomFactor, const CUIRect &View) +{ + if(g_Config.m_EdZoomTarget) + { + float PosX = (UI()->MouseX() - View.x) / View.w; + m_OffsetEnvelopeX += (m_OffsetEnvelopeX - PosX) * (1.0f - ZoomFactor); + } + else + m_OffsetEnvelopeX += (m_OffsetEnvelopeX - 0.5f) * (1.0f - ZoomFactor); +} + +void CEditor::UpdateZoomEnvelopeX(const CUIRect &View) +{ + float OldZoom = m_ZoomEnvelopeX.GetZoom(); + if(m_ZoomEnvelopeX.UpdateZoom()) + ZoomAdaptOffsetX(m_ZoomEnvelopeX.GetZoom() / OldZoom, View); +} + +void CEditor::ZoomAdaptOffsetY(float ZoomFactor, const CUIRect &View) +{ + if(g_Config.m_EdZoomTarget) + { + float PosY = 1.0f - (UI()->MouseY() - View.y) / View.h; + m_OffsetEnvelopeY += (m_OffsetEnvelopeY - PosY) * (1.0f - ZoomFactor); + } + else + m_OffsetEnvelopeY += (m_OffsetEnvelopeY - 0.5f) * (1.0f - ZoomFactor); +} + +void CEditor::UpdateZoomEnvelopeY(const CUIRect &View) +{ + float OldZoom = m_ZoomEnvelopeY.GetZoom(); + if(m_ZoomEnvelopeY.UpdateZoom()) + ZoomAdaptOffsetY(m_ZoomEnvelopeY.GetZoom() / OldZoom, View); +} + +void CEditor::ResetZoomEnvelope(CEnvelope *pEnvelope, int ActiveChannels) +{ + pEnvelope->FindTopBottom(ActiveChannels); + float Top = pEnvelope->m_Top; + float Bottom = pEnvelope->m_Bottom; + float EndTime = pEnvelope->EndTime(); + float ValueRange = absolute(Top - Bottom); + + if(ValueRange < 0.1f) + { + // Set view to some sane default if range is too small + m_OffsetEnvelopeY = 0.5f - Top / 0.1f; + m_ZoomEnvelopeY.SetZoomInstant(0.1f); + } + else + { + m_ZoomEnvelopeY.SetZoomInstant(1.25f * ValueRange); + if(Top >= 0 && Bottom >= 0) + m_OffsetEnvelopeY = 0.1f - Bottom / m_ZoomEnvelopeY.GetZoom(); + else if(Top <= 0 && Bottom <= 0) + m_OffsetEnvelopeY = 0.1f - Bottom / m_ZoomEnvelopeY.GetZoom(); + else + m_OffsetEnvelopeY = 0.1f + 0.8f * absolute(Bottom) / ValueRange; + } + + if(EndTime < 0.1f) + { + m_OffsetEnvelopeX = 0.5f - EndTime / 0.1f; + m_ZoomEnvelopeX.SetZoomInstant(0.1f); + } + else + { + m_ZoomEnvelopeX.SetZoomInstant(1.25f * EndTime); + m_OffsetEnvelopeX = 0.1f; + } +} + +float fxt2f(int t) +{ + return t / 1000.0f; +} + +int f2fxt(float t) +{ + return static_cast(t * 1000.0f); +} + +float CEditor::ScreenToEnvelopeX(const CUIRect &View, float x) const +{ + return (x - View.x - View.w * m_OffsetEnvelopeX) / View.w * m_ZoomEnvelopeX.GetZoom(); +} + +float CEditor::EnvelopeToScreenX(const CUIRect &View, float x) const +{ + return View.x + View.w * m_OffsetEnvelopeX + x / m_ZoomEnvelopeX.GetZoom() * View.w; +} + +float CEditor::ScreenToEnvelopeY(const CUIRect &View, float y) const +{ + return (View.h - y + View.y) / View.h * m_ZoomEnvelopeY.GetZoom() - m_OffsetEnvelopeY * m_ZoomEnvelopeY.GetZoom(); +} + +float CEditor::EnvelopeToScreenY(const CUIRect &View, float y) const +{ + return View.y + View.h - y / m_ZoomEnvelopeY.GetZoom() * View.h - m_OffsetEnvelopeY * View.h; +} + +float CEditor::ScreenToEnvelopeDX(const CUIRect &View, float dx) +{ + return dx / Graphics()->ScreenWidth() * UI()->Screen()->w / View.w * m_ZoomEnvelopeX.GetZoom(); +} + +float CEditor::ScreenToEnvelopeDY(const CUIRect &View, float dy) +{ + return dy / Graphics()->ScreenHeight() * UI()->Screen()->h / View.h * m_ZoomEnvelopeY.GetZoom(); +} + +void CEditor::RemoveTimeOffsetEnvelope(CEnvelope *pEnvelope) +{ + int TimeOffset = pEnvelope->m_vPoints[0].m_Time; + for(auto &Point : pEnvelope->m_vPoints) + Point.m_Time -= TimeOffset; + + m_OffsetEnvelopeX += fxt2f(TimeOffset) / m_ZoomEnvelopeX.GetZoom(); +}; + void CEditor::RenderEnvelopeEditor(CUIRect View) { RenderExtraEditorDragBar(View, &m_EnvelopeEditorSplit); @@ -5614,6 +5736,17 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(m_SelectedEnvelope >= 0 && m_SelectedEnvelope < (int)m_Map.m_vpEnvelopes.size()) pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; + enum + { + OP_NONE, + OP_DRAG_POINT, + OP_DRAG_POINT_X, + OP_DRAG_POINT_Y + }; + static int s_Operation = OP_NONE; + static float s_AccurateDragValueX = 0.0f; + static float s_AccurateDragValueY = 0.0f; + CUIRect ToolBar, CurveBar, ColorBar; View.HSplitTop(15.0f, &ToolBar, &View); View.HSplitTop(15.0f, &CurveBar, &View); @@ -5623,6 +5756,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) bool CurrentEnvelopeSwitched = false; // do the toolbar + static int s_ActiveChannels = 0xf; { CUIRect Button; CEnvelope *pNewEnv = nullptr; @@ -5688,6 +5822,15 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; } + if(pEnvelope) + { + ToolBar.VSplitRight(5.0f, &ToolBar, nullptr); + ToolBar.VSplitRight(20.0f, &ToolBar, &Button); + static int s_ResetZoomButton = 0; + if(DoButton_FontIcon(&s_ResetZoomButton, FONT_ICON_MAGNIFYING_GLASS, 0, &Button, 0, "Reset zoom to default value", IGraphics::CORNER_ALL, 9.0f)) + ResetZoomEnvelope(pEnvelope, s_ActiveChannels); + } + // Margin on the right side ToolBar.VSplitRight(7.0f, &ToolBar, nullptr); } @@ -5771,15 +5914,19 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ShowColorBar = true; View.HSplitTop(20.0f, &ColorBar, &View); ColorBar.Margin(2.0f, &ColorBar); - RenderBackground(ColorBar, m_CheckerTexture, 16.0f, 1.0f); } RenderBackground(View, m_CheckerTexture, 32.0f, 0.1f); if(pEnvelope) { + if(m_ResetZoomEnvelope) + { + m_ResetZoomEnvelope = false; + ResetZoomEnvelope(pEnvelope, s_ActiveChannels); + } + static int s_EnvelopeEditorID = 0; - static int s_ActiveChannels = 0xf; ColorRGBA aColors[] = {ColorRGBA(1, 0.2f, 0.2f), ColorRGBA(0.2f, 1, 0.2f), ColorRGBA(0.2f, 0.2f, 1), ColorRGBA(1, 1, 0.2f)}; @@ -5833,22 +5980,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ToolBar.VSplitLeft(40.0f, &Button, &ToolBar); UI()->DoLabel(&Button, "Sync.", 10.0f, TEXTALIGN_ML); - float EndTime = pEnvelope->EndTime(); - if(EndTime < 1) - EndTime = 1; - - pEnvelope->FindTopBottom(s_ActiveChannels); - float Top = pEnvelope->m_Top; - float Bottom = pEnvelope->m_Bottom; - - if(Top < 1) - Top = 1; - if(Bottom >= 0) - Bottom = 0; - - float TimeScale = EndTime / View.w; - float ValueScale = (Top - Bottom) / View.h; - if(UI()->MouseInside(&View)) UI()->SetHotItem(&s_EnvelopeEditorID); @@ -5858,19 +5989,47 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(UI()->MouseButtonClicked(1)) { // add point - int Time = (int)(((UI()->MouseX() - View.x) * TimeScale) * 1000.0f); + float Time = ScreenToEnvelopeX(View, UI()->MouseX()); ColorRGBA Channels; - pEnvelope->Eval(Time / 1000.0f, Channels); - pEnvelope->AddPoint(Time, + if(in_range(Time, 0.0f, pEnvelope->EndTime())) + pEnvelope->Eval(Time, Channels); + else + Channels = {0, 0, 0, 0}; + pEnvelope->AddPoint(f2fxt(Time), f2fx(Channels.r), f2fx(Channels.g), f2fx(Channels.b), f2fx(Channels.a)); + + if(Time < 0) + RemoveTimeOffsetEnvelope(pEnvelope); m_Map.OnModify(); } + else if(UI()->MouseButton(2) || (UI()->MouseButton(0) && Input()->ModifierIsPressed())) + { + m_OffsetEnvelopeX += UI()->MouseDeltaX() / Graphics()->ScreenWidth() * UI()->Screen()->w / View.w; + m_OffsetEnvelopeY -= UI()->MouseDeltaY() / Graphics()->ScreenHeight() * UI()->Screen()->h / View.h; + } + if(Input()->ShiftIsPressed()) + { + if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) + m_ZoomEnvelopeY.ChangeZoom(0.1f * m_ZoomEnvelopeY.GetZoom()); + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) + m_ZoomEnvelopeY.ChangeZoom(-0.1f * m_ZoomEnvelopeY.GetZoom()); + } + else + { + if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) + m_ZoomEnvelopeX.ChangeZoom(0.1f * m_ZoomEnvelopeX.GetZoom()); + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) + m_ZoomEnvelopeX.ChangeZoom(-0.1f * m_ZoomEnvelopeX.GetZoom()); + } m_ShowEnvelopePreview = SHOWENV_SELECTED; - m_pTooltip = "Press right mouse button to create a new point"; + m_pTooltip = "Press right mouse button to create a new point. Use shift to change the zoom axis."; } + UpdateZoomEnvelopeX(View); + UpdateZoomEnvelopeY(View); + // keep track of selected point to handle value/time text input static const void *s_pSelectedPoint = nullptr; @@ -5886,36 +6045,36 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) for(int i = 0; i < (int)pEnvelope->m_vPoints.size(); i++) { - const float PosX = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime; - const float PosY = (fx2f(pEnvelope->m_vPoints[i].m_aValues[c]) - Bottom) / (Top - Bottom); + float PosX = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i].m_Time)); + float PosY = EnvelopeToScreenY(View, fx2f(pEnvelope->m_vPoints[i].m_aValues[c])); // Out-Tangent if(pEnvelope->m_vPoints[i].m_Curvetype == CURVETYPE_BEZIER) { - const float OutTangentX = PosX + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] / 1000.0f / EndTime; - const float OutTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c]) / (Top - Bottom); + float TangentX = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i].m_Time + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c])); + float TangentY = EnvelopeToScreenY(View, fx2f(pEnvelope->m_vPoints[i].m_aValues[c] + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c])); if(s_pSelectedPoint == &pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] || (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == i)) Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); else Graphics()->SetColor(aColors[c].r, aColors[c].g, aColors[c].b, 0.4f); - IGraphics::CLineItem LineItem(View.x + PosX * View.w, View.y + View.h - PosY * View.h, View.x + OutTangentX * View.w, View.y + View.h - OutTangentY * View.h); + IGraphics::CLineItem LineItem(TangentX, TangentY, PosX, PosY); Graphics()->LinesDraw(&LineItem, 1); } // In-Tangent if(i > 0 && pEnvelope->m_vPoints[i - 1].m_Curvetype == CURVETYPE_BEZIER) { - const float InTangentX = PosX + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] / 1000.0f / EndTime; - const float InTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c]) / (Top - Bottom); + float TangentX = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i].m_Time + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c])); + float TangentY = EnvelopeToScreenY(View, fx2f(pEnvelope->m_vPoints[i].m_aValues[c] + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c])); if(s_pSelectedPoint == &pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] || (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == i)) Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); else Graphics()->SetColor(aColors[c].r, aColors[c].g, aColors[c].b, 0.4f); - IGraphics::CLineItem LineItem(View.x + PosX * View.w, View.y + View.h - PosY * View.h, View.x + InTangentX * View.w, View.y + View.h - InTangentY * View.h); + IGraphics::CLineItem LineItem(TangentX, TangentY, PosX, PosY); Graphics()->LinesDraw(&LineItem, 1); } } @@ -5926,6 +6085,13 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) // render lines { + float EndTimeTotal = maximum(0.000001f, pEnvelope->EndTime()); + float EndX = clamp(EnvelopeToScreenX(View, EndTimeTotal), View.x, View.x + View.w); + float StartX = clamp(View.x + View.w * m_OffsetEnvelopeX, View.x, View.x + View.w); + + float EndTime = ScreenToEnvelopeX(View, EndX); + float StartTime = ScreenToEnvelopeX(View, StartX); + UI()->ClipEnable(&View); Graphics()->TextureClear(); Graphics()->LinesBegin(); @@ -5936,23 +6102,26 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) else Graphics()->SetColor(aColors[c].r * 0.5f, aColors[c].g * 0.5f, aColors[c].b * 0.5f, 1); - float PrevX = 0; + int Steps = static_cast(((EndX - StartX) / UI()->Screen()->w) * Graphics()->ScreenWidth()); + float StepTime = (EndTime - StartTime) / static_cast(Steps); + float StepSize = (EndX - StartX) / static_cast(Steps); + ColorRGBA Channels; - pEnvelope->Eval(0.000001f, Channels); - float PrevValue = Channels[c]; - - int Steps = (int)((View.w / UI()->Screen()->w) * Graphics()->ScreenWidth()); - for(int i = 1; i <= Steps; i++) + pEnvelope->Eval(StartTime + StepTime, Channels); + float PrevY = EnvelopeToScreenY(View, Channels[c]); + for(int i = 2; i < Steps; i++) { - float a = i / (float)Steps; - pEnvelope->Eval(a * EndTime, Channels); - float v = Channels[c]; - v = (v - Bottom) / (Top - Bottom); + pEnvelope->Eval(StartTime + i * StepTime, Channels); + float CurrentY = EnvelopeToScreenY(View, Channels[c]); - IGraphics::CLineItem LineItem(View.x + PrevX * View.w, View.y + View.h - PrevValue * View.h, View.x + a * View.w, View.y + View.h - v * View.h); + IGraphics::CLineItem LineItem( + StartX + (i - 1) * StepSize, + PrevY, + StartX + i * StepSize, + CurrentY); Graphics()->LinesDraw(&LineItem, 1); - PrevX = a; - PrevValue = v; + + PrevY = CurrentY; } } Graphics()->LinesEnd(); @@ -5963,11 +6132,11 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { for(int i = 0; i < (int)pEnvelope->m_vPoints.size() - 1; i++) { - float t0 = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime; - float t1 = pEnvelope->m_vPoints[i + 1].m_Time / 1000.0f / EndTime; + float t0 = fxt2f(pEnvelope->m_vPoints[i].m_Time); + float t1 = fxt2f(pEnvelope->m_vPoints[i + 1].m_Time); CUIRect CurveButton; - CurveButton.x = CurveBar.x + (t0 + (t1 - t0) * 0.5f) * CurveBar.w; + CurveButton.x = EnvelopeToScreenX(View, t0 + (t1 - t0) * 0.5f); CurveButton.y = CurveBar.y; CurveButton.h = CurveBar.h; CurveButton.w = CurveBar.h; @@ -5977,14 +6146,29 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) const char *pTypeName = "!?"; if(0 <= pEnvelope->m_vPoints[i].m_Curvetype && pEnvelope->m_vPoints[i].m_Curvetype < (int)std::size(apTypeName)) pTypeName = apTypeName[pEnvelope->m_vPoints[i].m_Curvetype]; - if(DoButton_Editor(pID, pTypeName, 0, &CurveButton, 0, "Switch curve type (N = step, L = linear, S = slow, F = fast, M = smooth, B = bezier)")) - pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + 1) % NUM_CURVETYPES; + + if(CurveButton.x >= View.x) + { + if(DoButton_Editor(pID, pTypeName, 0, &CurveButton, 0, "Switch curve type (N = step, L = linear, S = slow, F = fast, M = smooth, B = bezier)")) + pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + 1) % NUM_CURVETYPES; + } } } // render colorbar if(ShowColorBar) { + UI()->ClipEnable(&ColorBar); + + float StartX = maximum(EnvelopeToScreenX(View, 0), ColorBar.x); + float EndX = EnvelopeToScreenX(View, pEnvelope->EndTime()); + CUIRect BackgroundView{ + StartX, + ColorBar.y, + minimum(EndX - StartX, ColorBar.x + ColorBar.w - StartX), + ColorBar.h}; + RenderBackground(BackgroundView, m_CheckerTexture, 16.0f, 1.0f); + Graphics()->TextureClear(); Graphics()->QuadsBegin(); for(int i = 0; i < (int)pEnvelope->m_vPoints.size() - 1; i++) @@ -6004,13 +6188,14 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) IGraphics::CColorVertex(3, r0, g0, b0, a0)}; Graphics()->SetColorVertex(Array, 4); - float x0 = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime; - float x1 = pEnvelope->m_vPoints[i + 1].m_Time / 1000.0f / EndTime; + float x0 = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i].m_Time)); + float x1 = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i + 1].m_Time)); - IGraphics::CQuadItem QuadItem(ColorBar.x + x0 * ColorBar.w, ColorBar.y, (x1 - x0) * ColorBar.w, ColorBar.h); + IGraphics::CQuadItem QuadItem(x0, ColorBar.y, x1 - x0, ColorBar.h); Graphics()->QuadsDrawTL(&QuadItem, 1); } Graphics()->QuadsEnd(); + UI()->ClipDisable(); } // render handles @@ -6025,9 +6210,12 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) // update displayed text s_CurValueInput.SetFloat(0.0f); s_CurTimeInput.SetFloat(0.0f); + + m_ResetZoomEnvelope = true; } { + UI()->ClipEnable(&View); Graphics()->TextureClear(); Graphics()->QuadsBegin(); for(int c = 0; c < pEnvelope->GetChannels(); c++) @@ -6039,11 +6227,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { // point handle { - const float PosX = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime; - const float PosY = (fx2f(pEnvelope->m_vPoints[i].m_aValues[c]) - Bottom) / (Top - Bottom); CUIRect Final; - Final.x = View.x + PosX * View.w; - Final.y = View.y + View.h - PosY * View.h; + Final.x = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i].m_Time)); + Final.y = EnvelopeToScreenY(View, fx2f(pEnvelope->m_vPoints[i].m_aValues[c])); Final.x -= 2.0f; Final.y -= 2.0f; Final.w = 4.0f; @@ -6051,7 +6237,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) const void *pID = &pEnvelope->m_vPoints[i].m_aValues[c]; - if(UI()->MouseInside(&Final)) + if(UI()->MouseInside(&Final) && UI()->MouseInside(&View)) UI()->SetHotItem(pID); float ColorMod = 1.0f; @@ -6063,27 +6249,69 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) m_SelectedQuadEnvelope = -1; m_SelectedEnvelopePoint = -1; + s_Operation = OP_NONE; + UI()->SetActiveItem(nullptr); } else { if(Input()->ShiftIsPressed()) { - if(i != 0) + if(s_Operation == OP_NONE || s_Operation == OP_DRAG_POINT_Y) { - float DeltaX = m_MouseDeltaX * TimeScale * (Input()->ModifierIsPressed() ? 1.0f : 1000.0f); - DeltaX = DeltaX < 0 ? -std::ceil(-DeltaX) : std::ceil(DeltaX); - pEnvelope->m_vPoints[i].m_Time += (int)DeltaX; + s_Operation = OP_DRAG_POINT_X; + s_AccurateDragValueX = pEnvelope->m_vPoints[i].m_Time; + } + else + { + float DeltaX = ScreenToEnvelopeDX(View, UI()->MouseDeltaX()) * (Input()->ModifierIsPressed() ? 50.0f : 1000.0f); + s_AccurateDragValueX += DeltaX; - if(pEnvelope->m_vPoints[i].m_Time < pEnvelope->m_vPoints[i - 1].m_Time) + pEnvelope->m_vPoints[i].m_Time = std::round(s_AccurateDragValueX); + if(i == 0 && pEnvelope->m_vPoints[i].m_Time != 0) + { + RemoveTimeOffsetEnvelope(pEnvelope); + s_AccurateDragValueX = 0.0f; + } + if(i != 0 && pEnvelope->m_vPoints[i].m_Time < pEnvelope->m_vPoints[i - 1].m_Time) + { pEnvelope->m_vPoints[i].m_Time = pEnvelope->m_vPoints[i - 1].m_Time + 1; - if(i + 1 != pEnvelope->m_vPoints.size() && pEnvelope->m_vPoints[i].m_Time > pEnvelope->m_vPoints[i + 1].m_Time) + s_AccurateDragValueX = pEnvelope->m_vPoints[i - 1].m_Time + 1; + } + if(i < pEnvelope->m_vPoints.size() - 1 && pEnvelope->m_vPoints[i].m_Time > pEnvelope->m_vPoints[i + 1].m_Time) + { pEnvelope->m_vPoints[i].m_Time = pEnvelope->m_vPoints[i + 1].m_Time - 1; + s_AccurateDragValueX = pEnvelope->m_vPoints[i + 1].m_Time - 1; + } + + pEnvelope->m_vPoints[i].m_Time = clamp(pEnvelope->m_vPoints[i].m_Time, f2fxt(ScreenToEnvelopeX(View, View.x)), f2fxt(ScreenToEnvelopeX(View, View.x + View.w))); + s_AccurateDragValueX = clamp(s_AccurateDragValueX, f2fxt(ScreenToEnvelopeX(View, View.x)), f2fxt(ScreenToEnvelopeX(View, View.x + View.w))); } } else { - pEnvelope->m_vPoints[i].m_aValues[c] -= f2fx(m_MouseDeltaY * (Input()->ModifierIsPressed() ? 0.001f : ValueScale)); + if(s_Operation == OP_NONE || s_Operation == OP_DRAG_POINT_X) + { + s_Operation = OP_DRAG_POINT_Y; + s_AccurateDragValueY = pEnvelope->m_vPoints[i].m_aValues[c]; + } + else + { + float DeltaY = ScreenToEnvelopeDY(View, UI()->MouseDeltaY()) * (Input()->ModifierIsPressed() ? 51.2f : 1024.0f); + s_AccurateDragValueY -= DeltaY; + + pEnvelope->m_vPoints[i].m_aValues[c] = std::round(s_AccurateDragValueY); + if(pEnvelope->GetChannels() == 4) + { + pEnvelope->m_vPoints[i].m_aValues[c] = clamp(pEnvelope->m_vPoints[i].m_aValues[c], 0, 1024); + s_AccurateDragValueY = clamp(s_AccurateDragValueY, 0, 1024); + } + else + { + pEnvelope->m_vPoints[i].m_aValues[c] = clamp(pEnvelope->m_vPoints[i].m_aValues[c], f2fx(ScreenToEnvelopeY(View, View.y + View.h)), f2fx(ScreenToEnvelopeY(View, View.y))); + s_AccurateDragValueY = clamp(s_AccurateDragValueY, f2fx(ScreenToEnvelopeY(View, View.y + View.h)), f2fx(ScreenToEnvelopeY(View, View.y))); + } + } } m_SelectedQuadEnvelope = m_SelectedEnvelope; @@ -6104,7 +6332,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } // remove point - if(UI()->MouseButtonClicked(1)) + if(UI()->MouseButtonClicked(1) && pEnvelope->m_vPoints.size() > 2) { if(s_pSelectedPoint == pID) { @@ -6165,18 +6393,12 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) // tangent handles for bezier curves { - const float PosX = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime; - const float PosY = (fx2f(pEnvelope->m_vPoints[i].m_aValues[c]) - Bottom) / (Top - Bottom); - // Out-Tangent handle if(pEnvelope->m_vPoints[i].m_Curvetype == CURVETYPE_BEZIER) { - const float OutTangentX = PosX + (pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] / 1000.0f / EndTime); - const float OutTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c]) / (Top - Bottom); - CUIRect Final; - Final.x = View.x + OutTangentX * View.w; - Final.y = View.y + View.h - OutTangentY * View.h; + Final.x = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i].m_Time + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c])); + Final.y = EnvelopeToScreenY(View, fx2f(pEnvelope->m_vPoints[i].m_aValues[c] + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c])); Final.x -= 2.0f; Final.y -= 2.0f; Final.w = 4.0f; @@ -6187,7 +6409,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) const void *pID = &pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c]; float ColorMod = 1.0f; - if(UI()->MouseInside(&Final)) + if(UI()->MouseInside(&Final) && UI()->MouseInside(&View)) UI()->SetHotItem(pID); if(UI()->CheckActiveItem(pID)) @@ -6197,17 +6419,32 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) m_SelectedQuadEnvelope = -1; m_SelectedEnvelopePoint = -1; + s_Operation = OP_NONE; + UI()->SetActiveItem(nullptr); } else { - float DeltaX = m_MouseDeltaX * TimeScale * (Input()->ModifierIsPressed() ? 1.0f : 1000.0f); - DeltaX = DeltaX < 0 ? -std::ceil(-DeltaX) : std::ceil(DeltaX); - pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] += (int)DeltaX; - pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c] -= f2fx(m_MouseDeltaY * (Input()->ModifierIsPressed() ? 0.005f : ValueScale)); + if(s_Operation == OP_NONE) + { + s_Operation = OP_DRAG_POINT; + s_AccurateDragValueX = pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c]; + s_AccurateDragValueY = pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c]; + } + else + { + float DeltaX = ScreenToEnvelopeDX(View, UI()->MouseDeltaX()) * (Input()->ModifierIsPressed() ? 50.0f : 1000.0f); + float DeltaY = ScreenToEnvelopeDY(View, UI()->MouseDeltaY()) * (Input()->ModifierIsPressed() ? 51.2f : 1024.0f); + s_AccurateDragValueX += DeltaX; + s_AccurateDragValueY -= DeltaY; - // clamp time value - pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] = clamp(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c], 0, EndTime * 1000.0f - pEnvelope->m_vPoints[i].m_Time); + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] = std::round(s_AccurateDragValueX); + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c] = std::round(s_AccurateDragValueY); + + // clamp time value + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] = clamp(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c], 0, f2fxt(ScreenToEnvelopeX(View, View.x + View.w)) - pEnvelope->m_vPoints[i].m_Time); + s_AccurateDragValueX = clamp(s_AccurateDragValueX, 0, f2fxt(ScreenToEnvelopeX(View, View.x + View.w)) - pEnvelope->m_vPoints[i].m_Time); + } m_SelectedQuadEnvelope = m_SelectedEnvelope; m_ShowEnvelopePreview = SHOWENV_SELECTED; @@ -6242,7 +6479,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(pID == s_pSelectedPoint && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)) { - pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] = clamp(s_CurTimeInput.GetFloat() * 1000.0f - pEnvelope->m_vPoints[i].m_Time, 0, EndTime * 1000.0f - pEnvelope->m_vPoints[i].m_Time); + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] = clamp(s_CurTimeInput.GetFloat() * 1000.0f - pEnvelope->m_vPoints[i].m_Time, 0, pEnvelope->m_vPoints[i + 1].m_Time - pEnvelope->m_vPoints[i].m_Time); pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c] = f2fx(s_CurValueInput.GetFloat()) - pEnvelope->m_vPoints[i].m_aValues[c]; Updated = true; } @@ -6270,12 +6507,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) // In-Tangent handle if(i > 0 && pEnvelope->m_vPoints[i - 1].m_Curvetype == CURVETYPE_BEZIER) { - const float InTangentX = PosX + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] / 1000.0f / EndTime; - const float InTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c]) / (Top - Bottom); - CUIRect Final; - Final.x = View.x + InTangentX * View.w; - Final.y = View.y + View.h - InTangentY * View.h; + Final.x = EnvelopeToScreenX(View, fxt2f(pEnvelope->m_vPoints[i].m_Time + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c])); + Final.y = EnvelopeToScreenY(View, fx2f(pEnvelope->m_vPoints[i].m_aValues[c] + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c])); Final.x -= 2.0f; Final.y -= 2.0f; Final.w = 4.0f; @@ -6286,7 +6520,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) const void *pID = &pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c]; float ColorMod = 1.0f; - if(UI()->MouseInside(&Final)) + if(UI()->MouseInside(&Final) && UI()->MouseInside(&View)) UI()->SetHotItem(pID); if(UI()->CheckActiveItem(pID)) @@ -6296,17 +6530,32 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) m_SelectedQuadEnvelope = -1; m_SelectedEnvelopePoint = -1; + s_Operation = OP_NONE; + UI()->SetActiveItem(nullptr); } else { - float DeltaX = m_MouseDeltaX * TimeScale * (Input()->ModifierIsPressed() ? 1.0f : 1000.0f); - DeltaX = DeltaX < 0 ? -std::ceil(-DeltaX) : std::ceil(DeltaX); - pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] += (int)DeltaX; - pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c] -= f2fx(m_MouseDeltaY * (Input()->ModifierIsPressed() ? 0.005f : ValueScale)); + if(s_Operation == OP_NONE) + { + s_Operation = OP_DRAG_POINT; + s_AccurateDragValueX = pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c]; + s_AccurateDragValueY = pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c]; + } + else + { + float DeltaX = ScreenToEnvelopeDX(View, UI()->MouseDeltaX()) * (Input()->ModifierIsPressed() ? 50.0f : 1000.0f); + float DeltaY = ScreenToEnvelopeDY(View, UI()->MouseDeltaY()) * (Input()->ModifierIsPressed() ? 51.2f : 1024.0f); + s_AccurateDragValueX += DeltaX; + s_AccurateDragValueY -= DeltaY; - // clamp time value - pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] = clamp(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c], -pEnvelope->m_vPoints[i].m_Time, 0); + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] = std::round(s_AccurateDragValueX); + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c] = std::round(s_AccurateDragValueY); + + // clamp time value + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] = clamp(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c], f2fxt(ScreenToEnvelopeX(View, View.x)) - pEnvelope->m_vPoints[i].m_Time, 0); + s_AccurateDragValueX = clamp(s_AccurateDragValueX, f2fxt(ScreenToEnvelopeX(View, View.x)) - pEnvelope->m_vPoints[i].m_Time, 0); + } m_SelectedQuadEnvelope = m_SelectedEnvelope; m_ShowEnvelopePreview = SHOWENV_SELECTED; @@ -6369,6 +6618,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } } Graphics()->QuadsEnd(); + UI()->ClipDisable(); } if(s_pSelectedPoint != nullptr) @@ -6658,7 +6908,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar) char aTimeStr[6]; str_timestamp_format(aTimeStr, sizeof(aTimeStr), "%H:%M"); - str_format(aBuf, sizeof(aBuf), "X: %.1f, Y: %.1f, Z: %.1f, A: %.1f, G: %i %s", UI()->MouseWorldX() / 32.0f, UI()->MouseWorldY() / 32.0f, m_Zoom, m_AnimateSpeed, m_GridFactor, aTimeStr); + str_format(aBuf, sizeof(aBuf), "X: %.1f, Y: %.1f, Z: %.1f, A: %.1f, G: %i %s", UI()->MouseWorldX() / 32.0f, UI()->MouseWorldY() / 32.0f, m_ZoomMapView.GetZoom(), m_AnimateSpeed, m_GridFactor, aTimeStr); UI()->DoLabel(&Info, aBuf, 10.0f, TEXTALIGN_MR); static int s_CloseButton = 0; @@ -6715,14 +6965,14 @@ void CEditor::Render() { // handle zoom hotkeys if(Input()->KeyPress(KEY_KP_MINUS)) - ChangeZoom(50.0f); + m_ZoomMapView.ChangeZoom(50.0f); if(Input()->KeyPress(KEY_KP_PLUS)) - ChangeZoom(-50.0f); + m_ZoomMapView.ChangeZoom(-50.0f); if(Input()->KeyPress(KEY_KP_MULTIPLY)) { m_EditorOffsetX = 0; m_EditorOffsetY = 0; - SetZoom(100.0f); + m_ZoomMapView.SetZoom(100.0f); } // handle brush save/load hotkeys @@ -6937,12 +7187,12 @@ void CEditor::Render() if(m_Dialog == DIALOG_NONE && !UI()->IsPopupHovered() && (!m_GuiActive || UI()->MouseInside(&View))) { if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) - ChangeZoom(20.0f); + m_ZoomMapView.ChangeZoom(20.0f); if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) - ChangeZoom(-20.0f); + m_ZoomMapView.ChangeZoom(-20.0f); } - UpdateZoom(); + UpdateZoomWorld(); // Popup menus must be rendered before the statusbar, because UI elements in // popup menus can set tooltips, which are rendered in the status bar. @@ -7047,8 +7297,6 @@ 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_MouseDeltaX = 0; @@ -7064,52 +7312,26 @@ void CEditor::Reset(bool CreateDefault) m_ShowEnvelopePreview = SHOWENV_NONE; m_ShiftBy = 1; + + m_ResetZoomEnvelope = true; } int CEditor::GetLineDistance() const { - if(m_Zoom <= 100.0f) + if(m_ZoomMapView.GetZoom() <= 100.0f) return 16; - else if(m_Zoom <= 250.0f) + else if(m_ZoomMapView.GetZoom() <= 250.0f) return 32; - else if(m_Zoom <= 450.0f) + else if(m_ZoomMapView.GetZoom() <= 450.0f) return 64; - else if(m_Zoom <= 850.0f) + else if(m_ZoomMapView.GetZoom() <= 850.0f) return 128; - else if(m_Zoom <= 1550.0f) + else if(m_ZoomMapView.GetZoom() <= 1550.0f) return 256; else return 512; } -void CEditor::SetZoom(float Target) -{ - Target = clamp(Target, MinZoomLevel(), MaxZoomLevel()); - - const float Now = Client()->GlobalTime(); - 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_EdSmoothZoomTime / 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) { // zoom to the current mouse position @@ -7130,47 +7352,14 @@ void CEditor::ZoomMouseTarget(float ZoomFactor) m_WorldOffsetY += (Mwy - m_WorldOffsetY) * (1.0f - ZoomFactor); } -void CEditor::UpdateZoom() +void CEditor::UpdateZoomWorld() { - if(m_Zooming) - { - const float Time = Client()->GlobalTime(); - const float OldLevel = m_Zoom; - if(Time >= m_ZoomSmoothingEnd) - { - m_Zoom = m_ZoomSmoothingTarget; - m_Zooming = false; - } - else - { - m_Zoom = m_ZoomSmoothing.Evaluate(ZoomProgress(Time)); - if((OldLevel < m_ZoomSmoothingTarget && m_Zoom > m_ZoomSmoothingTarget) || (OldLevel > m_ZoomSmoothingTarget && m_Zoom < m_ZoomSmoothingTarget)) - { - m_Zoom = m_ZoomSmoothingTarget; - m_Zooming = false; - } - } - 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_EdLimitMaxZoomLevel ? 2000.0f : std::numeric_limits::max(); -} - -float CEditor::ZoomProgress(float CurrentTime) const -{ - return (CurrentTime - m_ZoomSmoothingStart) / (m_ZoomSmoothingEnd - m_ZoomSmoothingStart); + float OldLevel = m_ZoomMapView.GetZoom(); + m_ZoomMapView.SetZoomRange(10.0f, g_Config.m_EdLimitMaxZoomLevel ? 2000.0f : std::numeric_limits::max()); + float NewLevel = m_ZoomMapView.GetZoom(); + if(m_ZoomMapView.UpdateZoom() && g_Config.m_EdZoomTarget) + ZoomMouseTarget(NewLevel / OldLevel); + m_WorldZoom = NewLevel / 100.0f; } void CEditor::Goto(float X, float Y) @@ -7813,3 +8002,90 @@ void CEditorMap::MakeTuneLayer(CLayer *pLayer) m_pTuneLayer->m_pEditor = m_pEditor; m_pTuneLayer->m_Texture = m_pEditor->GetTuneTexture(); } + +CSmoothZoom::CSmoothZoom(float InitialZoom, float Min, float Max, CEditor *pEditor) +{ + m_Zoom = InitialZoom; + m_MinZoomLevel = Min; + m_MaxZoomLevel = Max; + m_pEditor = pEditor; + m_Zooming = false; +} + +void CSmoothZoom::SetZoom(float Target) +{ + Target = clamp(Target, m_MinZoomLevel, m_MaxZoomLevel); + + const float Now = m_pEditor->Client()->GlobalTime(); + 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_EdSmoothZoomTime / 1000.0f; + + m_Zooming = true; +} + +void CSmoothZoom::ChangeZoom(float Amount) +{ + const float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom; + SetZoom(CurrentTarget + Amount); +} + +bool CSmoothZoom::UpdateZoom() +{ + if(m_Zooming) + { + const float Time = m_pEditor->Client()->GlobalTime(); + const float OldLevel = m_Zoom; + if(Time >= m_ZoomSmoothingEnd) + { + m_Zoom = m_ZoomSmoothingTarget; + m_Zooming = false; + } + else + { + m_Zoom = m_ZoomSmoothing.Evaluate(ZoomProgress(Time)); + if((OldLevel < m_ZoomSmoothingTarget && m_Zoom > m_ZoomSmoothingTarget) || (OldLevel > m_ZoomSmoothingTarget && m_Zoom < m_ZoomSmoothingTarget)) + { + m_Zoom = m_ZoomSmoothingTarget; + m_Zooming = false; + } + } + m_Zoom = clamp(m_Zoom, m_MinZoomLevel, m_MaxZoomLevel); + + return true; + } + + return false; +} + +float CSmoothZoom::ZoomProgress(float CurrentTime) const +{ + return (CurrentTime - m_ZoomSmoothingStart) / (m_ZoomSmoothingEnd - m_ZoomSmoothingStart); +} + +float CSmoothZoom::GetZoom() const +{ + return m_Zoom; +} + +void CSmoothZoom::SetZoomRange(float Min, float Max) +{ + m_MinZoomLevel = Min; + m_MaxZoomLevel = Max; +} + +void CSmoothZoom::SetZoomInstant(float Target) +{ + m_Zooming = false; + m_Zoom = Target; +} diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 68df07fdd..f903a2fc0 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -790,6 +790,34 @@ public: const char *GetTempFileName() const { return m_aTempFileName; } }; +class CSmoothZoom +{ +public: + CSmoothZoom(float InitialZoom, float Min, float Max, CEditor *pEditor); + + void SetZoomRange(float Min, float Max); + void SetZoom(float Target); + void SetZoomInstant(float Target); + void ChangeZoom(float Amount); + bool UpdateZoom(); + float GetZoom() const; + +private: + float ZoomProgress(float CurrentTime) const; + + bool m_Zooming; + float m_Zoom; + CCubicBezier m_ZoomSmoothing; + float m_ZoomSmoothingTarget; + float m_ZoomSmoothingStart; + float m_ZoomSmoothingEnd; + + float m_MinZoomLevel; + float m_MaxZoomLevel; + + CEditor *m_pEditor; +}; + class CEditor : public IEditor { class IInput *m_pInput = nullptr; @@ -837,6 +865,9 @@ public: CRenderTools *RenderTools() { return &m_RenderTools; } CEditor() : + m_ZoomMapView(200.0f, 10.0f, 2000.0f, this), + m_ZoomEnvelopeX(1.0f, 0.1f, 600.0f, this), + m_ZoomEnvelopeY(640.0f, 0.1f, 32000.0f, this), m_TilesetPicker(16, 16) { m_EntitiesTexture.Invalidate(); @@ -885,10 +916,12 @@ public: m_EditorOffsetX = 0.0f; m_EditorOffsetY = 0.0f; - m_Zoom = 200.0f; - m_Zooming = false; m_WorldZoom = 1.0f; + m_ResetZoomEnvelope = true; + m_OffsetEnvelopeX = 0.1f; + m_OffsetEnvelopeY = 0.5f; + m_ShowMousePointer = true; m_MouseDeltaX = 0; m_MouseDeltaY = 0; @@ -1158,14 +1191,17 @@ public: float m_EditorOffsetY; // Zooming - CCubicBezier m_ZoomSmoothing; - float m_ZoomSmoothingStart; - float m_ZoomSmoothingEnd; - bool m_Zooming; - float m_Zoom; - float m_ZoomSmoothingTarget; + CSmoothZoom m_ZoomMapView; float m_WorldZoom; + CSmoothZoom m_ZoomEnvelopeX; + CSmoothZoom m_ZoomEnvelopeY; + + bool m_ResetZoomEnvelope; + + float m_OffsetEnvelopeX; + float m_OffsetEnvelopeY; + bool m_ShowMousePointer; bool m_GuiActive; @@ -1464,13 +1500,23 @@ public: 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; + void UpdateZoomWorld(); + + void ZoomAdaptOffsetX(float ZoomFactor, const CUIRect &View); + void UpdateZoomEnvelopeX(const CUIRect &View); + + void ZoomAdaptOffsetY(float ZoomFactor, const CUIRect &View); + void UpdateZoomEnvelopeY(const CUIRect &View); + + void ResetZoomEnvelope(CEnvelope *pEnvelope, int ActiveChannels); + void RemoveTimeOffsetEnvelope(CEnvelope *pEnvelope); + float ScreenToEnvelopeX(const CUIRect &View, float x) const; + float EnvelopeToScreenX(const CUIRect &View, float x) const; + float ScreenToEnvelopeY(const CUIRect &View, float y) const; + float EnvelopeToScreenY(const CUIRect &View, float y) const; + float ScreenToEnvelopeDX(const CUIRect &View, float dx); + float ScreenToEnvelopeDY(const CUIRect &View, float dy); // DDRace