// Wild Magic Source Code
// David Eberly
// http://www.geometrictools.com
// Copyright (c) 1998-2007
//
// This library 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.1 of the License, or (at
// your option) any later version.  The license is available for reading at
// either of the locations:
//     http://www.gnu.org/copyleft/lgpl.html
//     http://www.geometrictools.com/License/WildMagicLicense.pdf
//
// Version: 4.0.0 (2006/06/28)

#include "WSceneBuilder.h"
#include "WExportSettings.h"
#include "bmmlib.h"

const TCHAR* WSceneBuilder::ms_aacMapName[NTEXMAPS] =
{
    _T("map_ambient"),             // ID_AM
    _T("map_diffuse"),             // ID_DI
    _T("map_specular"),            // ID_SP
    _T("map_shininess"),           // ID_SH
    _T("map_shininess_strength"),  // ID_SS
    _T("map_selfillumination"),    // ID_SI
    _T("map_opacity"),             // ID_OP
    _T("map_filter_color"),        // ID_FI
    _T("map_bump"),                // ID_BU
    _T("map_reflection"),          // ID_RL
    _T("map_refraction"),          // ID_RR
    _T("map_displacment")          // ID_DP
};

const char* WSceneBuilder::ms_aacEnvName[5] =
{
    "env_explicit",
    "env_sphere",
    "env_cylinder",
    "env_shrink",
    "env_screen"
};

const TCHAR* WSceneBuilder::ms_acMapEnvironment = _T("map_environment");
const TCHAR* WSceneBuilder::ms_acMapGeneric = _T("map_generic");

