//BSD License  
//DXGfx® - http://www.eteractions.com
//Copyright (c) 2005
//by Guillaume Randon
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software
//and associated documentation files (the "Software"), to deal in the Software without restriction,
//including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial
//portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;

namespace DXGfxLib
{
    public class MeshObjectWithLOD : MeshObject
    {
        /// <summary>
        /// Below this value the object is judge near to the camera.
        /// </summary>
        public static float near = 20.0f;

        /// <summary>
        /// Above near and below mid the object is at 'mid' distance to the view point.
        /// </summary>
        public static float mid = 50.0f;

        /// <summary>
        /// Above mid and below far the object is considered far away from the viewpoint.
        /// Above far it is very far!!
        /// </summary>
        public static float far = 250.0f;

        public bool visible = true;

        /// <summary>
        /// A reference to the progressiveMesh we build from the Mesh attached to this node!!
        /// </summary>
        protected ProgressiveMesh fullMesh = null;

        /// <summary>
        /// We also need this.
        /// </summary>
        protected ProgressiveMesh[] meshes = null;

        /// <summary>
        /// And this:)
        /// </summary>
        protected int currentMeshIndex = 0;

        /// <summary>
        /// If we are given the adjacency buffer we want to keep that information.
        /// </summary>
        protected int[] adjacencyBuffer;

        /// <summary>
        /// This is useful to attach a mesh and have the scene manage Level of Details for us.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="meshToAttach"></param>
        /// <param name="adjacencyBuffer"></param>
        /// <param name="materials"></param>
        /// <param name="effects"></param>
        public virtual void AttachWithLOD(Device d3ddevice, Mesh meshToAttach, int[] adjacencyBuffer, ExtendedMaterial[] materials,
            EffectInstance[] effects)
        {
            Attach(meshToAttach);

            this.adjacencyBuffer = adjacencyBuffer;

            // Code below for generation of the progressive mesh
            // is borowed more or less from the progressive Mesh sample of the DirectX SDK.
            int use32Bit = (int)(originalMesh.Options.Value & MeshFlags.Use32Bit);

            // Perform simple cleansing operations on mesh
            int[] outAdjacency;

            using (Mesh mesh = Mesh.Clean(CleanType.Simplification, originalMesh, adjacencyBuffer, out outAdjacency))
            {
                // Perform a weld to try and remove excess vertices.
                // Weld the mesh using all epsilons of 0.0f.  A small epsilon like 1e-6 works well too
                WeldEpsilons epsilons = new WeldEpsilons();
                int[] outAdjacency2;
                int[] faceRemap;
                GraphicsStream vertexRemap;
                mesh.WeldVertices(0, epsilons, outAdjacency, out outAdjacency2, out faceRemap, out vertexRemap);

                // Verify validity of mesh for simplification
                mesh.Validate(outAdjacency2);

                // Allocate a material/texture arrays
                if (materials != null)
                {
                    meshMaterials = new Material[materials.Length];
                    meshTextures = new Texture[materials.Length];

                    // Copy the materials and load the textures
                    for (int i = 0; i < meshMaterials.Length; i++)
                    {
                        meshMaterials[i] = materials[i].Material3D;
                        meshMaterials[i].AmbientColor = meshMaterials[i].DiffuseColor;

                        if ((materials[i].TextureFilename != null) && (materials[i].TextureFilename.Length > 0))
                        {
                            // Create the texture
                            meshTextures[i] = ResourceCache.GetGlobalInstance().CreateTextureFromFile(d3ddevice, materials[i].TextureFilename);
                        }
                    }
                }

                // Find the mesh's center, then generate a centering matrix
                using (VertexBuffer vb = mesh.VertexBuffer)
                {
                    using (GraphicsStream stm = vb.Lock(0, 0, LockFlags.NoSystemLock))
                    {
                        try
                        {
                            objectRadius = Geometry.ComputeBoundingSphere(stm,
                                mesh.NumberVertices, mesh.VertexFormat, out objectCenter);
                        }
                        finally
                        {
                            vb.Unlock();
                        }
                    }
                }

                // If the mesh is missing normals, generate them.
                Mesh currentMesh = mesh;
                if ((mesh.VertexFormat & VertexFormats.Normal) == 0)
                {
                    currentMesh = mesh.Clone(MeshFlags.Managed | (MeshFlags)use32Bit,
                        mesh.VertexFormat | VertexFormats.Normal, d3ddevice);

                    // Compute normals now
                    currentMesh.ComputeNormals();
                }

                using (currentMesh)
                {
                    // Generate Progressive Meshes
                    using (Microsoft.DirectX.Direct3D.ProgressiveMesh pMesh = new Microsoft.DirectX.Direct3D.ProgressiveMesh(currentMesh, adjacencyBuffer, null, 1, MeshFlags.SimplifyVertex))
                    {
                        int minVerts = pMesh.MinVertices;
                        int maxVerts = pMesh.MaxVertices;
                        int vertsPerMesh = (maxVerts - minVerts + 10) / 10;

                        // How many meshes should be in the array
                        //int numMeshes = Math.Max(1, (int)Math.Ceiling((maxVerts - minVerts + 1) / (float)vertsPerMesh));
                        int numMeshes = Math.Max(1, 3);
                        meshes = new Microsoft.DirectX.Direct3D.ProgressiveMesh[numMeshes];

                        // Clone full sized pmesh
                        fullMesh = pMesh.Clone(MeshFlags.Managed | MeshFlags.VbShare, pMesh.VertexFormat, d3ddevice);

                        // Clone all the separate pmeshes
                        for (int iMesh = 0; iMesh < numMeshes; iMesh++)
                        {
                            meshes[iMesh] = pMesh.Clone(MeshFlags.Managed | MeshFlags.VbShare, pMesh.VertexFormat, d3ddevice);

                            // Trim to appropriate space
                            meshes[iMesh].TrimByVertices(minVerts + vertsPerMesh * iMesh, minVerts + vertsPerMesh * (iMesh + 1));
                            meshes[iMesh].OptimizeBaseLevelOfDetail(MeshFlags.OptimizeVertexCache);
                        }

                        // Set the current to be max vertices
                        currentMeshIndex = numMeshes - 1;
                        meshes[currentMeshIndex].NumberVertices = maxVerts;
                        fullMesh.NumberVertices = maxVerts;
                    }
                }
            }
        }

