using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Xml;
using System.Xml.Serialization;
using System.Reflection;


namespace VisiMath.Geometry
{
    
    /// <summary>
    /// Geometric factory for making geometry objects
    /// </summary>
    public static class GeoFactory
    {

        #region Spheres

        /// <summary>
        /// Creates indexed mesh with sphere (with specified type of vertex)
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="center">Center of the sphere</param>
        /// <param name="radius">Radius</param>
        /// <param name="quality">Quality (0 to 1)</param>
        /// <returns>New indexed mesh containing the sphere</returns>
        public static IndexedMesh CreateSphere<T>(Vector3 center, float radius, float quality = 0.5f)
            where T : IVertex, new()
        {
            Sphere sph;
            return CreateSphere<T>(center, radius, out sph, quality);
        }

        /// <summary>
        /// Creates indexed mesh with sphere (with specified type of vertex)
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="sph">Will be filled with used sphere (for bounding object)</param>
        /// <param name="center">Center of the sphere</param>
        /// <param name="radius">Radius</param>
        /// <param name="quality">Quality (0 to 1)</param>
        /// <returns>New indexed mesh containing the sphere</returns>
        public static IndexedMesh CreateSphere<T>(Vector3 center, float radius, out Sphere sph, float quality = 0.5f)
            where T : IVertex, new()
        {
            sph = new Sphere(center, radius);
            return sph.ToIndexedMesh<T>(quality);
        }


        /// <summary>
        /// Creates indexed mesh with sphere (with NormalVertex)
        /// </summary>
        /// <param name="center">Center of the sphere</param>
        /// <param name="radius">Radius</param>
        /// <param name="quality">Quality (0 to 1)</param>
        /// <returns>New indexed mesh containing the sphere</returns>
        public static IndexedMesh CreateSphere(Vector3 center, float radius, float quality = 0.5f)
        {
            return CreateSphere<NormalVertex>(center, radius, quality);
        }

        /// <summary>
        /// Creates indexed mesh with sphere (with NormalVertex)
        /// </summary>
        /// <param name="sph">Will be filled with used sphere (for bounding object)</param>
        /// <param name="center">Center of the sphere</param>
        /// <param name="radius">Radius</param>
        /// <param name="quality">Quality (0 to 1)</param>
        /// <returns>New indexed mesh containing the sphere</returns>
        public static IndexedMesh CreateSphere(Vector3 center, float radius, out Sphere sph, float quality = 0.5f)
        {
            return CreateSphere<NormalVertex>(center, radius, out sph, quality);
        }

        #endregion


        #region Box
        /// <summary>
        /// Creates new indexed mesh (normalvertex) with simple box 
        /// </summary>
        /// <param name="min">Minimal values of the aa box</param>
        /// <param name="max">maximal values of the aa box</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox(Vector3 min, Vector3 max)
        {
            return CreateBox<NormalVertex>(min, max);
        }
        
        /// <summary>
        /// Creates new indexed mesh with simple box 
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="min">Minimal values of the aa box</param>
        /// <param name="max">maximal values of the aa box</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox<T>(Vector3 min, Vector3 max)
            where T : IVertex, new()
        {
            AABox box;
            return CreateBox<T>(min, max, out box);
        }

        /// <summary>
        /// Creates new indexed mesh (normalvertex) with simple box 
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="min">Minimal values of the aa box</param>
        /// <param name="max">maximal values of the aa box</param>
        /// <param name="box">Will be filled with used aabox (for bounding object)</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox(Vector3 min, Vector3 max, out AABox box)
        {
            return CreateBox<NormalVertex>(min, max, out box);
        }


        /// <summary>
        /// Creates new indexed mesh with simple box 
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="min">Minimal values of the aa box</param>
        /// <param name="max">maximal values of the aa box</param>
        /// <param name="box">Will be filled with used aabox (for bounding object)</param>
        /// <param name="VertexType">Required type of the vertex</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox(Type VertexType, Vector3 min, Vector3 max, out AABox box)
        {
            box = new AABox();
            box.Minimum = min;
            box.Maximum = max;
            return box.ToIndexedMesh(VertexType);
        }

        /// <summary>
        /// Creates new indexed mesh with simple box 
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="min">Minimal values of the aa box</param>
        /// <param name="max">maximal values of the aa box</param>
        /// <param name="box">Will be filled with used aabox (for bounding object)</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox<T>(Vector3 min, Vector3 max, out AABox box)
            where T : IVertex, new()
        {
            box = new AABox();
            box.Minimum = min;
            box.Maximum = max;
            return box.ToIndexedMesh<T>();
        }

