Pārlūkot izejas kodu

ReachGraphicsDemo update to SDK and MG 3.8.*

CartBlanche 1 nedēļu atpakaļ
vecāks
revīzija
9a14d88706
71 mainītis faili ar 3876 papildinājumiem un 2920 dzēšanām
  1. BIN
      ReachGraphicsDemo/.vs/ProjectEvaluation/reachgraphicsdemo.metadata.v9.bin
  2. BIN
      ReachGraphicsDemo/.vs/ProjectEvaluation/reachgraphicsdemo.projects.v9.bin
  3. BIN
      ReachGraphicsDemo/.vs/ProjectEvaluation/reachgraphicsdemo.strings.v9.bin
  4. BIN
      ReachGraphicsDemo/.vs/ReachGraphicsDemo/CopilotIndices/17.14.827.52834/CodeChunks.db
  5. BIN
      ReachGraphicsDemo/.vs/ReachGraphicsDemo/CopilotIndices/17.14.827.52834/SemanticSymbols.db
  6. BIN
      ReachGraphicsDemo/.vs/ReachGraphicsDemo/DesignTimeBuild/.dtbcache.v2
  7. BIN
      ReachGraphicsDemo/.vs/ReachGraphicsDemo/FileContentIndex/5d077434-f4d3-4037-afa8-42fe44878d43.vsidx
  8. BIN
      ReachGraphicsDemo/.vs/ReachGraphicsDemo/v17/.futdcache.v2
  9. 31 0
      ReachGraphicsDemo/.vs/ReachGraphicsDemo/v17/DocumentLayout.json
  10. 53 0
      ReachGraphicsDemo/.vscode/launch.json
  11. 153 0
      ReachGraphicsDemo/.vscode/tasks.json
  12. 253 246
      ReachGraphicsDemo/Core/AlphaDemo.cs
  13. 6 6
      ReachGraphicsDemo/Core/App.config
  14. 187 182
      ReachGraphicsDemo/Core/BasicDemo.cs
  15. 0 0
      ReachGraphicsDemo/Core/Content/BigFont.xnb
  16. 0 0
      ReachGraphicsDemo/Core/Content/Game.ico
  17. 0 0
      ReachGraphicsDemo/Core/Content/GameThumbnail.png
  18. 0 0
      ReachGraphicsDemo/Core/Content/background.xnb
  19. 0 0
      ReachGraphicsDemo/Core/Content/cat.xnb
  20. 0 0
      ReachGraphicsDemo/Core/Content/checker_0.xnb
  21. 0 0
      ReachGraphicsDemo/Core/Content/dude.xnb
  22. 0 0
      ReachGraphicsDemo/Core/Content/engine_diff_tex_0.xnb
  23. 0 0
      ReachGraphicsDemo/Core/Content/font.xnb
  24. 0 0
      ReachGraphicsDemo/Core/Content/grass1_0.xnb
  25. 0 0
      ReachGraphicsDemo/Core/Content/grid.xnb
  26. 0 0
      ReachGraphicsDemo/Core/Content/head_0.xnb
  27. 0 0
      ReachGraphicsDemo/Core/Content/jacket_0.xnb
  28. 0 0
      ReachGraphicsDemo/Core/Content/lightmap_0.xnb
  29. 0 0
      ReachGraphicsDemo/Core/Content/model.xnb
  30. 0 0
      ReachGraphicsDemo/Core/Content/pants_0.xnb
  31. 0 0
      ReachGraphicsDemo/Core/Content/saucer.xnb
  32. 0 0
      ReachGraphicsDemo/Core/Content/saucer_texture_0.xnb
  33. 0 0
      ReachGraphicsDemo/Core/Content/seattle_0.xnb
  34. 0 0
      ReachGraphicsDemo/Core/Content/sky.xnb
  35. 0 0
      ReachGraphicsDemo/Core/Content/tank.xnb
  36. 0 0
      ReachGraphicsDemo/Core/Content/tile1_0.xnb
  37. 0 0
      ReachGraphicsDemo/Core/Content/turret_alt_diff_tex_0.xnb
  38. 0 0
      ReachGraphicsDemo/Core/Content/upBodyC_0.xnb
  39. 53 0
      ReachGraphicsDemo/Core/DataTypes/AnimationClip.cs
  40. 212 0
      ReachGraphicsDemo/Core/DataTypes/AnimationPlayer.cs
  41. 58 0
      ReachGraphicsDemo/Core/DataTypes/Keyframe.cs
  42. 72 0
      ReachGraphicsDemo/Core/DataTypes/SkinningData.cs
  43. 434 437
      ReachGraphicsDemo/Core/DemoGame.cs
  44. 158 160
      ReachGraphicsDemo/Core/DualDemo.cs
  45. 130 133
      ReachGraphicsDemo/Core/EnvmapDemo.cs
  46. 481 0
      ReachGraphicsDemo/Core/MenuComponent.cs
  47. 198 169
      ReachGraphicsDemo/Core/MenuEntry.cs
  48. 294 279
      ReachGraphicsDemo/Core/ParticleDemo.cs
  49. 11 0
      ReachGraphicsDemo/Core/ReachGraphicsDemo.Core.csproj
  50. 160 157
      ReachGraphicsDemo/Core/SkinnedDemo.cs
  51. 67 60
      ReachGraphicsDemo/Core/Sky.cs
  52. 302 252
      ReachGraphicsDemo/Core/Tank.cs
  53. 174 169
      ReachGraphicsDemo/Core/TitleMenu.cs
  54. 0 274
      ReachGraphicsDemo/MenuComponent.cs
  55. 30 0
      ReachGraphicsDemo/Platforms/Android/AndroidManifest.xml
  56. 37 0
      ReachGraphicsDemo/Platforms/Android/ReachGraphicsDemo.Android.csproj
  57. 15 0
      ReachGraphicsDemo/Platforms/DesktopGL/Program.cs
  58. 27 0
      ReachGraphicsDemo/Platforms/DesktopGL/ReachGraphicsDemo.DesktopGL.csproj
  59. 27 0
      ReachGraphicsDemo/Platforms/Windows/Program.cs
  60. 28 0
      ReachGraphicsDemo/Platforms/Windows/ReachGraphicsDemo.Windows.csproj
  61. 0 0
      ReachGraphicsDemo/Platforms/iOS/Info.plist
  62. 14 0
      ReachGraphicsDemo/Platforms/iOS/Program.cs
  63. 31 0
      ReachGraphicsDemo/Platforms/iOS/ReachGraphicsDemo.iOS.csproj
  64. 0 70
      ReachGraphicsDemo/Program.cs
  65. 0 6
      ReachGraphicsDemo/Properties/AppManifest.xml
  66. 0 34
      ReachGraphicsDemo/Properties/AssemblyInfo.cs
  67. 0 20
      ReachGraphicsDemo/Properties/WindowsPhoneManifest.xml
  68. 138 0
      ReachGraphicsDemo/README.md
  69. 0 129
      ReachGraphicsDemo/ReachGraphicsDemo.Linux.csproj
  70. 0 137
      ReachGraphicsDemo/ReachGraphicsDemo.MacOS.csproj
  71. 42 0
      ReachGraphicsDemo/ReachGraphicsDemo.sln

BIN
ReachGraphicsDemo/.vs/ProjectEvaluation/reachgraphicsdemo.metadata.v9.bin


BIN
ReachGraphicsDemo/.vs/ProjectEvaluation/reachgraphicsdemo.projects.v9.bin


BIN
ReachGraphicsDemo/.vs/ProjectEvaluation/reachgraphicsdemo.strings.v9.bin


BIN
ReachGraphicsDemo/.vs/ReachGraphicsDemo/CopilotIndices/17.14.827.52834/CodeChunks.db


BIN
ReachGraphicsDemo/.vs/ReachGraphicsDemo/CopilotIndices/17.14.827.52834/SemanticSymbols.db


BIN
ReachGraphicsDemo/.vs/ReachGraphicsDemo/DesignTimeBuild/.dtbcache.v2


BIN
ReachGraphicsDemo/.vs/ReachGraphicsDemo/FileContentIndex/5d077434-f4d3-4037-afa8-42fe44878d43.vsidx


BIN
ReachGraphicsDemo/.vs/ReachGraphicsDemo/v17/.futdcache.v2


+ 31 - 0
ReachGraphicsDemo/.vs/ReachGraphicsDemo/v17/DocumentLayout.json

@@ -0,0 +1,31 @@
+{
+  "Version": 1,
+  "WorkspaceRootPath": "C:\\Users\\savag\\source\\repos\\CartBlanche\\MonoGame\\MonoGame-Samples\\ReachGraphicsDemo\\",
+  "Documents": [],
+  "DocumentGroupContainers": [
+    {
+      "Orientation": 0,
+      "VerticalTabListWidth": 256,
+      "DocumentGroups": [
+        {
+          "DockedWidth": 200,
+          "SelectedChildIndex": -1,
+          "Children": [
+            {
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{e506b91c-c606-466a-90a9-123d1d1e12b3}"
+            },
+            {
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
+            },
+            {
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}

+ 53 - 0
ReachGraphicsDemo/.vscode/launch.json

@@ -0,0 +1,53 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch Windows",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-windows",
+            "program": "${workspaceFolder}/bin/Debug/net8.0-windows/XnaGraphicsDemo.exe",
+            "args": [],
+            "cwd": "${workspaceFolder}",
+            "console": "internalConsole",
+            "stopAtEntry": false,
+            "requireExactSource": false
+        },
+        {
+            "name": "Launch DesktopGL",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-desktopgl",
+            "program": "${workspaceFolder}/bin/Debug/net8.0/XnaGraphicsDemo.exe",
+            "args": [],
+            "cwd": "${workspaceFolder}",
+            "console": "internalConsole",
+            "stopAtEntry": false,
+            "requireExactSource": false
+        },
+        {
+            "name": "Launch Android",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-android",
+            "program": "${workspaceFolder}/bin/Debug/net8.0-android/XnaGraphicsDemo.dll",
+            "args": [],
+            "cwd": "${workspaceFolder}",
+            "console": "internalConsole",
+            "stopAtEntry": false,
+            "requireExactSource": false
+        },
+        {
+            "name": "Launch iOS",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-ios",
+            "program": "${workspaceFolder}/bin/Debug/net8.0-ios/iossimulator-x64/XnaGraphicsDemo.dll",
+            "args": [],
+            "cwd": "${workspaceFolder}",
+            "console": "internalConsole",
+            "stopAtEntry": false,
+            "requireExactSource": false
+        }
+    ]
+}

+ 153 - 0
ReachGraphicsDemo/.vscode/tasks.json

@@ -0,0 +1,153 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "build-windows",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "${workspaceFolder}/Platforms/Windows/ReachGraphicsDemo.Windows.csproj"
+            ],
+            "group": "build",
+            "presentation": {
+                "echo": true,
+                "reveal": "silent",
+                "focus": false,
+                "panel": "shared",
+                "showReuseMessage": true,
+                "clear": false
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "build-desktopgl",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "${workspaceFolder}/Platforms/DesktopGL/ReachGraphicsDemo.DesktopGL.csproj"
+            ],
+            "group": "build",
+            "presentation": {
+                "echo": true,
+                "reveal": "silent",
+                "focus": false,
+                "panel": "shared",
+                "showReuseMessage": true,
+                "clear": false
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "build-android",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "${workspaceFolder}/Platforms/Android/ReachGraphicsDemo.Android.csproj"
+            ],
+            "group": "build",
+            "presentation": {
+                "echo": true,
+                "reveal": "silent",
+                "focus": false,
+                "panel": "shared",
+                "showReuseMessage": true,
+                "clear": false
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "run-windows",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "run",
+                "--project",
+                "${workspaceFolder}/Platforms/Windows/ReachGraphicsDemo.Windows.csproj"
+            ],
+            "group": "test",
+            "presentation": {
+                "echo": true,
+                "reveal": "always",
+                "focus": false,
+                "panel": "new"
+            },
+            "dependsOn": "build-windows"
+        },
+        {
+            "label": "run-desktopgl",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "run",
+                "--project",
+                "${workspaceFolder}/Platforms/DesktopGL/ReachGraphicsDemo.DesktopGL.csproj"
+            ],
+            "group": "test",
+            "presentation": {
+                "echo": true,
+                "reveal": "always",
+                "focus": false,
+                "panel": "new"
+            },
+            "dependsOn": "build-desktopgl"
+        },
+        {
+            "label": "build-ios",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "${workspaceFolder}/Platforms/iOS/ReachGraphicsDemo.iOS.csproj"
+            ],
+            "group": "build",
+            "presentation": {
+                "echo": true,
+                "reveal": "silent",
+                "focus": false,
+                "panel": "shared",
+                "showReuseMessage": true,
+                "clear": false
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "run-android",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "run",
+                "--project",
+                "${workspaceFolder}/Platforms/Android/ReachGraphicsDemo.Android.csproj"
+            ],
+            "group": "test",
+            "presentation": {
+                "echo": true,
+                "reveal": "always",
+                "focus": false,
+                "panel": "new"
+            },
+            "dependsOn": "build-android"
+        },
+        {
+            "label": "run-ios",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "run",
+                "--project",
+                "${workspaceFolder}/Platforms/iOS/ReachGraphicsDemo.iOS.csproj"
+            ],
+            "group": "test",
+            "presentation": {
+                "echo": true,
+                "reveal": "always",
+                "focus": false,
+                "panel": "new"
+            },
+            "dependsOn": "build-ios"
+        }
+    ]
+}

+ 253 - 246
ReachGraphicsDemo/AlphaDemo.cs → ReachGraphicsDemo/Core/AlphaDemo.cs

@@ -1,246 +1,253 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// AlphaDemo.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using SkinnedModel;
-using SimpleAnimation;
-using System.Diagnostics;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Demo shows how to use AlphaTestEffect.
-    /// </summary>
-    class AlphaDemo : MenuComponent
-    {
-        // Fields.
-        Tank tank = new Tank();
-        Model grid;
-        RenderTarget2D renderTarget;
-        AlphaTestEffect alphaTestEffect;
-
-        float cameraRotation = 0;
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public AlphaDemo(DemoGame game)
-            : base(game)
-        {
-            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
-        }
-
-
-        /// <summary>
-        /// Resets the menu state.
-        /// </summary>
-        public override void Reset()
-        {
-            cameraRotation = 0.85f;
-
-            base.Reset();
-        }
-
-
-        /// <summary>
-        /// Loads content for this demo.
-        /// </summary>
-        protected override void LoadContent()
-        {
-            tank.Load(Game.Content);
-
-            renderTarget = new RenderTarget2D(GraphicsDevice, 400, 400, false, SurfaceFormat.Color, DepthFormat.Depth24);
-
-            alphaTestEffect = new AlphaTestEffect(GraphicsDevice);
-            alphaTestEffect.AlphaFunction = CompareFunction.Greater;
-            alphaTestEffect.ReferenceAlpha = 128;
-
-            grid = Game.Content.Load<Model>("grid");
-        }
-
-
-        /// <summary>
-        /// Animates the tank model.
-        /// </summary>
-        public override void Update(GameTime gameTime)
-        {
-            tank.Animate(gameTime);
-
-            base.Update(gameTime);
-        }
-
-
-        /// <summary>
-        /// Draws the AlphaTestEffect demo.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            // Compute camera matrices.
-            float time = (float)gameTime.TotalGameTime.TotalSeconds;
-
-            Matrix tankRotation = Matrix.CreateRotationY(time * 0.15f);
-            Matrix sceneRotation = Matrix.CreateRotationY(cameraRotation);
-
-            Vector3 cameraPosition = new Vector3(1250, 250, 0);
-            Vector3 cameraTarget = new Vector3(0, -100, 0);
-
-            Matrix view = Matrix.CreateLookAt(cameraPosition,
-                                              cameraTarget,
-                                              Vector3.Up);
-
-            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
-                                                                    GraphicsDevice.Viewport.AspectRatio,
-                                                                    10,
-                                                                    10000);
-
-            // Draw a single copy of the tank model into a rendertarget.
-            DrawTankIntoRenderTarget(tankRotation, sceneRotation);
-
-            // Draw the scene background.
-            DrawTitle("alpha test effect", new Color(192, 192, 192), new Color(156, 156, 156));
-
-            GraphicsDevice.BlendState = BlendState.Opaque;
-            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
-            GraphicsDevice.DepthStencilState = DepthStencilState.None;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
-
-            grid.Draw(Matrix.CreateTranslation(0, -8, 0) * sceneRotation, view, projection);
-
-            // Draw many copies of the imposter sprite, faking the illusion of a more complex 3D scene with many tanks.
-            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;
-
-            DrawImposterSprites(tankRotation, sceneRotation, cameraPosition, cameraTarget, view, projection);
-
-            base.Draw(gameTime);
-        }
-
-
-        /// <summary>
-        /// Draws the 3D tank model into a rendertarget.
-        /// </summary>
-        void DrawTankIntoRenderTarget(Matrix tankRotation, Matrix sceneRotation)
-        {
-            Matrix view = Matrix.CreateLookAt(new Vector3(1250, 650, 0),
-                                              new Vector3(0, 0, 0),
-                                              Vector3.Up);
-
-            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
-                                                                    1,
-                                                                    10,
-                                                                    10000);
-
-            RenderTargetBinding[] previousRenderTargets = GraphicsDevice.GetRenderTargets();
-
-            GraphicsDevice.SetRenderTarget(renderTarget);
-
-            GraphicsDevice.BlendState = BlendState.Opaque;
-            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
-            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
-
-            GraphicsDevice.Clear(Color.Transparent);
-
-            tank.Draw(tankRotation * sceneRotation * Matrix.CreateScale(0.9f), view, projection, LightingMode.OneVertexLight, true);
-
-            GraphicsDevice.SetRenderTargets(previousRenderTargets);
-        }
-
-
-        /// <summary>
-        /// Draws many copies of the rendertarget as 2D billboard sprites, positioned within the 3D scene.
-        /// </summary>
-        void DrawImposterSprites(Matrix tankRotation, Matrix sceneRotation, Vector3 cameraPosition, Vector3 cameraTarget, Matrix view, Matrix projection)
-        {
-            const int start = -2;
-            const int end = 3;
-            const int count = (end - start) * (end - start);
-
-            const float size = 0.2f;
-            const float spacing = 240;
-
-            const float width = 120;
-            const float height1 = 135;
-            const float height2 = -135;
-
-            // Create billboard vertices.
-            VertexPositionTexture[] vertices = new VertexPositionTexture[count * 4];
-            int i = 0;
-
-            for (int x = start; x < end; x++)
-            {
-                for (int z = start; z < end; z++)
-                {
-                    Matrix scale = Matrix.CreateScale(size);
-                    Matrix translation = Matrix.CreateTranslation(x * spacing, 0, z * spacing);
-                    Matrix world = tankRotation * scale * translation * sceneRotation;
-
-                    Matrix billboard = Matrix.CreateConstrainedBillboard(world.Translation, cameraPosition, Vector3.Up, cameraTarget - cameraPosition, null);
-
-                    vertices[i].Position = Vector3.Transform(new Vector3(width, height1, 0), billboard);
-                    vertices[i++].TextureCoordinate = new Vector2(0, 0);
-
-                    vertices[i].Position = Vector3.Transform(new Vector3(-width, height1, 0), billboard);
-                    vertices[i++].TextureCoordinate = new Vector2(1, 0);
-
-                    vertices[i].Position = Vector3.Transform(new Vector3(-width, height2, 0), billboard);
-                    vertices[i++].TextureCoordinate = new Vector2(1, 1);
-
-                    vertices[i].Position = Vector3.Transform(new Vector3(width, height2, 0), billboard);
-                    vertices[i++].TextureCoordinate = new Vector2(0, 1);
-                }
-            }
-
-            // Create billboard indices.
-            short[] indices = new short[count * 6];
-            short currentVertex = 0;
-            i = 0;
-
-            while (i < indices.Length)
-            {
-                indices[i++] = currentVertex;
-                indices[i++] = (short)(currentVertex + 1);
-                indices[i++] = (short)(currentVertex + 2);
-
-                indices[i++] = currentVertex;
-                indices[i++] = (short)(currentVertex + 2);
-                indices[i++] = (short)(currentVertex + 3);
-
-                currentVertex += 4;
-            }
-
-            // Draw the billboard sprites.
-            alphaTestEffect.World = Matrix.Identity;
-            alphaTestEffect.View = view;
-            alphaTestEffect.Projection = projection;
-            alphaTestEffect.Texture = renderTarget;
-
-            alphaTestEffect.CurrentTechnique.Passes[0].Apply();
-
-            GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, count * 4, indices, 0, count * 2);
-        }
-
-
-        /// <summary>
-        /// Dragging on the menu background rotates the camera.
-        /// </summary>
-        protected override void OnDrag(Vector2 delta)
-        {
-            cameraRotation += delta.X / 400;
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// AlphaDemo.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SkinnedModel;
+using SimpleAnimation;
+using System.Diagnostics;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Demo shows how to use AlphaTestEffect.
+    /// </summary>
+    class AlphaDemo : MenuComponent
+    {
+        // Fields.
+        Tank tank = new Tank();
+        Model grid;
+        RenderTarget2D renderTarget;
+        AlphaTestEffect alphaTestEffect;
+
+        float cameraRotation = 0;
+
+
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        public AlphaDemo(DemoGame game)
+            : base(game)
+        {
+            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
+        }
+
+
+        /// <summary>
+        /// Resets the menu state.
+        /// </summary>
+        public override void Reset()
+        {
+            cameraRotation = 0.85f;
+
+            base.Reset();
+        }
+
+
+        /// <summary>
+        /// Loads content for this demo.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            tank.Load(Game.Content);
+
+            renderTarget = new RenderTarget2D(GraphicsDevice, 400, 400, false, SurfaceFormat.Color, DepthFormat.Depth24);
+
+            alphaTestEffect = new AlphaTestEffect(GraphicsDevice);
+            alphaTestEffect.AlphaFunction = CompareFunction.Greater;
+            alphaTestEffect.ReferenceAlpha = 128;
+
+            grid = Game.Content.Load<Model>("grid");
+        }
+
+
+        /// <summary>
+        /// Animates the tank model.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Update(GameTime gameTime)
+        {
+            tank.Animate(gameTime);
+
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// Draws the AlphaTestEffect demo.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Draw(GameTime gameTime)
+        {
+            // Compute camera matrices.
+            float time = (float)gameTime.TotalGameTime.TotalSeconds;
+
+            Matrix tankRotation = Matrix.CreateRotationY(time * 0.15f);
+            Matrix sceneRotation = Matrix.CreateRotationY(cameraRotation);
+
+            Vector3 cameraPosition = new Vector3(1250, 250, 0);
+            Vector3 cameraTarget = new Vector3(0, -100, 0);
+
+            Matrix view = Matrix.CreateLookAt(cameraPosition,
+                                              cameraTarget,
+                                              Vector3.Up);
+
+            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
+                                                                    GraphicsDevice.Viewport.AspectRatio,
+                                                                    10,
+                                                                    10000);
+
+            // Draw a single copy of the tank model into a rendertarget.
+            DrawTankIntoRenderTarget(tankRotation, sceneRotation);
+
+            // Draw the scene background.
+            DrawTitle("alpha test effect", new Color(192, 192, 192), new Color(156, 156, 156));
+
+            GraphicsDevice.BlendState = BlendState.Opaque;
+            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
+            GraphicsDevice.DepthStencilState = DepthStencilState.None;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
+
+            grid.Draw(Matrix.CreateTranslation(0, -8, 0) * sceneRotation, view, projection);
+
+            // Draw many copies of the imposter sprite, faking the illusion of a more complex 3D scene with many tanks.
+            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;
+
+            DrawImposterSprites(tankRotation, sceneRotation, cameraPosition, cameraTarget, view, projection);
+
+            base.Draw(gameTime);
+        }
+
+
+        /// <summary>
+        /// Draws the 3D tank model into a rendertarget.
+        /// </summary>
+        /// <param name="tankRotation">The tank rotation matrix.</param>
+        /// <param name="sceneRotation">The scene rotation matrix.</param>
+        void DrawTankIntoRenderTarget(Matrix tankRotation, Matrix sceneRotation)
+        {
+            Matrix view = Matrix.CreateLookAt(new Vector3(1250, 650, 0),
+                                              new Vector3(0, 0, 0),
+                                              Vector3.Up);
+
+            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
+                                                                    1,
+                                                                    10,
+                                                                    10000);
+
+            RenderTargetBinding[] previousRenderTargets = GraphicsDevice.GetRenderTargets();
+
+            GraphicsDevice.SetRenderTarget(renderTarget);
+
+            GraphicsDevice.BlendState = BlendState.Opaque;
+            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
+            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
+
+            GraphicsDevice.Clear(Color.Transparent);
+
+            tank.Draw(tankRotation * sceneRotation * Matrix.CreateScale(0.9f), view, projection, LightingMode.OneVertexLight, true);
+
+            GraphicsDevice.SetRenderTargets(previousRenderTargets);
+        }
+
+
+        /// <summary>
+        /// Draws many copies of the rendertarget as 2D billboard sprites, positioned within the 3D scene.
+        /// </summary>
+        /// <param name="tankRotation">The tank rotation matrix.</param>
+        /// <param name="sceneRotation">The scene rotation matrix.</param>
+        /// <param name="cameraPosition">The camera position.</param>
+        /// <param name="cameraTarget">The camera target.</param>
+        /// <param name="view">The view matrix.</param>
+        /// <param name="projection">The projection matrix.</param>
+        void DrawImposterSprites(Matrix tankRotation, Matrix sceneRotation, Vector3 cameraPosition, Vector3 cameraTarget, Matrix view, Matrix projection)
+        {
+            const int start = -2;
+            const int end = 3;
+            const int count = (end - start) * (end - start);
+
+            const float size = 0.2f;
+            const float spacing = 240;
+
+            const float width = 120;
+            const float height1 = 135;
+            const float height2 = -135;
+
+            // Create billboard vertices.
+            VertexPositionTexture[] vertices = new VertexPositionTexture[count * 4];
+            int i = 0;
+
+            for (int x = start; x < end; x++)
+            {
+                for (int z = start; z < end; z++)
+                {
+                    Matrix scale = Matrix.CreateScale(size);
+                    Matrix translation = Matrix.CreateTranslation(x * spacing, 0, z * spacing);
+                    Matrix world = tankRotation * scale * translation * sceneRotation;
+
+                    Matrix billboard = Matrix.CreateConstrainedBillboard(world.Translation, cameraPosition, Vector3.Up, cameraTarget - cameraPosition, null);
+
+                    vertices[i].Position = Vector3.Transform(new Vector3(width, height1, 0), billboard);
+                    vertices[i++].TextureCoordinate = new Vector2(0, 0);
+
+                    vertices[i].Position = Vector3.Transform(new Vector3(-width, height1, 0), billboard);
+                    vertices[i++].TextureCoordinate = new Vector2(1, 0);
+
+                    vertices[i].Position = Vector3.Transform(new Vector3(-width, height2, 0), billboard);
+                    vertices[i++].TextureCoordinate = new Vector2(1, 1);
+
+                    vertices[i].Position = Vector3.Transform(new Vector3(width, height2, 0), billboard);
+                    vertices[i++].TextureCoordinate = new Vector2(0, 1);
+                }
+            }
+
+            // Create billboard indices.
+            short[] indices = new short[count * 6];
+            short currentVertex = 0;
+            i = 0;
+
+            while (i < indices.Length)
+            {
+                indices[i++] = currentVertex;
+                indices[i++] = (short)(currentVertex + 1);
+                indices[i++] = (short)(currentVertex + 2);
+
+                indices[i++] = currentVertex;
+                indices[i++] = (short)(currentVertex + 2);
+                indices[i++] = (short)(currentVertex + 3);
+
+                currentVertex += 4;
+            }
+
+            // Draw the billboard sprites.
+            alphaTestEffect.World = Matrix.Identity;
+            alphaTestEffect.View = view;
+            alphaTestEffect.Projection = projection;
+            alphaTestEffect.Texture = renderTarget;
+
+            alphaTestEffect.CurrentTechnique.Passes[0].Apply();
+
+            GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, count * 4, indices, 0, count * 2);
+        }
+
+
+        /// <summary>
+        /// Dragging on the menu background rotates the camera.
+        /// </summary>
+        /// <param name="delta">The amount the mouse was dragged.</param>
+        protected override void OnDrag(Vector2 delta)
+        {
+            cameraRotation += delta.X / 400;
+        }
+    }
+}

