diff --git a/GlitchyEditor/src/EditWindows/GameViewportWindow.bf b/GlitchyEditor/src/EditWindows/GameViewportWindow.bf index 242f264..83a2fa0 100644 --- a/GlitchyEditor/src/EditWindows/GameViewportWindow.bf +++ b/GlitchyEditor/src/EditWindows/GameViewportWindow.bf @@ -13,14 +13,17 @@ namespace GlitchyEditor.EditWindows { public const String s_WindowTitle = "Game"; - private ImGui.Vec2 _oldViewportSize = .(100, 100); + private float2 _availableViewportSize = .(100, 100); + private float2 _renderedViewportSize = .(100, 100); private bool _viewPortChanged; private bool _visible; private RenderTargetGroup _renderTarget ~ _?.ReleaseRef(); - public float2 ViewportSize => (float2)_oldViewportSize; + public float2 AvailableViewportSize => _availableViewportSize; + public float2 RenderedViewportSize => _renderedViewportSize; + // Occurs when the viewport is resized. public Event> ViewportSizeChanged ~ _.Dispose(); // Occurs when an entity was clicked. @@ -71,19 +74,23 @@ namespace GlitchyEditor.EditWindows _hasFocus = ImGui.IsWindowFocused(); + ShowMenuBar(); + if(_renderTarget != null) { - ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize); + float2 scalingFactor = CalculateFitScalingFactor(_availableViewportSize, _renderedViewportSize); + + float2 previewResolution = scalingFactor * _renderedViewportSize; + + float2 position = max(0.0f, _availableViewportSize / 2.0f - previewResolution / 2.0f) + (float2)ImGui.GetCursorPos(); + ImGui.SetCursorPos((ImGui.Vec2)position); + + ImGui.Image(_renderTarget.GetViewBinding(0), (ImGui.Vec2)previewResolution); } ImGui.End(); - if(_oldViewportSize != viewportSize) - { - ViewportSizeChanged.Invoke(this, (float2)viewportSize); - _viewPortChanged = true; - _oldViewportSize = viewportSize; - } + SetAvailableViewportSize((float2)viewportSize); } /// Provides the ImGui Drop target and handles dropped payload. @@ -102,5 +109,175 @@ namespace GlitchyEditor.EditWindows ImGui.EndDragDropTarget(); } } + + private void SetAvailableViewportSize(float2 viewportSize) + { + if(any(_availableViewportSize != viewportSize)) + { + _availableViewportSize = viewportSize; + + UpdateCustomResolution(); + } + } + + private void UpdateCustomResolution() + { + float2 newRenderViewport = _renderedViewportSize; + + switch (resolutionMode) + { + case .Window: + newRenderViewport = _availableViewportSize; + case .CustomAspect: + float scalingFactor = CalculateFitScalingFactor(_availableViewportSize, _customAspectRatio); + newRenderViewport = max(ceil(_customAspectRatio * scalingFactor), 16); + case .CustomResolution: + newRenderViewport = _customResolution; + case .SixteenToNine: + case .FullHd: + newRenderViewport = .(1920, 1080); + case .WQHD: + newRenderViewport = .(2560, 1440); + case .UltraHd: + newRenderViewport = .(3840, 2160); + } + + if (any(newRenderViewport != _renderedViewportSize)) + { + _viewPortChanged = true; + _renderedViewportSize = newRenderViewport; + ViewportSizeChanged.Invoke(this, (float2)_renderedViewportSize); + } + } + + private float CalculateFitScalingFactor(float2 outerBox, float2 innerBox) + { + float outerAspectRatio = outerBox.X / outerBox.Y; + float innerAspectRatio = innerBox.X / innerBox.Y; + + float scalingFactor; + + // If our target shape is wider than our viewport shape, we have to fit in x-direction + // otherwise fit in y-direction + if (innerAspectRatio > outerAspectRatio) + { + scalingFactor = outerBox.X / innerBox.X; + } + else + { + scalingFactor = outerBox.Y / innerBox.Y; + } + + return scalingFactor; + } + + private enum ResolutionMode + { + case Window; + case CustomAspect; + case CustomResolution; + case SixteenToNine; + case FullHd; + case WQHD; + case UltraHd; + + public StringView GetPreviewName() + { + switch (this) + { + case .Window: + return "Window"; + case .CustomAspect: + return "Custom Aspect Ratio"; + case .CustomResolution: + return "Custom Resolution"; + case .SixteenToNine: + return "16:9"; + case .FullHd: + return "1080p (Full HD)"; + case .WQHD: + return "1440p (WQHD)"; + case .UltraHd: + return "2160p (4k)"; + } + } + } + + private float2 _customAspectRatio = .(16, 9); + private float2 _customResolution = .(-1, -1); + + ResolutionMode resolutionMode = .Window; + + private void ShowMenuBar() + { + if (ImGui.BeginMenuBar()) + { + ImGui.TextUnformatted("Resolution: "); + if (ImGui.BeginCombo("##resolution", resolutionMode.GetPreviewName().ToScopeCStr!(), .WidthFitPreview)) + { + for (ResolutionMode mode in Enum.EnumValuesEnumerator()) + { + if (ImGui.Selectable(mode.GetPreviewName().ToScopeCStr!(), mode == resolutionMode)) + { + resolutionMode = mode; + + if (mode == .CustomResolution && any(_customResolution == -1)) + { + _customResolution = _availableViewportSize; + } + + UpdateCustomResolution(); + } + } + + ImGui.EndCombo(); + } + + float itemWidth = ImGui.CalcTextSize("5.1234").x; + ImGui.PushItemWidth(itemWidth); + + if (resolutionMode == .CustomAspect) + { + if (ImGui.DragFloat("##aspectX", &_customAspectRatio.X, 1, 1.0f, 16384.0f, format: "%.4g")) + { + UpdateCustomResolution(); + } + if (ImGui.DragFloat("##aspectY", &_customAspectRatio.Y, 1, 1.0f, 16384.0f, format: "%.4g")) + { + UpdateCustomResolution(); + } + + ImGui.TextUnformatted(scope $"({_renderedViewportSize.X}px x {_renderedViewportSize.Y}px)"); + } + else if (resolutionMode == .CustomResolution) + { + int32 resX = (int32)_customResolution.X; + if (ImGui.DragInt("##resolutionX", &resX, 1, 1)) + { + _customResolution.X = resX; + UpdateCustomResolution(); + + } + int32 resY = (int32)_customResolution.Y; + if (ImGui.DragInt("##resolutionY", &resY, 1, 1)) + { + _customResolution.Y = resY; + UpdateCustomResolution(); + } + + int gcd = gcd((int)_customResolution.X, (int)_customResolution.Y); + + ImGui.TextUnformatted(scope $"({_customResolution.X / gcd}:{_customResolution.Y / gcd})"); + } + else + { + ImGui.TextUnformatted(scope $"({_renderedViewportSize.X}px x {_renderedViewportSize.Y}px)"); + } + + ImGui.PopItemWidth(); + + ImGui.EndMenuBar(); + } + } } } diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 1b66957..0060fb5 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -365,7 +365,7 @@ namespace GlitchyEditor ColorTargetDescriptions = TargetDescription[]( .(.R8G8B8A8_UNorm)) }); - _editorViewportTarget.[Friend]Identifier = "Game Viewport Target"; + _gameViewportTarget.[Friend]Identifier = "Game Viewport Target"; _editorIcons = new EditorIcons("Resources/Textures/EditorIcons.dds", .(64, 64)); _editorIcons.SamplerState = SamplerStateManager.AnisotropicClamp; @@ -460,8 +460,8 @@ namespace GlitchyEditor { _gameSceneRenderer.Scene = _activeScene; - _activeScene.SetViewportSize((uint32)_editor.GameViewportWindow.ViewportSize.X, (uint32)_editor.GameViewportWindow.ViewportSize.Y); - _gameSceneRenderer.SetViewportSize((uint32)_editor.GameViewportWindow.ViewportSize.X, (uint32)_editor.GameViewportWindow.ViewportSize.Y); + _activeScene.SetViewportSize((uint32)_editor.GameViewportWindow.RenderedViewportSize.X, (uint32)_editor.GameViewportWindow.RenderedViewportSize.Y); + _gameSceneRenderer.SetViewportSize((uint32)_editor.GameViewportWindow.RenderedViewportSize.X, (uint32)_editor.GameViewportWindow.RenderedViewportSize.Y); RenderCommand.Clear(_gameViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); _gameSceneRenderer.RenderRuntime(gameTime, _gameViewportTarget); @@ -960,10 +960,10 @@ namespace GlitchyEditor { /* * Update the viewport size because if the game windows size changed in - * "Game"-mode the updated aspect-rations will reset once we go back + * "Game"-mode the updated aspect-ratios will reset once we go back * into "Editor"-mode (because Game-Mode works on a copy of the scene). */ - GameViewportSizeChanged(null, _editor.GameViewportWindow.ViewportSize); + GameViewportSizeChanged(null, _editor.GameViewportWindow.RenderedViewportSize); } if (Application.Instance.Settings.EditorSettings.SwitchToEditorOnStop) diff --git a/GlitchyEngine/src/Math/Math.bf b/GlitchyEngine/src/Math/Math.bf index 81545ac..72840c4 100644 --- a/GlitchyEngine/src/Math/Math.bf +++ b/GlitchyEngine/src/Math/Math.bf @@ -864,4 +864,75 @@ static // transpose und determinante für Matrizen // sin, cos, tan, asin, acos, atan, atan2, cosh, sinh, tanh + +#region Fancy Stuff + + /// @brief Calculates the greates common divisor (greates common factor) of two unsgined integer a and b. + /// Based on https://en.wikipedia.org/wiki/Binary_GCD_algorithm + public static uint64 gcd(uint64 u, uint64 v) + { + if (u == 0) + return v; + if (v == 0) + return u; + + var u, v; + + // Essentially we want to know how many trailing zeros a and b have. + // TODO: If we have an efficient algorithm for that, this might become a lot faster. + uint64 trailingZerosU = 0; + while ((u % 2) == 0) + { + u >>= 1; + trailingZerosU++; + } + + uint64 trailingZerosV = 0; + while ((v % 2) == 0) + { + v >>= 1; + trailingZerosV++; + } + + uint64 k = Math.Min(trailingZerosU, trailingZerosV); + + while (true) + { + Log.EngineLogger.AssertDebug(u % 2 == 1, "a should be odd"); + Log.EngineLogger.AssertDebug(v % 2 == 1, "b should be odd"); + + if (u > v) + { + (u, v) = (v, u); + } + + v -= u; + // b is now even + + if (v == 0) + { + return u << k; + } + + // Make v odd again + while ((v % 2) == 0) + { + v >>= 1; + } + } + } + + /// @brief Calculates the greates common divisor (greates common factor) of two signed integer a and b. + /// Based on https://en.wikipedia.org/wiki/Binary_GCD_algorithm + public static int64 gcd(int64 u, int64 v) + { + return (int64)gcd((uint64)Math.Abs(u), (uint64)Math.Abs(v)); + } + + public static this() + { + int64 i = (int64)gcd(38, 123); + } + +#endregion } \ No newline at end of file