
#include "CAlignedBillboardSceneNode.h"

// Irrlicht includes
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "ICameraSceneNode.h"

#include <float.h> // For FLT_EPSILON


using namespace irr;

//! constructor
CAlignedBillboardSceneNode::CAlignedBillboardSceneNode(scene::ISceneNode* parent,
            scene::ISceneManager* sceneManager,
            s32 id,
            const core::dimension2d<f32>& size,
            bool positionIsCenter,
            const core::vector3df& position,
            const core::vector3df& alignment,
            video::SColor shade_top,
            video::SColor shade_down)
: ISceneNode(parent, sceneManager, id, position)
{
    #ifdef _DEBUG
    setDebugName("CAlignedBillboardSceneNode");
    #endif

    setAlignment(alignment);
    FaceCameraBy = FACE_TOWARDS_FROM_CENTER;
    PositionIsCenter = positionIsCenter;

    setSize(size);

    indices[0] = 0;
    indices[1] = 2;
    indices[2] = 1;
    indices[3] = 0;
    indices[4] = 3;
    indices[5] = 2;

    vertices[0].TCoords.set(1.0f, 1.0f);
    vertices[0].Color = shade_down;

    vertices[1].TCoords.set(1.0f, 0.0f);
    vertices[1].Color = shade_top;

    vertices[2].TCoords.set(0.0f, 0.0f);
    vertices[2].Color = shade_top;

    vertices[3].TCoords.set(0.0f, 1.0f);
    vertices[3].Color = shade_down;
}



CAlignedBillboardSceneNode::~CAlignedBillboardSceneNode()
{
}



//! pre render event
void CAlignedBillboardSceneNode::OnRegisterSceneNode()
{
    if (IsVisible)
        SceneManager->registerNodeForRendering(this);

    ISceneNode::OnRegisterSceneNode();
}


//! render
void CAlignedBillboardSceneNode::render()
{
    video::IVideoDriver* driver = SceneManager->getVideoDriver();
    scene::ICameraSceneNode* camera = SceneManager->getActiveCamera();

    if (!camera || !driver)
        return;

    core::vector3df length(Alignment * Height);
    core::vector3df halfLength(length * 0.5f);
    core::vector3df myStartPoint(getAbsolutePosition());
    core::vector3df myEndPoint(myStartPoint + length);
    core::vector3df myOrthogonalStart;
    core::vector3df myOrthogonalEnd;
    core::vector3df myUpStart;
    core::vector3df myUpEnd;

    if(PositionIsCenter)
    {
        myStartPoint -= halfLength;
        myEndPoint -= halfLength;
    }

    switch(FaceCameraBy)
    {
    case FACE_OPPOSITE:
        myUpStart = camera->getTarget() - camera->getAbsolutePosition();
        myUpStart *= -1.0f;
        myUpStart.normalize();
        myOrthogonalStart = Alignment.crossProduct(myUpStart);
        myOrthogonalStart *= BottomWidth * 0.5f;
        myUpEnd = myUpStart;
        myOrthogonalEnd = myOrthogonalStart;
        break;

    case FACE_TOWARDS_FROM_ENDS:
        // Make each end twist to face the camera position independently
        myUpStart = camera->getAbsolutePosition() - myStartPoint;
        myUpStart.normalize();
        myOrthogonalStart = Alignment.crossProduct(myUpStart);
        myOrthogonalStart *= BottomWidth * 0.5f;

        myUpEnd = camera->getAbsolutePosition() - myEndPoint;
        myUpEnd.normalize();
        myOrthogonalEnd = Alignment.crossProduct(myUpEnd);
        myOrthogonalEnd *= TopWidth * 0.5f;
        break;

    case FACE_TOWARDS_FROM_CENTER:
    default:
        {
        core::vector3df myCentre(getAbsolutePosition() + halfLength);
        myUpStart = camera->getAbsolutePosition() - myCentre;
        myUpStart.normalize();
        myOrthogonalStart = Alignment.crossProduct(myUpStart);
        myOrthogonalStart *= BottomWidth * 0.5f;

        myUpEnd = myUpStart;
        myOrthogonalEnd = myOrthogonalStart * TopWidth / BottomWidth;
        }
        break;
    }

    /* Vertices are:
    3--0
    | /|
    |/ |
    2--1
    */

    vertices[0].Normal = myUpEnd;
    vertices[0].Pos = myEndPoint + myOrthogonalEnd;

    vertices[1].Normal = myUpStart;
    vertices[1].Pos = myStartPoint + myOrthogonalStart;

    vertices[2].Normal = myUpStart;
    vertices[2].Pos = myStartPoint - myOrthogonalStart;

    vertices[3].Normal = myUpEnd;
    vertices[3].Pos = myEndPoint - myOrthogonalEnd;


    if (DebugDataVisible)
    {
        driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
        video::SMaterial m;
        m.Lighting = false;
        driver->setMaterial(m);
        driver->draw3DBox(BBox, video::SColor(0,208,195,152));
    }

    core::matrix4 mat;
    driver->setTransform(video::ETS_WORLD, mat);

    driver->setMaterial(Material);

    driver->drawIndexedTriangleList(vertices, 4, indices, 2);
}


