/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  hsVertexShader Class Functions                                          //
//                                                                          //
//// Version History /////////////////////////////////////////////////////////
//                                                                          //
//  5.9.2001 mcn - Updated to reflect the new (temporary) vertex color/     //
//                 lighting model.                                          //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////


#include "HeadSpin.h"
#include "hsBitVector.h"
#include "hsExceptionStack.h"
#include "hsMatrix44.h"
#include "hsWindows.h"

#include "MaxMain/MaxAPI.h"

#include "MaxMain/plMaxNode.h"
#include "plSurface/hsGMaterial.h"

#include "UserPropMgr.h"
#include "hsMaxLayerBase.h"
#include "hsVertexShader.h"

#include "hsConverterUtils.h"
#include "hsControlConverter.h"
#include "plSurface/hsGMaterial.h"
#include "plSurface/plLayerInterface.h"

#include "plDrawable/plGeometrySpan.h"

#include "plMaxLightContext.h"
#include "plRenderGlobalContext.h"
#include "plLightMapGen.h"

#define MF_NATIVE_MAX_LIGHT
#define MF_NO_RAY_SHADOW

extern UserPropMgr gUserPropMgr;
extern TimeValue GetTime(Interface *gi);

//===========================================================================
// hsVertexShader
//===========================================================================

hsVertexShader::hsVertexShader() :
    fConverterUtils(hsConverterUtils::Instance()),
    fInterface(),
    fLightMapGen(),
    fShaded()
{
    hsGuardBegin("hsVertexShader::hsVertexShader");
    fLocalToWorld.Reset();
    hsGuardEnd;
}

hsVertexShader::~hsVertexShader() noexcept(false)
{
    hsGuardBegin("hsVertexShader::~hsVertexShader");
    hsGuardEnd;
}

hsVertexShader& hsVertexShader::Instance()
{
    hsGuardBegin("hsVertexShader::Instance");
    static hsVertexShader instance;
    return instance;
    hsGuardEnd; 
}

void hsVertexShader::Open()
{
    hsGuardBegin("hsVertexShader::InitLights");

    fLocalToWorld.Reset();

    fInterface = ::GetCOREInterface();

    fLightMapGen = &plLightMapGen::Instance();

    hsGuardEnd;
}

void hsVertexShader::Close()
{
    hsGuardBegin("hsVertexShader::DeInitLights");

    fLightMapGen = nullptr;

    hsGuardEnd;
}

//// ShadeNode ///////////////////////////////////////////////////////////////
//  Same as the other ShadeNode, only this shades an array of plGeometrySpans.

void hsVertexShader::ShadeNode(INode* node, hsMatrix44& l2w, hsMatrix44& w2l, std::vector<plGeometrySpan *> &spans)
{
    // If we're flagged for WaterColor, our vertex colors are already done.
    if( ((plMaxNodeBase*)node)->GetCalcEdgeLens() || node->UserPropExists(_M("XXXWaterColor")) )
        return;

    fLightMapGen->InitNode(node);

    fLocalToWorld = l2w;
    hsMatrix44 tempMatrix = w2l; // l2w's inverse
    tempMatrix.GetTranspose( &fNormalToWorld ); // Inverse-transpose of the fLocalToWorld matrix, 
    
    for (plGeometrySpan* span : spans)
        IShadeSpan(span, node);
    
    fLightMapGen->DeInitNode();

    fShaded++;
}

//// IShadeSpan //////////////////////////////////////////////////////////////
//  Shades a single plGeometrySpan.
//  5.9.2001 mcn - Updated to support the new (temporary) vertex color/lighting
//                 method.

