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

Copyright (c) 2000-2006 Torus Knot Software Ltd
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.

You may alternatively use this source under the terms of a specific version of
the OGRE Unrestricted License provided you have obtained such a license from
Torus Knot Software Ltd.
-----------------------------------------------------------------------------
*/

#include "OgreStableHeaders.h"
#include "OgreShadowVolumeExtrudeProgram.h"
#include "OgreString.h"
#include "OgreGpuProgramManager.h"
#include "OgreGpuProgram.h"
#include "OgreException.h"

namespace Ogre {

    // c4 is the light position/direction in these
    String ShadowVolumeExtrudeProgram::mPointArbvp1 = 
        "!!ARBvp1.0\n"
        "PARAM c5 = { 0, 0, 0, 0 };\n"
        "TEMP R0;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c4 = program.local[4];\n"
        "ADD R0.xyz, v16.xyzx, -c4.xyzx;\n"
        "MOV R0.w, c5.x;\n"
        "MAD R0, v24.x, c4, R0;\n"
        "DP4 result.position.x, c0[0], R0;\n"
        "DP4 result.position.y, c0[1], R0;\n"
        "DP4 result.position.z, c0[2], R0;\n"
        "DP4 result.position.w, c0[3], R0;\n"
        "END\n";

    String ShadowVolumeExtrudeProgram::mPointVs_1_1 = 
        "vs_1_1\n"
        "def c5, 0, 0, 0, 0\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "add r0.xyz, v0.xyz, -c4.xyz\n"
        "mov r0.w, c5.x\n"
        "mad r0, v7.x, c4, r0\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n";

    String ShadowVolumeExtrudeProgram::mDirArbvp1 = 
        "!!ARBvp1.0\n"
        "TEMP R0;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c4 = program.local[4];\n"
        "ADD R0, v16, c4;\n"
        "MAD R0, v24.x, R0, -c4;\n"
        "DP4 result.position.x, c0[0], R0;\n"
        "DP4 result.position.y, c0[1], R0;\n"
        "DP4 result.position.z, c0[2], R0;\n"
        "DP4 result.position.w, c0[3], R0;\n"
        "END\n";

    String ShadowVolumeExtrudeProgram::mDirVs_1_1 = 
        "vs_1_1\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "add r0, v0, c4\n"
        "mad r0, v7.x, r0, -c4\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n";


    String ShadowVolumeExtrudeProgram::mPointArbvp1Debug = 
        "!!ARBvp1.0\n"
        "PARAM c5 = { 0, 0, 0, 0 };\n"
        "PARAM c6 = { 1, 1, 1, 1 };\n"
        "TEMP R0;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c4 = program.local[4];\n"
        "ADD R0.xyz, v16.xyzx, -c4.xyzx;\n"
        "MOV R0.w, c5.x;\n"
        "MAD R0, v24.x, c4, R0;\n"
        "DP4 result.position.x, c0[0], R0;\n"
        "DP4 result.position.y, c0[1], R0;\n"
        "DP4 result.position.z, c0[2], R0;\n"
        "DP4 result.position.w, c0[3], R0;\n"
        "MOV result.color.front.primary, c6.x;\n"
        "END\n";

    String ShadowVolumeExtrudeProgram::mPointVs_1_1Debug = 
        "vs_1_1\n"
        "def c5, 0, 0, 0, 0\n"
        "def c6, 1, 1, 1, 1\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "add r0.xyz, v0.xyz, -c4.xyz\n"
        "mov r0.w, c5.x\n"
        "mad r0, v7.x, c4, r0\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n"
        "mov oD0, c6.x\n";

    String ShadowVolumeExtrudeProgram::mDirArbvp1Debug = 
        "!!ARBvp1.0\n"
        "PARAM c5 = { 1, 1, 1, 1};\n"
        "TEMP R0;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c4 = program.local[4];\n"
        "ADD R0, v16, c4;\n"
        "MAD R0, v24.x, R0, -c4;\n"
        "DP4 result.position.x, c0[0], R0;\n"
        "DP4 result.position.y, c0[1], R0;\n"
        "DP4 result.position.z, c0[2], R0;\n"
        "DP4 result.position.w, c0[3], R0;\n"
        "MOV result.color.front.primary, c5.x;"
        "END\n";

    String ShadowVolumeExtrudeProgram::mDirVs_1_1Debug = 
        "vs_1_1\n"
        "def c5, 1, 1, 1, 1\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "add r0, v0, c4\n"
        "mad r0, v7.x, r0, -c4\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n"
        "mov oD0, c5.x\n";