        /// <summary>
        /// We want to override the Update method because we want an opportunity to calculate the distance
        /// to the view point in order to set the LOD we desire when rendering!
        /// </summary>
        /// <param name="frustrum"></param>
        /// <param name="elapsedTime"></param>
        /// <param name="appTime"></param>
        /// <param name="objectsToBeRendered"></param>
        public override void Update(Frustrum frustrum, float elapsedTime, double appTime, List<IDrawable> objectsToBeRendered)
        {
            base.Update(frustrum, elapsedTime, appTime, objectsToBeRendered);

            if (fullMesh != null)
            {
                // 20 m is near, 50 is medium, 100 is far!!
                float distToNearPlane = MathUtil.SignedDistance(frustrum.NearPlane, Position);

                if (distToNearPlane < near)
                {
                    SetNumberVertices(fullMesh.MaxVertices);
                    visible = true;
                }
                else if (distToNearPlane < mid)
                {
                    SetNumberVertices((int)Math.Ceiling(0.7 * fullMesh.MaxVertices));
                    visible = true;
                }
                else if (distToNearPlane < far)
                {
                    SetNumberVertices((int)Math.Ceiling(0.4 * fullMesh.MaxVertices));
                    visible = true;
                }
                else
                {
                    SetNumberVertices(0);
                    visible = false;
                }
            }
        }

        /// <summary>
        /// This is used in the Update method to specify with which level of detail the object should
        /// be displayed!
        /// </summary>
        /// <param name="numberVerts"></param>
        private void SetNumberVertices(int numberVerts)
        {
            // Update the full mesh first
            fullMesh.NumberVertices = numberVerts;

            // If current pm valid for desired value, then set the number of vertices directly
            if ((numberVerts >= meshes[currentMeshIndex].MinVertices) &&
                (numberVerts <= meshes[currentMeshIndex].MaxVertices))
            {
                meshes[currentMeshIndex].NumberVertices = numberVerts;
            }
            else
            {
                // Search for the right one
                currentMeshIndex = meshes.Length - 1;

                // Look for the correct "bin"
                while (currentMeshIndex > 0)
                {
                    // If number of vertices is less than current max then we found one to fit
                    if (numberVerts >= meshes[currentMeshIndex].MinVertices)
                        break;

                    currentMeshIndex--;
                }

                // Set the vertices on the newly selected mesh
                meshes[currentMeshIndex].NumberVertices = numberVerts;
            }
        }

        public override void Draw(Device device)
        {
            if ((fullMesh != null)&&visible)
            {
                for (int i = 0; i < originalMesh.NumberAttributes; i++)
                {
                    device.SetTexture(i, meshTextures[i]);
                    meshes[currentMeshIndex].DrawSubset(i);
                }
            }
        }

        /// <summary>
        /// This might be interesting if derived class want to set the device state in a specif way
        /// and just used the base MeshObject to perform the DrawSubset. Instead of a DrawSubset call
        /// on the full mesh this method will perform the DrawSubset on the right progressive mesh
        /// if possible.
        /// </summary>
        /// <param name="index"></param>
        public override void DrawSubset(int index)
        {
            if (fullMesh != null)
            {
                meshes[currentMeshIndex].DrawSubset(index);
            }
        }
    }
}