void hsVertexShader::IShadeSpan( plGeometrySpan *span, INode* node )
{
    hsColorRGBA         preDiffuse, rtDiffuse, matAmbient;
    hsBitVector         dirtyVector;
    bool                translucent, shadeIt, addingIt;
    plLayerInterface    *layer = nullptr;


    hsGuardBegin("hsVertexShader::ShadeSpan");

    auto dbgNodeName = node->GetName();

    if( span->fNumVerts == 0 )
        return;

    fShadeColorTable = new hsColorRGBA[ span->fNumVerts ];  
    fIllumColorTable = new hsColorRGBA[ span->fNumVerts ];  
    
    translucent = IsTranslucent( span->fMaterial );

    /// Get material layer #0
    addingIt = false;
    shadeIt = !( span->fProps & plGeometrySpan::kPropNoPreShade );

    if( span->fMaterial->GetNumLayers() != 0 )
    {
        layer = span->fMaterial->GetLayer( 0 );
        if( layer->GetShadeFlags() & hsGMatState::kShadeNoShade )
            shadeIt = false;
        if( layer->GetBlendFlags() & hsGMatState::kBlendAdd )
            addingIt = true;
    }
    float opacity = 1.f;
    for (size_t i = 0; i < span->fMaterial->GetNumLayers(); i++)
    {
        plLayerInterface* lay = span->fMaterial->GetLayer(i);
        if( (lay->GetBlendFlags() & hsGMatState::kBlendAlpha)
            &&
            (
                !i  
                ||
                (lay->GetMiscFlags() & hsGMatState::kMiscRestartPassHere)
            )
          )
        {
            opacity = span->fMaterial->GetLayer(i)->GetOpacity();
        }
    }

    /// Generate color table
    if( shadeIt )
        IShadeVertices( span, &dirtyVector, node, translucent );
    else
    {
        for (uint32_t i = 0; i < span->fNumVerts; i++)
        {
            /// This is good for the old way, but not sure about the new way. Test once new way is in again -mcn
//          fShadeColorTable[ i ].Set( 1, 1, 1, 1 );
//          fIllumColorTable[ i ].Set( 0, 0, 0, 1 );
            hsPoint3    position;
            hsVector3   normal;
            hsColorRGBA color, illum;

            span->ExtractVertex( i, &position, &normal, &color, &illum );
            span->ExtractInitColor( i, &color, &illum );
            fShadeColorTable[ i ].Set( color.r, color.g, color.b, color.a );
            fIllumColorTable[ i ].Set( illum.r, illum.g, illum.b, 1 );
        }
    }

    /// Get mat colors to modulate by
    if (layer == nullptr)
    {
        preDiffuse.Set( 1, 1, 1, 1 );
        rtDiffuse.Set( 1, 1, 1, 1 );
        matAmbient.Set( 0, 0, 0, 0 );
    }
    else
    {
        if( layer->GetShadeFlags() & hsGMatState::kShadeWhite )
        {
            preDiffuse.Set( 1, 1, 1, 1 );
            rtDiffuse.Set( 1, 1, 1, 1 );
            matAmbient.Set( 0, 0, 0, 0 );
        }
        else
        {
            preDiffuse = layer->GetPreshadeColor();     // This is for vertex-based lighting, which basically ignores preshading
            rtDiffuse = layer->GetRuntimeColor();       // This is for vertex-based lighting, which basically ignores preshading
            matAmbient = layer->GetAmbientColor();
            matAmbient.a = 0;
        }
        preDiffuse.a = opacity;
        rtDiffuse.a = opacity;
    }
#if 0

    /// Multiply by the material color, and scale by opacity if we're additive blending
    /// Apply colors now, multiplying by the material color as we go
    for (uint32_t i = 0; i < span->fNumVerts; i++)
    {
        fShadeColorTable[ i ] *= matDiffuse;
        fShadeColorTable[ i ] += matAmbient;
        fIllumColorTable[ i ] *= matDiffuse;
        fIllumColorTable[ i ] += matAmbient;
    }

    if( addingIt )
    {
        for (uint32_t i = 0; i < span->fNumVerts; i++)
        {
            float opacity = fShadeColorTable[ i ].a;
            fShadeColorTable[ i ] *= opacity;
            fIllumColorTable[ i ] *= opacity;
        }
    }
#else
    /// Combine shade and illum together into the diffuse color
    if( ( span->fProps & plGeometrySpan::kLiteMask ) != plGeometrySpan::kLiteMaterial )
    {
        /// The two vertex lighting formulas take in a vetex color pre-processed, i.e. in 
        /// the form of: vtxColor = ( maxVtxColor * materialDiffuse + maxIllumColor )
        span->fProps |= plGeometrySpan::kDiffuseFoldedIn;
        if( !shadeIt )
        {
            for (uint32_t i = 0; i < span->fNumVerts; i++)
            {
                fIllumColorTable[ i ].a = 0;
                fShadeColorTable[ i ] = (fShadeColorTable[ i ] * rtDiffuse) + fIllumColorTable[ i ];
                fIllumColorTable[ i ].Set( 0, 0, 0, 0 );
            }
        }
        else
        {
            for (uint32_t i = 0; i < span->fNumVerts; i++)
            {
                fIllumColorTable[ i ].a = 1.f;
                // Following needs to be changed to allow user input vertex colors to modulate
                // the runtime light values.
//              fShadeColorTable[ i ] = fIllumColorTable[ i ] * rtDiffuse;
                fShadeColorTable[ i ] = fShadeColorTable[ i ] * fIllumColorTable[ i ] * rtDiffuse;
                fIllumColorTable[ i ].Set( 0, 0, 0, 0 );
            }
        }
    }
    else
    {
        if( !shadeIt )
        {
            // Not shaded, so runtime lit, so we want BLACK vertex colors
            for (uint32_t i = 0; i < span->fNumVerts; i++)
            {
                fShadeColorTable[ i ].Set( 0, 0, 0, 0 );
                fIllumColorTable[ i ].Set( 0, 0, 0, 0 );
            }
        }
        else
        {
            for (uint32_t i = 0; i < span->fNumVerts; i++)
            {
                fShadeColorTable[ i ] *= fIllumColorTable[ i ];
                fIllumColorTable[ i ].Set( 0, 0, 0, 0 );
            }
        }
    }
#endif

    /// Loop and stuff
    for (uint32_t i = 0; i < span->fNumVerts; i++)
        span->StuffVertex( i, fShadeColorTable + i, fIllumColorTable + i );

    delete [] fShadeColorTable;       
    delete [] fIllumColorTable;       

    hsGuardEnd;
}

