//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;
using System.Drawing;

namespace DXGfxLib
{
    /// <summary>
    /// Billboards are texture quad always watching the camera. They are some time useful as impostors when you don't want
    /// to render a very complex object. Texture displayed by billboard should have an alpha channel in order not to occlude
    /// everything behind. The library will sort them approiately from back to front before rendering them with alpha.
    /// This class inherits from TexturedPlane, main differences are that TexturePlanes don't always look at the camera and
    /// are not necessarily rendered using alpha blending.
    /// </summary>
    internal class Billboard : TexturedPlane, IComparable
    {
        public static Matrix currentFrameBillboardRot;
        public static double lastRotUpdate = -1;

        /// <summary>
        /// All instances of this class are anticipated to use the same RenderState so let's keep that one in this
        /// member variable.
        /// </summary>
        public static StateBlock billboardRenderState = null;

        /// <summary>
        /// The distance to the viewing frustrum near plane in calculated in the Update phase. The value is keep in this
        /// member variable. The default value is 1000 so everything works all right if you don't plan to render objects
        /// farther than that (let's say the far plance distance to eye point is 1000). If you plan to render objects farther
        /// away from the viewpoint you will have to change this value. If you don't billbard instances might be rendering in
        /// an incorrect order for the alpha blending to provide the desired result.
        /// </summary>
        public double distToNearPlane = 1000.0f;

        /// <summary>
        /// In order to create a billboard instance we want the device to which it will be associated, its dimension the
        /// number or vertices to put along each dimension and the file name of the texture to use.
        /// </summary>
        /// <param name="d3ddevice">A valid Direct3D device to which the billboard will be rendered.</param>
        /// <param name="width"></param>
        /// <param name="heigth"></param>
        /// <param name="numVertX"></param>
        /// <param name="numVertZ"></param>
        /// <param name="texturefile">The texture to be mapped on out Billboard quad.</param>
        public Billboard(Device d3ddevice, int width, int heigth, int numVertX, int numVertZ, string texturefile)
            :
            base(d3ddevice, width, heigth, numVertX, numVertZ, texturefile)
        {
        }

        /// <summary>
        /// This construction creates a billboard from another billboard.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="srcBillBoard"></param>
        public Billboard(Device d3ddevice, Billboard srcBillBoard)
            : base(d3ddevice, srcBillBoard.width, srcBillBoard.heigth, srcBillBoard.numVertX, srcBillBoard.numVertZ, srcBillBoard.textureFileName)
        {
        }

        /// <summary>
        /// Billboards need to be sorted back to front, this method will be used by our sorted list to determine where
        /// a specific billboard instance should be put in the list.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override int CompareTo(Object obj)
        {
            Billboard otherBillboard = obj as Billboard;

            int retVal = 1;

            if (obj != null)
            {
                if (distToNearPlane > otherBillboard.distToNearPlane)
                {
                    retVal = -1;
                }
            }
            return retVal;
        }

        /// <summary>
        /// The usual upadte method implemented by each SceneObject or derived class. For the Billboard this method is used
        /// to position the billboard facing the camera. When you instanciate a billboard you need to either add it to the scene
        /// or to call this method yourself. If neither is done it is not ensured the billboard will face the camera nor that its
        /// distance to the frustrum near plane is computed which might result in very strange visual effects (and most probably
        /// unwanted).
        /// </summary>
        /// <param name="frustrum"></param>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        public override void Update(Frustrum frustrum, double appTime, float elapsedTime)
        {
            if (lastRotUpdate != appTime)
            {
                Matrix billboard = frustrum.matView;
                billboard.M41 = 0.0f;
                billboard.M42 = 0.0f;
                billboard.M43 = 0.0f;
                Quaternion q = Quaternion.RotationMatrix(billboard);
                q.Invert();
                q.X = 0.0f;
                q.Z = 0.0f;
                q.Normalize();


                currentFrameBillboardRot = Matrix.RotationQuaternion(q);
                lastRotUpdate = appTime;
            }

            worldMat.M11 = currentFrameBillboardRot.M11;
            worldMat.M12 = currentFrameBillboardRot.M12;
            worldMat.M13 = currentFrameBillboardRot.M13;

            worldMat.M21 = currentFrameBillboardRot.M21;
            worldMat.M22 = currentFrameBillboardRot.M22;
            worldMat.M23 = currentFrameBillboardRot.M23;

            worldMat.M31 = currentFrameBillboardRot.M31;
            worldMat.M32 = currentFrameBillboardRot.M32;
            worldMat.M33 = currentFrameBillboardRot.M33;
            distToNearPlane = MathUtil.SignedDistance(frustrum.NearPlane, Position);

            base.Update(frustrum, appTime, elapsedTime);
        }

        /// <summary>
        /// The usual draw method.
        /// </summary>
        /// <param name="device"></param>
        public override void Draw(Device device)
        {
            if (billboardRenderState == null)
            {
                device.Material = material;


                device.RenderState.AlphaBlendEnable = true;

                device.RenderState.SourceBlend = Blend.SourceAlpha;
                device.RenderState.DestinationBlend = Blend.InvSourceAlpha;

                device.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;
                device.TextureState[0].AlphaOperation = TextureOperation.SelectArg1;
                device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
                device.TextureState[0].ColorArgument2 = TextureArgument.Diffuse;
                device.TextureState[0].ColorOperation = TextureOperation.Modulate;

                billboardRenderState = new StateBlock(device, StateBlockType.All);
                billboardRenderState.Capture();
            }

            device.SetTexture(0, texture);
            
            device.SetTransform(TransformType.World, worldMat);


            originalMesh.DrawSubset(0);
        }
    }
}