        /// <summary>
        /// Creates new indexed mesh (normal vertex) with simple box 
        /// </summary>
        /// <param name="center">Center of the box</param>
        /// <param name="xwidth">Width in the x axis</param>
        /// <param name="ywidth">Width in the y axis</param>
        /// <param name="zwidth">Width in the z axis</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox(Vector3 center, float xwidth, float ywidth, float zwidth)
        {
            return CreateBox<NormalVertex>(center, xwidth, ywidth, zwidth);
        }


        /// <summary>
        /// Creates new indexed mesh with simple box 
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="center">Center of the box</param>
        /// <param name="xwidth">Width in the x axis</param>
        /// <param name="ywidth">Width in the y axis</param>
        /// <param name="zwidth">Width in the z axis</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox<T>(Vector3 center, float xwidth, float ywidth, float zwidth)
            where T : IVertex, new()
        {
            Vector3 max = center;
            Vector3 min = center;
            min.Substract(xwidth/2, ywidth/2, zwidth/2);
            max.Add(xwidth/2, ywidth/2, zwidth/2);
            return CreateBox<T>(min, max);
        }

        /// <summary>
        /// Creates new indexed mesh with simple box 
        /// </summary>
        /// <typeparam name="T">Type of vertex (if it has texture coordinates, vertices will be assigned spherical UV texturing coords)</typeparam>
        /// <param name="center">Center of the box</param>
        /// <param name="xwidth">Width in the x axis</param>
        /// <param name="ywidth">Width in the y axis</param>
        /// <param name="zwidth">Width in the z axis</param>
        /// <param name="box">Will be filled with used aabox (for bounding object)</param>
        /// <returns>New indexed mesh containing the box</returns>
        public static IndexedMesh CreateBox<T>(Vector3 center, float xwidth, float ywidth, float zwidth, out AABox box)
            where T : IVertex, new()
        {
            Vector3 max = center;
            Vector3 min = center;
            min.Substract(xwidth / 2, ywidth / 2, zwidth / 2);
            max.Add(xwidth / 2, ywidth / 2, zwidth / 2);
            return CreateBox<T>(min, max, out box);
        }

        #endregion

        #region Quads

        /// <summary>
        /// Creates new Quad (texturedvertex type)
        /// </summary>
        /// <param name="center">Center of the quad</param>
        /// <param name="width">Width (in x)</param>
        /// <param name="height">Height (in y)</param>
        /// <param name="normal">Normal of the quad (0,0,-1) means quad in x/y plane facing up</param>
        /// <returns>New quad</returns>
        public static QuadList CreateQuad(Vector3 center, float width, float height, Vector3 normal)
        {
            return CreateQuad<TexturedVertex>(center, width, height, normal);
        }

        /// <summary>
        /// Creates new Quad
        /// </summary>
        /// <param name="center">Center of the quad</param>
        /// <param name="width">Width (in x)</param>
        /// <param name="height">Height (in y)</param>
        /// <param name="normal">Normal of the quad (0,0,-1) means quad in x/y plane facing up</param>
        /// <returns>New quad</returns>
        public static QuadList CreateQuad<T>(Vector3 center, float width, float height, Vector3 normal)
            where T : IVertex, new()
        {
            return CreateQuad(typeof(T), center, width, height, normal);
        }

