//---------------------------------------------------------------------------------------------------------------------------------------------------
// <copyright file="GameObject.cs" company="DarkWynter Studios">
//     Copyright (C)2007 DarkWynter Studios.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------------------------------------------------------------------------------
// {Contact : darkwynter.com for licensing information
//---------------------------------------------------------------------------------------------------------------------------------------------------

namespace DarkWynter.Engine.GameObjects
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using System.Xml;
    using Xclna.Xna.Animation.Content;
    using Xclna.Xna.Animation;

    using Physics;
    using Globals;
    using ObjectLib;
    using DarkWynter.Engine.Init;
    using DarkWynter.Stream;
    using DarkWynter.Engine.Audio;
    #endregion

    /// <summary>
    /// Methods of GameObject follow the GameFlow pattern to ensure quick and efficient loading and unloading procedures.
    /// Additional properties are included in GameObject and are used by Renderer and Update to manipulate the GameObject.
    /// Each object specifies its draw and update procedures internally and is passed to Physics for in game motion and rotation.
    /// </summary>
    public class GameObject
    {
        #region GameObject Interface Properties
        private Mass _mass;
        //private XmlNode _xml;
        private Load _load;

        private string _objectModelName;
        private bool _isCollectable;
        private bool _isKey;
        private Draw _draw;
        private Draw _drawBoundingVolume;
        private Draw _drawAIVision;

        private bool _toDraw;
        public bool toDraw { get { return _toDraw; } set { _toDraw = value; } }

        /// <summary>
        /// Draw object
        /// </summary>
        public Draw draw { get { return _draw; } set { _draw = value; } }
        /// <summary>
        /// Draw object's bounding volume
        /// </summary>
        public Draw drawBoundingVolume { get { return _drawBoundingVolume; } set { _drawBoundingVolume = value; } }
        /// <summary>
        /// AI Vision Draw object
        /// </summary>
        public Draw drawAIVision { get { return _drawAIVision; } set { _drawAIVision = value; } }

        /// <summary>
        /// Mass object, handles physics of GameObject.
        /// </summary>
        public Mass mass { get { return _mass; } set { _mass = value; } }
        ///// <summary>
        ///// Xml data associated with this object.
        ///// </summary>
        //public XmlNode xml { get { return _xml; } set { _xml = value; } }
        /// <summary>
        /// Draw object
        /// </summary>
        public Load load { get { return _load; } set { _load = value; } }

        /// <summary>
        /// Text identifier for model name.
        /// </summary>
        public string objectModelName { get { return _objectModelName; } set { _objectModelName = value; } }
        /// <summary>
        /// Defines if this object is collectable by players.
        /// </summary>
        public bool isCollectable { get { return _isCollectable; } set { _isCollectable = value; } }
        /// <summary>
        /// Defines if this object is a Key object collected to pass the level.
        /// </summary>
        public bool isKey { get { return _isKey; } set { _isKey = value; } }


        /// <summary>
        /// Difference between object height and terrain at it's x/z location.
        /// HeightDifference is positive when object is below the terrain.
        /// </summary>
        public float heightDifference;
        /// <summary>
        /// Terrain height at object's x/z location.
        /// </summary>
        public float localTerrainHeight;

        /// <summary>
        /// Defines what kind of collision response this GameObject should have.
        /// </summary>
        public Enums_Engine.CollisionResponses collisionWithPlayerResponse = Enums_Engine.CollisionResponses.NONE;

        /// <summary>
        /// Defines the scale of damage or health an object bestows.
        /// </summary>
        public float collisionMultiplier = 0.001f;

        /// <summary>
        /// Game Object animated or not
        /// </summary>
        public bool modelAnimated;

        // Animation
        /// <summary>
        /// Model animator
        /// </summary>
        public ModelAnimator animator;
        /// <summary>
        /// Idle animation
        /// </summary>
        public AnimationController idle;
        /// <summary>
        /// Walk animation
        /// </summary>
        public AnimationController walk;
        /// <summary>
        /// Die animation
        /// </summary>
        public AnimationController die;
        /// <summary>
        /// Animation currently running
        /// </summary>
        public AnimationController currentAnimation;

        /// <summary>
        /// 3D Cue used with jump sound.
        /// </summary>
        public Cue3D jump;

        public bool destinationReached = false;

        public Vector3 _destination = new Vector3();
        public Vector3 destination
        {
            get { return _destination; }
            set { _destination = value; }
        }

        /// <summary>
        /// Index to the game controller associated with this object
        /// </summary>
        public int controllerIndex;
        #endregion

        #region Physics Properties
        /// <summary>
        /// Used to pass static instance data to shader (for static objects)
        /// </summary>
        public VertexFogBinormalTangentDepth staticObjectValues;

        /// <summary>
        /// Tangential component of force calculations.
        /// </summary>
        public Vector3 tangentialComponent = new Vector3();
        /// <summary>
        /// Normal component of force calculations.
        /// </summary>
        public Vector3 normalComponent = new Vector3();

        /// <summary>
        /// Used in collision; surface normal calculation.
        /// </summary>
        public Vector3 surfaceNormal;
        /// <summary>
        /// Used in collision; gravity normal calculation.
        /// </summary>
        public Vector3 gravityNormalComponent;
        /// <summary>
        /// Used in collision; tangential gravity-force calculation.
        /// </summary>
        public Vector3 gravityTangentialComponent;
        /// <summary>
        /// Used in collision; normal velocity-force calculation.
        /// </summary>
        public Vector3 velocityNormalComponent;
        /// <summary>
        /// Used in collision; tangential velocity-force calculation.
        /// </summary>
        public Vector3 velocityTangentialComponent;
        /// <summary>
        /// Used in collision; normal totalForce calculation.
        /// </summary>
        public Vector3 totalForceNormalComponent;
        /// <summary>
        /// Used in collision; tangential totalForce calculation.
        /// </summary>
        public Vector3 totalForceTangentialComponent;
        /// <summary>
        /// Used in collision; combined velocity and totalForce normal calculation.
        /// </summary>
        public Vector3 combinedNormalForce;
        /// <summary>
        /// Used in collision; combined velocity and totalForce tangential calculation.
        /// </summary>
        public Vector3 combinedTangentialForce;

        #endregion

        #region Xml Interface

        private string _type;
        public string type { get { return _type; } set { _type = value; } }

        private string _id;
        public string id { get { return _id; } set { _id = value; } }

        private string _typeID;
        public string typeID { get { return _typeID; } set { _typeID = value; } }

        private string _name;
        public string name { get { return _name; } set { _name = value; } }


        /// <summary>
        /// Defines the coordinate system the object is working in.
        /// </summary>
        public enum GridType { unit, coord, tile };
        private GridType _grid;
        public GridType grid
        {
            get
            {
                return _grid;
            }
            set
            {
                _grid = value;
            }
        }
        
        private string _x;
        private string _z;
        public string x 
        { 
            get 
            {
 
                return mass.currentPosition.X.ToString(); 
            } 
            set 
            {
                _x = value;
                float Y_height = 
                    DarkWynterEngine.objectLibrary.terrain.GetTerrainHeight(
                    float.Parse(value) / Statics_Engine.TerrainSettings.terrainScaleFactor,
                    mass.currentPosition.Z / Statics_Engine.TerrainSettings.terrainScaleFactor);


                mass.SetPosition(
                    new Vector3(float.Parse(value), Y_height, mass.currentPosition.Z),
                    new Vector3(float.Parse(value), Y_height, mass.currentPosition.Z - 1.0f));
            } 
        }
        public string z 
        { 
            get 
            { 
                return mass.currentPosition.Z.ToString(); 
            } 
            set 
            { 
                _z = value;
                float Y_height = 
                    DarkWynterEngine.objectLibrary.terrain.GetTerrainHeight(
                    mass.currentPosition.X / Statics_Engine.TerrainSettings.terrainScaleFactor,
                    float.Parse(value) / Statics_Engine.TerrainSettings.terrainScaleFactor);

                mass.SetPosition(
                    new Vector3(mass.currentPosition.X, Y_height, float.Parse(value)),
                    new Vector3(mass.currentPosition.X, Y_height, float.Parse(value) - 1.0f));
            } 
        }

        private string _maxScale;
        public string maxScale 
        { 
            get 
            { 
                return mass.scale.X.ToString(); 
            } 
            set 
            { 
                mass.scale = new Vector3(float.Parse(value)); 
            } 
        }

        private string _animated;
        public string animated 
        { 
            get 
            {
                if (_animated == "Yes") {return "Yes";}
                else {return "No";}
            }     
            set 
            {
                if (_animated == "Yes"){modelAnimated = true;}
                else{modelAnimated = false;}

            } 
        }

        private string _model;
        public string model 
        { 
            get 
            { 
                return _model; 
            } 
            set 
            { 
                _model = value;
                draw.model = Statics_Engine.SystemSettings.content.Load<Model>(this.model);
                foreach (ModelMesh mesh in draw.model.Meshes)
                {
                    for (int i = 0; i < mesh.MeshParts.Count; i++)
                    {
                        ModelMeshPart part = mesh.MeshParts[i];

                        part.Effect = ShaderParameters.DrawFX.effect;//ShaderParameters.DrawFX.effect.Clone(Statics.SystemSettings.graphics.GraphicsDevice);
                    }
                }

            } 
        }

        private string _texture;
        public string texture 
        { 
            get 
            { 
                if(_texture != null)
                    return _texture; 
                else
                    return "Content\\_textures\\"; 
            } 
            set 
            { 
                _texture = value;
                if (draw.textureList.Count > 0)
                    draw.textureList[0] = Statics_Engine.SystemSettings.content.Load<Texture2D>(value);
                else
                    draw.textureList.Add(Statics_Engine.SystemSettings.content.Load<Texture2D>(value));
            } 
        }

        private string _bumpTexture;
        public string bumpTexture 
        {
            get 
            {
                if (_bumpTexture != null)
                    return _bumpTexture;
                else
                    return "Content\\_textures\\";
            } 
            set 
            { 
                _bumpTexture = value;
                if (draw.textureList.Count > 1)
                    draw.textureList[1] = Statics_Engine.SystemSettings.content.Load<Texture2D>(value);
                else
                    draw.textureList.Add(Statics_Engine.SystemSettings.content.Load<Texture2D>(value));
            } 
        }

        #endregion

        #region Methods

        public GameObject()
        {
            mass = new Mass();
            mass.gameObjectPointer = this;
        }

        /// <summary>
        /// Game object constructor.
        /// Gets a pre-initialized mass object from the Statics.LevelSettings.darkMatter list.
        /// </summary>
        public GameObject(Load gameObjectLoader)
        {
            load = gameObjectLoader;

            //mass = Statics_Engine.LevelSettings.darkMatter[Statics_Engine.LevelSettings.darkMatterIterator++];
            mass = new Mass();
            mass.gameObjectPointer = this;

            //drawAIVision = new Draw();
        }

        /// <summary>
        /// Load object data from Xml.
        /// </summary>
        /// <param name="objectLibrary">ObjectLibrary that this object belongs to.</param>
        public virtual bool Load(ObjectLibrary objectLibrary)
        {
            return load.Load_Default(objectLibrary, this, false);

            #region Old Code
            //// Set up Model Manager
            //if (!LoadModel(load.node))
            //{
            //    return false;
            //}

            //// Get the Location Map
            //Texture2D locationMap = Statics_Engine.SystemSettings.content.Load<Texture2D>(node.Attributes["locationMap"].Value);
            //int length = locationMap.Width * locationMap.Width;
            //Color[] locationColorValues = new Color[length];
            //locationMap.GetData<Color>(locationColorValues);
            //Random rand = new Random();

            //// Precalculate the vertexmap/locationmap ratio for use in the for loop below
            //float vertexmapToObjectmapRelationship = Statics_Engine.TerrainSettings.vertexMapSize / locationMap.Width;

            //// Create a ruler 1 terrain mod long (distance between verticies
            //float terrainModWidth = Statics_Engine.TerrainSettings.terrainScaleFactor * vertexmapToObjectmapRelationship;

            //// Get the color value of the node
            //int mapColorR = int.Parse(node.Attributes["red"].Value);
            //int mapColorG = int.Parse(node.Attributes["green"].Value);
            //int mapColorB = int.Parse(node.Attributes["blue"].Value);

            //Vector3 scale = new Vector3((int)(float.Parse(node.Attributes["maxScale"].Value)));
            //float massValue = float.Parse(node.Attributes["mass"].Value);
            //isCollectable = bool.Parse(node.Attributes["isCollectable"].Value);
            //isKey = bool.Parse(node.Attributes["isKey"].Value);

            //// For each pixel of the treeMap
            //for (int l = 0; l < length; l++)
            //{
            //    // If the color matches create a prop
            //    if (locationColorValues[l].R == mapColorR && locationColorValues[l].G == mapColorG && locationColorValues[l].B == mapColorB)
            //    {
            //        // Get the 2D location from 1D array
            //        int h = l % locationMap.Width;
            //        int w = (int)l / locationMap.Width;

            //        // Start Type properties
            //        //gameObject.mass.objectType = objectType;
            //        mass.gameObjectPointer = this;
            //        objectModelName = node.Attributes["name"].Value;

            //        // Get terrain height at position
            //        float yPosition = objectLibrary.terrain.GetTerrainHeight(w * vertexmapToObjectmapRelationship,
            //                                                                 h * vertexmapToObjectmapRelationship);

            //        // Set the object world properties
            //        Vector3 locpos = new Vector3(w * terrainModWidth, yPosition, h * terrainModWidth);
            //        Vector3 refpos = new Vector3(w * terrainModWidth, yPosition, h * terrainModWidth - 1);
            //        mass.SetPosition(locpos, refpos);

            //        mass.Rotate(0.0f, 0.0f);
            //        mass.scale = scale;
            //        mass.mass = massValue;
            //        mass.isMoving = false;

            //        if (isKey)
            //        {
            //            collisionWithPlayerResponse = Enums_Engine.CollisionResponses.HEALTHBONUS;
            //        }
            //    }
            //}
            //return true;
            #endregion
        }


        /// <summary>
        /// Load object data from Xml.
        /// </summary>
        /// <param name="objectLibrary">ObjectLibrary that this object belongs to.</param>
        public bool Load_Instanced(ObjectLibrary objectLibrary)
        {
            return load.Load_Default(objectLibrary, this, false);
        }

        /// <summary>
        /// Load model data from XML
        /// </summary>
        /// <param name="node">The model xml node corresponding to this object</param>
        /// <returns></returns>
        public bool LoadModel(XmlNode node)
        {
            try
            {
                this.name = node.Attributes["name"].Value;
                this.maxScale = node.Attributes["maxScale"].Value;
                this.animated = node.Attributes["animated"].Value;


                grid = GridType.unit;
                if (node.Attributes["grid"] != null)
                {
                    switch (node.Attributes["grid"].Value)
                    {
                        case "unit":
                            grid = GridType.unit;
                            break;
                        case "tile":
                            grid = GridType.tile;
                            break;
                        case "coord":
                            grid = GridType.coord;
                            break;
                        default:
                            grid = GridType.unit;
                            break;
                    }
                }


                // Load Textures and Model
                draw = new Draw();
                draw.textureList.Clear();

                this.texture = node.Attributes["texture"].Value;
                this.bumpTexture = node.Attributes["bumpTexture"].Value;
                this.model = node.Attributes["model"].Value;


                // Create Animation Transforms
                if (this.name == "ss3" || this.name == "dojogirl")
                {
                    draw.initialTransform = Matrix.CreateRotationY((float)(-Math.PI / 2.0f)) *
                                       Matrix.CreateTranslation(new Vector3(0, -Statics_Engine.PlayerSettings.DEFAULT_PLAYER_HEIGHT / 2.0f, 0));
                }
                else if (this.name == "bunny")
                {
                    draw.initialTransform = Matrix.CreateTranslation(new Vector3(0, Statics_Engine.PlayerSettings.DEFAULT_PLAYER_HEIGHT, 0));
                }
                else if (this.name == "fish")
                {
                    draw.initialTransform = Matrix.CreateRotationY((float)(-Math.PI)) *
                                       Matrix.CreateTranslation(new Vector3(0, -Statics_Engine.PlayerSettings.DEFAULT_PLAYER_HEIGHT / 2.0f + 5, 0));
                }
                else
                {
                    draw.initialTransform = Matrix.Identity;
                }

                return true;
            }
            catch
            {
                return false;
            }
        }

        public virtual string ToXml()
        {


            string xml =

                "\n" +
                "<" + this.type+ " " +
                " typeID=\"" + this.typeID + "\"" +
                " id=\"" + this.id + "\"" +

                " name=\"" + this.name  + "\"" +

                " x=\"" + this.x + "\"" +
                " z=\"" + this.z + "\"" +
                " grid=\"" + this.grid.ToString() + "\"" +

                " model=\"" + this.model + "\"" +
                " texture=\"" + this.texture + "\"" +
                " bumpTexture=\"" + this.bumpTexture + "\"" +

                " maxScale=\"" + this.maxScale  + "\"" +
                " animated=\"" + this.animated  + "\"" +
                "/>" +
                "\n";

            return xml;

            

            //switch (_grid)
            //{
            //    case GridType.unit:
            //        return "unit";
            //        break;
            //    case GridType.tile:
            //        return "tile";
            //        break;
            //    case GridType.coord:
            //        return "coord";
            //        break;
            //    default:
            //        return "unit";
            //        break;
            //}
        }

        /// <summary>
        /// Runs the appropriate animation on the model
        /// </summary>
        /// <param name="controller">The animation to run</param>
        public void RunController(AnimationController controller)
        {
            foreach (BonePose p in animator.BonePoses)
            {
                p.CurrentController = (IAnimationController)controller;
                p.CurrentBlendController = null;
            }
            currentAnimation = controller;
        }

        /// <summary>
        /// Update this object
        /// </summary>
        /// <param name="objectLibrary">ObjectLibrary</param>
        public virtual void Update(ref ObjectLibrary objectLibrary)
        {
            mass.Update();
            mass.boundingVolume.UpdateSphere(mass.currentPosition);
        }
        /// <summary>
        /// Method called when the motion buttons have been pressed
        /// </summary>
        /// <param name="value">Vector 2 amount of translation to induce</param>
        public virtual void Translate(Vector2 value)
        {
            mass.MoveObject(value.Y, value.X);
        }
        /// <summary>
        /// Method called when the rotation buttons have been pressed
        /// </summary>
        /// <param name="value">Vector 2 amount of rotation</param>
        public virtual void Rotate(Vector2 value)
        {
            mass.Rotate(value.Y, value.X);
        }
        /// <summary>
        /// Prepare to fire the particle
        /// </summary>
        /// <param name="playerID">ID of player who fired this particle</param>
        /// <param name="type">Type of particle</param>
        /// <param name="initialPosition">Starting position</param>
        /// <param name="direction">Normal direction of particle</param>
        /// <param name="particleMassValue">Mass of particle</param>
        /// <param name="particleThermalValue">Themal value of particle</param>
        public void PrepAttack(int playerID, Vector3 initialPosition, Vector3 direction)
        {
            //particleType = type;
            mass.AddForce(direction * Statics_Engine.PlayerSettings.DIRECT_ATTACK_FORCE);
            mass.SetPosition(initialPosition + (Statics_Engine.PlayerSettings.PARTICLE_START_DISTANCE * direction),
                             initialPosition + ((Statics_Engine.PlayerSettings.PARTICLE_START_DISTANCE + 10) * direction));
            mass.lastPosition = Vector3.Zero + initialPosition;
            //ownerID = playerID;

            direction.Normalize();
            mass.normalVector = Vector3.Zero + direction;

            // Scale size of particle; Apply DPad Values between 1 and 75
            mass.scale = new Vector3(75 / 2250.0f + 3);
            mass.boundingVolume.radius = mass.scale.Y;
            mass.boundingVolume.UpdateSphere(mass.currentPosition);

            // Change Engergy of particle; Shift from 0 to 100 to -1 to 1
            mass.energy = ((float)100 * 2 - 75) / 75;
            if (mass.energy != 0.0f) { mass.isChanging = true; }
        }

        /// <summary>
        /// Default behavior applies force based physics for rebound.
        /// Override to supplement behavior.
        /// Return true if collidedObject is absorbed or destroyed, else false.
        /// </summary>
        /// <param name="collidedObject">GameObject that collided with this GameObject.</param>
        /// <param name="resultantForce">The force applied by the colliding GameObject to this GameObject</param>
        /// <param name="objectLibrary">ObjectLibrary that this object belongs to.</param>
        /// <returns>True = this object has to be recycled</returns>
        public virtual bool ObjectCollisionResponse(GameObject collidedObject, Vector3 resultantForce, ObjectLibrary objectLibrary)
        {
            mass.numberOfCollision++;
            mass.isMoving = true;

            return false;
        }
        /// <summary>
        /// Default behavior applies force based physics for rebound.
        /// Override to supplement or replace behavior.
        /// </summary>
        /// <param name="terrain">A copy of the terrain, provided for overrides</param>
        public virtual void TerrainCollisionResponse(Terrain terrain)
        {
            CalculateVectorComponents(surfaceNormal, Statics_Engine.GameSettings.accelDueToGravity);
            gravityNormalComponent = normalComponent + Vector3.Zero;
            gravityTangentialComponent = tangentialComponent + Vector3.Zero;

            // Calculate the components of velocity with reference to the surface normal
            CalculateVectorComponents(surfaceNormal, mass.velocity);
            velocityNormalComponent = normalComponent + Vector3.Zero;
            velocityTangentialComponent = tangentialComponent + Vector3.Zero;

            // Calculate the components of totalForce with reference to the surface normal
            CalculateVectorComponents(surfaceNormal, mass.totalForce);
            totalForceNormalComponent = normalComponent + Vector3.Zero;
            totalForceTangentialComponent = tangentialComponent + Vector3.Zero;

            // Calculate normal and tangential forces with reference to the their components
            combinedNormalForce = (mass.mass * velocityNormalComponent / Statics_Engine.SystemSettings.dt) + totalForceNormalComponent;
            combinedTangentialForce = (mass.mass * velocityTangentialComponent / Statics_Engine.SystemSettings.dt) +
                                       totalForceTangentialComponent +
                                       (mass.mass * gravityTangentialComponent);

            mass.numberOfCollision++;
        }
        /// <summary>
        /// Calculate components of collision.
        /// </summary>
        /// <param name="surfaceNormal">Normal of surface that is being collided against.</param>
        /// <param name="objectVelocity">Velocity of this GameObject.</param>
        private void CalculateVectorComponents(Vector3 surfaceNormal, Vector3 objectVelocity)
        {
            if (objectVelocity.Length() >= 0.01f || !float.IsInfinity(objectVelocity.Length()))
            {
                normalComponent = Vector3.Dot(objectVelocity, surfaceNormal) * surfaceNormal;

                tangentialComponent = objectVelocity - normalComponent;
            }
            else
            {
                // Object velocity is equal to zero so set both components to zero
                normalComponent = Vector3.Zero;
                tangentialComponent = Vector3.Zero;
            }
        }
        /// <summary>
        /// Sets the heightDifference parameter for this GameObject.
        /// HeightDifference is positive when object is below the terrain.
        /// </summary>
        /// <param name="terrain">Terrain object to determine height difference with.</param>
        public void GetObjectHeight(Terrain terrain)
        {
            // Used to calculate the average terrain height around the player
            localTerrainHeight = 0;

            // Error check
            mass.EnforceLevelBoundary();

            // Get X and Z positions
            int posX = (int)mass.currentPosition.X;
            int posZ = (int)mass.currentPosition.Z;

            // Add up all neighboring terrain points
            localTerrainHeight = terrain.GetTerrainHeight(posX / Statics_Engine.TerrainSettings.terrainScaleFactor, 
                                                          posZ / Statics_Engine.TerrainSettings.terrainScaleFactor);
            surfaceNormal = terrain.GetTerrainNormal(posX / Statics_Engine.TerrainSettings.terrainScaleFactor, 
                                                     posZ / Statics_Engine.TerrainSettings.terrainScaleFactor, mass.velocity);

            // Average all the neighboring terrain points
            float newHeight = localTerrainHeight + (mass.objectHeight / 2);

            // Get difference between average and player
            heightDifference = newHeight - mass.currentPosition.Y;
        }

        /// <summary>
        /// Draws a stand alone model with no instancing or triangle stripping.
        /// </summary>
        public virtual Draw Draw()
        {
            return this.draw;
        }
        /// <summary>
        /// Draws the model for ai vision
        /// </summary>
        public virtual void DrawAIVision()
        {
        }
        /// <summary>
        /// Called after Draw method.
        /// Allows GameObjects to add Billboards to be drawn.
        /// List is cleared each pass, or frame.
        /// </summary>
        /// <param name="objectLibrary">ObjectLibrary that this object belongs to.</param>
        /// <param name="gameObjectBillboards">Dynamic Billboard list.</param>
        public virtual void Draw_Billboards(ObjectLibrary objectLibrary, BillboardList gameObjectBillboards)
        {
            
        }
        #endregion
    }
}