    // c4 is the light position/direction in these
    // c5 is extrusion distance
    String ShadowVolumeExtrudeProgram::mPointArbvp1Finite = 
        "!!ARBvp1.0\n" 
        "PARAM c6 = { 1, 0, 0, 0 };\n"
        "TEMP R0, R1;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c5 = program.local[5];\n"
        "PARAM c4 = program.local[4];\n"
        "ADD R0.x, c6.x, -v24.x;\n"
        "MUL R0.w, R0.x, c5.x;\n"
        "ADD R0.xyz, v16.xyzx, -c4.xyzx;\n"
        "DP3 R1.w, R0.xyzx, R0.xyzx;\n"     // R1.w = Vector3(vertex - lightpos).sqrLength()
        "RSQ R1.w, R1.w;\n"                 // R1.w = 1 / Vector3(vertex - lightpos).length()
        "MUL R0.xyz, R1.w, R0.xyzx;\n"      // R0.xyz = Vector3(vertex - lightpos).normalisedCopy()
        "MAD R0.xyz, R0.w, R0.xyzx, v16.xyzx;\n"
        "DPH result.position.x, R0.xyzz, c0[0];\n"
        "DPH result.position.y, R0.xyzz, c0[1];\n"
        "DPH result.position.z, R0.xyzz, c0[2];\n"
        "DPH result.position.w, R0.xyzz, c0[3];\n"
        "END\n";

    String ShadowVolumeExtrudeProgram::mPointVs_1_1Finite = 
        "vs_1_1\n"
        "def c6, 1, 0, 0, 0\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "add r0.x, c6.x, -v7.x\n"
        "mul r1.x, r0.x, c5.x\n"
        "add r0.yzw, v0.xxyz, -c4.xxyz\n"
        "dp3 r0.x, r0.yzw, r0.yzw\n"
        "rsq r0.x, r0.x\n"
        "mul r0.xyz, r0.x, r0.yzw\n"
        "mad r0.xyz, r1.x, r0.xyz, v0.xyz\n"
        "mov r0.w, c6.x\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n";
    String ShadowVolumeExtrudeProgram::mDirArbvp1Finite = 
        "!!ARBvp1.0\n"
        "PARAM c6 = { 1, 0, 0, 0 };\n"
        "TEMP R0;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c4 = program.local[4];\n"
        "PARAM c5 = program.local[5];\n"
        "ADD R0.x, c6.x, -v24.x;\n"
        "MUL R0.x, R0.x, c5.x;\n"
        "MAD R0.xyz, -R0.x, c4.xyzx, v16.xyzx;\n"
        "DPH result.position.x, R0.xyzz, c0[0];\n"
        "DPH result.position.y, R0.xyzz, c0[1];\n"
        "DPH result.position.z, R0.xyzz, c0[2];\n"
        "DPH result.position.w, R0.xyzz, c0[3];\n"
        "END\n";
    String ShadowVolumeExtrudeProgram::mDirVs_1_1Finite = 
        "vs_1_1\n"
        "def c6, 1, 0, 0, 0\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "add r0.x, c6.x, -v7.x\n"
        "mul r0.x, r0.x, c5.x\n"
        "mad r0.xyz, -r0.x, c4.xyz, v0.xyz\n"
        "mov r0.w, c6.x\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n";
    String ShadowVolumeExtrudeProgram::mPointArbvp1FiniteDebug = 
        "!!ARBvp1.0\n"
        "PARAM c6 = { 1, 0, 0, 0 };\n"
        "TEMP R0, R1;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c5 = program.local[5];\n"
        "PARAM c4 = program.local[4];\n"
        "MOV result.color.front.primary, c6.x;\n"
        "ADD R0.x, c6.x, -v24.x;\n"
        "MUL R1.x, R0.x, c5.x;\n"
        "ADD R0.yzw, v16.xxyz, -c4.xxyz;\n"
        "DP3 R0.x, R0.yzwy, R0.yzwy;\n"
        "RSQ R0.x, R0.x;\n"
        "MUL R0.xyz, R0.x, R0.yzwy;\n"
        "MAD R0.xyz, R1.x, R0.xyzx, v16.xyzx;\n"
        "DPH result.position.x, R0.xyzz, c0[0];\n"
        "DPH result.position.y, R0.xyzz, c0[1];\n"
        "DPH result.position.z, R0.xyzz, c0[2];\n"
        "DPH result.position.w, R0.xyzz, c0[3];\n"
        "END\n";
    String ShadowVolumeExtrudeProgram::mPointVs_1_1FiniteDebug = 
        "vs_1_1\n"
        "def c6, 1, 0, 0, 0\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "mov oD0, c6.x\n"
        "add r0.x, c6.x, -v7.x\n"
        "mul r1.x, r0.x, c5.x\n"
        "add r0.yzw, v0.xxyz, -c4.xxyz\n"
        "dp3 r0.x, r0.yzw, r0.yzw\n"
        "rsq r0.x, r0.x\n"
        "mul r0.xyz, r0.x, r0.yzw\n"
        "mad r0.xyz, r1.x, r0.xyz, v0.xyz\n"
        "mov r0.w, c6.x\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n";
    String ShadowVolumeExtrudeProgram::mDirArbvp1FiniteDebug = 
        "!!ARBvp1.0\n"
        "PARAM c6 = { 1, 0, 0, 0 };\n"
        "TEMP R0;\n"
        "ATTRIB v24 = vertex.texcoord[0];\n"
        "ATTRIB v16 = vertex.position;\n"
        "PARAM c0[4] = { program.local[0..3] };\n"
        "PARAM c4 = program.local[4];\n"
        "PARAM c5 = program.local[5];\n"
        "MOV result.color.front.primary, c6.x;\n"
        "ADD R0.x, c6.x, -v24.x;\n"
        "MUL R0.x, R0.x, c5.x;\n"
        "MAD R0.xyz, -R0.x, c4.xyzx, v16.xyzx;\n"
        "DPH result.position.x, R0.xyzz, c0[0];\n"
        "DPH result.position.y, R0.xyzz, c0[1];\n"
        "DPH result.position.z, R0.xyzz, c0[2];\n"
        "DPH result.position.w, R0.xyzz, c0[3];\n"
        "END\n";
    String ShadowVolumeExtrudeProgram::mDirVs_1_1FiniteDebug = 
        "vs_1_1\n"
        "def c6, 1, 0, 0, 0\n"
        "dcl_texcoord0 v7\n"
        "dcl_position v0\n"
        "mov oD0, c6.x\n"
        "add r0.x, c6.x, -v7.x\n"
        "mul r0.x, r0.x, c5.x\n"
        "mad r0.xyz, -r0.x, c4.xyz, v0.xyz\n"
        "mov r0.w, c6.x\n"
        "dp4 oPos.x, c0, r0\n"
        "dp4 oPos.y, c1, r0\n"
        "dp4 oPos.z, c2, r0\n"
        "dp4 oPos.w, c3, r0\n";


