using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Windows.Media.Media3D;

namespace Wireframe
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        // The main object model group.
        private Model3DGroup MainModel3Dgroup = new Model3DGroup();

        // The camera.
        private PerspectiveCamera TheCamera;

        // The camera's current location.
        private double CameraPhi = Math.PI / 6.0;       // 30 degrees
        private double CameraTheta = Math.PI / 6.0;     // 30 degrees
        private double CameraR = 3.0;

        // The change in CameraPhi when you press the up and down arrows.
        private const double CameraDPhi = 0.1;

        // The change in CameraTheta when you press the left and right arrows.
        private const double CameraDTheta = 0.1;

        // The change in CameraR when you press + or -.
        private const double CameraDR = 0.1;

        // The models.
        private GeometryModel3D SurfaceModel = null, WireframeModel = null;

        // Create the scene.
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Give the camera its initial position.
            TheCamera = new PerspectiveCamera();
            TheCamera.FieldOfView = 60;
            MainViewport.Camera = TheCamera;
            PositionCamera();

            // Define lights.
            DefineLights();

            // Create the model.
            DefineModel(MainModel3Dgroup);

            // Add the group of models to a ModelVisual3D.
            ModelVisual3D modelVisual = new ModelVisual3D();
            modelVisual.Content = MainModel3Dgroup;

            // Display the main visual to the viewportt.
            MainViewport.Children.Add(modelVisual);
        }

        // Position the camera.
        private void PositionCamera()
        {
            // Calculate the camera's position in Cartesian coordinates.
            double y = CameraR * Math.Sin(CameraPhi);
            double hyp = CameraR * Math.Cos(CameraPhi);
            double x = hyp * Math.Cos(CameraTheta);
            double z = hyp * Math.Sin(CameraTheta);
            TheCamera.Position = new Point3D(x, y, z);

            // Look toward the origin.
            TheCamera.LookDirection = new Vector3D(-x, -y, -z);

            // Set the Up direction.
            TheCamera.UpDirection = new Vector3D(0, 1, 0);
        }

        // Define the lights.
        private void DefineLights()
        {
            AmbientLight ambientLight = new AmbientLight(Colors.Gray);
            DirectionalLight directionalLight1 =
                new DirectionalLight(Colors.DarkGray, new Vector3D(-1.0, -3.0, -2.0));
            DirectionalLight directionalLight2 =
                new DirectionalLight(Colors.DarkGray, new Vector3D(1.0, 2.0, 2.0));
            MainModel3Dgroup.Children.Add(ambientLight);
            MainModel3Dgroup.Children.Add(directionalLight1);
            MainModel3Dgroup.Children.Add(directionalLight2);
        }

        // Add the model to the Model3DGroup.
        private void DefineModel(Model3DGroup modelGroup)
        {
            MeshGeometry3D surfaceMesh = MakeSurface(modelGroup);
            MakeWireframe(modelGroup, surfaceMesh);
        }

        // Make a smooth surface and return the mesh.
        private MeshGeometry3D MakeSurface(Model3DGroup modelGroup)
        {
            // Make a mesh to hold the surface.
            MeshGeometry3D mesh = new MeshGeometry3D();

            // Define the area where we will darw the surface.
            const double xmin = -1.5;
            const double xmax = 1.5;
            const double dx = 0.25;
            const double zmin = -1.5;
            const double zmax = 1.5;
            const double dz = dx;

            // Create the surface's triangles.
            for (double x = xmin; x <= xmax - dx; x += dx)
            {
                for (double z = zmin; z <= zmax - dz; z += dx)
                {
                    // Make points at the corners of the surface
                    // over (x, z) - (x + dx, z + dz).
                    Point3D p00 = new Point3D(x, F(x, z), z);
                    Point3D p10 = new Point3D(x + dx, F(x + dx, z), z);
                    Point3D p01 = new Point3D(x, F(x, z + dz), z + dz);
                    Point3D p11 = new Point3D(x + dx, F(x + dx, z + dz), z + dz);

                    // Add the triangles.
                    mesh.AddTriangle(p00, p01, p11);
                    mesh.AddTriangle(p00, p11, p10);
                }
            }

            Console.WriteLine("Surface");
            Console.WriteLine(mesh.Positions.Count + " points");
            Console.WriteLine(mesh.TriangleIndices.Count / 3 + " triangles");
            Console.WriteLine();

            // Make the surface's material.
            DiffuseMaterial material = new DiffuseMaterial(Brushes.Orange);

            // Make the mesh's model.
            SurfaceModel = new GeometryModel3D(mesh, material);

            // Make the surface visible from both sides.
            SurfaceModel.BackMaterial = material;

            // Add the model to the model groups.
            modelGroup.Children.Add(SurfaceModel);

            return mesh;
        }

        // The function that defines the surface we are drawing.
        private double F(double x, double z)
        {
            const double two_pi = 2 * 3.14159265;
            double r2 = x * x + z * z;
            double r = Math.Sqrt(r2);
            double theta = Math.Atan2(z, x);
            return Math.Exp(-r2) * Math.Sin(two_pi * r) * Math.Cos(3 * theta);
        }

        // Make the surface's wireframe.
        private void MakeWireframe(Model3DGroup modelGroup, MeshGeometry3D sourceMesh)
        {
            // Convert the source mesh into a wireframe.
            MeshGeometry3D wireframe = sourceMesh.ToWireframe(0.01);

            Console.WriteLine("Wireframe");
            Console.WriteLine(wireframe.Positions.Count + " points");
            Console.WriteLine(wireframe.TriangleIndices.Count / 3 + " triangles");
            Console.WriteLine();

            // Make the surface's material.
            DiffuseMaterial material = new DiffuseMaterial(Brushes.Red);

            // Make the mesh's model.
            WireframeModel = new GeometryModel3D(wireframe, material);

            // Add the model to the model groups.
            modelGroup.Children.Add(WireframeModel);
        }

        // Adjust the camera's position.
        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Up:
                    CameraPhi += CameraDPhi;
                    if (CameraPhi > Math.PI / 2.0) CameraPhi = Math.PI / 2.0;
                    break;
                case Key.Down:
                    CameraPhi -= CameraDPhi;
                    if (CameraPhi < -Math.PI / 2.0) CameraPhi = -Math.PI / 2.0;
                    break;
                case Key.Left:
                    CameraTheta += CameraDTheta;
                    break;
                case Key.Right:
                    CameraTheta -= CameraDTheta;
                    break;
                case Key.Add:
                case Key.OemPlus:
                    CameraR -= CameraDR;
                    if (CameraR < CameraDR) CameraR = CameraDR;
                    break;
                case Key.Subtract:
                case Key.OemMinus:
                    CameraR += CameraDR;
                    break;
            }

            // Update the camera's position.
            PositionCamera();
        }

        // Show and hide the appropriate GeometryModel3Ds.
        private void chkContents_Click(object sender, RoutedEventArgs e)
        {
            // Remove the GeometryModel3Ds.
            for (int i = MainModel3Dgroup.Children.Count - 1; i >= 0; i--)
            {
                if (MainModel3Dgroup.Children[i] is GeometryModel3D)
                    MainModel3Dgroup.Children.RemoveAt(i);
            }

            // Add the selected GeometryModel3Ds.
            if ((SurfaceModel != null) && ((bool)chkSurface.IsChecked))
                MainModel3Dgroup.Children.Add(SurfaceModel);
            if ((WireframeModel != null) && ((bool)chkWireframe.IsChecked))
                MainModel3Dgroup.Children.Add(WireframeModel);
        }
    }
}
