Browse Source

Migrating distortion sample from:
https://github.com/simondarksidej/XNAGameStudio/wiki/Distortion

Simon (Darkside) Jackson 8 months ago
parent
commit
bf11fcc1c1
80 changed files with 4206 additions and 0 deletions
  1. 36 0
      MonoGameDistortionSample/.config/dotnet-tools.json
  2. 104 0
      MonoGameDistortionSample/Dependencies/DistortionPipeline/DisplacementMapProcessor.cs
  3. 65 0
      MonoGameDistortionSample/Dependencies/DistortionPipeline/DistorterMaterialProcessor.cs
  4. 34 0
      MonoGameDistortionSample/Dependencies/DistortionPipeline/DistorterModelProcessor.cs
  5. 13 0
      MonoGameDistortionSample/Dependencies/DistortionPipeline/DistortionPipeline.csproj
  6. 141 0
      MonoGameDistortionSample/Dependencies/DistortionPipeline/NormalMapProcessor.cs
  7. 89 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Cube.x
  8. 509 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Cylinder.x
  9. 108 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Distort.fx
  10. 189 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Distorters.fx
  11. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Dude.fbx
  12. 86 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Fonts/Hud.spritefont
  13. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Fonts/Roboto-Bold.ttf
  14. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Icon.bmp
  15. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Icon.ico
  16. 123 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/MonoGameDistortionSample.mgcb
  17. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/PrivacyGlass.png
  18. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Sunset.jpg
  19. 98 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Window.x
  20. 33 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/android-icons-generator.sh
  21. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/icon-1024.png
  22. 174 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/ios-icons-generator.sh
  23. 120 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/mac-icons-generator.sh
  24. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/splash.png
  25. 43 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Distorter.cs
  26. BIN
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Distortion.png
  27. 293 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/DistortionComponent.cs
  28. 88 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/LocalizationManager.cs
  29. 501 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.Designer.cs
  30. 126 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.es-ES.resx
  31. 126 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.fr-FR.resx
  32. 126 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.resx
  33. 11 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/MonoGameDistortionSample.Core.csproj
  34. 241 0
      MonoGameDistortionSample/MonoGameDistortionSample.Core/MonoGameDistortionSampleGame.cs
  35. 126 0
      MonoGameDistortionSample/MonoGameDistortionSample.sln
  36. 14 0
      MonoGameDistortionSample/Platforms/Android/.vscode/launch.json
  37. 15 0
      MonoGameDistortionSample/Platforms/Android/AndroidManifest.xml
  38. 52 0
      MonoGameDistortionSample/Platforms/Android/MainActivity.cs
  39. 27 0
      MonoGameDistortionSample/Platforms/Android/MonoGameDistortionSample.Android.csproj
  40. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-hdpi/icon.png
  41. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-hdpi/splash.png
  42. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-mdpi/icon.png
  43. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-mdpi/splash.png
  44. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-xhdpi/icon.png
  45. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-xhdpi/splash.png
  46. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxhdpi/icon.png
  47. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxhdpi/splash.png
  48. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxxhdpi/icon.png
  49. BIN
      MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxxhdpi/splash.png
  50. 4 0
      MonoGameDistortionSample/Platforms/Android/Resources/values/ic_launcher_background.xml
  51. 4 0
      MonoGameDistortionSample/Platforms/Android/Resources/values/strings.xml
  52. 17 0
      MonoGameDistortionSample/Platforms/Android/Resources/values/styles.xml
  53. 14 0
      MonoGameDistortionSample/Platforms/DesktopGL/.vscode/launch.json
  54. 27 0
      MonoGameDistortionSample/Platforms/DesktopGL/MonoGameDistortionSample.DesktopGL.csproj
  55. 15 0
      MonoGameDistortionSample/Platforms/DesktopGL/Program.cs
  56. 43 0
      MonoGameDistortionSample/Platforms/DesktopGL/app.manifest
  57. 14 0
      MonoGameDistortionSample/Platforms/WindowsDX/.vscode/launch.json
  58. 31 0
      MonoGameDistortionSample/Platforms/WindowsDX/MonoGameDistortionSample.WindowsDX.csproj
  59. 21 0
      MonoGameDistortionSample/Platforms/WindowsDX/Program.cs
  60. 42 0
      MonoGameDistortionSample/Platforms/WindowsDX/app.manifest
  61. 14 0
      MonoGameDistortionSample/Platforms/iOS/.vscode/launch.json
  62. 116 0
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/Contents.json
  63. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_1024x1024.png
  64. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_120x120.png
  65. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_152x152.png
  66. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_167x167.png
  67. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_180x180.png
  68. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_20x20.png
  69. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_29x29.png
  70. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_40x40.png
  71. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_58x58.png
  72. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_60x60.png
  73. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_76x76.png
  74. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_80x80.png
  75. BIN
      MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_87x87.png
  76. 6 0
      MonoGameDistortionSample/Platforms/iOS/Entitlements.plist
  77. 32 0
      MonoGameDistortionSample/Platforms/iOS/Info.plist
  78. 27 0
      MonoGameDistortionSample/Platforms/iOS/LaunchScreen.storyboard
  79. 25 0
      MonoGameDistortionSample/Platforms/iOS/MonoGameDistortionSample.iOS.csproj
  80. 43 0
      MonoGameDistortionSample/Platforms/iOS/Program.cs

+ 36 - 0
MonoGameDistortionSample/.config/dotnet-tools.json

@@ -0,0 +1,36 @@
+{
+  "version": 1,
+  "isRoot": true,
+  "tools": {
+    "dotnet-mgcb": {
+      "version": "3.8.4",
+      "commands": [
+        "mgcb"
+      ]
+    },
+    "dotnet-mgcb-editor": {
+      "version": "3.8.4",
+      "commands": [
+        "mgcb-editor"
+      ]
+    },
+    "dotnet-mgcb-editor-linux": {
+      "version": "3.8.4",
+      "commands": [
+        "mgcb-editor-linux"
+      ]
+    },
+    "dotnet-mgcb-editor-windows": {
+      "version": "3.8.4",
+      "commands": [
+        "mgcb-editor-windows"
+      ]
+    },
+    "dotnet-mgcb-editor-mac": {
+      "version": "3.8.4",
+      "commands": [
+        "mgcb-editor-mac"
+      ]
+    }
+  }
+}

+ 104 - 0
MonoGameDistortionSample/Dependencies/DistortionPipeline/DisplacementMapProcessor.cs

@@ -0,0 +1,104 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// DisplacementMapProcessor.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content.Pipeline;
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+using Microsoft.Xna.Framework.Graphics.PackedVector;
+using System.ComponentModel;
+#endregion
+
+namespace DistortionPipeline
+{
+    /// <summary>
+    /// Converts height maps into 2D displacement maps for use as a texture on 
+    /// distorters. The height map is first converted into a normal map before becoming
+    /// a displacement map. See the NormapMapProcessor class for more details.
+    /// </summary>
+    [ContentProcessor]
+    public class DisplacementMapProcessor :
+        ContentProcessor<TextureContent, TextureContent>
+    {
+        #region Processor Parameters
+
+
+        /// <summary>
+        /// Amount of distortion in the map.
+        /// </summary>
+        private float distortionScale = 0.5f;
+
+        [DisplayName("Distortion Scale")]
+        [DefaultValue(0.5f)]
+        [Description("Amount of distortion.")]
+        public float DistortionScale
+        {
+            get { return distortionScale; }
+            set { distortionScale = value; }
+        }
+
+
+        #endregion
+
+
+
+        /// <summary>
+        /// Converts a greyscale height bitmap into a distplacement map.
+        /// </summary>        
+        public override TextureContent Process(TextureContent input,
+                                               ContentProcessorContext context)
+        {
+            // this processor builds on the output of the NormalMapProcessor, so 
+            // we start by chaining to that
+            input = context.Convert<TextureContent, TextureContent>(input,
+                "NormalMapProcessor");
+
+            // Perform the conversion to a displacement map
+            PixelBitmapContent<NormalizedByte4> bitmap =
+                (PixelBitmapContent<NormalizedByte4>)input.Faces[0][0];
+            ConvertNormalsToDisplacement(bitmap, DistortionScale);
+
+            // Convert to NormalizedByte2 because only the X and Y channels are needed
+            input.ConvertBitmapType(typeof(PixelBitmapContent<NormalizedByte2>));
+
+            return input;
+        }
+
+        /// <summary>
+        /// Flattens vectors from a normal map into a 2D displacement map
+        /// and stores them in the RG portion of the bitmap.
+        /// </summary>
+        public static void ConvertNormalsToDisplacement(
+            PixelBitmapContent<NormalizedByte4> bitmap, float distortionScale)
+        {
+            for (int y = 0; y < bitmap.Height; y++)
+            {
+                for (int x = 0; x < bitmap.Width; x++)
+                {
+                    // Get the normal from the normal map
+                    Vector4 normal4 = bitmap.GetPixel(x, y).ToVector4();
+                    Vector3 normal3 = new Vector3(normal4.X, normal4.Y, normal4.Z);
+
+                    // Determine the magnitude of the distortion at this pixel
+                    float amount = Vector3.Dot(normal3, Vector3.Backward) *
+                        distortionScale;
+
+                    // Create a displacement vector of that magnitude in the direction
+                    // of the normal projected onto the plane of the texture.
+                    Vector2 normal2 = new Vector2(normal3.X, normal3.Y);
+                    Vector2 displacement = normal2 * amount + new Vector2(.5f, .5f);
+
+                    // Store the result
+                    bitmap.SetPixel(x, y,
+                        new NormalizedByte4(displacement.X, displacement.Y, 0, 0));
+                }
+            }
+        }
+    }
+}

+ 65 - 0
MonoGameDistortionSample/Dependencies/DistortionPipeline/DistorterMaterialProcessor.cs

@@ -0,0 +1,65 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// DistorterMaterialProcessor.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Content.Pipeline;
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+using Microsoft.Xna.Framework.Content.Pipeline.Processors;
+#endregion
+
+namespace DistortionPipeline
+{
+    /// <summary>
+    /// Applies the Distorters effect and builds a distortion map if one is specified.
+    /// Note that the distortion map is set as a parameter in the model file.
+    /// </summary>
+    [ContentProcessor]
+    class DistorterMaterialProcessor : MaterialProcessor
+    {
+        /// <summary>
+        /// Name of the effect parameter set for the displacement height map texture
+        /// </summary>
+        public const string DisplacementMapKey = "DisplacementMap";
+
+        /// <summary>
+        /// 
+        /// </summary>        
+        public override MaterialContent Process(MaterialContent input,
+            ContentProcessorContext context)
+        {
+            // Reference the Distorters effect file
+            EffectMaterialContent effect = new EffectMaterialContent();
+            effect.Effect =
+                new ExternalReference<EffectContent>("Distorters.fx");
+
+            // If the model specifies a displacement map, carry it over
+            ExternalReference<TextureContent> displacementMap;
+            if (input.Textures.TryGetValue(DisplacementMapKey, out displacementMap))
+            {
+                effect.Textures.Add(DisplacementMapKey, displacementMap);
+            }
+
+            // Continue processing the distorter effect with the default effect behavior
+            return base.Process(effect, context);
+        }
+
+        /// <summary>
+        /// Builds Distorter textures with the DisplacementMapProcessor
+        /// </summary>        
+        protected override ExternalReference<TextureContent> BuildTexture(
+            string textureName, ExternalReference<TextureContent> texture,
+            ContentProcessorContext context)
+        {
+            return context.BuildAsset<TextureContent,
+                TextureContent>(texture, "DisplacementMapProcessor");
+        }
+    }
+}

+ 34 - 0
MonoGameDistortionSample/Dependencies/DistortionPipeline/DistorterModelProcessor.cs

@@ -0,0 +1,34 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// DistorterModelProcessor.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using Microsoft.Xna.Framework.Content.Pipeline;
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+using Microsoft.Xna.Framework.Content.Pipeline.Processors;
+#endregion
+
+namespace DistortionPipeline
+{
+    /// <summary>
+    /// Processor for models which will be used as distorters. This will invoke
+    /// the DistorterMaterialProcessor to apply the Distorter.fx effect.
+    /// </summary>
+    [ContentProcessor]
+    class DistorterModelProcessor : ModelProcessor
+    {
+        protected override MaterialContent ConvertMaterial(MaterialContent material,
+            ContentProcessorContext context)
+        {
+            return context.Convert<MaterialContent, MaterialContent>(material,
+                                    "DistorterMaterialProcessor");
+        }
+    }
+
+}

+ 13 - 0
MonoGameDistortionSample/Dependencies/DistortionPipeline/DistortionPipeline.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.Content.Pipeline" Version="3.8.*">
+      <PrivateAssets>All</PrivateAssets>
+    </PackageReference>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*">
+      <PrivateAssets>All</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+</Project>

+ 141 - 0
MonoGameDistortionSample/Dependencies/DistortionPipeline/NormalMapProcessor.cs