//! sets the size of the billboard
void CAlignedBillboardSceneNode::setSize(const core::dimension2d<f32>& size)
{
    setSize(size.Width, size.Width, size.Height);
}

void CAlignedBillboardSceneNode::setSize(f32 bottomWidth,
                                         f32 topWidth,
                                         f32 height)
{
    BottomWidth = bottomWidth;
    TopWidth = topWidth;
    Height = height;

    if (Height == 0.0f )
        Height = 1.0f;

    f32 avg = (BottomWidth + Height)/6;
    BBox.MinEdge.set(-avg,-avg,-avg);
    BBox.MaxEdge.set(avg,avg,avg);
}


//! Writes attributes of the scene node.
void CAlignedBillboardSceneNode::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options)
{
    ISceneNode::serializeAttributes(out, options);

    out->addVector3d("Alignment", Alignment);
    out->addInt("FaceCameraBy", static_cast<s32>(FaceCameraBy));
    out->addBool("PositionIsCenter", PositionIsCenter);
}

//! Reads attributes of the scene node.
void CAlignedBillboardSceneNode::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
{
    ISceneNode::deserializeAttributes(in, options);

    core::vector3df alignment(in->getAttributeAsVector3d("Alignment"));
    setAlignment(alignment);

    FaceCameraBy = static_cast<TFaceCameraBy>(in->getAttributeAsInt("FaceCameraBy"));
    PositionIsCenter = in->getAttributeAsBool("PositionIsCenter");
}

//! Sets the alignment of the billboard.
void CAlignedBillboardSceneNode::setAlignment(const core::vector3df & alignment)
{
    if(fabs(alignment.X) > FLT_EPSILON || fabs(alignment.Y) > FLT_EPSILON || fabs(alignment.Z) > FLT_EPSILON)
    {
        Alignment = alignment;
        Alignment.normalize();
    }
    else
    {
        Alignment = core::vector3df(0.f, 1.f, 0.f);
    }
}


//! An alternative way to specify the location and size of the billboard.
//! You could use this for (e.g.) a "beam" effect.  It sets the position,
//! alignment and height of the billboard, but does not change its width
void CAlignedBillboardSceneNode::setStartAndEndPositions(const core::vector3df & startPosition,
                                     const core::vector3df & endPosition)
{
    setPosition(startPosition);

    core::vector3df alignment = endPosition - startPosition;

    Height = alignment.getLength();

    alignment.normalize();

    setAlignment(alignment);
}

void CAlignedBillboardSceneNode::translateTextureCoordinates(const core::dimension2df & translateBy)
{
    for(int vertex = 0; vertex < 4; ++vertex)
        vertices[vertex].TCoords.set(vertices[vertex].TCoords.X + translateBy.Width,
                                    vertices[vertex].TCoords.Y + translateBy.Height);
}

//! Set the color of all vertices of the billboard
//! \param overallColor: the color to set
void CAlignedBillboardSceneNode::setColor(const video::SColor & overallColor)
{
    for(int vertex = 0; vertex < 4; ++vertex)
        vertices[0].Color = overallColor;
}

//! Set the color of the top and bottom vertices of the billboard
//! \param topColor: the color to set the top vertices
//! \param bottomColor: the color to set the bottom vertices
void CAlignedBillboardSceneNode::setColor(const video::SColor & topColor, const video::SColor & bottomColor)
{
    vertices[0].Color = bottomColor;
    vertices[1].Color = topColor;
    vertices[2].Color = topColor;
    vertices[3].Color = bottomColor;
}

//! Gets the color of the top and bottom vertices of the billboard
//! \param[out] topColor: stores the color of the top vertices
//! \param[out] bottomColor: stores the color of the bottom vertices
void CAlignedBillboardSceneNode::getColor(video::SColor & topColor, video::SColor & bottomColor)
{
    bottomColor = vertices[0].Color;
    topColor = vertices[1].Color;
}