+ 6 - 6
ReachGraphicsDemo/App.config → ReachGraphicsDemo/Core/App.config

@@ -1,6 +1,6 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<configuration>
-  <startup useLegacyV2RuntimeActivationPolicy="true">
-    <supportedRuntime version="v4.0"/>
-  </startup>
-</configuration>
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+  <startup useLegacyV2RuntimeActivationPolicy="true">
+    <supportedRuntime version="v4.0"/>
+  </startup>
+</configuration>

+ 187 - 182
ReachGraphicsDemo/BasicDemo.cs → ReachGraphicsDemo/Core/BasicDemo.cs

@@ -1,182 +1,187 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// BasicDemo.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using SkinnedModel;
-using SimpleAnimation;
-using GeneratedGeometry;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Enum controls what kind of lighting to use.
-    /// </summary>
-    public enum LightingMode
-    {
-        NoLighting,
-        OneVertexLight,
-        ThreeVertexLights,
-        ThreePixelLights,
-    }
-
-    
-    /// <summary>
-    /// Demo shows how to use BasicEffect.
-    /// </summary>
-    class BasicDemo : MenuComponent
-    {
-        // Fields.
-        Model grid;
-        Tank tank = new Tank();
-        LightModeMenu lightMode;
-        BoolMenuEntry textureEnable;
-        float zoom = 1;
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public BasicDemo(DemoGame game)
-            : base(game)
-        {
-            Entries.Add(textureEnable = new BoolMenuEntry("texture"));
-            Entries.Add(lightMode = new LightModeMenu());
-            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
-        }
-
-
-        /// <summary>
-        /// Resets the menu state.
-        /// </summary>
-        public override void Reset()
-        {
-            lightMode.LightMode = LightingMode.ThreeVertexLights;
-            textureEnable.Value = true;
-            zoom = 1;
-
-            base.Reset();
-        }
-
-
-        /// <summary>
-        /// Loads content for this demo.
-        /// </summary>
-        protected override void LoadContent()
-        {
-            tank.Load(Game.Content);
-
-            grid = Game.Content.Load<Model>("grid");
-        }
-
-
-        /// <summary>
-        /// Updates the tank animation.
-        /// </summary>
-        public override void Update(GameTime gameTime)
-        {
-            tank.Animate(gameTime);
-
-            base.Update(gameTime);
-        }
-
-        
-        /// <summary>
-        /// Draws the BasicEffect demo.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            float time = (float)gameTime.TotalGameTime.TotalSeconds;
-
-            // Compute camera matrices.
-            Matrix rotation = Matrix.CreateRotationY(time * 0.1f);
-
-            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
-                                                                    GraphicsDevice.Viewport.AspectRatio,
-                                                                    10,
-                                                                    20000);
-
-            Matrix view = Matrix.CreateLookAt(new Vector3(1500, 550, 0) * zoom + new Vector3(0, 150, 0),
-                                              new Vector3(0, 150, 0),
-                                              Vector3.Up);
-
-            // Draw the title.
-            DrawTitle("basic effect", new Color(192, 192, 192), new Color(156, 156, 156));
-
-            // Set render states.
-            GraphicsDevice.BlendState = BlendState.Opaque;
-            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
-            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
-
-            // Draw the background grid.
-            grid.Draw(Matrix.CreateScale(1.5f) * rotation, view, projection);
-
-            // Draw the tank model.
-            tank.Draw(rotation, view, projection, lightMode.LightMode, textureEnable.Value);
-
-            base.Draw(gameTime);
-        }
-
-
-        /// <summary>
-        /// Dragging up and down on the menu background zooms in and out.
-        /// </summary>
-        protected override void OnDrag(Vector2 delta)
-        {
-            zoom = MathHelper.Clamp(zoom * (float)Math.Exp(delta.Y / 400), 0.4f, 6);
-        }
-
-
-        /// <summary>
-        /// Custom menu entry subclass for cycling through the different lighting options.
-        /// </summary>
-        class LightModeMenu : MenuEntry
-        {
-            public LightingMode LightMode = LightingMode.ThreeVertexLights;
-
-
-            public override void OnClicked()
-            {
-                if (LightMode == LightingMode.ThreePixelLights)
-                    LightMode = 0;
-                else
-                    LightMode++;
-
-                base.OnClicked();
-            }
-
-
-            public override string Text
-            {
-                get
-                {
-                    switch (LightMode)
-                    {
-                        case LightingMode.NoLighting:        return "no lighting";
-                        case LightingMode.OneVertexLight:    return "one vertex light";
-                        case LightingMode.ThreeVertexLights: return "three vertex lights";
-                        case LightingMode.ThreePixelLights:  return "three pixel lights";
-
-                        default:
-                            throw new NotSupportedException();
-                    }
-                }
-
-                set { }
-            }
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// BasicDemo.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SkinnedModel;
+using SimpleAnimation;
+using GeneratedGeometry;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Enum controls what kind of lighting to use.
+    /// </summary>
+    public enum LightingMode
+    {
+        NoLighting,
+        OneVertexLight,
+        ThreeVertexLights,
+        ThreePixelLights,
+    }
+
+    
+    /// <summary>
+    /// Demo shows how to use BasicEffect.
+    /// </summary>
+    class BasicDemo : MenuComponent
+    {
+        // Fields.
+        Model grid;
+        Tank tank = new Tank();
+        LightModeMenu lightMode;
+        BoolMenuEntry textureEnable;
+        float zoom = 1;
+
+
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        public BasicDemo(DemoGame game)
+            : base(game)
+        {
+            Entries.Add(textureEnable = new BoolMenuEntry("texture"));
+            Entries.Add(lightMode = new LightModeMenu());
+            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
+        }
+
+
+        /// <summary>
+        /// Resets the menu state.
+        /// </summary>
+        public override void Reset()
+        {
+            lightMode.LightMode = LightingMode.ThreeVertexLights;
+            textureEnable.Value = true;
+            zoom = 1;
+
+            base.Reset();
+        }
+
+
+        /// <summary>
+        /// Loads content for this demo.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            tank.Load(Game.Content);
+
+            grid = Game.Content.Load<Model>("grid");
+        }
+
+
+        /// <summary>
+        /// Updates the tank animation.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Update(GameTime gameTime)
+        {
+            tank.Animate(gameTime);
+
+            base.Update(gameTime);
+        }
+
+        
+        /// <summary>
+        /// Draws the BasicEffect demo.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Draw(GameTime gameTime)
+        {
+            float time = (float)gameTime.TotalGameTime.TotalSeconds;
+
+            // Compute camera matrices.
+            Matrix rotation = Matrix.CreateRotationY(time * 0.1f);
+
+            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
+                                                                    GraphicsDevice.Viewport.AspectRatio,
+                                                                    10,
+                                                                    20000);
+
+            Matrix view = Matrix.CreateLookAt(new Vector3(1500, 550, 0) * zoom + new Vector3(0, 150, 0),
+                                              new Vector3(0, 150, 0),
+                                              Vector3.Up);
+
+            // Draw the title.
+            DrawTitle("basic effect", new Color(192, 192, 192), new Color(156, 156, 156));
+
+            // Set render states.
+            GraphicsDevice.BlendState = BlendState.Opaque;
+            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
+            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
+
+            // Draw the background grid.
+            grid.Draw(Matrix.CreateScale(1.5f) * rotation, view, projection);
+
+            // Draw the tank model.
+            tank.Draw(rotation, view, projection, lightMode.LightMode, textureEnable.Value);
+
+            base.Draw(gameTime);
+        }
+
+
+        /// <summary>
+        /// Dragging up and down on the menu background zooms in and out.
+        /// </summary>
+        /// <param name="delta">The amount the mouse was dragged.</param>
+        protected override void OnDrag(Vector2 delta)
+        {
+            zoom = MathHelper.Clamp(zoom * (float)Math.Exp(delta.Y / 400), 0.4f, 6);
+        }
+
+
+        /// <summary>
+        /// Custom menu entry subclass for cycling through the different lighting options.
+        /// </summary>
+        class LightModeMenu : MenuEntry
+        {
+            public LightingMode LightMode = LightingMode.ThreeVertexLights;
+
+
+            /// <summary>
+            /// Handles the click event to cycle through lighting modes.
+            /// </summary>
+            public override void OnClicked()
+            {
+                if (LightMode == LightingMode.ThreePixelLights)
+                    LightMode = 0;
+                else
+                    LightMode++;
+
+                base.OnClicked();
+            }
+
+
+            /// <summary>
+            /// Gets or sets the text for the lighting mode menu entry.
+            /// </summary>
+            public override string Text
+            {
+                get
+                {
+                    switch (LightMode)
+                    {
+                        case LightingMode.NoLighting:        return "no lighting";
+                        case LightingMode.OneVertexLight:    return "one vertex light";
+                        case LightingMode.ThreeVertexLights: return "three vertex lights";
+                        case LightingMode.ThreePixelLights:  return "three pixel lights";
+
+                        default:
+                            throw new NotSupportedException();
+                    }
+                }
+
+                set { }
+            }
+        }
+    }
+}

+ 0 - 0
ReachGraphicsDemo/Content/BigFont.xnb → ReachGraphicsDemo/Core/Content/BigFont.xnb


+ 0 - 0
ReachGraphicsDemo/Game.ico → ReachGraphicsDemo/Core/Content/Game.ico


+ 0 - 0
ReachGraphicsDemo/GameThumbnail.png → ReachGraphicsDemo/Core/Content/GameThumbnail.png


+ 0 - 0
ReachGraphicsDemo/Content/background.xnb → ReachGraphicsDemo/Core/Content/background.xnb


+ 0 - 0
ReachGraphicsDemo/Content/cat.xnb → ReachGraphicsDemo/Core/Content/cat.xnb


+ 0 - 0
ReachGraphicsDemo/Content/checker_0.xnb → ReachGraphicsDemo/Core/Content/checker_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/dude.xnb → ReachGraphicsDemo/Core/Content/dude.xnb


+ 0 - 0
ReachGraphicsDemo/Content/engine_diff_tex_0.xnb → ReachGraphicsDemo/Core/Content/engine_diff_tex_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/font.xnb → ReachGraphicsDemo/Core/Content/font.xnb


+ 0 - 0
ReachGraphicsDemo/Content/grass1_0.xnb → ReachGraphicsDemo/Core/Content/grass1_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/grid.xnb → ReachGraphicsDemo/Core/Content/grid.xnb


+ 0 - 0
ReachGraphicsDemo/Content/head_0.xnb → ReachGraphicsDemo/Core/Content/head_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/jacket_0.xnb → ReachGraphicsDemo/Core/Content/jacket_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/lightmap_0.xnb → ReachGraphicsDemo/Core/Content/lightmap_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/model.xnb → ReachGraphicsDemo/Core/Content/model.xnb


+ 0 - 0
ReachGraphicsDemo/Content/pants_0.xnb → ReachGraphicsDemo/Core/Content/pants_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/saucer.xnb → ReachGraphicsDemo/Core/Content/saucer.xnb


+ 0 - 0
ReachGraphicsDemo/Content/saucer_texture_0.xnb → ReachGraphicsDemo/Core/Content/saucer_texture_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/seattle_0.xnb → ReachGraphicsDemo/Core/Content/seattle_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/sky.xnb → ReachGraphicsDemo/Core/Content/sky.xnb


+ 0 - 0
ReachGraphicsDemo/Content/tank.xnb → ReachGraphicsDemo/Core/Content/tank.xnb


+ 0 - 0
ReachGraphicsDemo/Content/tile1_0.xnb → ReachGraphicsDemo/Core/Content/tile1_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/turret_alt_diff_tex_0.xnb → ReachGraphicsDemo/Core/Content/turret_alt_diff_tex_0.xnb


+ 0 - 0
ReachGraphicsDemo/Content/upBodyC_0.xnb → ReachGraphicsDemo/Core/Content/upBodyC_0.xnb


+ 53 - 0
ReachGraphicsDemo/Core/DataTypes/AnimationClip.cs

@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// AnimationClip.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Content;
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// An animation clip is the runtime equivalent of the
+    /// Microsoft.Xna.Framework.Content.Pipeline.Graphics.AnimationContent type.
+    /// It holds all the keyframes needed to describe a single animation.
+    /// </summary>
+    public class AnimationClip
+    {
+        /// <summary>
+        /// Constructs a new animation clip object.
+        /// </summary>
+        public AnimationClip(TimeSpan duration, List<Keyframe> keyframes)
+        {
+            Duration = duration;
+            Keyframes = keyframes;
+        }
+
+
+        /// <summary>
+        /// Private constructor for use by the XNB deserializer.
+        /// </summary>
+        private AnimationClip()
+        {
+        }
+
+
+        /// <summary>
+        /// Gets the total length of the animation.
+        /// </summary>
+        [ContentSerializer]
+        public TimeSpan Duration { get; private set; }
+
+
+        /// <summary>
+        /// Gets a combined list containing all the keyframes for all bones,
+        /// sorted by time.
+        /// </summary>
+        [ContentSerializer]
+        public List<Keyframe> Keyframes { get; private set; }
+    }
+}

+ 212 - 0
ReachGraphicsDemo/Core/DataTypes/AnimationPlayer.cs

@@ -0,0 +1,212 @@
+//-----------------------------------------------------------------------------
+// AnimationPlayer.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// The animation player is in charge of decoding bone position
+    /// matrices from an animation clip.
+    /// </summary>
+    public class AnimationPlayer
+    {
+
+
+        // Information about the currently playing animation clip.
+        AnimationClip currentClipValue;
+        TimeSpan currentTimeValue;
+        int currentKeyframe;
+
+
+        // Current animation transform matrices.
+        Matrix[] boneTransforms;
+        Matrix[] worldTransforms;
+        Matrix[] skinTransforms;
+
+
+        // Backlink to the bind pose and skeleton hierarchy data.
+        SkinningData skinningDataValue;
+
+
+
+
+        /// <summary>
+        /// Constructs a new animation player.
+        /// </summary>
+        public AnimationPlayer(SkinningData skinningData)
+        {
+            if (skinningData == null)
+                throw new ArgumentNullException("skinningData");
+
+            skinningDataValue = skinningData;
+
+            boneTransforms = new Matrix[skinningData.BindPose.Count];
+            worldTransforms = new Matrix[skinningData.BindPose.Count];
+            skinTransforms = new Matrix[skinningData.BindPose.Count];
+        }
+
+
+        /// <summary>
+        /// Starts decoding the specified animation clip.
+        /// </summary>
+        public void StartClip(AnimationClip clip)
+        {
+            if (clip == null)
+                throw new ArgumentNullException("clip");
+
+            currentClipValue = clip;
+            currentTimeValue = TimeSpan.Zero;
+            currentKeyframe = 0;
+
+            // Initialize bone transforms to the bind pose.
+            skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
+        }
+
+
+        /// <summary>
+        /// Advances the current animation position.
+        /// </summary>
+        public void Update(TimeSpan time, bool relativeToCurrentTime,
+                           Matrix rootTransform)
+        {
+            UpdateBoneTransforms(time, relativeToCurrentTime);
+            UpdateWorldTransforms(rootTransform);
+            UpdateSkinTransforms();
+        }
+
+
+        /// <summary>
+        /// Helper used by the Update method to refresh the BoneTransforms data.
+        /// </summary>
+        public void UpdateBoneTransforms(TimeSpan time, bool relativeToCurrentTime)
+        {
+            if (currentClipValue == null)
+                throw new InvalidOperationException(
+                            "AnimationPlayer.Update was called before StartClip");
+
+            // Update the animation position.
+            if (relativeToCurrentTime)
+            {
+                time += currentTimeValue;
+
+                // If we reached the end, loop back to the start.
+                while (time >= currentClipValue.Duration)
+                    time -= currentClipValue.Duration;
+            }
+
+            if ((time < TimeSpan.Zero) || (time >= currentClipValue.Duration))
+                throw new ArgumentOutOfRangeException("time");
+
+            // If the position moved backwards, reset the keyframe index.
+            if (time < currentTimeValue)
+            {
+                currentKeyframe = 0;
+                skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
+            }
+
+            currentTimeValue = time;
+
+            // Read keyframe matrices.
+            IList<Keyframe> keyframes = currentClipValue.Keyframes;
+
+            while (currentKeyframe < keyframes.Count)
+            {
+                Keyframe keyframe = keyframes[currentKeyframe];
+
+                // Stop when we've read up to the current time position.
+                if (keyframe.Time > currentTimeValue)
+                    break;
+
+                // Use this keyframe.
+                boneTransforms[keyframe.Bone] = keyframe.Transform;
+
+                currentKeyframe++;
+            }
+        }
+
+
+        /// <summary>
+        /// Helper used by the Update method to refresh the WorldTransforms data.
+        /// </summary>
+        public void UpdateWorldTransforms(Matrix rootTransform)
+        {
+            // Root bone.
+            worldTransforms[0] = boneTransforms[0] * rootTransform;
+
+            // Child bones.
+            for (int bone = 1; bone < worldTransforms.Length; bone++)
+            {
+                int parentBone = skinningDataValue.SkeletonHierarchy[bone];
+
+                worldTransforms[bone] = boneTransforms[bone] *
+                                             worldTransforms[parentBone];
+            }
+        }
+
+
+        /// <summary>
+        /// Helper used by the Update method to refresh the SkinTransforms data.
+        /// </summary>
+        public void UpdateSkinTransforms()
+        {
+            for (int bone = 0; bone < skinTransforms.Length; bone++)
+            {
+                skinTransforms[bone] = skinningDataValue.InverseBindPose[bone] *
+                                            worldTransforms[bone];
+            }
+        }
+
+
+        /// <summary>
+        /// Gets the current bone transform matrices, relative to their parent bones.
+        /// </summary>
+        public Matrix[] GetBoneTransforms()
+        {
+            return boneTransforms;
+        }
+
+
+        /// <summary>
+        /// Gets the current bone transform matrices, in absolute format.
+        /// </summary>
+        public Matrix[] GetWorldTransforms()
+        {
+            return worldTransforms;
+        }
+
+
+        /// <summary>
+        /// Gets the current bone transform matrices,
+        /// relative to the skinning bind pose.
+        /// </summary>
+        public Matrix[] GetSkinTransforms()
+        {
+            return skinTransforms;
+        }
+
+
+        /// <summary>
+        /// Gets the clip currently being decoded.
+        /// </summary>
+        public AnimationClip CurrentClip
+        {
+            get { return currentClipValue; }
+        }
+
+
+        /// <summary>
+        /// Gets the current play position.
+        /// </summary>
+        public TimeSpan CurrentTime
+        {
+            get { return currentTimeValue; }
+        }
+    }
+}

+ 58 - 0
ReachGraphicsDemo/Core/DataTypes/Keyframe.cs

@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------------
+// Keyframe.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// Describes the position of a single bone at a single point in time.
+    /// </summary>
+    public class Keyframe
+    {
+        /// <summary>
+        /// Constructs a new keyframe object.
+        /// </summary>
+        public Keyframe(int bone, TimeSpan time, Matrix transform)
+        {
+            Bone = bone;
+            Time = time;
+            Transform = transform;
+        }
+
+
+        /// <summary>
+        /// Private constructor for use by the XNB deserializer.
+        /// </summary>
+        private Keyframe()
+        {
+        }
+
+
+        /// <summary>
+        /// Gets the index of the target bone that is animated by this keyframe.
+        /// </summary>
+        [ContentSerializer]
+        public int Bone { get; private set; }
+
+
+        /// <summary>
+        /// Gets the time offset from the start of the animation to this keyframe.
+        /// </summary>
+        [ContentSerializer]
+        public TimeSpan Time { get; private set; }
+
+
+        /// <summary>
+        /// Gets the bone transform for this keyframe.
+        /// </summary>
+        [ContentSerializer]
+        public Matrix Transform { get; private set; }
+    }
+}

+ 72 - 0
ReachGraphicsDemo/Core/DataTypes/SkinningData.cs

@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// SkinningData.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// Combines all the data needed to render and animate a skinned object.
+    /// This is typically stored in the Tag property of the Model being animated.
+    /// </summary>
+    public class SkinningData
+    {
+        /// <summary>
+        /// Constructs a new skinning data object.
+        /// </summary>
+        public SkinningData(Dictionary<string, AnimationClip> animationClips,
+                            List<Matrix> bindPose, List<Matrix> inverseBindPose,
+                            List<int> skeletonHierarchy)
+        {
+            AnimationClips = animationClips;
+            BindPose = bindPose;
+            InverseBindPose = inverseBindPose;
+            SkeletonHierarchy = skeletonHierarchy;
+        }
+
+
+        /// <summary>
+        /// Private constructor for use by the XNB deserializer.
+        /// </summary>
+        private SkinningData()
+        {
+        }
+
+
+        /// <summary>
+        /// Gets a collection of animation clips. These are stored by name in a
+        /// dictionary, so there could for instance be clips for "Walk", "Run",
+        /// "JumpReallyHigh", etc.
+        /// </summary>
+        [ContentSerializer]
+        public Dictionary<string, AnimationClip> AnimationClips { get; private set; }
+
+
+        /// <summary>
+        /// Bindpose matrices for each bone in the skeleton,
+        /// relative to the parent bone.
+        /// </summary>
+        [ContentSerializer]
+        public List<Matrix> BindPose { get; private set; }
+
+
+        /// <summary>
+        /// Vertex to bonespace transforms for each bone in the skeleton.
+        /// </summary>
+        [ContentSerializer]
+        public List<Matrix> InverseBindPose { get; private set; }
+
+
+        /// <summary>
+        /// For each bone in the skeleton, stores the index of the parent bone.
+        /// </summary>
+        [ContentSerializer]
+        public List<int> SkeletonHierarchy { get; private set; }
+    }
+}

