diff --git a/.gitmodules b/.gitmodules index 57ed0f15..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +0,0 @@ -[submodule "MonoGame"] - path = MonoGame - url = https://github.com/Quaver/MonoGame -[submodule "SpriteFontPlus"] - path = SpriteFontPlus - url = https://github.com/Quaver/SpriteFontPlus diff --git a/MonoGame b/MonoGame deleted file mode 160000 index 3144463f..00000000 --- a/MonoGame +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3144463f4166ead839a5f8944f2f85406efdf047 diff --git a/MonoGame.Framework.dll b/MonoGame.Framework.dll new file mode 100644 index 00000000..fc7002b0 Binary files /dev/null and b/MonoGame.Framework.dll differ diff --git a/SpriteFontPlus b/SpriteFontPlus deleted file mode 160000 index bae26aa2..00000000 --- a/SpriteFontPlus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bae26aa243d85668d4f4fec0a790ff6e5e0ab176 diff --git a/Wobble.Extended/Wobble.Extended.csproj b/Wobble.Extended/Wobble.Extended.csproj index e77223f1..035dfd21 100644 --- a/Wobble.Extended/Wobble.Extended.csproj +++ b/Wobble.Extended/Wobble.Extended.csproj @@ -1,11 +1,10 @@  - netstandard2.1 + net8.0 - @@ -17,4 +16,10 @@ + + + ..\MonoGame.Framework.dll + + + diff --git a/Wobble.Resources/Wobble.Resources/Shaders/fast-blur.mgfxo b/Wobble.Resources/Wobble.Resources/Shaders/fast-blur.mgfxo index 8ec44cb8..3cdf7ca3 100644 Binary files a/Wobble.Resources/Wobble.Resources/Shaders/fast-blur.mgfxo and b/Wobble.Resources/Wobble.Resources/Shaders/fast-blur.mgfxo differ diff --git a/Wobble.Resources/Wobble.Resources/Shaders/fast-gaussian-blur.mgfxo b/Wobble.Resources/Wobble.Resources/Shaders/fast-gaussian-blur.mgfxo index fe95a5b0..d79a4d0c 100644 Binary files a/Wobble.Resources/Wobble.Resources/Shaders/fast-gaussian-blur.mgfxo and b/Wobble.Resources/Wobble.Resources/Shaders/fast-gaussian-blur.mgfxo differ diff --git a/Wobble.Resources/Wobble.Resources/Shaders/frosty-blur.mgfxo b/Wobble.Resources/Wobble.Resources/Shaders/frosty-blur.mgfxo index 472512ee..5dc1b0b7 100644 Binary files a/Wobble.Resources/Wobble.Resources/Shaders/frosty-blur.mgfxo and b/Wobble.Resources/Wobble.Resources/Shaders/frosty-blur.mgfxo differ diff --git a/Wobble.Resources/Wobble.Resources/Shaders/gaussian-blur.mgfxo b/Wobble.Resources/Wobble.Resources/Shaders/gaussian-blur.mgfxo index a0af8a9d..cdedcfc9 100644 Binary files a/Wobble.Resources/Wobble.Resources/Shaders/gaussian-blur.mgfxo and b/Wobble.Resources/Wobble.Resources/Shaders/gaussian-blur.mgfxo differ diff --git a/Wobble.Tests.Hotload/Wobble.Tests.Hotload.csproj b/Wobble.Tests.Hotload/Wobble.Tests.Hotload.csproj index 70aff9bd..0a79046a 100644 --- a/Wobble.Tests.Hotload/Wobble.Tests.Hotload.csproj +++ b/Wobble.Tests.Hotload/Wobble.Tests.Hotload.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net8.0 @@ -10,4 +10,10 @@ + + + ..\MonoGame.Framework.dll + + + diff --git a/Wobble.Tests.Resources/Wobble.Tests.Resources.csproj b/Wobble.Tests.Resources/Wobble.Tests.Resources.csproj index cc6b81be..4ee456f3 100644 --- a/Wobble.Tests.Resources/Wobble.Tests.Resources.csproj +++ b/Wobble.Tests.Resources/Wobble.Tests.Resources.csproj @@ -8,4 +8,8 @@ + + + + diff --git a/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/grey.fx b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/grey.fx new file mode 100644 index 00000000..d7824d94 --- /dev/null +++ b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/grey.fx @@ -0,0 +1,45 @@ +#if OPENGL + #define SV_POSITION POSITION + #define VS_SHADERMODEL vs_3_0 + #define PS_SHADERMODEL ps_3_0 +#else + #define VS_SHADERMODEL vs_5_0 + #define PS_SHADERMODEL ps_5_0 +#endif + +Texture2D SpriteTexture; + +sampler2D SpriteTextureSampler = sampler_state +{ + Texture = ; +}; + +struct VertexShaderOutput +{ + float4 Position : SV_POSITION; + float4 Color : COLOR0; + float2 TextureCoordinates : TEXCOORD0; +}; + + +float p_strength; + + +float4 MainPS(VertexShaderOutput input) : COLOR +{ + float4 color = tex2D(SpriteTextureSampler,input.TextureCoordinates) * input.Color; + + float4 greyColor; + greyColor.rgb = (color.r + color.g + color.b) / 3.0f; + greyColor.a = color.a; + + return lerp(color, greyColor, p_strength); +} + +technique SpriteDrawing +{ + pass P0 + { + PixelShader = compile PS_SHADERMODEL MainPS(); + } +}; \ No newline at end of file diff --git a/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/grey.mgfxo b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/grey.mgfxo new file mode 100644 index 00000000..5fac3477 Binary files /dev/null and b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/grey.mgfxo differ diff --git a/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/semi-transparent.fx b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/semi-transparent.fx new file mode 100644 index 00000000..02893c04 --- /dev/null +++ b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/semi-transparent.fx @@ -0,0 +1,53 @@ +#if OPENGL + #define SV_POSITION POSITION + #define VS_SHADERMODEL vs_3_0 + #define PS_SHADERMODEL ps_3_0 +#else + #define VS_SHADERMODEL vs_4_0_level_9_1 + #define PS_SHADERMODEL ps_4_0_level_9_1 +#endif + +Texture2D SpriteTexture; + +sampler2D SpriteTextureSampler = sampler_state +{ + Texture = ; +}; + +struct VertexShaderOutput +{ + float4 Position : SV_POSITION; + float4 Color : COLOR0; + float2 TextureCoordinates : TEXCOORD0; +}; + + +float2 p_dimensions; + +float2 p_position; +float2 p_rectangle; + +float p_alpha; + + +float4 MainPS(VertexShaderOutput input) : COLOR +{ + + float2 coord = input.TextureCoordinates * p_dimensions; + + if (coord.x <= p_position.x + p_rectangle.x && coord.x >= p_position.x && + coord.y <= p_position.y + p_rectangle.y && coord.y >= p_position.y) + { + input.Color.a = p_alpha; + } + + return tex2D(SpriteTextureSampler,input.TextureCoordinates) * input.Color; +} + +technique SpriteDrawing +{ + pass P0 + { + PixelShader = compile PS_SHADERMODEL MainPS(); + } +}; \ No newline at end of file diff --git a/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/semi-transparent.mgfxo b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/semi-transparent.mgfxo index a1afb1ea..f5f152f6 100644 Binary files a/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/semi-transparent.mgfxo and b/Wobble.Tests.Resources/Wobble.Tests.Resources/Shaders/semi-transparent.mgfxo differ diff --git a/Wobble.Tests.Unit/Wobble.Tests.Unit.csproj b/Wobble.Tests.Unit/Wobble.Tests.Unit.csproj index 4af965ee..3a7f7c1f 100644 --- a/Wobble.Tests.Unit/Wobble.Tests.Unit.csproj +++ b/Wobble.Tests.Unit/Wobble.Tests.Unit.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net8.0 false @@ -17,4 +17,10 @@ + + + ..\MonoGame.Framework.dll + + + diff --git a/Wobble.Tests/Content/gotham.xnb b/Wobble.Tests/Content/gotham.xnb new file mode 100644 index 00000000..e7f8d0ef Binary files /dev/null and b/Wobble.Tests/Content/gotham.xnb differ diff --git a/Wobble.Tests/Content/gotham_0.xnb b/Wobble.Tests/Content/gotham_0.xnb new file mode 100644 index 00000000..184d161a Binary files /dev/null and b/Wobble.Tests/Content/gotham_0.xnb differ diff --git a/Wobble.Tests/Screens/Selection/SelectionScreen.cs b/Wobble.Tests/Screens/Selection/SelectionScreen.cs index 48c026b9..1f5170b5 100644 --- a/Wobble.Tests/Screens/Selection/SelectionScreen.cs +++ b/Wobble.Tests/Screens/Selection/SelectionScreen.cs @@ -17,12 +17,17 @@ public class SelectionScreen : Screen public Dictionary TestCasesScreens { get; } = new Dictionary { {ScreenType.DrawingSprites, "Drawing Sprites"}, + {ScreenType.Rotation, "Rotation"}, + {ScreenType.DrawableScaling, "Drawable Scaling"}, + {ScreenType.MatrixDrawing, "Matrix Drawing"}, {ScreenType.EasingAnimations, "Easing Animations"}, + {ScreenType.Layering, "Layering"}, {ScreenType.Audio, "Audio"}, {ScreenType.Discord, "Discord Rich Pr."}, {ScreenType.Background, "Background Sprite"}, {ScreenType.Scrolling, "Scroll Container"}, {ScreenType.BlurContainer, "Blur Container"}, + {ScreenType.RenderTarget, "Render Target"}, {ScreenType.BlurredBackgroundImage, "Blurred BG Image"}, {ScreenType.TextInput, "Text Input"}, {ScreenType.SpriteMaskContainer, "Sprite Masking"}, @@ -48,11 +53,16 @@ public class SelectionScreen : Screen public enum ScreenType { DrawingSprites, + Rotation, + DrawableScaling, + MatrixDrawing, EasingAnimations, + Layering, Audio, Discord, Background, Scrolling, + RenderTarget, BlurContainer, BlurredBackgroundImage, TextInput, diff --git a/Wobble.Tests/Screens/Selection/SelectionScreenView.cs b/Wobble.Tests/Screens/Selection/SelectionScreenView.cs index 724656d2..1cbe1ee0 100644 --- a/Wobble.Tests/Screens/Selection/SelectionScreenView.cs +++ b/Wobble.Tests/Screens/Selection/SelectionScreenView.cs @@ -15,11 +15,16 @@ using Wobble.Tests.Screens.Tests.BlurContainer; using Wobble.Tests.Screens.Tests.BlurredBgImage; using Wobble.Tests.Screens.Tests.Discord; +using Wobble.Tests.Screens.Tests.DrawableScaling; using Wobble.Tests.Screens.Tests.DrawingSprites; using Wobble.Tests.Screens.Tests.EasingAnimations; using Wobble.Tests.Screens.Tests.Joystick; using Wobble.Tests.Screens.Tests.Imgui; +using Wobble.Tests.Screens.Tests.Layering; +using Wobble.Tests.Screens.Tests.MatrixDrawing; using Wobble.Tests.Screens.Tests.Primitives; +using Wobble.Tests.Screens.Tests.RenderTarget; +using Wobble.Tests.Screens.Tests.Rotation; using Wobble.Tests.Screens.Tests.Scaling; using Wobble.Tests.Screens.Tests.ScheduledUpdates; using Wobble.Tests.Screens.Tests.Scrolling; @@ -103,9 +108,21 @@ private void CreateSelectionButtons() case ScreenType.DrawingSprites: button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestDrawingSpritesScreen()); break; + case ScreenType.Rotation: + button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestRotationScreen()); + break; + case ScreenType.DrawableScaling: + button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestDrawableScalingScreen()); + break; + case ScreenType.MatrixDrawing: + button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestMatrixDrawingScreen()); + break; case ScreenType.EasingAnimations: button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestEasingAnimationsScreen()); break; + case ScreenType.Layering: + button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestLayerScreen()); + break; case ScreenType.Audio: button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestAudioScreen()); break; @@ -121,6 +138,9 @@ private void CreateSelectionButtons() case ScreenType.BlurContainer: button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestBlurContainerScreen()); break; + case ScreenType.RenderTarget: + button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestRenderTargetScreen()); + break; case ScreenType.BlurredBackgroundImage: button.Clicked += (o, e) => ScreenManager.ChangeScreen(new TestBlurredBackgroundImageScreen()); break; diff --git a/Wobble.Tests/Screens/Tests/DrawableScaling/TestDrawableScalingScreen.cs b/Wobble.Tests/Screens/Tests/DrawableScaling/TestDrawableScalingScreen.cs new file mode 100644 index 00000000..16362889 --- /dev/null +++ b/Wobble.Tests/Screens/Tests/DrawableScaling/TestDrawableScalingScreen.cs @@ -0,0 +1,17 @@ +using Wobble.Screens; +using Wobble.Tests.Screens.Tests.Rotation; + +namespace Wobble.Tests.Screens.Tests.DrawableScaling +{ + public class TestDrawableScalingScreen : Screen + { + /// + /// + /// + public sealed override ScreenView View { get; protected set; } + + /// + /// + public TestDrawableScalingScreen() => View = new TestDrawableScalingScreenView(this); + } +} diff --git a/Wobble.Tests/Screens/Tests/DrawableScaling/TestDrawableScalingScreenView.cs b/Wobble.Tests/Screens/Tests/DrawableScaling/TestDrawableScalingScreenView.cs new file mode 100644 index 00000000..8d4a8acb --- /dev/null +++ b/Wobble.Tests/Screens/Tests/DrawableScaling/TestDrawableScalingScreenView.cs @@ -0,0 +1,176 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Wobble.Graphics; +using Wobble.Graphics.Sprites; +using Wobble.Input; +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.DrawableScaling +{ + public class TestDrawableScalingScreenView : ScreenView + { + /// + /// Green box sprite. + /// + public Sprite GreenBox { get; } + + public Sprite TopLeft { get; } + + public Sprite Mid { get; } + + public Sprite BottomRight { get; } + + public SpriteText SpriteText { get; } + + /// + /// The background color for the scene. + /// + private Color BackgroundColor { get; set; } = Color.CornflowerBlue; + + private Vector2 _scale = Vector2.One; + private bool _rotating; + + private SpriteText DebugText { get; } + + public Vector2 Scale + { + get => _scale; + private set + { + _scale = value; + GreenBox.Scale = value; + // Mid.Scale = value; + // BottomRight.Scale = value; + DebugText.ScheduleUpdate(() => DebugText.Text = $"Scale: {value}"); + } + } + + /// + /// + /// + /// + public TestDrawableScalingScreenView(Screen screen) : base(screen) + { + #region GREEN_BOX + + GreenBox = new Sprite() + { + Parent = Container, + Size = new ScalableVector2(200, 400), + Tint = Color.Green, + Position = new ScalableVector2(0, 0), + Alignment = Alignment.MidCenter, + Pivot = Vector2.One + }; + + TopLeft = new Sprite() + { + Parent = GreenBox, + Size = new ScalableVector2(100, 200), + Tint = Color.Blue, + Position = new ScalableVector2(0, 0), + Alignment = Alignment.TopLeft, + // Pivot = new Vector2(1, 1) + }; + + BottomRight = new Sprite() + { + Parent = GreenBox, + Size = new ScalableVector2(100, 200), + Tint = Color.YellowGreen, + Position = new ScalableVector2(0, 0), + Alignment = Alignment.BotRight, + Pivot = new Vector2(1, 1) + }; + + Mid = new Sprite() + { + Parent = GreenBox, + Size = new ScalableVector2(50, 50), + Tint = Color.Red, + Position = new ScalableVector2(0, 0), + Alignment = Alignment.MidCenter, + }; + + SpriteText = new SpriteText("exo2-bold", "ABC", 20) + { + Parent = BottomRight, + Size = new ScalableVector2(50, 50), + Alignment = Alignment.MidCenter + }; + + GreenBox.AddBorder(Color.White, 2); + + TopLeft.AddBorder(Color.Red, 2); + + Mid.AddBorder(Color.LightYellow, 2); + + BottomRight.AddBorder(Color.Purple, 2); + + #endregion + + DebugText = new SpriteText("exo2-bold", "Hello, World!", 18) + { + Parent = Container, + Alignment = Alignment.TopRight + }; + } + + /// + /// + /// + /// + public override void Update(GameTime gameTime) + { + Container?.Update(gameTime); + + if (KeyboardManager.IsUniqueKeyPress(Keys.Up)) + Scale += new Vector2(0, 0.25f); + + if (KeyboardManager.IsUniqueKeyPress(Keys.Right)) + Scale += new Vector2(0.25f, 0); + + if (KeyboardManager.IsUniqueKeyPress(Keys.Down)) + Scale -= new Vector2(0, 0.25f); + + if (KeyboardManager.IsUniqueKeyPress(Keys.Left)) + Scale -= new Vector2(0.25f, 0); + + if (KeyboardManager.IsUniqueKeyPress(Keys.R)) + _rotating = !_rotating; + + if (KeyboardManager.IsUniqueKeyPress(Keys.OemCloseBrackets)) + GreenBox.Rotation += MathF.PI / 2; + + if (KeyboardManager.IsUniqueKeyPress(Keys.OemOpenBrackets)) + GreenBox.Rotation -= MathF.PI / 2; + + if (_rotating) + GreenBox.Rotation += 0.0001f; + } + + /// + /// + /// + /// + public override void Draw(GameTime gameTime) + { + GameBase.Game.GraphicsDevice.Clear(BackgroundColor); + Container?.Draw(gameTime); + + try + { + GameBase.Game.SpriteBatch.End(); + } + catch (Exception) + { + } + } + + /// + /// + /// + public override void Destroy() => Container?.Destroy(); + } +} \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/DrawingSprites/TestDrawingSpritesScreenView.cs b/Wobble.Tests/Screens/Tests/DrawingSprites/TestDrawingSpritesScreenView.cs index 65f6933b..83dca4e2 100644 --- a/Wobble.Tests/Screens/Tests/DrawingSprites/TestDrawingSpritesScreenView.cs +++ b/Wobble.Tests/Screens/Tests/DrawingSprites/TestDrawingSpritesScreenView.cs @@ -53,6 +53,11 @@ public class TestDrawingSpritesScreenView : ScreenView /// private Sprite SpriteWithShader { get; } + /// + /// Sprite with multiple shader batch passes + /// + private AnimatableSprite SpriteMultiPass { get; } + /// /// Dictates if the sprite with shader's width is decreasing in the shader animation /// @@ -160,7 +165,53 @@ public TestDrawingSpritesScreenView(Screen screen) : base(screen) }) } }; -#endregion + + #endregion + #region SPRITE_MULTI_PASS + + SpriteMultiPass = new AnimatableSprite(Textures.TestSpriteSheet) + { + Size = new ScalableVector2(200, 200), + Alignment = Alignment.BotCenter, + Parent = Container, + Y = -50, + X = 150, + AdditionalPasses = new List + { + new () + { + SortMode = SpriteSortMode.Deferred, + BlendState = BlendState.NonPremultiplied, + SamplerState = SamplerState.PointClamp, + DepthStencilState = DepthStencilState.None, + RasterizerState = RasterizerState.CullNone, + // The shader attached is lerping the colors of the sprite towards greyscale. + // p_strength = 0.3f means 30% grey and 70% colorful + Shader = new Shader( + GameBase.Game.Resources.Get("Wobble.Tests.Resources/Shaders/grey.mgfxo"), + new Dictionary + { + { "p_strength", 0.0f} + }) + } + }, + SpriteBatchOptions = new SpriteBatchOptions { BlendState = BlendState.Additive } + }; + SpriteMultiPass.StartLoop(Direction.Forward, 60); + + // As a comparison, the original animatable sprite + new AnimatableSprite(Textures.TestSpriteSheet) + { + Size = new ScalableVector2(200, 200), + Alignment = Alignment.BotCenter, + Parent = Container, + Y = -50, + X = -150, + SpriteBatchOptions = new SpriteBatchOptions { BlendState = BlendState.Additive } + }.StartLoop(Direction.Forward, 60); + + #endregion + } /// @@ -182,13 +233,7 @@ public override void Draw(GameTime gameTime) GameBase.Game.GraphicsDevice.Clear(BackgroundColor); Container?.Draw(gameTime); - try - { - GameBase.Game.SpriteBatch.End(); - } - catch (Exception) - { - } + GameBase.Game.TryEndBatch(); } /// @@ -209,6 +254,8 @@ private void PerformShaderedSpriteAnimation(GameTime gameTime) // The current transparent rectangle for the sprite. var currentTransparentRect = (Vector2) SpriteWithShader.SpriteBatchOptions.Shader.Parameters["p_rectangle"]; + // We vary the strength of grey scale shader too + var currentGreyStrength = (float) SpriteMultiPass.AdditionalPasses[0].Shader.Parameters["p_strength"]; // When the width of the box is fully shown, we want to set it to be decreasing here. if (SpriteWithShaderWidthDecreasing && currentTransparentRect.X >= SpriteWithShader.Width - 0.01) @@ -231,12 +278,15 @@ private void PerformShaderedSpriteAnimation(GameTime gameTime) // otherwise, we'll want to lerp it back to 0. var targetWidth = SpriteWithShaderWidthDecreasing ? SpriteWithShader.Width : 0; var targetHeight = SpriteWithShaderHeightDecreasing ? SpriteWithShader.Height : 0; + var targetStrength = SpriteWithShaderHeightDecreasing ? 1f : 0f; var newWidth = MathHelper.Lerp(currentTransparentRect.X, targetWidth, animTime); var newHeight = MathHelper.Lerp(currentTransparentRect.Y, targetHeight, animTime); + var newStrength = MathHelper.Lerp(currentGreyStrength, targetStrength, animTime); // Set the new rectangle shader parameter. SpriteWithShader.SpriteBatchOptions.Shader.SetParameter("p_rectangle", new Vector2(newWidth, newHeight), true); + SpriteMultiPass.AdditionalPasses[0].Shader.SetParameter("p_strength", newStrength, true); } } } diff --git a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs index 72c2ba34..21827ab4 100644 --- a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs +++ b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs @@ -63,5 +63,11 @@ protected override void RenderImguiLayout() Button.IsGloballyClickable = !ImGui.IsAnyItemHovered(); } } + + public override void Destroy() + { + base.Destroy(); + Button.IsGloballyClickable = true; + } } } \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs index 0d869cee..751bedee 100644 --- a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs +++ b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs @@ -39,16 +39,13 @@ public TestImGuiScreenView(Screen screen) : base(screen) // Make a button // ReSharper disable once ObjectCreationAsStatement - for (var i = 0; i < 5000; i++) + Boxes.Add(new ImageButton(WobbleAssets.WhiteBox, (sender, args) => Logger.Important("CLICKED", LogType.Runtime, false)) { - Boxes.Add(new ImageButton(WobbleAssets.WhiteBox, (sender, args) => Logger.Important("CLICKED", LogType.Runtime, false)) - { - Parent = Container, - Alignment = Alignment.MidCenter, - Size = new ScalableVector2(100, 100), - Tint = Color.Crimson, - }); - } + Parent = Container, + Alignment = Alignment.MidCenter, + Size = new ScalableVector2(100, 100), + Tint = Color.Crimson, + }); // ReSharper disable once ObjectCreationAsStatement var box2 = new Sprite() diff --git a/Wobble.Tests/Screens/Tests/Layering/TestLayerScreen.cs b/Wobble.Tests/Screens/Tests/Layering/TestLayerScreen.cs new file mode 100644 index 00000000..173c82a6 --- /dev/null +++ b/Wobble.Tests/Screens/Tests/Layering/TestLayerScreen.cs @@ -0,0 +1,16 @@ +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.Layering +{ + public class TestLayerScreen : Screen + { + /// + /// + /// + public sealed override ScreenView View { get; protected set; } + + /// + /// + public TestLayerScreen() => View = new TestLayerScreenView(this); + } +} \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/Layering/TestLayerScreenView.cs b/Wobble.Tests/Screens/Tests/Layering/TestLayerScreenView.cs new file mode 100644 index 00000000..d273379d --- /dev/null +++ b/Wobble.Tests/Screens/Tests/Layering/TestLayerScreenView.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Wobble.Graphics; +using Wobble.Graphics.Sprites; +using Wobble.Input; +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.Layering +{ + public class TestLayerScreenView : ScreenView + { + /// + /// Green box sprite. + /// + private readonly List _layers = new(); + + private readonly Layer _layer3Upper; + + private readonly List _sprites = new(); + + private readonly LayeredContainer _layeredContainer; + + private Sprite DefaultLayerSprite { get; set; } + + private Sprite NullLayerSprite { get; set; } + + /// + /// + /// + /// + public TestLayerScreenView(Screen screen) : base(screen) + { + _layeredContainer = new LayeredContainer { Parent = Container }; + for (var i = 0; i < 5; i++) + { + _layers.Add(_layeredContainer.LayerManager.NewLayer($"Layer {i}")); + } + + // desired layer order: default -> 0 -> 1 -> 2 -> 3 -> 4 -> Top + _layers[4].RequireBelow(_layeredContainer.LayerManager.TopLayer); + _layers[0].RequireAbove(_layeredContainer.LayerManager.DefaultLayer); + + _layers[3].RequireAbove(_layers[2]); + _layers[2].RequireAbove(_layers[0]); + _layers[0].RequireBelow(_layers[4]); + _layers[3].RequireBelow(_layers[4]); + _layers[0].RequireBelow(_layers[1]); + _layers[1].RequireBelow(_layers[2]); + + // Cycle would be ignored + _layers[4].RequireBelow(_layers[0]); + + _layers[2].Isolate(); + + (_, _layer3Upper) = _layers[3].Wrap(); + + for (var i = 4; i >= 0; i--) + { + var size = (5 - i) * 100; + var tint = (float)(i + 1) / 5; + var sprite = new Sprite + { + Size = new ScalableVector2(size, size), + Parent = _layeredContainer, + Tint = new Color(tint, tint, tint, 1), + Layer = _layers[i] + }; + _sprites.Add(sprite); + new SpriteText("exo2-bold", $"Layer {i}", 18) + { + Parent = sprite, + Alignment = Alignment.BotRight, + Tint = Color.Red + }; + } + new Sprite + { + Size = new ScalableVector2(150, 150), + Tint = new Color(0.9f, 0.9f, 0.9f, 1), + Layer = _layer3Upper, + Parent = _layeredContainer + }; + + DefaultLayerSprite = new Sprite + { + Parent = _layeredContainer, + Size = new ScalableVector2(200, 200), + Position = new ScalableVector2(450, 450), + Tint = Color.Green, + Layer = _layeredContainer.LayerManager.DefaultLayer + }; + + NullLayerSprite = new Sprite + { + Parent = Container, + Size = new ScalableVector2(100, 100), + Position = new ScalableVector2(450, 400), + Tint = new Color(128, 128, 0, 128) + }; + } + + /// + /// + /// + /// + public override void Update(GameTime gameTime) + { + Container?.Update(gameTime); + if (KeyboardManager.IsUniqueKeyPress(Keys.D)) + _layeredContainer.LayerManager.Dump(); + if (KeyboardManager.IsUniqueKeyPress(Keys.C)) + _layeredContainer.LayerManager.DumpConstraints(); + if (KeyboardManager.IsUniqueKeyPress(Keys.R)) + { + _layeredContainer.LayerManager.ResetOrder(); + LayerManager.RequireOrder(new [] + { + _layeredContainer.LayerManager.TopLayer, + _layers[3], + _layers[0], + _layeredContainer.LayerManager.BottomLayer + }); + } + } + + /// + /// + /// + /// + public override void Draw(GameTime gameTime) + { + GameBase.Game.GraphicsDevice.Clear(Color.CornflowerBlue); + Container?.Draw(gameTime); + } + + /// + /// + /// + public override void Destroy() => Container?.Destroy(); + } +} \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/MatrixDrawing/TestMatrixDrawingScreen.cs b/Wobble.Tests/Screens/Tests/MatrixDrawing/TestMatrixDrawingScreen.cs new file mode 100644 index 00000000..4d91e4de --- /dev/null +++ b/Wobble.Tests/Screens/Tests/MatrixDrawing/TestMatrixDrawingScreen.cs @@ -0,0 +1,16 @@ +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.MatrixDrawing +{ + public class TestMatrixDrawingScreen : Screen + { + /// + /// + /// + public sealed override ScreenView View { get; protected set; } + + /// + /// + public TestMatrixDrawingScreen() => View = new TestMatrixDrawingScreenView(this); + } +} diff --git a/Wobble.Tests/Screens/Tests/MatrixDrawing/TestMatrixDrawingScreenView.cs b/Wobble.Tests/Screens/Tests/MatrixDrawing/TestMatrixDrawingScreenView.cs new file mode 100644 index 00000000..2aa56b5e --- /dev/null +++ b/Wobble.Tests/Screens/Tests/MatrixDrawing/TestMatrixDrawingScreenView.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using MonoGame.Extended.Timers; +using Wobble.Assets; +using Wobble.Graphics; +using Wobble.Graphics.Shaders; +using Wobble.Graphics.Sprites; +using Wobble.Graphics.UI.Buttons; +using Wobble.Logging; +using Wobble.Screens; +using Wobble.Tests.Assets; +using Wobble.Window; + +namespace Wobble.Tests.Screens.Tests.MatrixDrawing +{ + public class TestMatrixDrawingScreenView : ScreenView + { + private Texture2D texture; + private Matrix2 quarterBottomRightTransform; + private Matrix transform3d; + private float rotation; + private SpriteText spriteText; + private ContinuousClock clock = new (TimeSpan.FromMilliseconds(16)); + + /// + /// + /// + /// + public TestMatrixDrawingScreenView(Screen screen) : base(screen) + { + texture = WobbleAssets.Wallpaper; + spriteText = new SpriteText("exo2-regular", "", 15) { Parent = Container }; + + clock.Tick += (sender, args) => + spriteText.ScheduleUpdate(() => spriteText.Text = $"Rot: {rotation:0.0000}"); + clock.Start(); + } + + /// + /// + /// + /// + public override void Update(GameTime gameTime) + { + clock.Update(gameTime); + rotation += (float)gameTime.ElapsedGameTime.TotalSeconds * 0.5f; + var axis = Vector3.Up + Vector3.Left; + axis.Normalize(); + var size = 200; + transform3d = Matrix.Identity + // * Matrix.CreateTranslation(-250, 0, 0) + * Matrix.CreateScale(size, size, 1) + * Matrix.CreateFromQuaternion(Quaternion.CreateFromAxisAngle(axis, rotation)) + * Matrix.CreateTranslation(250, 250, size * 1.5f) + // * Matrix.CreateTranslation(0, 0, -2) + // * Matrix.CreateTranslation(500, 500, 0) + ; + quarterBottomRightTransform = Matrix2.CreateScale(WindowManager.VirtualScreen / 2) * + new Matrix2(1, 0, 0.1f, 1, 0, 0) * + Matrix2.CreateTranslation(WindowManager.VirtualScreen / 2); + Container?.Update(gameTime); + } + + /// + /// + /// + /// + public override void Draw(GameTime gameTime) + { + GameBase.Game.GraphicsDevice.Clear(Color.Black); + Container?.Draw(gameTime); + + GameBase.Game.SpriteBatch.Draw(texture, ref quarterBottomRightTransform, null, layerDepth: rotation * 10); + GameBase.Game.SpriteBatch.Draw(texture, ref transform3d, null); + + try + { + GameBase.Game.SpriteBatch.End(); + } + catch (Exception) + { + } + } + + /// + /// + /// + public override void Destroy() => Container?.Destroy(); + } +} \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/Primitives/TestPrimitivesScreenView.cs b/Wobble.Tests/Screens/Tests/Primitives/TestPrimitivesScreenView.cs index f83ea37a..3accffdc 100644 --- a/Wobble.Tests/Screens/Tests/Primitives/TestPrimitivesScreenView.cs +++ b/Wobble.Tests/Screens/Tests/Primitives/TestPrimitivesScreenView.cs @@ -50,22 +50,23 @@ public TestPrimitivesScreenView(Screen screen) : base(screen) HorizontalLine.EndPosition = new Vector2(HorizontalLine.AbsolutePosition.X + 500, HorizontalLine.AbsolutePosition.Y); - Rect = new RectangleSprite(2) + Rect = new RectangleSprite(5) { Parent = Container, Alignment = Alignment.TopCenter, - Y = 55, - Size = new ScalableVector2(50, 20), - X = -25 + Y = 65, + Size = new ScalableVector2(50, 20) }; FilledRect = new FilledRectangleSprite() { Parent = Container, Alignment = Alignment.TopCenter, - Position = new ScalableVector2(-50, Rect.Y + Rect.Height + 5), + Position = new ScalableVector2(0, Rect.Y + Rect.Height + 10), Size = new ScalableVector2(100, 30) }; + Rect.AddBorder(Color.Green, 2); + PrimitiveLineBatch = new PrimitiveLineBatch(new List() { diff --git a/Wobble.Tests/Screens/Tests/RenderTarget/TestRenderTargetScreen.cs b/Wobble.Tests/Screens/Tests/RenderTarget/TestRenderTargetScreen.cs new file mode 100644 index 00000000..65ad5c83 --- /dev/null +++ b/Wobble.Tests/Screens/Tests/RenderTarget/TestRenderTargetScreen.cs @@ -0,0 +1,17 @@ +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.RenderTarget +{ + public class TestRenderTargetScreen : Screen + { + /// + /// + /// + public sealed override ScreenView View { get; protected set; } + + /// + /// + /// + public TestRenderTargetScreen() => View = new TestRenderTargetScreenView(this); + } +} \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/RenderTarget/TestRenderTargetScreenView.cs b/Wobble.Tests/Screens/Tests/RenderTarget/TestRenderTargetScreenView.cs new file mode 100644 index 00000000..089cead6 --- /dev/null +++ b/Wobble.Tests/Screens/Tests/RenderTarget/TestRenderTargetScreenView.cs @@ -0,0 +1,161 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Wobble.Graphics; +using Wobble.Graphics.Sprites; +using Wobble.Input; +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.RenderTarget +{ + public class TestRenderTargetScreenView : ScreenView + { + public Container RenderTargetContainer { get; } + + public Sprite MainComponentSprite { get; } + public Sprite SupposedMainCaptureRegion { get; } + public RenderProjectionSprite CustomProjectionSprite { get; } + + public SpriteText RotationText { get; } + + public SpriteText ScaleText { get; } + + private TimeSpan _textUpdateTimer = TimeSpan.Zero; + private readonly Padding _overflowRenderPadding = new(75, 75, 75, 75); + + public float Rotation + { + get => MainComponentSprite.Rotation; + set + { + MainComponentSprite.Rotation = -value; + CustomProjectionSprite.Rotation = value; + // if (RenderTargetContainer.DefaultProjectionSprite != null) + // { + // SupposedMainCaptureRegion.Rotation = value; + // RenderTargetContainer.DefaultProjectionSprite.Rotation = value; + // } + } + } + + public Vector2 Scale + { + get => MainComponentSprite.Scale; + set + { + MainComponentSprite.Scale = value; + // CustomProjectionSprite.Scale = value; + // if (RenderTargetContainer.DefaultProjectionSprite != null) + // { + // SupposedMainCaptureRegion.Scale = value; + // RenderTargetContainer.DefaultProjectionSprite.Scale = value; + // } + } + } + + /// + /// + /// + /// + public TestRenderTargetScreenView(Screen screen) : base(screen) + { + RenderTargetContainer = new Container() + { + Parent = Container, + Size = new ScalableVector2(250, 500), + Position = new ScalableVector2(100, 100) + }; + + SupposedMainCaptureRegion = new Sprite() + { + Parent = Container, + Size = new ScalableVector2(250, 500), + Position = new ScalableVector2(100, 100), + Tint = new Color(0, 255, 0, 50) + }; + + MainComponentSprite = new Sprite() + { + Parent = RenderTargetContainer, + Alignment = Alignment.TopRight, + Tint = Color.Red, + Position = new ScalableVector2(0, 250), + Size = new ScalableVector2(125, 250), + Pivot = new Vector2(0, 0) + }; + + CustomProjectionSprite = new RenderProjectionSprite() + { + Parent = Container, + Alignment = Alignment.MidRight, + Size = new ScalableVector2(125, 250), + Position = new ScalableVector2(-100, 0) + }; + + RenderTargetContainer.RenderTargetOptions.BackgroundColor = new Color(0, 0, 255, 50); + RenderTargetContainer.RenderTargetOptions.OverflowRenderPadding = _overflowRenderPadding; + CustomProjectionSprite.BindProjectionContainer(RenderTargetContainer); + + RotationText = new SpriteText("exo2-bold", $"Rotation: 0", 18) + { + Parent = Container, + Alignment = Alignment.TopCenter, + Y = 15, + }; + ScaleText = new SpriteText("exo2-bold", $"Scale: 0", 18) + { + Parent = Container, + Alignment = Alignment.TopCenter, + Y = 50, + }; + } + + /// + /// + /// + /// + public override void Update(GameTime gameTime) + { + Rotation = ((float)gameTime.TotalGameTime.TotalSeconds * 0.5f) % (2 * MathF.PI); + Scale = Vector2.One * MathF.Pow(MathF.Sin((float)gameTime.TotalGameTime.TotalSeconds), 1); + + if (KeyboardManager.IsUniqueKeyPress(Keys.C)) + { + if (RenderTargetContainer.RenderTargetOptions.RenderTarget.Value == null) + RenderTargetContainer.CastToRenderTarget(); + else + RenderTargetContainer.StopCasting(); + } + + Container?.Update(gameTime); + UpdateText(gameTime); + } + + private void UpdateText(GameTime gameTime) + { + _textUpdateTimer += gameTime.ElapsedGameTime; + + if (_textUpdateTimer >= TimeSpan.FromMilliseconds(16)) + { + RotationText.ScheduleUpdate(() => RotationText.Text = $"Rotation: {Rotation:0.000}"); + ScaleText.ScheduleUpdate(() => ScaleText.Text = $"Scale: {Scale}"); + _textUpdateTimer = TimeSpan.Zero; + } + } + + /// + /// + /// + /// + public override void Draw(GameTime gameTime) + { + GameBase.Game.GraphicsDevice.Clear(Color.CornflowerBlue); + Container?.Draw(gameTime); + } + + /// + /// + /// + public override void Destroy() => Container?.Destroy(); + } +} \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/Rotation/TestRotationScreen.cs b/Wobble.Tests/Screens/Tests/Rotation/TestRotationScreen.cs new file mode 100644 index 00000000..c221aa0a --- /dev/null +++ b/Wobble.Tests/Screens/Tests/Rotation/TestRotationScreen.cs @@ -0,0 +1,16 @@ +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.Rotation +{ + public class TestRotationScreen : Screen + { + /// + /// + /// + public sealed override ScreenView View { get; protected set; } + + /// + /// + public TestRotationScreen() => View = new TestRotationScreenView(this); + } +} diff --git a/Wobble.Tests/Screens/Tests/Rotation/TestRotationScreenView.cs b/Wobble.Tests/Screens/Tests/Rotation/TestRotationScreenView.cs new file mode 100644 index 00000000..4c8dd4fd --- /dev/null +++ b/Wobble.Tests/Screens/Tests/Rotation/TestRotationScreenView.cs @@ -0,0 +1,171 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using MonoGame.Extended.Timers; +using Wobble.Assets; +using Wobble.Graphics; +using Wobble.Graphics.Sprites; +using Wobble.Input; +using Wobble.Screens; + +namespace Wobble.Tests.Screens.Tests.Rotation +{ + public class TestRotationScreenView : ScreenView + { + /// + /// Green box sprite. + /// + public Sprite GreenBox { get; } + + public Sprite BlueBox { get; } + + public Sprite Shaft { get; } + + public Sprite CollisionBox { get; } + + public Sprite SpriteFixZRotation { get; } + + /// + /// The background color for the scene. + /// + private Color BackgroundColor { get; set; } = Color.CornflowerBlue; + + private bool _rotating = true; + + private SpriteText DebugText { get; } + + private float _increment = 0.0005f; + + private readonly ContinuousClock _clock = new(TimeSpan.FromMilliseconds(16)); + + /// + /// + /// + /// + public TestRotationScreenView(Screen screen) : base(screen) + { + #region GREEN_BOX + + GreenBox = new Sprite + { + Image = WobbleAssets.Wallpaper, + Parent = Container, + Size = new ScalableVector2(50, 100), + Tint = Color.Green, + Position = new ScalableVector2(200, 200), + Alignment = Alignment.MidCenter, + RotationAxis = Vector3.Normalize(new Vector3(0, 1, 1)), + Z = 200 + // Pivot = Vector2.Zero + }; + + BlueBox = new Sprite() + { + Parent = GreenBox, + Size = new ScalableVector2(100, 25), + Tint = Color.Blue, + Position = new ScalableVector2(50, 100), + Alignment = Alignment.TopLeft, + Pivot = new Vector2(1, 1) + }; + + Shaft = new Sprite() + { + Parent = GreenBox, + Size = new ScalableVector2(100 / MathF.Cos(25f / 100), 5), + Tint = Color.Blue, + Position = new ScalableVector2(50, 100), + Alignment = Alignment.TopLeft, + Rotation = MathF.Atan(25f / 100), + Pivot = Vector2.Zero + }; + + CollisionBox = new Sprite + { + Parent = Container, + Tint = new Color(255, 255, 255, 100), + Alignment = Alignment.TopLeft, + Size = new ScalableVector2(564, 880), + Position = new ScalableVector2(32, -56), + }; + + // The sprite should have a visually constant rotation + SpriteFixZRotation = new Sprite + { + Parent = GreenBox, + Image = WobbleAssets.Wallpaper, + Alignment = Alignment.MidCenter, + Size = new ScalableVector2(200, 50), + Position = new ScalableVector2(200, 100), + IndependentRotation = true, + Rotation = MathHelper.ToRadians(30), + RotationAxis = Vector3.Normalize(new Vector3(1, 0, 1)) + }; + + GreenBox.AddBorder(Color.White, 2); + + BlueBox.AddBorder(Color.Red, 2); + + CollisionBox.AddBorder(Color.Red, 2); + + #endregion + + DebugText = new SpriteText("exo2-bold", "Hello, World!", 18) + { + Parent = Container, Alignment = Alignment.TopRight + }; + _clock.Tick += (sender, args) => + DebugText.ScheduleUpdate(() => DebugText.Text = $"{GreenBox.Rotation:0.00} {_increment}/tick"); + _clock.Start(); + } + + /// + /// + /// + /// + public override void Update(GameTime gameTime) + { + _clock.Update(gameTime); + Container?.Update(gameTime); + + if (KeyboardManager.IsUniqueKeyPress(Keys.R)) + _rotating = !_rotating; + + if (KeyboardManager.IsUniqueKeyPress(Keys.OemCloseBrackets)) + _increment *= 2; + + if (KeyboardManager.IsUniqueKeyPress(Keys.OemOpenBrackets)) + _increment /= 2; + + if (_rotating) + { + GreenBox.Rotation += _increment; + BlueBox.Rotation += _increment; + CollisionBox.Rotation += _increment; + } + } + + /// + /// + /// + /// + public override void Draw(GameTime gameTime) + { + GameBase.Game.GraphicsDevice.Clear(BackgroundColor); + Container?.Draw(gameTime); + + try + { + GameBase.Game.SpriteBatch.End(); + } + catch (Exception) + { + } + } + + /// + /// + /// + public override void Destroy() => Container?.Destroy(); + } +} \ No newline at end of file diff --git a/Wobble.Tests/Screens/Tests/SpriteTextPlusNew/TestSpriteTextPlusScreenView.cs b/Wobble.Tests/Screens/Tests/SpriteTextPlusNew/TestSpriteTextPlusScreenView.cs index c88e718f..19c6b14e 100644 --- a/Wobble.Tests/Screens/Tests/SpriteTextPlusNew/TestSpriteTextPlusScreenView.cs +++ b/Wobble.Tests/Screens/Tests/SpriteTextPlusNew/TestSpriteTextPlusScreenView.cs @@ -3,7 +3,6 @@ using FontStashSharp; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using SpriteFontPlus; using Wobble.Graphics; using Wobble.Graphics.Animations; using Wobble.Graphics.Sprites.Text; diff --git a/Wobble.Tests/Wobble.Tests.csproj b/Wobble.Tests/Wobble.Tests.csproj index 881c7552..118d19ad 100644 --- a/Wobble.Tests/Wobble.Tests.csproj +++ b/Wobble.Tests/Wobble.Tests.csproj @@ -1,17 +1,29 @@  Exe - netcoreapp3.1 + net8.0 - + + + PreserveNewest + + + PreserveNewest + + + + + ..\MonoGame.Framework.dll + + \ No newline at end of file diff --git a/Wobble.Tests/WobbleTestsGame.cs b/Wobble.Tests/WobbleTestsGame.cs index b2a6596b..a08895b2 100644 --- a/Wobble.Tests/WobbleTestsGame.cs +++ b/Wobble.Tests/WobbleTestsGame.cs @@ -2,11 +2,16 @@ using System.Collections.Generic; using System.Drawing; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using Wobble.Graphics; using Wobble.Graphics.BitmapFonts; +using Wobble.Graphics.Sprites; using Wobble.Graphics.Sprites.Text; +using Wobble.Graphics.UI.Debugging; using Wobble.Input; using Wobble.IO; +using Wobble.Logging; using Wobble.Managers; using Wobble.Screens; using Wobble.Tests.Screens.Selection; @@ -19,6 +24,14 @@ public class WobbleTestsGame : WobbleGame { protected override bool IsReadyToUpdate { get; set; } + private FpsCounter FpsCounter { get; set; } + + private SpriteText WaylandState { get; set; } + + public WobbleTestsGame() : base(true) + { + } + /// /// /// @@ -83,6 +96,21 @@ protected override void LoadContent() IsReadyToUpdate = true; + FpsCounter = new FpsCounter(FontManager.LoadBitmapFont("Content/gotham"), 18) + { + Parent = GlobalUserInterface, + Alignment = Alignment.BotRight, + Size = new ScalableVector2(70, 30), + }; + + WaylandState = new SpriteText("exo2-semibold", $"Wayland: {WaylandVsync}", 18) + { + Parent = GlobalUserInterface, + Alignment = Alignment.BotRight, + Position = new ScalableVector2(0, -30), + Visible = OperatingSystem.IsLinux() + }; + // Once the assets load, we'll start the main screen ScreenManager.ChangeScreen(new SelectionScreen()); } @@ -107,6 +135,22 @@ protected override void Update(GameTime gameTime) // TODO: Your global update logic goes here. if (KeyboardManager.IsUniqueKeyPress(Keys.Escape)) ScreenManager.ChangeScreen(new SelectionScreen()); + + if (KeyboardManager.IsUniqueKeyPress(Keys.W) && OperatingSystem.IsLinux()) + { + WaylandVsync = !WaylandVsync; + WaylandState.ScheduleUpdate(() => WaylandState.Text = $"Wayland: {WaylandVsync}"); + } + + if (KeyboardManager.IsAltDown() && KeyboardManager.IsUniqueKeyPress(Keys.P)) + { + GameBase.DefaultSpriteBatchOptions.Fov = MathF.PI / 3 * 2; + GameBase.DefaultSpriteBatchOptions.ProjectionType = + GameBase.DefaultSpriteBatchOptions.ProjectionType == ProjectionType.Orthographic + ? ProjectionType.Perspective + : ProjectionType.Orthographic; + Logger.Important($"Current projection type: {GameBase.DefaultSpriteBatchOptions.ProjectionType}", LogType.Runtime); + } } protected override void Draw(GameTime gameTime) @@ -115,6 +159,7 @@ protected override void Draw(GameTime gameTime) return; base.Draw(gameTime); + GlobalUserInterface?.Draw(gameTime); } } } diff --git a/Wobble.sln b/Wobble.sln index 5148fe84..c6a2bd1e 100644 --- a/Wobble.sln +++ b/Wobble.sln @@ -15,12 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wobble.Extended", "Wobble.E EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wobble.Tests.Hotload", "Wobble.Tests.Hotload\Wobble.Tests.Hotload.csproj", "{AB46D815-2DA6-452A-9A99-4AF5CF06D19F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Framework.DesktopGL", "MonoGame\MonoGame.Framework\MonoGame.Framework.DesktopGL.csproj", "{C93E372C-7A7D-4657-A514-C4EF20960E50}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wobble.Tests.Unit", "Wobble.Tests.Unit\Wobble.Tests.Unit.csproj", "{A8B8CC69-03FC-47B5-85DB-87A7EEEDBF2C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpriteFontPlus", "SpriteFontPlus\src\SpriteFontPlus\SpriteFontPlus.csproj", "{8C770216-930A-4C0B-92F7-4E747F905C89}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,18 +47,10 @@ Global {AB46D815-2DA6-452A-9A99-4AF5CF06D19F}.Debug|Any CPU.Build.0 = Debug|Any CPU {AB46D815-2DA6-452A-9A99-4AF5CF06D19F}.Release|Any CPU.ActiveCfg = Release|Any CPU {AB46D815-2DA6-452A-9A99-4AF5CF06D19F}.Release|Any CPU.Build.0 = Release|Any CPU - {C93E372C-7A7D-4657-A514-C4EF20960E50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C93E372C-7A7D-4657-A514-C4EF20960E50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C93E372C-7A7D-4657-A514-C4EF20960E50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C93E372C-7A7D-4657-A514-C4EF20960E50}.Release|Any CPU.Build.0 = Release|Any CPU {A8B8CC69-03FC-47B5-85DB-87A7EEEDBF2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A8B8CC69-03FC-47B5-85DB-87A7EEEDBF2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A8B8CC69-03FC-47B5-85DB-87A7EEEDBF2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A8B8CC69-03FC-47B5-85DB-87A7EEEDBF2C}.Release|Any CPU.Build.0 = Release|Any CPU - {8C770216-930A-4C0B-92F7-4E747F905C89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C770216-930A-4C0B-92F7-4E747F905C89}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C770216-930A-4C0B-92F7-4E747F905C89}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C770216-930A-4C0B-92F7-4E747F905C89}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Wobble/GameBase.cs b/Wobble/GameBase.cs index 86d04c31..2f898b75 100644 --- a/Wobble/GameBase.cs +++ b/Wobble/GameBase.cs @@ -33,7 +33,7 @@ internal set public static SpriteBatchOptions DefaultSpriteBatchOptions { get; set; } = new SpriteBatchOptions() { SortMode = SpriteSortMode.Immediate, - BlendState = BlendState.NonPremultiplied, + BlendState = BlendState.NonPremultiplied }; /// diff --git a/Wobble/Graphics/Animations/AnimationProperty.cs b/Wobble/Graphics/Animations/AnimationProperty.cs index e0519a69..190eb38e 100644 --- a/Wobble/Graphics/Animations/AnimationProperty.cs +++ b/Wobble/Graphics/Animations/AnimationProperty.cs @@ -10,6 +10,7 @@ public enum AnimationProperty Width, Height, Alpha, + UIAlpha, Rotation, Color, Wait diff --git a/Wobble/Graphics/Camera.cs b/Wobble/Graphics/Camera.cs new file mode 100644 index 00000000..364bb348 --- /dev/null +++ b/Wobble/Graphics/Camera.cs @@ -0,0 +1,65 @@ +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Wobble.Graphics +{ + public class Camera : Drawable3D + { + private Vector3 _target = Vector3.Zero; + + public Vector3 Target + { + get => _target; + set + { + _target = value; + ViewMatrix = Matrix.CreateLookAt(Position, Target, Vector3.Up); // Y up + } + } + + public float TargetX + { + get => Target.X; + set => Target = new Vector3(value, Target.Y, Target.Z); + } + public float TargetY + { + get => Target.Y; + set => Target = new Vector3(Target.X, value, Target.Z); + } + public float TargetZ + { + get => Target.Z; + set => Target = new Vector3(Target.X, Target.Y, value); + } + + public Matrix ProjectionMatrix { get; private set; } + public Matrix ViewMatrix { get; private set; } + public ProjectionMode ProjectionMode { get; } + + public Camera(World world, Vector3 target) + { + ProjectionMode = new ProjectionMode(GameBase.Game.Graphics.GraphicsDevice.Viewport.Bounds, this); + World = world; + Target = target; + } + + protected override void OnRectangleRecalculated() + { + base.OnRectangleRecalculated(); + CalculateMatrices(); + } + + public void CalculateMatrices() + { + ProjectionMatrix = ProjectionMode.GetProjectionMatrix(); + ViewMatrix = Matrix.CreateLookAt(Position, Target, Vector3.Up); // Y up + } + + public void Draw(GameTime gameTime) + { + World.Draw(gameTime, this); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Drawable.cs b/Wobble/Graphics/Drawable.cs index 3edd0d6c..016c5689 100644 --- a/Wobble/Graphics/Drawable.cs +++ b/Wobble/Graphics/Drawable.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Xna.Framework; using MonoGame.Extended; using Wobble.Graphics.Animations; using Wobble.Graphics.Primitives; using Wobble.Graphics.Sprites; -using Wobble.Graphics.UI.Buttons; +using Wobble.Graphics.Transform; using Wobble.Input; using Wobble.Logging; using Wobble.Window; @@ -18,6 +17,22 @@ namespace Wobble.Graphics /// public abstract class Drawable : IDrawable, IDisposable { + /// + /// Represents the rotation, position, scale, and origin of the quad we're drawing + /// + public virtual DrawableTransform Transform { get; } = new(); + + /// + /// The main render target to render to. + /// + public RenderTargetOptions RenderTargetOptions { get; } = new RenderTargetOptions(); + + /// + /// A projection sprite that has the same dimension, position, rotation and parent as the container. + /// It shows , which the container can render its entire content to + /// + public RenderProjectionSprite DefaultProjectionSprite { get; private set; } + /// /// The total amount of drawables that are drawn on-screen. /// @@ -28,6 +43,11 @@ public abstract class Drawable : IDrawable, IDisposable /// public int DrawOrder { get; set; } + /// + /// A faster way of null checking render target options + /// + private bool _isCasting; + /// /// The parent of this drawable in which it depends on for its position and size. /// @@ -37,15 +57,22 @@ public Drawable Parent get => _parent; set { + Transform.Parent = value?.Transform; // If this drawable previously had a parent, remove it from the old parent's list // of children. _parent?.Children.Remove(this); + _parent = value; + // If we do end up having a non-null value for the new parent, we'll want to // add this drawable to their list of children. if (value != null) { value.Children.Add(this); + + // Derive layer from our parent + if (value.SetChildrenLayer) + Layer = value.Layer; } else if (DestroyIfParentIsNull) { @@ -53,13 +80,50 @@ public Drawable Parent // destroy and dispose of the object. for (var i = Children.Count - 1; i >= 0; i--) Children[i].Destroy(); + + Children.Clear(); } - _parent = value; + // When both are null, we implicitly add this to the Default layer + if (value == null && (Layer == null || IsDisposed)) + Layer = null; + RecalculateRectangles(); } } + private Layer _layer; + + /// + /// Whether to set children's layer too when changes + /// + public bool SetChildrenLayer { get; set; } = false; + + /// + /// Layer of this drawable. If null, it will be drawn over the Default layer. + /// + public Layer Layer + { + get => _layer; + set + { + if (_layer == value && _layer != null) + return; + + _layer?.RemoveDrawable(this); + value?.AddDrawable(this); + _layer = value; + + if (!SetChildrenLayer) + return; + + foreach (var child in Children) + { + child.Layer = value; + } + } + } + /// /// Completely destroys the object if the parent is null /// @@ -74,41 +138,49 @@ public Drawable Parent /// /// The drawable's rectangle relative to the entire screen. /// - public RectangleF ScreenRectangle { get; private set; } = new RectangleF(); + public RectangleF ScreenRectangle => Transform.ScreenRectangle; + + /// + /// If outside this region, this will not be drawed. + /// + private RectangleF DrawRectangleMask { get; set; } = + new RectangleF(0, 0, WindowManager.Width, WindowManager.Height); + + /// + /// Clipping region for children. Useful to RenderTargets + /// + protected virtual RectangleF ChildDrawRectangleMask { get; set; } + + /// + /// The bounding box of the drawable relative to the entire screen. + /// + public RectangleF ScreenMinimumBoundingRectangle => Transform.ScreenMinimumBoundingRectangle; /// /// The rectangle relative to the drawable's parent. /// - public RectangleF RelativeRectangle { get; private set; } + public RectangleF RelativeRectangle => Transform.RelativeRectangle; /// /// The position of the drawable /// - private ScalableVector2 _position = new ScalableVector2(0, 0); public ScalableVector2 Position { - get => _position; - set - { - _position = value; - RecalculateRectangles(); - } + get => Transform.Position; + set => Transform.Position = value; } + public event EventHandler SizeChanged; /// /// The size of the drawable. /// - private ScalableVector2 _size = new ScalableVector2(0, 0); public ScalableVector2 Size { - get => _size; + get => Transform.Size; set { - var width = MathHelper.Clamp(value.X.Value, 0, int.MaxValue); - var height = MathHelper.Clamp(value.Y.Value, 0, int.MaxValue); - - _size = new ScalableVector2(width, height, value.X.Scale, value.Y.Scale); - RecalculateRectangles(); + Transform.Size = value; + SizeChanged?.Invoke(this, value); } } @@ -144,6 +216,15 @@ public float Y } } + /// + /// The relative Z position of the object + /// + public float Z + { + get => Transform.Z; + set => Transform.Z = value; + } + /// /// The width of the object. /// @@ -174,6 +255,12 @@ public float WidthScale set => Size = new ScalableVector2(Size.X.Value, Size.Y.Value, value, Size.Y.Scale); } + public Vector2 Scale + { + get => Transform.Scale; + set => Transform.Scale = value; + } + /// /// The height of the object. /// @@ -204,22 +291,147 @@ public float HeightScale set { Size = new ScalableVector2(Size.X.Value, Size.Y.Value, Size.X.Scale, value); - RecalculateRectangles(); } } /// - /// The alignment of the object. + /// The pivot about which the rotation will be performed. + /// (0, 0) corresponds to the top-left corner, (1, 1) bottom right. /// - private Alignment _alignment = Alignment.TopLeft; - public Alignment Alignment + public Vector2 Pivot + { + get => Transform.Pivot; + set => Transform.Pivot = value; + } + + /// + /// Angle of the sprite with it's origin in the centre. (TEMPORARILY NOT USED YET) + /// + public float Rotation + { + get => Transform.RotationAngle; + set => Transform.RotationAngle = value; + } + + /// + /// The axis of rotation + /// + public Vector3 RotationAxis + { + get => Transform.RotationAxis; + set => Transform.RotationAxis = value; + } + + /// + /// The underlying quaternion that represents the rotation. + /// Changing this will not change and , + /// so be careful. + /// + public Quaternion RotationQuaternion { - get => _alignment; + get => Transform.Rotation; + set => Transform.Rotation = value; + } + + /// + /// The final color to be drawn on screen when calling draws. + /// This color is affected by . + /// + protected Color AbsoluteColor { get; private set; } = Color.White; + + /// + /// The base color of its children. This excludes the effect from . + /// + private Vector4 ChildrenColor { get; set; } = Vector4.One; + + private float _uiAlpha = 1; + + /// + /// Sets the alpha for itself, while multiplying the alpha to all of its children and consequently descendents + /// + public float UIAlpha + { + get => _uiAlpha; set { - _alignment = value; - RecalculateRectangles(); + _uiAlpha = value; + RecalculateColor(); + } + } + + private Color _uiTint = Color.White; + + /// + /// Sets the tint for itself, while multiplying the tint to all of its children and consequently descendents + /// + public Color UITint + { + get => _uiTint; + set + { + _uiTint = value; + RecalculateColor(); + } + } + + /// + /// Additional color applied to the drawable, overridable. + /// This is overridden in for compatibility. + /// + protected virtual Color RelativeColor => Color.White; + + /// + /// Updates the and of itself only + /// + protected void RecalculateSelfColor() + { + var parentChildrenColor = Parent?.ChildrenColor ?? Vector4.One; + ChildrenColor = (_uiTint * _uiAlpha).ToVector4() * parentChildrenColor; + + // RelativeColor affects the final color but not the children's color + AbsoluteColor = new Color(ChildrenColor * RelativeColor.ToVector4()); + } + + /// + /// Updates the and of itself and all of its children, + /// recursively. + /// + /// + protected void RecalculateColor() + { + RecalculateSelfColor(); + + try + { + foreach (var child in Children) + { + child.RecalculateColor(); + } } + catch (InvalidOperationException e) + { + Logger.Error(e, LogType.Runtime); + } + } + + /// + /// Applying this to gives the screen space position + /// + private Matrix2 _childPositionTransform = Matrix2.Identity; + + /// + /// A transform that rotates the relative coordinates about the pivot + /// Applying this to gives the relative coordinate after rotation. + /// + private Matrix2 _childRelativeTransform = Matrix2.Identity; + + /// + /// The alignment of the object. + /// + public Alignment Alignment + { + get => Transform.Alignment; + set => Transform.Alignment = value; } /// @@ -343,6 +555,12 @@ public bool Visible /// private List ScheduledUpdates { get; } = new List(); + public Drawable() + { + Transform.PositionRecalculated += RecalculateRectangles; + Transform.TransformUpdated += RecalculateRectangles; + } + /// /// /// @@ -385,7 +603,7 @@ public virtual void Draw(GameTime gameTime) if (!Visible) return; - if (!RectangleF.Intersects(ScreenRectangle, new RectangleF(0, 0, WindowManager.Width, WindowManager.Height)) && !DrawIfOffScreen) + if (!RectangleF.Intersects(ScreenMinimumBoundingRectangle, DrawRectangleMask) && !DrawIfOffScreen) return; // Draw the children and set their order. @@ -394,29 +612,88 @@ public virtual void Draw(GameTime gameTime) TotalDrawn++; DrawOrder = TotalDrawn; + if (_isCasting) + Transform.OverrideChildMatrix(RenderTargetOptions.TransformMatrix); try { for (var i = 0; i < Children.Count; i++) { var drawable = Children[i]; - drawable.Draw(gameTime); + + if (drawable.Layer != null) + continue; + + drawable.WrappedDraw(gameTime); TotalDrawn++; drawable.DrawOrder = TotalDrawn; } } - // In the case of modifying a drawable collection, an InvalidOperationException might occur - catch (InvalidOperationException e) - { - if (!e.Message.Contains("Collection was modified; enumeration operation may not execute.")) - throw; - } catch (Exception e) { Logger.Error(e, LogType.Runtime); } } + public void WrappedDraw(GameTime gameTime) + { + if (!_isCasting) + { + Draw(gameTime); + return; + } + + if (Parent == null) + DefaultProjectionSprite?.Draw(gameTime); + GameBase.Game.ScheduledRenderTargetDraws.Add(DrawToRenderTarget); + } + + + /// + /// Draw this container to a render target so its view can be duplicated and shown in + /// a different way. + /// **THIS CAN CAUSE PERFORMANCE DEGREDATION** + /// + /// + /// The render target is bounded by the size of the container, so + /// anything outside this container will be clipped + /// + /// Whether a sprite will be spawned to show the container as normal + public void CastToRenderTarget(bool projectDefault = true) + { + _isCasting = true; + RenderTargetOptions.ContainerRectangleSize = + new Point((int)RelativeRectangle.Size.Width, (int)RelativeRectangle.Size.Height); + RenderTargetOptions.Enabled = true; + Transform.OverrideChildMatrix(new Transform3D(RenderTargetOptions.TransformMatrix)); + + DefaultProjectionSprite?.Destroy(); + + if (projectDefault) + { + DefaultProjectionSprite = new RenderProjectionSprite + { + Size = Size, + Position = Position, + Rotation = Rotation, + Alignment = Alignment, + Parent = Parent + }; + DefaultProjectionSprite.BindProjectionContainer(this); + DefaultProjectionSprite.SyncSourceProperties(); + } + RecalculateRectangles(); + } + + public void StopCasting() + { + _isCasting = false; + DefaultProjectionSprite?.Destroy(); + RenderTargetOptions.Enabled = false; + DefaultProjectionSprite = null; + Transform.OverrideChildMatrix(null); + } + /// /// Destroys the object. Removes the parent object. Any derivates should /// free any resources used by the object. @@ -438,11 +715,11 @@ public void AddBorder(Color color, float thickness = 1) Border = new PrimitiveLineBatch(new List() { - new Vector2(0, 0), - new Vector2(Width, 0), - new Vector2(Width, Height), - new Vector2(0, Height), - new Vector2(0, 0) + new(ScreenMinimumBoundingRectangle.Left, ScreenMinimumBoundingRectangle.Top), + new(ScreenMinimumBoundingRectangle.Right, ScreenMinimumBoundingRectangle.Top), + new(ScreenMinimumBoundingRectangle.Right, ScreenMinimumBoundingRectangle.Bottom), + new(ScreenMinimumBoundingRectangle.Left, ScreenMinimumBoundingRectangle.Bottom), + new(ScreenMinimumBoundingRectangle.Left, ScreenMinimumBoundingRectangle.Top), }, thickness) { Alignment = Alignment.TopLeft, @@ -450,6 +727,14 @@ public void AddBorder(Color color, float thickness = 1) Tint = color, UsePreviousSpriteBatchOptions = true }; + Border.Transform.OverrideSelfMatrix(new Transform3D()); + } + + public void RecalculateDrawMask() + { + DrawRectangleMask = Parent?.ChildDrawRectangleMask + ?? new RectangleF(0, 0, WindowManager.Width, WindowManager.Height); + ChildDrawRectangleMask = _isCasting ? RenderTargetOptions.RenderTarget.Value.Bounds : DrawRectangleMask; } /// @@ -463,44 +748,31 @@ public void AddBorder(Color color, float thickness = 1) /// protected void RecalculateRectangles() { - // Make it relative to the parent. - if (Parent != null) - { - var width = Size.X.Value + Parent.ScreenRectangle.Width * WidthScale; - var height = Size.Y.Value + Parent.ScreenRectangle.Height * HeightScale; - var x = Position.X.Value; - var y = Position.Y.Value; - RelativeRectangle = new RectangleF(x, y, width, height); - ScreenRectangle = GraphicsHelper.AlignRect(Alignment, RelativeRectangle, Parent.ScreenRectangle); - } - // Make it relative to the screen size. - else - { - var width = Size.X.Value + WindowManager.VirtualScreen.X * WidthScale; - var height = Size.Y.Value + WindowManager.VirtualScreen.Y * HeightScale; - var x = Position.X.Value; - var y = Position.Y.Value; + RecalculateDrawMask(); - RelativeRectangle = new RectangleF(x, y, width, height); - ScreenRectangle = GraphicsHelper.AlignRect(Alignment, RelativeRectangle, WindowManager.Rectangle); - } // Recalculate the border points. if (Border != null) { Border.Points = new List() { - new Vector2(0, 0), - new Vector2(Width, 0), - new Vector2(Width, Height), - new Vector2(0, Height), - new Vector2(0, 0) + new(ScreenMinimumBoundingRectangle.Left, ScreenMinimumBoundingRectangle.Top), + new(ScreenMinimumBoundingRectangle.Right, ScreenMinimumBoundingRectangle.Top), + new(ScreenMinimumBoundingRectangle.Right, ScreenMinimumBoundingRectangle.Bottom), + new(ScreenMinimumBoundingRectangle.Left, ScreenMinimumBoundingRectangle.Bottom), + new(ScreenMinimumBoundingRectangle.Left, ScreenMinimumBoundingRectangle.Top), }; } - for (var i = 0; i < Children.Count; i++) - Children[i].RecalculateRectangles(); + DefaultProjectionSprite?.SyncSourceProperties(); + + if (RenderTargetOptions.Enabled) + { + RenderTargetOptions.ContainerRectangleSize = + new Point((int)RelativeRectangle.Width, (int)RelativeRectangle.Height); + RenderTargetOptions.ResetRenderTarget(); + } // Raise recalculated event. OnRectangleRecalculated(); @@ -523,6 +795,21 @@ protected virtual void OnRectangleRecalculated() /// internal static void ResetTotalDrawnCount() => TotalDrawn = 0; + private void DrawToRenderTarget(GameTime gameTime) + { + if (!_isCasting) + return; + + GameBase.Game.TryEndBatch(); + GameBase.Game.GraphicsDevice.SetRenderTarget(RenderTargetOptions.RenderTarget.Value); + GameBase.Game.GraphicsDevice.Clear(RenderTargetOptions.BackgroundColor); + + Draw(gameTime); + + // Attempt to end the spritebatch + _ = GameBase.Game.TryEndBatch(); + } + /// /// Performs all of the Animations in the queue. /// @@ -562,7 +849,10 @@ public void PerformTransformations(GameTime gameTime) Width = (int)animation.PerformInterpolation(gameTime); break; case AnimationProperty.Height: - Height = (int)animation.PerformInterpolation(gameTime); + Height = (int) animation.PerformInterpolation(gameTime); + break; + case AnimationProperty.UIAlpha: + UIAlpha = animation.PerformInterpolation(gameTime); break; case AnimationProperty.Alpha: var type = GetType(); @@ -624,6 +914,12 @@ public void PerformTransformations(GameTime gameTime) case AnimationProperty.Height: a.Start = Height; break; + case AnimationProperty.Rotation: + a.Start = Rotation; + break; + case AnimationProperty.UIAlpha: + a.Start = UIAlpha; + break; case AnimationProperty.Alpha: var type = GetType(); @@ -633,16 +929,6 @@ public void PerformTransformations(GameTime gameTime) a.Start = sprite.Alpha; } - break; - case AnimationProperty.Rotation: - if (this is Sprite) - { - var sprite = (Sprite)this; - a.Start = sprite.Rotation; - } - else - throw new NotImplementedException(); - break; case AnimationProperty.Color: if (this is Sprite) @@ -680,7 +966,8 @@ public void ClearAnimations() /// Returns if the Drawable is currently hovered /// /// - public bool IsHovered() => GraphicsHelper.RectangleContains(ScreenRectangle, MouseManager.CurrentState.Position); + public bool IsHovered() => + GraphicsHelper.RectangleContains(ScreenRectangle, MouseManager.CurrentState.Position); /// /// Removes all previously scheduled updates, and schedules a new one to run in the next frame @@ -819,6 +1106,20 @@ public Drawable ChangeSizeTo(Vector2 size, Easing easingType, int time) return this; } + /// + /// + /// + /// + /// + /// + public Drawable UIFadeTo(float alpha, Easing easingType, int time) + { + lock (Animations) + Animations.Add(new Animation(AnimationProperty.UIAlpha, easingType, UIAlpha, alpha, time)); + + return this; + } + /// /// /// @@ -839,4 +1140,4 @@ public virtual Drawable Wait(int time = 0) IsDisposed = true; } } -} +} \ No newline at end of file diff --git a/Wobble/Graphics/Drawable3D.cs b/Wobble/Graphics/Drawable3D.cs new file mode 100644 index 00000000..9c8c8be6 --- /dev/null +++ b/Wobble/Graphics/Drawable3D.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Wobble.Logging; + +namespace Wobble.Graphics +{ + public abstract class Drawable3D + { + private Vector3 _size = Vector3.Zero; + private Vector3 _position = Vector3.Zero; + private Drawable3D _parent; + private Vector3 _scale = Vector3.One; + private Quaternion _rotation = Quaternion.Identity; + + public Vector3 Position + { + get => _position; + set + { + _position = value; + RecalculateRectangles(); + } + } + + public float X + { + get => Position.X; + set => Position = new Vector3(value, Position.Y, Position.Z); + } + + public float Y + { + get => Position.Y; + set => Position = new Vector3(Position.X, value, Position.Z); + } + + public float Z + { + get => Position.Z; + set => Position = new Vector3(Position.X, Position.Y, value); + } + + public Quaternion Rotation + { + get => _rotation; + set + { + _rotation = value; + RecalculateRectangles(); + } + } + + public List Children { get; set; } = new List(); + + public Vector3 Size + { + get => _size; + set + { + _size = value; + RecalculateRectangles(); + } + } + + public Vector3 Scale + { + get => _scale; + set + { + _scale = value; + RecalculateRectangles(); + } + } + + public Vector3 ScaledWorldSize => WorldScale * Size; + public Vector3 WorldPosition { get; private set; } + + public Vector3 WorldScale { get; private set; } + + public Drawable3D Parent + { + get => _parent; + set + { + Parent?.Children.Remove(this); + value?.Children.Add(this); + _parent = value; + } + } + + public World World { get; set; } + + public BoundingBox WorldBoundingBox { get; set; } + + public BoundingBox RelativeBoundingBox { get; set; } + + /// + /// Recalculates the local and global rectangles of the object. Makes sure that the position + /// and sizes are relative to the parent if the drawable has one. + /// + protected void RecalculateRectangles() + { + // Make it relative to the parent. + if (Parent != null) + { + WorldScale = Scale * Parent.WorldScale; + RelativeBoundingBox = new BoundingBox(Vector3.Zero, Size * WorldScale); + WorldPosition = Position + Parent.WorldBoundingBox.Min; + WorldBoundingBox = new BoundingBox(WorldPosition, RelativeBoundingBox.Max + WorldPosition); + } + // Make it relative to the screen size. + else + { + WorldScale = Scale; + RelativeBoundingBox = new BoundingBox(Vector3.Zero, Size * Scale); + WorldBoundingBox = new BoundingBox(Position, RelativeBoundingBox.Max + Position); + } + + for (var i = 0; i < Children.Count; i++) + Children[i].RecalculateRectangles(); + + // Raise recalculated event. + OnRectangleRecalculated(); + } + + /// + /// + /// + protected virtual void OnRectangleRecalculated() + { + } + + // TODO Use WorldPosition + public Matrix Transformation => Matrix.CreateTranslation(-Position) * Matrix.CreateScale(Scale) * + Matrix.CreateFromQuaternion(Rotation); + + public bool Visible { get; set; } = true; + + /// + /// A list of updates that are scheduled to be run at the beginning of . + /// Should be used for scheduling UI updates from a separate thread. + /// + private List ScheduledUpdates { get; } = new List(); + + protected Drawable3D() + { + } + + /// + /// + /// + /// + public virtual void Update(GameTime gameTime) + { + RunScheduledUpdates(); + + // Update all of the contained children. + for (var i = Children.Count - 1; i >= 0; i--) + { + try + { + //TotalDrawn++; + //Children[i].DrawOrder = TotalDrawn; + Children[i].Update(gameTime); + } + // Handle + catch (ArgumentOutOfRangeException e) + { + // In the event that a child was updated but the list was somehow modified + // just break out of the loop for now. + if (i < 0 || i >= Children.Count) + break; + } + catch (Exception e) + { + Logger.Error(e, LogType.Runtime); + } + } + } + + /// + /// Runs all updates that are scheduled for this drawable during + /// + protected void RunScheduledUpdates() + { + lock (ScheduledUpdates) + { + if (ScheduledUpdates.Count == 0) + return; + + var updates = new List(ScheduledUpdates); + ScheduledUpdates.Clear(); + + foreach (var update in updates) + update.Invoke(); + } + } + + public virtual void Draw(GameTime gameTime, Camera camera) + { + if (!Visible) return; + for (var i = 0; i < Children.Count; i++) + { + var drawable = Children[i]; + drawable.Draw(gameTime, camera); + } + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/GlobalLayerManager.cs b/Wobble/Graphics/GlobalLayerManager.cs new file mode 100644 index 00000000..351cc62f --- /dev/null +++ b/Wobble/Graphics/GlobalLayerManager.cs @@ -0,0 +1,56 @@ +namespace Wobble.Graphics +{ + public class GlobalLayerManager : LayerManager + { + + + /// + /// Layer to draw the cursor + /// + public Layer CursorLayer { get; private set; } + + /// + /// Global UI layer + /// + public Layer GlobalUILayer { get; private set; } + + /// + /// Layer to draw dialogs + /// + public Layer DialogLayer { get; private set; } + + /// + /// UI layer of the current screen + /// + public Layer UILayer { get; private set; } + + /// + /// Layer to draw background images + /// + public Layer BackgroundLayer { get; private set; } + + protected override void InitializeLayers() + { + GlobalUILayer = NewLayer("GlobalUI"); + DialogLayer = NewLayer("Dialog"); + UILayer = NewLayer("UI"); + CursorLayer = NewLayer("Cursor"); + BackgroundLayer = NewLayer("Background"); + + TopLayer.LayerFlags = LayerFlags.Top | LayerFlags.NoChildren; + BottomLayer.LayerFlags = LayerFlags.Bottom | LayerFlags.NoChildren; + + RequireOrder(new[] + { + TopLayer, + CursorLayer, + GlobalUILayer, + DialogLayer, + UILayer, + DefaultLayer, + BackgroundLayer, + BottomLayer + }); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/GraphicsHelper.cs b/Wobble/Graphics/GraphicsHelper.cs index f3030f98..37a73c9b 100644 --- a/Wobble/Graphics/GraphicsHelper.cs +++ b/Wobble/Graphics/GraphicsHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Microsoft.Xna.Framework; using MonoGame.Extended; @@ -11,13 +12,16 @@ public static class GraphicsHelper /// /// The value (percentage) which the object will be aligned to (0=min, 0.5 =mid, 1.0 = max) /// The size of the object - /// - /// + /// + /// /// + /// /// - public static float Align(float scale, float objectSize, float boundaryX, float boundaryY, float offset = 0) + public static float Align(float scale, float objectSize, float boundaryLeft, float boundaryRight, + float offset = 0, bool relative = false) { - return Math.Min(boundaryX, boundaryY) + (Math.Abs(boundaryX - boundaryY) - objectSize) * scale + offset; + var res = (boundaryRight - boundaryLeft - objectSize) * scale + offset; + return relative ? res : boundaryLeft + res; } /// @@ -26,8 +30,10 @@ public static float Align(float scale, float objectSize, float boundaryX, float /// The alignment of the object. /// The size of the object. /// The Rectangle of the boundary. + /// /// - public static RectangleF AlignRect(Alignment objectAlignment, RectangleF objectRect, RectangleF boundary) + public static RectangleF AlignRect(Alignment objectAlignment, RectangleF objectRect, RectangleF boundary, + bool relative = false) { float alignX = 0; float alignY = 0; @@ -67,12 +73,63 @@ public static RectangleF AlignRect(Alignment objectAlignment, RectangleF objectR } //Set X and Y Alignments - alignX = Align(alignX, objectRect.Width, boundary.X, boundary.X + boundary.Width, objectRect.X); - alignY = Align(alignY, objectRect.Height, boundary.Y, boundary.Y + boundary.Height, objectRect.Y); + alignX = Align(alignX, objectRect.Width, boundary.X, boundary.X + boundary.Width, objectRect.X, relative); + alignY = Align(alignY, objectRect.Height, boundary.Y, boundary.Y + boundary.Height, objectRect.Y, relative); return new RectangleF(alignX, alignY, objectRect.Width, objectRect.Height); } + public static RectangleF Offset(RectangleF objectRect, RectangleF offset) + { + return new RectangleF(objectRect.X + offset.X, objectRect.Y + offset.Y, + objectRect.Width, objectRect.Height); + } + + public static RectangleF Transform(RectangleF objectRect, Matrix2 matrix, Vector2 scale) + { + var resultPosition = matrix.Transform(objectRect.Position); + var resultSize = new Size2(objectRect.Width * scale.X, objectRect.Height * scale.Y); + return new RectangleF(resultPosition, resultSize); + } + + public static RectangleF MinimumBoundingRectangle(RectangleF objectRect, float angleRadians, + bool relative = false) + { + var cos = MathF.Cos(angleRadians); + var sin = MathF.Sin(angleRadians); + var topLeft = Vector2.Zero; + var bottomLeft = new Vector2(-sin * objectRect.Height, cos * objectRect.Height); + var bottomRight = new Vector2(cos * objectRect.Width - sin * objectRect.Height, + sin * objectRect.Width + cos * objectRect.Height); + var topRight = new Vector2(cos * objectRect.Width, sin * objectRect.Width); + var minimumBoundingRectangle = MinimumBoundingRectangle(topLeft, topRight, bottomLeft, bottomRight); + if (!relative) + minimumBoundingRectangle.Offset(objectRect.Position); + return minimumBoundingRectangle; + } + + public static RectangleF MinimumBoundingRectangle( + Vector2 topLeft, Vector2 topRight, Vector2 bottomLeft, Vector2 bottomRight) + { + var minX = MathF.Min(MathF.Min(topLeft.X, bottomLeft.X), MathF.Min(bottomRight.X, topRight.X)); + var minY = MathF.Min(MathF.Min(topLeft.Y, bottomLeft.Y), MathF.Min(bottomRight.Y, topRight.Y)); + var maxX = MathF.Max(MathF.Max(topLeft.X, bottomLeft.X), MathF.Max(bottomRight.X, topRight.X)); + var maxY = MathF.Max(MathF.Max(topLeft.Y, bottomLeft.Y), MathF.Max(bottomRight.Y, topRight.Y)); + var minimumBoundingRectangle = new RectangleF(minX, minY, maxX - minX, maxY - minY); + return minimumBoundingRectangle; + } + + public static RectangleF MinimumBoundingRectangle( + Vector3 topLeft, Vector3 topRight, Vector3 bottomLeft, Vector3 bottomRight) + { + var minX = MathF.Min(MathF.Min(topLeft.X, bottomLeft.X), MathF.Min(bottomRight.X, topRight.X)); + var minY = MathF.Min(MathF.Min(topLeft.Y, bottomLeft.Y), MathF.Min(bottomRight.Y, topRight.Y)); + var maxX = MathF.Max(MathF.Max(topLeft.X, bottomLeft.X), MathF.Max(bottomRight.X, topRight.X)); + var maxY = MathF.Max(MathF.Max(topLeft.Y, bottomLeft.Y), MathF.Max(bottomRight.Y, topRight.Y)); + var minimumBoundingRectangle = new RectangleF(minX, minY, maxX - minX, maxY - minY); + return minimumBoundingRectangle; + } + /// /// Converts a Vector2 to Point /// @@ -95,12 +152,14 @@ public static RectangleF AlignRect(Alignment objectAlignment, RectangleF objectR /// public static bool RectangleContains(DrawRectangle rect, Vector2 point) { - return (point.X >= rect.X && point.X <= rect.X + rect.Width && point.Y >= rect.Y && point.Y <= rect.Y + rect.Height); + return (point.X >= rect.X && point.X <= rect.X + rect.Width && point.Y >= rect.Y && + point.Y <= rect.Y + rect.Height); } public static bool RectangleContains(RectangleF rect, Vector2 point) { - return (point.X >= rect.X && point.X <= rect.X + rect.Width && point.Y >= rect.Y && point.Y <= rect.Y + rect.Height); + return (point.X >= rect.X && point.X <= rect.X + rect.Width && point.Y >= rect.Y && + point.Y <= rect.Y + rect.Height); } } } \ No newline at end of file diff --git a/Wobble/Graphics/ImGUI/ImGuiRenderer.cs b/Wobble/Graphics/ImGUI/ImGuiRenderer.cs index 9a42fceb..a0f345a8 100644 --- a/Wobble/Graphics/ImGUI/ImGuiRenderer.cs +++ b/Wobble/Graphics/ImGUI/ImGuiRenderer.cs @@ -83,6 +83,9 @@ public sealed class ImGuiRenderer : IDisposable /// private ImGuiOptions Options { get; } + // ReSharper disable once InconsistentNaming + private ImGuiIOPtr IO { get; set; } + /// /// public ImFontPtr DefaultFontPtr { get; private set; } @@ -99,6 +102,7 @@ public ImGuiRenderer(bool destroyContext = true, ImGuiOptions options = null, fl Context = ImGui.CreateContext(); ImGui.SetCurrentContext(Context); + IO = ImGui.GetIO(); LoadedTextures = new Dictionary(); @@ -126,19 +130,16 @@ public ImGuiRenderer(bool destroyContext = true, ImGuiOptions options = null, fl /// public unsafe void RebuildFontAtlas() { - // Get font texture from ImGui - var io = ImGui.GetIO(); - if (Options != null) { if (Options.LoadDefaultFont) - DefaultFontPtr = io.Fonts.AddFontDefault(); + DefaultFontPtr = IO.Fonts.AddFontDefault(); foreach (var font in Options.Fonts) - font.Context = io.Fonts.AddFontFromFileTTF(font.Path, font.Size * Scale); + font.Context = IO.Fonts.AddFontFromFileTTF(font.Path, font.Size * Scale); } - io.Fonts.GetTexDataAsRGBA32(out var pixelData, out var width, out var height, out var bytesPerPixel); + IO.Fonts.GetTexDataAsRGBA32(out var pixelData, out var width, out var height, out var bytesPerPixel); // Copy the data to a managed array var pixels = new byte[width * height * bytesPerPixel]; @@ -156,8 +157,8 @@ public unsafe void RebuildFontAtlas() FontTextureId = BindTexture(tex2D); // Let ImGui know where to find the texture - io.Fonts.SetTexID(FontTextureId.Value); - io.Fonts.ClearTexData(); // Clears CPU side texture data + IO.Fonts.SetTexID(FontTextureId.Value); + IO.Fonts.ClearTexData(); // Clears CPU side texture data } /// @@ -183,7 +184,7 @@ public IntPtr BindTexture(Texture2D texture) /// public void BeforeLayout(GameTime gameTime) { - ImGui.GetIO().DeltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; + IO.DeltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; UpdateInput(); @@ -208,35 +209,28 @@ public void AfterLayout() /// private void SetupInput() { - var io = ImGui.GetIO(); - - Keys.Add(io.KeyMap[(int)ImGuiKey.Tab] = (int)Microsoft.Xna.Framework.Input.Keys.Tab); - Keys.Add(io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Left); - Keys.Add(io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Right); - Keys.Add(io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Up); - Keys.Add(io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Down); - Keys.Add(io.KeyMap[(int)ImGuiKey.PageUp] = (int)Microsoft.Xna.Framework.Input.Keys.PageUp); - Keys.Add(io.KeyMap[(int)ImGuiKey.PageDown] = (int)Microsoft.Xna.Framework.Input.Keys.PageDown); - Keys.Add(io.KeyMap[(int)ImGuiKey.Home] = (int)Microsoft.Xna.Framework.Input.Keys.Home); - Keys.Add(io.KeyMap[(int)ImGuiKey.End] = (int)Microsoft.Xna.Framework.Input.Keys.End); - Keys.Add(io.KeyMap[(int)ImGuiKey.Delete] = (int)Microsoft.Xna.Framework.Input.Keys.Delete); - Keys.Add(io.KeyMap[(int)ImGuiKey.Backspace] = (int)Microsoft.Xna.Framework.Input.Keys.Back); - Keys.Add(io.KeyMap[(int)ImGuiKey.Enter] = (int)Microsoft.Xna.Framework.Input.Keys.Enter); - Keys.Add(io.KeyMap[(int)ImGuiKey.Escape] = (int)Microsoft.Xna.Framework.Input.Keys.Escape); - Keys.Add(io.KeyMap[(int)ImGuiKey.A] = (int)Microsoft.Xna.Framework.Input.Keys.A); - Keys.Add(io.KeyMap[(int)ImGuiKey.C] = (int)Microsoft.Xna.Framework.Input.Keys.C); - Keys.Add(io.KeyMap[(int)ImGuiKey.V] = (int)Microsoft.Xna.Framework.Input.Keys.V); - Keys.Add(io.KeyMap[(int)ImGuiKey.X] = (int)Microsoft.Xna.Framework.Input.Keys.X); - Keys.Add(io.KeyMap[(int)ImGuiKey.Y] = (int)Microsoft.Xna.Framework.Input.Keys.Y); - Keys.Add(io.KeyMap[(int)ImGuiKey.Z] = (int)Microsoft.Xna.Framework.Input.Keys.Z); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Tab] = (int)Microsoft.Xna.Framework.Input.Keys.Tab); + Keys.Add(IO.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Left); + Keys.Add(IO.KeyMap[(int)ImGuiKey.RightArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Right); + Keys.Add(IO.KeyMap[(int)ImGuiKey.UpArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Up); + Keys.Add(IO.KeyMap[(int)ImGuiKey.DownArrow] = (int)Microsoft.Xna.Framework.Input.Keys.Down); + Keys.Add(IO.KeyMap[(int)ImGuiKey.PageUp] = (int)Microsoft.Xna.Framework.Input.Keys.PageUp); + Keys.Add(IO.KeyMap[(int)ImGuiKey.PageDown] = (int)Microsoft.Xna.Framework.Input.Keys.PageDown); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Home] = (int)Microsoft.Xna.Framework.Input.Keys.Home); + Keys.Add(IO.KeyMap[(int)ImGuiKey.End] = (int)Microsoft.Xna.Framework.Input.Keys.End); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Delete] = (int)Microsoft.Xna.Framework.Input.Keys.Delete); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Backspace] = (int)Microsoft.Xna.Framework.Input.Keys.Back); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Enter] = (int)Microsoft.Xna.Framework.Input.Keys.Enter); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Escape] = (int)Microsoft.Xna.Framework.Input.Keys.Escape); + Keys.Add(IO.KeyMap[(int)ImGuiKey.A] = (int)Microsoft.Xna.Framework.Input.Keys.A); + Keys.Add(IO.KeyMap[(int)ImGuiKey.C] = (int)Microsoft.Xna.Framework.Input.Keys.C); + Keys.Add(IO.KeyMap[(int)ImGuiKey.V] = (int)Microsoft.Xna.Framework.Input.Keys.V); + Keys.Add(IO.KeyMap[(int)ImGuiKey.X] = (int)Microsoft.Xna.Framework.Input.Keys.X); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Y] = (int)Microsoft.Xna.Framework.Input.Keys.Y); + Keys.Add(IO.KeyMap[(int)ImGuiKey.Z] = (int)Microsoft.Xna.Framework.Input.Keys.Z); // MonoGame-specific ////////////////////// - Game.Window.TextInput += (s, a) => - { - if (a.Character == '\t') return; - - io.AddInputCharacter(a.Character); - }; + Game.Window.TextInput += OnWindowOnTextInput; /////////////////////////////////////////// // FNA-specific /////////////////////////// @@ -248,11 +242,18 @@ private void SetupInput() //}; /////////////////////////////////////////// - ImGui.GetIO().Fonts.AddFontDefault(); + IO.Fonts.AddFontDefault(); // ImGUI provides out-of-the-box clipboard only on Windows. For other platforms, we need to set up the function pointers. - io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(SetClipboardTextFnDelegate); - io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(GetClipboardTextFnDelegate); + IO.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(SetClipboardTextFnDelegate); + IO.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(GetClipboardTextFnDelegate); + } + + private void OnWindowOnTextInput(object s, TextInputEventArgs a) + { + if (a.Character == '\t') return; + + IO.AddInputCharacter(a.Character); } /* @@ -298,11 +299,9 @@ private Effect UpdateEffect(Texture2D texture) { Effect = Effect ?? new BasicEffect(GraphicsDevice); - var io = ImGui.GetIO(); - Effect.World = Matrix.Identity; Effect.View = Matrix.Identity; - Effect.Projection = Matrix.CreateOrthographicOffCenter(0, io.DisplaySize.X, io.DisplaySize.Y, 0, -1f, 1f); + Effect.Projection = Matrix.CreateOrthographicOffCenter(0, IO.DisplaySize.X, IO.DisplaySize.Y, 0, -1f, 1f); Effect.TextureEnabled = true; Effect.Texture = texture; Effect.VertexColorEnabled = true; @@ -315,30 +314,28 @@ private Effect UpdateEffect(Texture2D texture) /// private void UpdateInput() { - var io = ImGui.GetIO(); - var mouse = Mouse.GetState(); var keyboard = Keyboard.GetState(); foreach (var t in Keys) - io.KeysDown[t] = keyboard.IsKeyDown((Keys)t); + IO.KeysDown[t] = keyboard.IsKeyDown((Keys)t); - io.KeyShift = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightShift); - io.KeyCtrl = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightControl); - io.KeyAlt = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightAlt); - io.KeySuper = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftWindows) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightWindows); + IO.KeyShift = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightShift); + IO.KeyCtrl = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightControl); + IO.KeyAlt = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightAlt); + IO.KeySuper = keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftWindows) || keyboard.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightWindows); - io.DisplaySize = new System.Numerics.Vector2(GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight); - io.DisplayFramebufferScale = new System.Numerics.Vector2(1f, 1f); + IO.DisplaySize = new System.Numerics.Vector2(GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight); + IO.DisplayFramebufferScale = new System.Numerics.Vector2(1f, 1f); - io.MousePos = new System.Numerics.Vector2(mouse.X, mouse.Y); + IO.MousePos = new System.Numerics.Vector2(mouse.X, mouse.Y); - io.MouseDown[0] = mouse.LeftButton == ButtonState.Pressed; - io.MouseDown[1] = mouse.RightButton == ButtonState.Pressed; - io.MouseDown[2] = mouse.MiddleButton == ButtonState.Pressed; + IO.MouseDown[0] = mouse.LeftButton == ButtonState.Pressed; + IO.MouseDown[1] = mouse.RightButton == ButtonState.Pressed; + IO.MouseDown[2] = mouse.MiddleButton == ButtonState.Pressed; var scrollDelta = mouse.ScrollWheelValue - ScrollWheelValue; - io.MouseWheel = scrollDelta > 0 ? 1 : scrollDelta < 0 ? -1 : 0; + IO.MouseWheel = scrollDelta > 0 ? 1 : scrollDelta < 0 ? -1 : 0; ScrollWheelValue = mouse.ScrollWheelValue; } @@ -365,7 +362,7 @@ private void RenderDrawData(ImDrawDataPtr drawData) GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) - drawData.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale); + drawData.ScaleClipRects(IO.DisplayFramebufferScale); // Setup projection GraphicsDevice.Viewport = new Viewport(0, 0, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight); @@ -514,6 +511,7 @@ public void Dispose() RasterizerState?.Dispose(); VertexBuffer?.Dispose(); IndexBuffer?.Dispose(); + Game.Window.TextInput -= OnWindowOnTextInput; } } } \ No newline at end of file diff --git a/Wobble/Graphics/Layer.cs b/Wobble/Graphics/Layer.cs new file mode 100644 index 00000000..0f61a184 --- /dev/null +++ b/Wobble/Graphics/Layer.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; +using Wobble.Logging; + +namespace Wobble.Graphics +{ + public class Layer + { + /// + /// Unique identifier of the layer + /// + public string Name { get; } + + /// + /// The special attributes of this layer, to indicate whether this is a top/bottom layer, + /// whether this layer should have children, etc. + /// + public LayerFlags LayerFlags { get; set; } = LayerFlags.None; + + /// + /// Indicates whether the layer will be drawn. + /// + public bool Visible { get; set; } = true; + + /// + /// The layers that need to be drawn above this layer + /// + private readonly HashSet requiredUpperLayers = new HashSet(); + + /// + /// The layers that need to be drawn below this layer + /// + private readonly HashSet requiredLowerLayers = new HashSet(); + + /// + /// Stores the data required to run Tarjan's strongly connected components algorithm + /// + internal TarjanData LayerTarjanData; + + /// + /// The list of all drawables that is drawn on this layer + /// + private readonly List drawables = new List(); + + /// + /// The this layer belongs to. + /// For any operations changing constraints, the layers involved must have the same + /// + private readonly LayerManager layerManager; + + internal Layer(string name, LayerManager layerManager) + { + this.layerManager = layerManager; + Name = name; + } + + private void AddRequiredUpperLayer(Layer upperLayer) + { + requiredUpperLayers.Add(upperLayer); + upperLayer.requiredLowerLayers.Add(this); + } + + internal bool RemoveRequiredUpperLayer(Layer upperLayer) + { + upperLayer.requiredLowerLayers.Remove(this); + return requiredUpperLayers.Remove(upperLayer); + } + + /// + /// Tries to add a constraint that this layer should be drawn below + /// + /// + /// Whether the constraint is successfully applied. If not, nothing will be changed. + public bool RequireBelow(Layer upperLayer) + { + // Don't allow different layer managers + if (upperLayer.layerManager != layerManager) + return false; + + // Don't allow adding layers above the Top layer, or adding layers below the Bottom layer + if (LayerFlags.HasFlag(LayerFlags.Top) || upperLayer.LayerFlags.HasFlag(LayerFlags.Bottom)) + return false; + + AddRequiredUpperLayer(upperLayer); + var cycles = layerManager.RecalculateZValues(); + if (cycles.Count <= 0) + return true; + + // Build a representation of the cycle formed + var sb = new StringBuilder(); + foreach (var cycle in cycles) + { + foreach (var layer in cycle) + { + sb.Append(layer.Name); + sb.Append(" -> "); + } + + sb.Append(cycle[0].Name); + sb.Append(", "); + } + + Logger.Warning( + $"Unable to make layer '{Name}' below layer '{upperLayer.Name}'" + + $" since the following cycle(s) would be introduced: {sb}", + LogType.Runtime); + + // Revert the changes + RemoveRequiredUpperLayer(upperLayer); + layerManager.RecalculateZValues(); + return false; + } + + /// + /// Tries to add a constraint that this layer should be drawn above + /// + /// + public bool RequireAbove(Layer lowerLayer) + { + return lowerLayer.RequireBelow(this); + } + + /// + /// Removes the constraint that this layer should be below + /// + /// + /// Whether this constraint was present before calling + public bool StopRequireBelow(Layer upperLayer) + { + if (!RemoveRequiredUpperLayer(upperLayer)) + return false; + layerManager.RecalculateZValues(); + return true; + } + + /// + /// Removes the constraint that this layer should be above + /// + /// + /// + public bool StopRequireAbove(Layer lowerLayer) + { + return lowerLayer.StopRequireBelow(this); + } + + /// + /// Tries to add a constraint that this layer should be drawn between and . + /// + /// + /// + /// + public bool RequireBetween(Layer lowerLayer, Layer upperLayer) + { + if (!RequireAbove(lowerLayer)) + return false; + if (!RequireBelow(upperLayer)) + { + StopRequireAbove(lowerLayer); + return false; + } + + return true; + } + + + /// + /// Wraps the layer between two layers, {}.Lower and {}.Upper + /// Any constraint applied to it will be moved to the new lower and upper layer. + /// This is useful if you want to put something immediately above or below the layer. + /// + /// + public (Layer LowerLayer, Layer UpperLayer) Wrap() + { + var lowerLayer = layerManager.NewLayer($"{Name}.Lower"); + var upperLayer = layerManager.NewLayer($"{Name}.Upper"); + + foreach (var requiredLowerLayer in requiredLowerLayers) + { + requiredLowerLayer.requiredUpperLayers.Remove(this); + requiredLowerLayer.requiredUpperLayers.Add(lowerLayer); + lowerLayer.requiredLowerLayers.Add(lowerLayer); + } + + requiredLowerLayers.Clear(); + requiredLowerLayers.Add(lowerLayer); + + foreach (var requiredUpperLayer in requiredUpperLayers) + { + requiredUpperLayer.requiredLowerLayers.Remove(this); + requiredUpperLayer.requiredLowerLayers.Add(upperLayer); + upperLayer.requiredUpperLayers.Add(requiredUpperLayer); + } + + requiredUpperLayers.Clear(); + requiredUpperLayers.Add(upperLayer); + + lowerLayer.requiredUpperLayers.Add(this); + upperLayer.requiredLowerLayers.Add(this); + + layerManager.RecalculateZValues(); + return (lowerLayer, upperLayer); + } + + /// + /// Removes every relation to this layer. The layer's constraints are applied to its upper and lower layers + /// to preserve order. + /// + /// + public bool Isolate() + { + if (LayerFlags.HasFlag(LayerFlags.Top) || LayerFlags.HasFlag(LayerFlags.Bottom)) + return false; + + foreach (var requiredLowerLayer in requiredLowerLayers) + { + requiredLowerLayer.requiredUpperLayers.Remove(this); + requiredLowerLayer.requiredUpperLayers.UnionWith(requiredUpperLayers); + } + + + foreach (var requiredUpperLayer in requiredUpperLayers) + { + requiredUpperLayer.requiredLowerLayers.Remove(this); + requiredUpperLayer.requiredLowerLayers.UnionWith(requiredLowerLayers); + } + + requiredLowerLayers.Clear(); + requiredUpperLayers.Clear(); + + layerManager.RecalculateZValues(); + return true; + } + + /// + /// Clears and . + /// This does not recalculate Z values. + /// + internal void ClearAllConstraints() + { + requiredLowerLayers.Clear(); + requiredUpperLayers.Clear(); + } + + /// + /// + /// + /// Layer is marked + internal void AddDrawable(Drawable drawable) + { + if (LayerFlags.HasFlag(LayerFlags.NoChildren)) + throw new InvalidOperationException( + $"Cannot add drawable to layer '{Name}' since it's flagged NoChildren"); + drawables.Add(drawable); + } + + /// + /// + /// + internal void RemoveDrawable(Drawable drawable) => drawables.Remove(drawable); + + public void Draw(GameTime gameTime) + { + if (!Visible) + return; + + foreach (var drawable in drawables) + { + drawable.Draw(gameTime); + } + } + + internal void ResetTarjanData() => LayerTarjanData = new TarjanData { Index = -1 }; + + /// + /// Part of Tarjan's SCC algorithm. + /// + /// + /// + /// + /// + internal void StrongConnect(ref int currentIndex, ref int order, + Stack stack, + List> stronglyConnectedComponents) + { + LayerTarjanData.LowLink = currentIndex; + LayerTarjanData.Index = currentIndex; + currentIndex++; + stack.Push(this); + LayerTarjanData.InStack = true; + + foreach (var layer in requiredUpperLayers) + { + if (layer.LayerTarjanData.Index == -1) + { + layer.StrongConnect(ref currentIndex, ref order, stack, stronglyConnectedComponents); + LayerTarjanData.LowLink = Math.Min(LayerTarjanData.LowLink, layer.LayerTarjanData.LowLink); + } + else if (layer.LayerTarjanData.InStack) + { + LayerTarjanData.LowLink = Math.Min(LayerTarjanData.LowLink, layer.LayerTarjanData.Index); + } + } + + if (LayerTarjanData.LowLink != LayerTarjanData.Index) + return; + + LayerTarjanData.Order = order++; + var stronglyConnectComponent = new List(); + Layer v; + do + { + v = stack.Pop(); + v.LayerTarjanData.InStack = false; + v.LayerTarjanData.Order = LayerTarjanData.Order; + stronglyConnectComponent.Add(v); + } while (v != this); + + stronglyConnectedComponents.Add(stronglyConnectComponent); + } + + public void Dump() + { + var drawablesDump = + drawables.Count <= 10 ? string.Join(", ", drawables.Select(d => d.GetType().Name)) : ""; + Logger.Debug($"Layer '{Name}' ({LayerTarjanData.Order}): {drawables.Count} drawables {drawablesDump}", + LogType.Runtime); + } + + public void DumpConstraints() + { + var lowerLayers = string.Join(", ", requiredLowerLayers.Select(l => l.Name)); + var upperLayers = string.Join(", ", requiredUpperLayers.Select(l => l.Name)); + Logger.Debug($"Layer '{Name}' ({LayerTarjanData.Order}): [{lowerLayers}] < {Name} < [{upperLayers}]", + LogType.Runtime); + } + + public void Destroy() + { + drawables.Clear(); + layerManager?.RemoveLayer(this); + } + + ~Layer() + { + Destroy(); + } + + internal struct TarjanData + { + /// + /// Whether the layer was in the stack + /// + public bool InStack; + + /// + /// The order the layer is visited in DFS + /// + public int Index; + + /// + /// The lowest order of the layer reachable from (i.e. above) this layer + /// + public int LowLink; + + /// + /// The order of the SCC. This is the reverse index of the layer when topologically sorted. + /// + public int Order; + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/LayerFlags.cs b/Wobble/Graphics/LayerFlags.cs new file mode 100644 index 00000000..f43c4d02 --- /dev/null +++ b/Wobble/Graphics/LayerFlags.cs @@ -0,0 +1,30 @@ +using System; + +namespace Wobble.Graphics +{ + [Flags] + public enum LayerFlags + { + None = 0, + + /// + /// No layers can be put on top of this layer + /// + Top = 1 << 0, + + /// + /// No layers can be put below this layer + /// + Bottom = 1 << 1, + + /// + /// No children can be attached to this layer + /// + NoChildren = 1 << 2, + + /// + /// Allows scripts to do changes to the layers, or attach drawables to it. + /// + AllowScriptManipulation = 1 << 3 + } +} \ No newline at end of file diff --git a/Wobble/Graphics/LayerManager.cs b/Wobble/Graphics/LayerManager.cs new file mode 100644 index 00000000..a4cabf82 --- /dev/null +++ b/Wobble/Graphics/LayerManager.cs @@ -0,0 +1,252 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.Xna.Framework; +using Wobble.Logging; + +namespace Wobble.Graphics +{ + public class LayerManager + { + /// + /// Top-most layer that should have NO children + /// + public Layer TopLayer { get; private set; } + + /// + /// Default layer + /// + public Layer DefaultLayer { get; private set; } + + /// + /// Bottom-most layer that should contain NO children + /// + public Layer BottomLayer { get; private set; } + + private readonly Dictionary _layers = new Dictionary(); + + /// + /// Readonly view of the layers + /// + public IReadOnlyDictionary Layers => new ReadOnlyDictionary(_layers); + + private readonly List _sortedLayers = new List(); + + /// + /// The list of layers ordered by draw order (0 is topmost). Layers are drawn from bottom to top. + /// + public IReadOnlyList SortedLayers => new ReadOnlyCollection(_sortedLayers); + + private int _defaultLayerIndex; + + protected virtual void InitializeLayers() + { + } + + /// + /// Sets up basic layers, including topmost and bottom-most layer. + /// + public void Initialize() + { + TopLayer = NewLayer("Top"); + BottomLayer = NewLayer("Bottom"); + DefaultLayer = NewLayer("Default"); + + TopLayer.LayerFlags = LayerFlags.Top | LayerFlags.NoChildren; + BottomLayer.LayerFlags = LayerFlags.Bottom | LayerFlags.NoChildren; + + SetupDefaultOrder(); + + InitializeLayers(); + + RecalculateZValues(); + } + + private void SetupDefaultOrder() + { + RequireOrder(new[] + { + TopLayer, + DefaultLayer, + BottomLayer + }); + } + + /// + /// Creates a layers that persists in one screen only + /// + /// + /// + public Layer NewLayer(string name) + { + if (_layers.TryGetValue(name, out var layer)) + return layer; + layer = new Layer(name, this); + _layers.TryAdd(name, layer); + return layer; + } + + /// + /// Removes the layer and any constraints about it + /// + /// + public void RemoveLayer(Layer layerToRemove) + { + _layers.Remove(layerToRemove.Name); + foreach (var (_, layer) in _layers) + { + layer.RemoveRequiredUpperLayer(layerToRemove); + } + + RecalculateZValues(); + } + + /// + /// Requires that the layers in will be ordered top to bottom from first to last. + /// + /// + public static void RequireOrder(IReadOnlyList layersTopToBottom) + { + if (layersTopToBottom.Count < 2) + return; + + for (var i = 0; i < layersTopToBottom.Count - 1; i++) + { + layersTopToBottom[i].RequireAbove(layersTopToBottom[i + 1]); + } + } + + /// + /// Removes all constraints applied to any layer, except the default constraint (bottom to default to top) + /// + public void ResetOrder() + { + foreach (var (_, layer) in _layers) + { + layer.ClearAllConstraints(); + } + + SetupDefaultOrder(); + } + + /// + /// Runs Tarjan's SCC algorithm to do the following things in O(|V| + |E|) time: + /// * Find any SCC and thus cycle. It's trivial that an SCC must contain at least one cycle. + /// * Build a topologically sorted list of layers, ordered from top-most to bottom-most. + /// + /// List of SCCs that contain more than one layer (i.e. cycles) + internal List> RecalculateZValues() + { + var cycles = new List>(); + if (_layers.Count == 0) + return cycles; + + var currentIndex = 0; + var order = 0; + var stack = new Stack(); + var stronglyConnectedComponents = new List>(); + + foreach (var layer in _layers.Values) + layer.ResetTarjanData(); + + foreach (var layer in _layers.Values) + { + if (layer.LayerTarjanData.Index == -1) + layer.StrongConnect(ref currentIndex, ref order, stack, stronglyConnectedComponents); + } + + _defaultLayerIndex = -1; + _sortedLayers.Clear(); + foreach (var stronglyConnectedComponent in stronglyConnectedComponents) + { + _sortedLayers.AddRange(stronglyConnectedComponent); + if (stronglyConnectedComponent.Count <= 1) + continue; + cycles.Add(stronglyConnectedComponent); + } + + return cycles; + } + + /// + /// Draws all layers that are below the Default layer + /// + /// + public void DrawBackground(GameTime gameTime) + { + for (var i = _sortedLayers.Count - 1; i >= 0; i--) + { + var layer = _sortedLayers[i]; + if (layer == DefaultLayer) + { + _defaultLayerIndex = i; + return; + } + + layer.Draw(gameTime); + } + } + + /// + /// Draws the Default layer + /// + /// + public void DrawDefault(GameTime gameTime) => DefaultLayer.Draw(gameTime); + + public void DrawAll(GameTime gameTime) + { + DrawBackground(gameTime); + DrawDefault(gameTime); + DrawForeground(gameTime); + } + + /// + /// Draws all layers that are above the Default layer + /// + /// + public void DrawForeground(GameTime gameTime) + { + for (var i = _defaultLayerIndex - 1; i >= 0; i--) + { + var layer = _sortedLayers[i]; + layer.Draw(gameTime); + } + } + + public void Dump() + { + Logger.Debug($"{_layers.Count} Layers:", LogType.Runtime); + foreach (var layer in _sortedLayers) + { + layer.Dump(); + } + } + + public void DumpConstraints() + { + Logger.Debug($"{_layers.Count} Layers:", LogType.Runtime); + foreach (var layer in _sortedLayers) + { + layer.DumpConstraints(); + } + } + + public void Destroy() + { + _defaultLayerIndex = -1; + var layersToDestroy = _layers.Values.ToList(); + foreach (var layer in layersToDestroy) + { + layer.Destroy(); + } + + _sortedLayers.Clear(); + _layers.Clear(); + } + + ~LayerManager() + { + Destroy(); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/LayeredContainer.cs b/Wobble/Graphics/LayeredContainer.cs new file mode 100644 index 00000000..8bfc4419 --- /dev/null +++ b/Wobble/Graphics/LayeredContainer.cs @@ -0,0 +1,37 @@ +using Microsoft.Xna.Framework; + +namespace Wobble.Graphics +{ + public class LayeredContainer : Container + { + /// + /// Children with a set layer will be drawn by a separate layer manager + /// + public LayerManager LayerManager { get; private set; } + + public LayeredContainer() + { + InitializeLayerManager(); + } + + protected virtual void InitializeLayerManager() + { + LayerManager = new LayerManager(); + LayerManager.Initialize(); + } + + public override void Draw(GameTime gameTime) + { + LayerManager.DrawBackground(gameTime); + base.Draw(gameTime); + LayerManager.DrawDefault(gameTime); + LayerManager.DrawForeground(gameTime); + } + + public override void Destroy() + { + base.Destroy(); + LayerManager?.Destroy(); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/ModelObject.cs b/Wobble/Graphics/ModelObject.cs new file mode 100644 index 00000000..c2a00302 --- /dev/null +++ b/Wobble/Graphics/ModelObject.cs @@ -0,0 +1,77 @@ +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Wobble.Graphics +{ + public class ModelObject : Drawable3D + { + private Model _model; + + public Model Model + { + get => _model; + set + { + _model = value; + CalculateModelBoundingBox(); + } + } + + public BoundingBox ModelBoundingBox { get; private set; } + + public ModelObject(Model model) + { + Model = model; + + RecalculateRectangles(); + } + + public void CalculateModelBoundingBox() + { + var min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); + var max = new Vector3(float.MinValue, float.MinValue, float.MinValue); + + foreach (var mesh in Model.Meshes) + { + foreach (var meshPart in mesh.MeshParts) + { + var vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride; + var vertexBufferSize = meshPart.NumVertices * vertexStride; + + var vertexDataSize = vertexBufferSize / sizeof(float); + var vertexData = new float[vertexDataSize]; + meshPart.VertexBuffer.GetData(vertexData); + + for (var i = 0; i < vertexDataSize; i += vertexStride / sizeof(float)) + { + var vertex = new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]); + min = Vector3.Min(min, vertex); + max = Vector3.Max(max, vertex); + } + } + } + + ModelBoundingBox = new BoundingBox(min, max); + } + + public override void Draw(GameTime gameTime, Camera camera) + { + foreach (var mesh in Model.Meshes) + { + foreach (var effect in mesh.Effects.Cast()) + { + effect.EnableDefaultLighting(); + // effect.AmbientLightColor = new Vector3(1f, 0, 0); + effect.View = camera.ViewMatrix; + effect.World = Transformation * camera.World.Matrix; + effect.Projection = camera.ProjectionMatrix; + } + + mesh.Draw(); + } + + base.Draw(gameTime, camera); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Padding.cs b/Wobble/Graphics/Padding.cs new file mode 100644 index 00000000..e3481bf7 --- /dev/null +++ b/Wobble/Graphics/Padding.cs @@ -0,0 +1,100 @@ +using System.Diagnostics; +using System.Runtime.Serialization; +using Microsoft.Xna.Framework; + +namespace Wobble.Graphics +{ + /// + /// Specifies the padding from four directions (up, down, left, right) + /// + [DataContract] + [DebuggerDisplay("{DebugDisplayString,nq}")] + public struct Padding + { + [DataMember] public int Left; + [DataMember] public int Right; + [DataMember] public int Up; + [DataMember] public int Down; + + public static readonly Padding Zero = new Padding(0, 0, 0, 0); + + public Padding(int left, int right, int up, int down) + { + Left = left; + Right = right; + Up = up; + Down = down; + } + + internal string DebugDisplayString => $"L: {Left} U: {Up} R: {Right} D: {Down}"; + + /// + /// Displacement in position when padding *inwards* + /// + public Point Offset => new Point(Left, Up); + + /// + /// Increase in size when padding *outwards* + /// + /// + public Point SizeIncrement => new Point(Left + Right, Up + Down); + + public static Rectangle operator -(Rectangle source, Padding padding) + { + return padding.PadInwards(source); + } + + public static Rectangle operator +(Rectangle source, Padding padding) + { + return padding.PadOutwards(source); + } + + /// + /// Extends the in all four directions + /// + /// + /// + public Rectangle PadOutwards(Rectangle sourceRectangle) => + new Rectangle(sourceRectangle.Location - Offset, sourceRectangle.Size + SizeIncrement); + + /// + /// Shrinks the in all four directions + /// + /// + /// + public Rectangle PadInwards(Rectangle sourceRectangle) => + new Rectangle(sourceRectangle.Location + Offset, sourceRectangle.Size - SizeIncrement); + + public bool Equals(Padding other) + { + return Left == other.Left && Right == other.Right && Up == other.Up && Down == other.Down; + } + + public override bool Equals(object obj) + { + return obj is Padding other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Left; + hashCode = (hashCode * 397) ^ Right; + hashCode = (hashCode * 397) ^ Up; + hashCode = (hashCode * 397) ^ Down; + return hashCode; + } + } + + public static bool operator ==(Padding left, Padding right) + { + return left.Equals(right); + } + + public static bool operator !=(Padding left, Padding right) + { + return !left.Equals(right); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/PrimitiveObject.cs b/Wobble/Graphics/PrimitiveObject.cs new file mode 100644 index 00000000..b1be1d11 --- /dev/null +++ b/Wobble/Graphics/PrimitiveObject.cs @@ -0,0 +1,62 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Wobble.Graphics +{ + public abstract class PrimitiveObject : Drawable3D + { + protected VertexPositionColorTexture[] Vertices; + protected int[] Indices; + private VertexBuffer _vertexBuffer; + private IndexBuffer _indexBuffer; + public GraphicsDevice GraphicsDevice = GameBase.Game.GraphicsDevice; + public BasicEffect Effect; + public Texture2D Texture { get; set; } + + protected PrimitiveObject() + { + Effect = new BasicEffect(GraphicsDevice); + RecalculateRectangles(); + } + + public void UpdatePrimitives() + { + SetupIndices(); + SetupVertices(); + CopyToBuffer(); + } + + public abstract void SetupIndices(); + public abstract void SetupVertices(); + + public void CopyToBuffer() + { + _vertexBuffer = new VertexBuffer(GraphicsDevice, + VertexPositionColorTexture.VertexDeclaration, Vertices.Length, BufferUsage.WriteOnly); + _vertexBuffer.SetData(Vertices); + + _indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, + Indices.Length, BufferUsage.WriteOnly); + _indexBuffer.SetData(Indices); + } + + public override void Draw(GameTime gameTime, Camera camera) + { + Effect.World = Transformation * camera.World.Matrix; + Effect.Projection = camera.ProjectionMatrix; + Effect.View = camera.ViewMatrix; + Effect.VertexColorEnabled = true; + Effect.TextureEnabled = Texture != null; + Effect.Texture = Texture; + GraphicsDevice.Indices = _indexBuffer; + GraphicsDevice.SetVertexBuffer(_vertexBuffer); + foreach (var pass in Effect.CurrentTechnique.Passes) + { + pass.Apply(); + GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, Indices.Length / 3); + } + + base.Draw(gameTime, camera); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Primitives/FilledRectangleSprite.cs b/Wobble/Graphics/Primitives/FilledRectangleSprite.cs index 040f60c4..dd7b9b52 100644 --- a/Wobble/Graphics/Primitives/FilledRectangleSprite.cs +++ b/Wobble/Graphics/Primitives/FilledRectangleSprite.cs @@ -16,8 +16,9 @@ public override void DrawToSpriteBatch() if (!Visible) return; - GameBase.Game.SpriteBatch.FillRectangle(new Vector2(RenderRectangle.X, RenderRectangle.Y), - new Vector2(RenderRectangle.Width, RenderRectangle.Height), Tint * Alpha, Rotation); + var worldMatrix = Transform.SelfWorldMatrix.Matrix; + GameBase.Game.SpriteBatch.FillRectangle( + RelativeRectangle.Size, ref worldMatrix, AbsoluteColor); } } } \ No newline at end of file diff --git a/Wobble/Graphics/Primitives/Line.cs b/Wobble/Graphics/Primitives/Line.cs index e3aa59a9..34c81c61 100644 --- a/Wobble/Graphics/Primitives/Line.cs +++ b/Wobble/Graphics/Primitives/Line.cs @@ -43,8 +43,8 @@ public override void DrawToSpriteBatch() if (!Visible) return; - GameBase.Game.SpriteBatch.DrawLine(new Vector2(RenderRectangle.X, RenderRectangle.Y), - EndPosition, Tint * Alpha, Thickness); + GameBase.Game.SpriteBatch.DrawLine(AbsolutePosition, + EndPosition, AbsoluteColor, Thickness); } } } diff --git a/Wobble/Graphics/Primitives/PrimitiveLineBatch.cs b/Wobble/Graphics/Primitives/PrimitiveLineBatch.cs index b319afc5..25bcc92e 100644 --- a/Wobble/Graphics/Primitives/PrimitiveLineBatch.cs +++ b/Wobble/Graphics/Primitives/PrimitiveLineBatch.cs @@ -35,8 +35,8 @@ public override void DrawToSpriteBatch() if (!Visible) return; - Primitives2D.DrawPoints(GameBase.Game.SpriteBatch, - new Vector2(RenderRectangle.X, RenderRectangle.Y), Points, Tint * Alpha, Thickness); + var transform = Transform.SelfWorldMatrix.Matrix; + Primitives2D.DrawPoints(GameBase.Game.SpriteBatch, Points, ref transform, AbsoluteColor, Thickness); } } } \ No newline at end of file diff --git a/Wobble/Graphics/Primitives/Primitives2D.cs b/Wobble/Graphics/Primitives/Primitives2D.cs index 57b1e115..6f282f52 100644 --- a/Wobble/Graphics/Primitives/Primitives2D.cs +++ b/Wobble/Graphics/Primitives/Primitives2D.cs @@ -23,6 +23,7 @@ 3. This notice may not be removed or altered from any source using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -117,6 +118,29 @@ public static void FillRectangle(this SpriteBatch spriteBatch, Vector2 location, 0); } + /// + /// Draws a filled rectangle + /// + /// The destination drawing surface + /// Where to draw + /// The size of the rectangle + /// The angle in radians to draw the rectangle at + /// The color to draw the rectangle in + public static void FillRectangle(this SpriteBatch spriteBatch, Vector2 size, ref Matrix transform, Color color) + { + if (pixel == null) + { + CreateThePixel(spriteBatch); + } + + // stretch the pixel between the two vectors + spriteBatch.Draw(pixel, + size, + ref transform, + null, + color); + } + /// /// Draws a filled rectangle @@ -182,6 +206,18 @@ public static void DrawRectangle(this SpriteBatch spriteBatch, Rectangle rect, C DrawLine(spriteBatch, new Vector2(rect.Right + 1f, rect.Y), new Vector2(rect.Right + 1f, rect.Bottom + thickness), color, thickness); // right } + public static void DrawQuad(this SpriteBatch spriteBatch, Vector3[] vertices, Color color, + float thickness) + { + if (vertices.Length < 4) + throw new InvalidOperationException("The number of vertices must be at least 4."); + DrawLine(spriteBatch, vertices[0], vertices[1], color, thickness); // top + DrawLine(spriteBatch, vertices[1], vertices[3], color, thickness); // left + DrawLine(spriteBatch, vertices[3], vertices[2], color, thickness); // right + DrawLine(spriteBatch, vertices[2], vertices[0], color, thickness); // bottom + + } + /// /// Draws a rectangle with the thickness provided @@ -277,6 +313,25 @@ public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point1, Vector DrawLine(spriteBatch, point1, distance, angle, color, thickness); } + public static void DrawLine(this SpriteBatch spriteBatch, Vector3 point1, Vector3 point2, Color color, + float thickness) + { + if (pixel == null) + { + CreateThePixel(spriteBatch); + } + var extension = Vector3.Cross(point2 - point1, Vector3.UnitZ); + extension.Normalize(); + extension *= thickness; + spriteBatch.Draw(pixel, new[] + { + point1 - extension, + point1, + point2 - extension, + point2 + }, color, Vector2.Zero, Vector2.One); + } + /// /// Draws a line from point1 to point2 with an offset @@ -461,6 +516,20 @@ public static void DrawPoints(SpriteBatch spriteBatch, Vector2 position, List points, ref Matrix transform, Color color, float thickness) + { + if (points.Count < 2) + return; + + var srcPoints = points.Select(v => new Vector3(v, 0)).ToArray(); + var resultPoints = new Vector3[points.Count]; + Vector3.Transform(srcPoints, 0, ref transform, resultPoints, 0, points.Count); + + for (var i = 1; i < points.Count; i++) + { + DrawLine(spriteBatch, resultPoints[i - 1], resultPoints[i], color, thickness); + } + } /// diff --git a/Wobble/Graphics/Primitives/RectangleSprite.cs b/Wobble/Graphics/Primitives/RectangleSprite.cs index 54b3b94f..6e8fe546 100644 --- a/Wobble/Graphics/Primitives/RectangleSprite.cs +++ b/Wobble/Graphics/Primitives/RectangleSprite.cs @@ -27,8 +27,7 @@ public override void DrawToSpriteBatch() if (!Visible) return; - GameBase.Game.SpriteBatch.DrawRectangle(new Vector2(RenderRectangle.X, RenderRectangle.Y), - new Vector2(RenderRectangle.Width, RenderRectangle.Height), Tint * Alpha, Thickness); + GameBase.Game.SpriteBatch.DrawQuad(Transform.Vertices, AbsoluteColor, Thickness); } } } \ No newline at end of file diff --git a/Wobble/Graphics/ProjectionMode.cs b/Wobble/Graphics/ProjectionMode.cs new file mode 100644 index 00000000..22784603 --- /dev/null +++ b/Wobble/Graphics/ProjectionMode.cs @@ -0,0 +1,106 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Wobble.Graphics +{ + public class ProjectionMode + { + private Rectangle _viewportBounds; + private ProjectionType _projectionType; + private float _fieldOfView = 45f; + private float _nearPlaneDistance = 1f; + private float _farPlaneDistance = 1000f; + private float _zNearPlane = 0; + private float _zFarPlane = 1; + + public Camera Camera { get; set; } + + public Rectangle ViewportBounds + { + get => _viewportBounds; + set => _viewportBounds = value; + } + + public ProjectionMode(Rectangle viewportBounds, Camera camera) + { + ViewportBounds = viewportBounds; + Camera = camera; + } + + public ProjectionType ProjectionType + { + get => _projectionType; + set + { + _projectionType = value; + Camera.CalculateMatrices(); + } + } + + public float FieldOfView + { + get => _fieldOfView; + set + { + _fieldOfView = value; + Camera.CalculateMatrices(); + } + } + + public float NearPlaneDistance + { + get => _nearPlaneDistance; + set + { + _nearPlaneDistance = value; + Camera.CalculateMatrices(); + } + } + + public float FarPlaneDistance + { + get => _farPlaneDistance; + set + { + _farPlaneDistance = value; + Camera.CalculateMatrices(); + } + } + + public float ZNearPlane + { + get => _zNearPlane; + set + { + _zNearPlane = value; + Camera.CalculateMatrices(); + } + } + + public float ZFarPlane + { + get => _zFarPlane; + set + { + _zFarPlane = value; + Camera.CalculateMatrices(); + } + } + + public Matrix GetProjectionMatrix() + { + switch (ProjectionType) + { + case ProjectionType.Perspective: + return Matrix.CreatePerspectiveFieldOfView( + MathHelper.ToRadians(FieldOfView), GameBase.Game.Graphics.GraphicsDevice.Viewport.AspectRatio, + NearPlaneDistance, FarPlaneDistance); + case ProjectionType.Orthographic: + return Matrix.CreateOrthographicOffCenter(ViewportBounds, ZNearPlane, ZFarPlane); + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Quad.cs b/Wobble/Graphics/Quad.cs new file mode 100644 index 00000000..c30251c2 --- /dev/null +++ b/Wobble/Graphics/Quad.cs @@ -0,0 +1,33 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Wobble.Graphics +{ + public class Quad : PrimitiveObject + { + public override void SetupIndices() + { + Indices = new[] + { + 0, 1, 2, + 0, 2, 3 + }; + } + + public override void SetupVertices() + { + Vertices = new[] + { + new VertexPositionColorTexture(Vector3.Zero * Size, Color.White, Vector2.Zero), + new VertexPositionColorTexture(Vector3.UnitX * Size, Color.White, Vector2.UnitX), + new VertexPositionColorTexture((Vector3.UnitX + Vector3.UnitY) * Size, Color.White, Vector2.UnitX + Vector2.UnitY), + new VertexPositionColorTexture(Vector3.UnitY * Size, Color.White, Vector2.UnitY), + }; + } + + public override void Draw(GameTime gameTime, Camera camera) + { + base.Draw(gameTime, camera); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/RenderTargetOptions.cs b/Wobble/Graphics/RenderTargetOptions.cs new file mode 100644 index 00000000..2c16550d --- /dev/null +++ b/Wobble/Graphics/RenderTargetOptions.cs @@ -0,0 +1,166 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using Wobble.Bindables; +using Wobble.Graphics.Transform; +using Wobble.Window; + +namespace Wobble.Graphics +{ + /// + /// Specification of a render target + /// + public class RenderTargetOptions + { + private Rectangle _renderRectangle; + private bool _enabled; + private Point _containerRectangleSize; + private Padding _overflowRenderPadding; + private Transform3D _transformMatrix; + + /// + /// Whether the render target should be and is being used. + /// When enabling, will be refreshed if the dimensions don't match or if it's null. + /// When disabling, will be disposed + /// Both actions will trigger of + /// + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + if (value) + { + ResetRenderTarget(); + } + else + { + RenderTarget.Value?.Dispose(); + RenderTarget.Value = null; + } + RecalculateTransformMatrix(); + } + } + + public Bindable RenderTarget { get; } = new Bindable(null); + + /// + /// The size of the container itself. This does not include the overflow area. + /// + public Point ContainerRectangleSize + { + get => _containerRectangleSize; + set + { + if (_containerRectangleSize == value) + return; + _containerRectangleSize = value; + _renderRectangle = _overflowRenderPadding.PadOutwards(new Rectangle(Point.Zero, value)); + RecalculateTransformMatrix(); + } + } + + /// + /// Padding *outwards* from + /// It and combined gives . + /// For example, in quaver the playfield container's rectangle doesn't contain every child in its rectangle. + /// i.e. the actual rendering needs to be done on a larger render rectangle. + /// Adding padding makes the extra rendering visible. + /// + public Padding OverflowRenderPadding + { + get => _overflowRenderPadding; + set + { + if (_overflowRenderPadding == value) + return; + _overflowRenderPadding = value; + _renderRectangle = value.PadOutwards(new Rectangle(Point.Zero, _containerRectangleSize)); + RecalculateTransformMatrix(); + ResetRenderTarget(); + } + } + + /// + /// Absolute translation needed for the children of the container to be drawn. + /// + public Vector3 RenderOffset { get; private set; } + + /// + /// Relative rectangle of the full render target + /// + public Rectangle RenderRectangle => _renderRectangle; + + /// + /// Matrix needed to translate the position of children. + /// This includes translation by + /// followed by inverse scaling of . + /// + public Transform3D TransformMatrix => _transformMatrix; + + /// + /// When rendering to , the background color to give. + /// + public Color BackgroundColor { get; set; } = Color.Transparent; + + // SpriteBatchOptions will scale thing to WindowManager.ScreenScale, but out render target is already + // scaled, so we should scale them back. + public Vector3 Scale { get; private set; } + + public Vector3[] RelativeVertices { get; private set; } = new Vector3[4]; + + public RenderTargetOptions() + { + WindowManager.ResolutionChanged += WindowManagerOnResolutionChanged; + WindowManager.VirtualScreenSizeChanged += WindowManagerOnVirtualScreenSizeChanged; + } + + ~RenderTargetOptions() + { + WindowManager.ResolutionChanged -= WindowManagerOnResolutionChanged; + WindowManager.VirtualScreenSizeChanged -= WindowManagerOnVirtualScreenSizeChanged; + } + + private void WindowManagerOnVirtualScreenSizeChanged(object sender, WindowVirtualScreenSizeChangedEventArgs e) + { + RecalculateTransformMatrix(); + } + + private void WindowManagerOnResolutionChanged(object sender, WindowResolutionChangedEventArgs e) + { + RecalculateTransformMatrix(); + } + + public void RecalculateTransformMatrix() + { + var renderRectangle = RenderRectangle; + Scale = new Vector3(1 / WindowManager.ScreenScale.X, 1 / WindowManager.ScreenScale.Y, 1); + RenderOffset = new Vector3(-renderRectangle.X, -renderRectangle.Y, 0); + _transformMatrix = new Transform3D(MatrixHelper.CreateTranslationScale(RenderOffset, Scale)); + RelativeVertices[0] = new Vector3(renderRectangle.X, renderRectangle.Y, 0); + RelativeVertices[1] = new Vector3(renderRectangle.X + renderRectangle.Width, renderRectangle.Y, 0); + RelativeVertices[2] = new Vector3(renderRectangle.X, renderRectangle.Y + renderRectangle.Height, 0); + RelativeVertices[3] = new Vector3(renderRectangle.X + renderRectangle.Width, + renderRectangle.Y + renderRectangle.Height, 0); + } + + /// + /// Refreshes the render target. + /// Since creating a is expensive, it's only done when + /// has a null value or when the dimensions don't match. + /// + public void ResetRenderTarget() + { + if (!Enabled) + return; + + if (RenderTarget.Value != null && RenderTarget.Value.Bounds.Size == _renderRectangle.Size) + return; + + RenderTarget.Value = new RenderTarget2D(GameBase.Game.GraphicsDevice, + _renderRectangle.Width, _renderRectangle.Height, false, + GameBase.Game.GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.None); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/ScalableVector.cs b/Wobble/Graphics/ScalableVector.cs index 4f1e9f5d..8ef63c49 100644 --- a/Wobble/Graphics/ScalableVector.cs +++ b/Wobble/Graphics/ScalableVector.cs @@ -5,12 +5,12 @@ public struct ScalableVector /// /// The value of the vector. /// - public float Value { get; set; } + public float Value; /// /// The scale of the vector. /// - public float Scale { get; set; } + public float Scale; public ScalableVector(float value = 0, float scale = 0) { diff --git a/Wobble/Graphics/ScalableVector2.cs b/Wobble/Graphics/ScalableVector2.cs index e97bc5ce..2faa4040 100644 --- a/Wobble/Graphics/ScalableVector2.cs +++ b/Wobble/Graphics/ScalableVector2.cs @@ -1,4 +1,6 @@ -namespace Wobble.Graphics +using Wobble.Graphics.Animations; + +namespace Wobble.Graphics { /// /// 2 Dimensional ScalableVector @@ -8,12 +10,12 @@ public struct ScalableVector2 /// /// The X value /// - public ScalableVector X { get; } + public ScalableVector X; /// /// The y value. /// - public ScalableVector Y { get; } + public ScalableVector Y; /// /// Constructor @@ -32,5 +34,14 @@ public ScalableVector2(float xVal, float yVal, float xScale = 0, float yScale = public static ScalableVector2 operator /(ScalableVector2 lhs, float rhs) => new ScalableVector2(lhs.X.Value / rhs, lhs.Y.Value / rhs, lhs.X.Scale, lhs.Y.Scale); + + public static ScalableVector2 Lerp(ScalableVector2 from, ScalableVector2 to, float progress) + { + var x = EasingFunctions.Linear(from.X.Value, to.X.Value, progress); + var y = EasingFunctions.Linear(from.Y.Value, to.Y.Value, progress); + var xScale = EasingFunctions.Linear(from.X.Scale, to.X.Scale, progress); + var yScale = EasingFunctions.Linear(from.Y.Scale, to.Y.Scale, progress); + return new ScalableVector2(x, y, xScale, yScale); + } } } \ No newline at end of file diff --git a/Wobble/Graphics/Shaders/GaussianBlur.cs b/Wobble/Graphics/Shaders/GaussianBlur.cs index 336ec5d3..a9ae29b0 100644 --- a/Wobble/Graphics/Shaders/GaussianBlur.cs +++ b/Wobble/Graphics/Shaders/GaussianBlur.cs @@ -252,6 +252,7 @@ public Texture2D PerformGaussianBlur(Texture2D srcTexture) effect.Parameters["colorMapTexture"].SetValue(srcTexture); effect.Parameters["offsets"].SetValue(offsetsHoriz); + GameBase.Game.TryEndBatch(); GameBase.Game.SpriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect); GameBase.Game.SpriteBatch.Draw(srcTexture, destRect1, Color.White); GameBase.Game.SpriteBatch.End(); diff --git a/Wobble/Graphics/Shaders/Shader.cs b/Wobble/Graphics/Shaders/Shader.cs index 5a4a14ea..0edf6cda 100644 --- a/Wobble/Graphics/Shaders/Shader.cs +++ b/Wobble/Graphics/Shaders/Shader.cs @@ -112,5 +112,13 @@ public void SetParameter(string key, object value, bool setDictionaryValue) else throw new InvalidTypeParameterException($"ShaderEffect Parameter {key} has invalid type: {type}"); } + + public bool TrySetParameter(string key, object value, bool setDictionaryValue) + { + if (ShaderEffect.Parameters[key] == null) + return false; + SetParameter(key, value, setDictionaryValue); + return true; + } } } \ No newline at end of file diff --git a/Wobble/Graphics/SpriteBatchOptions.cs b/Wobble/Graphics/SpriteBatchOptions.cs index 507bf66f..1752dd21 100644 --- a/Wobble/Graphics/SpriteBatchOptions.cs +++ b/Wobble/Graphics/SpriteBatchOptions.cs @@ -17,6 +17,10 @@ public class SpriteBatchOptions public SamplerState SamplerState { get; set; } = SamplerState.LinearClamp; public DepthStencilState DepthStencilState { get; set; } public RasterizerState RasterizerState { get; set; } + public float Fov { get; set; } = MathF.PI / 2; + public ProjectionType ProjectionType { get; set; } = ProjectionType.Orthographic; + public float ZFar { get; set; } = 1000.0f; + /// /// Custom shader for this sprite. /// @@ -47,7 +51,8 @@ public void Begin() matrix = null; _ = GameBase.Game.TryEndBatch(); - GameBase.Game.SpriteBatch.Begin(SortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Shader?.ShaderEffect, matrix); + GameBase.Game.SpriteBatch.Begin(SortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Shader?.ShaderEffect, matrix, + ProjectionType, Fov, ZFar); } } } diff --git a/Wobble/Graphics/Sprites/BlurContainer.cs b/Wobble/Graphics/Sprites/BlurContainer.cs index 0d8338ce..2dd95783 100644 --- a/Wobble/Graphics/Sprites/BlurContainer.cs +++ b/Wobble/Graphics/Sprites/BlurContainer.cs @@ -12,7 +12,7 @@ namespace Wobble.Graphics.Sprites /// /// Container for blurring sprites. Any child element will be placed under the same blur effect. /// - public class BlurContainer : RenderTargetContainer + public class BlurContainer : Container { /// /// The type of blur to give it. @@ -35,8 +35,9 @@ public class BlurContainer : RenderTargetContainer /// public BlurContainer(BlurType blurType, float strength) { + CastToRenderTarget(); // ReSharper disable once ArrangeConstructorOrDestructorBody - SpriteBatchOptions = new SpriteBatchOptions() + DefaultProjectionSprite.SpriteBatchOptions = new SpriteBatchOptions() { SortMode = SpriteSortMode.Deferred, BlendState = BlendState.NonPremultiplied, @@ -56,7 +57,7 @@ public BlurContainer(BlurType blurType, float strength) BlurType = blurType; Strength = strength; - SpriteBatchOptions.Shader = new Shader(BlurEffects[BlurType], new Dictionary + DefaultProjectionSprite.SpriteBatchOptions.Shader = new Shader(BlurEffects[BlurType], new Dictionary { {"p_blurValues", new Vector3(Width, Height, Strength)} }); @@ -69,10 +70,10 @@ public BlurContainer(BlurType blurType, float strength) public override void Draw(GameTime gameTime) { // Set to the correct blur type. - SpriteBatchOptions.Shader.ShaderEffect = BlurEffects[BlurType]; + DefaultProjectionSprite.SpriteBatchOptions.Shader.ShaderEffect = BlurEffects[BlurType]; // Set dictionary parameters with updated properties. - SpriteBatchOptions.Shader.SetParameter("p_blurValues", new Vector3(Width, Height, Strength), true); + DefaultProjectionSprite.SpriteBatchOptions.Shader.SetParameter("p_blurValues", new Vector3(Width, Height, Strength), true); base.Draw(gameTime); } diff --git a/Wobble/Graphics/Sprites/RenderProjectionSprite.cs b/Wobble/Graphics/Sprites/RenderProjectionSprite.cs new file mode 100644 index 00000000..2f63f9c0 --- /dev/null +++ b/Wobble/Graphics/Sprites/RenderProjectionSprite.cs @@ -0,0 +1,102 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using Wobble.Bindables; + +namespace Wobble.Graphics.Sprites +{ + public class RenderProjectionSprite : Sprite + { + private Drawable _boundProjectionContainerSource; + + protected override void OnRectangleRecalculated() + { + } + + public override void DrawToSpriteBatch() + { + if (!Visible || _boundProjectionContainerSource == null) + return; + + var matrix = Transform.SelfWorldMatrix.Matrix; + var vertices = new Vector3[4]; + Array.Copy(_boundProjectionContainerSource.RenderTargetOptions.RelativeVertices, vertices, 4); + var rectangleSize = _boundProjectionContainerSource.RenderTargetOptions.ContainerRectangleSize.ToVector2(); + for (var i = 0; i < vertices.Length; i++) + { + vertices[i] = new Vector3(vertices[i].X / rectangleSize.X * RelativeRectangle.Width, + vertices[i].Y / rectangleSize.Y * RelativeRectangle.Height, 0); + } + + GameBase.Game.SpriteBatch.Draw(Image, vertices, ref matrix, null, AbsoluteColor, + SpriteEffect); + } + + public override void Destroy() + { + base.Destroy(); + if (_boundProjectionContainerSource != null) + { + _boundProjectionContainerSource.RenderTargetOptions.RenderTarget.ValueChanged -= OnRenderTargetChange; + _boundProjectionContainerSource.SizeChanged -= ContainerSizeChanged; + } + } + + /// + /// When called, the sprite will show the image of the container instead. + /// If the container is not drawing to render target, it will automatically do so + /// + /// The container to project its drawing from + public void BindProjectionContainer(Drawable container) + { + if (_boundProjectionContainerSource != null) + _boundProjectionContainerSource.RenderTargetOptions.RenderTarget.ValueChanged -= OnRenderTargetChange; + + _boundProjectionContainerSource = container; + + if (_boundProjectionContainerSource.RenderTargetOptions.RenderTarget?.Value == null) + _boundProjectionContainerSource.CastToRenderTarget(); + + SetRenderTarget(_boundProjectionContainerSource.RenderTargetOptions.RenderTarget?.Value); + container.RenderTargetOptions.RenderTarget.ValueChanged += OnRenderTargetChange; + container.SizeChanged += ContainerSizeChanged; + } + + public void SyncSourceProperties() + { + if (Parent != _boundProjectionContainerSource.Parent) + Parent = _boundProjectionContainerSource.Parent; + Scale = _boundProjectionContainerSource.Scale; + Rotation = _boundProjectionContainerSource.Rotation; + Position = _boundProjectionContainerSource.Position; + Alignment = _boundProjectionContainerSource.Alignment; + Size = _boundProjectionContainerSource.Size; + } + + private void ContainerSizeChanged(object sender, ScalableVector2 e) + { + UpdateShaderSizeParameter(); + } + + private void OnRenderTargetChange(object sender, BindableValueChangedEventArgs target2D) + { + SetRenderTarget(target2D.Value); + } + + private void SetRenderTarget(RenderTarget2D renderTarget2D) + { + Image = renderTarget2D; + UpdateShaderSizeParameter(); + } + + public void UpdateShaderSizeParameter() + { + var size = (Image?.Bounds.Size ?? new Point(1, 1)).ToVector2(); + SpriteBatchOptions?.Shader?.TrySetParameter("UVToSize", size, true); + size.X = 1 / size.X; + size.Y = 1 / size.Y; + SpriteBatchOptions?.Shader?.TrySetParameter("SizeToUV", size, true); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Sprites/RenderTargetContainer.cs b/Wobble/Graphics/Sprites/RenderTargetContainer.cs deleted file mode 100644 index 4ad03a0b..00000000 --- a/Wobble/Graphics/Sprites/RenderTargetContainer.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Wobble.Window; - -namespace Wobble.Graphics.Sprites -{ - /// - /// Sprite used to draw things to a different RenderTarget. - /// - public class RenderTargetContainer : Sprite - { - /// - /// The main render target to render to. - /// - private RenderTarget2D RenderTarget { get; } - - /// - /// - /// - /// - public RenderTargetContainer(ScalableVector2 size) - { - Size = size; - RenderTarget = new RenderTarget2D(GameBase.Game.GraphicsDevice, (int)Width, (int)Height); - } - - /// - /// - public RenderTargetContainer() - { - Size = new ScalableVector2(WindowManager.Width, WindowManager.Height); - RenderTarget = new RenderTarget2D(GameBase.Game.GraphicsDevice, (int)Width, (int)Height); - } - - /// - /// - /// - /// - public override void Draw(GameTime gameTime) - { - GameBase.Game.GraphicsDevice.SetRenderTarget(RenderTarget); - GameBase.Game.GraphicsDevice.Clear(Color.Transparent); - - // Draw all of the children - Children.ForEach(x => - { - x.UsePreviousSpriteBatchOptions = true; - x.Draw(gameTime); - }); - - // Attempt to end the spritebatch - _ = GameBase.Game.TryEndBatch(); - - // Reset the render target. - GameBase.Game.GraphicsDevice.SetRenderTarget(null); - - // Grab the old image so we can set it back later. - var oldImage = Image; - - // Change the image to the render target - // and draw it. - Image = RenderTarget; - base.Draw(gameTime); - - // Reset image. - Image = oldImage; - - // Attempt to end the spritebatch - _ = GameBase.Game.TryEndBatch(); - } - } -} diff --git a/Wobble/Graphics/Sprites/ScrollContainer.cs b/Wobble/Graphics/Sprites/ScrollContainer.cs index 502c94f3..45c478ab 100644 --- a/Wobble/Graphics/Sprites/ScrollContainer.cs +++ b/Wobble/Graphics/Sprites/ScrollContainer.cs @@ -225,11 +225,22 @@ public override void Update(GameTime gameTime) } // Make sure content container is clamped to the viewport. - TargetY = MathHelper.Clamp(TargetY, -ContentContainer.Height + Height, 0); + var contentHeightDifference = Height - ContentContainer.Height; + + TargetY = MathHelper.Clamp(TargetY, contentHeightDifference, 0); // Calculate the scrollbar's y position. - var percentage = Math.Abs(-ContentContainer.Y / (-ContentContainer.Height + Height) * 100); - Scrollbar.Y = percentage / 100 * (Scrollbar.Parent.Height - Scrollbar.Height) - (Scrollbar.Parent.Height - Scrollbar.Height); + var percentage = Math.Abs(-ContentContainer.Y / contentHeightDifference * 100); + + float scrollBarHeightDifference = Scrollbar.Parent.Height - Scrollbar.Height; + const float invisibleHeight = 1e9f; + + // When contentHeightDifference == 0, percentage would give an NaN value. + // We should avoid NaN values. When this happens, set the Y to a very large number + // so it gets clipped off. We can't use inf here because it gets decomposed into NaN somehow. + Scrollbar.Y = contentHeightDifference == 0 + ? invisibleHeight + : (percentage / 100 * scrollBarHeightDifference - scrollBarHeightDifference); if (IsMinScrollYEnabled && Scrollbar.Y < MinScrollBarY) Scrollbar.Y = MinScrollBarY; diff --git a/Wobble/Graphics/Sprites/Sprite.cs b/Wobble/Graphics/Sprites/Sprite.cs index 9b32c057..17c08c22 100644 --- a/Wobble/Graphics/Sprites/Sprite.cs +++ b/Wobble/Graphics/Sprites/Sprite.cs @@ -1,11 +1,10 @@ using System; +using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MonoGame.Extended; using Wobble.Assets; using Wobble.Graphics.Animations; -using Wobble.Graphics.Shaders; -using Wobble.Window; namespace Wobble.Graphics.Sprites { @@ -15,6 +14,18 @@ public class Sprite : Drawable /// the image texture of the sprite. /// private Texture2D _image; + + /// + /// the source texture of the sprite. If there are passes to the shaders, _image will be changed + /// but this won't + /// + private Texture2D _originalTexture; + + /// + /// If is not empty, this is used for applying shaders in them + /// + private RenderTarget2D _intermediateImage; + public Texture2D Image { get => _image; @@ -23,32 +34,43 @@ public Texture2D Image if (value == null) return; - _image = value; + _image = _originalTexture = value; - Origin = new Vector2(Image.Width / 2f, Image.Height / 2f); - RecalculateRectangles(); + Origin = new Vector2(Image.Width * Pivot.X, Image.Height * Pivot.Y); + + if (AdditionalPasses != null && AdditionalPasses.Count > 0) + { + _intermediateImage?.Dispose(); + _intermediateImage = new RenderTarget2D(GameBase.Game.GraphicsDevice, _image.Width, _image.Height, false, + GameBase.Game.GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.None); + GameBase.Game.ScheduledRenderTargetDraws.Add(PerformAdditionalPasses); + } } } + public List AdditionalPasses { get; set; } /// - /// Angle of the sprite with it's origin in the centre. (TEMPORARILY NOT USED YET) + /// The XNA SpriteEffects the sprite will have. + /// When the sprite is negatively scaled on some axis, it will be flipped over that axis too. /// - private float _rotation; - public float Rotation + public SpriteEffects SpriteEffect { - get => _rotation; - set => _rotation = MathHelper.ToRadians(value); + get + { + var spriteEffects = _spriteEffect; + if (ScreenRectangle.Width < 0) + spriteEffects ^= SpriteEffects.FlipHorizontally; + if (ScreenRectangle.Height < 0) + spriteEffects ^= SpriteEffects.FlipVertically; + return spriteEffects; + } + set => _spriteEffect = value; } - /// - /// The XNA SpriteEffects the sprite will have. - /// - public SpriteEffects SpriteEffect { get; set; } = SpriteEffects.None; - /// /// The origin of this object used for rotation. /// - public Vector2 Origin { get; private set; } + public Vector2 Origin { get; protected set; } /// /// The rectangle used to render the sprite. @@ -59,14 +81,24 @@ public float Rotation /// The tint this QuaverSprite will inherit. /// private Color _tint = Color.White; - public Color _color = Color.White; + + protected override Color RelativeColor + { + get + { + var baseColor = base.RelativeColor; + var spriteColor = _tint * _alpha; + return new Color(baseColor.ToVector4() * spriteColor.ToVector4()); + } + } + public Color Tint { get => _tint; set { _tint = value; - _color = _tint * _alpha; + RecalculateSelfColor(); } } @@ -80,7 +112,7 @@ public float Alpha set { _alpha = value; - _color = _tint * _alpha; + RecalculateSelfColor(); if (!SetChildrenAlpha) return; @@ -97,11 +129,45 @@ public float Alpha } } + /// + /// Additional rotation applied to this sprite only, and not to its children + /// + public Quaternion SpriteRotation + { + get => Transform.SelfRotation; + set => Transform.SelfRotation = value; + } + + private SpriteEffects _spriteEffect = SpriteEffects.None; + + /// + /// If true, the rotation of sprite shown on screen will be independent of its parent. + /// + public bool IndependentRotation + { + get => Transform.IndependentRotation; + set => Transform.IndependentRotation = value; + } + + /// + /// Actual rotation of sprite shown on screen. + /// It is decided by and parent's + /// + public float SpriteOverallRotation { get; protected set; } + /// /// Dictates if we want to set the alpha of the children as well. /// public bool SetChildrenAlpha { get; set; } + public override void Update(GameTime gameTime) + { + base.Update(gameTime); + + if (_originalTexture is RenderTarget2D && AdditionalPasses != null && AdditionalPasses.Count > 0) + GameBase.Game.ScheduledRenderTargetDraws.Add(PerformAdditionalPasses); + } + /// /// /// Draws the sprite to the screen. @@ -155,6 +221,32 @@ public override void Draw(GameTime gameTime) base.Draw(gameTime); } + /// + /// Transforms the from + /// Due to the nature of the MonoGame's drawing order, + /// Changes to will be delayed by one frame + /// + private void PerformAdditionalPasses(GameTime gameTime) + { + _ = GameBase.Game.TryEndBatch(); + GameBase.Game.GraphicsDevice.SetRenderTarget(_intermediateImage); + GameBase.Game.GraphicsDevice.Clear(Color.Transparent); + + for (var index = 0; index < AdditionalPasses.Count; index++) + { + var pass = AdditionalPasses[index]; + pass.Begin(); + + var target = index == 0 ? _originalTexture : _intermediateImage; + + GameBase.Game.SpriteBatch.Draw(target, new RectangleF(Point2.Zero, new Size2(target.Width, target.Height)), null, Color.White, 0, Vector2.Zero, SpriteEffects.None, 0f); + } + + _ = GameBase.Game.TryEndBatch(); + GameBase.Game.GraphicsDevice.SetRenderTarget(null); + _image = _intermediateImage; + } + /// /// /// @@ -163,7 +255,8 @@ public override void DrawToSpriteBatch() if (!Visible) return; - GameBase.Game.SpriteBatch.Draw(Image, RenderRectangle, null, _color, _rotation, Origin, SpriteEffect, 0f); + var matrix = Transform.SelfWorldMatrix.Matrix; + GameBase.Game.SpriteBatch.Draw(Image, RelativeRectangle.Size, ref matrix, null, AbsoluteColor, SpriteEffect); } /// @@ -180,13 +273,6 @@ public override void Destroy() /// protected override void OnRectangleRecalculated() { - if (Image == null) - return; - - // Update the render rectangle - // Add Width / 2 and Height / 2 to X, Y because that's what Origin is set to (in the Image setter). - RenderRectangle = new RectangleF(ScreenRectangle.X + ScreenRectangle.Width / 2f, ScreenRectangle.Y + ScreenRectangle.Height / 2f, - ScreenRectangle.Width, ScreenRectangle.Height); } /// diff --git a/Wobble/Graphics/Sprites/SpriteTextBitmap.cs b/Wobble/Graphics/Sprites/SpriteTextBitmap.cs index 63bc8190..ddba87b0 100644 --- a/Wobble/Graphics/Sprites/SpriteTextBitmap.cs +++ b/Wobble/Graphics/Sprites/SpriteTextBitmap.cs @@ -128,7 +128,7 @@ public override void DrawToSpriteBatch() } else { - GameBase.Game.SpriteBatch.DrawString(Font, DisplayedText, AbsolutePosition, _color, Rotation, + GameBase.Game.SpriteBatch.DrawString(Font, DisplayedText, AbsolutePosition, AbsoluteColor, Rotation, Vector2.Zero, new Vector2((float)FontSize / Font.LineHeight, (float)FontSize / Font.LineHeight), Effects, 0, null); } @@ -247,7 +247,7 @@ private void CacheTexture() if (pixelWidth == 0 || pixelHeight == 0 || string.IsNullOrEmpty(Text)) return; - GameBase.Game.ScheduledRenderTargetDraws.Add(() => + GameBase.Game.ScheduledRenderTargetDraws.Add(gameTime => { if (pixelWidth < 1) pixelWidth = 1; @@ -281,7 +281,6 @@ private void CacheTexture() Image = RenderTarget; - GameBase.Game.GraphicsDevice.SetRenderTarget(null); CachedTexture = true; }); } diff --git a/Wobble/Graphics/Sprites/Text/SpriteTextPlus.cs b/Wobble/Graphics/Sprites/Text/SpriteTextPlus.cs index 9905597f..05aef34b 100644 --- a/Wobble/Graphics/Sprites/Text/SpriteTextPlus.cs +++ b/Wobble/Graphics/Sprites/Text/SpriteTextPlus.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Linq; using Microsoft.Xna.Framework; -using SpriteFontPlus; using Wobble.Graphics.Animations; namespace Wobble.Graphics.Sprites.Text @@ -260,8 +259,8 @@ private void RefreshText() width = Math.Max(width, lineSprite.Width); - Font.Store.Size = FontSize; - height += Font.Store.GetLineHeight(); + Font.FontSize = FontSize; + height += Font.Store.LineHeight; } Size = new ScalableVector2(width, height); @@ -280,14 +279,14 @@ public void TruncateWithEllipsis(int maxWidth) { var text = Text; - Font.Store.Size = FontSize; + Font.FontSize = FontSize; var totalWidth = Font.Store.MeasureString(text).X; while (totalWidth > maxWidth) { text = text.Substring(0, text.Length - 1); - Font.Store.Size = FontSize; + Font.FontSize = FontSize; totalWidth = Font.Store.MeasureString(text).X; } @@ -310,12 +309,12 @@ public override void DrawToSpriteBatch() return; SetSize(); - GameBase.Game.SpriteBatch.DrawString(Font.Store, Text, AbsolutePosition, _tint * Alpha); + Font.Store.DrawText(GameBase.Game.SpriteBatch, Text, AbsolutePosition, _tint * Alpha); } private void SetSize() { - Font.Store.Size = FontSize; + Font.FontSize = FontSize; var (x, y) = Font.Store.MeasureString(Text); Size = new ScalableVector2(x, y); } diff --git a/Wobble/Graphics/Sprites/Text/SpriteTextPlusLine.cs b/Wobble/Graphics/Sprites/Text/SpriteTextPlusLine.cs index d158966d..b40f8829 100644 --- a/Wobble/Graphics/Sprites/Text/SpriteTextPlusLine.cs +++ b/Wobble/Graphics/Sprites/Text/SpriteTextPlusLine.cs @@ -146,7 +146,7 @@ public override void Update(GameTime gameTime) if (_dirty) { _dirty = false; - GameBase.Game.ScheduledRenderTargetDraws.Add(() => Cache(gameTime)); + GameBase.Game.ScheduledRenderTargetDraws.Add(Cache); } base.Update(gameTime); @@ -218,12 +218,10 @@ private void Cache(GameTime gameTime) GameBase.Game.GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.None); GameBase.Game.GraphicsDevice.SetRenderTarget(RenderTarget); - GameBase.Game.GraphicsDevice.Clear(Color.TransparentBlack); + GameBase.Game.GraphicsDevice.Clear(Color.Transparent); _raw.Draw(gameTime); _ = GameBase.Game.TryEndBatch(); - GameBase.Game.GraphicsDevice.SetRenderTarget(null); - Image = RenderTarget; } } diff --git a/Wobble/Graphics/Sprites/Text/SpriteTextPlusLineRaw.cs b/Wobble/Graphics/Sprites/Text/SpriteTextPlusLineRaw.cs index 2ab1a765..2eafc51c 100644 --- a/Wobble/Graphics/Sprites/Text/SpriteTextPlusLineRaw.cs +++ b/Wobble/Graphics/Sprites/Text/SpriteTextPlusLineRaw.cs @@ -1,4 +1,3 @@ -using SpriteFontPlus; using Wobble.Window; namespace Wobble.Graphics.Sprites.Text @@ -59,13 +58,13 @@ public override void DrawToSpriteBatch() if (!Visible) return; - Font.Store.Size = FontSize; - GameBase.Game.SpriteBatch.DrawString(Font.Store, Text, AbsolutePosition, _color); + Font.FontSize = FontSize; + Font.Store.DrawText(GameBase.Game.SpriteBatch, Text, AbsolutePosition, AbsoluteColor); } private void RefreshSize() { - Font.Store.Size = FontSize; + Font.FontSize = FontSize; var (x, y) = Font.Store.MeasureString(Text); Size = new ScalableVector2(x, y); diff --git a/Wobble/Graphics/Sprites/Text/WobbleFontStore.cs b/Wobble/Graphics/Sprites/Text/WobbleFontStore.cs index 5c79720a..abf9339b 100644 --- a/Wobble/Graphics/Sprites/Text/WobbleFontStore.cs +++ b/Wobble/Graphics/Sprites/Text/WobbleFontStore.cs @@ -1,22 +1,35 @@ using System; using System.Collections.Generic; +using FontStashSharp; using Microsoft.Xna.Framework.Graphics; -using SpriteFontPlus; namespace Wobble.Graphics.Sprites.Text { public class WobbleFontStore { + private float _fontSize; + private readonly FontSystem _fontSystem; + /// /// All of the contained fonts at different sizes /// - public DynamicSpriteFont Store { get; } + public DynamicSpriteFont Store { get; set; } /// /// The size the font was initially created ad /// public int DefaultSize { get; } + public float FontSize + { + get => _fontSize; + set + { + _fontSize = value; + Store = _fontSystem.GetFont(value); + } + } + /// /// /// @@ -25,13 +38,20 @@ public class WobbleFontStore public WobbleFontStore(int size, byte[] font, Dictionary addedFonts = null) { DefaultSize = size; - Store = DynamicSpriteFont.FromTtf(font, size); + + _fontSystem = new FontSystem(); + _fontSystem.AddFont(font); + Store = _fontSystem.GetFont(size); if (addedFonts == null) + { + FontSize = size; return; + } foreach (var f in addedFonts) AddFont(f.Key, f.Value); + FontSize = size; } /// @@ -39,6 +59,6 @@ public WobbleFontStore(int size, byte[] font, Dictionary addedFo /// /// /// - public void AddFont(string name, byte[] font) => Store.AddTtf(name, font); + public void AddFont(string name, byte[] font) => _fontSystem.AddFont(font); } } \ No newline at end of file diff --git a/Wobble/Graphics/Transform/DrawableTransform.cs b/Wobble/Graphics/Transform/DrawableTransform.cs new file mode 100644 index 00000000..cc02be3d --- /dev/null +++ b/Wobble/Graphics/Transform/DrawableTransform.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections.Specialized; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using Wobble.Window; + +namespace Wobble.Graphics.Transform +{ + /// + /// Represents the position, rotation, and scale of a three-dimensional game object. + /// + /// + /// + /// + /// Every game object has a transform which is used to store and manipulate the position, rotation and scale + /// of the object. Every transform can have a parent, which allows to apply position, rotation and scale to game + /// objects hierarchically. + /// + /// + public class DrawableTransform : WobbleBaseTransform + { + private Vector3 _calculatedPosition; + private Vector3 _rotationAxis = Vector3.UnitZ; + private float _rotationAngle; + private Quaternion _selfRotation = Quaternion.Identity; + private Vector2 _scale = Vector2.One; + private Vector2 _pivot = new(0.5f, 0.5f); + private Vector3 _origin = new(0.5f, 0.5f, 0.5f); + private ScalableVector2 _size; + private ScalableVector2 _position; + private Alignment _alignment = Alignment.TopLeft; + private bool _independentRotation; + private Transform3D _selfLocalMatrix; + private Transform3D _childLocalMatrix; + private Transform3D _selfWorldMatrix; + private Transform3D _childWorldMatrix; + private BitVector32 _selfFlags; + private static readonly int SelfMatrixOverride = BitVector32.CreateMask(); + private static readonly int SelfRotationSet = BitVector32.CreateMask(SelfMatrixOverride); + private BitVector32 _childFlags; + private static readonly int ChildMatrixOverride = BitVector32.CreateMask(); + + /// + /// top left, top right, bottom left, bottom right + /// + private readonly Vector3[] sourceVertices = new Vector3[4]; + + private readonly Vector3[] _vertices = new Vector3[4]; + + #region World + + public Quaternion SelfRotation + { + get => _selfRotation; + set + { + _selfRotation = value; + _selfFlags[SelfRotationSet] = value != Quaternion.Identity; + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + public override Transform3D SelfWorldMatrix + { + get + { + if (_selfFlags.Data == 0) + return WorldMatrix; + + RecalculateWorldMatrixIfNecessary(); + return _selfWorldMatrix; + } + protected set + { + _selfWorldMatrix = value; + RecalculateVertices(); + } + } + + public override Transform3D ChildWorldMatrix + { + get + { + if (_childFlags.Data == 0) + return WorldMatrix; + + RecalculateWorldMatrixIfNecessary(); + return _childWorldMatrix; + } + protected set => _childWorldMatrix = value; + } + + public Vector3[] Vertices + { + get + { + RecalculateWorldMatrixIfNecessary(); + return _vertices; + } + } + + /// + /// Gets the world position. + /// + /// + /// The world position. + /// + public Vector3 WorldPosition => WorldMatrix.Translation; + + /// + /// Gets the world scale. + /// + /// + /// The world scale. + /// + public Vector3 WorldScale + { + get + { + WorldMatrix.Decompose(out _, out _, out var scale); + return scale; + } + } + + + /// + /// Gets the world rotation quaternion in radians. + /// + /// + /// The world rotation quaternion in radians. + /// + public Quaternion WorldRotation + { + get + { + WorldMatrix.Decompose(out _, out var rotation, out _); + return rotation; + } + } + + #endregion + + #region Local + + public Alignment Alignment + { + get => _alignment; + set + { + _alignment = value; + RecalculatePosition(); + } + } + + public ScalableVector2 Size + { + get => _size; + set + { + _size = value; + RecalculatePosition(); + } + } + + public ScalableVector2 Position + { + get => _position; + set + { + _position = value; + RecalculatePosition(); + } + } + + public float Z + { + get => _calculatedPosition.Z; + set + { + _calculatedPosition.Z = value; + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + /// + /// Gets or sets the local position. + /// + /// + /// The local position. + /// + public Vector3 CalculatedPosition + { + get => _calculatedPosition; + protected set + { + _calculatedPosition = value; + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + public float RotationAngle + { + get => _rotationAngle; + set + { + _rotationAngle = value; + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + public Vector3 RotationAxis + { + get => _rotationAxis; + set + { + _rotationAxis = value; + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + /// + /// Gets or sets the local rotation quaternion in radians. + /// + /// + /// The local rotation quaternion in radians. + /// + public Quaternion Rotation + { + get { return Quaternion.CreateFromAxisAngle(_rotationAxis, _rotationAngle); } + set + { + _rotationAngle = MathF.Acos(value.W) * 2; + _rotationAxis = new Vector3(value.X, value.Y, value.Z); + _rotationAxis.Normalize(); + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + /// + /// Gets or sets the local scale. + /// + /// + /// The local scale. + /// + public Vector2 Scale + { + get { return _scale; } + set + { + _scale = value; + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + /// + /// Gets or sets the local scale. + /// + /// + /// The local scale. + /// + public Vector3 Origin + { + get { return _origin; } + set + { + _origin = value; + _pivot = new Vector2(_origin.X / RelativeRectangle.Width, _origin.Y / RelativeRectangle.Height); + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + /// + /// If true, cancels all rotation effects from the parent. + /// + public bool IndependentRotation + { + get => _independentRotation; + set + { + _independentRotation = value; + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + public Vector2 Pivot + { + get => _pivot; + set + { + _pivot = value; + _origin = new Vector3(RelativeRectangle.Size * _pivot, 0.5f); + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + } + + #endregion + + private bool NeedsSelfTransformCalculation => _selfFlags.Data != 0 && !_selfFlags[SelfMatrixOverride]; + + private bool NeedsChildTransformCalculation => _childFlags.Data != 0 && !_childFlags[ChildMatrixOverride]; + + public DrawableTransform() + { + ParentChanged += OnParentChanged; + TransformUpdated += RecalculateVertices; + } + + public event Action PositionRecalculated; + + /// + /// The drawable's rectangle relative to the entire screen. + /// + public RectangleF ScreenRectangle { get; private set; } + + /// + /// The bounding box of the drawable relative to the entire screen. + /// + public RectangleF ScreenMinimumBoundingRectangle { get; private set; } + + /// + /// The rectangle relative to the drawable's parent. + /// + public RectangleF RelativeRectangle { get; private set; } + + public void OverrideChildMatrix(Transform3D? matrix) + { + if (matrix.HasValue) + { + _childFlags[ChildMatrixOverride] = true; + ChildWorldMatrix = matrix.Value; + } + else + { + _childFlags[ChildMatrixOverride] = false; + } + + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + + public void OverrideSelfMatrix(Transform3D? matrix) + { + if (matrix.HasValue) + { + _selfFlags[SelfMatrixOverride] = true; + SelfWorldMatrix = matrix.Value; + } + else + { + _selfFlags[SelfMatrixOverride] = false; + } + + LocalMatrixBecameDirty(); + WorldMatrixBecameDirty(); + } + + protected virtual void RecalculatePosition() + { + var parent = (DrawableTransform)Parent; + // Make it relative to the parent. + var width = _size.X.Value + + (parent?.RelativeRectangle.Width ?? WindowManager.VirtualScreen.X) * _size.X.Scale; + var height = _size.Y.Value + + (parent?.RelativeRectangle.Height ?? WindowManager.VirtualScreen.Y) * _size.Y.Scale; + var x = _position.X.Value; + var y = _position.Y.Value; + + // Make it relative to the screen size. + RelativeRectangle = GraphicsHelper.AlignRect(_alignment, new RectangleF(x, y, width, height), + parent?.RelativeRectangle ?? WindowManager.Rectangle, true); + + sourceVertices[1].X = sourceVertices[3].X = width; + sourceVertices[2].Y = sourceVertices[3].Y = height; + + Pivot = _pivot; + CalculatedPosition = new Vector3(RelativeRectangle.Position, _calculatedPosition.Z); + + SelfWorldMatrix.Decompose(out var worldScale, out _, out var worldPos); + var worldSize = new Vector2(RelativeRectangle.Width * worldScale.X, + RelativeRectangle.Height * worldScale.Y); + ScreenRectangle = new RectangleF(worldPos.X, worldPos.Y, worldSize.X, worldSize.Y); + + RecalculateVertices(); + + PositionRecalculated?.Invoke(); + } + + private void RecalculateVertices() + { + SelfWorldMatrix.Transform(sourceVertices, Vertices); + ScreenMinimumBoundingRectangle = GraphicsHelper.MinimumBoundingRectangle( + Vertices[0], Vertices[1], Vertices[2], Vertices[3]); + } + + private void OnParentChanged(WobbleBaseTransform oldParent, + WobbleBaseTransform newParent) + { + if (oldParent is DrawableTransform oldDrawableTransform) + { + oldDrawableTransform.PositionRecalculated -= RecalculatePosition; + } + + if (newParent is DrawableTransform newDrawableTransform) + { + newDrawableTransform.PositionRecalculated += RecalculatePosition; + } + + RecalculatePosition(); + } + + protected override void RecalculateWorldMatrix(ref Transform3D localMatrix, + out Transform3D worldMatrix) + { + if (Parent != null) + { + var parentChildWorldMatrix = Parent.ChildWorldMatrix; + Transform3D.Multiply(ref localMatrix, ref parentChildWorldMatrix, out worldMatrix); + if (_selfFlags.Data != 0 && !_selfFlags[SelfMatrixOverride]) + Transform3D.Multiply(ref _selfLocalMatrix, ref parentChildWorldMatrix, out _selfWorldMatrix); + if (_childFlags.Data != 0 && !_childFlags[ChildMatrixOverride]) + Transform3D.Multiply(ref _childLocalMatrix, ref parentChildWorldMatrix, out _childWorldMatrix); + } + else + { + worldMatrix = localMatrix; + if (NeedsSelfTransformCalculation) + _selfWorldMatrix = _selfLocalMatrix; + if (NeedsChildTransformCalculation) + _childWorldMatrix = _childLocalMatrix; + } + } + + protected override void RecalculateLocalMatrix(out Transform3D localMatrix) + { + var rotation = Rotation; + if (IndependentRotation && Parent != null) + { + var parentChildMatrix = Parent.ChildWorldMatrix; + parentChildMatrix.Decompose(out _, out var parentWorldRotation, out _); + rotation = Quaternion.Inverse(parentWorldRotation) * rotation; + } + + localMatrix = new Transform3D(_calculatedPosition, rotation, new Vector3(_scale.X, _scale.Y, 1), _origin); + + if (NeedsSelfTransformCalculation) + { + var selfRotation = SelfRotation * rotation; + _selfLocalMatrix = new Transform3D(_calculatedPosition, selfRotation, + new Vector3(_scale.X, _scale.Y, 1), + _origin); + } + + if (NeedsChildTransformCalculation) + { + // ... + _childLocalMatrix = localMatrix; + } + } + + public override string ToString() + { + return $"Position: {CalculatedPosition}, Rotation: {Rotation}, Scale: {Scale}"; + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Transform/MatrixHelper.cs b/Wobble/Graphics/Transform/MatrixHelper.cs new file mode 100644 index 00000000..b4799b64 --- /dev/null +++ b/Wobble/Graphics/Transform/MatrixHelper.cs @@ -0,0 +1,22 @@ +using Microsoft.Xna.Framework; + +namespace Wobble.Graphics.Transform; + +public static class MatrixHelper +{ + /// + /// Result of * + /// + /// + /// + /// + public static Matrix CreateTranslationScale(Vector3 translation, Vector3 scale) + { + return new Matrix( + scale.X, 0, 0, 0, + 0, scale.Y, 0, 0, + 0, 0, scale.Z, 0, + translation.X * scale.X, translation.Y * scale.Y, translation.Z * scale.Z, 1 + ); + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Transform/Transform3D.cs b/Wobble/Graphics/Transform/Transform3D.cs new file mode 100644 index 00000000..34e10470 --- /dev/null +++ b/Wobble/Graphics/Transform/Transform3D.cs @@ -0,0 +1,127 @@ +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using Plane = Microsoft.Xna.Framework.Plane; +using Quaternion = Microsoft.Xna.Framework.Quaternion; +using Vector2 = Microsoft.Xna.Framework.Vector2; +using Vector3 = Microsoft.Xna.Framework.Vector3; + +namespace Wobble.Graphics.Transform +{ + public struct Transform3D + { + private Matrix _matrix = Matrix.Identity; + + public Matrix Matrix => _matrix; + + public Vector3 Translation + { + get => _matrix.Translation; + set => _matrix.Translation = value; + } + + public float RotationZ + { + get + { + Decompose2D(out _, out var rotation, out _); + return rotation; + } + } + + public Plane Plane => new (_matrix.Translation, Vector3.Cross(_matrix.Right, _matrix.Up)); + + public Transform3D() + { + } + + public Transform3D(Matrix matrix) + { + _matrix = matrix; + } + + public Transform3D(Transform3D transform) + { + _matrix = transform._matrix; + } + + public Transform3D(Vector3 position, Quaternion rotation, Vector3 scale) + { + var scaleMatrix = Matrix.CreateScale(scale); + var rotationMatrix = Matrix.CreateFromQuaternion(rotation); + Matrix.Multiply(ref scaleMatrix, ref rotationMatrix, out _matrix); + Translate(position); + } + + public Transform3D(Vector3 position, Quaternion rotation, Vector3 scale, Vector3 origin) + { + var m1 = MatrixHelper.CreateTranslationScale(-origin, scale); + var rotationMatrix = Matrix.CreateFromQuaternion(rotation); + Matrix.Multiply(ref m1, ref rotationMatrix, out _matrix); + Translate(origin + position); + } + + public void Invert() + { + Matrix.Invert(ref _matrix, out _matrix); + } + + public Transform3D Inverted() + { + var transform = new Transform3D(this); + transform.Invert(); + return transform; + } + + public Vector3 Transform(Vector3 vector) + { + return Vector3.Transform(vector, _matrix); + } + + public void Transform(Vector3[] source, Vector3[] result) + { + Transform(source, ref this, result); + } + + public static void Transform(Vector3[] source, ref Transform3D transform, Vector3[] result) + { + Vector3.Transform(source, ref transform._matrix, result); + } + + public static Transform3D Transform(Transform3D left, Transform3D right) + { + Multiply(ref left, ref right, out var result); + return result; + } + + public Vector3 BasisTransform(Vector3 vector) + { + return Vector3.TransformNormal(vector, _matrix); + } + + public void TranslateLocal(Vector3 translation) + { + _matrix.Translation += BasisTransform(translation); + } + + public void Translate(Vector3 translation) + { + _matrix.Translation += translation; + } + + public void Decompose(out Vector3 scale, out Quaternion rotation, out Vector3 position) + { + _matrix.Decompose(out scale, out rotation, out position); + } + + public void Decompose2D(out Vector2 scale, out float rotation, out Vector2 position) + { + _matrix.Decompose(out position, out rotation, out scale); + } + + public static void Multiply(ref Transform3D matrix, ref Transform3D transform, out Transform3D result) + { + result = new Transform3D(); + Matrix.Multiply(ref matrix._matrix, ref transform._matrix, out result._matrix); + } + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Transform/TransformFlags.cs b/Wobble/Graphics/Transform/TransformFlags.cs new file mode 100644 index 00000000..0d0781bc --- /dev/null +++ b/Wobble/Graphics/Transform/TransformFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Wobble.Graphics.Transform +{ + [Flags] + internal enum TransformFlags : byte + { + WorldMatrixIsDirty = 1 << 0, + LocalMatrixIsDirty = 1 << 1, + All = WorldMatrixIsDirty | LocalMatrixIsDirty + } +} \ No newline at end of file diff --git a/Wobble/Graphics/Transform/WobbleBaseTransform.cs b/Wobble/Graphics/Transform/WobbleBaseTransform.cs new file mode 100644 index 00000000..a7361daf --- /dev/null +++ b/Wobble/Graphics/Transform/WobbleBaseTransform.cs @@ -0,0 +1,172 @@ +using System; +using System.ComponentModel; +using MonoGame.Extended; + +namespace Wobble.Graphics.Transform +{ + /// + /// Represents the base class for the position, rotation, and scale of a game object in two-dimensions or + /// three-dimensions. + /// + /// The type of the matrix. + /// + /// + /// Every game object has a transform which is used to store and manipulate the position, rotation and scale + /// of the object. Every transform can have a parent, which allows to apply position, rotation and scale to game + /// objects hierarchically. + /// + /// + /// This class shouldn't be used directly. Instead use either of the derived classes; or + /// Transform3D. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class WobbleBaseTransform + where TMatrix : struct + { + private TransformFlags _flags = TransformFlags.All; // dirty flags, set all dirty flags when created + private TMatrix _localMatrix; // model space to local space + private WobbleBaseTransform _parent; // parent + private TMatrix _worldMatrix; // local space to world space + + public virtual TMatrix ChildWorldMatrix + { + get => WorldMatrix; + protected set => throw new InvalidOperationException(); + } + + public virtual TMatrix SelfWorldMatrix + { + get => WorldMatrix; + protected set => throw new InvalidOperationException(); + } + + // internal contructor because people should not be using this class directly; they should use Transform2D or Transform3D + protected WobbleBaseTransform() + { + } + + /// + /// Gets the model-to-local space. + /// + /// + /// The model-to-local space. + /// + public TMatrix LocalMatrix + { + get + { + RecalculateLocalMatrixIfNecessary(); // attempt to update local matrix upon request if it is dirty + return _localMatrix; + } + } + + /// + /// Gets the local-to-world space. + /// + /// + /// The local-to-world space. + /// + public TMatrix WorldMatrix + { + get + { + RecalculateWorldMatrixIfNecessary(); // attempt to update world matrix upon request if it is dirty + return _worldMatrix; + } + } + + /// + /// Gets or sets the parent instance. + /// + /// + /// The parent instance. + /// + /// + /// + /// Setting to a non-null instance enables this instance to + /// inherit the position, rotation, and scale of the parent instance. Setting to + /// null disables the inheritance altogether for this instance. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public WobbleBaseTransform Parent + { + get { return _parent; } + set + { + if (_parent == value) + return; + + var oldParentTransform = Parent; + _parent = value; + OnParentChanged(oldParentTransform, value); + } + } + + public event Action TransformBecameDirty; // observer pattern for when the world (or local) matrix became dirty + public event Action TransformUpdated; // observer pattern for after the world (or local) matrix was re-calculated + public event Action, WobbleBaseTransform> ParentChanged; + + protected internal void LocalMatrixBecameDirty() + { + _flags |= TransformFlags.LocalMatrixIsDirty; + } + + protected internal void WorldMatrixBecameDirty() + { + _flags |= TransformFlags.WorldMatrixIsDirty; + TransformBecameDirty?.Invoke(); + } + + private void OnParentChanged(WobbleBaseTransform oldParent, WobbleBaseTransform newParent) + { + var parent = oldParent; + while (parent != null) + { + parent.TransformBecameDirty -= ParentOnTransformBecameDirty; + parent = parent.Parent; + } + + parent = newParent; + while (parent != null) + { + parent.TransformBecameDirty += ParentOnTransformBecameDirty; + parent = parent.Parent; + } + ParentChanged?.Invoke(oldParent, newParent); + } + + private void ParentOnTransformBecameDirty() + { + _flags |= TransformFlags.All; + } + + public void RecalculateWorldMatrixIfNecessary() + { + if ((_flags & TransformFlags.WorldMatrixIsDirty) == 0) + return; + + RecalculateLocalMatrixIfNecessary(); + RecalculateWorldMatrix(ref _localMatrix, out _worldMatrix); + + _flags &= ~TransformFlags.WorldMatrixIsDirty; + TransformUpdated?.Invoke(); + } + + protected abstract void RecalculateWorldMatrix(ref TMatrix localMatrix, out TMatrix worldMatrix); + + private void RecalculateLocalMatrixIfNecessary() + { + if ((_flags & TransformFlags.LocalMatrixIsDirty) == 0) + return; + + RecalculateLocalMatrix(out _localMatrix); + + _flags &= ~TransformFlags.LocalMatrixIsDirty; + WorldMatrixBecameDirty(); + } + + protected abstract void RecalculateLocalMatrix(out TMatrix localMatrix); + } +} \ No newline at end of file diff --git a/Wobble/Graphics/UI/Form/Textbox.cs b/Wobble/Graphics/UI/Form/Textbox.cs index 59dc67dc..49a81c67 100644 --- a/Wobble/Graphics/UI/Form/Textbox.cs +++ b/Wobble/Graphics/UI/Form/Textbox.cs @@ -527,7 +527,7 @@ private void ChangeCursorLocation() } var substring = RawText.Substring(0, CursorPosition); - InputText.Font.Store.Size = InputText.FontSize; + InputText.Font.FontSize = InputText.FontSize; var x = InputText.Font.Store.MeasureString(substring).X; Cursor.X = x + InputText.X; @@ -552,7 +552,7 @@ private void UpdateSelectedSprite() } var startSubstring = RawText.Substring(0, SelectedPart.start); var selectedSubstring = RawText.Substring(SelectedPart.start, SelectedPart.end - SelectedPart.start); - InputText.Font.Store.Size = InputText.FontSize; + InputText.Font.FontSize = InputText.FontSize; var x = InputText.Font.Store.MeasureString(startSubstring).X; var width = InputText.Font.Store.MeasureString(selectedSubstring).X; diff --git a/Wobble/Graphics/World.cs b/Wobble/Graphics/World.cs new file mode 100644 index 00000000..7a492af1 --- /dev/null +++ b/Wobble/Graphics/World.cs @@ -0,0 +1,20 @@ +using Microsoft.Xna.Framework; + +namespace Wobble.Graphics +{ + public class World : Drawable3D + { + public Matrix Matrix { get; } + + public World() + { + Matrix = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up); + RecalculateRectangles(); + } + + public Camera CreateCamera(Vector3 target) + { + return new Camera(this, target); + } + } +} \ No newline at end of file diff --git a/Wobble/Screens/ScreenManager.cs b/Wobble/Screens/ScreenManager.cs index 942c00f1..da22b423 100644 --- a/Wobble/Screens/ScreenManager.cs +++ b/Wobble/Screens/ScreenManager.cs @@ -1,10 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Xna.Framework; -using Wobble.Graphics.UI.Buttons; namespace Wobble.Screens { @@ -29,7 +24,6 @@ public static class ScreenManager /// public static void ChangeScreen(Screen screen, bool switchImmediately = false) { - ; lock (LockObject) { if (switchImmediately) @@ -40,6 +34,7 @@ public static void ChangeScreen(Screen screen, bool switchImmediately = false) return; } + QueuedScreen?.Destroy(); QueuedScreen = screen; } } @@ -56,9 +51,12 @@ public static void Update(GameTime gameTime) return; // Switch to queued screen after last update. - CurrentScreen?.Destroy(); - CurrentScreen = QueuedScreen; - QueuedScreen = null; + lock (LockObject) + { + CurrentScreen?.Destroy(); + CurrentScreen = QueuedScreen; + QueuedScreen = null; + } } /// @@ -67,4 +65,4 @@ public static void Update(GameTime gameTime) /// public static void Draw(GameTime gameTime) => CurrentScreen?.Draw(gameTime); } -} +} \ No newline at end of file diff --git a/Wobble/Window/WindowManager.cs b/Wobble/Window/WindowManager.cs index 6e0c8a1b..438a6e83 100644 --- a/Wobble/Window/WindowManager.cs +++ b/Wobble/Window/WindowManager.cs @@ -99,8 +99,6 @@ public static void Update() // Create the matrix at which we'll be drawing sprites. ScalingFactor = new Vector3(ScreenScale.X, ScreenScale.Y, 1); Scale = Matrix.CreateScale(ScalingFactor); - - gdm.ApplyChanges(); } /// @@ -115,6 +113,7 @@ public static void Update() public static void ChangeVirtualScreenSize(Vector2 newScreenSize) { VirtualScreen = newScreenSize; + Update(); VirtualScreenSizeChanged?.Invoke(typeof(WindowManager), new WindowVirtualScreenSizeChangedEventArgs(VirtualScreen)); } @@ -152,6 +151,7 @@ public static void ChangeScreenResolution(Point resolution) gdm.PreferredBackBufferWidth = resolution.X; gdm.PreferredBackBufferHeight = resolution.Y; gdm.ApplyChanges(); + Update(); // Raise an event to let everyone know that the window has changed. ResolutionChanged?.Invoke(typeof(WindowManager), new WindowResolutionChangedEventArgs(resolution, oldResolution)); @@ -169,8 +169,7 @@ public static void ChangeScreenResolution(Point resolution) /// private static void UpdateBackBufferSize() { - GameBase.Game.Graphics.PreferredBackBufferWidth = GameBase.Game.Window.ClientBounds.Width; - GameBase.Game.Graphics.PreferredBackBufferHeight = GameBase.Game.Window.ClientBounds.Height; + ChangeScreenResolution(GameBase.Game.Window.ClientBounds.Size); } } } \ No newline at end of file diff --git a/Wobble/Wobble.csproj b/Wobble/Wobble.csproj index dab4934b..841df462 100644 --- a/Wobble/Wobble.csproj +++ b/Wobble/Wobble.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net8.0 true 7.1 @@ -8,20 +8,20 @@ false + - - - + + + + - - @@ -41,4 +41,9 @@ Always + + + ..\MonoGame.Framework.dll + + diff --git a/Wobble/WobbleGame.cs b/Wobble/WobbleGame.cs index f5dd2e30..14fb7155 100644 --- a/Wobble/WobbleGame.cs +++ b/Wobble/WobbleGame.cs @@ -21,6 +21,7 @@ using Wobble.Platform.Linux; using Wobble.Screens; using Wobble.Window; +using NativeLibrary = Wobble.Platform.Linux.NativeLibrary; namespace Wobble { @@ -90,7 +91,7 @@ public abstract class WobbleGame : Game /// /// - public List ScheduledRenderTargetDraws { get; } = new List(); + public List> ScheduledRenderTargetDraws { get; } = new List>(); /// /// The sprite used for clearing the alpha channel. Its alpha must be 1 (fully opaque) and its color does not matter. @@ -265,12 +266,17 @@ protected override void Draw(GameTime gameTime) if (!IsReadyToUpdate) return; + var hasScheduledRenderTargetDraws = ScheduledRenderTargetDraws.Count > 0; + for (var i = ScheduledRenderTargetDraws.Count - 1; i >= 0; i--) { - ScheduledRenderTargetDraws[i]?.Invoke(); + ScheduledRenderTargetDraws[i]?.Invoke(gameTime); ScheduledRenderTargetDraws.Remove(ScheduledRenderTargetDraws[i]); } + if (hasScheduledRenderTargetDraws) + GraphicsDevice.SetRenderTarget(null); + base.Draw(gameTime); // Draw the current game screen. diff --git a/Wobble/libSDL2-2.0.0.dylib b/Wobble/libSDL2-2.0.0.dylib index 30e9dd6a..c5536c26 100644 Binary files a/Wobble/libSDL2-2.0.0.dylib and b/Wobble/libSDL2-2.0.0.dylib differ diff --git a/Wobble/libopenal.1.dylib b/Wobble/libopenal.1.dylib index df9570d5..883af954 100644 Binary files a/Wobble/libopenal.1.dylib and b/Wobble/libopenal.1.dylib differ diff --git a/Wobble/x64/SDL2.dll b/Wobble/x64/SDL2.dll index 0edb386e..05b0de1e 100644 Binary files a/Wobble/x64/SDL2.dll and b/Wobble/x64/SDL2.dll differ diff --git a/Wobble/x64/libSDL2-2.0.so.0 b/Wobble/x64/libSDL2-2.0.so.0 index a84e44c7..a9c6806d 100644 Binary files a/Wobble/x64/libSDL2-2.0.so.0 and b/Wobble/x64/libSDL2-2.0.so.0 differ diff --git a/Wobble/x64/soft_oal.dll b/Wobble/x64/soft_oal.dll index e24538a5..02027ee4 100644 Binary files a/Wobble/x64/soft_oal.dll and b/Wobble/x64/soft_oal.dll differ