/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright © 2000-2004 The OGRE Team
Also see acknowledgements in Readme.html

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
-----------------------------------------------------------------------------
*/
#include "OgreStableHeaders.h"
#include "OgreLight.h"
#include "OgreEdgeListBuilder.h"

namespace Ogre {
    // ------------------------------------------------------------------------
    void ShadowCaster::updateEdgeListLightFacing(EdgeData* edgeData, 
        const Vector4& lightPos)
    {
        edgeData->updateTriangleLightFacing(lightPos);
    }
    // ------------------------------------------------------------------------
    void ShadowCaster::generateShadowVolume(EdgeData* edgeData, 
        HardwareIndexBufferSharedPtr indexBuffer, const Light* light,
        ShadowRenderableList& shadowRenderables, unsigned long flags)
    {
        // Edge groups should be 1:1 with shadow renderables
        assert(edgeData->edgeGroups.size() == shadowRenderables.size());

        EdgeData::EdgeGroupList::iterator egi, egiend;
        ShadowRenderableList::iterator si;

        Light::LightTypes lightType = light->getType();

        // Lock index buffer for writing
        unsigned short* pIdx = static_cast<unsigned short*>(
            indexBuffer->lock(HardwareBuffer::HBL_DISCARD));
        size_t indexStart = 0;

        // Iterate over the groups and form renderables for each based on their
        // lightFacing
        si = shadowRenderables.begin();
        egiend = edgeData->edgeGroups.end();
        for (egi = edgeData->edgeGroups.begin(); egi != egiend; ++egi, ++si)
        {
            EdgeData::EdgeGroup& eg = *egi;
            RenderOperation* lightShadOp = 0;
            // Initialise the index bounds for this shadow renderable
            RenderOperation* shadOp = (*si)->getRenderOperationForUpdate();
            shadOp->indexData->indexCount = 0;
            shadOp->indexData->indexStart = indexStart;
            // original number of verts (without extruded copy)
            size_t originalVertexCount = eg.vertexData->vertexCount;
            bool  firstDarkCapTri = true;
            unsigned short darkCapStart;

            EdgeData::EdgeList::iterator i, iend;
            iend = eg.edges.end();
            for (i = eg.edges.begin(); i != iend; ++i)
            {
                EdgeData::Edge& edge = *i;

                EdgeData::Triangle &t1 = edgeData->triangles[edge.triIndex[0]];
                EdgeData::Triangle &t2 = 
                    edge.degenerate? edgeData->triangles[edge.triIndex[0]] : edgeData->triangles[edge.triIndex[1]];
                if (t1.lightFacing && (edge.degenerate || !t2.lightFacing))
                {
                    /* Silhouette edge, first tri facing the light
                    Also covers degenerate tris where only tri 1 is valid
                    Remember verts run anticlockwise along the edge from 
                    tri 0 so to point shadow volume tris outward, light cap 
                    indexes have to be backwards

                    We emit 2 tris if light is a point light, 1 if light 
                    is directional, because directional lights cause all
                    points to converge to a single point at infinity.

                    First side tri = near1, near0, far0
                    Second tri = far0, far1, near1

                    'far' indexes are 'near' index + originalVertexCount
                    because 'far' verts are in the second half of the 
                    buffer
                    */
                    *pIdx++ = edge.vertIndex[1];
                    *pIdx++ = edge.vertIndex[0];
                    *pIdx++ = edge.vertIndex[0] + originalVertexCount;
                    shadOp->indexData->indexCount += 3;

                    // Are we extruding to infinity?
                    if (!(lightType == Light::LT_DIRECTIONAL &&
                        flags & SRF_EXTRUDE_TO_INFINITY))
                    {
                        // additional tri to make quad
                        *pIdx++ = edge.vertIndex[0] + originalVertexCount;
                        *pIdx++ = edge.vertIndex[1] + originalVertexCount;
                        *pIdx++ = edge.vertIndex[1];
                        shadOp->indexData->indexCount += 3;
                    }
                    // Do dark cap tri
                    // Use McGuire et al method, a triangle fan covering all silhouette
                    // edges and one point (taken from the initial tri)
                    if (flags & SRF_INCLUDE_DARK_CAP)
                    {
                        if (firstDarkCapTri)
                        {
                            darkCapStart = edge.vertIndex[0] + originalVertexCount;
                            firstDarkCapTri = false;
                        }
                        else
                        {
                            *pIdx++ = darkCapStart;
                            *pIdx++ = edge.vertIndex[1] + originalVertexCount;
                            *pIdx++ = edge.vertIndex[0] + originalVertexCount;
                            shadOp->indexData->indexCount += 3;
                        }

                    }
                }
                else if (!t1.lightFacing && (edge.degenerate || t2.lightFacing))
                {
                    // Silhouette edge, second tri facing the light
                    // Note edge indexes inverse of when t1 is light facing 
                    *pIdx++ = edge.vertIndex[0];
                    *pIdx++ = edge.vertIndex[1];
                    *pIdx++ = edge.vertIndex[1] + originalVertexCount;
                    shadOp->indexData->indexCount += 3;

                    // Are we extruding to infinity?
                    if (!(lightType == Light::LT_DIRECTIONAL &&
                        flags & SRF_EXTRUDE_TO_INFINITY))
                    {
                        // additional tri to make quad
                        *pIdx++ = edge.vertIndex[1] + originalVertexCount;
                        *pIdx++ = edge.vertIndex[0] + originalVertexCount;
                        *pIdx++ = edge.vertIndex[0];
                        shadOp->indexData->indexCount += 3;
                    }
                    // Do dark cap tri
                    // Use McGuire et al method, a triangle fan covering all silhouette
                    // edges and one point (taken from the initial tri)
                    if (flags & SRF_INCLUDE_DARK_CAP)
                    {
                        if (firstDarkCapTri)
                        {
                            darkCapStart = edge.vertIndex[1] + originalVertexCount;
                            firstDarkCapTri = false;
                        }
                        else
                        {
                            *pIdx++ = darkCapStart;
                            *pIdx++ = edge.vertIndex[0] + originalVertexCount;
                            *pIdx++ = edge.vertIndex[1] + originalVertexCount;
                            shadOp->indexData->indexCount += 3;
                        }

                    }
                }

            }

            // Do light cap
            if (flags & SRF_INCLUDE_LIGHT_CAP) 
            {
                ShadowRenderable* lightCapRend = 0;

                if ((*si)->isLightCapSeparate())
                {
                    // separate light cap
                    lightCapRend = (*si)->getLightCapRenderable();
                    lightShadOp = lightCapRend->getRenderOperationForUpdate();
                    lightShadOp->indexData->indexCount = 0;
                    // start indexes after the current total
                    // NB we don't update the total here since that's done below
                    lightShadOp->indexData->indexStart = 
                        indexStart + shadOp->indexData->indexCount;
                }

                EdgeData::TriangleList::iterator ti, tiend;
                tiend = edgeData->triangles.end();
                for (ti = edgeData->triangles.begin(); ti != tiend; ++ti)
                {
                    EdgeData::Triangle& t = *ti;
                    // Light facing, and vertex set matches
                    if (t.lightFacing && t.vertexSet == eg.vertexSet)
                    {
                        *pIdx++ = t.vertIndex[0];
                        *pIdx++ = t.vertIndex[1];
                        *pIdx++ = t.vertIndex[2];
                        if (lightShadOp)
                        {
                            lightShadOp->indexData->indexCount += 3;
                        }
                        else
                        {
                            shadOp->indexData->indexCount += 3;
                        }
                    }
                }

            }
            // update next indexStart (all renderables sharing the buffer)
            indexStart += shadOp->indexData->indexCount;
            // Add on the light cap too
            if (lightShadOp)
                indexStart += lightShadOp->indexData->indexCount;


        }


        // Unlock index buffer
        indexBuffer->unlock();

		// In debug mode, check we didn't overrun the index buffer
		assert(indexStart <= indexBuffer->getNumIndexes() &&
            "Index buffer overrun while generating shadow volume!! "
			"You must increase the size of the shadow index buffer.");

    }
    // ------------------------------------------------------------------------
    void ShadowCaster::extrudeVertices(
        HardwareVertexBufferSharedPtr vertexBuffer, 
        size_t originalVertexCount, const Vector4& light, Real extrudeDist)
    {
        assert (vertexBuffer->getVertexSize() == sizeof(Real) * 3
            && "Position buffer should contain only positions!");

        // Extrude the first area of the buffer into the second area
        // Lock the entire buffer for writing, even though we'll only be
        // updating the latter because you can't have 2 locks on the same
        // buffer
        Real* pSrc = static_cast<Real*>(
            vertexBuffer->lock(HardwareBuffer::HBL_NORMAL));

        Real* pDest = pSrc + originalVertexCount * 3;
        // Assume directional light, extrusion is along light direction
        Vector3 extrusionDir(-light.x, -light.y, -light.z);
        extrusionDir.normalise();
        extrusionDir *= extrudeDist;
        for (size_t vert = 0; vert < originalVertexCount; ++vert)
        {
            if (light.w != 0.0f)
            {
                // Point light, adjust extrusionDir
                extrusionDir.x = pSrc[0] - light.x;
                extrusionDir.y = pSrc[1] - light.y;
                extrusionDir.z = pSrc[2] - light.z;
                extrusionDir.normalise();
                extrusionDir *= extrudeDist;
            }
            *pDest++ = *pSrc++ + extrusionDir.x;
            *pDest++ = *pSrc++ + extrusionDir.y;
            *pDest++ = *pSrc++ + extrusionDir.z;

        }
        vertexBuffer->unlock();

    }
    // ------------------------------------------------------------------------
    void ShadowCaster::extrudeBounds(AxisAlignedBox& box, const Vector4& light, Real extrudeDist) const
    {
        Vector3 extrusionDir;

        if (light.w == 0)
        {
            // Parallel projection guarantees min/max relationship remains the same
            extrusionDir.x = -light.x;
            extrusionDir.y = -light.y;
            extrusionDir.z = -light.z;
            extrusionDir.normalise();
            extrusionDir *= extrudeDist;
            box.setExtents(box.getMinimum() + extrusionDir, 
                box.getMaximum() + extrusionDir);
        }
        else
        {
            const Vector3* corners = box.getAllCorners();
            Vector3 vmin, vmax;

            for (unsigned short i = 0; i < 8; ++i)
            {
                extrusionDir.x = corners[i].x - light.x;
                extrusionDir.y = corners[i].y - light.y;
                extrusionDir.z = corners[i].z - light.z;
                extrusionDir.normalise();
                extrusionDir *= extrudeDist;
                Vector3 res = corners[i] + extrusionDir;
                if (i == 0)
                {
                    vmin = res;
                    vmax = res;
                }
                else
                {
                    vmin.makeFloor(res);
                    vmax.makeCeil(res);
                }
            }

            box.setExtents(vmin, vmax);

        }

    }
    // ------------------------------------------------------------------------
    Real ShadowCaster::getExtrusionDistance(const Vector3& objectPos, const Light* light) const
    {
        Vector3 diff = objectPos - light->getDerivedPosition();
        return light->getAttenuationRange() - diff.length();
    }

}
