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