        /// <summary>
        /// Creates new Quad
        /// </summary>
        /// <param name="center">Center of the quad</param>
        /// <param name="width">Width (in x)</param>
        /// <param name="height">Height (in y)</param>
        /// <param name="normal">Normal of the quad (0,0,-1) means quad in x/y plane facing up</param>
        /// <param name="VertexType">Required vertex type</param>
        /// <returns>New quad</returns>
        public static QuadList CreateQuad(Type VertexType, Vector3 center, float width, float height, Vector3 normal)
        {
            IVertexArray varray = VertexFactory.CreateArray(VertexType, 4);
            float halfw = width * 0.5f;
            float halfh = height * 0.5f;

            //Vector3 up = new Vector3(0,0,-1);

            //Vector3 QuadRight = normal.Cross(ref up);
            //Vector3 QuadUp = QuadRight.Cross(ref normal);

            //QuadRight.Normalize();
            //QuadUp.Normalize();

            //QuadRight.Mul(halfw);
            //QuadUp.Mul(halfh);

            IVertex vert0 = VertexFactory.CreateNewVertex(VertexType);
            IVertex vert1 = VertexFactory.CreateNewVertex(VertexType);
            IVertex vert2 = VertexFactory.CreateNewVertex(VertexType);
            IVertex vert3 = VertexFactory.CreateNewVertex(VertexType);

            //vert0.Position = center + QuadRight + QuadUp;
            //vert1.Position = center + QuadRight - QuadUp;
            //vert2.Position = center - QuadRight + QuadUp;
            //vert3.Position = center - QuadRight - QuadUp;
            vert0.Position = new Vector3(-halfw, -halfh, 0);
            vert1.Position = new Vector3(halfw, -halfh,0);
            vert2.Position = new Vector3(halfw, halfh,0);
            vert3.Position = new Vector3(-halfw, halfh,0);

            Type vertype = VertexType;

            if (VertexFactory.HasNormal(vertype))
            {
                vert0.Normal = vert1.Normal = vert2.Normal = vert3.Normal = normal;
            }

            if (VertexFactory.HasColor(vertype))
            {
                vert0.Color = vert1.Color = vert2.Color = vert3.Color = ColorByte.White;
            }

            if (VertexFactory.HasTexCoord(vertype))
            {
                vert0.TexCoord2 = new Vector2(1, 1);
                vert1.TexCoord2 = new Vector2(1, 0);
                vert2.TexCoord2 = new Vector2(0, 1);
                vert3.TexCoord2 = new Vector2(0, 0);
            }

            varray[0] = vert0;
            varray[1] = vert1;
            varray[2] = vert2;
            varray[3] = vert3;

            Matrix4 trans = Matrix4.CreateRotationNormal(Vector3.UnitZ, normal);
            trans.Translation = center;
            trans.TransformFast(varray);


            return new QuadList(varray);
        }

        #endregion

        #region Fractal objects

        public static IndexedMesh CreateBoulder(ColorByte basecolor, float radius, float quality, Random rgen = null)
        {
            Vector3 cen = Vector3.Zero;

            Sphere sph = new Sphere(cen, radius);

            IndexedMesh rval = sph.ToIndexedMesh(typeof(TexturedVertex), quality);

            //IndexedMesh rval = sph.ToIndexedMesh(typeof(NormalVertex), quality);
            //IVertexArray rvalarray = VertexFactory.CreateArray(typeof(TexturedVertex), rval.Vertices.Count);


            Random rnd = rgen;
            if (rnd == null)
                rnd = new Random();

            VisiMath.Fractals.PerlinNoise noise = new Fractals.PerlinNoise(rnd, 256, 3);

            for (int i = 0; i < rval.Vertices.Count; i++)
            {
                IVertex ver = VertexFactory.CopyVertex(rval.Vertices[i], typeof(TexturedVertex));

                Vector3 norm = ver.Normal;

                /*
                float u = 0.5f+norm.X/2;
                float v = 0.5f + norm.Z / 2;
                Vector2 tx = new Vector2(u, v);
                */

                Vector3 tx = new Vector3(0.5f+norm.X/2, 0.5f + norm.Y/2, 0.5f+norm.Z/2);

                float noisevalue = 1.0f*radius * noise.GetNoise(tx, 0.5f, 8);

                Vector3 newpos = ver.Position;
                newpos.Add(norm * noisevalue);

                ver.Position = newpos;

                rval.Vertices[i] = ver;
                
                //ver.TexCoord2 = tx;
                //rvalarray[i] = ver;
            }

            //rval.Vertices = rvalarray;

            rval.RecalculateNormals();
            

            return rval;
        }





        #endregion

        #region Geometry object register


        private static void RegisterInternalTypes()
        {
            RegisterGeometryTypes(Assembly.GetExecutingAssembly());
        }

        private static Dictionary<string, Type> GeoTypes = null;

        /// <summary>
        /// For user loading geometry objects. Every IGeometry type in the assembly of the library is added automatically, 
        /// but user types from different assemblies must be registered by hand with this function or with 
        /// RegisterGeometryTypes(Assembly assembly) for assemblies.
        /// </summary>
        /// <param name="geotype">Geometry type</param>
        public static void RegisterGeometryType(Type geotype)
        {
            if (GeoTypes == null)
            {
                RegisterInternalTypes();
            }

            if (GeoTypes.ContainsValue(geotype))
                return;

            if (geotype.GetInterface("IGeometry") == null)
                throw new ArgumentException("RegisterGeometryType - type is not IGeometry!");
            ConstructorInfo ci = geotype.GetConstructor(new Type[] { typeof(XmlReader) });
            if (ci == null)
                throw new ArgumentException("RegisterGeometryType - type is IGeometry, but doesn't implement constructor(XmlReader reader)!");

            GeoTypes.Add(geotype.Name, geotype);
        }