//// IShadeVertices //////////////////////////////////////////////////////////
//  Shades an array of vertices from a plGeometrySpan.
//  5.9.2001 mcn - Updated for the new lighting model. Now on runtime, we
//                 want the following properties on each vertex:
//                  diffuseColor = vertexColor * matDiffuse + matAmbient (including alpha)
//                  specularColor = ( illumniation + pre-shading ) * matDiffuse + matAmbient
//                 We do the mat modulation outside of this function, so we 
//                 just gotta make sure the two arrays get the right values.

void hsVertexShader::IShadeVertices( plGeometrySpan *span, hsBitVector *dirtyVector, INode* node, bool translucent )
{
    hsGuardBegin( "hsVertexShader::IShadeVertices" );

    plMaxNode* maxNode = (plMaxNode*)node;
    if (maxNode->CanConvert() && (nullptr != maxNode->GetLightMapComponent()))
        return;

    int     index;

    hsPoint3        position;
    hsVector3       normal;
    hsColorRGBA     color, illum;
    plTmpVertex3    *vertices;


    /// Allocate temp vertex array
    vertices = new plTmpVertex3[ span->fNumVerts ];
    for( index = 0; index < span->fNumVerts; index++ )
    {
        span->ExtractVertex( index, &position, &normal, &color, &illum );
        span->ExtractInitColor( index, &color, &illum );

        /// fShadeColorTable is the shaded portion. fIllumColorTable is the illuminated portion;
        /// for more and less confusing details, see above.
        fShadeColorTable[ index ].Set( color.r, color.g, color.b, color.a );
        fIllumColorTable[ index ].Set( illum.r, illum.g, illum.b, 1 );

        position = fLocalToWorld * position;
        normal = fNormalToWorld * normal;

        vertices[ index ].fLocalPos = position;
        vertices[ index ].fNormal = normal;
        vertices[ index ].fNormal.Normalize();
    }

    auto dbgNodeName = node->GetName();

    TimeValue t = fInterface->GetTime();
    Box3 bbox;
    node->EvalWorldState(t).obj->GetDeformBBox(t, bbox, &node->GetObjectTM(t));
    plMaxLightContext ctx(bbox, t);

    for( index = 0; index < span->fNumVerts; index++ )
        INativeShadeVtx(fIllumColorTable[index], ctx, vertices[ index ], translucent);

    // Delete temp arrays
    delete [] vertices;

    hsGuardEnd;
}

void hsVertexShader::INativeShadeVtx(hsColorRGBA& shade, plMaxLightContext& ctx, const plTmpVertex3& vtx, bool translucent)
{
    ctx.SetPoint(vtx.fLocalPos, vtx.fNormal);

    Color color = fLightMapGen->ShadePoint(ctx);
    shade.r += color.r;
    shade.g += color.g;
    shade.b += color.b;

    // To handle two-sided translucency here, we should compute the shade on each side and sum them.
    if( translucent )
    {
        ctx.SetPoint(vtx.fLocalPos, -vtx.fNormal);
        color = fLightMapGen->ShadePoint(ctx);
        shade.r += color.r;
        shade.g += color.g;
        shade.b += color.b;
    }
}

void hsVertexShader::INativeShadowVtx(hsColorRGBA& shade, plMaxLightContext& ctx, const plTmpVertex3& vtx, bool translucent)
{
    ctx.SetPoint(vtx.fLocalPos, vtx.fNormal);

    Color color = fLightMapGen->ShadowPoint(ctx);
    shade.r += color.r;
    shade.g += color.g;
    shade.b += color.b;
}

bool hsVertexShader::IsTranslucent( hsGMaterial *material )
{
    hsGuardBegin("hsVertexShader::IsTranslucent");

    if( material )
    {
        plLayerInterface* layer = material->GetLayer(0);
        if( layer && ( layer->GetShadeFlags() & hsGMatState::kShadeSoftShadow ) )
        {
            return true;
        }
    }

    return false;
    hsGuardEnd; 
}


