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 TexturedCube
{
    /// <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 / 3.0;     // 60 degrees
        private double CameraR = 5.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;

        // 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 viewport.
            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 directionalLight =
                new DirectionalLight(Colors.Gray, new Vector3D(-1.0, -3.0, -2.0));
            MainModel3Dgroup.Children.Add(ambientLight);
            MainModel3Dgroup.Children.Add(directionalLight);
        }

        // Add the model to the Model3DGroup.
        private void DefineModel(Model3DGroup modelGroup)
        {
            MakeMesh(modelGroup);
        }

        // Make a textured cube.
        private void MakeMesh(Model3DGroup modelGroup)
        {
            // Make a mesh to hold the surface.
            MeshGeometry3D mesh = new MeshGeometry3D();

            // Create the cube's sides.
            Vector3D vx = new Vector3D(2, 0, 0);
            Vector3D vy = new Vector3D(0, 2, 0);
            Vector3D vz = new Vector3D(0, 0, 2);
            AddRectangle(mesh, new Point3D(-1, -1, 1), vx, vy);     // Front.
            AddRectangle(mesh, new Point3D(-1, -1, -1), vz, vy);    // Left.
            AddRectangle(mesh, new Point3D(1, -1, 1), -vz, vy);     // Right.
            AddRectangle(mesh, new Point3D(1, -1, -1), -vx, vy);    // Back.
            AddRectangle(mesh, new Point3D(-1, 1, 1), vx, -vz);     // Top.
            AddRectangle(mesh, new Point3D(-1, -1, 1), -vz, vx);    // Bottom.

            // Make the surface's material using an image brush.
            ImageBrush metalBrush = new ImageBrush();
            metalBrush.ImageSource =
                new BitmapImage(new Uri("metal.jpg", UriKind.Relative));
            DiffuseMaterial metalMaterial = new DiffuseMaterial(metalBrush);

            // Make the mesh's model.
            GeometryModel3D surfaceModel = new GeometryModel3D(mesh, metalMaterial);

            // Make the surface visible from both sides.
            //surfaceModel.BackMaterial = metalMaterial;

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

        // Add a rectangle to the mesh.
        private void AddRectangle(MeshGeometry3D mesh, Point3D llCorner, Vector3D right, Vector3D up)
        {
            // Get the index of the first point.
            int i = mesh.Positions.Count;

            // Add the points.
            mesh.Positions.Add(llCorner);               // A = lower left corner.
            mesh.Positions.Add(llCorner + right);       // B = lower right corner.
            mesh.Positions.Add(llCorner + right + up);  // C = upper right corner.
            mesh.Positions.Add(llCorner + up);          // D = upper left corner.

            // Set texture coordinates.
            mesh.TextureCoordinates.Add(new Point(0, 1));   // A
            mesh.TextureCoordinates.Add(new Point(1, 1));   // B
            mesh.TextureCoordinates.Add(new Point(1, 0));   // C
            mesh.TextureCoordinates.Add(new Point(0, 0));   // D

            // Define triangle ABC.
            mesh.TriangleIndices.Add(i);
            mesh.TriangleIndices.Add(i + 1);
            mesh.TriangleIndices.Add(i + 2);

            // Define triangle ACD.
            mesh.TriangleIndices.Add(i);
            mesh.TriangleIndices.Add(i + 2);
            mesh.TriangleIndices.Add(i + 3);
        }

        // 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();
        }
    }
}