        /// <summary>
        /// Registers all IGeometry objects in assembly to registered type database
        /// </summary>
        /// <param name="assembly">Assembly with IGeometry types</param>
        public static void RegisterGeometryTypes(Assembly assembly)
        {
            if (GeoTypes == null)
                GeoTypes = new Dictionary<string, Type>(20);

            Type[] types = assembly.GetExportedTypes();

            foreach (Type t in types)
            {
                if (t.GetInterface("IGeometry") != null)
                {
                    if (!(t.IsInterface || t.IsAbstract) && !GeoTypes.ContainsValue(t))
                    {
                        
                        //ConstructorInfo[] consts = t.GetConstructors();
                        ConstructorInfo ci = t.GetConstructor(new Type[] { typeof(XmlReader) });
                        if (ci != null)
                            GeoTypes.Add(t.Name, t);
                        else throw new ArgumentException(t.Name + " - IGeometry object without XMLReader constructor!");
                    }
                }
            }
        }


        /// <summary>
        /// Returns geotype of specified typename
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static Type GetGeoTypeByName(string name)
        {
            if (GeoTypes == null)
            {
                RegisterInternalTypes();
            }
            Type rval;
            if (GeoTypes.TryGetValue(name, out rval))
                return rval;
            return null;
        }

        #endregion

        #region XMLImport / export

        /// <summary>
        /// Tries to read IGeometry object from XML stream
        /// </summary>
        /// <param name="reader">XML reader</param>
        /// <returns>IGeometry object or null if type is not recognized</returns>
        public static IGeometry ReadXml(XmlReader reader)
        {
            string geotypename = reader["type"];

            //choose correct geometry type...

            Type geotype = GetGeoTypeByName(geotypename);
            if (geotype == null)
            {
                return null;
            }

            //MethodInfo mi = geotype.GetMethod("ReadFromXML", BindingFlags.Static);
            //if (mi == null)
                //return null;

            //return (IGeometry) mi.Invoke(null, new object[] {reader});

            ConstructorInfo ci = geotype.GetConstructor(new Type[]{reader.GetType()});
            if (ci == null)
                return null;

            object rval;
            try
            {
                rval = ci.Invoke(new object[] { reader });
            }
            catch
            {
                reader.Skip();
                return null;
            }

            return (IGeometry)rval;
        }


        /// <summary>
        /// Strongly typed reading. Tries to read IGeometry object from XML stream and return it if type is correct.
        /// Otherwise throws argument exception (as null cannot be used for generic type)
        /// </summary>
        /// <param name="reader">XML reader</param>
        /// <returns>IGeometry object or null</returns>
        public static T ReadXml<T>(XmlReader reader)
            where T : IGeometry
        {
            IGeometry outvalue;
            bool conv;
            T retval;
            if (ReadXml<T>(reader, out retval, out outvalue, out conv))
                return retval;
            throw new ArgumentException("GeoFactory:ReadXML - reading object in specified type failed!");
        }

        /// <summary>
        /// Strongly typed reading. Tries to read IGeometry object from XML stream and return it if type is correct.
        /// If not, tries to convert object.
        /// </summary>
        /// <param name="reader">XML reader</param>
        /// <param name="original">Will be set to original loaded geometry</param>
        /// <param name="converted">Will be set to true if original had to been converted</param>
        /// <param name="geometry">Geometry to be readen from xml</param>
        /// <returns>True if reading sucessful</returns>
        public static bool ReadXml<T>(XmlReader reader, out T geometry, out IGeometry original, out bool converted)
            where T:IGeometry
        {
            original = ReadXml(reader);
            if (original == null)
                throw new ArgumentException("GeometryFactory:ReadXML - No geometry in the XML stream");

            Type target = typeof(T);
            converted = false;

            if (target == original.GetType())
            {
                geometry = (T)original;
                return true;
            }

            //attempt conversion...

            converted = true;
            if (target == typeof(IndexedMesh))
            {
                IGeometry ig = (IGeometry)original.ToIndexedMesh();
                if (ig != null)
                {
                    geometry = (T)ig;
                    return true;
                }
            }

            if (target == typeof(TriangleList))
            {
                IGeometry ig = (IGeometry)original.ToTriangles();
                if (ig != null)
                {
                    geometry = (T)ig;
                    return true;
                }
            }

            if (target == typeof(LineList))
            {
                IGeometry ig = (IGeometry)original.ToLines();
                if (ig != null)
                {
                    geometry = (T)ig;
                    return true;
                }
            }

            if (target == typeof(Point3DList))
            {
                IGeometry ig = (IGeometry)original.ToPoints();
                if (ig != null)
                {
                    geometry = (T)ig;
                    return true;
                }
            }
            geometry = default(T);
            return false;
        }