    const String ShadowVolumeExtrudeProgram::programNames[OGRE_NUM_SHADOW_EXTRUDER_PROGRAMS] = 
    {
        "Ogre/ShadowExtrudePointLight",
            "Ogre/ShadowExtrudePointLightDebug",
            "Ogre/ShadowExtrudeDirLight",
            "Ogre/ShadowExtrudeDirLightDebug",
            "Ogre/ShadowExtrudePointLightFinite",
            "Ogre/ShadowExtrudePointLightFiniteDebug",
            "Ogre/ShadowExtrudeDirLightFinite",
            "Ogre/ShadowExtrudeDirLightFiniteDebug"
    };

	bool ShadowVolumeExtrudeProgram::mInitialised = false;
    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    void ShadowVolumeExtrudeProgram::initialise(void)
    {
		if (!mInitialised)
		{
			String syntax;
			bool vertexProgramFinite[8] = 
			{
				false, false, false, false, 
					true, true, true, true
			};
			bool vertexProgramDebug[8] = 
			{
				false, true, false, true, 
					false, true, false, true
			};
			Light::LightTypes vertexProgramLightTypes[8] = 
			{
				Light::LT_POINT, Light::LT_POINT, 
					Light::LT_DIRECTIONAL, Light::LT_DIRECTIONAL, 
					Light::LT_POINT, Light::LT_POINT, 
					Light::LT_DIRECTIONAL, Light::LT_DIRECTIONAL 
			};

			// load hardware extrusion programs for point & dir lights
			if (GpuProgramManager::getSingleton().isSyntaxSupported("arbvp1"))
			{
				// ARBvp1
				syntax = "arbvp1";
			}
			else if (GpuProgramManager::getSingleton().isSyntaxSupported("vs_1_1"))
			{
				syntax = "vs_1_1";
			}
			else
			{
				OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, 
					"Vertex programs are supposedly supported, but neither "
					"arbvp1 nor vs_1_1 syntaxes are present.", 
					"SceneManager::initShadowVolumeMaterials");
			}
			// Create all programs
			for (unsigned short v = 0; v < OGRE_NUM_SHADOW_EXTRUDER_PROGRAMS; ++v)
			{
				// Create debug extruders
				if (GpuProgramManager::getSingleton().getByName(
					programNames[v]).isNull())
				{
					GpuProgramPtr vp = 
						GpuProgramManager::getSingleton().createProgramFromString(
						programNames[v], ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME,
						ShadowVolumeExtrudeProgram::getProgramSource(
						vertexProgramLightTypes[v], syntax, 
						vertexProgramFinite[v], vertexProgramDebug[v]),
						GPT_VERTEX_PROGRAM, syntax);
					vp->load();
				}
			}
			mInitialised = true;
		}
    }
    //---------------------------------------------------------------------
    void ShadowVolumeExtrudeProgram::shutdown(void)
    {
        if (mInitialised)
        {
            for (unsigned short v = 0; v < OGRE_NUM_SHADOW_EXTRUDER_PROGRAMS; ++v)
            {
                // Destroy debug extruders
                GpuProgramManager::getSingleton().remove(programNames[v]);
            }
            mInitialised = false;
        }
    }
    //---------------------------------------------------------------------
    const String& ShadowVolumeExtrudeProgram::getProgramSource(
        Light::LightTypes lightType, const String syntax, bool finite, bool debug)
    {
        if (lightType == Light::LT_DIRECTIONAL)
        {
            if (syntax == "arbvp1")
            {
                if (finite)
                {
                    if (debug)
                    {
                        return getDirectionalLightExtruderArbvp1FiniteDebug();
                    }
                    else
                    {
                        return getDirectionalLightExtruderArbvp1Finite();
                    }
                }
                else
                {
                    if (debug)
                    {
                        return getDirectionalLightExtruderArbvp1Debug();
                    }
                    else
                    {
                        return getDirectionalLightExtruderArbvp1();
                    }
                }
            }
            else
            {
                if (finite)
                {
                    if (debug)
                    {
                        return getDirectionalLightExtruderVs_1_1FiniteDebug();
                    }
                    else
                    {
                        return getDirectionalLightExtruderVs_1_1Finite();
                    }
                }
                else
                {
                    if (debug)
                    {
                        return getDirectionalLightExtruderVs_1_1Debug();
                    }
                    else
                    {
                        return getDirectionalLightExtruderVs_1_1();
                    }
                }
            }
        }
        else
        {
            if (syntax == "arbvp1")
            {
                if (finite)
                {
                    if (debug)
                    {
                        return getPointLightExtruderArbvp1FiniteDebug();
                    }
                    else
                    {
                        return getPointLightExtruderArbvp1Finite();
                    }
                }
                else
                {
                    if (debug)
                    {
                        return getPointLightExtruderArbvp1Debug();
                    }
                    else
                    {
                        return getPointLightExtruderArbvp1();
                    }
                }
            }
            else
            {
                if (finite)
                {
                    if (debug)
                    {
                        return getPointLightExtruderVs_1_1FiniteDebug();
                    }
                    else
                    {
                        return getPointLightExtruderVs_1_1Finite();
                    }
                }
                else
                {
                    if (debug)
                    {
                        return getPointLightExtruderVs_1_1Debug();
                    }
                    else
                    {
                        return getPointLightExtruderVs_1_1();
                    }
                }
            }
        }
        // to keep compiler happy
        return StringUtil::BLANK;
    }
    //---------------------------------------------------------------------
    const String& ShadowVolumeExtrudeProgram::getProgramName(
        Light::LightTypes lightType, bool finite, bool debug)
    {
        if (lightType == Light::LT_DIRECTIONAL)
        {
            if (finite)
            {
                if (debug)
                {
                    return programNames[DIRECTIONAL_LIGHT_FINITE_DEBUG];
                }
                else
                {
                    return programNames[DIRECTIONAL_LIGHT_FINITE];
                }
            }
            else
            {
                if (debug)
                {
                    return programNames[DIRECTIONAL_LIGHT_DEBUG];
                }
                else
                {
                    return programNames[DIRECTIONAL_LIGHT];
                }
            }
        }
        else
        {
            if (finite)
            {
                if (debug)
                {
                    return programNames[POINT_LIGHT_FINITE_DEBUG];
                }
                else
                {
                    return programNames[POINT_LIGHT_FINITE];
                }
            }
            else
            {
                if (debug)
                {
                    return programNames[POINT_LIGHT_DEBUG];
                }
                else
                {
                    return programNames[POINT_LIGHT];
                }
            }
        }
    }



}