+ 434 - 437
ReachGraphicsDemo/DemoGame.cs → ReachGraphicsDemo/Core/DemoGame.cs

@@ -1,437 +1,434 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// DemoGame.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Audio;
-using Microsoft.Xna.Framework.Content;
-using Microsoft.Xna.Framework.GamerServices;
-using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Input;
-using Microsoft.Xna.Framework.Input.Touch;
-using Microsoft.Xna.Framework.Media;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// The main game class.
-    /// </summary>
-    public class DemoGame : Microsoft.Xna.Framework.Game
-    {
-        // Constants.
-        const float TransitionSpeed = 1.5f;
-        const float ZoomyTextLifespan = 0.75f;
-
-
-        // Properties.
-        public GraphicsDeviceManager Graphics { get; private set; }
-        public SpriteBatch SpriteBatch { get; private set; }
-        public SpriteFont Font { get; private set; }
-        public SpriteFont BigFont { get; private set; }
-        public Texture2D BlankTexture { get; private set; }
-        public Matrix ScaleMatrix { get; private set; }
-
-
-        // Fields.
-        List<MenuComponent> menuComponents = new List<MenuComponent>();
-
-        GameTime currentGameTime;
-
-
-        // Transition effects provide swooshy crossfades when moving from one screen to another.
-        float transitionTimer = float.MaxValue;
-        int transitionMode;
-        RenderTarget2D transitionRenderTarget;
-
-
-        // Zoomy text provides visual feedback when selecting menu items.
-        // This is implemented by the main game, rather than any individual menu
-        // screen, because the zoomy effect from selecting a menu item needs to
-        // display across the transition while that menu makes way for a new one.
-        class ZoomyText
-        {
-            public string Text;
-            public Vector2 Position;
-            public float Age;
-        }
-
-        static List<ZoomyText> zoomyTexts = new List<ZoomyText>();
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public DemoGame()
-        {
-            Content.RootDirectory = "Content";
-
-            Graphics = new GraphicsDeviceManager(this);
-
-            Graphics.PreferredBackBufferWidth = 480;
-            Graphics.PreferredBackBufferHeight = 800;
-
-#if WINDOWS_PHONE
-            Graphics.IsFullScreen = true;
-#else
-            IsMouseVisible = true;
-#endif
-
-            TargetElapsedTime = TimeSpan.FromSeconds(1 / 30.0);
-
-            // Create all the different menu screens.
-            menuComponents.Add(new TitleMenu(this));
-            menuComponents.Add(new BasicDemo(this));
-            menuComponents.Add(new DualDemo(this));
-            menuComponents.Add(new AlphaDemo(this));
-            menuComponents.Add(new SkinnedDemo(this));
-            menuComponents.Add(new EnvmapDemo(this));
-            menuComponents.Add(new ParticleDemo(this));
-
-            // Set all the menu screens except the first to hidden and inactive. 
-            foreach (MenuComponent component in menuComponents)
-            {
-                component.Enabled = component.Visible = false;
-
-                Components.Add(component);
-            }
-
-            // Make the title menu active and visible.
-            menuComponents[0].Enabled = menuComponents[0].Visible = true;
-        }
-
-
-        /// <summary>
-        /// Changes which menu screen is currently active.
-        /// </summary>
-        public void SetActiveMenu(int index)
-        {
-            // Trigger the transition effect.
-            for (int i = 0; i < menuComponents.Count; i++)
-            {
-                if (menuComponents[i].Visible)
-                {
-                    BeginTransition(i, index);
-                    break;
-                }
-            }
-
-            // Mark the previous menu as inactive, and the new one as active.
-            for (int i = 0; i < menuComponents.Count; i++)
-            {
-                menuComponents[i].Enabled = menuComponents[i].Visible = (i == index);
-
-                menuComponents[i].Reset();
-            }
-        }
-
-
-        /// <summary>
-        /// Loads content and creates graphics resources.
-        /// </summary>
-        protected override void LoadContent()
-        {
-            SpriteBatch = new SpriteBatch(GraphicsDevice);
-
-            Font = Content.Load<SpriteFont>("font");
-            BigFont = Content.Load<SpriteFont>("BigFont");
-
-            BlankTexture = new Texture2D(GraphicsDevice, 1, 1);
-            BlankTexture.SetData(new Color[] { Color.White });
-
-            transitionRenderTarget = new RenderTarget2D(GraphicsDevice, 480, 800, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, 0);
-        }
-
-
-        /// <summary>
-        /// Updates the transition effect and zoomy text animations.
-        /// </summary>
-        protected override void Update(GameTime gameTime)
-        {
-            currentGameTime = gameTime;
-
-            UpdateZoomyText(gameTime);
-
-            if (transitionTimer < float.MaxValue)
-                transitionTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;
-
-            // This updates game components, including the currently active menu screen.
-            base.Update(gameTime);
-        }
-
-
-        /// <summary>
-        /// Draws the game.
-        /// </summary>
-        protected override void Draw(GameTime gameTime)
-        {
-            ScaleMatrix = Matrix.CreateScale(Graphics.PreferredBackBufferWidth / 480f, Graphics.PreferredBackBufferHeight / 800f, 1);
-
-            // This draws game components, including the currently active menu screen.
-            base.Draw(gameTime);
-
-            DrawTransitionEffect();
-            DrawZoomyText();
-        }
-
-
-        /// <summary>
-        /// Begins a transition effect, capturing a copy of the current screen into the transitionRenderTarget.
-        /// </summary>
-        void BeginTransition(int oldMenuIndex, int newMenuIndex)
-        {
-            ScaleMatrix = Matrix.Identity;
-
-            GraphicsDevice.SetRenderTarget(transitionRenderTarget);
-
-            // Draw the old menu screen into the rendertarget.
-            menuComponents[oldMenuIndex].Draw(currentGameTime);
-
-            // Force the rendertarget alpha channel to fully opaque.
-            SpriteBatch.Begin(0, BlendState.Additive);
-            SpriteBatch.Draw(BlankTexture, new Rectangle(0, 0, 480, 800), new Color(0, 0, 0, 255));
-            SpriteBatch.End();
-
-            GraphicsDevice.SetRenderTarget(null);
-
-            // Initialize the transition state.
-            transitionTimer = (float)TargetElapsedTime.TotalSeconds;
-            transitionMode = newMenuIndex;
-        }
-
-
-        /// <summary>
-        /// Draws the transition effect, displaying various animating pieces of the rendertarget
-        /// which contains the previous scene image over the top of the new scene. There are
-        /// various different effects which animate these pieces in different ways.
-        /// </summary>
-        void DrawTransitionEffect()
-        {
-            if (transitionTimer >= TransitionSpeed)
-                return;
-
-            SpriteBatch.Begin();
-
-            float mu = transitionTimer / TransitionSpeed;
-            float alpha = 1 - mu;
-
-            switch (transitionMode)
-            {
-                case 1:
-                    // BasicEffect
-                    DrawOpenCurtainsTransition(alpha);
-                    break;
-
-                case 2:
-                case 5:
-                    // DualTexture
-                    // EnvironmentMap
-                    DrawSpinningSquaresTransition(mu, alpha);
-                    break;
-
-                case 3:
-                case 4:
-                    // AlphaTest and Skinning
-                    DrawChequeredAppearTransition(mu);
-                    break;
-
-                case 6:
-                    // Particles
-                    DrawFallingLinesTransition(mu);
-                    break;
-
-                default:
-                    // Returning to menu.
-                    DrawShrinkAndSpinTransition(mu, alpha);
-                    break;
-            }
-
-            SpriteBatch.End();
-        }
-
-
-        /// <summary>
-        /// Transition effect where the screen splits in half, opening down the middle.
-        /// </summary>
-        void DrawOpenCurtainsTransition(float alpha)
-        {
-            int w = (int)(240 * alpha * alpha);
-
-            SpriteBatch.Draw(transitionRenderTarget, new Rectangle(0, 0, w, 800), new Rectangle(0, 0, 240, 800), Color.White * alpha);
-            SpriteBatch.Draw(transitionRenderTarget, new Rectangle(480 - w, 0, w, 800), new Rectangle(240, 0, 240, 800), Color.White * alpha);
-        }
-
-
-        /// <summary>
-        /// Transition effect where the screen splits into pieces, each spinning off in a different direction.
-        /// </summary>
-        void DrawSpinningSquaresTransition(float mu, float alpha)
-        {
-            Random random = new Random(23);
-
-            for (int x = 0; x < 4; x++)
-            {
-                for (int y = 0; y < 8; y++)
-                {
-                    Rectangle rect = new Rectangle(480 * x / 4, 800 * y / 8, 480 / 4, 800 / 8);
-
-                    Vector2 origin = new Vector2(rect.Width, rect.Height) / 2;
-
-                    float rotation = (float)(random.NextDouble() - 0.5) * mu * mu * 2;
-                    float scale = 1 + (float)(random.NextDouble() - 0.5f) * mu * mu;
-
-                    Vector2 pos = new Vector2(rect.Center.X, rect.Center.Y);
-
-                    pos.X += (float)(random.NextDouble() - 0.5) * mu * mu * 400;
-                    pos.Y += (float)(random.NextDouble() - 0.5) * mu * mu * 400;
-
-                    SpriteBatch.Draw(transitionRenderTarget, pos, rect, Color.White * alpha, rotation, origin, scale, 0, 0);
-                }
-            }
-        }
-
-
-        /// <summary>
-        /// Transition effect where each square of the image appears at a different time.
-        /// </summary>
-        void DrawChequeredAppearTransition(float mu)
-        {
-            Random random = new Random(23);
-
-            for (int x = 0; x < 8; x++)
-            {
-                for (int y = 0; y < 16; y++)
-                {
-                    Rectangle rect = new Rectangle(480 * x / 8, 800 * y / 16, 480 / 8, 800 / 16);
-
-                    if (random.NextDouble() > mu * mu)
-                        SpriteBatch.Draw(transitionRenderTarget, rect, rect, Color.White);
-                }
-            }
-
-            // The zoomy text effect doesn't look so good with this
-            // particular transition effect, so we temporarily disable it.
-            zoomyTexts.Clear();
-        }
-
-
-        /// <summary>
-        /// Transition effect where the image dissolves into a sequence of vertically falling lines.
-        /// </summary>
-        void DrawFallingLinesTransition(float mu)
-        {
-            Random random = new Random(23);
-
-            const int segments = 60;
-
-            for (int x = 0; x < segments; x++)
-            {
-                Rectangle rect = new Rectangle(480 * x / segments, 0, 480 / segments, 800);
-
-                Vector2 pos = new Vector2(rect.X, 0);
-
-                pos.Y += 800 * (float)Math.Pow(mu, random.NextDouble() * 10);
-
-                SpriteBatch.Draw(transitionRenderTarget, pos, rect, Color.White);
-            }
-        }
-
-
-        /// <summary>
-        /// Transition effect where the image spins off toward the bottom left of the screen.
-        /// </summary>
-        void DrawShrinkAndSpinTransition(float mu, float alpha)
-        {
-            Vector2 origin = new Vector2(240, 400);
-            Vector2 translate = (new Vector2(32, 800 - 32) - origin) * mu * mu;
-
-            float rotation = mu * mu * -4;
-            float scale = alpha * alpha;
-
-            Color tint = Color.White * (float)Math.Sqrt(alpha);
-
-            SpriteBatch.Draw(transitionRenderTarget, origin + translate, null, tint, rotation, origin, scale, 0, 0);
-        }
-
-
-        /// <summary>
-        /// Creates a new zoomy text menu item selection effect.
-        /// </summary>
-        public static void SpawnZoomyText(string text, Vector2 position)
-        {
-            zoomyTexts.Add(new ZoomyText { Text = text, Position = position });
-        }
-
-
-        /// <summary>
-        /// Updates the zoomy text animations.
-        /// </summary>
-        static void UpdateZoomyText(GameTime gameTime)
-        {
-            int i = 0;
-
-            while (i < zoomyTexts.Count)
-            {
-                zoomyTexts[i].Age += (float)gameTime.ElapsedGameTime.TotalSeconds;
-
-                if (zoomyTexts[i].Age >= ZoomyTextLifespan)
-                    zoomyTexts.RemoveAt(i);
-                else
-                    i++;
-            }
-        }
-
-
-        /// <summary>
-        /// Draws the zoomy text animations.
-        /// </summary>
-        void DrawZoomyText()
-        {
-            if (zoomyTexts.Count <= 0)
-                return;
-
-            SpriteBatch.Begin(0, null, null, null, null, null, ScaleMatrix);
-
-            foreach (ZoomyText zoomyText in zoomyTexts)
-            {
-                Vector2 pos = zoomyText.Position + Font.MeasureString(zoomyText.Text) / 2;
-
-                float age = zoomyText.Age / ZoomyTextLifespan;
-                float sqrtAge = (float)Math.Sqrt(age);
-
-                float scale = 0.333f + sqrtAge * 2f;
-
-                float alpha = 1 - age;
-
-                SpriteFont font = BigFont;
-
-                // Our BigFont only contains characters a-z, so if the text
-                // contains any numbers, we have to use the other font instead.
-                foreach (char ch in zoomyText.Text)
-                {
-                    if (char.IsDigit(ch))
-                    {
-                        font = Font;
-                        scale *= 2;
-                        break;
-                    }
-                }
-
-                Vector2 origin = font.MeasureString(zoomyText.Text) / 2;
-
-                SpriteBatch.DrawString(font, zoomyText.Text, pos, Color.Lerp(new Color(64, 64, 255), Color.White, sqrtAge) * alpha, 0, origin, scale, 0, 0);
-            }
-
-            SpriteBatch.End();
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// DemoGame.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Input.Touch;
+using Microsoft.Xna.Framework.Media;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// The main game class.
+    /// </summary>
+    public class DemoGame : Game
+    {
+        // Constants.
+        const float TransitionSpeed = 1.5f;
+        const float ZoomyTextLifespan = 0.75f;
+
+
+        // Properties.
+        public GraphicsDeviceManager Graphics { get; private set; }
+        public SpriteBatch SpriteBatch { get; private set; }
+        public SpriteFont Font { get; private set; }
+        public SpriteFont BigFont { get; private set; }
+        public Texture2D BlankTexture { get; private set; }
+        public Matrix ScaleMatrix { get; private set; }
+
+
+        // Fields.
+        List<MenuComponent> menuComponents = new List<MenuComponent>();
+
+        GameTime currentGameTime;
+
+
+        // Transition effects provide swooshy crossfades when moving from one screen to another.
+        float transitionTimer = float.MaxValue;
+        int transitionMode;
+        RenderTarget2D transitionRenderTarget;
+
+
+        // Zoomy text provides visual feedback when selecting menu items.
+        // This is implemented by the main game, rather than any individual menu
+        // screen, because the zoomy effect from selecting a menu item needs to
+        // display across the transition while that menu makes way for a new one.
+        class ZoomyText
+        {
+            public string Text;
+            public Vector2 Position;
+            public float Age;
+        }
+
+        static List<ZoomyText> zoomyTexts = new List<ZoomyText>();
+
+        const int BACK_BUFFER_WIDTH = 480;
+        const int BACK_BUFFER_HEIGHT = 640;
+
+
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        public DemoGame()
+        {
+            Content.RootDirectory = "Content";
+
+            Graphics = new GraphicsDeviceManager(this);
+
+            Graphics.PreferredBackBufferWidth = BACK_BUFFER_WIDTH;
+            Graphics.PreferredBackBufferHeight = BACK_BUFFER_HEIGHT;
+
+#if MOBILE
+            Graphics.IsFullScreen = true;
+#endif
+            IsMouseVisible = true;
+
+            TargetElapsedTime = TimeSpan.FromSeconds(1 / 30.0);
+
+            // Create all the different menu screens.
+            menuComponents.Add(new TitleMenu(this));
+            menuComponents.Add(new BasicDemo(this));
+            menuComponents.Add(new DualDemo(this));
+            menuComponents.Add(new AlphaDemo(this));
+            menuComponents.Add(new SkinnedDemo(this));
+            menuComponents.Add(new EnvmapDemo(this));
+            menuComponents.Add(new ParticleDemo(this));
+
+            // Set all the menu screens except the first to hidden and inactive. 
+            foreach (MenuComponent component in menuComponents)
+            {
+                component.Enabled = component.Visible = false;
+
+                Components.Add(component);
+            }
+
+            // Make the title menu active and visible.
+            menuComponents[0].Enabled = menuComponents[0].Visible = true;
+        }
+
+
+        /// <summary>
+        /// Changes which menu screen is currently active.
+        /// </summary>
+        public void SetActiveMenu(int index)
+        {
+            // Trigger the transition effect.
+            for (int i = 0; i < menuComponents.Count; i++)
+            {
+                if (menuComponents[i].Visible)
+                {
+                    BeginTransition(i, index);
+                    break;
+                }
+            }
+
+            // Mark the previous menu as inactive, and the new one as active.
+            for (int i = 0; i < menuComponents.Count; i++)
+            {
+                menuComponents[i].Enabled = menuComponents[i].Visible = (i == index);
+
+                menuComponents[i].Reset();
+            }
+        }
+
+
+        /// <summary>
+        /// Loads content and creates graphics resources.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            SpriteBatch = new SpriteBatch(GraphicsDevice);
+
+            Font = Content.Load<SpriteFont>("font");
+            BigFont = Content.Load<SpriteFont>("BigFont");
+
+            BlankTexture = new Texture2D(GraphicsDevice, 1, 1);
+            BlankTexture.SetData(new Color[] { Color.White });
+
+            transitionRenderTarget = new RenderTarget2D(GraphicsDevice, BACK_BUFFER_WIDTH, BACK_BUFFER_HEIGHT, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, 0);
+        }
+
+
+        /// <summary>
+        /// Updates the transition effect and zoomy text animations.
+        /// </summary>
+        protected override void Update(GameTime gameTime)
+        {
+            currentGameTime = gameTime;
+
+            UpdateZoomyText(gameTime);
+
+            if (transitionTimer < float.MaxValue)
+                transitionTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;
+
+            // This updates game components, including the currently active menu screen.
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// Draws the game.
+        /// </summary>
+        protected override void Draw(GameTime gameTime)
+        {
+            ScaleMatrix = Matrix.CreateScale(Graphics.PreferredBackBufferWidth / BACK_BUFFER_WIDTH, Graphics.PreferredBackBufferHeight / BACK_BUFFER_HEIGHT, 1);
+
+            // This draws game components, including the currently active menu screen.
+            base.Draw(gameTime);
+
+            DrawTransitionEffect();
+            DrawZoomyText();
+        }
+
+
+        /// <summary>
+        /// Begins a transition effect, capturing a copy of the current screen into the transitionRenderTarget.
+        /// </summary>
+        void BeginTransition(int oldMenuIndex, int newMenuIndex)
+        {
+            ScaleMatrix = Matrix.Identity;
+
+            GraphicsDevice.SetRenderTarget(transitionRenderTarget);
+
+            // Draw the old menu screen into the rendertarget.
+            menuComponents[oldMenuIndex].Draw(currentGameTime);
+
+            // Force the rendertarget alpha channel to fully opaque.
+            SpriteBatch.Begin(0, BlendState.Additive);
+            SpriteBatch.Draw(BlankTexture, new Rectangle(0, 0, BACK_BUFFER_WIDTH, BACK_BUFFER_HEIGHT), new Color(0, 0, 0, 255));
+            SpriteBatch.End();
+
+            GraphicsDevice.SetRenderTarget(null);
+
+            // Initialize the transition state.
+            transitionTimer = (float)TargetElapsedTime.TotalSeconds;
+            transitionMode = newMenuIndex;
+        }
+
+
+        /// <summary>
+        /// Draws the transition effect, displaying various animating pieces of the rendertarget
+        /// which contains the previous scene image over the top of the new scene. There are
+        /// various different effects which animate these pieces in different ways.
+        /// </summary>
+        void DrawTransitionEffect()
+        {
+            if (transitionTimer >= TransitionSpeed)
+                return;
+
+            SpriteBatch.Begin();
+
+            float mu = transitionTimer / TransitionSpeed;
+            float alpha = 1 - mu;
+
+            switch (transitionMode)
+            {
+                case 1:
+                    // BasicEffect
+                    DrawOpenCurtainsTransition(alpha);
+                    break;
+
+                case 2:
+                case 5:
+                    // DualTexture
+                    // EnvironmentMap
+                    DrawSpinningSquaresTransition(mu, alpha);
+                    break;
+
+                case 3:
+                case 4:
+                    // AlphaTest and Skinning
+                    DrawChequeredAppearTransition(mu);
+                    break;
+
+                case 6:
+                    // Particles
+                    DrawFallingLinesTransition(mu);
+                    break;
+
+                default:
+                    // Returning to menu.
+                    DrawShrinkAndSpinTransition(mu, alpha);
+                    break;
+            }
+
+            SpriteBatch.End();
+        }
+
+
+        /// <summary>
+        /// Transition effect where the screen splits in half, opening down the middle.
+        /// </summary>
+        void DrawOpenCurtainsTransition(float alpha)
+        {
+            int w = (int)(BACK_BUFFER_WIDTH / 2 * alpha * alpha);
+
+            SpriteBatch.Draw(transitionRenderTarget, new Rectangle(0, 0, w, BACK_BUFFER_HEIGHT), new Rectangle(0, 0, BACK_BUFFER_WIDTH / 2, BACK_BUFFER_HEIGHT), Color.White * alpha);
+            SpriteBatch.Draw(transitionRenderTarget, new Rectangle(BACK_BUFFER_WIDTH - w, 0, w, BACK_BUFFER_HEIGHT), new Rectangle(BACK_BUFFER_WIDTH / 2, 0, BACK_BUFFER_WIDTH / 2, BACK_BUFFER_HEIGHT), Color.White * alpha);
+        }
+
+
+        /// <summary>
+        /// Transition effect where the screen splits into pieces, each spinning off in a different direction.
+        /// </summary>
+        void DrawSpinningSquaresTransition(float mu, float alpha)
+        {
+            Random random = new Random(23);
+
+            for (int x = 0; x < 4; x++)
+            {
+                for (int y = 0; y < 8; y++)
+                {
+                    Rectangle rect = new Rectangle(BACK_BUFFER_WIDTH * x / 4, BACK_BUFFER_HEIGHT * y / 8, BACK_BUFFER_WIDTH / 4, BACK_BUFFER_HEIGHT / 8);
+
+                    Vector2 origin = new Vector2(rect.Width, rect.Height) / 2;
+
+                    float rotation = (float)(random.NextDouble() - 0.5) * mu * mu * 2;
+                    float scale = 1 + (float)(random.NextDouble() - 0.5f) * mu * mu;
+
+                    Vector2 pos = new Vector2(rect.Center.X, rect.Center.Y);
+
+                    pos.X += (float)(random.NextDouble() - 0.5) * mu * mu * BACK_BUFFER_HEIGHT / 2;
+                    pos.Y += (float)(random.NextDouble() - 0.5) * mu * mu * BACK_BUFFER_HEIGHT / 2;
+
+                    SpriteBatch.Draw(transitionRenderTarget, pos, rect, Color.White * alpha, rotation, origin, scale, 0, 0);
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Transition effect where each square of the image appears at a different time.
+        /// </summary>
+        void DrawChequeredAppearTransition(float mu)
+        {
+            Random random = new Random(23);
+
+            for (int x = 0; x < 8; x++)
+            {
+                for (int y = 0; y < 16; y++)
+                {
+                    Rectangle rect = new Rectangle(BACK_BUFFER_WIDTH * x / 8, BACK_BUFFER_HEIGHT * y / 16, BACK_BUFFER_WIDTH / 8, BACK_BUFFER_HEIGHT / 16);
+
+                    if (random.NextDouble() > mu * mu)
+                        SpriteBatch.Draw(transitionRenderTarget, rect, rect, Color.White);
+                }
+            }
+
+            // The zoomy text effect doesn't look so good with this
+            // particular transition effect, so we temporarily disable it.
+            zoomyTexts.Clear();
+        }
+
+
+        /// <summary>
+        /// Transition effect where the image dissolves into a sequence of vertically falling lines.
+        /// </summary>
+        void DrawFallingLinesTransition(float mu)
+        {
+            Random random = new Random(23);
+
+            const int segments = 60;
+
+            for (int x = 0; x < segments; x++)
+            {
+                Rectangle rect = new Rectangle(BACK_BUFFER_WIDTH * x / segments, 0, BACK_BUFFER_WIDTH / segments, BACK_BUFFER_HEIGHT);
+
+                Vector2 pos = new Vector2(rect.X, 0);
+
+                pos.Y += BACK_BUFFER_HEIGHT * (float)Math.Pow(mu, random.NextDouble() * 10);
+
+                SpriteBatch.Draw(transitionRenderTarget, pos, rect, Color.White);
+            }
+        }
+
+
+        /// <summary>
+        /// Transition effect where the image spins off toward the bottom left of the screen.
+        /// </summary>
+        void DrawShrinkAndSpinTransition(float mu, float alpha)
+        {
+            Vector2 origin = new Vector2(BACK_BUFFER_WIDTH / 2, BACK_BUFFER_HEIGHT / 2);
+            Vector2 translate = (new Vector2(32, BACK_BUFFER_HEIGHT - 32) - origin) * mu * mu;
+
+            float rotation = mu * mu * -4;
+            float scale = alpha * alpha;
+
+            Color tint = Color.White * (float)Math.Sqrt(alpha);
+
+            SpriteBatch.Draw(transitionRenderTarget, origin + translate, null, tint, rotation, origin, scale, 0, 0);
+        }
+
+
+        /// <summary>
+        /// Creates a new zoomy text menu item selection effect.
+        /// </summary>
+        public static void SpawnZoomyText(string text, Vector2 position)
+        {
+            zoomyTexts.Add(new ZoomyText { Text = text, Position = position });
+        }
+
+
+        /// <summary>
+        /// Updates the zoomy text animations.
+        /// </summary>
+        static void UpdateZoomyText(GameTime gameTime)
+        {
+            int i = 0;
+
+            while (i < zoomyTexts.Count)
+            {
+                zoomyTexts[i].Age += (float)gameTime.ElapsedGameTime.TotalSeconds;
+
+                if (zoomyTexts[i].Age >= ZoomyTextLifespan)
+                    zoomyTexts.RemoveAt(i);
+                else
+                    i++;
+            }
+        }
+
+
+        /// <summary>
+        /// Draws the zoomy text animations.
+        /// </summary>
+        void DrawZoomyText()
+        {
+            if (zoomyTexts.Count <= 0)
+                return;
+
+            SpriteBatch.Begin(0, null, null, null, null, null, ScaleMatrix);
+
+            foreach (ZoomyText zoomyText in zoomyTexts)
+            {
+                Vector2 pos = zoomyText.Position + Font.MeasureString(zoomyText.Text) / 2;
+
+                float age = zoomyText.Age / ZoomyTextLifespan;
+                float sqrtAge = (float)Math.Sqrt(age);
+
+                float scale = 0.333f + sqrtAge * 2f;
+
+                float alpha = 1 - age;
+
+                SpriteFont font = BigFont;
+
+                // Our BigFont only contains characters a-z, so if the text
+                // contains any numbers, we have to use the other font instead.
+                foreach (char ch in zoomyText.Text)
+                {
+                    if (char.IsDigit(ch))
+                    {
+                        font = Font;
+                        scale *= 2;
+                        break;
+                    }
+                }
+
+                Vector2 origin = font.MeasureString(zoomyText.Text) / 2;
+
+                SpriteBatch.DrawString(font, zoomyText.Text, pos, Color.Lerp(new Color(64, 64, 255), Color.White, sqrtAge) * alpha, 0, origin, scale, 0, 0);
+            }
+
+            SpriteBatch.End();
+        }
+    }
+}

+ 158 - 160
ReachGraphicsDemo/DualDemo.cs → ReachGraphicsDemo/Core/DualDemo.cs

@@ -1,160 +1,158 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// DualDemo.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using SkinnedModel;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Demo shows how to use DualTextureEffect.
-    /// </summary>
-    class DualDemo : MenuComponent
-    {
-        // Fields.
-        Model model;
-
-        BoolMenuEntry showTexture;
-        BoolMenuEntry showLightmap;
-
-        Texture2D grey;
-
-        float cameraRotation = 0;
-        float cameraArc = 0;
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public DualDemo(DemoGame game)
-            : base(game)
-        {
-            Entries.Add(showTexture = new BoolMenuEntry("texture"));
-            Entries.Add(showLightmap = new BoolMenuEntry("light map"));
-            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
-        }
-
-
-        /// <summary>
-        /// Resets the menu state.
-        /// </summary>
-        public override void Reset()
-        {
-            showTexture.Value = true;
-            showLightmap.Value = true;
-
-            cameraRotation = 124;
-            cameraArc = -12;
-
-            base.Reset();
-        }
-
-
-        /// <summary>
-        /// Loads content for this demo.
-        /// </summary>
-        protected override void LoadContent()
-        {
-            model = Game.Content.Load<Model>("model");
-
-            grey = new Texture2D(GraphicsDevice, 1, 1);
-            grey.SetData(new Color[] { new Color(128, 128, 128, 255) });
-        }
-
-
-        /// <summary>
-        /// Draws the DualTextureEffect demo.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            DrawTitle("dual texture effect", new Color(128, 160, 128), new Color(96, 128, 96));
-
-            // Compute camera matrices.
-            float time = (float)gameTime.TotalGameTime.TotalSeconds;
-
-            Matrix rotation = Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
-                              Matrix.CreateRotationZ(MathHelper.ToRadians(cameraArc));
-
-            Matrix view = Matrix.CreateLookAt(new Vector3(35, 13, 0),
-                                              new Vector3(0, 3, 0),
-                                              Vector3.Up);
-
-            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
-                                                                    GraphicsDevice.Viewport.AspectRatio,
-                                                                    2, 100);
-
-            Matrix[] transforms = new Matrix[model.Bones.Count];
-
-            model.CopyAbsoluteBoneTransformsTo(transforms);
-
-            GraphicsDevice.BlendState = BlendState.Opaque;
-            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
-            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
-
-            foreach (ModelMesh mesh in model.Meshes)
-            {
-                List<Texture2D> textures = new List<Texture2D>();
-
-                foreach (DualTextureEffect effect in mesh.Effects)
-                {
-                    Matrix world = transforms[mesh.ParentBone.Index] * rotation;
-
-                    effect.World = world;
-                    effect.View = view;
-                    effect.Projection = projection;
-
-                    effect.DiffuseColor = new Vector3(0.75f);
-
-                    // Store the previous textures.
-                    textures.Add(effect.Texture);
-                    textures.Add(effect.Texture2);
-
-                    // Optionally disable one or both textures.
-                    if (!showTexture.Value)
-                        effect.Texture = grey;
-
-                    if (!showLightmap.Value)
-                        effect.Texture2 = grey;
-                }
-
-                // Draw the mesh.
-                mesh.Draw();
-
-                // Restore the original textures.
-                int i = 0;
-
-                foreach (DualTextureEffect effect in mesh.Effects)
-                {
-                    effect.Texture = textures[i++];
-                    effect.Texture2 = textures[i++];
-                }
-            }
-
-            base.Draw(gameTime);
-        }
-
-
-        /// <summary>
-        /// Dragging on the menu background rotates the camera.
-        /// </summary>
-        protected override void OnDrag(Vector2 delta)
-        {
-            cameraRotation = MathHelper.Clamp(cameraRotation + delta.X / 8, 0, 180);
-            cameraArc = MathHelper.Clamp(cameraArc - delta.Y / 8, -50, 15);
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// DualDemo.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SkinnedModel;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Demo shows how to use DualTextureEffect.
+    /// </summary>
+    class DualDemo : MenuComponent
+    {
+        // Fields.
+        Model model;
+
+        BoolMenuEntry showTexture;
+        BoolMenuEntry showLightmap;
+
+        Texture2D grey;
+
+        float cameraRotation = 0;
+        float cameraArc = 0;
+
+
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        public DualDemo(DemoGame game)
+            : base(game)
+        {
+            Entries.Add(showTexture = new BoolMenuEntry("texture"));
+            Entries.Add(showLightmap = new BoolMenuEntry("light map"));
+            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
+        }
+
+
+        /// <summary>
+        /// Resets the menu state.
+        /// </summary>
+        public override void Reset()
+        {
+            showTexture.Value = true;
+            showLightmap.Value = true;
+
+            cameraRotation = 124;
+            cameraArc = -12;
+
+            base.Reset();
+        }
+
+
+        /// <summary>
+        /// Loads content for this demo.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            model = Game.Content.Load<Model>("model");
+
+            grey = new Texture2D(GraphicsDevice, 1, 1);
+            grey.SetData(new Color[] { new Color(128, 128, 128, 255) });
+        }
+
+
+        /// <summary>
+        /// Draws the DualTextureEffect demo.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Draw(GameTime gameTime)
+        {
+            DrawTitle("dual texture effect", new Color(128, 160, 128), new Color(96, 128, 96));
+
+            // Compute camera matrices.
+            float time = (float)gameTime.TotalGameTime.TotalSeconds;
+
+            Matrix rotation = Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
+                              Matrix.CreateRotationZ(MathHelper.ToRadians(cameraArc));
+
+            Matrix view = Matrix.CreateLookAt(new Vector3(35, 13, 0),
+                                              new Vector3(0, 3, 0),
+                                              Vector3.Up);
+
+            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
+                                                                    GraphicsDevice.Viewport.AspectRatio,
+                                                                    2, 100);
+
+            Matrix[] transforms = new Matrix[model.Bones.Count];
+
+            model.CopyAbsoluteBoneTransformsTo(transforms);
+
+            GraphicsDevice.BlendState = BlendState.Opaque;
+            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
+            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
+
+            foreach (ModelMesh mesh in model.Meshes)
+            {
+                List<Texture2D> textures = new List<Texture2D>();
+
+                foreach (DualTextureEffect effect in mesh.Effects)
+                {
+                    Matrix world = transforms[mesh.ParentBone.Index] * rotation;
+
+                    effect.World = world;
+                    effect.View = view;
+                    effect.Projection = projection;
+
+                    effect.DiffuseColor = new Vector3(0.75f);
+
+                    // Store the previous textures.
+                    textures.Add(effect.Texture);
+                    textures.Add(effect.Texture2);
+
+                    // Optionally disable one or both textures.
+                    if (!showTexture.Value)
+                        effect.Texture = grey;
+
+                    if (!showLightmap.Value)
+                        effect.Texture2 = grey;
+                }
+
+                // Draw the mesh.
+                mesh.Draw();
+
+                // Restore the original textures.
+                int i = 0;
+
+                foreach (DualTextureEffect effect in mesh.Effects)
+                {
+                    effect.Texture = textures[i++];
+                    effect.Texture2 = textures[i++];
+                }
+            }
+
+            base.Draw(gameTime);
+        }
+
+
+        /// <summary>
+        /// Dragging on the menu background rotates the camera.
+        /// </summary>
+        /// <param name="delta">The amount the mouse was dragged.</param>
+        protected override void OnDrag(Vector2 delta)
+        {
+            cameraRotation = MathHelper.Clamp(cameraRotation + delta.X / 8, 0, 180);
+            cameraArc = MathHelper.Clamp(cameraArc - delta.Y / 8, -50, 15);
+        }
+    }
+}

+ 130 - 133
ReachGraphicsDemo/EnvmapDemo.cs → ReachGraphicsDemo/Core/EnvmapDemo.cs

@@ -1,133 +1,130 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// EnvmapDemo.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using SkinnedModel;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Demo shows how to use EnvironmentMapEffect.
-    /// </summary>
-    class EnvmapDemo : MenuComponent
-    {
-        // Fields.
-        Model model;
-        Texture2D background;
-
-        FloatMenuEntry amount;
-        FloatMenuEntry fresnel;
-        FloatMenuEntry specular;
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public EnvmapDemo(DemoGame game)
-            : base(game)
-        {
-            Entries.Add(amount = new FloatMenuEntry() { Text = "envmap" });
-            Entries.Add(fresnel = new FloatMenuEntry() { Text = "fresnel" });
-            Entries.Add(specular = new FloatMenuEntry() { Text = "specular" });
-            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
-        }
-
-
-        /// <summary>
-        /// Resets the menu state.
-        /// </summary>
-        public override void Reset()
-        {
-            amount.Value = 1;
-            fresnel.Value = 0.25f;
-            specular.Value = 0.5f;
-
-            base.Reset();
-        }
-
-
-        /// <summary>
-        /// Loads content for this demo.
-        /// </summary>
-        protected override void LoadContent()
-        {
-            background = Game.Content.Load<Texture2D>("background");
-            model = Game.Content.Load<Model>("saucer");
-        }
-
-
-        /// <summary>
-        /// Draws the EnvironmentMapEffect demo.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            GraphicsDevice.Clear(Color.Black);
-
-            // Draw the background image.
-            SpriteBatch.Begin(0, BlendState.Opaque);
-            SpriteBatch.Draw(background, new Rectangle(0, 0, 480, 800), Color.White);
-            SpriteBatch.End();
-
-            DrawTitle("environment map effect", null, new Color(93, 142, 196));
-
-            // Compute camera matrices.
-            float time = (float)gameTime.TotalGameTime.TotalSeconds;
-
-            Matrix rotation = Matrix.CreateRotationX(time * 0.3f) *
-                              Matrix.CreateRotationY(time);
-
-            Matrix view = Matrix.CreateLookAt(new Vector3(4500, -400, 0),
-                                              new Vector3(0, -400, 0),
-                                              Vector3.Up);
-
-            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
-                                                                    GraphicsDevice.Viewport.AspectRatio,
-                                                                    10, 10000);
-
-            Matrix[] transforms = new Matrix[model.Bones.Count];
-
-            model.CopyAbsoluteBoneTransformsTo(transforms);
-
-            GraphicsDevice.BlendState = BlendState.Opaque;
-            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
-            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap; 
-            
-            // Draw the spaceship model.
-            foreach (ModelMesh mesh in model.Meshes)
-            {
-                foreach (EnvironmentMapEffect effect in mesh.Effects)
-                {
-                    Matrix world = transforms[mesh.ParentBone.Index] * rotation;
-
-                    effect.World = world;
-                    effect.View = view;
-                    effect.Projection = projection;
-
-                    effect.EnableDefaultLighting();
-
-                    effect.EnvironmentMapAmount = amount.Value;
-                    effect.FresnelFactor = fresnel.Value * 2;
-                    effect.EnvironmentMapSpecular = new Vector3(1, 1, 0.5f) * specular.Value;
-                }
-
-                mesh.Draw();
-            }
-            
-            base.Draw(gameTime);
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// EnvmapDemo.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SkinnedModel;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Demo shows how to use EnvironmentMapEffect.
+    /// </summary>
+    class EnvmapDemo : MenuComponent
+    {
+        // Fields.
+        Model model;
+        Texture2D background;
+
+        FloatMenuEntry amount;
+        FloatMenuEntry fresnel;
+        FloatMenuEntry specular;
+
+
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        public EnvmapDemo(DemoGame game)
+            : base(game)
+        {
+            Entries.Add(amount = new FloatMenuEntry() { Text = "envmap" });
+            Entries.Add(fresnel = new FloatMenuEntry() { Text = "fresnel" });
+            Entries.Add(specular = new FloatMenuEntry() { Text = "specular" });
+            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
+        }
+
+
+        /// <summary>
+        /// Resets the menu state.
+        /// </summary>
+        public override void Reset()
+        {
+            amount.Value = 1;
+            fresnel.Value = 0.25f;
+            specular.Value = 0.5f;
+
+            base.Reset();
+        }
+
+
+        /// <summary>
+        /// Loads content for this demo.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            background = Game.Content.Load<Texture2D>("background");
+            model = Game.Content.Load<Model>("saucer");
+        }
+
+
+        /// <summary>
+        /// Draws the EnvironmentMapEffect demo.
+        /// </summary>
+        /// <param name="gameTime">Provides a snapshot of timing values.</param>
+        public override void Draw(GameTime gameTime)
+        {
+            GraphicsDevice.Clear(Color.Black);
+
+            // Draw the background image.
+            SpriteBatch.Begin(0, BlendState.Opaque);
+            SpriteBatch.Draw(background, new Rectangle(0, 0, 480, 800), Color.White);
+            SpriteBatch.End();
+
+            DrawTitle("environment map effect", null, new Color(93, 142, 196));
+
+            // Compute camera matrices.
+            float time = (float)gameTime.TotalGameTime.TotalSeconds;
+
+            Matrix rotation = Matrix.CreateRotationX(time * 0.3f) *
+                              Matrix.CreateRotationY(time);
+
+            Matrix view = Matrix.CreateLookAt(new Vector3(4500, -400, 0),
+                                              new Vector3(0, -400, 0),
+                                              Vector3.Up);
+
+            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
+                                                                    GraphicsDevice.Viewport.AspectRatio,
+                                                                    10, 10000);
+
+            Matrix[] transforms = new Matrix[model.Bones.Count];
+
+            model.CopyAbsoluteBoneTransformsTo(transforms);
+
+            GraphicsDevice.BlendState = BlendState.Opaque;
+            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
+            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap; 
+            
+            // Draw the spaceship model.
+            foreach (ModelMesh mesh in model.Meshes)
+            {
+                foreach (EnvironmentMapEffect effect in mesh.Effects)
+                {
+                    Matrix world = transforms[mesh.ParentBone.Index] * rotation;
+
+                    effect.World = world;
+                    effect.View = view;
+                    effect.Projection = projection;
+
+                    effect.EnableDefaultLighting();
+
+                    effect.EnvironmentMapAmount = amount.Value;
+                    effect.FresnelFactor = fresnel.Value * 2;
+                    effect.EnvironmentMapSpecular = new Vector3(1, 1, 0.5f) * specular.Value;
+                }
+
+                mesh.Draw();
+            }
+            
+            base.Draw(gameTime);
+        }
+    }
+}