#region Legacy Code - Heat dissipation and State Change
/* Legacy Code
        // Test Function to update energy distribution
        public void UpdateHeatDissipation(ObjectLibrary objectLibrary, float dt)
        {
            List<Particle> newParticlesToAddToList = new List<Particle>();

            foreach (Particle particle in particles)
            {
                particle.mass.UpdatePosition(dt);
            }

            // Iterate through dynamic particles
            for (int i = 0; i < particles.Count; i++)
            {
                Particle particle = particles[i];

                // List to store the pointers to neighboring particles
                List<Particle> neighboringParticleList = new List<Particle>();

                // Used to calculate the average value of all neighboring particles
                float averageEnergy = particle.mass.energy;

                // Get the list of neighboring particle pointers
                neighboringParticleList = GetSurroundingParticles(particle.mass.currentPosition, objectLibrary);

                // Calculate their total energy
                foreach (Particle neigborParticle in neighboringParticleList)
                {
                    averageEnergy += neigborParticle.mass.energy;
                }

                // Find the average
                averageEnergy /= neighboringParticleList.Count + 1;

                // If the average value is not equal to zero then we have some heat dissipation to do
                if (averageEnergy != particle.mass.energy && neighboringParticleList.Count != 0)
                {
                    // Iterate through neighboring particles
                    foreach (Particle neigborParticle in neighboringParticleList)
                    {
                        // If their value is not equal to the average set it and set the flag
                        if (neigborParticle.mass.energy != averageEnergy)
                        {
                            neigborParticle.mass.SetEnergy(averageEnergy);

                            if (!neigborParticle.mass.isChanging)
                            {
                                neigborParticle.mass.isChanging = true;
                                newParticlesToAddToList.Add(neigborParticle);
                            }
                        }
                    }

                    // Set the starting particle's energy to average value as well
                    particle.mass.SetEnergy(averageEnergy);
                }
                else if (particle.mass.energy != 0.0f)
                {
                    DissipateHeat(particle);
                }
                else
                {
                    // The particle and it's neighbors are in equilibrium so remove it from the list
                    particle.mass.isChanging = false;
                    if (!particle.mass.isMoving)
                    {
                        // Particle needs to be put back into the static list
                        MoveParticle(particles, particles, particle);
                    }
                    else
                    {
                        particles.Remove(particle);
                    }

                    i--;
                }
            }

            foreach (Particle newParticle in newParticlesToAddToList)
            {
                if (particles.Contains(newParticle))
                {
                    continue;
                }

                if (newParticle.mass.isMoving)
                {
                    CopyParticle(particles, particles, newParticle);
                }
                else
                {
                    MoveParticle(particles, particles, newParticle);
                }
            }
        }
        
        public void StateChange(ObjectLibrary objectLibrary, float elementUse, GameObject gameObject)
        {
            Particle particle = (Particle)gameObject;

            gameObject.mass.ChangeEnergy(elementUse / 2.0f);

            if (gameObject.mass.isChanging)
            {
                return;
            }
            else if (gameObject.mass.isMoving)
            {
                // Particle is in the dynamic list so we need to copy it to the Thermal list
                switch (particle.particleType)
                {
                    case Particle.ParticleType.Air:
                        {
                            objectLibrary.particleManager.air.CopyParticle(objectLibrary.particleManager.air.dynamicParticles,
                                                                             objectLibrary.particleManager.air.thermalParticles,
                                                                             particle);
                            break;
                        }
                    case Particle.ParticleType.Water:
                        {
                            objectLibrary.particleManager.water.CopyParticle(objectLibrary.particleManager.water.dynamicParticles,
                                                                                objectLibrary.particleManager.water.thermalParticles,
                                                                                particle);
                            break;
                        }
                    case Particle.ParticleType.Earth:
                        {
                            objectLibrary.particleManager.earth.CopyParticle(objectLibrary.particleManager.earth.dynamicParticles,
                                                                               objectLibrary.particleManager.earth.thermalParticles,
                                                                               particle);
                            break;
                        }
                }
                gameObject.mass.isChanging = true;
            }
            else
            {
                // It's in the static list, so move the particle to the dynamic list
                switch (particle.particleType)
                {
                    case Particle.ParticleType.Air:
                        {
                            objectLibrary.particleManager.air.MoveParticle(objectLibrary.particleManager.air.staticParticles,
                                                                             objectLibrary.particleManager.air.thermalParticles,
                                                                             particle);
                            break;
                        }
                    case Particle.ParticleType.Water:
                        {
                            objectLibrary.particleManager.water.MoveParticle(objectLibrary.particleManager.water.staticParticles,
                                                                                objectLibrary.particleManager.water.thermalParticles,
                                                                                particle);
                            break;
                        }
                    case Particle.ParticleType.Earth:
                        {
                            objectLibrary.particleManager.earth.MoveParticle(objectLibrary.particleManager.earth.staticParticles,
                                                                               objectLibrary.particleManager.earth.thermalParticles,
                                                                               particle);
                            break;
                        }
                }
                gameObject.mass.isChanging = true;
            }
        }
*/
#endregion