@@ -0,0 +1,141 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// NormalMapProcessor.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content.Pipeline;
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+using Microsoft.Xna.Framework.Graphics.PackedVector;
+#endregion
+
+namespace DistortionPipeline
+{
+    /// <summary>
+    /// Custom content processor converts greyscale displacement bitmaps into
+    /// a normal-map format. In the source image, use black for pixels that are
+    /// further away and white for ones that are bumped upward. The output
+    /// texture contains three-component normal vectors. This processor works
+    /// best if the source bitmap is slightly blurred.
+    /// </summary>
+    /// <remarks>
+    /// This class was originally provided in the Sprite Effects sample.
+    /// </remarks>
+    [ContentProcessor]
+    public class NormalMapProcessor : ContentProcessor<TextureContent, TextureContent>
+    {
+        // Controls how extreme the output normalmap should be.
+        const float bumpSize = 4f;
+
+        /// <summary>
+        /// Converts a greyscale displacement bitmap into normalmap format.
+        /// </summary>
+        public override TextureContent Process(TextureContent input,
+                                               ContentProcessorContext context)
+        {
+            // Convert the input bitmap to Vector4 format, for ease of processing.
+            input.ConvertBitmapType(typeof(PixelBitmapContent<Vector4>));
+
+            PixelBitmapContent<Vector4> bitmap;
+            bitmap = (PixelBitmapContent<Vector4>)input.Faces[0][0];
+
+            // Calculate normalmap vectors.
+            ConvertGreyToAlpha(bitmap);
+            ConvertAlphaToNormals(bitmap);
+
+            // Convert the result into NormalizedByte4 format.
+            input.ConvertBitmapType(typeof(PixelBitmapContent<NormalizedByte4>));
+
+            return input;
+        }
+
+
+        /// <summary>
+        /// Copies greyscale color information into the alpha channel.
+        /// </summary>
+        static void ConvertGreyToAlpha(PixelBitmapContent<Vector4> bitmap)
+        {
+            for (int y = 0; y < bitmap.Height; y++)
+            {
+                for (int x = 0; x < bitmap.Width; x++)
+                {
+                    Vector4 value = bitmap.GetPixel(x, y);
+
+                    // Copy a greyscale version of the RGB data into the alpha channel.
+                    float greyscale = (value.X + value.Y + value.Z) / 3;
+
+                    value.W = greyscale;
+
+                    bitmap.SetPixel(x, y, value);
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Using height data stored in the alpha channel, computes normalmap
+        /// vectors and stores them in the RGB portion of the bitmap.
+        /// </summary>
+        static void ConvertAlphaToNormals(PixelBitmapContent<Vector4> bitmap)
+        {
+            for (int y = 0; y < bitmap.Height; y++)
+            {
+                for (int x = 0; x < bitmap.Width; x++)
+                {
+                    // Look up the heights to either side of this pixel.
+                    float left = GetHeight(bitmap, x - 1, y);
+                    float right = GetHeight(bitmap, x + 1, y);
+
+                    float top = GetHeight(bitmap, x, y - 1);
+                    float bottom = GetHeight(bitmap, x, y + 1);
+
+                    // Compute gradient vectors, then cross them to get the normal.
+                    Vector3 dx = new Vector3(1, 0, (right - left) * bumpSize);
+                    Vector3 dy = new Vector3(0, 1, (bottom - top) * bumpSize);
+
+                    Vector3 normal = Vector3.Cross(dx, dy);
+
+                    normal.Normalize();
+
+                    // Store the result.
+                    float alpha = GetHeight(bitmap, x, y);
+
+                    bitmap.SetPixel(x, y, new Vector4(normal, alpha));
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Helper for looking up height values from the bitmap alpha channel,
+        /// clamping if the specified position is off the edge of the bitmap.
+        /// </summary>
+        static float GetHeight(PixelBitmapContent<Vector4> bitmap, int x, int y)
+        {
+            if (x < 0)
+            {
+                x = 0;
+            }
+            else if (x >= bitmap.Width)
+            {
+                x = bitmap.Width - 1;
+            }
+
+            if (y < 0)
+            {
+                y = 0;
+            }
+            else if (y >= bitmap.Height)
+            {
+                y = bitmap.Height - 1;
+            }
+
+            return bitmap.GetPixel(x, y).W;
+        }
+    }
+}

+ 89 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Cube.x

@@ -0,0 +1,89 @@
+xof 0303txt 0032
+template FVFData {
+ <b6e70a0e-8ef9-4e83-94ad-ecc8b0c04897>
+ DWORD dwFVF;
+ DWORD nDWords;
+ array DWORD data[nDWords];
+}
+
+template EffectInstance {
+ <e331f7e4-0559-4cc2-8e99-1cec1657928f>
+ STRING EffectFilename;
+ [...]
+}
+
+template EffectParamFloats {
+ <3014b9a0-62f5-478c-9b86-e4ac9f4e418b>
+ STRING ParamName;
+ DWORD nFloats;
+ array FLOAT Floats[nFloats];
+}
+
+template EffectParamString {
+ <1dbc4c88-94c1-46ee-9076-2c28818c9481>
+ STRING ParamName;
+ STRING Value;
+}
+
+template EffectParamDWord {
+ <e13963bc-ae51-4c5d-b00f-cfa3a9d97ce5>
+ STRING ParamName;
+ DWORD Value;
+}
+
+
+Frame Box01 {
+ 
+
+ FrameTransformMatrix {
+  1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000;;
+ }
+
+ Mesh  {
+  8;
+  -0.500000;-0.500000;-0.500000;,
+  0.500000;-0.500000;-0.500000;,
+  -0.500000;0.500000;-0.500000;,
+  0.500000;0.500000;-0.500000;,
+  -0.500000;-0.500000;0.500000;,
+  0.500000;-0.500000;0.500000;,
+  -0.500000;0.500000;0.5000;,
+  0.500000;0.500000;0.500000;;
+  12;
+  3;0,2,3;,
+  3;3,1,0;,
+  3;4,5,7;,
+  3;7,6,4;,
+  3;0,1,5;,
+  3;5,4,0;,
+  3;1,3,7;,
+  3;7,5,1;,
+  3;3,2,6;,
+  3;6,7,3;,
+  3;2,0,4;,
+  3;4,6,2;;
+
+  MeshNormals  {
+   6;
+   0.000000;0.000000;-1.000000;,
+   0.000000;0.000000;1.000000;,
+   0.000000;-1.000000;0.000000;,
+   1.000000;0.000000;0.000000;,
+   0.000000;1.000000;0.000000;,
+   -1.000000;0.000000;0.000000;;
+   12;
+   3;0,0,0;,
+   3;0,0,0;,
+   3;1,1,1;,
+   3;1,1,1;,
+   3;2,2,2;,
+   3;2,2,2;,
+   3;3,3,3;,
+   3;3,3,3;,
+   3;4,4,4;,
+   3;4,4,4;,
+   3;5,5,5;,
+   3;5,5,5;;
+  }
+ }
+}

+ 509 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Cylinder.x

@@ -0,0 +1,509 @@
+xof 0303txt 0064
+		Material default {
+			1.00000; 1.00000; 1.00000; 1.00000;;
+		1.00000;
+			1.00000; 1.00000; 1.00000;;
+			0.00000e+0; 0.00000e+0; 0.00000e+0;;
+		}
+Frame cylinder1 {
+	Mesh {
+		192;
+		1.00000, 1.00000, 0.00000e+0;,
+		0.980785, 1.00000, 0.195090;,
+		0.980785, -1.00000, 0.195090;,
+		1.00000, -1.00000, 0.00000e+0;,
+		1.00000, 1.00000, 0.00000e+0;,
+		0.980785, 1.00000, -0.195090;,
+		0.923880, 1.00000, -0.382683;,
+		0.831470, 1.00000, -0.555570;,
+		0.707107, 1.00000, -0.707107;,
+		0.555570, 1.00000, -0.831470;,
+		0.382683, 1.00000, -0.923880;,
+		0.195090, 1.00000, -0.980785;,
+		-1.83691e-16, 1.00000, -1.00000;,
+		-0.195090, 1.00000, -0.980785;,
+		-0.382683, 1.00000, -0.923880;,
+		-0.555570, 1.00000, -0.831470;,
+		-0.707107, 1.00000, -0.707107;,
+		-0.831470, 1.00000, -0.555570;,
+		-0.923880, 1.00000, -0.382683;,
+		-0.980785, 1.00000, -0.195090;,
+		-1.00000, 1.00000, 1.22461e-16;,
+		-0.980785, 1.00000, 0.195090;,
+		-0.923880, 1.00000, 0.382683;,
+		-0.831470, 1.00000, 0.555570;,
+		-0.707107, 1.00000, 0.707107;,
+		-0.555570, 1.00000, 0.831470;,
+		-0.382683, 1.00000, 0.923880;,
+		-0.195090, 1.00000, 0.980785;,
+		6.12303e-17, 1.00000, 1.00000;,
+		0.195090, 1.00000, 0.980785;,
+		0.382683, 1.00000, 0.923880;,
+		0.555570, 1.00000, 0.831470;,
+		0.707107, 1.00000, 0.707107;,
+		0.831470, 1.00000, 0.555570;,
+		0.923880, 1.00000, 0.382683;,
+		0.980785, 1.00000, 0.195090;,
+		1.00000, 1.00000, 0.00000e+0;,
+		1.00000, -1.00000, 0.00000e+0;,
+		0.980785, -1.00000, -0.195090;,
+		0.980785, 1.00000, -0.195090;,
+		0.980785, 1.00000, 0.195090;,
+		0.923880, 1.00000, 0.382683;,
+		0.923880, -1.00000, 0.382683;,
+		0.980785, -1.00000, 0.195090;,
+		0.923880, 1.00000, 0.382683;,
+		0.831470, 1.00000, 0.555570;,
+		0.831470, -1.00000, 0.555570;,
+		0.923880, -1.00000, 0.382683;,
+		0.831470, 1.00000, 0.555570;,
+		0.707107, 1.00000, 0.707107;,
+		0.707107, -1.00000, 0.707107;,
+		0.831470, -1.00000, 0.555570;,
+		0.707107, 1.00000, 0.707107;,
+		0.555570, 1.00000, 0.831470;,
+		0.555570, -1.00000, 0.831470;,
+		0.707107, -1.00000, 0.707107;,
+		0.555570, 1.00000, 0.831470;,
+		0.382683, 1.00000, 0.923880;,
+		0.382683, -1.00000, 0.923880;,
+		0.555570, -1.00000, 0.831470;,
+		0.382683, 1.00000, 0.923880;,
+		0.195090, 1.00000, 0.980785;,
+		0.195090, -1.00000, 0.980785;,
+		0.382683, -1.00000, 0.923880;,
+		0.195090, 1.00000, 0.980785;,
+		6.12303e-17, 1.00000, 1.00000;,
+		6.12303e-17, -1.00000, 1.00000;,
+		0.195090, -1.00000, 0.980785;,
+		6.12303e-17, 1.00000, 1.00000;,
+		-0.195090, 1.00000, 0.980785;,
+		-0.195090, -1.00000, 0.980785;,
+		6.12303e-17, -1.00000, 1.00000;,
+		-0.195090, 1.00000, 0.980785;,
+		-0.382683, 1.00000, 0.923880;,
+		-0.382683, -1.00000, 0.923880;,
+		-0.195090, -1.00000, 0.980785;,
+		-0.382683, 1.00000, 0.923880;,
+		-0.555570, 1.00000, 0.831470;,
+		-0.555570, -1.00000, 0.831470;,
+		-0.382683, -1.00000, 0.923880;,
+		-0.555570, 1.00000, 0.831470;,
+		-0.707107, 1.00000, 0.707107;,
+		-0.707107, -1.00000, 0.707107;,
+		-0.555570, -1.00000, 0.831470;,
+		-0.707107, 1.00000, 0.707107;,
+		-0.831470, 1.00000, 0.555570;,
+		-0.831470, -1.00000, 0.555570;,
+		-0.707107, -1.00000, 0.707107;,
+		-0.831470, 1.00000, 0.555570;,
+		-0.923880, 1.00000, 0.382683;,
+		-0.923880, -1.00000, 0.382683;,
+		-0.831470, -1.00000, 0.555570;,
+		-0.923880, 1.00000, 0.382683;,
+		-0.980785, 1.00000, 0.195090;,
+		-0.980785, -1.00000, 0.195090;,
+		-0.923880, -1.00000, 0.382683;,
+		-0.980785, 1.00000, 0.195090;,
+		-1.00000, 1.00000, 1.22461e-16;,
+		-1.00000, -1.00000, 1.22461e-16;,
+		-0.980785, -1.00000, 0.195090;,
+		-1.00000, 1.00000, 1.22461e-16;,
+		-0.980785, 1.00000, -0.195090;,
+		-0.980785, -1.00000, -0.195090;,
+		-1.00000, -1.00000, 1.22461e-16;,
+		-0.980785, 1.00000, -0.195090;,
+		-0.923880, 1.00000, -0.382683;,
+		-0.923880, -1.00000, -0.382683;,
+		-0.980785, -1.00000, -0.195090;,
+		-0.923880, 1.00000, -0.382683;,
+		-0.831470, 1.00000, -0.555570;,
+		-0.831470, -1.00000, -0.555570;,
+		-0.923880, -1.00000, -0.382683;,
+		-0.831470, 1.00000, -0.555570;,
+		-0.707107, 1.00000, -0.707107;,
+		-0.707107, -1.00000, -0.707107;,
+		-0.831470, -1.00000, -0.555570;,
+		-0.707107, 1.00000, -0.707107;,
+		-0.555570, 1.00000, -0.831470;,
+		-0.555570, -1.00000, -0.831470;,
+		-0.707107, -1.00000, -0.707107;,
+		-0.555570, 1.00000, -0.831470;,
+		-0.382683, 1.00000, -0.923880;,
+		-0.382683, -1.00000, -0.923880;,
+		-0.555570, -1.00000, -0.831470;,
+		-0.382683, 1.00000, -0.923880;,
+		-0.195090, 1.00000, -0.980785;,
+		-0.195090, -1.00000, -0.980785;,
+		-0.382683, -1.00000, -0.923880;,
+		-0.195090, 1.00000, -0.980785;,
+		-1.83691e-16, 1.00000, -1.00000;,
+		-1.83691e-16, -1.00000, -1.00000;,
+		-0.195090, -1.00000, -0.980785;,
+		-1.83691e-16, 1.00000, -1.00000;,
+		0.195090, 1.00000, -0.980785;,
+		0.195090, -1.00000, -0.980785;,
+		-1.83691e-16, -1.00000, -1.00000;,
+		0.195090, 1.00000, -0.980785;,
+		0.382683, 1.00000, -0.923880;,
+		0.382683, -1.00000, -0.923880;,
+		0.195090, -1.00000, -0.980785;,
+		0.382683, 1.00000, -0.923880;,
+		0.555570, 1.00000, -0.831470;,
+		0.555570, -1.00000, -0.831470;,
+		0.382683, -1.00000, -0.923880;,
+		0.555570, 1.00000, -0.831470;,
+		0.707107, 1.00000, -0.707107;,
+		0.707107, -1.00000, -0.707107;,
+		0.555570, -1.00000, -0.831470;,
+		0.707107, 1.00000, -0.707107;,
+		0.831470, 1.00000, -0.555570;,
+		0.831470, -1.00000, -0.555570;,
+		0.707107, -1.00000, -0.707107;,
+		0.831470, 1.00000, -0.555570;,
+		0.923880, 1.00000, -0.382683;,
+		0.923880, -1.00000, -0.382683;,
+		0.831470, -1.00000, -0.555570;,
+		0.923880, 1.00000, -0.382683;,
+		0.980785, 1.00000, -0.195090;,
+		0.980785, -1.00000, -0.195090;,
+		0.923880, -1.00000, -0.382683;,
+		1.00000, -1.00000, 0.00000e+0;,
+		0.980785, -1.00000, 0.195090;,
+		0.923880, -1.00000, 0.382683;,
+		0.831470, -1.00000, 0.555570;,
+		0.707107, -1.00000, 0.707107;,
+		0.555570, -1.00000, 0.831470;,
+		0.382683, -1.00000, 0.923880;,
+		0.195090, -1.00000, 0.980785;,
+		6.12303e-17, -1.00000, 1.00000;,
+		-0.195090, -1.00000, 0.980785;,
+		-0.382683, -1.00000, 0.923880;,
+		-0.555570, -1.00000, 0.831470;,
+		-0.707107, -1.00000, 0.707107;,
+		-0.831470, -1.00000, 0.555570;,
+		-0.923880, -1.00000, 0.382683;,
+		-0.980785, -1.00000, 0.195090;,
+		-1.00000, -1.00000, 1.22461e-16;,
+		-0.980785, -1.00000, -0.195090;,
+		-0.923880, -1.00000, -0.382683;,
+		-0.831470, -1.00000, -0.555570;,
+		-0.707107, -1.00000, -0.707107;,
+		-0.555570, -1.00000, -0.831470;,
+		-0.382683, -1.00000, -0.923880;,
+		-0.195090, -1.00000, -0.980785;,
+		-1.83691e-16, -1.00000, -1.00000;,
+		0.195090, -1.00000, -0.980785;,
+		0.382683, -1.00000, -0.923880;,
+		0.555570, -1.00000, -0.831470;,
+		0.707107, -1.00000, -0.707107;,
+		0.831470, -1.00000, -0.555570;,
+		0.923880, -1.00000, -0.382683;,
+		0.980785, -1.00000, -0.195090;;
+
+		34;
+		4;0, 1, 2, 3 ;,
+		32;4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 ;,
+		4;36, 37, 38, 39 ;,
+		4;40, 41, 42, 43 ;,
+		4;44, 45, 46, 47 ;,
+		4;48, 49, 50, 51 ;,
+		4;52, 53, 54, 55 ;,
+		4;56, 57, 58, 59 ;,
+		4;60, 61, 62, 63 ;,
+		4;64, 65, 66, 67 ;,
+		4;68, 69, 70, 71 ;,
+		4;72, 73, 74, 75 ;,
+		4;76, 77, 78, 79 ;,
+		4;80, 81, 82, 83 ;,
+		4;84, 85, 86, 87 ;,
+		4;88, 89, 90, 91 ;,
+		4;92, 93, 94, 95 ;,
+		4;96, 97, 98, 99 ;,
+		4;100, 101, 102, 103 ;,
+		4;104, 105, 106, 107 ;,
+		4;108, 109, 110, 111 ;,
+		4;112, 113, 114, 115 ;,
+		4;116, 117, 118, 119 ;,
+		4;120, 121, 122, 123 ;,
+		4;124, 125, 126, 127 ;,
+		4;128, 129, 130, 131 ;,
+		4;132, 133, 134, 135 ;,
+		4;136, 137, 138, 139 ;,
+		4;140, 141, 142, 143 ;,
+		4;144, 145, 146, 147 ;,
+		4;148, 149, 150, 151 ;,
+		4;152, 153, 154, 155 ;,
+		4;156, 157, 158, 159 ;,
+		32;160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 ;;
+
+		MeshNormals {
+			192;		0.893561, 0.448942, -1.68219e-16;,
+		0.876391, 0.448942, 0.174325;,
+		0.876391, -0.448942, 0.174325;,
+		0.893561, -0.448942, -9.96852e-17;,
+		0.893561, 0.448942, -1.68219e-16;,
+		0.876391, 0.448942, -0.174325;,
+		0.825543, 0.448942, -0.341951;,
+		0.742969, 0.448942, -0.496436;,
+		0.631843, 0.448942, -0.631843;,
+		0.496436, 0.448942, -0.742969;,
+		0.341951, 0.448942, -0.825543;,
+		0.174325, 0.448942, -0.876391;,
+		-2.42983e-16, 0.448942, -0.893561;,
+		-0.174325, 0.448942, -0.876391;,
+		-0.341951, 0.448942, -0.825543;,
+		-0.496436, 0.448942, -0.742969;,
+		-0.631843, 0.448942, -0.631843;,
+		-0.742969, 0.448942, -0.496436;,
+		-0.825543, 0.448942, -0.341951;,
+		-0.876391, 0.448942, -0.174325;,
+		-0.893561, 0.448942, -3.73819e-17;,
+		-0.876391, 0.448942, 0.174325;,
+		-0.825543, 0.448942, 0.341951;,
+		-0.742969, 0.448942, 0.496436;,
+		-0.631843, 0.448942, 0.631843;,
+		-0.496436, 0.448942, 0.742969;,
+		-0.341951, 0.448942, 0.825543;,
+		-0.174325, 0.448942, 0.876391;,
+		-6.23032e-18, 0.448942, 0.893561;,
+		0.174325, 0.448942, 0.876391;,
+		0.341951, 0.448942, 0.825543;,
+		0.496436, 0.448942, 0.742969;,
+		0.631843, 0.448942, 0.631843;,
+		0.742969, 0.448942, 0.496436;,
+		0.825543, 0.448942, 0.341951;,
+		0.876391, 0.448942, 0.174325;,
+		0.893561, 0.448942, -1.68219e-16;,
+		0.893561, -0.448942, -9.96852e-17;,
+		0.876391, -0.448942, -0.174325;,
+		0.876391, 0.448942, -0.174325;,
+		0.876391, 0.448942, 0.174325;,
+		0.825543, 0.448942, 0.341951;,
+		0.825543, -0.448942, 0.341951;,
+		0.876391, -0.448942, 0.174325;,
+		0.825543, 0.448942, 0.341951;,
+		0.742969, 0.448942, 0.496436;,
+		0.742969, -0.448942, 0.496436;,
+		0.825543, -0.448942, 0.341951;,
+		0.742969, 0.448942, 0.496436;,
+		0.631843, 0.448942, 0.631843;,
+		0.631843, -0.448942, 0.631843;,
+		0.742969, -0.448942, 0.496436;,
+		0.631843, 0.448942, 0.631843;,
+		0.496436, 0.448942, 0.742969;,
+		0.496436, -0.448942, 0.742969;,
+		0.631843, -0.448942, 0.631843;,
+		0.496436, 0.448942, 0.742969;,
+		0.341951, 0.448942, 0.825543;,
+		0.341951, -0.448942, 0.825543;,
+		0.496436, -0.448942, 0.742969;,
+		0.341951, 0.448942, 0.825543;,
+		0.174325, 0.448942, 0.876391;,
+		0.174325, -0.448942, 0.876391;,
+		0.341951, -0.448942, 0.825543;,
+		0.174325, 0.448942, 0.876391;,
+		-6.23032e-18, 0.448942, 0.893561;,
+		-6.23032e-18, -0.448942, 0.893561;,
+		0.174325, -0.448942, 0.876391;,
+		-6.23032e-18, 0.448942, 0.893561;,
+		-0.174325, 0.448942, 0.876391;,
+		-0.174325, -0.448942, 0.876391;,
+		-6.23032e-18, -0.448942, 0.893561;,
+		-0.174325, 0.448942, 0.876391;,
+		-0.341951, 0.448942, 0.825543;,
+		-0.341951, -0.448942, 0.825543;,
+		-0.174325, -0.448942, 0.876391;,
+		-0.341951, 0.448942, 0.825543;,
+		-0.496436, 0.448942, 0.742969;,
+		-0.496436, -0.448942, 0.742969;,
+		-0.341951, -0.448942, 0.825543;,
+		-0.496436, 0.448942, 0.742969;,
+		-0.631843, 0.448942, 0.631843;,
+		-0.631843, -0.448942, 0.631843;,
+		-0.496436, -0.448942, 0.742969;,
+		-0.631843, 0.448942, 0.631843;,
+		-0.742969, 0.448942, 0.496436;,
+		-0.742969, -0.448942, 0.496436;,
+		-0.631843, -0.448942, 0.631843;,
+		-0.742969, 0.448942, 0.496436;,
+		-0.825543, 0.448942, 0.341951;,
+		-0.825543, -0.448942, 0.341951;,
+		-0.742969, -0.448942, 0.496436;,
+		-0.825543, 0.448942, 0.341951;,
+		-0.876391, 0.448942, 0.174325;,
+		-0.876391, -0.448942, 0.174325;,
+		-0.825543, -0.448942, 0.341951;,
+		-0.876391, 0.448942, 0.174325;,
+		-0.893561, 0.448942, -3.73819e-17;,
+		-0.893561, -0.448942, 3.11516e-17;,
+		-0.876391, -0.448942, 0.174325;,
+		-0.893561, 0.448942, -3.73819e-17;,
+		-0.876391, 0.448942, -0.174325;,
+		-0.876391, -0.448942, -0.174325;,
+		-0.893561, -0.448942, 3.11516e-17;,
+		-0.876391, 0.448942, -0.174325;,
+		-0.825543, 0.448942, -0.341951;,
+		-0.825543, -0.448942, -0.341951;,
+		-0.876391, -0.448942, -0.174325;,
+		-0.825543, 0.448942, -0.341951;,
+		-0.742969, 0.448942, -0.496436;,
+		-0.742969, -0.448942, -0.496436;,
+		-0.825543, -0.448942, -0.341951;,
+		-0.742969, 0.448942, -0.496436;,
+		-0.631843, 0.448942, -0.631843;,
+		-0.631843, -0.448942, -0.631843;,
+		-0.742969, -0.448942, -0.496436;,
+		-0.631843, 0.448942, -0.631843;,
+		-0.496436, 0.448942, -0.742969;,
+		-0.496436, -0.448942, -0.742969;,
+		-0.631843, -0.448942, -0.631843;,
+		-0.496436, 0.448942, -0.742969;,
+		-0.341951, 0.448942, -0.825543;,
+		-0.341951, -0.448942, -0.825543;,
+		-0.496436, -0.448942, -0.742969;,
+		-0.341951, 0.448942, -0.825543;,
+		-0.174325, 0.448942, -0.876391;,
+		-0.174325, -0.448942, -0.876391;,
+		-0.341951, -0.448942, -0.825543;,
+		-0.174325, 0.448942, -0.876391;,
+		-2.42983e-16, 0.448942, -0.893561;,
+		-2.42983e-16, -0.448942, -0.893561;,
+		-0.174325, -0.448942, -0.876391;,
+		-2.42983e-16, 0.448942, -0.893561;,
+		0.174325, 0.448942, -0.876391;,
+		0.174325, -0.448942, -0.876391;,
+		-2.42983e-16, -0.448942, -0.893561;,
+		0.174325, 0.448942, -0.876391;,
+		0.341951, 0.448942, -0.825543;,
+		0.341951, -0.448942, -0.825543;,
+		0.174325, -0.448942, -0.876391;,
+		0.341951, 0.448942, -0.825543;,
+		0.496436, 0.448942, -0.742969;,
+		0.496436, -0.448942, -0.742969;,
+		0.341951, -0.448942, -0.825543;,
+		0.496436, 0.448942, -0.742969;,
+		0.631843, 0.448942, -0.631843;,
+		0.631843, -0.448942, -0.631843;,
+		0.496436, -0.448942, -0.742969;,
+		0.631843, 0.448942, -0.631843;,
+		0.742969, 0.448942, -0.496436;,
+		0.742969, -0.448942, -0.496436;,
+		0.631843, -0.448942, -0.631843;,
+		0.742969, 0.448942, -0.496436;,
+		0.825543, 0.448942, -0.341951;,
+		0.825543, -0.448942, -0.341951;,
+		0.742969, -0.448942, -0.496436;,
+		0.825543, 0.448942, -0.341951;,
+		0.876391, 0.448942, -0.174325;,
+		0.876391, -0.448942, -0.174325;,
+		0.825543, -0.448942, -0.341951;,
+		0.893561, -0.448942, -9.96852e-17;,
+		0.876391, -0.448942, 0.174325;,
+		0.825543, -0.448942, 0.341951;,
+		0.742969, -0.448942, 0.496436;,
+		0.631843, -0.448942, 0.631843;,
+		0.496436, -0.448942, 0.742969;,
+		0.341951, -0.448942, 0.825543;,
+		0.174325, -0.448942, 0.876391;,
+		-6.23032e-18, -0.448942, 0.893561;,
+		-0.174325, -0.448942, 0.876391;,
+		-0.341951, -0.448942, 0.825543;,
+		-0.496436, -0.448942, 0.742969;,
+		-0.631843, -0.448942, 0.631843;,
+		-0.742969, -0.448942, 0.496436;,
+		-0.825543, -0.448942, 0.341951;,
+		-0.876391, -0.448942, 0.174325;,
+		-0.893561, -0.448942, 3.11516e-17;,
+		-0.876391, -0.448942, -0.174325;,
+		-0.825543, -0.448942, -0.341951;,
+		-0.742969, -0.448942, -0.496436;,
+		-0.631843, -0.448942, -0.631843;,
+		-0.496436, -0.448942, -0.742969;,
+		-0.341951, -0.448942, -0.825543;,
+		-0.174325, -0.448942, -0.876391;,
+		-2.42983e-16, -0.448942, -0.893561;,
+		0.174325, -0.448942, -0.876391;,
+		0.341951, -0.448942, -0.825543;,
+		0.496436, -0.448942, -0.742969;,
+		0.631843, -0.448942, -0.631843;,
+		0.742969, -0.448942, -0.496436;,
+		0.825543, -0.448942, -0.341951;,
+		0.876391, -0.448942, -0.174325;;
+		34;
+		4;0, 1, 2, 3 ;,
+		32;4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 ;,
+		4;36, 37, 38, 39 ;,
+		4;40, 41, 42, 43 ;,
+		4;44, 45, 46, 47 ;,
+		4;48, 49, 50, 51 ;,
+		4;52, 53, 54, 55 ;,
+		4;56, 57, 58, 59 ;,
+		4;60, 61, 62, 63 ;,
+		4;64, 65, 66, 67 ;,
+		4;68, 69, 70, 71 ;,
+		4;72, 73, 74, 75 ;,
+		4;76, 77, 78, 79 ;,
+		4;80, 81, 82, 83 ;,
+		4;84, 85, 86, 87 ;,
+		4;88, 89, 90, 91 ;,
+		4;92, 93, 94, 95 ;,
+		4;96, 97, 98, 99 ;,
+		4;100, 101, 102, 103 ;,
+		4;104, 105, 106, 107 ;,
+		4;108, 109, 110, 111 ;,
+		4;112, 113, 114, 115 ;,
+		4;116, 117, 118, 119 ;,
+		4;120, 121, 122, 123 ;,
+		4;124, 125, 126, 127 ;,
+		4;128, 129, 130, 131 ;,
+		4;132, 133, 134, 135 ;,
+		4;136, 137, 138, 139 ;,
+		4;140, 141, 142, 143 ;,
+		4;144, 145, 146, 147 ;,
+		4;148, 149, 150, 151 ;,
+		4;152, 153, 154, 155 ;,
+		4;156, 157, 158, 159 ;,
+		32;160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191 ;;
+		}
+		MeshMaterialList {
+			1;
+			34;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			0;
+			;
+			{default}
+		}
+	}}

+ 108 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Distort.fx

@@ -0,0 +1,108 @@
+//-----------------------------------------------------------------------------
+// Distort.fx
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+#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 Texture : register(s0);
+sampler SceneTexture : register(s0)
+{
+    Texture = <Texture>;
+};
+
+Texture2D Displacement : register(s1);
+sampler DistortionMap : register(s1)
+{
+    Texture = <Displacement>;
+};
+
+#define SAMPLE_COUNT 15
+float2 SampleOffsets[SAMPLE_COUNT];
+float SampleWeights[SAMPLE_COUNT];
+
+// The Distortion map represents zero displacement as 0.5, but in an 8 bit color
+// channel there is no exact value for 0.5. ZeroOffset adjusts for this error.
+const float ZeroOffset = 0.5 / 255.0;
+
+struct VertexShaderOutput
+{
+	float4 Position : SV_POSITION;
+	float4 Color : COLOR0;
+	float2 TextureCoordinates : TEXCOORD0;
+};
+
+float4 Distort_PixelShader(float2 TexCoord : TEXCOORD0, 
+    uniform bool distortionBlur) : COLOR0
+{
+    // Look up the displacement
+    float2 displacement = tex2D(DistortionMap, TexCoord).rg;
+    
+    float4 finalColor = float4(0,0,0,0);
+    // We need to constrain the area potentially subjected to the gaussian blur to the
+    // distorted parts of the scene texture.  Therefore, we can sample for the color
+    // we used to clear the distortion map (black).  We used 0 to avoid any potential
+    // rounding errors.
+    if ((displacement.x == 0) && (displacement.y == 0))
+    {
+        finalColor = tex2D(SceneTexture, TexCoord);
+    }
+    else
+    {
+        // Convert from [0,1] to [-.5, .5) 
+        // .5 is excluded by adjustment for zero
+        displacement -= .5 + ZeroOffset;
+
+        if (distortionBlur)
+        {
+            // Combine a number of weighted displaced-image filter taps
+            for (int i = 0; i < SAMPLE_COUNT; i++)
+            {
+                finalColor += tex2D(SceneTexture, TexCoord.xy + displacement + 
+                    SampleOffsets[i]) * SampleWeights[i];
+            }
+        }
+        else
+        {
+            // Look up the displaced color, without multisampling
+            finalColor = tex2D(SceneTexture, TexCoord.xy + displacement);  
+        }
+    }
+
+    return finalColor;
+}
+
+float4 Distort_PixelShaderBlur(VertexShaderOutput input) : COLOR0
+{
+    return Distort_PixelShader(input.TextureCoordinates, true);
+}
+
+float4 Distort_PixelShaderNoBlur(VertexShaderOutput input) : COLOR0
+{
+    return Distort_PixelShader(input.TextureCoordinates, false);
+}
+
+technique Distort
+{
+    pass
+    {
+        PixelShader = compile PS_SHADERMODEL Distort_PixelShaderNoBlur();
+    }
+}
+
+technique DistortBlur
+{
+    pass
+    {
+        PixelShader = compile PS_SHADERMODEL Distort_PixelShaderBlur();
+    }
+};

+ 189 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Distorters.fx

@@ -0,0 +1,189 @@
+//-----------------------------------------------------------------------------
+// Distorters.fx
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#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
+
+// common parameters
+float4x4 WorldViewProjection;
+float4x4 WorldView;
+float DistortionScale;
+float Time;
+
+
+//-----------------------------------------------------------------------------
+//
+// Displacement Mapping
+//
+//-----------------------------------------------------------------------------
+
+struct PositionColorTextured
+{
+   float4 Position : POSITION;
+   float4 Color : COLOR0;
+   float2 TexCoord : TEXCOORD;
+};
+
+PositionColorTextured TransformAndTexture_VertexShader(PositionColorTextured input)
+{
+    PositionColorTextured output;
+    
+    output.Position = mul(input.Position, WorldViewProjection);
+    output.Color = input.Color;
+    output.TexCoord = input.TexCoord;
+    
+    return output;
+}
+
+texture2D DisplacementMap;
+sampler2D DisplacementMapSampler = sampler_state
+{
+    texture = <DisplacementMap>;
+};
+
+float4 Textured_PixelShader(PositionColorTextured input) : COLOR
+{
+    float4 color = tex2D(DisplacementMapSampler, input.TexCoord);
+    
+    // Ignore the blue channel    
+    return float4(color.rg, 0, color.a);
+}
+
+technique DisplacementMapped
+{
+    pass
+    {
+        VertexShader = compile VS_SHADERMODEL TransformAndTexture_VertexShader();
+        PixelShader = compile PS_SHADERMODEL Textured_PixelShader();
+    }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Heat-Haze Displacement
+//
+//-----------------------------------------------------------------------------
+
+struct PositionPosition
+{
+    float4 Position : POSITION;
+        
+    // the pixel shader does not have direct access to the position of the pixel
+    // being shaded, so we must pass this information through from the vertex shader
+    float4 PositionAsTexCoord : TEXCOORD;
+};
+
+PositionPosition TransformAndCopyPosition_VertexShader(PositionColorTextured input)
+{
+    PositionPosition output;
+    
+    output.Position = mul(input.Position, WorldViewProjection);
+    output.PositionAsTexCoord = output.Position;
+    
+    return output;
+}
+
+float4 HeatHaze_PixelShader(PositionColorTextured input) : COLOR
+{
+    float2 displacement;
+    displacement.x = sin(input.TexCoord.x / 60 + Time * 1.5) * sin(input.TexCoord.x / 10) *
+        cos(input.TexCoord.x / 50);
+    displacement.y = sin(input.TexCoord.y / 50 - Time * 2.75);
+    displacement *= DistortionScale;
+    displacement = (displacement + float2(1, 1)) / 2;
+    
+    return float4(displacement, 0, 1);
+}
+
+technique HeatHaze
+{
+    pass
+    {
+        VertexShader = compile VS_SHADERMODEL TransformAndCopyPosition_VertexShader();
+        PixelShader = compile PS_SHADERMODEL HeatHaze_PixelShader();
+    }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Pull-In Displacement
+//
+//-----------------------------------------------------------------------------
+
+struct PositionNormal
+{
+   float4 Position : POSITION;
+   float3 Normal : NORMAL;
+};
+
+struct PositionDisplacement
+{
+   float4 Position : POSITION;
+   float2 Displacement : TEXCOORD;
+};
+
+PositionDisplacement PullIn_VertexShader(PositionNormal input)
+{
+   PositionDisplacement output;
+
+   output.Position = mul(input.Position, WorldViewProjection);
+   float3 normalWV = mul(input.Normal, WorldView);
+   normalWV.y = -normalWV.y;
+   
+   float amount = dot(normalWV, float3(0,0,1)) * DistortionScale;
+   output.Displacement = float2(.5,.5) + float2(amount * normalWV.xy);
+
+   return output;   
+}
+
+float4 DisplacementPassthrough_PixelShader(PositionColorTextured input) : COLOR
+{  
+   return float4(input.TexCoord, 0, 1);
+}
+
+technique PullIn
+{
+    pass
+    {
+        VertexShader = compile VS_SHADERMODEL PullIn_VertexShader();
+        PixelShader = compile PS_SHADERMODEL DisplacementPassthrough_PixelShader();
+    }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Zero Displacement (provided for reference)
+//
+//-----------------------------------------------------------------------------
+
+
+float4 TransformOnly_VertexShader(PositionColorTextured input) : POSITION
+{
+    return mul(input.Position, WorldViewProjection);
+}
+
+float4 ZeroDisplacement_PixelShader(PositionColorTextured input) : COLOR
+{
+    return float4(.5, .5, 0, 0);
+}
+
+technique ZeroDisplacement
+{
+    pass
+    {
+        VertexShader = compile VS_SHADERMODEL TransformOnly_VertexShader();
+        PixelShader = compile PS_SHADERMODEL ZeroDisplacement_PixelShader();
+    }
+}

BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Dude.fbx


+ 86 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Fonts/Hud.spritefont

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+This file contains an xml description of a font, and will be read by the XNA
+Framework Content Pipeline. Follow the comments to customize the appearance
+of the font in your game, and to change the characters which are available to draw
+with.
+-->
+<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
+  <Asset Type="Graphics:FontDescription">
+
+    <!--
+    Modify this string to change the font that will be imported.
+    -->
+    <FontName>Roboto-Bold</FontName>
+
+    <!--
+    Size is a float value, measured in points. Modify this value to change
+    the size of the font.
+    -->
+    <Size>14</Size>
+
+    <!--
+    Spacing is a float value, measured in pixels. Modify this value to change
+    the amount of spacing in between characters.
+    -->
+    <Spacing>0</Spacing>
+
+    <!--
+    UseKerning controls the layout of the font. If this value is true, kerning information
+    will be used when placing characters.
+    -->
+    <UseKerning>true</UseKerning>
+
+    <!--
+    Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
+    and "Bold, Italic", and are case sensitive.
+    -->
+    <Style>Regular</Style>
+
+    <!--
+    If you uncomment this line, the default character will be substituted if you draw
+    or measure text that contains characters which were not included in the font.
+    -->
+    <!-- <DefaultCharacter>*</DefaultCharacter> -->
+
+    <!--
+    CharacterRegions control what letters are available in the font. Every
+    character from Start to End will be built and made available for drawing. The
+    default range is from 32, (ASCII space), to 254, ('■'), covering the basic Latin
+    character set as well as extended character set.
+    The characters are ordered according to the Unicode standard.
+    See the documentation for more information.
+    -->
+    <CharacterRegions>
+      <!-- Latin Character and Extended Set -->
+      <CharacterRegion>
+        <Start>&#32;</Start>
+        <End>&#254;</End>
+      </CharacterRegion>
+
+      <!-- Hiragana (U+3040 - U+309F) -->
+      <CharacterRegion>
+        <Start>&#12352;</Start>
+        <End>&#12447;</End>
+      </CharacterRegion>
+
+      <!-- Katakana (U+30A0 - U+30FF) -->
+      <CharacterRegion>
+        <Start>&#12448;</Start>
+        <End>&#12543;</End>
+      </CharacterRegion>
+
+      <!-- Common Kanji (CJK Unified Ideographs - typically U+4E00 to U+9FFF) -->
+      <CharacterRegion>
+        <Start>&#19968;</Start>
+        <End>&#40879;</End>
+      </CharacterRegion>
+
+      <!-- Cyrillic Character Set -->
+      <CharacterRegion>
+        <Start>&#1040;</Start>
+        <End>&#1103;</End>
+      </CharacterRegion>
+    </CharacterRegions>
+  </Asset>
+</XnaContent>

BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Fonts/Roboto-Bold.ttf


BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Icon.bmp


BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Icon.ico


+ 123 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/MonoGameDistortionSample.mgcb

@@ -0,0 +1,123 @@
+
+#----------------------------- Global Properties ----------------------------#
+
+/outputDir:bin/$(Platform)
+/intermediateDir:obj/$(Platform)
+/platform:Windows
+/config:
+/profile:Reach
+/compress:False
+
+#-------------------------------- References --------------------------------#
+
+/reference:..\..\Dependencies\DistortionPipeline\bin\Debug\net8.0\DistortionPipeline.dll
+
+#---------------------------------- Content ---------------------------------#
+
+#begin Cube.x
+/importer:XImporter
+/processor:ModelProcessor
+/processorParam:ColorKeyColor=0,0,0,0
+/processorParam:ColorKeyEnabled=True
+/processorParam:DefaultEffect=BasicEffect
+/processorParam:GenerateMipmaps=True
+/processorParam:GenerateTangentFrames=False
+/processorParam:PremultiplyTextureAlpha=True
+/processorParam:PremultiplyVertexColors=True
+/processorParam:ResizeTexturesToPowerOfTwo=False
+/processorParam:RotationX=0
+/processorParam:RotationY=0
+/processorParam:RotationZ=0
+/processorParam:Scale=1
+/processorParam:SwapWindingOrder=False
+/processorParam:TextureFormat=Compressed
+/build:Cube.x
+
+#begin Cylinder.x
+/importer:XImporter
+/processor:DistorterModelProcessor
+/processorParam:ColorKeyColor=0,0,0,0
+/processorParam:ColorKeyEnabled=True
+/processorParam:DefaultEffect=BasicEffect
+/processorParam:GenerateMipmaps=True
+/processorParam:GenerateTangentFrames=False
+/processorParam:PremultiplyTextureAlpha=True
+/processorParam:PremultiplyVertexColors=True
+/processorParam:ResizeTexturesToPowerOfTwo=False
+/processorParam:RotationX=0
+/processorParam:RotationY=0
+/processorParam:RotationZ=0
+/processorParam:Scale=1
+/processorParam:SwapWindingOrder=False
+/processorParam:TextureFormat=Compressed
+/build:Cylinder.x
+
+#begin Distort.fx
+/importer:EffectImporter
+/processor:EffectProcessor
+/processorParam:DebugMode=Auto
+/build:Distort.fx
+
+#begin Distorters.fx
+/importer:EffectImporter
+/processor:EffectProcessor
+/processorParam:DebugMode=Auto
+/build:Distorters.fx
+
+#begin Dude.fbx
+/importer:FbxImporter
+/processor:DistorterModelProcessor
+/processorParam:ColorKeyColor=0,0,0,0
+/processorParam:ColorKeyEnabled=True
+/processorParam:DefaultEffect=BasicEffect
+/processorParam:GenerateMipmaps=True
+/processorParam:GenerateTangentFrames=False
+/processorParam:PremultiplyTextureAlpha=True
+/processorParam:PremultiplyVertexColors=True
+/processorParam:ResizeTexturesToPowerOfTwo=False
+/processorParam:RotationX=0
+/processorParam:RotationY=0
+/processorParam:RotationZ=0
+/processorParam:Scale=1
+/processorParam:SwapWindingOrder=False
+/processorParam:TextureFormat=Compressed
+/build:Dude.fbx
+
+#begin Fonts/Hud.spritefont
+/importer:FontDescriptionImporter
+/processor:FontDescriptionProcessor
+/processorParam:PremultiplyAlpha=True
+/processorParam:TextureFormat=Compressed
+/build:Fonts/Hud.spritefont
+
+#begin Sunset.jpg
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=True
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:Sunset.jpg
+
+#begin Window.x
+/importer:XImporter
+/processor:DistorterModelProcessor
+/processorParam:ColorKeyColor=0,0,0,0
+/processorParam:ColorKeyEnabled=True
+/processorParam:DefaultEffect=BasicEffect
+/processorParam:GenerateMipmaps=True
+/processorParam:GenerateTangentFrames=False
+/processorParam:PremultiplyTextureAlpha=True
+/processorParam:PremultiplyVertexColors=True
+/processorParam:ResizeTexturesToPowerOfTwo=False
+/processorParam:RotationX=0
+/processorParam:RotationY=0
+/processorParam:RotationZ=0
+/processorParam:Scale=1
+/processorParam:SwapWindingOrder=False
+/processorParam:TextureFormat=Compressed
+/build:Window.x
+

BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/PrivacyGlass.png


BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Sunset.jpg


+ 98 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/Window.x

@@ -0,0 +1,98 @@
+xof 0303txt 0032
+template FVFData {
+ <b6e70a0e-8ef9-4e83-94ad-ecc8b0c04897>
+ DWORD dwFVF;
+ DWORD nDWords;
+ array DWORD data[nDWords];
+}
+
+template EffectInstance {
+ <e331f7e4-0559-4cc2-8e99-1cec1657928f>
+ STRING EffectFilename;
+ [...]
+}
+
+template EffectParamFloats {
+ <3014b9a0-62f5-478c-9b86-e4ac9f4e418b>
+ STRING ParamName;
+ DWORD nFloats;
+ array FLOAT Floats[nFloats];
+}
+
+template EffectParamString {
+ <1dbc4c88-94c1-46ee-9076-2c28818c9481>
+ STRING ParamName;
+ STRING Value;
+}
+
+template EffectParamDWord {
+ <e13963bc-ae51-4c5d-b00f-cfa3a9d97ce5>
+ STRING ParamName;
+ DWORD Value;
+}
+
+Material PrivacyGlassMaterial {
+ 1.000000;1.000000;1.000000;1.000000;;
+ 0.000000;
+ 1.000000;1.000000;1.000000;;
+ 0.000000;0.000000;0.000000;;
+
+ EffectInstance {
+  "Distorters.fx";
+  
+  EffectParamString {
+   "DisplacementMap";
+   "PrivacyGlass.png";
+  }
+ }
+}
+
+
+Frame Plane01 {
+ 
+
+ FrameTransformMatrix {
+  1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,0.000000,1.000000;;
+ }
+
+ Mesh  {
+  4;
+  -0.500000;-0.500000;0.000000;,
+  0.500000;-0.500000;0.000000;,
+  -0.500000;0.500000;0.000000;,
+  0.500000;0.500000;0.000000;;
+  4;
+  3;2,0,3;,
+  3;1,3,0;,
+  3;3,0,2;,
+  3;0,3,1;;
+
+  MeshNormals  {
+   1;
+   0.000000;0.000000;1.000000;;
+   4;
+   3;0,0,0;,
+   3;0,0,0;,
+   3;0,0,0;,
+   3;0,0,0;;
+  }
+
+  MeshTextureCoords  {
+   4;
+   0.000000;1.000000;,
+   1.000000;1.000000;,
+   0.000000;0.000000;,
+   1.000000;0.000000;;
+  }
+  
+  MeshMaterialList {
+	1;
+	4;
+	0,
+	0,
+	0,
+	0;
+	{ PrivacyGlassMaterial }
+  }
+ }
+}

+ 33 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/android-icons-generator.sh

@@ -0,0 +1,33 @@
+#!/bin/zsh
+
+# Declare a constant variables
+readonly resources_base_path="../../MonoGameDistortionSample.Android/Resources"
+readonly drawable_resources_path="$resources_base_path/drawable-"
+readonly mdpi="mdpi"
+readonly hdpi="hdpi"
+readonly xhdpi="xhdpi"
+readonly xxhdpi="xxhdpi"
+readonly xxxhdpi="xxxhdpi"
+
+echo "Generating Android icons"
+
+echo "Generating Android splash screens"
+mkdir -p "$drawable_resources_path$mdpi"
+mkdir -p "$drawable_resources_path$hdpi"
+mkdir -p "$drawable_resources_path$xhdpi"
+mkdir -p "$drawable_resources_path$xxhdpi"
+mkdir -p "$drawable_resources_path$xxxhdpi"
+
+sips -Z 48 icon-1024.png -o "$drawable_resources_path$mdpi/icon.png"
+sips -Z 72 icon-1024.png -o "$drawable_resources_path$hdpi/icon.png"
+sips -Z 96 icon-1024.png -o "$drawable_resources_path$xhdpi/icon.png"
+sips -Z 144 icon-1024.png -o "$drawable_resources_path$xxhdpi/icon.png"
+sips -Z 192 icon-1024.png -o "$drawable_resources_path$xxxhdpi/icon.png"
+
+sips -Z 470 splash.png -o "$drawable_resources_path$mdpi/splash.png"
+sips -Z 640 splash.png -o "$drawable_resources_path$hdpi/splash.png"
+sips -Z 960 splash.png -o "$drawable_resources_path$xhdpi/splash.png"
+sips -Z 1440 splash.png -o "$drawable_resources_path$xxhdpi/splash.png"
+sips -Z 1920 splash.png -o "$drawable_resources_path$xxxhdpi/splash.png"
+
+echo "Android Generation Complete!"

BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/icon-1024.png


+ 174 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/ios-icons-generator.sh

@@ -0,0 +1,174 @@
+#!/bin/zsh
+
+# Declare a constant variables
+readonly top_level_path="../../MonoGameDistortionSample.iOS/AppIcon.xcassets"
+readonly xcassets_path="$top_level_path/AppIcon.appiconset"
+
+while true; do
+  # Prompt for user confirmation
+  echo -n "This script will delete your '$xcassets_path' directory and recreate all the assets again. Are you sure you wish to proceed? [(y)es/(n)o]: "
+  read confirm
+
+  # Check the user's response
+  if [[ "${confirm:l}" == "y" || "${confirm:l}" == "yes" ]]; then
+      # Check if the directory exists
+      if [[ -d "$top_level_path" ]]; then
+          # Top level directory exists, delete it
+          rm -rf "$top_level_path"
+          echo "'$top_level_path' directory deleted successfully."
+      else
+          echo "'$top_level_path' directory does not exist. Continuing."
+      fi
+      break
+  elif [[ "${confirm:l}" == "n" || "${confirm:l}" == "no" ]]; then
+      echo "Deletion canceled."
+      exit 0
+  else
+      echo "Invalid input. Please enter 'y'/'yes' or 'n'/'no'."
+  fi
+done
+
+echo "iOS Icon Generation Started!"
+
+echo "Creating $xcassets_path directory"
+mkdir -p "$xcassets_path"
+
+# Generate the required icon sizes
+echo "Generating iOS icons"
+sips -Z 20 icon-1024.png -o "$xcassets_path/icon_20x20.png"
+sips -Z 29 icon-1024.png -o "$xcassets_path/icon_29x29.png"
+sips -Z 40 icon-1024.png -o "$xcassets_path/icon_40x40.png"
+sips -Z 58 icon-1024.png -o "$xcassets_path/icon_58x58.png"
+sips -Z 60 icon-1024.png -o "$xcassets_path/icon_60x60.png"
+sips -Z 76 icon-1024.png -o "$xcassets_path/icon_76x76.png"
+sips -Z 80 icon-1024.png -o "$xcassets_path/icon_80x80.png"
+sips -Z 87 icon-1024.png -o "$xcassets_path/icon_87x87.png"
+sips -Z 120 icon-1024.png -o "$xcassets_path/icon_120x120.png"
+sips -Z 152 icon-1024.png -o "$xcassets_path/icon_152x152.png"
+sips -Z 167 icon-1024.png -o "$xcassets_path/icon_167x167.png"
+sips -Z 180 icon-1024.png -o "$xcassets_path/icon_180x180.png"
+# yes I know it's the same size
+sips -Z 1024 icon-1024.png -o "$xcassets_path/icon_1024x1024.png"
+
+# Create the Contents.json file
+echo "Generating Contents.json file"
+cat > "$xcassets_path/Contents.json" <<EOF
+{
+  "images" : [
+    {
+      "filename" : "icon_40x40.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_60x60.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_58x58.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_87x87.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_80x80.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_120x120.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_120x120.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "60x60"
+    },
+    {
+      "filename" : "icon_180x180.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "60x60"
+    },
+    {
+      "filename" : "icon_20x20.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_40x40.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_29x29.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_58x58.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_40x40.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_80x80.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_76x76.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "76x76"
+    },
+    {
+      "filename" : "icon_152x152.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "76x76"
+    },
+    {
+      "filename" : "icon_167x167.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "83.5x83.5"
+    },
+    {
+      "filename" : "icon_1024x1024.png",
+      "idiom" : "ios-marketing",
+      "scale" : "1x",
+      "size" : "1024x1024"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
+EOF
+
+echo "iOS Icon Generation Complete!"

+ 120 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/mac-icons-generator.sh

@@ -0,0 +1,120 @@
+#!/bin/zsh
+
+# Declare a constant variables
+readonly top_level_path="../../MonoGameDistortionSample.Desktop/AppIcon.xcassets"
+readonly xcassets_path="$top_level_path/AppIcon.appiconset"
+
+while true; do
+  # Prompt for user confirmation
+  echo -n "This script will delete your '$xcassets_path' directory and recreate all the assets again. Are you sure you wish to proceed? [(y)es/(n)o]: "
+  read confirm
+
+  # Check the user's response
+  if [[ "${confirm:l}" == "y" || "${confirm:l}" == "yes" ]]; then
+      # Check if the directory exists
+      if [[ -d "$top_level_path" ]]; then
+          # Top level directory exists, delete it
+          rm -rf "$top_level_path"
+          echo "'$top_level_path' directory deleted successfully."
+      else
+          echo "'$top_level_path' directory does not exist. Continuing."
+      fi
+      break
+  elif [[ "${confirm:l}" == "n" || "${confirm:l}" == "no" ]]; then
+      echo "Deletion canceled."
+      exit 0
+  else
+      echo "Invalid input. Please enter 'y'/'yes' or 'n'/'no'."
+  fi
+done
+
+echo "macOS Icon Generation Started!"
+
+echo "Creating $xcassets_path directory"
+mkdir -p "$xcassets_path"
+
+# Generate the required icon sizes
+echo "Generating macOS icons"
+sips -Z 16 icon-1024.png -o "$xcassets_path/icon_16x16.png"
+sips -Z 32 icon-1024.png -o "$xcassets_path/icon_32x32.png"
+sips -Z 64 icon-1024.png -o "$xcassets_path/icon_64x64.png"
+sips -Z 128 icon-1024.png -o "$xcassets_path/icon_128x128.png"
+sips -Z 256 icon-1024.png -o "$xcassets_path/icon_256x256.png"
+sips -Z 512 icon-1024.png -o "$xcassets_path/icon_512x512.png"
+# yes I know it's the same size
+sips -Z 1024 icon-1024.png -o "$xcassets_path/icon_1024x1024.png"
+
+# Create the Contents.json file
+echo "Generating Contents.json file"
+cat > "$xcassets_path/Contents.json" <<EOF
+{
+  "images" : [
+    {
+      "filename": "icon_16x16.png",
+      "idiom": "mac",
+      "scale": "1x",
+      "size": "16x16"
+    },
+    {
+      "filename": "icon_32x32.png",
+      "idiom": "mac",
+      "scale": "2x",
+      "size": "16x16"
+    },
+    {
+      "filename": "icon_32x32.png",
+      "idiom": "mac",
+      "scale": "1x",
+      "size": "32x32"
+    },
+    {
+      "filename": "icon_64x64.png",
+      "idiom": "mac",
+      "scale": "2x",
+      "size": "32x32"
+    },
+    {
+      "filename": "icon_128x128.png",
+      "idiom": "mac",
+      "scale": "1x",
+      "size": "128x128"
+    },
+    {
+      "filename": "icon_256x256.png",
+      "idiom": "mac",
+      "scale": "2x",
+      "size": "128x128"
+    },
+    {
+      "filename": "icon_256x256.png",
+      "idiom": "mac",
+      "scale": "1x",
+      "size": "256x256"
+    },
+    {
+      "filename": "icon_512x512.png",
+      "idiom": "mac",
+      "scale": "2x",
+      "size": "256x256"
+    },
+    {
+      "filename": "icon_512x512.png",
+      "idiom": "mac",
+      "scale": "1x",
+      "size": "512x512"
+    },
+    {
+      "filename": "icon_1024x1024.png",
+      "idiom": "mac",
+      "scale": "2x",
+      "size": "512x512"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
+EOF
+
+echo "macOS Icon Generation Complete!"

BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Content/splash.png


+ 43 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Distorter.cs

@@ -0,0 +1,43 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// Distorter.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+#endregion
+
+namespace MonoGameDistortionSample.Core
+{
+    /// <summary>
+    /// A combination of model and distortion technique.
+    /// </summary>
+    public class Distorter
+    {
+        public string ModelName = String.Empty;
+        public Model Model = null;
+        public float DistortionScale = 0.005f;
+        public Matrix World = Matrix.Identity;
+        public DistortionComponent.DistortionTechnique Technique = 
+            DistortionComponent.DistortionTechnique.ZeroDisplacement;
+        public bool DistortionBlur = false;
+
+        public override string ToString()
+        {
+            string output = 
+                DistortionComponent.GetDistortionTechniqueFriendlyName(Technique) + 
+                " (" + ModelName + ")";
+            if (DistortionBlur)
+            {
+                output += ", Blurred";
+            }
+            return output;
+        }
+    }
+}

BIN
MonoGameDistortionSample/MonoGameDistortionSample.Core/Distortion.png


+ 293 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/DistortionComponent.cs

@@ -0,0 +1,293 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// DistortionComponent.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+#endregion
+
+namespace MonoGameDistortionSample.Core
+{
+    public class DistortionComponent : DrawableGameComponent
+    {
+        #region Enumerations
+        public enum DistortionTechnique
+        {
+            DisplacementMapped,
+            HeatHaze,
+            PullIn,
+            ZeroDisplacement,
+        }
+        #endregion
+
+        #region Constant Data
+        private const float blurAmount = 2f;
+
+        private readonly static string[] distortionTechniqueFriendlyNames = new string[]
+        {
+            "Displacement-Mapped",
+            "Heat-Haze",
+            "Pull-In",
+            "Zero Displacement",
+        };
+
+        public static string GetDistortionTechniqueFriendlyName(
+            DistortionTechnique technique)
+        {
+            return distortionTechniqueFriendlyNames[(int)technique];
+        }
+        #endregion
+
+        #region Fields
+        SpriteBatch spriteBatch;
+        RenderTarget2D sceneMap;
+        RenderTarget2D distortionMap;
+
+        Effect distortEffect;
+        EffectTechnique distortTechnique;
+        EffectTechnique distortBlurTechnique;
+
+        public Matrix View;
+        public Matrix Projection;
+
+        public Distorter Distorter;
+        public bool ShowDistortionMap = false;
+        #endregion
+
+        #region Initialization
+        public DistortionComponent(Game game)
+            : base(game) { }
+
+
+        /// <summary>
+        /// Load your graphics content.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            spriteBatch = new SpriteBatch(GraphicsDevice);
+            distortEffect = Game.Content.Load<Effect>("Distort");
+            distortTechnique = distortEffect.Techniques["Distort"];
+            distortBlurTechnique = distortEffect.Techniques["DistortBlur"];
+
+            // update the projection matrix
+            Projection = Matrix.CreatePerspectiveFieldOfView(1f, GraphicsDevice.Viewport.AspectRatio, 1f, 10000f);
+
+            // look up the resolution and format of our main backbuffer
+            PresentationParameters pp = GraphicsDevice.PresentationParameters;
+            int width = pp.BackBufferWidth;
+            int height = pp.BackBufferHeight;
+            SurfaceFormat format = pp.BackBufferFormat;
+            DepthFormat depthFormat = pp.DepthStencilFormat;
+
+            // create textures for reading back the backbuffer contents
+            sceneMap = new RenderTarget2D(GraphicsDevice, width, height, false, format, depthFormat);
+            distortionMap = new RenderTarget2D(GraphicsDevice, width, height, false, format, depthFormat);
+
+            // set the blur parameters for the current viewport
+            SetBlurEffectParameters(1f / (float)width, 1f / (float)height);
+        }
+
+
+        /// <summary>
+        /// Unload your graphics content.
+        /// </summary>
+        protected override void UnloadContent()
+        {
+            spriteBatch = null;
+            distortEffect = null;
+            distortTechnique = null;
+            distortBlurTechnique = null;
+
+            if (sceneMap != null)
+            {
+                sceneMap.Dispose();
+                sceneMap = null;
+            }
+            if (distortionMap != null)
+            {
+                distortionMap.Dispose();
+                distortionMap = null;
+            }
+        }
+        #endregion
+
+        #region Draw
+
+        /// <summary>
+        /// This should be called at the very start of scene rendering. The distortion
+        /// component uses it to redirect drawing into its custom rendertarget, so it
+        /// can capture the scene image ready to apply the distortion postprocess.
+        /// </summary>
+        public void BeginDraw()
+        {
+            if (Visible)
+            {
+                GraphicsDevice.SetRenderTarget(sceneMap);
+            }
+        }
+        
+        /// <summary>
+        /// Grab a scene that has already been rendered, 
+        /// and add a distortion effect over the top of it.
+        /// </summary>
+        public override void Draw(GameTime gameTime)
+        {
+            // now draw the distortion map
+            GraphicsDevice.SetRenderTarget(ShowDistortionMap ? null : distortionMap);
+            GraphicsDevice.Clear(Color.Transparent);
+
+            // draw the distorter
+            if (Distorter != null)
+            {
+                Matrix worldView = Distorter.World * View;
+                Matrix[] transforms = new Matrix[Distorter.Model.Bones.Count];
+                Distorter.Model.CopyAbsoluteBoneTransformsTo(transforms);
+
+                // make sure the depth buffering is on, so only parts of the scene
+                // behind the distortion effect are affected
+                GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+
+                foreach (ModelMesh mesh in Distorter.Model.Meshes)
+                {
+                    Matrix meshWorldView = transforms[mesh.ParentBone.Index] * 
+                        worldView;                    
+                    foreach (Effect effect in mesh.Effects)
+                    {                        
+                        effect.CurrentTechnique = 
+                            effect.Techniques[Distorter.Technique.ToString()];
+                        effect.Parameters["WorldView"].SetValue(meshWorldView);
+                        effect.Parameters["WorldViewProjection"].SetValue(
+                            meshWorldView * Projection);
+                        effect.Parameters["DistortionScale"].SetValue(
+                            Distorter.DistortionScale);
+                        effect.Parameters["Time"].SetValue(
+                            (float)gameTime.TotalGameTime.TotalSeconds);
+                    }
+                    mesh.Draw();
+                }
+            }
+
+            // if we want to show the distortion map, then the backbuffer is done.
+            // if we want to render the scene distorted, then we need to resolve the
+            // backbuffer as the distortion map and use it to distort the scene
+            if (!ShowDistortionMap)
+            {
+                GraphicsDevice.SetRenderTarget(null);
+
+                // draw the scene image again, distorting it with the distortion map
+                GraphicsDevice.Textures[1] = distortionMap;
+                GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp;
+                Viewport viewport = GraphicsDevice.Viewport;
+                distortEffect.CurrentTechnique = Distorter.DistortionBlur ?
+                    distortBlurTechnique : distortTechnique;
+                DrawFullscreenQuad(sceneMap, viewport.Width, viewport.Height,
+                    distortEffect);
+            }
+        } 
+
+        /// <summary>
+        /// Helper for drawing a texture into the current rendertarget,
+        /// using a custom shader to apply postprocessing effects.
+        /// </summary>
+        void DrawFullscreenQuad(Texture2D texture, int width, int height, Effect effect)
+        {
+            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect);
+            spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
+            spriteBatch.End();
+        }
+        #endregion
+
+        #region Blur Calculation
+        /// <summary>
+        /// Computes sample weightings and texture coordinate offsets
+        /// for one pass of a separable gaussian blur filter.
+        /// </summary>
+        /// <remarks>
+        /// This function was originally provided in the BloomComponent class in the 
+        /// Bloom Postprocess sample.
+        /// </remarks>
+        void SetBlurEffectParameters(float dx, float dy)
+        {
+            // Look up the sample weight and offset effect parameters.
+            EffectParameter weightsParameter, offsetsParameter;
+
+            weightsParameter = distortEffect.Parameters["SampleWeights"];
+            offsetsParameter = distortEffect.Parameters["SampleOffsets"];
+
+            // Look up how many samples our gaussian blur effect supports.
+            int sampleCount = weightsParameter.Elements.Count;
+
+            // Create temporary arrays for computing our filter settings.
+            float[] sampleWeights = new float[sampleCount];
+            Vector2[] sampleOffsets = new Vector2[sampleCount];
+
+            // The first sample always has a zero offset.
+            sampleWeights[0] = ComputeGaussian(0);
+            sampleOffsets[0] = new Vector2(0);
+
+            // Maintain a sum of all the weighting values.
+            float totalWeights = sampleWeights[0];
+
+            // Add pairs of additional sample taps, positioned
+            // along a line in both directions from the center.
+            for (int i = 0; i < sampleCount / 2; i++)
+            {
+                // Store weights for the positive and negative taps.
+                float weight = ComputeGaussian(i + 1);
+
+                sampleWeights[i * 2 + 1] = weight;
+                sampleWeights[i * 2 + 2] = weight;
+
+                totalWeights += weight * 2;
+
+                // To get the maximum amount of blurring from a limited number of
+                // pixel shader samples, we take advantage of the bilinear filtering
+                // hardware inside the texture fetch unit. If we position our texture
+                // coordinates exactly halfway between two texels, the filtering unit
+                // will average them for us, giving two samples for the price of one.
+                // This allows us to step in units of two texels per sample, rather
+                // than just one at a time. The 1.5 offset kicks things off by
+                // positioning us nicely in between two texels.
+                float sampleOffset = i * 2 + 1.5f;
+
+                Vector2 delta = new Vector2(dx, dy) * sampleOffset;
+
+                // Store texture coordinate offsets for the positive and negative taps.
+                sampleOffsets[i * 2 + 1] = delta;
+                sampleOffsets[i * 2 + 2] = -delta;
+            }
+
+            // Normalize the list of sample weightings, so they will always sum to one.
+            for (int i = 0; i < sampleWeights.Length; i++)
+            {
+                sampleWeights[i] /= totalWeights;
+            }
+
+            // Tell the effect about our new filter settings.
+            weightsParameter.SetValue(sampleWeights);
+            offsetsParameter.SetValue(sampleOffsets);
+        }
+
+        /// <summary>
+        /// Evaluates a single point on the gaussian falloff curve.
+        /// Used for setting up the blur filter weightings.
+        /// </summary>
+        /// <remarks>
+        /// This function was originally provided in the BloomComponent class in the 
+        /// Bloom Postprocess sample.
+        /// </remarks>
+        static float ComputeGaussian(float n)
+        {
+            return (float)((1.0 / Math.Sqrt(2 * Math.PI * blurAmount)) *
+                           Math.Exp(-(n * n) / (2 * blurAmount * blurAmount)));
+        }
+        #endregion
+    }
+}

+ 88 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/LocalizationManager.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using System.Resources;
+using System.Threading;
+
+namespace MonoGameDistortionSample.Core.Localization;
+
+/// <summary>
+/// Manages localization settings for the game, including retrieving supported cultures and setting the current culture for localization.
+/// </summary>
+internal class LocalizationManager
+{
+    /// <summary>
+    /// the culture code we default to
+    /// </summary>
+    public const string DEFAULT_CULTURE_CODE = "en-EN";
+
+    /// <summary>
+    /// Retrieves a list of supported cultures based on available language resources in the game.
+    /// This method checks the current culture settings and the satellite assemblies for available localized resources.
+    /// </summary>
+    /// <returns>A list of <see cref="CultureInfo"/> objects representing the cultures supported by the game.</returns>
+    /// <remarks>
+    /// This method iterates through all specific cultures defined in the satellite assemblies and attempts to load the corresponding resource set.
+    /// If a resource set is found for a particular culture, that culture is added to the list of supported cultures. The invariant culture
+    /// is always included in the returned list as it represents the default (non-localized) resources.
+    /// </remarks>
+    public static List<CultureInfo> GetSupportedCultures()
+    {
+        // Create a list to hold supported cultures
+        List<CultureInfo> supportedCultures = new List<CultureInfo>();
+
+        // Get the current assembly
+        Assembly assembly = Assembly.GetExecutingAssembly();
+
+        // Resource manager for your Resources.resx
+        ResourceManager resourceManager = new ResourceManager("MonoGameDistortionSample.Core.Localization.Resources", assembly);
+
+        // Get all cultures defined in the satellite assemblies
+        CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
+
+        foreach (CultureInfo culture in cultures)
+        {
+            try
+            {
+                // Try to get the resource set for this culture
+                var resourceSet = resourceManager.GetResourceSet(culture, true, false);
+                if (resourceSet != null)
+                {
+                    supportedCultures.Add(culture);
+                }
+            }
+            catch (MissingManifestResourceException)
+            {
+                // This exception is thrown when there is no .resx for the culture, ignore it
+            }
+        }
+
+        // Always add the default (invariant) culture - the base .resx file
+        supportedCultures.Add(CultureInfo.InvariantCulture);
+
+        return supportedCultures;
+    }
+
+    /// <summary>
+    /// Sets the current culture of the game based on the specified culture code.
+    /// This method updates both the current culture and UI culture for the current thread.
+    /// </summary>
+    /// <param name="cultureCode">The culture code (e.g., "en-US", "fr-FR") to set for the game.</param>
+    /// <remarks>
+    /// This method modifies the <see cref="Thread.CurrentThread.CurrentCulture"/> and <see cref="Thread.CurrentThread.CurrentUICulture"/> properties,
+    /// which affect how dates, numbers, and other culture-specific values are formatted, as well as how localized resources are loaded.
+    /// </remarks>
+    public static void SetCulture(string cultureCode)
+    {
+        if (string.IsNullOrEmpty(cultureCode))
+            throw new ArgumentNullException(nameof(cultureCode), "A culture code must be provided.");
+
+        // Create a CultureInfo object from the culture code
+        CultureInfo culture = new CultureInfo(cultureCode);
+
+        // Set the current culture and UI culture for the current thread
+        Thread.CurrentThread.CurrentCulture = culture;
+        Thread.CurrentThread.CurrentUICulture = culture;
+    }
+}

+ 501 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.Designer.cs

@@ -0,0 +1,501 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MonoGameDistortionSample.Core.Localization {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    ///   This class was generated by MSBuild using the GenerateResource task.
+    ///   To add or remove a member, edit your .resx file then rerun MSBuild.
+    /// </summary>
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Build.Tasks.StronglyTypedResourceBuilder", "15.1.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MonoGameDistortionSample.Core.Localization.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to About.
+        /// </summary>
+        internal static string About {
+            get {
+                return ResourceManager.GetString("About", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Back.
+        /// </summary>
+        internal static string Back {
+            get {
+                return ResourceManager.GetString("Back", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Click Anywhere.
+        /// </summary>
+        internal static string ClickAnywhere {
+            get {
+                return ResourceManager.GetString("ClickAnywhere", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Collect These!.
+        /// </summary>
+        internal static string CollectThese {
+            get {
+                return ResourceManager.GetString("CollectThese", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Display Mode : {0}.
+        /// </summary>
+        internal static string DisplayMode {
+            get {
+                return ResourceManager.GetString("DisplayMode", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Don&apos;t Die!.
+        /// </summary>
+        internal static string DontDie {
+            get {
+                return ResourceManager.GetString("DontDie", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to english (English).
+        /// </summary>
+        internal static string English {
+            get {
+                return ResourceManager.GetString("English", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Initialize can only be called once.
+        /// </summary>
+        internal static string ErrorAccelerometerInitializeOnce {
+            get {
+                return ResourceManager.GetString("ErrorAccelerometerInitializeOnce", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to You must Initialize before you can call GetState().
+        /// </summary>
+        internal static string ErrorAccelerometerMustInitialize {
+            get {
+                return ResourceManager.GetString("ErrorAccelerometerMustInitialize", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Acceleration: {0}, IsActive: {1}.
+        /// </summary>
+        internal static string ErrorAccelerometerToString {
+            get {
+                return ResourceManager.GetString("ErrorAccelerometerToString", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to A level must have an exit..
+        /// </summary>
+        internal static string ErrorLevelExit {
+            get {
+                return ResourceManager.GetString("ErrorLevelExit", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to The length of line {0} is different from all preceeding lines..
+        /// </summary>
+        internal static string ErrorLevelLineLength {
+            get {
+                return ResourceManager.GetString("ErrorLevelLineLength", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to A level may only have one exit..
+        /// </summary>
+        internal static string ErrorLevelOneExit {
+            get {
+                return ResourceManager.GetString("ErrorLevelOneExit", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to A level may only have one starting point..
+        /// </summary>
+        internal static string ErrorLevelOneStartingPoint {
+            get {
+                return ResourceManager.GetString("ErrorLevelOneStartingPoint", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to A level must have a starting point..
+        /// </summary>
+        internal static string ErrorLevelStartingPoint {
+            get {
+                return ResourceManager.GetString("ErrorLevelStartingPoint", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to No animation is currently playing..
+        /// </summary>
+        internal static string ErrorNoAnimation {
+            get {
+                return ResourceManager.GetString("ErrorNoAnimation", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported tile type character &apos;{0}&apos; at position {1}, {2}..
+        /// </summary>
+        internal static string ErrorUnsupportedTileType {
+            get {
+                return ResourceManager.GetString("ErrorUnsupportedTileType", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Exit.
+        /// </summary>
+        internal static string Exit {
+            get {
+                return ResourceManager.GetString("Exit", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Are you sure you want to exit?.
+        /// </summary>
+        internal static string ExitQuestion {
+            get {
+                return ResourceManager.GetString("ExitQuestion", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Fullscreen.
+        /// </summary>
+        internal static string FullScreen {
+            get {
+                return ResourceManager.GetString("FullScreen", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Gems Collected.
+        /// </summary>
+        internal static string GemsCollected {
+            get {
+                return ResourceManager.GetString("GemsCollected", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Get To Here!.
+        /// </summary>
+        internal static string GetToHere {
+            get {
+                return ResourceManager.GetString("GetToHere", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Language: .
+        /// </summary>
+        internal static string Language {
+            get {
+                return ResourceManager.GetString("Language", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Let&apos;s GO!!!!.
+        /// </summary>
+        internal static string LetsGo {
+            get {
+                return ResourceManager.GetString("LetsGo", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Level Completed!.
+        /// </summary>
+        internal static string LevelCompleted {
+            get {
+                return ResourceManager.GetString("LevelCompleted", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Loading....
+        /// </summary>
+        internal static string Loading {
+            get {
+                return ResourceManager.GetString("Loading", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Main Menu.
+        /// </summary>
+        internal static string MainMenu {
+            get {
+                return ResourceManager.GetString("MainMenu", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MonoGame Site.
+        /// </summary>
+        internal static string MonoGameSite {
+            get {
+                return ResourceManager.GetString("MonoGameSite", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to CONGRATULATIONS!! You set a new high score..
+        /// </summary>
+        internal static string NewHighScore {
+            get {
+                return ResourceManager.GetString("NewHighScore", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to No = B button, Esc.
+        /// </summary>
+        internal static string NoButtonHelp {
+            get {
+                return ResourceManager.GetString("NoButtonHelp", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to [ No ].
+        /// </summary>
+        internal static string NoButtonText {
+            get {
+                return ResourceManager.GetString("NoButtonText", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Particle Effect: .
+        /// </summary>
+        internal static string ParticleEffect {
+            get {
+                return ResourceManager.GetString("ParticleEffect", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Paused.
+        /// </summary>
+        internal static string Paused {
+            get {
+                return ResourceManager.GetString("Paused", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Play.
+        /// </summary>
+        internal static string Play {
+            get {
+                return ResourceManager.GetString("Play", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Quit.
+        /// </summary>
+        internal static string Quit {
+            get {
+                return ResourceManager.GetString("Quit", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Are you sure you want to quit?.
+        /// </summary>
+        internal static string QuitQuestion {
+            get {
+                return ResourceManager.GetString("QuitQuestion", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Resume.
+        /// </summary>
+        internal static string Resume {
+            get {
+                return ResourceManager.GetString("Resume", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SCORE: .
+        /// </summary>
+        internal static string Score {
+            get {
+                return ResourceManager.GetString("Score", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Settings.
+        /// </summary>
+        internal static string Settings {
+            get {
+                return ResourceManager.GetString("Settings", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Show Virtual GamePad: .
+        /// </summary>
+        internal static string ShowVirtualGamePad {
+            get {
+                return ResourceManager.GetString("ShowVirtualGamePad", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Tap Anywhere.
+        /// </summary>
+        internal static string TapAnywhere {
+            get {
+                return ResourceManager.GetString("TapAnywhere", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Tap To Pause.
+        /// </summary>
+        internal static string TapToPause {
+            get {
+                return ResourceManager.GetString("TapToPause", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to TIME: .
+        /// </summary>
+        internal static string Time {
+            get {
+                return ResourceManager.GetString("Time", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Time Ran Out!.
+        /// </summary>
+        internal static string TimeRanOut {
+            get {
+                return ResourceManager.GetString("TimeRanOut", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Tutorial.
+        /// </summary>
+        internal static string Tutorial {
+            get {
+                return ResourceManager.GetString("Tutorial", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Windowed.
+        /// </summary>
+        internal static string Windowed {
+            get {
+                return ResourceManager.GetString("Windowed", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Yes = A Button, Space, Enter.
+        /// </summary>
+        internal static string YesButtonHelp {
+            get {
+                return ResourceManager.GetString("YesButtonHelp", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to [ Yes ].
+        /// </summary>
+        internal static string YesButtonText {
+            get {
+                return ResourceManager.GetString("YesButtonText", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to You DIED!.
+        /// </summary>
+        internal static string YouDied {
+            get {
+                return ResourceManager.GetString("YouDied", resourceCulture);
+            }
+        }
+    }
+}

+ 126 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.es-ES.resx

@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="TapAnywhere" xml:space="preserve">
+    <value>Toca En Cualquier Sitio</value>
+  </data>
+  <data name="ClickAnywhere" xml:space="preserve">
+    <value>Click Anywhere</value>
+  </data>
+</root>

+ 126 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.fr-FR.resx

@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>s
+  <data name="TapAnywhere" xml:space="preserve">
+    <value>Tap Anywhere</value>
+  </data>
+  <data name="ClickAnywhere" xml:space="preserve">
+    <value>Click Anywhere</value>
+  </data>
+</root>

+ 126 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/Localization/Resources.resx

@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="TapAnywhere" xml:space="preserve">
+    <value>Tap Anywhere</value>
+  </data>
+  <data name="ClickAnywhere" xml:space="preserve">
+    <value>Click Anywhere</value>
+  </data>
+</root>

+ 11 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/MonoGameDistortionSample.Core.csproj

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <Platforms>AnyCPU</Platforms>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*">
+      <PrivateAssets>All</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+</Project>

+ 241 - 0
MonoGameDistortionSample/MonoGameDistortionSample.Core/MonoGameDistortionSampleGame.cs

@@ -0,0 +1,241 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace MonoGameDistortionSample.Core
+{
+    /// <summary>
+    /// This sample demonstrates a variety of image-distorting 
+    /// post-processing techniques.
+    /// </summary>
+    public class MonoGameDistortionSampleGame : Microsoft.Xna.Framework.Game
+    {
+        #region Fields
+        GraphicsDeviceManager graphics;
+
+        const float initialViewAngle = MathHelper.Pi / 2f;
+        float viewAngle = initialViewAngle;
+        const float CameraRotationSpeed = 0.1f;
+        const float ViewDistance = 750.0f;
+
+        DistortionComponent distortionComponent;
+        Distorter[] distorters;
+        int currentDistorter;
+
+        SpriteBatch spriteBatch;
+        SpriteFont spriteFont;
+        Vector2 overlayTextLocation;
+        Texture2D background;
+
+        KeyboardState lastKeyboardState = new KeyboardState();
+        GamePadState lastGamePadState = new GamePadState();
+        KeyboardState currentKeyboardState = new KeyboardState();
+        GamePadState currentGamePadState = new GamePadState();
+        #endregion
+
+        #region Initialization
+        public MonoGameDistortionSampleGame()
+        {
+            graphics = new GraphicsDeviceManager(this);
+            Content.RootDirectory = "Content";
+
+            distortionComponent = new DistortionComponent(this);
+            Components.Add(distortionComponent);
+        }
+
+
+        protected override void Initialize()
+        {
+            distorters = new Distorter[3];
+            currentDistorter = 0;
+
+            base.Initialize();
+        }
+
+
+        /// <summary>
+        /// Load your graphics content.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
+            spriteFont = Content.Load<SpriteFont>("Fonts/hud");
+            background = Content.Load<Texture2D>("Sunset");
+
+            distorters[0] = new Distorter();
+            distorters[0].ModelName = "Dude";
+            distorters[0].World = Matrix.CreateTranslation(0, -40, 0) *
+                Matrix.CreateScale(8);
+            distorters[0].Model = Content.Load<Model>("Dude");
+            distorters[0].Technique =
+                DistortionComponent.DistortionTechnique.PullIn;
+            distorters[0].DistortionScale = 0.0003f;
+            distorters[0].DistortionBlur = true;
+
+            distorters[1] = new Distorter();
+            distorters[1].ModelName = "Cylinder";
+            distorters[1].World = Matrix.CreateScale(200);
+            distorters[1].Model = Content.Load<Model>("Cylinder");
+            distorters[1].Technique =
+                DistortionComponent.DistortionTechnique.HeatHaze;
+            distorters[1].DistortionScale = 0.025f;
+            distorters[1].DistortionBlur = true;
+
+            distorters[2] = new Distorter();
+            distorters[2].ModelName = "Window";
+            distorters[2].World = Matrix.CreateScale(500);
+            distorters[2].Model = Content.Load<Model>("Window");
+            distorters[2].Technique =
+                DistortionComponent.DistortionTechnique.DisplacementMapped;
+            distorters[2].DistortionScale = 0.025f;
+            distorters[2].DistortionBlur = false;
+
+            overlayTextLocation = new Vector2(
+                (float)graphics.GraphicsDevice.Viewport.X +
+                (float)graphics.GraphicsDevice.Viewport.Width * 0.1f,
+                (float)graphics.GraphicsDevice.Viewport.Y +
+                (float)graphics.GraphicsDevice.Viewport.Height * 0.1f
+                );
+        }
+
+
+        #endregion
+
+        #region Update and Draw
+        /// <summary>
+        /// Allows the game to run logic.
+        /// </summary>
+        protected override void Update(GameTime gameTime)
+        {
+            HandleInput();
+
+            // update the distortion component
+            distortionComponent.Distorter = distorters[currentDistorter];
+
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// This is called when the game should draw itself.
+        /// </summary>
+        protected override void Draw(GameTime gameTime)
+        {
+            distortionComponent.BeginDraw();
+
+            GraphicsDevice.Clear(Color.Black);
+
+            // Draw the background image.
+            spriteBatch.Begin(0, BlendState.Opaque);
+            spriteBatch.Draw(background, GraphicsDevice.Viewport.Bounds, Color.White);
+            spriteBatch.End();
+
+            // Draw other components (which includes the distortion).
+            base.Draw(gameTime);
+
+            // Display some text over the top. Note how we draw this after distortion
+            // because we don't want the text to be affected by the postprocessing.
+            DrawOverlayText();
+        }
+
+
+        /// <summary>
+        /// Displays an overlay showing what the controls are,
+        /// and which settings are currently selected.
+        /// </summary>
+        void DrawOverlayText()
+        {
+            string text = distorters[currentDistorter].ToString() + "\n\n" +
+                "A: Cycle Distorter\n" +
+                "X: " + (distorters[currentDistorter].DistortionBlur ? "Disable" :
+                    "Enable") + " Distorter Blur\n" +
+                "B: " + (distortionComponent.ShowDistortionMap ? "Hide" : "Show") +
+                " Distortion Map\n";
+
+            spriteBatch.Begin();
+
+            // Draw the string twice to create a drop shadow, first colored black
+            // and offset one pixel to the bottom right, then again in white at the
+            // intended position. this makes text easier to read over the background.
+            spriteBatch.DrawString(spriteFont, text, overlayTextLocation + Vector2.One,
+                Color.Black);
+            spriteBatch.DrawString(spriteFont, text, overlayTextLocation, Color.White);
+
+            spriteBatch.End();
+        }
+        #endregion
+
+        #region Handle Input
+        /// <summary>
+        /// Handles input for quitting or changing the sample settings.
+        /// </summary>
+        private void HandleInput()
+        {
+            lastKeyboardState = currentKeyboardState;
+            lastGamePadState = currentGamePadState;
+
+            currentKeyboardState = Keyboard.GetState();
+            currentGamePadState = GamePad.GetState(PlayerIndex.One);
+
+            // Check for exit.
+            if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
+                currentGamePadState.Buttons.Back == ButtonState.Pressed)
+            {
+                Exit();
+            }
+
+            // Cycle mode
+            if ((currentGamePadState.Buttons.A == ButtonState.Pressed &&
+                 lastGamePadState.Buttons.A != ButtonState.Pressed) ||
+                (currentKeyboardState.IsKeyDown(Keys.Space) &&
+                 lastKeyboardState.IsKeyUp(Keys.Space)) ||
+                (currentKeyboardState.IsKeyDown(Keys.A) &&
+                 lastKeyboardState.IsKeyUp(Keys.A)))
+            {
+                currentDistorter = (currentDistorter + 1) % distorters.Length;
+                viewAngle = initialViewAngle;
+            }
+
+            // Toggle showing the distortion map on or off?
+            if ((currentGamePadState.Buttons.B == ButtonState.Pressed &&
+                 lastGamePadState.Buttons.B != ButtonState.Pressed) ||
+                (currentKeyboardState.IsKeyDown(Keys.Tab) &&
+                 lastKeyboardState.IsKeyUp(Keys.Tab)) ||
+                (currentKeyboardState.IsKeyDown(Keys.B) &&
+                 lastKeyboardState.IsKeyUp(Keys.B)))
+            {
+                distortionComponent.ShowDistortionMap =
+                    !distortionComponent.ShowDistortionMap;
+            }
+
+            // Toggle showing the distortion map on or off?
+            if ((currentGamePadState.Buttons.X == ButtonState.Pressed &&
+                 lastGamePadState.Buttons.X != ButtonState.Pressed) ||
+                (currentKeyboardState.IsKeyDown(Keys.LeftControl) &&
+                 lastKeyboardState.IsKeyUp(Keys.LeftControl)) ||
+                (currentKeyboardState.IsKeyDown(Keys.X) &&
+                 lastKeyboardState.IsKeyUp(Keys.X)))
+            {
+                distorters[currentDistorter].DistortionBlur =
+                    !distorters[currentDistorter].DistortionBlur;
+            }
+
+            // rotate the camera, using the left thumbstick and arrow keys
+            float viewAngleChange = currentGamePadState.ThumbSticks.Left.X;
+            if (currentKeyboardState.IsKeyDown(Keys.Left))
+            {
+                viewAngleChange = -1;
+            }
+            else if (currentKeyboardState.IsKeyDown(Keys.Right))
+            {
+                viewAngleChange = 1;
+            }
+            viewAngle += viewAngleChange * CameraRotationSpeed;
+            distortionComponent.View = Matrix.CreateLookAt(ViewDistance *
+                new Vector3((float)Math.Cos(viewAngle), 0, (float)Math.Sin(viewAngle)),
+                Vector3.Zero, Vector3.Up);
+        }
+        #endregion
+    }
+}

+ 126 - 0
MonoGameDistortionSample/MonoGameDistortionSample.sln

@@ -0,0 +1,126 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34723.18
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGameDistortionSample.Android", "Platforms\Android\MonoGameDistortionSample.Android.csproj", "{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGameDistortionSample.iOS", "Platforms\iOS\MonoGameDistortionSample.iOS.csproj", "{109593E3-17AE-4482-9556-4B2068F9479B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGameDistortionSample.Core", "MonoGameDistortionSample.Core\MonoGameDistortionSample.Core.csproj", "{47BD21C1-2170-456C-AF98-6D92C5F73BA9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGameDistortionSample.DesktopGL", "Platforms\DesktopGL\MonoGameDistortionSample.DesktopGL.csproj", "{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGameDistortionSample.WindowsDX", "Platforms\WindowsDX\MonoGameDistortionSample.WindowsDX.csproj", "{0BBBD441-30E0-43E7-B691-EA19A797AA9B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platforms", "Platforms", "{B4A0E918-51B4-40FF-9845-60F2616B3F27}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{609F40C5-C560-40E6-76C7-691311A42DF0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistortionPipeline", "Dependencies\DistortionPipeline\DistortionPipeline.csproj", "{70E0404C-CBD2-478A-867E-195EB1DFC54D}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|x64.Build.0 = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|x64.Deploy.0 = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Debug|x86.Build.0 = Debug|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|Any CPU.Deploy.0 = Release|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|x64.ActiveCfg = Release|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|x64.Build.0 = Release|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|x64.Deploy.0 = Release|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|x86.ActiveCfg = Release|Any CPU
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3}.Release|x86.Build.0 = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|x64.Build.0 = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|x64.Deploy.0 = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Debug|x86.Build.0 = Debug|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|Any CPU.Deploy.0 = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|x64.ActiveCfg = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|x64.Build.0 = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|x64.Deploy.0 = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|x86.ActiveCfg = Release|Any CPU
+		{109593E3-17AE-4482-9556-4B2068F9479B}.Release|x86.Build.0 = Release|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Debug|x64.Build.0 = Debug|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Debug|x86.Build.0 = Debug|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Release|x64.ActiveCfg = Release|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Release|x64.Build.0 = Release|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Release|x86.ActiveCfg = Release|Any CPU
+		{47BD21C1-2170-456C-AF98-6D92C5F73BA9}.Release|x86.Build.0 = Release|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Debug|x64.Build.0 = Debug|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Debug|x86.Build.0 = Debug|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Release|x64.ActiveCfg = Release|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Release|x64.Build.0 = Release|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Release|x86.ActiveCfg = Release|Any CPU
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6}.Release|x86.Build.0 = Release|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Debug|x64.Build.0 = Debug|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Debug|x86.Build.0 = Debug|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Release|x64.ActiveCfg = Release|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Release|x64.Build.0 = Release|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Release|x86.ActiveCfg = Release|Any CPU
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B}.Release|x86.Build.0 = Release|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Debug|x64.Build.0 = Debug|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Debug|x86.Build.0 = Debug|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Release|x64.ActiveCfg = Release|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Release|x64.Build.0 = Release|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Release|x86.ActiveCfg = Release|Any CPU
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D}.Release|x86.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{6161B64E-0BF7-42D9-B2BA-6BB16460BFE3} = {B4A0E918-51B4-40FF-9845-60F2616B3F27}
+		{109593E3-17AE-4482-9556-4B2068F9479B} = {B4A0E918-51B4-40FF-9845-60F2616B3F27}
+		{85B968E5-2E46-45A3-AD10-1F3F1FB6F9B6} = {B4A0E918-51B4-40FF-9845-60F2616B3F27}
+		{0BBBD441-30E0-43E7-B691-EA19A797AA9B} = {B4A0E918-51B4-40FF-9845-60F2616B3F27}
+		{70E0404C-CBD2-478A-867E-195EB1DFC54D} = {609F40C5-C560-40E6-76C7-691311A42DF0}
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {51ECE597-0299-4753-B7F5-4E8EDECD7BB4}
+	EndGlobalSection
+EndGlobal

+ 14 - 0
MonoGameDistortionSample/Platforms/Android/.vscode/launch.json

@@ -0,0 +1,14 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "name": "C#: MonoGameDistortionSample Debug",
+        "type": "maui",
+        "request": "launch",
+        "preLaunchTask": "maui: Build"
+      }
+    ],
+  }

+ 15 - 0
MonoGameDistortionSample/Platforms/Android/AndroidManifest.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.companyname.MonoGameDistortionSample" android:versionCode="1" android:versionName="1.0">
+	<uses-feature android:glEsVersion="0x00020000" android:required="true" />
+	<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="34" />
+	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+	<uses-permission android:name="android.permission.INTERNET" />
+	<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+	<application
+		android:hardwareAccelerated="true"
+		android:icon="@drawable/icon"
+		android:isGame="true"
+		android:label="@string/app_name"
+		android:theme="@style/MainTheme"
+		/>
+</manifest>

+ 52 - 0
MonoGameDistortionSample/Platforms/Android/MainActivity.cs

@@ -0,0 +1,52 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Android.Views;
+
+using Microsoft.Xna.Framework;
+
+using MonoGameDistortionSample.Core;
+
+namespace MonoGameDistortionSample.Android
+{
+    /// <summary>
+    /// The main activity for the Android application. It initializes the game instance,
+    /// sets up the rendering view, and starts the game loop.
+    /// </summary>
+    /// <remarks>
+    /// This class is responsible for managing the Android activity lifecycle and integrating
+    /// with the MonoGame framework.
+    /// </remarks>
+    [Activity(
+        Label = "MonoGameDistortionSample",
+        MainLauncher = true,
+        Icon = "@drawable/icon",
+        Theme = "@style/Theme.Splash",
+        AlwaysRetainTaskState = true,
+        LaunchMode = LaunchMode.SingleInstance,
+        ScreenOrientation = ScreenOrientation.SensorLandscape,
+        ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden
+    )]
+    public class MainActivity : AndroidGameActivity
+    {
+        private MonoGameDistortionSampleGame _game;
+        private View _view;
+
+        /// <summary>
+        /// Called when the activity is first created. Initializes the game instance,
+        /// retrieves its rendering view, and sets it as the content view of the activity.
+        /// Finally, starts the game loop.
+        /// </summary>
+        /// <param name="bundle">A Bundle containing the activity's previously saved state, if any.</param>
+        protected override void OnCreate(Bundle bundle)
+        {
+            base.OnCreate(bundle);
+
+            _game = new MonoGameDistortionSampleGame();
+            _view = _game.Services.GetService(typeof(View)) as View;
+
+            SetContentView(_view);
+            _game.Run();
+        }
+    }
+}

+ 27 - 0
MonoGameDistortionSample/Platforms/Android/MonoGameDistortionSample.Android.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0-android</TargetFramework>
+    <SupportedOSPlatformVersion>29</SupportedOSPlatformVersion>
+    <ApplicationId>com.companyname.MonoGameDistortionSample</ApplicationId>
+    <ApplicationVersion>1</ApplicationVersion>
+    <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
+    <AssemblyName>MonoGameDistortionSample</AssemblyName>
+  </PropertyGroup>
+  <ItemGroup>
+    <MonoGameContentReference Include="..\..\MonoGameDistortionSample.Core\Content\MonoGameDistortionSample.mgcb">
+      <Link>Content\MonoGameDistortionSample.mgcb</Link>
+    </MonoGameContentReference>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\MonoGameDistortionSample.Core\MonoGameDistortionSample.Core.csproj" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Framework.Android" Version="3.8.*" />
+  </ItemGroup>
+  <Target Name="RestoreDotnetTools" BeforeTargets="CollectPackageReferences">
+    <Message Text="Restoring dotnet tools (this might take a while depending on your internet speed and should only happen upon building your project for the first time, or after upgrading MonoGame, or clearing your nuget cache)" Importance="High" />
+    <Exec Command="dotnet tool restore" />
+  </Target>
+</Project>

BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-hdpi/icon.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-hdpi/splash.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-mdpi/icon.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-mdpi/splash.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-xhdpi/icon.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-xhdpi/splash.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxhdpi/icon.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxhdpi/splash.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxxhdpi/icon.png


BIN
MonoGameDistortionSample/Platforms/Android/Resources/drawable-xxxhdpi/splash.png


+ 4 - 0
MonoGameDistortionSample/Platforms/Android/Resources/values/ic_launcher_background.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#9ACEEB</color>
+</resources>

+ 4 - 0
MonoGameDistortionSample/Platforms/Android/Resources/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="app_name">MonoGameDistortionSample</string>
+</resources>

+ 17 - 0
MonoGameDistortionSample/Platforms/Android/Resources/values/styles.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <style name="Theme.Splash" parent="android:Theme">
+    <item name="android:windowBackground">@drawable/splash</item>
+    <item name="android:windowNoTitle">true</item>
+  </style>
+  <style name="MainTheme">
+    <!-- Set theme colors from https://aka.ms/material-colors -->
+    <!-- colorPrimary is used for the default action bar background -->
+    <!--<item name="colorPrimary">#2196F3</item>-->
+    <!-- colorPrimaryDark is used for the status bar -->
+    <!--<item name="colorPrimaryDark">#1976D2</item>-->
+    <!-- colorAccent is used as the default value for colorControlActivated
+         which is used to tint widgets -->
+    <!--<item name="colorAccent">#FF4081</item>-->
+  </style>
+</resources>

+ 14 - 0
MonoGameDistortionSample/Platforms/DesktopGL/.vscode/launch.json

@@ -0,0 +1,14 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "name": "C#: MonoGameDistortionSample Debug",
+        "type": "dotnet",
+        "request": "launch",
+        "projectPath": "${workspaceFolder}/MonoGameDistortionSample.DesktopGL.csproj"
+      }
+    ],
+  }

+ 27 - 0
MonoGameDistortionSample/Platforms/DesktopGL/MonoGameDistortionSample.DesktopGL.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <PublishReadyToRun>false</PublishReadyToRun>
+    <TieredCompilation>false</TieredCompilation>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <ApplicationIcon>../../MonoGameDistortionSample.Core/Content/Icon.ico</ApplicationIcon>
+    <AssemblyName>MonoGameDistortionSample</AssemblyName>
+  </PropertyGroup>
+  <ItemGroup>
+    <MonoGameContentReference Include="..\..\MonoGameDistortionSample.Core\Content\MonoGameDistortionSample.mgcb">
+      <Link>Content\MonoGameDistortionSample.mgcb</Link>
+    </MonoGameContentReference>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\MonoGameDistortionSample.Core\MonoGameDistortionSample.Core.csproj" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+  <Target Name="RestoreDotnetTools" BeforeTargets="CollectPackageReferences">
+    <Message Text="Restoring dotnet tools (this might take a while depending on your internet speed and should only happen upon building your project for the first time, or after upgrading MonoGame, or clearing your nuget cache)" Importance="High" />
+    <Exec Command="dotnet tool restore" />
+  </Target>
+</Project>

+ 15 - 0
MonoGameDistortionSample/Platforms/DesktopGL/Program.cs

@@ -0,0 +1,15 @@
+using MonoGameDistortionSample.Core;
+
+internal class Program
+{
+    /// <summary>
+    /// The main entry point for the application. 
+    /// This creates an instance of your game and calls it's Run() method 
+    /// </summary>
+    /// <param name="args">Command-line arguments passed to the application.</param>
+    private static void Main(string[] args)
+    {
+        using var game = new MonoGameDistortionSampleGame();
+        game.Run();
+    }
+}

+ 43 - 0
MonoGameDistortionSample/Platforms/DesktopGL/app.manifest

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="com.companyname.MonoGameDistortionSample"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on and is
+           is designed to work with. Uncomment the appropriate elements and Windows will 
+           automatically selected the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+    </application>
+  </compatibility>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+    </windowsSettings>
+  </application>
+
+</assembly>

+ 14 - 0
MonoGameDistortionSample/Platforms/WindowsDX/.vscode/launch.json

@@ -0,0 +1,14 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "name": "C#: MonoGameDistortionSample Debug",
+        "type": "dotnet",
+        "request": "launch",
+        "projectPath": "${workspaceFolder}/MonoGameDistortionSample.WindowsDX.csproj"
+      }
+    ],
+  }

+ 31 - 0
MonoGameDistortionSample/Platforms/WindowsDX/MonoGameDistortionSample.WindowsDX.csproj

@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net8.0-windows</TargetFramework>
+    <RollForward>Major</RollForward>
+    <PublishReadyToRun>false</PublishReadyToRun>
+    <TieredCompilation>false</TieredCompilation>
+    <UseWindowsForms>true</UseWindowsForms>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <ApplicationIcon>../../MonoGameDistortionSample.Core/Content/Icon.ico</ApplicationIcon>
+    <AssemblyName>MonoGameDistortionSample</AssemblyName>
+  </PropertyGroup>
+  <ItemGroup>
+    <MonoGameContentReference Include="..\..\MonoGameDistortionSample.Core\Content\MonoGameDistortionSample.mgcb">
+      <Link>Content\MonoGameDistortionSample.mgcb</Link>
+    </MonoGameContentReference>
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Framework.WindowsDX" Version="3.8.*" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\MonoGameDistortionSample.Core\MonoGameDistortionSample.Core.csproj">
+      <ReferenceSourceTarget></ReferenceSourceTarget>
+    </ProjectReference>
+  </ItemGroup>
+  <Target Name="RestoreDotnetTools" BeforeTargets="CollectPackageReferences">
+    <Message Text="Restoring dotnet tools (this might take a while depending on your internet speed and should only happen upon building your project for the first time, or after upgrading MonoGame, or clearing your nuget cache)" Importance="High" />
+    <Exec Command="dotnet tool restore" />
+  </Target>
+</Project>

+ 21 - 0
MonoGameDistortionSample/Platforms/WindowsDX/Program.cs

@@ -0,0 +1,21 @@
+using MonoGameDistortionSample.Core;
+using System.Windows.Forms;
+
+internal class Program
+{
+    /// <summary>
+    /// The main entry point for the application on Windows.
+    /// Configures the application for high DPI awareness.
+    /// It also creates an instance of your game and calls it's Run() method 
+    /// </summary>
+    /// <param name="args">Command-line arguments passed to the application.</param>
+    private static void Main(string[] args)
+    {
+        // Configure the application to be DPI-aware for better display scaling.
+        Application.SetHighDpiMode(HighDpiMode.SystemAware);
+
+        // Create an instance of the game and start the game loop.
+        using var game = new MonoGameDistortionSampleGame();
+        game.Run();
+    }
+}

+ 42 - 0
MonoGameDistortionSample/Platforms/WindowsDX/app.manifest

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="com.companyname.MonoGameDistortionSample"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on and is
+           is designed to work with. Uncomment the appropriate elements and Windows will 
+           automatically selected the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <!-- supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" / -->
+
+      <!-- Windows 7 -->
+      <!-- supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" / -->
+
+      <!-- Windows 8 -->
+      <!-- supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" / -->
+
+      <!-- Windows 8.1 -->
+      <!-- supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" / -->
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+      <!-- Windows 11 -->
+      <supportedOS Id="{008CCA07-0A1B-4941-9610-3B04C7D9D46F}" />
+
+    </application>
+  </compatibility>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+  </application>
+
+</assembly>

+ 14 - 0
MonoGameDistortionSample/Platforms/iOS/.vscode/launch.json

@@ -0,0 +1,14 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "name": "C#: MonoGameDistortionSample Debug",
+        "type": "maui",
+        "request": "launch",
+        "preLaunchTask": "maui: Build"
+      }
+    ],
+  }

+ 116 - 0
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,116 @@
+{
+  "images" : [
+    {
+      "filename" : "icon_40x40.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_60x60.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_58x58.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_87x87.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_80x80.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_120x120.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_120x120.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "60x60"
+    },
+    {
+      "filename" : "icon_180x180.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "60x60"
+    },
+    {
+      "filename" : "icon_20x20.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_40x40.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "icon_29x29.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_58x58.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "icon_40x40.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_80x80.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "icon_76x76.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "76x76"
+    },
+    {
+      "filename" : "icon_152x152.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "76x76"
+    },
+    {
+      "filename" : "icon_167x167.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "83.5x83.5"
+    },
+    {
+      "filename" : "icon_1024x1024.png",
+      "idiom" : "ios-marketing",
+      "scale" : "1x",
+      "size" : "1024x1024"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_1024x1024.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_120x120.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_152x152.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_167x167.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_180x180.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_20x20.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_29x29.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_40x40.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_58x58.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_60x60.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_76x76.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_80x80.png


BIN
MonoGameDistortionSample/Platforms/iOS/AppIcon.xcassets/AppIcon.appiconset/icon_87x87.png


+ 6 - 0
MonoGameDistortionSample/Platforms/iOS/Entitlements.plist

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+</dict>
+</plist>

+ 32 - 0
MonoGameDistortionSample/Platforms/iOS/Info.plist

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>MinimumOSVersion</key>
+	<string>12.2</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>CFBundleName</key>
+	<string>MonoGameDistortionSample</string>
+	<key>UIRequiresFullScreen</key>
+	<true/>
+	<key>UIStatusBarHidden</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIDeviceFamily</key>
+	<array>
+		<integer>1</integer>
+		<integer>2</integer>
+	</array>
+	<key>CFBundleIdentifier</key>
+	<string>com.companyname.MonoGameDistortionSample</string>
+	<key>CFBundleDisplayName</key>
+	<string>MonoGameDistortionSample</string>
+	<key>XSAppIconAssets</key>
+	<string>AppIcon.xcassets/AppIcon.appiconset</string>
+</dict>
+</plist>

+ 27 - 0
MonoGameDistortionSample/Platforms/iOS/LaunchScreen.storyboard

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS" />
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530" />
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb" />
+                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok" />
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600" />
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" />
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite" />
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder" />
+            </objects>
+            <point key="canvasLocation" x="53" y="375" />
+        </scene>
+    </scenes>
+</document>

+ 25 - 0
MonoGameDistortionSample/Platforms/iOS/MonoGameDistortionSample.iOS.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0-ios</TargetFramework>
+    <SupportedOSPlatformVersion>12.2</SupportedOSPlatformVersion>
+    <CodesignKey>iPhone Developer</CodesignKey>
+    <Configurations>Release;Debug</Configurations>
+  </PropertyGroup>
+  <ItemGroup>
+    <MonoGameContentReference Include="..\..\MonoGameDistortionSample.Core\Content\MonoGameDistortionSample.mgcb">
+      <Link>Content\MonoGameDistortionSample.mgcb</Link>
+    </MonoGameContentReference>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\MonoGameDistortionSample.Core\MonoGameDistortionSample.Core.csproj" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Framework.iOS" Version="3.8.*" />
+  </ItemGroup>
+  <Target Name="RestoreDotnetTools" BeforeTargets="CollectPackageReferences">
+    <Message Text="Restoring dotnet tools (this might take a while depending on your internet speed and should only happen upon building your project for the first time, or after upgrading MonoGame, or clearing your nuget cache)" Importance="High" />
+    <Exec Command="dotnet tool restore" />
+  </Target>
+</Project>

+ 43 - 0
MonoGameDistortionSample/Platforms/iOS/Program.cs

@@ -0,0 +1,43 @@
+using MonoGameDistortionSample.Core;
+using Foundation;
+using UIKit;
+
+namespace MonoGameDistortionSample.iOS
+{
+    [Register("AppDelegate")]
+    internal class Program : UIApplicationDelegate
+    {
+        private static MonoGameDistortionSampleGame _game;
+
+        /// <summary>
+        /// Initializes and starts the game by creating an instance of the 
+        /// Game class and calls its Run method.
+        /// </summary>
+        internal static void RunGame()
+        {
+            _game = new MonoGameDistortionSampleGame();
+            _game.Run();
+        }
+
+        /// <summary>
+        /// Called when the application has finished launching. 
+        /// This method starts the game by calling RunGame.
+        /// </summary>
+        /// <param name="app">The UIApplication instance representing the application.</param>
+        public override void FinishedLaunching(UIApplication app)
+        {
+            RunGame();
+        }
+
+        /// <summary>
+        /// The main entry point for the application. 
+        /// This sets up the application and specifies the UIApplicationDelegate 
+        /// class to handle application lifecycle events.
+        /// </summary>
+        /// <param name="args">Command-line arguments passed to the application.</param>
+        static void Main(string[] args)
+        {
+            UIApplication.Main(args, null, typeof(Program));
+        }
+    }
+}