+ 481 - 0
ReachGraphicsDemo/Core/MenuComponent.cs

@@ -0,0 +1,481 @@
+//-----------------------------------------------------------------------------
+// MenuComponent.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Base class for all the different screens used in the demo. This provides
+    /// a simple touch menu which can display a list of options, and detect when
+    /// a menu item is clicked.
+    /// </summary>
+    class MenuComponent : DrawableGameComponent
+    {
+        // Properties.
+        /// <summary>
+        /// Gets the game instance as a DemoGame.
+        /// </summary>
+        new public DemoGame Game { get { return (DemoGame)base.Game; } }
+        /// <returns>The game instance as a DemoGame.</returns>
+
+        /// <summary>
+        /// Gets the SpriteBatch used for drawing.
+        /// </summary>
+        public SpriteBatch SpriteBatch { get { return Game.SpriteBatch; } }
+        /// <returns>The SpriteBatch used for drawing.</returns>
+        /// <summary>
+        /// Gets the default font for menu text.
+        /// </summary>
+        public SpriteFont Font { get { return Game.Font; } }
+        /// <returns>The default font for menu text.</returns>
+        /// <summary>
+        /// Gets the large font for menu titles.
+        /// </summary>
+        public SpriteFont BigFont { get { return Game.BigFont; } }
+        /// <returns>The large font for menu titles.</returns>
+
+        /// <summary>
+        /// Gets the list of menu entries.
+        /// </summary>
+        protected List<MenuEntry> Entries { get; private set; }
+        /// <returns>The list of menu entries.</returns>
+
+        /// <summary>
+        /// Gets the last touch point position.
+        /// </summary>
+        protected Vector2 LastTouchPoint { get; private set; }
+        /// <returns>The last touch point position.</returns>
+
+
+        // Fields.
+        /// <summary>
+        /// Indicates whether a touch is currently active.
+        /// </summary>
+        bool touchDown = true;
+
+        /// <summary>
+        /// The index of the currently selected menu entry for navigation.
+        /// Also used in attract mode to keep everything in sync.
+        /// </summary>
+        protected int selectedEntry = 0;
+
+        // Static field to track the last selected menu item across all menus
+        /// <summary>
+        /// Tracks the last selected menu item across all menus.
+        /// </summary>
+        static int lastSelectedMenuItem = 0;
+
+        /// <summary>
+        /// Timer for attract (demo) mode inactivity.
+        /// </summary>
+        static TimeSpan attractTimer;
+        /// <summary>
+        /// Stores the last mouse input state.
+        /// </summary>
+        static MouseState lastInputState = new MouseState(-1, -1, -1, 0, 0, 0, 0, 0);
+        /// <summary>
+        /// Stores the last keyboard input state.
+        /// </summary>
+        static KeyboardState lastKeyboardState;
+
+
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        public MenuComponent(DemoGame game)
+            : base(game)
+        {
+            Entries = new List<MenuEntry>();
+        }
+
+
+        /// <summary>
+        /// Initializes the menu, computing the screen position of each entry.
+        /// </summary>
+        public override void Initialize()
+        {
+            Vector2 pos = new Vector2(MenuEntry.Border, 800 - MenuEntry.Border - Entries.Count * MenuEntry.Height);
+
+            foreach (MenuEntry entry in Entries)
+            {
+                entry.Position = pos;
+
+                pos.Y += MenuEntry.Height;
+            }
+
+            base.Initialize();
+        }
+
+
+        /// <summary>
+        /// Resets the menu, whenever we transition to or from a different screen.
+        /// </summary>
+        virtual public void Reset()
+        {
+            if (selectedEntry >= 0)
+                Entries[selectedEntry].IsFocused = false;
+
+            touchDown = true;
+
+            // Restore the last selected menu item if valid, otherwise select the first item
+            selectedEntry = (lastSelectedMenuItem >= 0 && lastSelectedMenuItem < Entries.Count)
+                              ? lastSelectedMenuItem
+                              : 0;
+
+            // Set initial keyboard focus
+            UpdateMenuFocus();
+        }
+
+
+        /// <summary>
+        /// Updates the menu state, processing user input.
+        /// </summary>
+        public override void Update(GameTime gameTime)
+        {
+            // We read input using the mouse API, which will report the first touch point
+            // when run on the phone, but also works on Windows using a regular mouse.
+            MouseState input = Game.IsActive ? Mouse.GetState() : new MouseState();
+            KeyboardState keyboardInput = Game.IsActive ? Keyboard.GetState() : new KeyboardState();
+
+            // Handle keyboard input
+            HandleKeyboardInput(keyboardInput);
+
+            // Scale input if we are running in an unusual screen resolution.
+            int touchX = input.X * 480 / Game.Graphics.PreferredBackBufferWidth;
+            int touchY = input.Y * 800 / Game.Graphics.PreferredBackBufferHeight;
+
+            // Process the input.
+            if (input.LeftButton == ButtonState.Pressed)
+            {
+                HandleTouchDown(touchX, touchY);
+            }
+            else
+            {
+                HandleTouchUp();
+            }
+
+            HandleAttractMode(gameTime, input, keyboardInput);
+        }
+
+
+        /// <summary>
+        /// Handles input while a touch is occurring.
+        /// </summary>
+        /// <summary>
+        /// Handles input while a touch is occurring, updating selection and focus.
+        /// </summary>
+        void HandleTouchDown(int touchX, int touchY)
+        {
+            // Hit test the touch position against the list of menu items.
+            int currentEntry = -1;
+
+            for (int i = 0; i < Entries.Count; i++)
+            {
+                if ((touchY >= Entries[i].Position.Y) && (touchY < Entries[i].Position.Y + MenuEntry.Height))
+                {
+                    currentEntry = i;
+                    break;
+                }
+            }
+
+            if (touchDown)
+            {
+                // Are we already processing a touch?
+                if (selectedEntry >= 0)
+                {
+                    if (currentEntry == selectedEntry || Entries[selectedEntry].IsDraggable)
+                    {
+                        // Pass drag input to the currently selected item.
+                        Entries[selectedEntry].IsFocused = true;
+
+                        Entries[selectedEntry].OnDragged(touchX - LastTouchPoint.X);
+                    }
+                    else
+                    {
+                        // If the drag moves off the selected item, unfocus it.
+                        Entries[selectedEntry].IsFocused = false;
+                    }
+                }
+                else
+                {
+                    // If the touch was not on any menu item, process a backgroun drag.
+                    OnDrag(new Vector2(touchX, touchY) - LastTouchPoint);
+                }
+            }
+            else
+            {
+                // We are not currently processing a touch.
+                touchDown = true;
+                selectedEntry = currentEntry;
+
+                // Clear keyboard focus when using touch
+                foreach (MenuEntry entry in Entries)
+                    entry.IsFocused = false;
+
+                if (selectedEntry >= 0)
+                {
+                    // Focus the menu item that has just been touched.
+                    Entries[selectedEntry].IsFocused = true;
+                }
+            }
+
+            // Store the most recent touch location.
+            LastTouchPoint = new Vector2(touchX, touchY);
+        }
+
+
+        /// <summary>
+        /// Handles input when the touch is released.
+        /// </summary>
+        /// <summary>
+        /// Handles input when the touch is released, triggering click actions if needed.
+        /// </summary>
+        void HandleTouchUp()
+        {
+            if (touchDown && selectedEntry >= 0 && Entries[selectedEntry].IsFocused)
+            {
+                // Save the current selection as the last selected menu item
+                lastSelectedMenuItem = selectedEntry;
+
+                // If we were touching a menu item, and just released it, process the click action.
+                Entries[selectedEntry].IsFocused = false;
+                Entries[selectedEntry].OnClicked();
+            }
+
+            touchDown = false;
+        }
+
+
+        /// <summary>
+        /// Checks if there's any actual keyboard activity between two keyboard states.
+        /// </summary>
+        /// <summary>
+        /// Checks if there is any keyboard activity between two keyboard states.
+        /// </summary>
+        /// <param name="current">The current keyboard state.</param>
+        /// <param name="previous">The previous keyboard state.</param>
+        /// <returns>True if there is keyboard activity; otherwise, false.</returns>
+        bool HasKeyboardActivity(KeyboardState current, KeyboardState previous)
+        {
+            // Check if any key was pressed or released
+            Keys[] currentKeys = current.GetPressedKeys();
+            Keys[] previousKeys = previous.GetPressedKeys();
+
+            // If the number of pressed keys changed, there's activity
+            if (currentKeys.Length != previousKeys.Length)
+                return true;
+
+            // Check if any different keys are pressed
+            foreach (Keys key in currentKeys)
+            {
+                if (!previous.IsKeyDown(key))
+                    return true;
+            }
+
+            foreach (Keys key in previousKeys)
+            {
+                if (!current.IsKeyDown(key))
+                    return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// If no input is provided, we go into an automatic attract mode, which cycles
+        /// through the various options. This was great for leaving the demo unattended
+        /// at the kiosk during the MIX10 conference!
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        /// <param name="input">The current mouse state.</param>
+        /// <param name="keyboardInput">The current keyboard state.</param>
+        void HandleAttractMode(GameTime gameTime, MouseState input, KeyboardState keyboardInput)
+        {
+            // Check if there's any actual keyboard activity
+            bool keyboardActivity = HasKeyboardActivity(keyboardInput, lastKeyboardState);
+
+            if (input != lastInputState || keyboardActivity || touchDown)
+            {
+                // If input has changed, reset the timer.
+                attractTimer = TimeSpan.FromSeconds(-15);
+                lastInputState = input;
+                lastKeyboardState = keyboardInput;
+            }
+            else
+            {
+                // If no input occurs, increment the timer.
+                attractTimer += gameTime.ElapsedGameTime;
+
+                if (attractTimer > AttractDelay)
+                {
+                    // Timeout! Run the attract action.
+                    attractTimer = TimeSpan.Zero;
+                    OnAttract();
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Allows subclasses to customize their attract behavior. The default is
+        /// to simulate a click on the last menu entry, which is usually "back".
+        /// </summary>
+        protected virtual void OnAttract()
+        {
+            Entries[Entries.Count - 1].OnClicked();
+        }
+
+
+        /// <summary>
+        /// Allows subclasses to customize how long they wait before cycling through the attract sequence.
+        /// </summary>
+        protected virtual TimeSpan AttractDelay { get { return TimeSpan.FromSeconds(10); } }
+
+
+        /// <summary>
+        /// Draws the list of menu entries.
+        /// </summary>
+        public override void Draw(GameTime gameTime)
+        {
+            SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
+
+            foreach (MenuEntry entry in Entries)
+            {
+                entry.Draw(SpriteBatch, Font, Game.BlankTexture);
+            }
+
+            SpriteBatch.End();
+        }
+
+
+        /// <summary>
+        /// Draws the menu title at the top of the screen, optionally with a background color.
+        /// </summary>
+        /// <param name="title">The title text to display.</param>
+        /// <param name="backgroundColor">The background color to use, or null for none.</param>
+        /// <param name="titleColor">The color to use for the title text.</param>
+        protected void DrawTitle(string title, Color? backgroundColor, Color titleColor)
+        {
+            if (backgroundColor.HasValue)
+                GraphicsDevice.Clear(backgroundColor.Value);
+
+            SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
+            SpriteBatch.DrawString(BigFont, title, new Vector2(480, 24), titleColor, MathHelper.PiOver2, Vector2.Zero, 1, 0, 0);
+            SpriteBatch.End();
+        }
+
+
+        /// <summary>
+        /// Handles a drag on the background of the screen. Subclasses can override to implement custom drag behavior.
+        /// </summary>
+        /// <param name="delta">The amount the pointer has moved since the last drag event.</param>
+        protected virtual void OnDrag(Vector2 delta)
+        {
+        }
+
+        /// <summary>
+        /// Handles keyboard input for menu navigation and selection.
+        /// </summary>
+        /// <param name="keyboardInput">The current keyboard state.</param>
+        void HandleKeyboardInput(KeyboardState keyboardInput)
+        {
+            // Check for new key presses
+            bool upPressed = keyboardInput.IsKeyDown(Keys.Up) && !lastKeyboardState.IsKeyDown(Keys.Up);
+            bool downPressed = keyboardInput.IsKeyDown(Keys.Down) && !lastKeyboardState.IsKeyDown(Keys.Down);
+            bool enterPressed = (keyboardInput.IsKeyDown(Keys.Enter) && !lastKeyboardState.IsKeyDown(Keys.Enter)) ||
+                               (keyboardInput.IsKeyDown(Keys.Space) && !lastKeyboardState.IsKeyDown(Keys.Space));
+            bool escapePressed = keyboardInput.IsKeyDown(Keys.Escape) && !lastKeyboardState.IsKeyDown(Keys.Escape);
+
+            // Handle navigation
+            if (upPressed && Entries.Count > 0)
+            {
+                // Clear touch selection and focus when using keyboard
+                if (selectedEntry >= 0)
+                    Entries[selectedEntry].IsFocused = false;
+
+
+                selectedEntry--;
+                if (selectedEntry < 0)
+                    selectedEntry = Entries.Count - 1;
+
+                UpdateMenuFocus();
+            }
+            else if (downPressed && Entries.Count > 0)
+            {
+                // Clear touch selection and focus when using keyboard
+                if (selectedEntry >= 0)
+                    Entries[selectedEntry].IsFocused = false;
+
+                selectedEntry++;
+                if (selectedEntry >= Entries.Count)
+                    selectedEntry = 0;
+
+                UpdateMenuFocus();
+            }
+            else if (enterPressed && Entries.Count > 0)
+            {
+                // Save the current selection as the last selected menu item
+                lastSelectedMenuItem = selectedEntry;
+
+                // Execute the currently selected menu item
+                Entries[selectedEntry].OnClicked();
+            }
+            else if (escapePressed)
+            {
+                // Go back - simulate clicking the last menu entry (usually "back" or "quit")
+                if (Entries.Count > 0)
+                    Entries[Entries.Count - 1].OnClicked();
+            }
+
+            lastKeyboardState = keyboardInput;
+        }
+
+        /// <summary>
+        /// Updates the focus state for keyboard navigation, ensuring only the selected entry is focused.
+        /// </summary>
+        protected void UpdateMenuFocus()
+        {
+            // Clear all focus first
+            foreach (MenuEntry entry in Entries)
+                entry.IsFocused = false;
+
+            // Set focus on the keyboard-selected item
+            if (selectedEntry >= 0 && selectedEntry < Entries.Count)
+                Entries[selectedEntry].IsFocused = true;
+        }
+
+        /// <summary>
+        /// Gets the currently selected menu item index for keyboard navigation.
+        /// </summary>
+        /// <returns>The index of the selected menu item.</returns>
+        public int GetSelectedIndex()
+        {
+            return selectedEntry;
+        }
+
+        /// <summary>
+        /// Sets the selected menu item index for keyboard navigation, updating focus accordingly.
+        /// </summary>
+        /// <param name="index">The index of the menu item to select.</param>
+        public void SetSelectedIndex(int index)
+        {
+            if (index >= 0 && index < Entries.Count)
+            {
+                selectedEntry = index;
+                lastSelectedMenuItem = index;
+                UpdateMenuFocus();
+            }
+        }
+    }
+}

+ 198 - 169
ReachGraphicsDemo/MenuEntry.cs → ReachGraphicsDemo/Core/MenuEntry.cs

@@ -1,169 +1,198 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// MenuEntry.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Input;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Base class for each entry in a MenuComponent.
-    /// </summary>
-    class MenuEntry
-    {
-        // Constants.
-        public const int Height = 64;
-        public const int Border = 32;
-
-
-        // Properties.
-        public virtual string Text { get; set; }
-        public Vector2 Position { get; set; }
-        public bool IsFocused { get; set; }
-        public bool IsDraggable { get; set; }
-        public Action Clicked { get; set; }
-
-        public Color Color { get { return IsFocused ? Color.Blue : Color.White; } }
-
-        Vector2 positionOffset;
-
-        
-        /// <summary>
-        /// Draws the menu entry.
-        /// </summary>
-        public virtual void Draw(SpriteBatch spriteBatch, SpriteFont font, Texture2D blankTexture)
-        {
-            positionOffset = new Vector2(0, (Height - font.LineSpacing) / 2);
-
-            spriteBatch.DrawString(font, Text, Position + positionOffset, Color);
-        }
-
-
-        /// <summary>
-        /// Handles clicks on this menu entry.
-        /// </summary>
-        public virtual void OnClicked()
-        {
-            // If we have a click delegate, call that now.
-            if (Clicked != null)
-                Clicked();
-
-            // If we are not draggable, spawn a visual feedback effect.
-            if (!IsDraggable)
-                DemoGame.SpawnZoomyText(Text, Position + positionOffset);
-        }
-
-
-        /// <summary>
-        /// Handles dragging this menu entry from left to right.
-        /// </summary>
-        public virtual void OnDragged(float delta)
-        {
-        }
-    }
-
-
-    /// <summary>
-    /// Menu entry subclass for boolean toggle values.
-    /// </summary>
-    class BoolMenuEntry : MenuEntry
-    {
-        // Properties.
-        public bool Value { get; set; }
-        public string Label { get; set; }
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public BoolMenuEntry(string label)
-        {
-            Label = label;
-        }
-
-
-        /// <summary>
-        /// Click handler toggles the boolean value.
-        /// </summary>
-        public override void OnClicked()
-        {
-            Value = !Value;
-
-            base.OnClicked();
-        }
-
-
-        /// <summary>
-        /// Customize our text string.
-        /// </summary>
-        public override string Text
-        {
-            get { return Label + " " + (Value ? "on" : "off"); }
-            set { }
-        }
-    }
-
-
-    /// <summary>
-    /// Menu entry subclass for floating point slider values.
-    /// </summary>
-    class FloatMenuEntry : MenuEntry
-    {
-        // Properties.
-        public float Value { get; set; }
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public FloatMenuEntry()
-        {
-            IsDraggable = true;
-        }
-
-
-        /// <summary>
-        /// Drag handler changes the slider position.
-        /// </summary>
-        public override void OnDragged(float delta)
-        {
-            const float speed = 1f / 300;
-
-            Value = MathHelper.Clamp(Value + delta * speed, 0, 1);
-        }
-
-
-        /// <summary>
-        /// Custom draw function displays a slider bar in addition to the item text.
-        /// </summary>
-        public override void Draw(SpriteBatch spriteBatch, SpriteFont font, Texture2D blankTexture)
-        {
-            base.Draw(spriteBatch, font, blankTexture);
-
-            Vector2 size = font.MeasureString(Text);
-            size.Y /= 2;
-
-            Vector2 pos = Position + size;
-
-            pos.X += 8;
-            pos.Y += (Height - font.LineSpacing) / 2;
-
-            float w = 480 - Border - pos.X;
-
-            spriteBatch.Draw(blankTexture, new Rectangle((int)pos.X, (int)pos.Y - 3, (int)(w * Value), 6), Color);
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// MenuEntry.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Base class for each entry in a MenuComponent.
+    /// </summary>
+    class MenuEntry
+    {
+        // Constants.
+        public const int Height = 64;
+        public const int Border = 32;
+
+
+        // Properties.
+        /// <summary>
+        /// Gets or sets the text displayed for this menu entry.
+        /// </summary>
+        public virtual string Text { get; set; }
+        /// <returns>The text displayed for this menu entry.</returns>
+        /// <summary>
+        /// Gets or sets the position of this menu entry on the screen.
+        /// </summary>
+        public Vector2 Position { get; set; }
+        /// <returns>The position of this menu entry on the screen.</returns>
+        /// <summary>
+        /// Gets or sets whether this menu entry is currently focused.
+        /// </summary>
+        public bool IsFocused { get; set; }
+        /// <returns>True if the menu entry is focused; otherwise, false.</returns>
+        /// <summary>
+        /// Gets or sets whether this menu entry can be dragged.
+        /// </summary>
+        public bool IsDraggable { get; set; }
+        /// <returns>True if the menu entry can be dragged; otherwise, false.</returns>
+        /// <summary>
+        /// Gets or sets the action to invoke when this menu entry is clicked.
+        /// </summary>
+        public Action Clicked { get; set; }
+        /// <returns>The action to invoke when this menu entry is clicked.</returns>
+
+        /// <summary>
+        /// Gets the color of the menu entry, blue if focused, white otherwise.
+        /// </summary>
+        public Color Color { get { return IsFocused ? Color.Blue : Color.White; } }
+        /// <returns>The color of the menu entry.</returns>
+
+        Vector2 positionOffset;
+
+        
+        /// <summary>
+        /// Draws the menu entry.
+        /// </summary>
+        /// <param name="spriteBatch">The SpriteBatch used for drawing.</param>
+        /// <param name="font">The font used to draw the text.</param>
+        /// <param name="blankTexture">A blank texture for drawing backgrounds or highlights.</param>
+        public virtual void Draw(SpriteBatch spriteBatch, SpriteFont font, Texture2D blankTexture)
+        {
+            positionOffset = new Vector2(0, (Height - font.LineSpacing) / 2);
+
+            spriteBatch.DrawString(font, Text, Position + positionOffset, Color);
+        }
+
+
+        /// <summary>
+        /// Handles clicks on this menu entry, invoking the Clicked action and spawning feedback if not draggable.
+        /// </summary>
+        public virtual void OnClicked()
+        {
+            // If we have a click delegate, call that now.
+            if (Clicked != null)
+                Clicked();
+
+            // If we are not draggable, spawn a visual feedback effect.
+            if (!IsDraggable)
+                DemoGame.SpawnZoomyText(Text, Position + positionOffset);
+        }
+
+
+        /// <summary>
+        /// Handles dragging this menu entry from left to right.
+        /// </summary>
+        /// <param name="delta">The amount the pointer has moved since the last drag event.</param>
+        public virtual void OnDragged(float delta)
+        {
+        }
+    }
+
+
+    /// <summary>
+    /// Menu entry subclass for boolean toggle values.
+    /// </summary>
+    class BoolMenuEntry : MenuEntry
+    {
+        // Properties.
+        public bool Value { get; set; }
+        public string Label { get; set; }
+
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BoolMenuEntry"/> class with the specified label.
+        /// </summary>
+        /// <param name="label">The label for the toggle entry.</param>
+        public BoolMenuEntry(string label)
+        {
+            Label = label;
+        }
+
+
+        /// <summary>
+        /// Click handler toggles the boolean value and invokes the base click logic.
+        /// </summary>
+        public override void OnClicked()
+        {
+            Value = !Value;
+
+            base.OnClicked();
+        }
+
+
+        /// <summary>
+        /// Gets the display text for the toggle entry, showing the label and current value.
+        /// </summary>
+        public override string Text
+        {
+            get { return Label + " " + (Value ? "on" : "off"); }
+            set { }
+        }
+    }
+
+
+    /// <summary>
+    /// Menu entry subclass for floating point slider values.
+    /// </summary>
+    class FloatMenuEntry : MenuEntry
+    {
+        // Properties.
+        public float Value { get; set; }
+
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FloatMenuEntry"/> class and marks it as draggable.
+        /// </summary>
+        public FloatMenuEntry()
+        {
+            IsDraggable = true;
+        }
+
+
+        /// <summary>
+        /// Drag handler changes the slider position.
+        /// </summary>
+        /// <param name="delta">The amount the pointer has moved since the last drag event.</param>
+        public override void OnDragged(float delta)
+        {
+            const float speed = 1f / 300;
+
+            Value = MathHelper.Clamp(Value + delta * speed, 0, 1);
+        }
+
+
+        /// <summary>
+        /// Custom draw function displays a slider bar in addition to the item text.
+        /// </summary>
+        /// <param name="spriteBatch">The SpriteBatch used for drawing.</param>
+        /// <param name="font">The font used to draw the text.</param>
+        /// <param name="blankTexture">A blank texture for drawing the slider bar.</param>
+        public override void Draw(SpriteBatch spriteBatch, SpriteFont font, Texture2D blankTexture)
+        {
+            base.Draw(spriteBatch, font, blankTexture);
+
+            Vector2 size = font.MeasureString(Text);
+            size.Y /= 2;
+
+            Vector2 pos = Position + size;
+
+            pos.X += 8;
+            pos.Y += (Height - font.LineSpacing) / 2;
+
+            float w = 480 - Border - pos.X;
+
+            spriteBatch.Draw(blankTexture, new Rectangle((int)pos.X, (int)pos.Y - 3, (int)(w * Value), 6), Color);
+        }
+    }
+}

+ 294 - 279
ReachGraphicsDemo/ParticleDemo.cs → ReachGraphicsDemo/Core/ParticleDemo.cs

@@ -1,279 +1,294 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// ParticleDemo.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using SkinnedModel;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Demo shows how to use SpriteBatch.
-    /// </summary>
-    class ParticleDemo : MenuComponent
-    {
-        const int MaxParticles = 5000;
-
-        struct Particle
-        {
-            public Vector2 Position;
-            public Vector2 Velocity;
-            public float Size;
-            public float Rotation;
-            public float Spin;
-            public Color Color;
-        }
-
-        Particle[] particles = new Particle[MaxParticles];
-
-        int firstParticle;
-        int particleCount;
-
-        FloatMenuEntry spawnRate;
-        float spawnCounter;
-
-        Texture2D cat;
-
-        Random random = new Random();
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public ParticleDemo(DemoGame game)
-            : base(game)
-        {
-            Entries.Add(spawnRate = new FloatMenuEntry() { Text = "spawn rate" });
-
-            // This menu option for changing the resolution is currently disabled,
-            // because the image scaler feature is not yet implemented in the CTP release.
-            /*
-            Entries.Add(new ResolutionMenu(game.graphics));
-            */
-
-            Entries.Add(new MenuEntry
-            {
-                Text = "back",
-                Clicked = delegate
-                {
-                    // Before we quit back out of this menu, reset back to the default resolution.
-                    if (game.Graphics.PreferredBackBufferWidth != 480)
-                    {
-                        game.Graphics.PreferredBackBufferWidth = 480;
-                        game.Graphics.PreferredBackBufferHeight = 800;
-
-                        game.Graphics.ApplyChanges();
-                    }
-                    
-                    Game.SetActiveMenu(0);
-            } });
-        }
-
-
-        /// <summary>
-        /// Resets the menu state.
-        /// </summary>
-        public override void Reset()
-        {
-            firstParticle = 0;
-            particleCount = 0;
-            spawnRate.Value = 0.2f;
-
-            base.Reset();
-        }
-
-
-        /// <summary>
-        /// Loads content for this demo.
-        /// </summary>
-        protected override void LoadContent()
-        {
-            cat = Game.Content.Load<Texture2D>("cat");
-        }
-
-
-        /// <summary>
-        /// Updates the particle system.
-        /// </summary>
-        public override void Update(GameTime gameTime)
-        {
-            int i = firstParticle;
-
-            for (int j = particleCount; j > 0; j--)
-            {
-                // Move a particle.
-                particles[i].Position += particles[i].Velocity;
-                particles[i].Rotation += particles[i].Spin;
-                particles[i].Velocity.Y += 0.1f;
-
-                // Retire old particles?
-                const float borderPadding = 96;
-
-                if (i == firstParticle)
-                {
-                    if ((particles[i].Position.X < -borderPadding) ||
-                        (particles[i].Position.X > 480 + borderPadding) ||
-                        (particles[i].Position.Y < -borderPadding) ||
-                        (particles[i].Position.Y > 800 + borderPadding))
-                    {
-                        if (++firstParticle >= MaxParticles)
-                            firstParticle = 0;
-
-                        particleCount--;
-                    }
-                }
-
-                if (++i >= MaxParticles)
-                    i = 0;
-            }
-
-            // Spawn new particles?
-            spawnCounter += spawnRate.Value * 10;
-
-            while (spawnCounter > 1)
-            {
-                SpawnParticle(null);
-                spawnCounter--;
-            }
-
-            base.Update(gameTime);
-        }
-
-
-        /// <summary>
-        /// Helper creates a new cat particle.
-        /// </summary>
-        void SpawnParticle(Vector2? position)
-        {
-            if (particleCount >= MaxParticles)
-                return;
-
-            int i = firstParticle + particleCount;
-
-            if (i >= MaxParticles)
-                i -= MaxParticles;
-
-            particles[i].Position = position ?? new Vector2((float)random.NextDouble() * 480, (float)random.NextDouble() * 800);
-            particles[i].Velocity = new Vector2((float)random.NextDouble() - 0.5f, (float)random.NextDouble() - 0.5f) * 10f;
-            particles[i].Size = (float)random.NextDouble() * 0.5f + 0.5f;
-            particles[i].Rotation = 0;
-            particles[i].Spin = ((float)random.NextDouble() - 0.5f) * 0.1f;
-
-            if (position.HasValue)
-            {
-                // Explicitly positioned particles have no tint.
-                particles[i].Color = Color.White;
-            }
-            else
-            {
-                // Randomly positioned particles have random tint colors.
-                byte r = (byte)(128 + random.NextDouble() * 127);
-                byte g = (byte)(128 + random.NextDouble() * 127);
-                byte b = (byte)(128 + random.NextDouble() * 127);
-
-                particles[i].Color = new Color(r, g, b);
-            }
-
-            particleCount++;
-        }
-
-
-        /// <summary>
-        /// Draws the cat particle system.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            DrawTitle("particles", Color.CornflowerBlue, Color.Lerp(Color.Blue, Color.CornflowerBlue, 0.85f));
-
-            SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
-
-            Vector2 origin = new Vector2(cat.Width, cat.Height) / 2;
-
-            int i = firstParticle + particleCount - 1;
-
-            if (i >= MaxParticles)
-                i -= MaxParticles;
-
-            for (int j = 0; j < particleCount; j++)
-            {
-                SpriteBatch.Draw(cat, particles[i].Position, null, particles[i].Color, particles[i].Rotation, origin, particles[i].Size, 0, 0);
-
-                if (--i < 0)
-                    i = MaxParticles - 1;
-            }
-
-            SpriteBatch.End();
-
-            base.Draw(gameTime);
-        }
-
-
-        /// <summary>
-        /// Dragging on the menu background creates new particles.
-        /// </summary>
-        protected override void OnDrag(Vector2 delta)
-        {
-            SpawnParticle(LastTouchPoint);
-        }
-
-
-        /// <summary>
-        /// Custom menu entry subclass for cycling through different backbuffer resolutions.
-        /// </summary>
-        class ResolutionMenu : MenuEntry
-        {
-            GraphicsDeviceManager graphics;
-
-
-            public ResolutionMenu(GraphicsDeviceManager graphics)
-            {
-                this.graphics = graphics;
-            }
-
-
-            public override string Text
-            {
-                get { return string.Format("{0}x{1}", graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); }
-                set { }
-            }
-
-
-            public override void OnClicked()
-            {
-                switch (graphics.PreferredBackBufferWidth)
-                {
-                    case 480:
-                        graphics.PreferredBackBufferWidth = 360;
-                        graphics.PreferredBackBufferHeight = 600;
-                        break;
-
-                    case 360:
-                        graphics.PreferredBackBufferWidth = 240;
-                        graphics.PreferredBackBufferHeight = 400;
-                        break;
-
-                    case 240:
-                        graphics.PreferredBackBufferWidth = 480;
-                        graphics.PreferredBackBufferHeight = 800;
-                        break;
-                }
-
-                graphics.ApplyChanges();
-
-                base.OnClicked();
-            }
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// ParticleDemo.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SkinnedModel;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Demo shows how to use SpriteBatch.
+    /// </summary>
+    class ParticleDemo : MenuComponent
+    {
+        const int MaxParticles = 5000;
+
+        /// <summary>
+        /// Represents a single particle in the particle system.
+        /// </summary>
+        struct Particle
+        {
+            public Vector2 Position;
+            public Vector2 Velocity;
+            public float Size;
+            public float Rotation;
+            public float Spin;
+            public Color Color;
+        }
+
+        Particle[] particles = new Particle[MaxParticles];
+
+        int firstParticle;
+        int particleCount;
+
+        FloatMenuEntry spawnRate;
+        float spawnCounter;
+
+        Texture2D cat;
+
+        Random random = new Random();
+
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ParticleDemo"/> class and sets up menu entries.
+        /// </summary>
+        /// <param name="game">The game instance.</param>
+        public ParticleDemo(DemoGame game)
+            : base(game)
+        {
+            Entries.Add(spawnRate = new FloatMenuEntry() { Text = "spawn rate" });
+
+            // This menu option for changing the resolution is currently disabled,
+            // because the image scaler feature is not yet implemented in the CTP release.
+            /*
+            Entries.Add(new ResolutionMenu(game.graphics));
+            */
+
+            Entries.Add(new MenuEntry
+            {
+                Text = "back",
+                Clicked = delegate
+                {
+                    // Before we quit back out of this menu, reset back to the default resolution.
+                    if (game.Graphics.PreferredBackBufferWidth != 480)
+                    {
+                        game.Graphics.PreferredBackBufferWidth = 480;
+                        game.Graphics.PreferredBackBufferHeight = 800;
+
+                        game.Graphics.ApplyChanges();
+                    }
+                    
+                    Game.SetActiveMenu(0);
+            } });
+        }
+
+
+        /// <summary>
+        /// Resets the menu state and particle system to defaults.
+        /// </summary>
+        public override void Reset()
+        {
+            firstParticle = 0;
+            particleCount = 0;
+            spawnRate.Value = 0.2f;
+
+            base.Reset();
+        }
+
+
+        /// <summary>
+        /// Loads content for this demo, including the cat texture.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            cat = Game.Content.Load<Texture2D>("cat");
+        }
+
+
+        /// <summary>
+        /// Updates the particle system and spawns new particles as needed.
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        public override void Update(GameTime gameTime)
+        {
+            int i = firstParticle;
+
+            for (int j = particleCount; j > 0; j--)
+            {
+                // Move a particle.
+                particles[i].Position += particles[i].Velocity;
+                particles[i].Rotation += particles[i].Spin;
+                particles[i].Velocity.Y += 0.1f;
+
+                // Retire old particles?
+                const float borderPadding = 96;
+
+                if (i == firstParticle)
+                {
+                    if ((particles[i].Position.X < -borderPadding) ||
+                        (particles[i].Position.X > 480 + borderPadding) ||
+                        (particles[i].Position.Y < -borderPadding) ||
+                        (particles[i].Position.Y > 800 + borderPadding))
+                    {
+                        if (++firstParticle >= MaxParticles)
+                            firstParticle = 0;
+
+                        particleCount--;
+                    }
+                }
+
+                if (++i >= MaxParticles)
+                    i = 0;
+            }
+
+            // Spawn new particles?
+            spawnCounter += spawnRate.Value * 10;
+
+            while (spawnCounter > 1)
+            {
+                SpawnParticle(null);
+                spawnCounter--;
+            }
+
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// Helper method to create a new cat particle at the given position, or at a random position if null.
+        /// </summary>
+        /// <param name="position">The position to spawn the particle, or null for random.</param>
+        void SpawnParticle(Vector2? position)
+        /// <returns>Nothing. Spawns a new particle if possible.</returns>
+        {
+            if (particleCount >= MaxParticles)
+                return;
+
+            int i = firstParticle + particleCount;
+
+            if (i >= MaxParticles)
+                i -= MaxParticles;
+
+            particles[i].Position = position ?? new Vector2((float)random.NextDouble() * 480, (float)random.NextDouble() * 800);
+            particles[i].Velocity = new Vector2((float)random.NextDouble() - 0.5f, (float)random.NextDouble() - 0.5f) * 10f;
+            particles[i].Size = (float)random.NextDouble() * 0.5f + 0.5f;
+            particles[i].Rotation = 0;
+            particles[i].Spin = ((float)random.NextDouble() - 0.5f) * 0.1f;
+
+            if (position.HasValue)
+            {
+                // Explicitly positioned particles have no tint.
+                particles[i].Color = Color.White;
+            }
+            else
+            {
+                // Randomly positioned particles have random tint colors.
+                byte r = (byte)(128 + random.NextDouble() * 127);
+                byte g = (byte)(128 + random.NextDouble() * 127);
+                byte b = (byte)(128 + random.NextDouble() * 127);
+
+                particles[i].Color = new Color(r, g, b);
+            }
+
+            particleCount++;
+        }
+
+
+        /// <summary>
+        /// Draws the cat particle system and menu.
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        public override void Draw(GameTime gameTime)
+        {
+            DrawTitle("particles", Color.CornflowerBlue, Color.Lerp(Color.Blue, Color.CornflowerBlue, 0.85f));
+
+            SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
+
+            Vector2 origin = new Vector2(cat.Width, cat.Height) / 2;
+
+            int i = firstParticle + particleCount - 1;
+
+            if (i >= MaxParticles)
+                i -= MaxParticles;
+
+            for (int j = 0; j < particleCount; j++)
+            {
+                SpriteBatch.Draw(cat, particles[i].Position, null, particles[i].Color, particles[i].Rotation, origin, particles[i].Size, 0, 0);
+
+                if (--i < 0)
+                    i = MaxParticles - 1;
+            }
+
+            SpriteBatch.End();
+
+            base.Draw(gameTime);
+        }
+
+
+        /// <summary>
+        /// Dragging on the menu background creates new particles at the last touch point.
+        /// </summary>
+        /// <param name="delta">The amount the pointer has moved since the last drag event.</param>
+        protected override void OnDrag(Vector2 delta)
+        {
+            SpawnParticle(LastTouchPoint);
+        }
+
+
+        /// <summary>
+        /// Custom menu entry subclass for cycling through different backbuffer resolutions.
+        /// </summary>
+        class ResolutionMenu : MenuEntry
+        {
+            GraphicsDeviceManager graphics;
+
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ResolutionMenu"/> class for changing resolutions.
+        /// </summary>
+        /// <param name="graphics">The graphics device manager to modify.</param>
+        public ResolutionMenu(GraphicsDeviceManager graphics)
+            {
+                this.graphics = graphics;
+            }
+
+
+            /// <summary>
+            /// Gets the display text for the current resolution.
+            /// </summary>
+            public override string Text
+            {
+                get { return string.Format("{0}x{1}", graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); }
+                set { }
+            }
+
+
+            /// <summary>
+            /// Cycles through available resolutions and applies the change.
+            /// </summary>
+            public override void OnClicked()
+            {
+                switch (graphics.PreferredBackBufferWidth)
+                {
+                    case 480:
+                        graphics.PreferredBackBufferWidth = 360;
+                        graphics.PreferredBackBufferHeight = 600;
+                        break;
+
+                    case 360:
+                        graphics.PreferredBackBufferWidth = 240;
+                        graphics.PreferredBackBufferHeight = 400;
+                        break;
+
+                    case 240:
+                        graphics.PreferredBackBufferWidth = 480;
+                        graphics.PreferredBackBufferHeight = 800;
+                        break;
+                }
+
+                graphics.ApplyChanges();
+
+                base.OnClicked();
+            }
+        }
+    }
+}

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

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <RootNamespace>XnaGraphicsDemo</RootNamespace>
+    <AssemblyName>XnaGraphicsDemo.Core</AssemblyName>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
+  </ItemGroup>
+</Project>

+ 160 - 157
ReachGraphicsDemo/SkinnedDemo.cs → ReachGraphicsDemo/Core/SkinnedDemo.cs

@@ -1,157 +1,160 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// SkinnedDemo.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using SkinnedModel;
-using GeneratedGeometry;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Demo shows how to use SkinnedEffect.
-    /// </summary>
-    class SkinnedDemo : MenuComponent
-    {
-        // Fields.
-        Sky sky;
-        Model dude;
-        AnimationPlayer animationPlayer;
-
-        float cameraRotation = 0;
-        float cameraArc = 0;
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public SkinnedDemo(DemoGame game)
-            : base(game)
-        {
-            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
-        }
-
-
-        /// <summary>
-        /// Resets the menu state.
-        /// </summary>
-        public override void Reset()
-        {
-            cameraRotation = 0;
-            cameraArc = 0;
-
-            base.Reset();
-        }
-
-
-        /// <summary>
-        /// Loads content for this demo.
-        /// </summary>
-        protected override void LoadContent()
-        {
-            sky = Game.Content.Load<Sky>("sky");
-            dude = Game.Content.Load<Model>("dude");
-
-            // Look up our custom skinning information.
-            SkinningData skinningData = dude.Tag as SkinningData;
-
-            if (skinningData == null)
-                throw new InvalidOperationException
-                    ("This model does not contain a SkinningData tag.");
-
-            // Create an animation player, and start decoding an animation clip.
-            animationPlayer = new AnimationPlayer(skinningData);
-
-            AnimationClip clip = skinningData.AnimationClips["Take 001"];
-
-            animationPlayer.StartClip(clip);
-        }
-
-
-        /// <summary>
-        /// Updates the animation.
-        /// </summary>
-        public override void Update(GameTime gameTime)
-        {
-            animationPlayer.Update(gameTime.ElapsedGameTime, true, Matrix.Identity);
-
-            base.Update(gameTime);
-        }
-
-
-        /// <summary>
-        /// Draws the SkinnedEffect demo.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            // Compute camera matrices.
-            const float cameraDistance = 100;
-
-            Matrix view = Matrix.CreateTranslation(0, -40, 0) *
-                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
-                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
-                          Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance),
-                                              new Vector3(0, 0, 0), Vector3.Up);
-
-            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
-                                                                    GraphicsDevice.Viewport.AspectRatio,
-                                                                    1,
-                                                                    10000);
-
-            // Draw the background.
-            GraphicsDevice.Clear(Color.Black);
-
-            sky.Draw(view, projection);
-
-            DrawTitle("skinned effect", null, new Color(127, 112, 104));
-
-            // Draw the animating character.
-            GraphicsDevice.BlendState = BlendState.Opaque;
-            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
-            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
-
-            Matrix[] bones = animationPlayer.GetSkinTransforms();
-
-            foreach (ModelMesh mesh in dude.Meshes)
-            {
-                foreach (SkinnedEffect effect in mesh.Effects)
-                {
-                    effect.SetBoneTransforms(bones);
-                    effect.View = view;
-                    effect.Projection = projection;
-
-                    effect.EnableDefaultLighting();
-
-                    effect.SpecularColor = Vector3.Zero;
-                }
-
-                mesh.Draw();
-            }
-
-            base.Draw(gameTime);
-        }
-
-
-        /// <summary>
-        /// Dragging on the menu background rotates the camera.
-        /// </summary>
-        protected override void OnDrag(Vector2 delta)
-        {
-            cameraRotation += delta.X / 4;
-            cameraArc = MathHelper.Clamp(cameraArc - delta.Y / 4, -70, 70);
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// SkinnedDemo.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SkinnedModel;
+using GeneratedGeometry;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// Demo shows how to use SkinnedEffect.
+    /// </summary>
+    class SkinnedDemo : MenuComponent
+    {
+        // Fields.
+        Sky sky;
+        Model dude;
+        AnimationPlayer animationPlayer;
+
+        float cameraRotation = 0;
+        float cameraArc = 0;
+
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SkinnedDemo"/> class and sets up menu entries.
+        /// </summary>
+        /// <param name="game">The game instance.</param>
+        public SkinnedDemo(DemoGame game)
+            : base(game)
+        {
+            Entries.Add(new MenuEntry { Text = "back", Clicked = delegate { Game.SetActiveMenu(0); } });
+        }
+
+
+        /// <summary>
+        /// Resets the menu state and camera rotation values.
+        /// </summary>
+        public override void Reset()
+        {
+            cameraRotation = 0;
+            cameraArc = 0;
+
+            base.Reset();
+        }
+
+
+        /// <summary>
+        /// Loads content for this demo, including the sky and animated model.
+        /// </summary>
+        /// <summary>
+        /// Loads content for this demo, including the sky and animated model.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            sky = Game.Content.Load<Sky>("sky");
+            dude = Game.Content.Load<Model>("dude");
+
+            // Look up our custom skinning information.
+            SkinningData skinningData = dude.Tag as SkinningData;
+
+            if (skinningData == null)
+                throw new InvalidOperationException
+                    ("This model does not contain a SkinningData tag.");
+
+            // Create an animation player, and start decoding an animation clip.
+            animationPlayer = new AnimationPlayer(skinningData);
+
+            AnimationClip clip = skinningData.AnimationClips["Take 001"];
+
+            animationPlayer.StartClip(clip);
+        }
+
+
+        /// <summary>
+        /// Updates the animation player and menu state.
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        public override void Update(GameTime gameTime)
+        {
+            animationPlayer.Update(gameTime.ElapsedGameTime, true, Matrix.Identity);
+
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// Draws the SkinnedEffect demo, including the sky and animated character.
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        public override void Draw(GameTime gameTime)
+        {
+            // Compute camera matrices.
+            const float cameraDistance = 100;
+
+            Matrix view = Matrix.CreateTranslation(0, -40, 0) *
+                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
+                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
+                          Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance),
+                                              new Vector3(0, 0, 0), Vector3.Up);
+
+            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
+                                                                    GraphicsDevice.Viewport.AspectRatio,
+                                                                    1,
+                                                                    10000);
+
+            // Draw the background.
+            GraphicsDevice.Clear(Color.Black);
+
+            sky.Draw(view, projection);
+
+            DrawTitle("skinned effect", null, new Color(127, 112, 104));
+
+            // Draw the animating character.
+            GraphicsDevice.BlendState = BlendState.Opaque;
+            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
+            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
+
+            Matrix[] bones = animationPlayer.GetSkinTransforms();
+
+            foreach (ModelMesh mesh in dude.Meshes)
+            {
+                foreach (SkinnedEffect effect in mesh.Effects)
+                {
+                    effect.SetBoneTransforms(bones);
+                    effect.View = view;
+                    effect.Projection = projection;
+
+                    effect.EnableDefaultLighting();
+
+                    effect.SpecularColor = Vector3.Zero;
+                }
+
+                mesh.Draw();
+            }
+
+            base.Draw(gameTime);
+        }
+
+
+        /// <summary>
+        /// Dragging on the menu background rotates the camera.
+        /// </summary>
+        /// <param name="delta">The amount the pointer has moved since the last drag event.</param>
+        protected override void OnDrag(Vector2 delta)
+        {
+            cameraRotation += delta.X / 4;
+            cameraArc = MathHelper.Clamp(cameraArc - delta.Y / 4, -70, 70);
+        }
+    }
+}