//----------------------------------------------------------------------------
void WSceneBuilder::ConvertMaterials (INode* pkTopNode)
{
    CollectMaterials(pkTopNode);

    if (!m_pkSettings->IncludeMaterials) 
    {
        return;
    }

    int iQuantity = m_kMtlList.GetQuantity();
    m_kMtlTree.resize(iQuantity);
    for (int i = 0; i < m_kMtlList.GetQuantity(); i++)
    {
        Mtl* pkMtl = m_kMtlList.Get(i);
        if (pkMtl)
        {
            ConvertMaterial(*pkMtl,i,-1,m_kMtlTree[i]);
        }
    }
}
//----------------------------------------------------------------------------
void WSceneBuilder::CollectMaterials (INode* pkNode)
{
    Mtl* pkMtl = pkNode->GetMtl();
    if (pkMtl)
    {
        m_kMtlList.Add(pkMtl);
    }

    for (int i = 0; i < pkNode->NumberOfChildren(); i++)
    {
        CollectMaterials(pkNode->GetChildNode(i));
    }
}
//----------------------------------------------------------------------------
void WSceneBuilder::ConvertMaterial (Mtl& rkMtl, int iMtlID, int iSubNo,
    WMaterialTree& rkTree)
{
    // create a Wild Magic material from the Max material
    Wm4::MaterialStatePtr spkMState = WM4_NEW Wm4::MaterialState;
    Color kColor = rkMtl.GetAmbient();
    float fAlpha = 1.0f - rkMtl.GetXParency();
    spkMState->Ambient = Wm4::ColorRGB(kColor.r,kColor.g,kColor.b);
    kColor = rkMtl.GetDiffuse();
    spkMState->Diffuse = Wm4::ColorRGB(kColor.r,kColor.g,kColor.b);
    kColor = rkMtl.GetSpecular();
    spkMState->Specular = Wm4::ColorRGB(kColor.r,kColor.g,kColor.b);
    spkMState->Shininess = 128.0f*rkMtl.GetShininess();
    spkMState->Alpha = fAlpha;

    const char* acName = (const char*)rkMtl.GetName();
    spkMState->SetName(acName);
    rkTree.SetMaterial(spkMState);

    // get the subtextures
    int iTQuantity = rkMtl.NumSubTexmaps();
    if (iTQuantity > 0)
    {
        rkTree.SetTChildQuantity(iTQuantity);
        for (int i = 0; i < iTQuantity; i++)
        {
            Texmap* pkSubTex = rkMtl.GetSubTexmap(i);
            if (pkSubTex)
            {
                // Check if the material is "standard" and if the subtexture
                // is enabled.  If not enabled, the subtexture is not
                // converted.
                if (rkMtl.ClassID() == Class_ID(DMTL_CLASS_ID,0)
                &&  !((StdMat&)rkMtl).MapEnabled(i))
                {
                    continue;
                }

                ConvertTexture(*pkSubTex,rkMtl.ClassID(),i,
                    rkTree.GetTChild(i));
            }
        }
    }

    // get the submaterials
    int iMQuantity = rkMtl.NumSubMtls();
    if (iMQuantity > 0)
    {
        rkTree.SetMChildQuantity(iMQuantity);
        for (int i = 0; i < iMQuantity; i++)
        {
            Mtl* pkSubMtl = rkMtl.GetSubMtl(i);
            if (pkSubMtl)
            {
                ConvertMaterial(*pkSubMtl,0,i,rkTree.GetMChild(i));
            }
        }
    }
}
//----------------------------------------------------------------------------
void WSceneBuilder::ConvertTexture (Texmap& rkTex, Class_ID kClassID,
    int iSubNo, WTextureTree& rkTree)
{
    if (rkTex.ClassID() == Class_ID(BMTEX_CLASS_ID,0))
    {
        // Get the bitmap.
        BitmapTex& rkBmpTex = (BitmapTex&)rkTex;
        Bitmap* pkBmp = rkBmpTex.GetBitmap(m_iTimeStart);
        if (!pkBmp)
        {
            assert(false);
            return;
        }

        // Get the bitmap filename.  This name is stored in the Image object
        // associated with the TextureEffect.
        TSTR kFileName = rkBmpTex.GetMapName();
        char acDrive[_MAX_DRIVE];
        char acDir[_MAX_DIR];
        char acFName[_MAX_FNAME];  // Use this name (path is stripped).
        char acExt[_MAX_EXT];
#if _MSC_VER >= 1400
        _splitpath_s(kFileName.data(),acDrive,_MAX_DRIVE,acDir,_MAX_DIR,
            acFName,_MAX_FNAME,acExt,_MAX_EXT);
#else
        _splitpath(kFileName.data(),acDrive,acDir,acFName,acExt);
#endif
        Wm4::System::Strcat(acFName,_MAX_FNAME,".wmif");

        // Load the bitmap and create the Image object.
        Wm4::Image* pkWm4Image;
        Wm4::Image::FormatMode eFormat;
        int iBytesPerPixel;
        int iWidth = pkBmp->Width();
        int iHeight = pkBmp->Height();   
        BOOL bHasAlpha = pkBmp->HasAlpha();
        if (bHasAlpha)
        {
            eFormat =  Wm4::Image::IT_RGBA8888;
            iBytesPerPixel = 4;
        }
        else
        {
            eFormat = Wm4::Image::IT_RGB888;
            iBytesPerPixel = 3;
        }

        WIN32_FIND_DATA kFindData;
        HANDLE hFile = FindFirstFile(acFName,&kFindData);
        FindClose(hFile);
        if (hFile == INVALID_HANDLE_VALUE || m_pkSettings->GenerateMaps)
        {
            int iQuantity = iWidth*iHeight*iBytesPerPixel;
            unsigned char* aucDst = WM4_NEW unsigned char[iQuantity];
            unsigned char* pucDst = aucDst;
            BMM_Color_64* pkScanLine = WM4_NEW BMM_Color_64[iWidth];
            BMM_Color_64* pkPixel;
            BMM_Color_32 kScaledColor;
            for (int iY = iHeight - 1; iY >= 0; iY--)
            {
                pkBmp->GetLinearPixels(0,iY,iWidth,pkScanLine);
                pkPixel = pkScanLine;
                for (int iX = 0; iX < iWidth; iX++)
                {
                    AColor kColor(*pkPixel);
                    kScaledColor = (BMM_Color_32)kColor;
                    *pucDst++ = (unsigned char)kScaledColor.r;
                    *pucDst++ = (unsigned char)kScaledColor.g;
                    *pucDst++ = (unsigned char)kScaledColor.b;
                    if (bHasAlpha)
                    {
                        *pucDst++ = (unsigned char)kScaledColor.a;
                    }
                    pkPixel++;
                }
            }
            WM4_DELETE[] pkScanLine;
            pkWm4Image = WM4_NEW Wm4::Image(eFormat,iWidth,iHeight,aucDst,
                acFName);
            pkWm4Image->Save(acFName);
        }
        else
        {
            // TO DO:  How should this be handled?
            assert(false);
            pkWm4Image = WM4_NEW Wm4::Image(eFormat,pkBmp->Width(),
                pkBmp->Height(),0,acFName);
        }

        // Strip off the ".wmif" that was added previously.
        assert(acFName[strlen(acFName)-5] == '.');
        acFName[strlen(acFName)-5] = 0;

        // Create the single-texture effect.  TO DO: Need to add logic for
        // multiple textures per effect, including how to combine.
        Wm4::TextureEffect* pkEffect = WM4_NEW Wm4::TextureEffect(acFName);
        rkTree.SetEffect(pkEffect);

        // Create a name for the TextureEffect object.
        const TCHAR* acTexName = rkTex.GetName();
        const TCHAR* acMapName;
        if (kClassID == Class_ID(0,0))
        {
            acMapName = ms_acMapEnvironment;
        }
        else if (kClassID == Class_ID(DMTL_CLASS_ID,0))
        {
            acMapName = ms_aacMapName[iSubNo];
        }
        else
        {
            acMapName = ms_acMapGeneric;
        }
        std::string kEffectName = std::string(acTexName) +
            std::string(": ") + std::string(acMapName);
        pkEffect->SetName(kEffectName);

        // Set the texture parameters.
        Wm4::PixelShader* pkPShader = pkEffect->GetPShader(0);
        //pkPShader->SetTextureQuantity(1);
        Wm4::Texture* pkTexture = pkPShader->GetTexture(0);
    
        StdUVGen* pkUVGen = rkBmpTex.GetUVGen();
        if (pkUVGen)
        {
            pkTexture->SetName(ms_aacEnvName[pkUVGen->GetCoordMapping(0)]);

            // TO DO.  Need to handle animated texture coordinates.  What to
            // do when the UV parameters are not the standard ones?  The
            // functions below are called through pkUVGen.  The "noise"
            // parameters appear to be for procedural generation of textures.
            //
            // U offset:      GetUOffs(time)
            // V offset:      GetVOffs(time)
            // U tiling:      GetUScl(time)
            // V tiling:      GetVScl(time)
            // angle:         GetAng(time)
            // blur:          GetBlur(time)
            // blur offset:   GetBlurOffs(time)
            // noise amount:  GetNoiseAmt(time)
            // noise size:    GetNoiseSize(time)
            // noise level:   GetNoiseLev(time)
            // noise phase:   GetNoisePhs(time)

            // Apparently Max always uses wrapping?
            pkTexture->SetWrapType(0,Wm4::Texture::REPEAT);
            pkTexture->SetWrapType(1,Wm4::Texture::REPEAT);
        }

        switch (rkBmpTex.GetFilterType())
        {
        case FILTER_PYR:
            // bilinear filter, trilinear mipmap
            pkTexture->SetFilterType(Wm4::Texture::LINEAR_LINEAR);
            break;
        case FILTER_SAT:
            // TO DO. What is it and how to convert?
            pkTexture->SetFilterType(Wm4::Texture::LINEAR);
            break;
        default:
            pkTexture->SetFilterType(Wm4::Texture::NEAREST);
            break;
        }
    }

    int iTQuantity = rkTex.NumSubTexmaps();
    if (iTQuantity > 0)
    {
        rkTree.SetChildQuantity(iTQuantity);
        for (int i = 0; i < iTQuantity; i++)
        {
            Texmap* pkSubTex = rkTex.GetSubTexmap(i);
            if (pkSubTex)
            {
                ConvertTexture(*pkSubTex,rkTex.ClassID(),i,
                    rkTree.GetChild(i));
            }
        }
    }
}
//----------------------------------------------------------------------------