        #endregion


        #region Mesh loading

        public static void SaveOBJ(string filename, IndexedMesh mesh)
        {
            //todo - use FILESYSTEM!!!

            //create obj file
            TextWriter tw = new StreamWriter(File.Open(filename + ".obj", FileMode.Create), Encoding.ASCII);

            if (tw == null)
            {
                throw new ArgumentException("Cannot open file " + filename + ".obj for saving!");
            }

            tw.WriteLine("#VISIMATH exported model");
            //tw.WriteLine("mtllib " + filename + ".mtl");
            tw.WriteLine("");
            tw.WriteLine("#Vertices");


            CultureInfo cf = new CultureInfo("en-US");

            for (int i = 0; i < mesh.Vertices.Count; i++)
            {
                tw.WriteLine(string.Format(cf, "v {0} {1} {2}", mesh.Vertices[i].Position.X, mesh.Vertices[i].Position.Y, mesh.Vertices[i].Position.Z));
            }

            tw.WriteLine("");
            tw.WriteLine("#Normal coordinates");

            for (int i = 0; i < mesh.Vertices.Count; i++)
            {
                tw.WriteLine(string.Format(cf, "vn {0} {1} {2}", mesh.Vertices[i].Normal.X, mesh.Vertices[i].Normal.Y, mesh.Vertices[i].Normal.Z));
            }


            tw.WriteLine("");
            tw.WriteLine("#Texture coordinates");

            for (int i = 0; i < mesh.Vertices.Count; i++)
            {
                tw.WriteLine(string.Format(cf, "vt {0} {1}", mesh.Vertices[i].TexCoord2.X, mesh.Vertices[i].TexCoord2.Y));
            }

            tw.WriteLine("#Poly data");
            tw.WriteLine();

            for (int i = 0; i < mesh.CurrentIndices.Length/3; i++)
            {                
                tw.Write("f ");
                for (int j = 0; j < 3; j++)
                {
                    int baka = 1 + mesh.CurrentIndices[i * 3 + j];
                    string str = baka.ToString();
                    tw.Write(str + "/" + str + "/" + str + " ");
                }
                tw.WriteLine();
            }

            tw.Close();
        }



        #endregion

        #region Bounding objects

        /// <summary>
        /// Creates AA bounding box from generic geometry object (will use conversion to points!)
        /// </summary>
        /// <param name="geoobject">Geometry object</param>
        /// <returns>AA bounding box</returns>
        public static AABox CreateBoundingBox(IGeometry geoobject)
        {
            Point3DList plist = geoobject.ToPoints();
            if (plist == null)
                return null;
            return CreateBoundingBox(plist);
        }

        /// <summary>
        /// Calculates AA bounding box for any vertex based geometry
        /// </summary>
        /// <param name="geoobject">Vertex based geometry object</param>
        /// <returns>Bounding box</returns>
        public static AABox CreateBoundingBox(Geometry geoobject)
        {
            Vector3 min = new Vector3();
            Vector3 max = new Vector3();

            for (int i = 0; i < geoobject.Vertices.Count; i++)
            {
                Vector3 pos = geoobject.GetPosition(i);

                if (pos.X < min.X)
                    min.X = pos.X;
                if (pos.Y < min.Y)
                    min.Y = pos.Y;
                if (pos.Z < min.Z)
                    min.Z = pos.Z;
                if (pos.X > max.X)
                    max.X = pos.X;
                if (pos.Y > max.Y)
                    max.Y = pos.Y;
                if (pos.Z > max.Z)
                    max.Z = pos.Z;
            }

            AABox rval = new AABox();
            rval.Minimum = min;
            rval.Maximum = max;
            return rval;
        }

        /// <summary>
        /// Calculates AA bounding box for current subset of indexed vertex based geometry
        /// </summary>
        /// <param name="geoobject">Indexed Vertex based geometry object</param>
        /// <returns>Bounding box</returns>
        public static AABox CreateBoundingBox(IndexedGeo geoobject)
        {
            Vector3 min = new Vector3(float.MaxValue);
            Vector3 max = new Vector3(float.MinValue);

            for (int i = 0; i < geoobject.Vertices.Count; i++)
            {
                Vector3 pos = geoobject.Vertices[i].Position;
                if (pos.X < min.X)
                    min.X = pos.X;
                if (pos.Y < min.Y)
                    min.Y = pos.Y;
                if (pos.Z < min.Z)
                    min.Z = pos.Z;
                if (pos.X > max.X)
                    max.X = pos.X;
                if (pos.Y > max.Y)
                    max.Y = pos.Y;
                if (pos.Z > max.Z)
                    max.Z = pos.Z;
            }


            AABox rval = new AABox();
            rval.Minimum = min;
            rval.Maximum = max;


            return rval;
        }

        /// <summary>
        /// Creates bounding sphere from generic geometry object (will use conversion to points!)
        /// </summary>
        /// <param name="geoobject">Geometry object</param>
        /// <returns>bounding sphere</returns>
        public static Sphere CreateBoundingSphere(IGeometry geoobject)
        {
            Point3DList plist = geoobject.ToPoints();
            if (plist == null)
                return null;
            return CreateBoundingSphere(plist);
        }

        /// <summary>
        /// Calculates bounding sphere for any vertex based geometry
        /// </summary>
        /// <param name="geoobject">Vertex based geometry object</param>
        /// <returns>Bounding sphere</returns>
        public static Sphere CreateBoundingSphere(Geometry geoobject)
        {
            Vector3 center = geoobject.Center;
            float rad = 0;

            for (int i = 0; i < geoobject.Vertices.Count; i++)
            {
                Vector3 pos = geoobject.GetPosition(i);

                float dst = pos.DistanceSquared(ref center);
                if (dst > rad)
                    rad = dst;
            }

            return new Sphere(center, MathTools.Sqrt(rad));
        }

        /// <summary>
        /// Calculates bounding sphere for current subset of indexed vertex based geometry
        /// </summary>
        /// <param name="geoobject">Indexed Vertex based geometry object</param>
        /// <returns>Bounding sphere</returns>
        public static Sphere CreateBoundingSphere(IndexedGeo geoobject)
        {
            Vector3 center = geoobject.Center;
            float rad = 0;

            for (int i = 0; i < geoobject.CurrentIndices.Length; i++)
            {
                Vector3 pos = geoobject.GetPosition(geoobject.CurrentIndices[i]);

                float dst = pos.DistanceSquared(ref center);
                if (dst > rad)
                    rad = dst;
            }

            return new Sphere(center, MathTools.Sqrt(rad));
        }

        /// <summary>
        /// Calculates best bounding object for current subset of generic geometry object (will use conversion to points)
        /// </summary>
        /// <param name="geoobject">Generic geometry object</param>
        /// <returns>Bounding box or bounding sphere (depending on which one better fits)</returns>
        public static IGeometry CreateBoundingObject(IGeometry geoobject)
        {
            Sphere sph = CreateBoundingSphere(geoobject);
            AABox box = CreateBoundingBox(geoobject);
            return ChooseBoundingObject(sph, box);
        }

        /// <summary>
        /// Calculates best bounding object for current subset of vertex based geometry
        /// </summary>
        /// <param name="geoobject">Vertex based geometry object</param>
        /// <returns>Bounding box or bounding sphere (depending on which one better fits)</returns>
        public static IGeometry CreateBoundingObject(Geometry geoobject)
        {
            Sphere sph = CreateBoundingSphere(geoobject);
            AABox box = CreateBoundingBox(geoobject);
            return ChooseBoundingObject(sph, box);
        }

        /// <summary>
        /// Calculates best bounding object for current subset of indexed vertex based geometry
        /// </summary>
        /// <param name="geoobject">Indexed Vertex based geometry object</param>
        /// <returns>Bounding box or bounding sphere (depending on which one better fits)</returns>
        public static IGeometry CreateBoundingObject(IndexedGeo geoobject)
        {
            Sphere sph = CreateBoundingSphere(geoobject);
            AABox box = CreateBoundingBox(geoobject);
            return ChooseBoundingObject(sph, box);
        }

        public static IGeometry ChooseBoundingObject(Sphere sph, AABox box)
        {
            float sphvol = sph.Volume;

            float boxvol = box.Volume;

            if (boxvol < sphvol)
                return box;
            return sph;
        }

        #endregion


    }
}
