diff --git a/Maze-WPF/Wpf_3D/App.xaml b/Maze-WPF/Wpf_3D/App.xaml new file mode 100644 index 0000000..b77c977 --- /dev/null +++ b/Maze-WPF/Wpf_3D/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Maze-WPF/Wpf_3D/App.xaml.cs b/Maze-WPF/Wpf_3D/App.xaml.cs new file mode 100644 index 0000000..fc97245 --- /dev/null +++ b/Maze-WPF/Wpf_3D/App.xaml.cs @@ -0,0 +1,9 @@ +using System.Windows; + +namespace Wpf_3D { + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application { + } +} diff --git a/Maze-WPF/Wpf_3D/AssemblyInfo.cs b/Maze-WPF/Wpf_3D/AssemblyInfo.cs new file mode 100644 index 0000000..74087a1 --- /dev/null +++ b/Maze-WPF/Wpf_3D/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Maze-WPF/Wpf_3D/Ball3DGenerator.cs b/Maze-WPF/Wpf_3D/Ball3DGenerator.cs new file mode 100644 index 0000000..c7091cf --- /dev/null +++ b/Maze-WPF/Wpf_3D/Ball3DGenerator.cs @@ -0,0 +1,112 @@ +using Global; +using System; +using System.Windows.Media.Media3D; + +namespace Wpf_3D { + /// + /// This class was made with the help of this article + /// https://www.csharphelper.com/howtos/howto_3D_sphere.html + /// Alot of code is reused from this. + /// + public class Ball3DGenerator { + private MeshGeometry3D mesh; + + public Model3D GenerateBall(Ball ball) { + Model3DGroup modelGroup = new Model3DGroup(); + DiffuseMaterial ballMaterial = new DiffuseMaterial(ToBrush(ball.color)); + mesh = new MeshGeometry3D(); + double x = ball.x + 0.1; + double y = ball.y + 0.1; + double z = 0.5; + + AddSphere(new Point3D(x + ball.size, y + ball.size, z), ball.size, 32, 32); + GeometryModel3D ballModel = new GeometryModel3D(mesh, ballMaterial); + modelGroup.Children.Add(ballModel); + return modelGroup; + } + + /// + /// This conversion methode is taken from stackoverflow + /// + /// + /// + /// + private System.Windows.Media.Brush ToBrush(System.Drawing.Color color) { + return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B)); + } + + private void AddTriangle(Point3D p1, Point3D p2, Point3D p3) { + int index1 = mesh.Positions.Count; + int index2 = index1 + 1; + int index3 = index1 + 2; + mesh.Positions.Add(p1); + mesh.Positions.Add(p2); + mesh.Positions.Add(p3); + mesh.TriangleIndices.Add(index1); + mesh.TriangleIndices.Add(index2); + mesh.TriangleIndices.Add(index3); + } + + /// + /// Taken from https://www.csharphelper.com/howtos/howto_3D_sphere.html + /// + /// + /// + /// + /// + private void AddSphere(Point3D center, + double radius, int num_phi, int num_theta) { + double phi0, theta0; + double dphi = Math.PI / num_phi; + double dtheta = 2 * Math.PI / num_theta; + + phi0 = 0; + double y0 = radius * Math.Cos(phi0); + double r0 = radius * Math.Sin(phi0); + for (int i = 0; i < num_phi; i++) { + double phi1 = phi0 + dphi; + double y1 = radius * Math.Cos(phi1); + double r1 = radius * Math.Sin(phi1); + + // Point ptAB has phi value A and theta value B. + // For example, pt01 has phi = phi0 and theta = theta1. + // Find the points with theta = theta0. + theta0 = 0; + Point3D pt00 = new Point3D( + center.X + r0 * Math.Cos(theta0), + center.Y + y0, + center.Z + r0 * Math.Sin(theta0)); + Point3D pt10 = new Point3D( + center.X + r1 * Math.Cos(theta0), + center.Y + y1, + center.Z + r1 * Math.Sin(theta0)); + for (int j = 0; j < num_theta; j++) { + // Find the points with theta = theta1. + double theta1 = theta0 + dtheta; + Point3D pt01 = new Point3D( + center.X + r0 * Math.Cos(theta1), + center.Y + y0, + center.Z + r0 * Math.Sin(theta1)); + Point3D pt11 = new Point3D( + center.X + r1 * Math.Cos(theta1), + center.Y + y1, + center.Z + r1 * Math.Sin(theta1)); + + // Create the triangles. + AddTriangle(pt00, pt11, pt10); + AddTriangle(pt00, pt01, pt11); + + // Move to the next value of theta. + theta0 = theta1; + pt00 = pt01; + pt10 = pt11; + } + + // Move to the next value of phi. + phi0 = phi1; + y0 = y1; + r0 = r1; + } + } + } +} \ No newline at end of file diff --git a/Maze-WPF/Wpf_3D/MainWindow.xaml b/Maze-WPF/Wpf_3D/MainWindow.xaml new file mode 100644 index 0000000..aeebbc3 --- /dev/null +++ b/Maze-WPF/Wpf_3D/MainWindow.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Maze-WPF/Wpf_3D/MainWindow.xaml.cs b/Maze-WPF/Wpf_3D/MainWindow.xaml.cs new file mode 100644 index 0000000..559870c --- /dev/null +++ b/Maze-WPF/Wpf_3D/MainWindow.xaml.cs @@ -0,0 +1,256 @@ +using Global; +using Logic; +using Logica; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media.Media3D; + + +namespace Wpf_3D { + public partial class MainWindow : Window { + private int vertR = 0; //vertical angle + private int horzR = 0; //horizontal angle + private ModelVisual3D visual3D; + private Maze filled_maze; + private Ball3DGenerator ball3DGenerator = new Ball3DGenerator(); + private Model3DGroup modelGroup = new Model3DGroup(); + Maze3DGenerator mazeGen = new Maze3DGenerator(); + private bool isGameOver = false; + private bool popupBeingShown = false; + private CancellationTokenSource gameLoopCancellationTokenSource; + private PhysicsEngine physicsEngine; + + + public MainWindow() { + InitializeComponent(); + StartGamePopup(); + + } + + /// + /// initialise the maze generation & 3D models + /// + private void InitializeMaze() { + Maze maze = new Maze(15, 15); //Change these parameters to change the size of the maze. + maze.GenerateGrid(System.Drawing.Color.Red); + DepthFirstAlgoritm algo = new DepthFirstAlgoritm(maze); + algo.Generate(); + filled_maze = algo.maze; + mazeGen = new Maze3DGenerator(); + AddGuiElements(mazeGen.GenerateMaze(algo.maze), ball3DGenerator.GenerateBall(maze.ball)); + physicsEngine = new PhysicsEngine(maze.ball, maze); + isGameOver = false; + Task.Run(async () => await GameLoopAsync()); + + } + + /// + /// calculate physics on the ball & redraw it every 50ms + /// + /// + private async Task GameLoopAsync() { + gameLoopCancellationTokenSource = new CancellationTokenSource(); + while (!isGameOver) { + await Task.Delay(50); + Dispatcher.Invoke(() => { + CalculateBallMovement(); + }); + } + } + + /// + /// Popup for the start of the game + /// Starts the game; + /// + private void StartGamePopup() { + MessageBoxResult result = MessageBox.Show("Druk op OK om het spel te starten, \r\nKantel het bord met de pijltjes (↑ ↓ → ←)\r\nEn zoom de camera in & uit met de 'I' en 'O' toetsen.\r\nDuw op 'R' om het doolhof te resetten", "welkom", MessageBoxButton.OK); + if (result == MessageBoxResult.OK) { + InitializeMaze(); + } + else { + Application.Current.Shutdown(); + } + } + + /// + /// Popup for the end of the game. + /// Can restart or end the game + /// + private async void ShowEndPopup() { + if (physicsEngine.isFinished == true && !popupBeingShown) { + popupBeingShown = true; + MessageBoxResult result = MessageBox.Show("Proficiat!\r\nU hebt het doolhof opgelost. \r\nDuw op OK om opnieuw te beginnen en duw op cancel om te stoppen", "Proficiat!", MessageBoxButton.OKCancel); + if (result == MessageBoxResult.OK) { + await Task.Delay(100); + InitializeMaze(); + } + else if (result == MessageBoxResult.Cancel) { + gameLoopCancellationTokenSource.Cancel(); + Application.Current.Shutdown(); + } + popupBeingShown = false; + } + } + + /// + /// Add all GUI elements to the modelgroup + /// + /// + /// + /// + private Task AddGuiElements(Model3D maze, Model3D ball) { + modelGroup.Children.Clear(); + modelGroup.Children.Add(maze); + modelGroup.Children.Add(ball); + + visual3D = new ModelVisual3D { + Content = modelGroup + }; + + viewport3D1.Children[1] = visual3D; + CentreScreen(); + return null; + } + + + /// + /// Call the PhysicsEngine and move the ball. + /// Check if the ball is in the end cell + /// + private void CalculateBallMovement() { + if (!isGameOver) { + Ball calcBall = physicsEngine.CalculateAngleMovement(vertR, horzR); + Dispatcher.Invoke(() => { + modelGroup.Children[1] = ball3DGenerator.GenerateBall(calcBall); + }); + + if (physicsEngine.isFinished) { + ShowEndPopup(); + isGameOver = true; + } + } + } + + + /// + /// Center the screen + /// + private void CentreScreen() { + vertR = 0; + horzR = 0; + Transform3DGroup rotationGroup = new Transform3DGroup(); + double centerX = filled_maze.length / 2; + double centerY = filled_maze.height / 2; + TranslateTransform3D centerTranslation = new TranslateTransform3D(-centerX, -centerY, 0); + rotationGroup.Children.Add(centerTranslation); + visual3D.Transform = rotationGroup; + } + + + /// + /// Handle the GUI controls + /// + /// + /// + private void Window_KeyDown(object sender, KeyEventArgs e) { + switch (e.Key) { + case Key.Left: + horzR--; + HorizontalMovement(); + break; + case Key.Right: + horzR++; + HorizontalMovement(); + break; + case Key.Up: + vertR--; + VerticalMovement(); + break; + case Key.Down: + vertR++; + VerticalMovement(); + break; + case Key.I: + ZoomCamera(-5); + break; + case Key.O: + ZoomCamera(5); + break; + case Key.R: + InitializeMaze(); + break; + } + } + + /// + /// Angle the board on the horizontal axis + /// + private void HorizontalMovement() { + if (horzR > 30) { + horzR = 30; + } + if (horzR < -30) { + horzR = -30; + } + + double centerX = filled_maze.length / 2; + double centerY = filled_maze.height / 2; + + TranslateTransform3D centerTranslation = new TranslateTransform3D(-centerX, -centerY, 0); + RotateTransform3D horizontalRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), horzR)); + RotateTransform3D verticalRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1, 0, 0), vertR)); + Transform3DGroup rotationGroup = new Transform3DGroup(); + rotationGroup.Children.Add(centerTranslation); + rotationGroup.Children.Add(horizontalRotation); + rotationGroup.Children.Add(verticalRotation); + visual3D.Transform = rotationGroup; + } + + + + /// + /// Angle the board on the vertical axis + /// + private void VerticalMovement() { + if (vertR > 30) { + vertR = 30; + } + if (vertR < -30) { + vertR = -30; + } + + double centerX = filled_maze.length / 2; + double centerY = filled_maze.height / 2; + + TranslateTransform3D centerTranslation = new TranslateTransform3D(-centerX, -centerY, 0); + RotateTransform3D horizontalRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), horzR)); + RotateTransform3D verticalRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1, 0, 0), vertR)); + Transform3DGroup rotationGroup = new Transform3DGroup(); + rotationGroup.Children.Add(centerTranslation); + rotationGroup.Children.Add(horizontalRotation); + rotationGroup.Children.Add(verticalRotation); + visual3D.Transform = rotationGroup; + } + + + /// + /// Change the perspective of the camera to zoom in & out + /// + /// + private void ZoomCamera(double zoomFactor) { + PerspectiveCamera camera = (PerspectiveCamera)viewport3D1.Camera; + camera.FieldOfView += zoomFactor; + + if (camera.FieldOfView < 1.0) { + camera.FieldOfView = 1.0; + } + if (camera.FieldOfView > 120.0) { + camera.FieldOfView = 120.0; + } + } + + } + +} diff --git a/Maze-WPF/Wpf_3D/Maze3DGenerator.cs b/Maze-WPF/Wpf_3D/Maze3DGenerator.cs new file mode 100644 index 0000000..48fe4f9 --- /dev/null +++ b/Maze-WPF/Wpf_3D/Maze3DGenerator.cs @@ -0,0 +1,138 @@ +using Logica; +using System.Collections.Generic; + +namespace Wpf_3D { + using System.Windows.Media; + using System.Windows.Media.Media3D; + + public class Maze3DGenerator { + List geom = new List(); + private Maze maze; + + + public Model3D GenerateMaze(Maze maze) { + this.maze = maze; + geom.Clear(); + GenCubes(); + Model3D mazeModel = ConvertToSingleModel(geom); + + return mazeModel; + } + + private void GenCubes() { + for (int x = 0; x < maze.cels.GetLength(0); x++) { + for (int y = 0; y < maze.cels.GetLength(1); y++) { + if (maze.cels[x, y].isWall) { + GenCube(x, y); + } + else { + GenFloor(x, y); + } + } + } + } + + private void GenCube(int x, int y) { + int z = 0; + var material = new MaterialGroup(); + material.Children.Add(new DiffuseMaterial(Brushes.Red)); + material.Children.Add(new SpecularMaterial(Brushes.White, 30)); + + var geometry = new MeshGeometry3D { + Positions = new Point3DCollection + { + new Point3D(x, y, 0 + z), + new Point3D(x + 1, y, 0 + z), + new Point3D(x, y + 1, 0 + z), + new Point3D(x + 1, y + 1, 0 + z), + new Point3D(x, y, 1 + z), + new Point3D(x + 1, y, 1 + z), + new Point3D(x, y + 1, 1 + z), + new Point3D(x + 1, y + 1, 1 + z) + }, + TriangleIndices = new Int32Collection + { + // Bottom face + 0, 1, 2, 1, 3, 2, + + // Top face + 4, 6, 5, 5, 6, 7, + + // Side faces + 0, 4, 1, 1, 4, 5, + 2, 3, 6, 3, 7, 6, + 0, 2, 4, 2, 6, 4, + 1, 5, 3, 3, 5, 7 + }, + Normals = new Vector3DCollection + { + new Vector3D(0, 0, -1), // Bottom face + new Vector3D(0, 0, 1), // Top face + new Vector3D(-1, 0, 0), // Left face + new Vector3D(1, 0, 0), // Right face + new Vector3D(0, -1, 0), // Front face + new Vector3D(0, 1, 0) // Back face + } + }; + + var model = new GeometryModel3D { + Geometry = geometry, + Material = material + }; + geometry.Freeze(); + geom.Add(model); + } + + /// + /// Generate a floor tile and add it to the model + /// + /// + /// + private void GenFloor(int x, int y) { + int z = 0; + var floorMaterial = new DiffuseMaterial(); + if (maze.cels[x, y].color == System.Drawing.Color.Blue) { + floorMaterial = new DiffuseMaterial(Brushes.Blue); + } + else { + floorMaterial = new DiffuseMaterial(Brushes.Green); + } + var floorGeometry = new MeshGeometry3D { + Positions = new Point3DCollection + { + new Point3D(x, y, 0 + z), + new Point3D(x + 1, y, 0 + z), + new Point3D(x, y + 1, 0 + z), + new Point3D(x + 1, y + 1, 0 + z), + }, + TriangleIndices = new Int32Collection { + 0, 2, 1, + 1, 2, 3, + 1, 3, 2, + 2, 0, 1 + } + }; + + var floorModel = new GeometryModel3D { + Geometry = floorGeometry, + Material = floorMaterial + }; + + geom.Add(floorModel); + } + + /// + /// Convert the List of Geometry 3D models to one single modelgroup + /// + /// + /// + private Model3D ConvertToSingleModel(List geom) { + Model3DGroup modelGroup = new Model3DGroup(); + foreach (var geometryModel in geom) { + modelGroup.Children.Add(geometryModel); + } + return modelGroup; + } + } + +} diff --git a/Maze-WPF/Wpf_3D/Wpf_3D.csproj b/Maze-WPF/Wpf_3D/Wpf_3D.csproj new file mode 100644 index 0000000..a3009d4 --- /dev/null +++ b/Maze-WPF/Wpf_3D/Wpf_3D.csproj @@ -0,0 +1,15 @@ + + + + WinExe + net7.0-windows + enable + true + + + + + + + + diff --git a/Maze-WPF/Wpf_3D/Wpf_3D.csproj.user b/Maze-WPF/Wpf_3D/Wpf_3D.csproj.user new file mode 100644 index 0000000..06ea4fd --- /dev/null +++ b/Maze-WPF/Wpf_3D/Wpf_3D.csproj.user @@ -0,0 +1,14 @@ + + + + + + Designer + + + + + Designer + + + \ No newline at end of file diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Global.dll b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Global.dll new file mode 100644 index 0000000..d70d2ec Binary files /dev/null and b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Global.dll differ diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Global.pdb b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Global.pdb new file mode 100644 index 0000000..63a4af0 Binary files /dev/null and b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Global.pdb differ diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Logic.dll b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Logic.dll new file mode 100644 index 0000000..14cfaf1 Binary files /dev/null and b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Logic.dll differ diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Logic.pdb b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Logic.pdb new file mode 100644 index 0000000..f17ffb1 Binary files /dev/null and b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Logic.pdb differ diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.deps.json b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.deps.json new file mode 100644 index 0000000..d667116 --- /dev/null +++ b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.deps.json @@ -0,0 +1,50 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v7.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v7.0": { + "Wpf_3D/1.0.0": { + "dependencies": { + "Global": "1.0.0", + "Logic": "1.0.0" + }, + "runtime": { + "Wpf_3D.dll": {} + } + }, + "Global/1.0.0": { + "runtime": { + "Global.dll": {} + } + }, + "Logic/1.0.0": { + "dependencies": { + "Global": "1.0.0" + }, + "runtime": { + "Logic.dll": {} + } + } + } + }, + "libraries": { + "Wpf_3D/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Global/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Logic/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.dll b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.dll new file mode 100644 index 0000000..a41bc3a Binary files /dev/null and b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.dll differ diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.exe b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.exe new file mode 100644 index 0000000..ed97696 Binary files /dev/null and b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.exe differ diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.pdb b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.pdb new file mode 100644 index 0000000..427032d Binary files /dev/null and b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.pdb differ diff --git a/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.runtimeconfig.json b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.runtimeconfig.json new file mode 100644 index 0000000..13a9e46 --- /dev/null +++ b/Maze-WPF/Wpf_3D/bin/Debug/net7.0-windows/Wpf_3D.runtimeconfig.json @@ -0,0 +1,15 @@ +{ + "runtimeOptions": { + "tfm": "net7.0", + "frameworks": [ + { + "name": "Microsoft.NETCore.App", + "version": "7.0.0" + }, + { + "name": "Microsoft.WindowsDesktop.App", + "version": "7.0.0" + } + ] + } +} \ No newline at end of file