+ 67 - 60
ReachGraphicsDemo/Sky.cs → ReachGraphicsDemo/Core/Sky.cs

@@ -1,60 +1,67 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// Sky.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;
-using Microsoft.Xna.Framework.Graphics;
-#endregion
-
-namespace GeneratedGeometry
-{
-    /// <summary>
-    /// Runtime class for loading and rendering a textured skydome
-    /// that was created during the build process by the SkyProcessor.
-    /// </summary>
-    public class Sky
-    {
-        #region Fields
-
-        public Model Model;
-        public Texture2D Texture;
-
-        #endregion
-
-
-        /// <summary>
-        /// Helper for drawing the skydome mesh.
-        /// </summary>
-        public void Draw(Matrix view, Matrix projection)
-        {
-            GraphicsDevice GraphicsDevice = Texture.GraphicsDevice;
-
-            GraphicsDevice.BlendState = BlendState.Opaque;
-            GraphicsDevice.RasterizerState = RasterizerState.CullNone;
-            GraphicsDevice.DepthStencilState = DepthStencilState.None;
-            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
-
-            view.Translation = Vector3.Zero;
-
-            foreach (ModelMesh mesh in Model.Meshes)
-            {
-                foreach (BasicEffect effect in mesh.Effects)
-                {
-                    effect.View = view;
-                    effect.Projection = projection;
-                    effect.Texture = Texture;
-                    effect.TextureEnabled = true;
-                }
-
-                mesh.Draw();
-            }
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// Sky.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace GeneratedGeometry
+{
+    /// <summary>
+    /// Runtime class for loading and rendering a textured skydome
+    /// that was created during the build process by the SkyProcessor.
+    /// </summary>
+    public class Sky
+    {
+
+        /// <summary>
+        /// Gets or sets the skydome model.
+        /// </summary>
+        public Model Model;
+        /// <summary>
+        /// Gets or sets the texture applied to the skydome.
+        /// </summary>
+        public Texture2D Texture;
+
+
+
+        /// <summary>
+        /// Helper for drawing the skydome mesh with the specified view and projection matrices.
+        /// </summary>
+        /// <param name="view">The view matrix.</param>
+        /// <param name="projection">The projection matrix.</param>
+        public void Draw(Matrix view, Matrix projection)
+        {
+            /// <summary>
+            /// Draws the skydome mesh with the specified view and projection matrices.
+            /// </summary>
+            /// <param name="view">The view matrix.</param>
+            /// <param name="projection">The projection matrix.</param>
+            GraphicsDevice GraphicsDevice = Texture.GraphicsDevice;
+
+            GraphicsDevice.BlendState = BlendState.Opaque;
+            GraphicsDevice.RasterizerState = RasterizerState.CullNone;
+            GraphicsDevice.DepthStencilState = DepthStencilState.None;
+            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
+
+            view.Translation = Vector3.Zero;
+
+            foreach (ModelMesh mesh in Model.Meshes)
+            {
+                foreach (BasicEffect effect in mesh.Effects)
+                {
+                    effect.View = view;
+                    effect.Projection = projection;
+                    effect.Texture = Texture;
+                    effect.TextureEnabled = true;
+                }
+
+                mesh.Draw();
+            }
+        }
+    }
+}

+ 302 - 252
ReachGraphicsDemo/Tank.cs → ReachGraphicsDemo/Core/Tank.cs

@@ -1,252 +1,302 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// Tank.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;
-using Microsoft.Xna.Framework.Graphics;
-using XnaGraphicsDemo;
-using System;
-#endregion
-
-namespace SimpleAnimation
-{
-    /// <summary>
-    /// Helper class for drawing a tank model with animated wheels and turret.
-    /// </summary>
-    public class Tank
-    {
-        #region Fields
-
-
-        // The XNA framework Model object that we are going to display.
-        Model tankModel;
-
-
-        // Shortcut references to the bones that we are going to animate.
-        // We could just look these up inside the Draw method, but it is more
-        // efficient to do the lookups while loading and cache the results.
-        ModelBone leftBackWheelBone;
-        ModelBone rightBackWheelBone;
-        ModelBone leftFrontWheelBone;
-        ModelBone rightFrontWheelBone;
-        ModelBone leftSteerBone;
-        ModelBone rightSteerBone;
-        ModelBone turretBone;
-        ModelBone cannonBone;
-        ModelBone hatchBone;
-
-
-        // Store the original transform matrix for each animating bone.
-        Matrix leftBackWheelTransform;
-        Matrix rightBackWheelTransform;
-        Matrix leftFrontWheelTransform;
-        Matrix rightFrontWheelTransform;
-        Matrix leftSteerTransform;
-        Matrix rightSteerTransform;
-        Matrix turretTransform;
-        Matrix cannonTransform;
-        Matrix hatchTransform;
-
-        
-        // Array holding all the bone transform matrices for the entire model.
-        // We could just allocate this locally inside the Draw method, but it
-        // is more efficient to reuse a single array, as this avoids creating
-        // unnecessary garbage.
-        Matrix[] boneTransforms;
-
-
-        // Current animation positions.
-        float wheelRotationValue;
-        float steerRotationValue;
-        float turretRotationValue;
-        float cannonRotationValue;
-        float hatchRotationValue;
-
-
-        #endregion
-
-        #region Properties
-
-
-        /// <summary>
-        /// Gets or sets the wheel rotation amount.
-        /// </summary>
-        public float WheelRotation
-        {
-            get { return wheelRotationValue; }
-            set { wheelRotationValue = value; }
-        }
-
-
-        /// <summary>
-        /// Gets or sets the steering rotation amount.
-        /// </summary>
-        public float SteerRotation
-        {
-            get { return steerRotationValue; }
-            set { steerRotationValue = value; }
-        }
-
-
-        /// <summary>
-        /// Gets or sets the turret rotation amount.
-        /// </summary>
-        public float TurretRotation
-        {
-            get { return turretRotationValue; }
-            set { turretRotationValue = value; }
-        }
-
-
-        /// <summary>
-        /// Gets or sets the cannon rotation amount.
-        /// </summary>
-        public float CannonRotation
-        {
-            get { return cannonRotationValue; }
-            set { cannonRotationValue = value; }
-        }
-
-
-        /// <summary>
-        /// Gets or sets the entry hatch rotation amount.
-        /// </summary>
-        public float HatchRotation
-        {
-            get { return hatchRotationValue; }
-            set { hatchRotationValue = value; }
-        }
-
-
-        #endregion
-
-
-        /// <summary>
-        /// Loads the tank model.
-        /// </summary>
-        public void Load(ContentManager content)
-        {
-            // Load the tank model from the ContentManager.
-            tankModel = content.Load<Model>("tank");
-
-            // Look up shortcut references to the bones we are going to animate.
-            leftBackWheelBone = tankModel.Bones["l_back_wheel_geo"];
-            rightBackWheelBone = tankModel.Bones["r_back_wheel_geo"];
-            leftFrontWheelBone = tankModel.Bones["l_front_wheel_geo"];
-            rightFrontWheelBone = tankModel.Bones["r_front_wheel_geo"];
-            leftSteerBone = tankModel.Bones["l_steer_geo"];
-            rightSteerBone = tankModel.Bones["r_steer_geo"];
-            turretBone = tankModel.Bones["turret_geo"];
-            cannonBone = tankModel.Bones["canon_geo"];
-            hatchBone = tankModel.Bones["hatch_geo"];
-
-            // Store the original transform matrix for each animating bone.
-            leftBackWheelTransform = leftBackWheelBone.Transform;
-            rightBackWheelTransform = rightBackWheelBone.Transform;
-            leftFrontWheelTransform = leftFrontWheelBone.Transform;
-            rightFrontWheelTransform = rightFrontWheelBone.Transform;
-            leftSteerTransform = leftSteerBone.Transform;
-            rightSteerTransform = rightSteerBone.Transform;
-            turretTransform = turretBone.Transform;
-            cannonTransform = cannonBone.Transform;
-            hatchTransform = hatchBone.Transform;
-
-            // Allocate the transform matrix array.
-            boneTransforms = new Matrix[tankModel.Bones.Count];
-        }
-
-
-        /// <summary>
-        /// Animates the tank model.
-        /// </summary>
-        /// <param name="gameTime"></param>
-        public void Animate(GameTime gameTime)
-        {
-            float time = (float)gameTime.TotalGameTime.TotalSeconds;
-
-            SteerRotation = (float)Math.Sin(time * 0.75f) * 0.5f;
-            TurretRotation = (float)Math.Sin(time * 0.333f) * 1.25f;
-            CannonRotation = (float)Math.Sin(time * 0.25f) * 0.333f - 0.333f;
-            HatchRotation = MathHelper.Clamp((float)Math.Sin(time * 2) * 2, -1, 0);
-        }
-
-        
-        /// <summary>
-        /// Draws the tank model, using the current animation settings.
-        /// </summary>
-        public void Draw(Matrix world, Matrix view, Matrix projection, LightingMode lightMode, bool textureEnable)
-        {
-            // Set the world matrix as the root transform of the model.
-            tankModel.Root.Transform = world;
-
-            // Calculate matrices based on the current animation position.
-            Matrix wheelRotation = Matrix.CreateRotationX(wheelRotationValue);
-            Matrix steerRotation = Matrix.CreateRotationY(steerRotationValue);
-            Matrix turretRotation = Matrix.CreateRotationY(turretRotationValue);
-            Matrix cannonRotation = Matrix.CreateRotationX(cannonRotationValue);
-            Matrix hatchRotation = Matrix.CreateRotationX(hatchRotationValue);
-
-            // Apply matrices to the relevant bones.
-            leftBackWheelBone.Transform = wheelRotation * leftBackWheelTransform;
-            rightBackWheelBone.Transform = wheelRotation * rightBackWheelTransform;
-            leftFrontWheelBone.Transform = wheelRotation * leftFrontWheelTransform;
-            rightFrontWheelBone.Transform = wheelRotation * rightFrontWheelTransform;
-            leftSteerBone.Transform = steerRotation * leftSteerTransform;
-            rightSteerBone.Transform = steerRotation * rightSteerTransform;
-            turretBone.Transform = turretRotation * turretTransform;
-            cannonBone.Transform = cannonRotation * cannonTransform;
-            hatchBone.Transform = hatchRotation * hatchTransform;
-
-            // Look up combined bone matrices for the entire model.
-            tankModel.CopyAbsoluteBoneTransformsTo(boneTransforms);
-
-            // Draw the model.
-            foreach (ModelMesh mesh in tankModel.Meshes)
-            {
-                foreach (BasicEffect effect in mesh.Effects)
-                {
-                    effect.World = boneTransforms[mesh.ParentBone.Index];
-                    effect.View = view;
-                    effect.Projection = projection;
-
-                    switch (lightMode)
-                    {
-                        case LightingMode.NoLighting:
-                            effect.LightingEnabled = false;
-                            break;
-
-                        case LightingMode.OneVertexLight:
-                            effect.EnableDefaultLighting();
-                            effect.PreferPerPixelLighting = false;
-                            effect.DirectionalLight1.Enabled = false;
-                            effect.DirectionalLight2.Enabled = false;
-                            break;
-
-                        case LightingMode.ThreeVertexLights:
-                            effect.EnableDefaultLighting();
-                            effect.PreferPerPixelLighting = false;
-                            break;
-
-                        case LightingMode.ThreePixelLights:
-                            effect.EnableDefaultLighting();
-                            effect.PreferPerPixelLighting = true;
-                            break;
-                    }
-
-                    effect.SpecularColor = new Vector3(0.8f, 0.8f, 0.6f);
-                    effect.SpecularPower = 16;
-                    effect.TextureEnabled = textureEnable;
-                }
-
-                mesh.Draw();
-            }
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// Tank.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using XnaGraphicsDemo;
+using System;
+
+namespace SimpleAnimation
+{
+    /// <summary>
+    /// Helper class for drawing a tank model with animated wheels and turret.
+    /// </summary>
+    public class Tank
+    {
+
+
+        // The XNA framework Model object that we are going to display.
+        Model tankModel;
+
+
+        // Shortcut references to the bones that we are going to animate.
+        // We could just look these up inside the Draw method, but it is more
+        // efficient to do the lookups while loading and cache the results.
+        ModelBone leftBackWheelBone;
+        ModelBone rightBackWheelBone;
+        ModelBone leftFrontWheelBone;
+        ModelBone rightFrontWheelBone;
+        ModelBone leftSteerBone;
+        ModelBone rightSteerBone;
+        ModelBone turretBone;
+        ModelBone cannonBone;
+        ModelBone hatchBone;
+
+
+        // Store the original transform matrix for each animating bone.
+        Matrix leftBackWheelTransform;
+        Matrix rightBackWheelTransform;
+        Matrix leftFrontWheelTransform;
+        Matrix rightFrontWheelTransform;
+        Matrix leftSteerTransform;
+        Matrix rightSteerTransform;
+        Matrix turretTransform;
+        Matrix cannonTransform;
+        Matrix hatchTransform;
+
+        
+        // Array holding all the bone transform matrices for the entire model.
+        // We could just allocate this locally inside the Draw method, but it
+        // is more efficient to reuse a single array, as this avoids creating
+        // unnecessary garbage.
+        Matrix[] boneTransforms;
+
+
+        // Current animation positions.
+        float wheelRotationValue;
+        float steerRotationValue;
+        float turretRotationValue;
+        float cannonRotationValue;
+        float hatchRotationValue;
+
+
+
+
+
+        /// <summary>
+        /// Gets or sets the wheel rotation amount.
+        /// </summary>
+        public float WheelRotation
+        {
+            /// <summary>
+            /// Gets the wheel rotation value.
+            /// </summary>
+            /// <returns>The current wheel rotation value.</returns>
+            get { return wheelRotationValue; }
+            /// <summary>
+            /// Sets the wheel rotation value.
+            /// </summary>
+            /// <param name="value">The new wheel rotation value.</param>
+            set { wheelRotationValue = value; }
+        }
+
+
+        /// <summary>
+        /// Gets or sets the steering rotation amount.
+        /// </summary>
+        /// <summary>
+        /// Gets or sets the wheel rotation amount for animation.
+        /// </summary>
+        public float SteerRotation
+        {
+            /// <summary>
+            /// Gets the steer rotation value.
+            /// </summary>
+            /// <returns>The current steer rotation value.</returns>
+            get { return steerRotationValue; }
+            /// <summary>
+            /// Sets the steer rotation value.
+            /// </summary>
+            /// <param name="value">The new steer rotation value.</param>
+            set { steerRotationValue = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the steering rotation amount for animation.
+        /// </summary>
+
+        /// <summary>
+        /// Gets or sets the turret rotation amount.
+        /// </summary>
+        public float TurretRotation
+        {
+            /// <summary>
+            /// Gets the turret rotation value.
+            /// </summary>
+            /// <returns>The current turret rotation value.</returns>
+            get { return turretRotationValue; }
+            /// <summary>
+            /// Sets the turret rotation value.
+            /// </summary>
+            /// <param name="value">The new turret rotation value.</param>
+            set { turretRotationValue = value; }
+        }
+
+
+        /// <summary>
+        /// <summary>
+        /// Gets or sets the cannon rotation amount for animation.
+        /// </summary>
+        /// Gets or sets the cannon rotation amount.
+        /// </summary>
+        public float CannonRotation
+        {
+            /// <summary>
+            /// Gets the cannon rotation value.
+            /// </summary>
+            /// <returns>The current cannon rotation value.</returns>
+            get { return cannonRotationValue; }
+            /// <summary>
+            /// Sets the cannon rotation value.
+            /// </summary>
+            /// <param name="value">The new cannon rotation value.</param>
+            set { cannonRotationValue = value; }
+        /// <summary>
+        /// Gets or sets the entry hatch rotation amount for animation.
+        /// </summary>
+        }
+
+
+        /// <summary>
+        /// Gets or sets the entry hatch rotation amount.
+        /// </summary>
+        public float HatchRotation
+        {
+            /// <summary>
+            /// Gets the hatch rotation value.
+            /// </summary>
+            /// <returns>The current hatch rotation value.</returns>
+            get { return hatchRotationValue; }
+            /// <summary>
+            /// Sets the hatch rotation value.
+            /// </summary>
+            /// <param name="value">The new hatch rotation value.</param>
+            set { hatchRotationValue = value; }
+        }
+
+
+
+
+        /// <summary>
+        /// Loads the tank model and caches bone references and transforms.
+        /// </summary>
+        /// <param name="content">The content manager to load the model from.</param>
+        public void Load(ContentManager content)
+        {
+            // Load the tank model from the ContentManager.
+            tankModel = content.Load<Model>("tank");
+
+            // Look up shortcut references to the bones we are going to animate.
+            leftBackWheelBone = tankModel.Bones["l_back_wheel_geo"];
+            rightBackWheelBone = tankModel.Bones["r_back_wheel_geo"];
+            leftFrontWheelBone = tankModel.Bones["l_front_wheel_geo"];
+            rightFrontWheelBone = tankModel.Bones["r_front_wheel_geo"];
+            leftSteerBone = tankModel.Bones["l_steer_geo"];
+            rightSteerBone = tankModel.Bones["r_steer_geo"];
+            turretBone = tankModel.Bones["turret_geo"];
+            cannonBone = tankModel.Bones["canon_geo"];
+            hatchBone = tankModel.Bones["hatch_geo"];
+
+            // Store the original transform matrix for each animating bone.
+            leftBackWheelTransform = leftBackWheelBone.Transform;
+            rightBackWheelTransform = rightBackWheelBone.Transform;
+            leftFrontWheelTransform = leftFrontWheelBone.Transform;
+            rightFrontWheelTransform = rightFrontWheelBone.Transform;
+            leftSteerTransform = leftSteerBone.Transform;
+            rightSteerTransform = rightSteerBone.Transform;
+            turretTransform = turretBone.Transform;
+            cannonTransform = cannonBone.Transform;
+            hatchTransform = hatchBone.Transform;
+
+            // Allocate the transform matrix array.
+            boneTransforms = new Matrix[tankModel.Bones.Count];
+        }
+
+
+        /// <summary>
+        /// Animates the tank model by updating rotation values based on elapsed time.
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        public void Animate(GameTime gameTime)
+        {
+            float time = (float)gameTime.TotalGameTime.TotalSeconds;
+
+            SteerRotation = (float)Math.Sin(time * 0.75f) * 0.5f;
+            TurretRotation = (float)Math.Sin(time * 0.333f) * 1.25f;
+            CannonRotation = (float)Math.Sin(time * 0.25f) * 0.333f - 0.333f;
+            HatchRotation = MathHelper.Clamp((float)Math.Sin(time * 2) * 2, -1, 0);
+        }
+
+        
+        /// <summary>
+        /// Draws the tank model, using the current animation settings and lighting mode.
+        /// </summary>
+        /// <param name="world">The world matrix for the tank.</param>
+        /// <param name="view">The view matrix.</param>
+        /// <param name="projection">The projection matrix.</param>
+        /// <param name="lightMode">The lighting mode to use.</param>
+        /// <param name="textureEnable">Whether to enable texturing.</param>
+        public void Draw(Matrix world, Matrix view, Matrix projection, LightingMode lightMode, bool textureEnable)
+        {
+            // Set the world matrix as the root transform of the model.
+            tankModel.Root.Transform = world;
+
+            // Calculate matrices based on the current animation position.
+            Matrix wheelRotation = Matrix.CreateRotationX(wheelRotationValue);
+            Matrix steerRotation = Matrix.CreateRotationY(steerRotationValue);
+            Matrix turretRotation = Matrix.CreateRotationY(turretRotationValue);
+            Matrix cannonRotation = Matrix.CreateRotationX(cannonRotationValue);
+            Matrix hatchRotation = Matrix.CreateRotationX(hatchRotationValue);
+
+            // Apply matrices to the relevant bones.
+            leftBackWheelBone.Transform = wheelRotation * leftBackWheelTransform;
+            rightBackWheelBone.Transform = wheelRotation * rightBackWheelTransform;
+            leftFrontWheelBone.Transform = wheelRotation * leftFrontWheelTransform;
+            rightFrontWheelBone.Transform = wheelRotation * rightFrontWheelTransform;
+            leftSteerBone.Transform = steerRotation * leftSteerTransform;
+            rightSteerBone.Transform = steerRotation * rightSteerTransform;
+            turretBone.Transform = turretRotation * turretTransform;
+            cannonBone.Transform = cannonRotation * cannonTransform;
+            hatchBone.Transform = hatchRotation * hatchTransform;
+
+            // Look up combined bone matrices for the entire model.
+            tankModel.CopyAbsoluteBoneTransformsTo(boneTransforms);
+
+            // Draw the model.
+            foreach (ModelMesh mesh in tankModel.Meshes)
+            {
+                foreach (BasicEffect effect in mesh.Effects)
+                {
+                    effect.World = boneTransforms[mesh.ParentBone.Index];
+                    effect.View = view;
+                    effect.Projection = projection;
+
+                    switch (lightMode)
+                    {
+                        case LightingMode.NoLighting:
+                            effect.LightingEnabled = false;
+                            break;
+
+                        case LightingMode.OneVertexLight:
+                            effect.EnableDefaultLighting();
+                            effect.PreferPerPixelLighting = false;
+                            effect.DirectionalLight1.Enabled = false;
+                            effect.DirectionalLight2.Enabled = false;
+                            break;
+
+                        case LightingMode.ThreeVertexLights:
+                            effect.EnableDefaultLighting();
+                            effect.PreferPerPixelLighting = false;
+                            break;
+
+                        case LightingMode.ThreePixelLights:
+                            effect.EnableDefaultLighting();
+                            effect.PreferPerPixelLighting = true;
+                            break;
+                    }
+
+                    effect.SpecularColor = new Vector3(0.8f, 0.8f, 0.6f);
+                    effect.SpecularPower = 16;
+                    effect.TextureEnabled = textureEnable;
+                }
+
+                mesh.Draw();
+            }
+        }
+    }
+}

+ 174 - 169
ReachGraphicsDemo/TitleMenu.cs → ReachGraphicsDemo/Core/TitleMenu.cs

@@ -1,169 +1,174 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// TitleMenu.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// The main menu screen allows users to choose between the various demo screens.
-    /// </summary>
-    class TitleMenu : MenuComponent
-    {
-        // Constants.
-        const float XnaSpawnRate = 1.5f;
-        const float XnaLifespan = 7;
-
-
-        // Fields.
-        int attractCycle;
-        float time;
-
-        Random random = new Random();
-
-
-        // We display a set of floating "xna" text labels in the background of the menu.
-        class FloatingXna
-        {
-            public Vector2 Position;
-            public float Age;
-            public float Size;
-        }
-
-        List<FloatingXna> floatingXnas = new List<FloatingXna>();
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public TitleMenu(DemoGame game)
-            : base(game)
-        {
-            Entries.Add(new MenuEntry { Text = "basic effect",           Clicked = delegate { Game.SetActiveMenu(1); } });
-            Entries.Add(new MenuEntry { Text = "dual texture effect",    Clicked = delegate { Game.SetActiveMenu(2); } });
-            Entries.Add(new MenuEntry { Text = "alpha test effect",      Clicked = delegate { Game.SetActiveMenu(3); } });
-            Entries.Add(new MenuEntry { Text = "skinned effect",         Clicked = delegate { Game.SetActiveMenu(4); } });
-            Entries.Add(new MenuEntry { Text = "environment map effect", Clicked = delegate { Game.SetActiveMenu(5); } });
-            Entries.Add(new MenuEntry { Text = "particles",              Clicked = delegate { Game.SetActiveMenu(6); } });
-            Entries.Add(new MenuEntry { Text = "quit",                   Clicked = delegate { game.Exit(); } });
-        }
-
-
-        /// <summary>
-        /// Resets the menu state.
-        /// </summary>
-        public override void Reset()
-        {
-            floatingXnas.Clear();
-            time = 0;
-
-            base.Reset();
-        }
-
-
-        /// <summary>
-        /// The main menu wants a shorter attract delay than the other screens.
-        /// </summary>
-        override protected TimeSpan AttractDelay { get { return TimeSpan.FromSeconds(3); } }
-
-
-        /// <summary>
-        /// When the attract mode timeout is reached, we cycle through each other screen in turn.
-        /// </summary>
-        override protected void OnAttract()
-        {
-            Entries[attractCycle].OnClicked();
-
-            if (++attractCycle >= Entries.Count - 1)
-                attractCycle = 0;
-        }
-
-
-        /// <summary>
-        /// Updates the floating "xna" background labels.
-        /// </summary>
-        public override void Update(GameTime gameTime)
-        {
-            time += (float)gameTime.ElapsedGameTime.TotalSeconds;
-
-            // Spawn a new label?
-            if (time > XnaSpawnRate)
-            {
-                FloatingXna xna = new FloatingXna();
-
-                xna.Size = (float)random.NextDouble() * 2 + 0.5f;
-
-                xna.Position.X = (float)random.NextDouble() * 320 + 80;
-                xna.Position.Y = (float)random.NextDouble() * 700 + 50;
-
-                floatingXnas.Add(xna);
-
-                time -= XnaSpawnRate;
-            }
-
-            // Animate the existing labels.
-            int i = 0;
-
-            while (i < floatingXnas.Count)
-            {
-                FloatingXna xna = floatingXnas[i];
-
-                xna.Age += (float)gameTime.ElapsedGameTime.TotalSeconds;
-
-                // Different size labels move at different speeds.
-                float speed = 1.5f - xna.Size;
-
-                if (Math.Abs(speed) > 0.01f)
-                    xna.Position.Y -= xna.Age * xna.Age / speed / 10;
-
-                // Remove old labels.
-                if (xna.Age >= XnaLifespan)
-                    floatingXnas.RemoveAt(i);
-                else
-                    i++;
-            }
-
-            base.Update(gameTime);
-        }
-
-
-        /// <summary>
-        /// Draws the main menu.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            DrawTitle("xna demo", Color.CornflowerBlue, Color.Lerp(Color.Blue, Color.CornflowerBlue, 0.85f));
-
-            // Draw the background "xna" labels.
-            SpriteBatch.Begin();
-
-            foreach (FloatingXna blob in floatingXnas)
-            {
-                float alpha = Math.Min(blob.Age, 1) * Math.Min((XnaLifespan - blob.Age) / (XnaLifespan - 2), 1);
-
-                alpha *= alpha;
-                alpha /= 8;
-
-                SpriteBatch.DrawString(BigFont, "xna", blob.Position, Color.Blue * alpha, MathHelper.PiOver2, Vector2.Zero, blob.Size, 0, 0);
-            }
-
-            SpriteBatch.End();
-
-            // This will draw the various menu items.
-            base.Draw(gameTime);
-        }
-    }
-}
+//-----------------------------------------------------------------------------
+// TitleMenu.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace XnaGraphicsDemo
+{
+    /// <summary>
+    /// The main menu screen allows users to choose between the various demo screens.
+    /// </summary>
+    class TitleMenu : MenuComponent
+    {
+        // Constants.
+        const float XnaSpawnRate = 1.5f;
+        const float XnaLifespan = 7;
+
+
+        // Fields.
+        /// <summary>
+        /// Gets or sets the current attract mode cycle index.
+        /// </summary>
+        /// <summary>
+        /// Gets or sets the elapsed time for floating label updates.
+        /// </summary>
+        float time;
+
+        Random random = new Random();
+
+
+        // We display a set of floating "xna" text labels in the background of the menu.
+        class FloatingXna
+        {
+            public Vector2 Position;
+            public float Age;
+            public float Size;
+        }
+
+        List<FloatingXna> floatingXnas = new List<FloatingXna>();
+
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="TitleMenu"/> class and sets up menu entries.
+        /// </summary>
+        /// <param name="game">The game instance.</param>
+        public TitleMenu(DemoGame game)
+            : base(game)
+        {
+            Entries.Add(new MenuEntry { Text = "basic effect",           Clicked = delegate { Game.SetActiveMenu(1); } });
+            Entries.Add(new MenuEntry { Text = "dual texture effect",    Clicked = delegate { Game.SetActiveMenu(2); } });
+            Entries.Add(new MenuEntry { Text = "alpha test effect",      Clicked = delegate { Game.SetActiveMenu(3); } });
+            Entries.Add(new MenuEntry { Text = "skinned effect",         Clicked = delegate { Game.SetActiveMenu(4); } });
+            Entries.Add(new MenuEntry { Text = "environment map effect", Clicked = delegate { Game.SetActiveMenu(5); } });
+            Entries.Add(new MenuEntry { Text = "particles",              Clicked = delegate { Game.SetActiveMenu(6); } });
+#if !IOS
+            Entries.Add(new MenuEntry { Text = "quit",                   Clicked = delegate { game.Exit(); } });
+#endif
+        }
+
+
+        /// <summary>
+        /// Resets the menu state and clears floating labels.
+        /// </summary>
+        public override void Reset()
+        {
+            floatingXnas.Clear();
+            time = 0;
+
+            base.Reset();
+        }
+
+
+        /// <summary>
+        /// Gets the attract mode delay for the main menu (shorter than other screens).
+        /// </summary>
+        override protected TimeSpan AttractDelay { get { return TimeSpan.FromSeconds(3); } }
+
+
+        /// <summary>
+        /// When the attract mode timeout is reached, cycles through each demo screen in turn.
+        /// </summary>
+        override protected void OnAttract()
+        {
+            Entries[selectedEntry].OnClicked();
+
+            selectedEntry = (selectedEntry + 1) % (Entries.Count - 1); // Loop, skip "quit"
+        }
+
+
+        /// <summary>
+        /// Updates the floating "xna" background labels and handles their animation and removal.
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        public override void Update(GameTime gameTime)
+        {
+            time += (float)gameTime.ElapsedGameTime.TotalSeconds;
+
+            // Spawn a new label?
+            if (time > XnaSpawnRate)
+            {
+                FloatingXna xna = new FloatingXna();
+
+                xna.Size = (float)random.NextDouble() * 2 + 0.5f;
+
+                xna.Position.X = (float)random.NextDouble() * 320 + 80;
+                xna.Position.Y = (float)random.NextDouble() * 700 + 50;
+
+                floatingXnas.Add(xna);
+
+                time -= XnaSpawnRate;
+            }
+
+            // Animate the existing labels.
+            int i = 0;
+
+            while (i < floatingXnas.Count)
+            {
+                FloatingXna xna = floatingXnas[i];
+
+                xna.Age += (float)gameTime.ElapsedGameTime.TotalSeconds;
+
+                // Different size labels move at different speeds.
+                float speed = 1.5f - xna.Size;
+
+                if (Math.Abs(speed) > 0.01f)
+                    xna.Position.Y -= xna.Age * xna.Age / speed / 10;
+
+                // Remove old labels.
+                if (xna.Age >= XnaLifespan)
+                    floatingXnas.RemoveAt(i);
+                else
+                    i++;
+            }
+
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// Draws the main menu, including floating labels and menu items.
+        /// </summary>
+        /// <param name="gameTime">The current game time.</param>
+        public override void Draw(GameTime gameTime)
+        {
+            DrawTitle("MonoGame demo", Color.CornflowerBlue, Color.Lerp(Color.Blue, Color.CornflowerBlue, 0.85f));
+
+            // Draw the background "xna" labels.
+            SpriteBatch.Begin();
+
+            foreach (FloatingXna blob in floatingXnas)
+            {
+                float alpha = Math.Min(blob.Age, 1) * Math.Min((XnaLifespan - blob.Age) / (XnaLifespan - 2), 1);
+
+                alpha *= alpha;
+                alpha /= 8;
+
+                SpriteBatch.DrawString(BigFont, "MonoGame", blob.Position, Color.Blue * alpha, MathHelper.PiOver2, Vector2.Zero, blob.Size, 0, 0);
+            }
+
+            SpriteBatch.End();
+
+            // This will draw the various menu items.
+            base.Draw(gameTime);
+        }
+    }
+}

+ 0 - 274
ReachGraphicsDemo/MenuComponent.cs

@@ -1,274 +0,0 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// MenuComponent.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Input;
-#endregion
-
-namespace XnaGraphicsDemo
-{
-    /// <summary>
-    /// Base class for all the different screens used in the demo. This provides
-    /// a simple touch menu which can display a list of options, and detect when
-    /// a menu item is clicked.
-    /// </summary>
-    class MenuComponent : DrawableGameComponent
-    {
-        // Properties.
-        new public DemoGame Game { get { return (DemoGame)base.Game; } }
-
-        public SpriteBatch SpriteBatch { get { return Game.SpriteBatch; } }
-        public SpriteFont Font { get { return Game.Font; } }
-        public SpriteFont BigFont { get { return Game.BigFont; } }
-
-        protected List<MenuEntry> Entries { get; private set; }
-
-        protected Vector2 LastTouchPoint { get; private set; }
-
-
-        // Fields.
-        bool touchDown = true;
-        int touchSelection = -1;
-
-        static TimeSpan attractTimer;
-        static MouseState lastInputState = new MouseState(-1, -1, -1, 0, 0, 0, 0, 0);
-
-
-        /// <summary>
-        /// Constructor.
-        /// </summary>
-        public MenuComponent(DemoGame game)
-            : base(game)
-        {
-            Entries = new List<MenuEntry>();
-        }
-
-
-        /// <summary>
-        /// Initializes the menu, computing the screen position of each entry.
-        /// </summary>
-        public override void Initialize()
-        {
-            Vector2 pos = new Vector2(MenuEntry.Border, 800 - MenuEntry.Border - Entries.Count * MenuEntry.Height);
-
-            foreach (MenuEntry entry in Entries)
-            {
-                entry.Position = pos;
-
-                pos.Y += MenuEntry.Height;
-            }
-
-            base.Initialize();
-        }
-
-
-        /// <summary>
-        /// Resets the menu, whenever we transition to or from a different screen.
-        /// </summary>
-        virtual public void Reset()
-        {
-            if (touchSelection >= 0)
-                Entries[touchSelection].IsFocused = false;
-
-            touchDown = true;
-            touchSelection = -1;
-        }
-
-
-        /// <summary>
-        /// Updates the menu state, processing user input.
-        /// </summary>
-        public override void Update(GameTime gameTime)
-        {
-            // We read input using the mouse API, which will report the first touch point
-            // when run on the phone, but also works on Windows using a regular mouse.
-            MouseState input = Game.IsActive ? Mouse.GetState() : new MouseState();
-
-            // Scale input if we are running in an unusual screen resolution.
-            int touchX = input.X * 480 / Game.Graphics.PreferredBackBufferWidth;
-            int touchY = input.Y * 800 / Game.Graphics.PreferredBackBufferHeight;
-
-            // Process the input.
-            if (input.LeftButton == ButtonState.Pressed)
-            {
-                HandleTouchDown(touchX, touchY);
-            }
-            else
-            {
-                HandleTouchUp();
-            }
-
-            HandleAttractMode(gameTime, input);
-        }
-
-
-        /// <summary>
-        /// Handles input while a touch is occurring.
-        /// </summary>
-        void HandleTouchDown(int touchX, int touchY)
-        {
-            // Hit test the touch position against the list of menu items.
-            int currentEntry = -1;
-
-            for (int i = 0; i < Entries.Count; i++)
-            {
-                if ((touchY >= Entries[i].Position.Y) && (touchY < Entries[i].Position.Y + MenuEntry.Height))
-                {
-                    currentEntry = i;
-                    break;
-                }
-            }
-
-            if (touchDown)
-            {
-                // Are we already processing a touch?
-                if (touchSelection >= 0)
-                {
-                    if (currentEntry == touchSelection || Entries[touchSelection].IsDraggable)
-                    {
-                        // Pass drag input to the currently selected item.
-                        Entries[touchSelection].IsFocused = true;
-
-                        Entries[touchSelection].OnDragged(touchX - LastTouchPoint.X);
-                    }
-                    else
-                    {
-                        // If the drag moves off the selected item, unfocus it.
-                        Entries[touchSelection].IsFocused = false;
-                    }
-                }
-                else
-                {
-                    // If the touch was not on any menu item, process a backgroun drag.
-                    OnDrag(new Vector2(touchX, touchY) - LastTouchPoint);
-                }
-            }
-            else
-            {
-                // We are not currently processing a touch.
-                touchDown = true;
-                touchSelection = currentEntry;
-
-                if (touchSelection >= 0)
-                {
-                    // Focus the menu item that has just been touched.
-                    Entries[touchSelection].IsFocused = true;
-                }
-            }
-
-            // Store the most recent touch location.
-            LastTouchPoint = new Vector2(touchX, touchY);
-        }
-
-
-        /// <summary>
-        /// Handles input when the touch is released.
-        /// </summary>
-        void HandleTouchUp()
-        {
-            if (touchDown && touchSelection >= 0 && Entries[touchSelection].IsFocused)
-            {
-                // If we were touching a menu item, and just released it, process the click action.
-                Entries[touchSelection].IsFocused = false;
-                Entries[touchSelection].OnClicked();
-            }
-
-            touchDown = false;
-            touchSelection = -1;
-        }
-
-
-        /// <summary>
-        /// If no input is provided, we go into an automatic attract mode, which cycles
-        /// through the various options. This was great for leaving the demo unattended
-        /// at the kiosk during the MIX10 conference!
-        /// </summary>
-        void HandleAttractMode(GameTime gameTime, MouseState input)
-        {
-            if (input != lastInputState || touchDown)
-            {
-                // If input has changed, reset the timer.
-                attractTimer = TimeSpan.FromSeconds(-15);
-                lastInputState = input;
-            }
-            else
-            {
-                // If no input occurs, increment the timer.
-                attractTimer += gameTime.ElapsedGameTime;
-
-                if (attractTimer > AttractDelay)
-                {
-                    // Timeout! Run the attract action.
-                    attractTimer = TimeSpan.Zero;
-                    OnAttract();
-                }
-            }
-        }
-
-
-        /// <summary>
-        /// Allows subclasses to customize their attract behavior. The default is
-        /// to simulate a click on the last menu entry, which is usually "back".
-        /// </summary>
-        protected virtual void OnAttract()
-        {
-            Entries[Entries.Count - 1].OnClicked();
-        }
-
-        
-        /// <summary>
-        /// Allows subclasses to customize how long they wait before cycling through the attract sequence.
-        /// </summary>
-        protected virtual TimeSpan AttractDelay { get { return TimeSpan.FromSeconds(10); } }
-
-
-        /// <summary>
-        /// Draws the list of menu entries.
-        /// </summary>
-        public override void Draw(GameTime gameTime)
-        {
-            SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
-
-            foreach (MenuEntry entry in Entries)
-            {
-                entry.Draw(SpriteBatch, Font, Game.BlankTexture);
-            }
-
-            SpriteBatch.End();
-        }
-
-
-        /// <summary>
-        /// Draws the menu title.
-        /// </summary>
-        protected void DrawTitle(string title, Color? backgroundColor, Color titleColor)
-        {
-            if (backgroundColor.HasValue)
-                GraphicsDevice.Clear(backgroundColor.Value);
-
-            SpriteBatch.Begin(0, null, null, null, null, null, Game.ScaleMatrix);
-            SpriteBatch.DrawString(BigFont, title, new Vector2(480, 24), titleColor, MathHelper.PiOver2, Vector2.Zero, 1, 0, 0);
-            SpriteBatch.End();
-        }
-
-
-        /// <summary>
-        /// Handles a drag on the background of the screen.
-        /// </summary>
-        protected virtual void OnDrag(Vector2 delta)
-        {
-        }
-    }
-}

+ 30 - 0
ReachGraphicsDemo/Platforms/Android/AndroidManifest.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          android:versionCode="1"
+          android:versionName="1.0"
+          package="com.monogame.reachgraphicsdemo">
+  
+  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
+  
+  <uses-permission android:name="android.permission.WAKE_LOCK" />
+  
+  <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+  
+  <application android:label="Reach Graphics Demo"
+               android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+    
+    <activity android:name="Microsoft.Xna.Framework.AndroidGameActivity"
+              android:label="Reach Graphics Demo"
+              android:launchMode="singleInstance"
+              android:screenOrientation="landscape"
+              android:configChanges="orientation|keyboardHidden|keyboard"
+              android:exported="true">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+    
+  </application>
+  
+</manifest>

+ 37 - 0
ReachGraphicsDemo/Platforms/Android/ReachGraphicsDemo.Android.csproj

@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0-android</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>XnaGraphicsDemo</RootNamespace>
+    <AssemblyName>XnaGraphicsDemo</AssemblyName>
+    <AndroidApplication>true</AndroidApplication>
+    <AndroidUseIntermediateDesignerFile>True</AndroidUseIntermediateDesignerFile>
+    <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.Android" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Core\ReachGraphicsDemo.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+  <ItemGroup>
+    <AndroidManifest Include="Platforms\Android\AndroidManifest.xml" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <AndroidResource Include="Platforms\Android\Resources\**\*" />
+  </ItemGroup>
+
+</Project>

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

@@ -0,0 +1,15 @@
+using System;
+using XnaGraphicsDemo;
+
+namespace XnaGraphicsDemo
+{
+    public static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            using (var game = new DemoGame())
+                game.Run();
+        }
+    }
+}

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

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <RootNamespace>XnaGraphicsDemo</RootNamespace>
+    <AssemblyName>XnaGraphicsDemo</AssemblyName>
+    <ApplicationIcon>..\..\Core\Content\Game.ico</ApplicationIcon>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Core\ReachGraphicsDemo.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+</Project>

+ 27 - 0
ReachGraphicsDemo/Platforms/Windows/Program.cs

@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------------
+// Program.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+
+namespace XnaGraphicsDemo
+{
+    static class Program
+    {
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main(string[] args)
+        {
+            using (var game = new DemoGame())
+            {
+                game.Run();
+            }
+        }
+    }
+}
+

+ 28 - 0
ReachGraphicsDemo/Platforms/Windows/ReachGraphicsDemo.Windows.csproj

@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0-windows</TargetFramework>
+    <RootNamespace>XnaGraphicsDemo</RootNamespace>
+    <AssemblyName>XnaGraphicsDemo</AssemblyName>
+    <UseWindowsForms>true</UseWindowsForms>
+    <ApplicationIcon>..\..\Core\Content\Game.ico</ApplicationIcon>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.WindowsDX" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Core\ReachGraphicsDemo.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+</Project>

+ 0 - 0
ReachGraphicsDemo/Info.plist → ReachGraphicsDemo/Platforms/iOS/Info.plist


+ 14 - 0
ReachGraphicsDemo/Platforms/iOS/Program.cs

@@ -0,0 +1,14 @@
+using System;
+using XnaGraphicsDemo;
+
+namespace XnaGraphicsDemo
+{
+    public static class Program
+    {
+        static void Main(string[] args)
+        {
+            using (var game = new DemoGame())
+                game.Run();
+        }
+    }
+}

+ 31 - 0
ReachGraphicsDemo/Platforms/iOS/ReachGraphicsDemo.iOS.csproj

@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0-ios</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>XnaGraphicsDemo</RootNamespace>
+    <AssemblyName>XnaGraphicsDemo</AssemblyName>
+    <SupportedOSPlatformVersion>10.0</SupportedOSPlatformVersion>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.iOS" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Core\ReachGraphicsDemo.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <BundleResource Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </BundleResource>
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="Platforms\iOS\Info.plist" />
+  </ItemGroup>
+
+</Project>

+ 0 - 70
ReachGraphicsDemo/Program.cs

@@ -1,70 +0,0 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// Program.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#if MONOMAC
-using MonoMac.AppKit;
-using MonoMac.Foundation;
-#endif
-
-
-using System;
-
-namespace XnaGraphicsDemo
-{
-#if MONOMAC
-	class Program
-	{
-		static void Main (string[] args)
-		{
-			NSApplication.Init ();
-
-			using (var p = new NSAutoreleasePool ()) {
-				NSApplication.SharedApplication.Delegate = new AppDelegate ();
-
-				// Set our Application Icon
-				//NSImage appIcon = NSImage.ImageNamed ("GameThumbnail.png");
-				//NSApplication.SharedApplication.ApplicationIconImage = appIcon;
-
-				NSApplication.Main (args);
-			}
-		}
-	}
-
-	class AppDelegate : NSApplicationDelegate
-	{
-		private DemoGame game;
-
-		public override void FinishedLaunching (MonoMac.Foundation.NSObject notification)
-		{
-			game = new DemoGame();
-			game.Run();
-		}
-
-		public override bool ApplicationShouldTerminateAfterLastWindowClosed (NSApplication sender)
-		{
-			return true;
-		}
-	}	
-#else
-    static class Program
-    {
-        /// <summary>
-        /// The main entry point for the application.
-        /// </summary>
-        static void Main(string[] args)
-        {
-            using (DemoGame game = new DemoGame())
-            {
-                game.Run();
-            }
-        }
-    }
-#endif
-}
-

+ 0 - 6
ReachGraphicsDemo/Properties/AppManifest.xml

@@ -1,6 +0,0 @@
-<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
->
-    <Deployment.Parts>
-    </Deployment.Parts>
-</Deployment>

+ 0 - 34
ReachGraphicsDemo/Properties/AssemblyInfo.cs

@@ -1,34 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("MIX10 Graphics Demo")]
-[assembly: AssemblyProduct("MIX10 Graphics Demo")]
-[assembly: AssemblyDescription("Graphics Effects Demo, from the MIX10 conference.")]
-[assembly: AssemblyCompany("Microsoft")]
-[assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible 
-// to COM components.  If you need to access a type in this assembly from 
-// COM, set the ComVisible attribute to true on that type. Only Windows
-// assemblies support COM.
-[assembly: ComVisible(false)]
-
-// On Windows, the following GUID is for the ID of the typelib if this
-// project is exposed to COM. On other platforms, it unique identifies the
-// title storage container when deploying this assembly to the device.
-[assembly: Guid("ba971892-de57-4fa4-b7e6-65d9d6da5af7")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-[assembly: AssemblyVersion("1.0.0.0")]

+ 0 - 20
ReachGraphicsDemo/Properties/WindowsPhoneManifest.xml

@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2009/deployment" AppPlatformVersion="7.0">
-  <App xmlns="" ProductID="{ba971892-de57-4fa4-b7e6-65d9d6da5af7}" Title="MIX10 Graphics Demo" RuntimeType="XNA" Version="1.0.0.0" Genre="Apps.Games" Author="" Description="" Publisher="">
-    <IconPath IsRelative="true" IsResource="false">GameThumbnail.png</IconPath>
-    <Capabilities />
-    <Tasks>
-      <DefaultTask Name="_default" />
-    </Tasks>
-    <Tokens>
-      <PrimaryToken TokenID="MIX10GraphicsDemoToken" TaskName="_default">
-        <TemplateType5>
-          <BackgroundImageURI IsRelative="true" IsResource="false">GameThumbnail.png</BackgroundImageURI>
-          <Count>0</Count>
-          <Title>ReachGraphicsDemo</Title>
-        </TemplateType5>
-      </PrimaryToken>
-    </Tokens>
-  </App>
-</Deployment>
-

+ 138 - 0
ReachGraphicsDemo/README.md

@@ -0,0 +1,138 @@
+# Reach Graphics Demo - MonoGame 3.8.4
+
+This project demonstrates various graphics techniques using MonoGame 3.8.4, including alpha blending, dual texturing, environment mapping, particle systems, and skinned animation.
+
+## Project Structure
+
+The solution contains multiple platform-specific projects:
+
+- **Platforms/Windows/ReachGraphicsDemo.Windows.csproj** - Windows DirectX platform (net8.0-windows)
+- **Platforms/DesktopGL/ReachGraphicsDemo.DesktopGL.csproj** - Cross-platform OpenGL (net8.0)
+- **Platforms/Android/ReachGraphicsDemo.Android.csproj** - Android platform (net8.0-android)
+- **Platforms/iOS/ReachGraphicsDemo.iOS.csproj** - iOS platform (net8.0-ios)
+- **Core/ReachGraphicsDemo.Core.csproj** - Shared game logic and assets
+
+## Features
+
+- **Alpha Demo**: Demonstrates alpha blending and transparency effects
+- **Basic Demo**: Shows basic 3D rendering techniques
+- **Dual Demo**: Illustrates dual texturing and multi-pass rendering
+- **Environment Mapping Demo**: Shows environment/reflection mapping
+- **Particle Demo**: Demonstrates particle system effects
+- **Skinned Demo**: Shows animated character with bone-based animation
+
+## Prerequisites
+
+- .NET 8.0 SDK
+- Visual Studio 2022 or VS Code with C# extension
+- For Android: Android SDK and workload (`dotnet workload install android`)
+- For iOS: Xcode and workload (`dotnet workload install ios`)
+
+## Building the Project
+
+### Using Visual Studio
+
+1. Open `ReachGraphicsDemo.sln` in Visual Studio 2022
+2. Select your target project (Windows, DesktopGL, Android, or iOS) under the `Platforms/` directory
+3. Build and run (F5)
+
+### Using .NET CLI
+
+**Windows:**
+```powershell
+cd Platforms/Windows
+ dotnet build ReachGraphicsDemo.Windows.csproj
+ dotnet run --project ReachGraphicsDemo.Windows.csproj
+```
+
+**DesktopGL (Cross-platform):**
+```powershell
+cd Platforms/DesktopGL
+ dotnet build ReachGraphicsDemo.DesktopGL.csproj
+ dotnet run --project ReachGraphicsDemo.DesktopGL.csproj
+```
+
+**Android:**
+```powershell
+cd Platforms/Android
+ dotnet build ReachGraphicsDemo.Android.csproj
+```
+
+**iOS:**
+```powershell
+cd Platforms/iOS
+ dotnet build ReachGraphicsDemo.iOS.csproj
+```
+
+### Using VS Code
+
+1. Open the project folder in VS Code
+2. Use Ctrl+Shift+P to open the command palette
+3. Run tasks:
+   - "build-windows" - Build Windows version
+   - "build-desktopgl" - Build DesktopGL version
+   - "build-android" - Build Android version
+   - "run-windows" - Build and run Windows version
+   - "run-desktopgl" - Build and run DesktopGL version
+
+## Platform-Specific Notes
+
+### Windows
+- Uses DirectX backend
+- Requires Windows 10 or later
+- Hardware acceleration recommended
+
+### DesktopGL
+- Uses OpenGL backend
+- Cross-platform (Windows, Linux, macOS)
+- Requires OpenGL 3.0+ support
+
+### Android
+- Minimum API level 21 (Android 5.0)
+- Uses OpenGL ES 2.0
+- AndroidManifest.xml configured for landscape orientation
+
+### iOS
+- Minimum iOS 10.0
+- Uses OpenGL ES 2.0
+- Info.plist configured for landscape orientation
+
+## Content Pipeline
+
+This project uses pre-compiled XNB content files located in the `Core/Content/` directory. The content includes:
+
+- Fonts (BigFont.xnb, font.xnb)
+- Textures (various .xnb files)
+- 3D Models (model.xnb, tank.xnb, etc.)
+- Audio and other assets
+
+## Controls
+
+- Navigate menus using keyboard or touch input
+- Each demo has specific controls displayed on screen
+- ESC to return to main menu
+
+## Troubleshooting
+
+### Build Issues
+- Ensure .NET 8.0 SDK is installed
+- Restore NuGet packages: `dotnet restore`
+- Clean and rebuild: `dotnet clean && dotnet build`
+
+### Missing Workloads
+```powershell
+# Install Android workload
+dotnet workload install android
+
+# Install iOS workload (macOS only)
+dotnet workload install ios
+```
+
+### Graphics Issues
+- Ensure graphics drivers are up to date
+- For DesktopGL: Verify OpenGL 3.0+ support
+- For Android: Check OpenGL ES 2.0 support
+
+## License
+
+This project is based on Microsoft XNA Community Game Platform samples. See individual source files for license information.

+ 0 - 129
ReachGraphicsDemo/ReachGraphicsDemo.Linux.csproj

@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>10.0.0</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{28D96778-9314-4A7E-A673-2A84DFAF3654}</ProjectGuid>
-    <OutputType>Exe</OutputType>
-    <RootNamespace>XnaGraphicsDemo</RootNamespace>
-    <AssemblyName>XnaGraphicsDemo</AssemblyName>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\Debug</OutputPath>
-    <DefineConstants>DEBUG;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>False</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>none</DebugType>
-    <Optimize>True</Optimize>
-    <OutputPath>bin\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>False</ConsolePause>
-  </PropertyGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <ItemGroup>
-    <ProjectReference Include="..\ReachGraphicsDemoDataTypes\ReachGraphicsDemoDataTypes.Linux.csproj">
-      <Project>{8D359A6F-B0BB-4799-8311-406D5F57025A}</Project>
-      <Name>ReachGraphicsDemoDataTypes.Linux</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="AlphaDemo.cs" />
-    <Compile Include="BasicDemo.cs" />
-    <Compile Include="DemoGame.cs" />
-    <Compile Include="DualDemo.cs" />
-    <Compile Include="EnvmapDemo.cs" />
-    <Compile Include="MenuComponent.cs" />
-    <Compile Include="MenuEntry.cs" />
-    <Compile Include="ParticleDemo.cs" />
-    <Compile Include="Program.cs" />
-    <Compile Include="SkinnedDemo.cs" />
-    <Compile Include="Sky.cs" />
-    <Compile Include="Tank.cs" />
-    <Compile Include="TitleMenu.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <Folder Include="Content\" />
-    <Folder Include="Properties\" />
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="Content\BigFont.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\background.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\cat.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\checker_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\dude.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\engine_diff_tex_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\font.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\grass1_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\grid.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\head_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\jacket_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\lightmap_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\model.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\pants_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\saucer.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\saucer_texture_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\seattle_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\sky.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\tank.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\tile1_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\turret_alt_diff_tex_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\upBodyC_0.xnb">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-  </ItemGroup>
-</Project>

+ 0 - 137
ReachGraphicsDemo/ReachGraphicsDemo.MacOS.csproj

@@ -1,137 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <ProjectGuid>{EF8F598D-9F68-44B1-9DD1-BEF79E088CF9}</ProjectGuid>
-    <ProjectTypeGuids>{948B3504-5B70-4649-8FE4-BDE1FB46EC69};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
-    <OutputType>Exe</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>XnaGraphicsDemo</RootNamespace>
-    <AssemblyName>XnaGraphicsDemo</AssemblyName>
-    <XnaFrameworkVersion>v4.0</XnaFrameworkVersion>
-    <XnaPlatform>Windows</XnaPlatform>
-    <XnaProfile>Reach</XnaProfile>
-    <XnaCrossPlatformGroupID>2c0a54c1-9a32-4765-8a0a-c9dd328bf889</XnaCrossPlatformGroupID>
-    <XnaOutputType>Game</XnaOutputType>
-    <ApplicationIcon>Game.ico</ApplicationIcon>
-    <Thumbnail>GameThumbnail.png</Thumbnail>
-    <ProductVersion>10.0.0</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
-    <OutputPath>bin\x86\Debug</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <NoStdLib>True</NoStdLib>
-    <UseVSHostingProcess>false</UseVSHostingProcess>
-    <PlatformTarget>x86</PlatformTarget>
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>False</Optimize>
-    <DefineConstants>DEBUG;TRACE;MONOMAC</DefineConstants>
-    <XnaCompressContent>false</XnaCompressContent>
-    <EnableCodeSigning>False</EnableCodeSigning>
-    <CreatePackage>False</CreatePackage>
-    <EnablePackageSigning>False</EnablePackageSigning>
-    <IncludeMonoRuntime>False</IncludeMonoRuntime>
-    <UseSGen>False</UseSGen>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
-    <OutputPath>bin\x86\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <NoStdLib>True</NoStdLib>
-    <UseVSHostingProcess>false</UseVSHostingProcess>
-    <PlatformTarget>x86</PlatformTarget>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>True</Optimize>
-    <DefineConstants>TRACE;MONOMAC</DefineConstants>
-    <XnaCompressContent>true</XnaCompressContent>
-    <EnableCodeSigning>False</EnableCodeSigning>
-    <CreatePackage>False</CreatePackage>
-    <EnablePackageSigning>False</EnablePackageSigning>
-    <IncludeMonoRuntime>False</IncludeMonoRuntime>
-    <UseSGen>False</UseSGen>
-  </PropertyGroup>
-  <ItemGroup>
-    <None Include="Info.plist" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="AlphaDemo.cs" />
-    <Compile Include="BasicDemo.cs" />
-    <Compile Include="DualDemo.cs" />
-    <Compile Include="EnvmapDemo.cs" />
-    <Compile Include="MenuComponent.cs" />
-    <Compile Include="MenuEntry.cs" />
-    <Compile Include="ParticleDemo.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="SkinnedDemo.cs" />
-    <Compile Include="Sky.cs" />
-    <Compile Include="Tank.cs" />
-    <Compile Include="TitleMenu.cs" />
-    <Compile Include="Program.cs" />
-    <Compile Include="DemoGame.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="Content\BigFont.xnb" />
-    <Content Include="Content\background.xnb" />
-    <Content Include="Content\cat.xnb" />
-    <Content Include="Content\checker_0.xnb" />
-    <Content Include="Content\dude.xnb" />
-    <Content Include="Content\engine_diff_tex_0.xnb" />
-    <Content Include="Content\font.xnb" />
-    <Content Include="Content\grass1_0.xnb" />
-    <Content Include="Content\grid.xnb" />
-    <Content Include="Content\head_0.xnb" />
-    <Content Include="Content\jacket_0.xnb" />
-    <Content Include="Content\lightmap_0.xnb" />
-    <Content Include="Content\model.xnb" />
-    <Content Include="Content\pants_0.xnb" />
-    <Content Include="Content\saucer.xnb" />
-    <Content Include="Content\saucer_texture_0.xnb" />
-    <Content Include="Content\seattle_0.xnb" />
-    <Content Include="Content\sky.xnb" />
-    <Content Include="Content\tank.xnb" />
-    <Content Include="Content\tile1_0.xnb" />
-    <Content Include="Content\turret_alt_diff_tex_0.xnb" />
-    <Content Include="Content\upBodyC_0.xnb" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="App.config" />
-  </ItemGroup>
-  <ItemGroup>
-    <Reference Include="mscorlib" />
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="MonoMac, Version=0.0.0.0, Culture=neutral">
-      <Private>False</Private>
-    </Reference>
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(MSBuildExtensionsPath)\Mono\MonoMac\v0.0\Mono.MonoMac.targets" />
-  <!--
-      To modify your build process, add your task inside one of the targets below and uncomment it. 
-      Other similar extension points exist, see Microsoft.Common.targets.
-      <Target Name="BeforeBuild">
-      </Target>
-      <Target Name="AfterBuild">
-      </Target>
-    -->
-  <ItemGroup>
-    <ProjectReference Include="..\ReachGraphicsDemoDataTypes\ReachGraphicsDemoDataTypes.MacOS.csproj">
-      <Project>{24A0BCFD-3B8C-4AC3-AE75-E50D2F7B8C79}</Project>
-      <Name>ReachGraphicsDemoDataTypes.MacOS</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\MonoGame.Framework\MonoGame.Framework.MacOS.csproj">
-      <Project>{36C538E6-C32A-4A8D-A39C-566173D7118E}</Project>
-      <Name>MonoGame.Framework.MacOS</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\ThirdParty\Lidgren.Network\Lidgren.Network.MacOS.csproj">
-      <Project>{AE483C29-042E-4226-BA52-D247CE7676DA}</Project>
-      <Name>Lidgren.Network.MacOS</Name>
-    </ProjectReference>
-  </ItemGroup>
-</Project>

+ 42 - 0
ReachGraphicsDemo/ReachGraphicsDemo.sln

@@ -0,0 +1,42 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31912.275
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReachGraphicsDemo.Core", "Core\ReachGraphicsDemo.Core.csproj", "{11111111-1111-1111-1111-111111111111}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReachGraphicsDemo.Windows", "Platforms\Windows\ReachGraphicsDemo.Windows.csproj", "{22222222-2222-2222-2222-222222222222}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReachGraphicsDemo.DesktopGL", "Platforms\DesktopGL\ReachGraphicsDemo.DesktopGL.csproj", "{33333333-3333-3333-3333-333333333333}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReachGraphicsDemo.Android", "Platforms\Android\ReachGraphicsDemo.Android.csproj", "{44444444-4444-4444-4444-444444444444}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReachGraphicsDemo.iOS", "Platforms\iOS\ReachGraphicsDemo.iOS.csproj", "{55555555-5555-5555-5555-555555555555}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{11111111-1111-1111-1111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{11111111-1111-1111-1111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{11111111-1111-1111-1111-111111111111}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{11111111-1111-1111-1111-111111111111}.Release|Any CPU.Build.0 = Release|Any CPU
+		{22222222-2222-2222-2222-222222222222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{22222222-2222-2222-2222-222222222222}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{22222222-2222-2222-2222-222222222222}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{22222222-2222-2222-2222-222222222222}.Release|Any CPU.Build.0 = Release|Any CPU
+		{33333333-3333-3333-3333-333333333333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{33333333-3333-3333-3333-333333333333}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{33333333-3333-3333-3333-333333333333}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{33333333-3333-3333-3333-333333333333}.Release|Any CPU.Build.0 = Release|Any CPU
+		{44444444-4444-4444-4444-444444444444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{44444444-4444-4444-4444-444444444444}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{44444444-4444-4444-4444-444444444444}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{44444444-4444-4444-4444-444444444444}.Release|Any CPU.Build.0 = Release|Any CPU
+		{55555555-5555-5555-5555-555555555555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{55555555-5555-5555-5555-555555555555}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{55555555-5555-5555-5555-555555555555}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{55555555-5555-5555-5555-555555555555}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal