/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------

Copyright (c) 2006-2022, assimp team

All rights reserved.

Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.

* Redistributions in binary form must reproduce the above
  copyright notice, this list of conditions and the
  following disclaimer in the documentation and/or other
  materials provided with the distribution.

* Neither the name of the assimp team, nor the names of its
  contributors may be used to endorse or promote products
  derived from this software without specific prior
  written permission of the assimp team.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#include "assimp_view.h"
#include "AnimEvaluator.h"
#include "SceneAnimator.h"
#include <assimp/StringUtils.h>

#include <commdlg.h>

namespace AssimpView {

using namespace Assimp;

extern std::string g_szCheckerBackgroundShader;

struct SVertex
{
    float x,y,z,w,u,v;
};

CDisplay CDisplay::s_cInstance;

extern COLORREF g_aclCustomColors[16] /*= {0}*/;
extern HKEY g_hRegistry;
extern float g_fLoadTime;

//-------------------------------------------------------------------------------
// Table of colors used for normal vectors.
//-------------------------------------------------------------------------------
D3DXVECTOR4 g_aclNormalColors[14] =
{
    D3DXVECTOR4(0xFF / 255.0f,0xFF / 255.0f,0xFF / 255.0f, 1.0f), // white

    D3DXVECTOR4(0xFF / 255.0f,0x00 / 255.0f,0x00 / 255.0f,1.0f), // red
    D3DXVECTOR4(0x00 / 255.0f,0xFF / 255.0f,0x00 / 255.0f,1.0f), // green
    D3DXVECTOR4(0x00 / 255.0f,0x00 / 255.0f,0xFF / 255.0f,1.0f), // blue

    D3DXVECTOR4(0xFF / 255.0f,0xFF / 255.0f,0x00 / 255.0f,1.0f), // yellow
    D3DXVECTOR4(0xFF / 255.0f,0x00 / 255.0f,0xFF / 255.0f,1.0f), // magenta
    D3DXVECTOR4(0x00 / 255.0f,0xFF / 255.0f,0xFF / 255.0f,1.0f), // wtf

    D3DXVECTOR4(0xFF / 255.0f,0x60 / 255.0f,0x60 / 255.0f,1.0f), // light red
    D3DXVECTOR4(0x60 / 255.0f,0xFF / 255.0f,0x60 / 255.0f,1.0f), // light green
    D3DXVECTOR4(0x60 / 255.0f,0x60 / 255.0f,0xFF / 255.0f,1.0f), // light blue

    D3DXVECTOR4(0xA0 / 255.0f,0x00 / 255.0f,0x00 / 255.0f,1.0f), // dark red
    D3DXVECTOR4(0x00 / 255.0f,0xA0 / 255.0f,0x00 / 255.0f,1.0f), // dark green
    D3DXVECTOR4(0x00 / 255.0f,0x00 / 255.0f,0xA0 / 255.0f,1.0f), // dark blue

    D3DXVECTOR4(0x88 / 255.0f,0x88 / 255.0f,0x88 / 255.0f, 1.0f) // gray
};


//-------------------------------------------------------------------------------
// Recursively count the number of nodes in an asset's node graph
// Used by LoadAsset()
//-------------------------------------------------------------------------------
void GetNodeCount(aiNode* pcNode, unsigned int* piCnt)
{
    *piCnt = *piCnt+1;
    for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) {
        GetNodeCount(pcNode->mChildren[i], piCnt);
    }
}

//-------------------------------------------------------------------------------
int CDisplay::EnableAnimTools(BOOL hm) {
    EnableWindow(GetDlgItem(g_hDlg,IDC_PLAY),hm);
    EnableWindow(GetDlgItem(g_hDlg,IDC_SLIDERANIM),hm);

    return 1;
}

//-------------------------------------------------------------------------------
// Fill animation combo box
int CDisplay::FillAnimList(void) {
    if (0 != g_pcAsset->pcScene->mNumAnimations)
    {
        // now fill in all animation names
        for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumAnimations;++i)    {
            SendDlgItemMessage(g_hDlg,IDC_COMBO1,CB_ADDSTRING,0,
                ( LPARAM ) g_pcAsset->pcScene->mAnimations[i]->mName.data);
        }

        // also add a dummy - 'none'
        SendDlgItemMessage(g_hDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)"none");

        // select first
        SendDlgItemMessage(g_hDlg,IDC_COMBO1,CB_SETCURSEL,0,0);

        EnableAnimTools(TRUE);
    }
    else // tools remain disabled
        EnableAnimTools(FALSE);

    return 1;
}
//-------------------------------------------------------------------------------
// Clear the list of animations
int CDisplay::ClearAnimList(void)
{
    // clear the combo box
    SendDlgItemMessage(g_hDlg,IDC_COMBO1,CB_RESETCONTENT,0,0);
    return 1;
}
//-------------------------------------------------------------------------------
// Clear the tree view
int CDisplay::ClearDisplayList(void)
{
    // clear the combo box
    TreeView_DeleteAllItems(GetDlgItem(g_hDlg,IDC_TREE1));
    this->Reset();
    return 1;
}
//-------------------------------------------------------------------------------
// Add a specific node to the display list
int CDisplay::AddNodeToDisplayList(
    unsigned int iIndex,
    unsigned int iDepth,
    aiNode* pcNode,
    HTREEITEM hRoot)
{
    ai_assert(nullptr != pcNode);
    ai_assert(nullptr != hRoot);

    char chTemp[MAXLEN];

    if(0 == pcNode->mName.length)   {
        if (iIndex >= 100)  {
            iIndex += iDepth  * 1000;
        }
        else if (iIndex >= 10)
        {
            iIndex += iDepth  * 100;
        }
        else
			iIndex += iDepth  * 10;
        ai_snprintf(chTemp, MAXLEN,"Node %u",iIndex);
    }
    else {
        ai_snprintf(chTemp, MAXLEN,"%s",pcNode->mName.data);
    }
    ai_snprintf(chTemp+strlen(chTemp), MAXLEN- strlen(chTemp),  iIndex ? " (%i)" : " (%i meshes)",pcNode->mNumMeshes);

    TVITEMEXW tvi;
    TVINSERTSTRUCTW sNew;

    wchar_t tmp[512];
    int t = MultiByteToWideChar(CP_UTF8,0,chTemp,-1,tmp,512);

    tvi.pszText = tmp;
    tvi.cchTextMax = (int)t;

    tvi.mask = TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_HANDLE | TVIF_PARAM;
    tvi.iImage = this->m_aiImageList[AI_VIEW_IMGLIST_NODE];
    tvi.iSelectedImage = this->m_aiImageList[AI_VIEW_IMGLIST_NODE];
    tvi.lParam = (LPARAM)5;

    sNew.itemex = tvi;
    sNew.hInsertAfter = TVI_LAST;
    sNew.hParent = hRoot;

    // add the item to the list
    HTREEITEM hTexture = (HTREEITEM)SendMessage(GetDlgItem(g_hDlg,IDC_TREE1),
        TVM_INSERTITEMW,
        0,
        (LPARAM)(LPTVINSERTSTRUCT)&sNew);

    // recursively add all child nodes
    ++iDepth;
    for (unsigned int i = 0; i< pcNode->mNumChildren;++i){
        AddNodeToDisplayList(i,iDepth,pcNode->mChildren[i],hTexture);
    }

    // add the node to the list
    NodeInfo info;
    info.hTreeItem = hTexture;
    info.psNode = pcNode;
    this->AddNode(info);
    return 1;
}

//-------------------------------------------------------------------------------
int CDisplay::AddMeshToDisplayList(unsigned int iIndex, HTREEITEM hRoot)
{
    aiMesh* pcMesh = g_pcAsset->pcScene->mMeshes[iIndex];

    char chTemp[MAXLEN];

    if(0 == pcMesh->mName.length)   {
        ai_snprintf(chTemp,MAXLEN,"Mesh %u",iIndex);
    }
    else {
        ai_snprintf(chTemp,MAXLEN,"%s",pcMesh->mName.data);
    }
    ai_snprintf(chTemp+strlen(chTemp),MAXLEN-strlen(chTemp),  iIndex ? " (%i)" : " (%i faces)",pcMesh->mNumFaces);

    TVITEMEXW tvi;
    TVINSERTSTRUCTW sNew;

    wchar_t tmp[512];
    int t = MultiByteToWideChar(CP_UTF8,0,chTemp,-1,tmp,512);

    tvi.pszText = tmp;
    tvi.cchTextMax = (int)t;

    tvi.mask = TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_HANDLE | TVIF_PARAM;
    tvi.iImage = this->m_aiImageList[AI_VIEW_IMGLIST_NODE];
    tvi.iSelectedImage = this->m_aiImageList[AI_VIEW_IMGLIST_NODE];
    tvi.lParam = (LPARAM)5;

    sNew.itemex = tvi;
    sNew.hInsertAfter = TVI_LAST;
    sNew.hParent = hRoot;

    // add the item to the list
    HTREEITEM hTexture = (HTREEITEM)SendMessage(GetDlgItem(g_hDlg,IDC_TREE1),
        TVM_INSERTITEMW,
        0,
        (LPARAM)(LPTVINSERTSTRUCT)&sNew);

    // add the mesh to the list of all mesh entries in the scene browser
    MeshInfo info;
    info.hTreeItem = hTexture;
    info.psMesh = pcMesh;
    AddMesh(info);
    return 1;
}

//-------------------------------------------------------------------------------
// Replace the currently selected texture by another one
int CDisplay::ReplaceCurrentTexture(const char* szPath)
{
    ai_assert(nullptr != szPath);

    // well ... try to load it
    IDirect3DTexture9* piTexture = nullptr;
    aiString szString;
    strcpy(szString.data,szPath);
    szString.length = static_cast<ai_uint32>(strlen(szPath));
    CMaterialManager::Instance().LoadTexture(&piTexture,&szString);

    if (!piTexture) {
        CLogDisplay::Instance().AddEntry("[ERROR] Unable to load this texture",
            D3DCOLOR_ARGB(0xFF,0xFF,0x0,0x0));
        return 0;
    }

    // we must also change the icon of the corresponding tree
    // view item if the default texture was previously set
    TVITEMEX tvi;
    tvi.mask = TVIF_SELECTEDIMAGE | TVIF_IMAGE;
    tvi.iImage = m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];
    tvi.iSelectedImage = m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];

    TreeView_SetItem(GetDlgItem(g_hDlg,IDC_TREE1),
        m_pcCurrentTexture->hTreeItem);

    // update all meshes referencing this material
    for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
    {
        if (this->m_pcCurrentTexture->iMatIndex != g_pcAsset->pcScene->mMeshes[i]->mMaterialIndex)
            continue;

        AssetHelper::MeshHelper* pcMesh = g_pcAsset->apcMeshes[i];
        IDirect3DTexture9** tex = nullptr;
        const char* tex_string  = nullptr;

        switch (this->m_pcCurrentTexture->iType)
        {
        case aiTextureType_DIFFUSE:
            tex = &pcMesh->piDiffuseTexture;
            tex_string = "DIFFUSE_TEXTURE";
            break;
        case aiTextureType_AMBIENT:
            tex = &pcMesh->piAmbientTexture;
            tex_string = "AMBIENT_TEXTURE";
            break;
        case aiTextureType_SPECULAR:
            tex = &pcMesh->piSpecularTexture;
            tex_string = "SPECULAR_TEXTURE";
            break;
        case aiTextureType_EMISSIVE:
            tex = &pcMesh->piEmissiveTexture;
            tex_string = "EMISSIVE_TEXTURE";
            break;
        case aiTextureType_LIGHTMAP:
            tex = &pcMesh->piLightmapTexture;
            tex_string = "LIGHTMAP_TEXTURE";
            break;
        case aiTextureType_DISPLACEMENT:
        case aiTextureType_REFLECTION:
        case aiTextureType_UNKNOWN:
            break;
        case aiTextureType_SHININESS:
            tex = &pcMesh->piShininessTexture;
            tex_string = "SHININESS_TEXTURE";
            break;
        case aiTextureType_NORMALS:
        case aiTextureType_HEIGHT:

            // special handling here
            if (pcMesh->piNormalTexture && pcMesh->piNormalTexture != piTexture)    {
                piTexture->AddRef();
                pcMesh->piNormalTexture->Release();
                pcMesh->piNormalTexture = piTexture;
                CMaterialManager::Instance().HMtoNMIfNecessary(pcMesh->piNormalTexture,&pcMesh->piNormalTexture,true);
                m_pcCurrentTexture->piTexture = &pcMesh->piNormalTexture;

                if (!pcMesh->bSharedFX) {
                    pcMesh->piEffect->SetTexture("NORMAL_TEXTURE",piTexture);
                }
            }
            break;
        default: //case aiTextureType_OPACITY && case aiTextureType_OPACITY | 0x40000000:

            tex = &pcMesh->piOpacityTexture;
            tex_string = "OPACITY_TEXTURE";
            break;
        };
        if (tex && *tex && *tex != piTexture)
        {
            (**tex).Release();
            *tex = piTexture;
            m_pcCurrentTexture->piTexture = tex;

            pcMesh->piEffect->SetTexture(tex_string,piTexture);
        }
    }

    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::AddTextureToDisplayList(unsigned int iType,
    unsigned int iIndex,
    const aiString* szPath,
    HTREEITEM hFX,
    unsigned int iUVIndex       /*= 0*/,
    const float fBlendFactor    /*= 0.0f*/,
    aiTextureOp eTextureOp      /*= aiTextureOp_Multiply*/,
    unsigned int iMesh      /*= 0*/)
{
    ai_assert(nullptr != szPath);

    char chTemp[512];
    char chTempEmb[256];
    const char* sz = strrchr(szPath->data,'\\');
    if (!sz)sz = strrchr(szPath->data,'/');
    if (!sz)
    {
        if ('*' == *szPath->data)
        {
            int iIndex2 = atoi(szPath->data+1);
            ai_snprintf(chTempEmb,256,"Embedded #%i",iIndex2);
            sz = chTempEmb;
        }
        else
        {
            sz = szPath->data;
        }
    }

    bool bIsExtraOpacity = 0 != (iType & 0x40000000);
    const char* szType;
    IDirect3DTexture9** piTexture;
    switch (iType)
    {
    case aiTextureType_DIFFUSE:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piDiffuseTexture;
        szType = "Diffuse";
        break;
    case aiTextureType_SPECULAR:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piSpecularTexture;
        szType = "Specular";
        break;
    case aiTextureType_AMBIENT:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piAmbientTexture;
        szType = "Ambient";
        break;
    case aiTextureType_EMISSIVE:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piEmissiveTexture;
        szType = "Emissive";
        break;
    case aiTextureType_HEIGHT:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piNormalTexture;
        szType = "Heightmap";
        break;
    case aiTextureType_NORMALS:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piNormalTexture;
        szType = "Normalmap";
        break;
    case aiTextureType_SHININESS:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piShininessTexture;
        szType = "Shininess";
        break;
    case aiTextureType_LIGHTMAP:
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piLightmapTexture;
        szType = "Lightmap";
        break;
    case aiTextureType_DISPLACEMENT:
        piTexture = nullptr;
        szType = "Displacement";
        break;
    case aiTextureType_REFLECTION:
        piTexture = nullptr;
        szType = "Reflection";
        break;
    case aiTextureType_UNKNOWN:
        piTexture = nullptr;
        szType = "Unknown";
        break;
    default: // opacity + opacity | mask
        piTexture = &g_pcAsset->apcMeshes[iMesh]->piOpacityTexture;
        szType = "Opacity";
        break;
    };
    if (bIsExtraOpacity)    {
        ai_snprintf(chTemp,512,"%s %i (<copy of diffuse #1>)",szType,iIndex+1);
    }
    else
		ai_snprintf(chTemp,512,"%s %i (%s)",szType,iIndex+1,sz);

    TVITEMEX tvi;
    TVINSERTSTRUCT sNew;
    tvi.pszText = chTemp;
    tvi.cchTextMax = (int)strlen(chTemp);
    tvi.mask = TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_HANDLE;
    tvi.lParam = (LPARAM)20;

    // find out whether this is the default texture or not

    if (piTexture && *piTexture)    {
        // {9785DA94-1D96-426b-B3CB-BADC36347F5E}
        static const GUID guidPrivateData =
            { 0x9785da94, 0x1d96, 0x426b,
            { 0xb3, 0xcb, 0xba, 0xdc, 0x36, 0x34, 0x7f, 0x5e } };

        uint32_t iData = 0;
        DWORD dwSize = 4;
        (*piTexture)->GetPrivateData(guidPrivateData,&iData,&dwSize);

        if (0xFFFFFFFF == iData)
        {
            tvi.iImage = m_aiImageList[AI_VIEW_IMGLIST_TEXTURE_INVALID];
            tvi.iSelectedImage = m_aiImageList[AI_VIEW_IMGLIST_TEXTURE_INVALID];
        }
        else
        {
            tvi.iImage = m_aiImageList[AI_VIEW_IMGLIST_TEXTURE];
            tvi.iSelectedImage = m_aiImageList[AI_VIEW_IMGLIST_TEXTURE];
        }
    }
    else
    {
        tvi.iImage = m_aiImageList[AI_VIEW_IMGLIST_TEXTURE_INVALID];
        tvi.iSelectedImage = m_aiImageList[AI_VIEW_IMGLIST_TEXTURE_INVALID];
    }

    sNew.itemex = tvi;
    sNew.hInsertAfter = TVI_LAST;
    sNew.hParent = hFX;

    // add the item to the list
    HTREEITEM hTexture = (HTREEITEM)SendMessage(GetDlgItem(g_hDlg,IDC_TREE1),
        TVM_INSERTITEM,
        0,
        (LPARAM)(LPTVINSERTSTRUCT)&sNew);

    // add it to the list
    CDisplay::TextureInfo sInfo;
    sInfo.iUV = iUVIndex;
    sInfo.fBlend = fBlendFactor;
    sInfo.eOp = eTextureOp;
    sInfo.szPath = szPath->data;
    sInfo.hTreeItem = hTexture;
    sInfo.piTexture = piTexture;
    sInfo.iType = iType;
    sInfo.iMatIndex = g_pcAsset->pcScene->mMeshes[iMesh]->mMaterialIndex;
    AddTexture(sInfo);
    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::AddMaterialToDisplayList(HTREEITEM hRoot,
    unsigned int iIndex)
{
    ai_assert(nullptr != hRoot);

    aiMaterial* pcMat = g_pcAsset->pcScene->mMaterials[iIndex];


    // find the first mesh using this material index
    unsigned int iMesh = 0;
    for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
    {
        if (iIndex == g_pcAsset->pcScene->mMeshes[i]->mMaterialIndex)
        {
            iMesh = i;
            break;
        }
    }

    // use the name of the material, if possible
    char chTemp[512];
    aiString szOut;
    if (AI_SUCCESS != aiGetMaterialString(pcMat,AI_MATKEY_NAME,&szOut))
    {
        ai_snprintf(chTemp,512,"Material %i",iIndex+1);
    }
    else
    {
        ai_snprintf(chTemp,512,"%s (%i)",szOut.data,iIndex+1);
    }
    TVITEMEXW tvi;
    TVINSERTSTRUCTW sNew;

    wchar_t tmp[512];
    int t = MultiByteToWideChar(CP_UTF8,0,chTemp,-1,tmp,512);

    tvi.pszText = tmp;
    tvi.cchTextMax = (int)t;
    tvi.mask = TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_HANDLE | TVIF_PARAM ;
    tvi.iImage = m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];
    tvi.iSelectedImage = m_aiImageList[AI_VIEW_IMGLIST_MATERIAL];
    tvi.lParam = (LPARAM)10;

    sNew.itemex = tvi;
    sNew.hInsertAfter = TVI_LAST;
    sNew.hParent = hRoot;

    // add the item to the list
    HTREEITEM hTexture = (HTREEITEM)SendMessage(GetDlgItem(g_hDlg,IDC_TREE1),
        TVM_INSERTITEMW,
        0,
        (LPARAM)(LPTVINSERTSTRUCT)&sNew);

    // for each texture in the list ... add it
    unsigned int iUV;
    float fBlend;
    aiTextureOp eOp;
    aiString szPath;
    bool bNoOpacity = true;
    for (unsigned int i = 0; i <= AI_TEXTURE_TYPE_MAX;++i)
    {
        unsigned int iNum = 0;
        while (true)
        {
            if (AI_SUCCESS != aiGetMaterialTexture(pcMat,(aiTextureType)i,iNum,
                &szPath,nullptr, &iUV,&fBlend,&eOp))
            {
                break;
            }
            if (aiTextureType_OPACITY == i)bNoOpacity = false;
            AddTextureToDisplayList(i,iNum,&szPath,hTexture,iUV,fBlend,eOp,iMesh);
            ++iNum;
        }
    }

    AssetHelper::MeshHelper* pcMesh = g_pcAsset->apcMeshes[iMesh];

    if (pcMesh->piDiffuseTexture && pcMesh->piDiffuseTexture == pcMesh->piOpacityTexture && bNoOpacity)
    {
        // check whether the diffuse texture is not a default texture

        // {9785DA94-1D96-426b-B3CB-BADC36347F5E}
        static const GUID guidPrivateData =
            { 0x9785da94, 0x1d96, 0x426b,
            { 0xb3, 0xcb, 0xba, 0xdc, 0x36, 0x34, 0x7f, 0x5e } };

        uint32_t iData = 0;
        DWORD dwSize = 4;
        if(FAILED( pcMesh->piDiffuseTexture->GetPrivateData(guidPrivateData,&iData,&dwSize) ||
            0xffffffff == iData))
        {
            // seems the diffuse texture contains alpha, therefore it has been
            // added to the opacity channel, too. Add a special value ...
            AddTextureToDisplayList(aiTextureType_OPACITY | 0x40000000,
                0,&szPath,hTexture,iUV,fBlend,eOp,iMesh);
        }
    }

    // add the material to the list
    MaterialInfo info;
    info.hTreeItem = hTexture;
    info.psMaterial = pcMat;
    info.iIndex = iIndex;
    info.piEffect = g_pcAsset->apcMeshes[iMesh]->piEffect;
    this->AddMaterial(info);
    return 1;
}
//-------------------------------------------------------------------------------
// Expand all elements in the tree-view
int CDisplay::ExpandTree()
{
    // expand all materials
    for (std::vector< MaterialInfo >::iterator
        i =  m_asMaterials.begin();
        i != m_asMaterials.end();++i)
    {
        TreeView_Expand(GetDlgItem(g_hDlg,IDC_TREE1),(*i).hTreeItem,TVE_EXPAND);
    }
    // expand all nodes
    for (std::vector< NodeInfo >::iterator
        i =  m_asNodes.begin();
        i != m_asNodes.end();++i)
    {
        TreeView_Expand(GetDlgItem(g_hDlg,IDC_TREE1),(*i).hTreeItem,TVE_EXPAND);
    }
    TreeView_Expand(GetDlgItem(g_hDlg,IDC_TREE1),m_hRoot,TVE_EXPAND);
    return 1;
}
//-------------------------------------------------------------------------------
// Get image list for tree view
int CDisplay::LoadImageList(void)
{
    if (!m_hImageList)
    {
        // First, create the image list we will need.
        // FIX: Need RGB888 color space to display all colors correctly
        HIMAGELIST hIml = ImageList_Create( 16,16,ILC_COLOR24, 5, 0 );

        // Load the bitmaps and add them to the image lists.
        HBITMAP hBmp = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BFX));
        m_aiImageList[AI_VIEW_IMGLIST_MATERIAL] = ImageList_Add(hIml, hBmp, nullptr);
        DeleteObject(hBmp);

        hBmp = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BNODE));
        m_aiImageList[AI_VIEW_IMGLIST_NODE] = ImageList_Add(hIml, hBmp, nullptr);
        DeleteObject(hBmp);

        hBmp = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BTX));
        m_aiImageList[AI_VIEW_IMGLIST_TEXTURE] = ImageList_Add(hIml, hBmp, nullptr);
        DeleteObject(hBmp);

        hBmp = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BTXI));
        m_aiImageList[AI_VIEW_IMGLIST_TEXTURE_INVALID] = ImageList_Add(hIml, hBmp, nullptr);
        DeleteObject(hBmp);

        hBmp = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BROOT));
        m_aiImageList[AI_VIEW_IMGLIST_MODEL] = ImageList_Add(hIml, hBmp, nullptr);
        DeleteObject(hBmp);

        // Associate the image list with the tree.
        TreeView_SetImageList(GetDlgItem(g_hDlg,IDC_TREE1), hIml, TVSIL_NORMAL);

        m_hImageList = hIml;
    }
    return 1;
}
//-------------------------------------------------------------------------------
// Fill tree view
int CDisplay::FillDisplayList(void)
{
    LoadImageList();

    // Initialize the tree view window.
    // fill in the first entry
    TVITEMEX tvi;
    TVINSERTSTRUCT sNew;
    tvi.pszText = (char*) "Model";
    tvi.cchTextMax = (int)strlen(tvi.pszText);
    tvi.mask = TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_HANDLE | TVIF_STATE;
    tvi.state = TVIS_EXPANDED;
    tvi.iImage = m_aiImageList[AI_VIEW_IMGLIST_MODEL];
    tvi.iSelectedImage = m_aiImageList[AI_VIEW_IMGLIST_MODEL];
    tvi.lParam = (LPARAM)0;

    sNew.itemex = tvi;
    sNew.hInsertAfter = TVI_ROOT;
    sNew.hParent = 0;

    // add the root item to the tree
    m_hRoot = (HTREEITEM)SendMessage(GetDlgItem(g_hDlg,IDC_TREE1),
        TVM_INSERTITEM,
        0,
        (LPARAM)(LPTVINSERTSTRUCT)&sNew);

    // add each loaded material to the tree
    for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMaterials;++i)
        AddMaterialToDisplayList(m_hRoot,i);

    // add each mesh to the tree
    for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
        AddMeshToDisplayList(i,m_hRoot);

    // now add all loaded nodes recursively
    AddNodeToDisplayList(0,0,g_pcAsset->pcScene->mRootNode,m_hRoot);

    // now expand all parent nodes in the tree
    ExpandTree();

    // everything reacts a little bit slowly if D3D is rendering,
    // so give GDI a small hint to leave the couch and work ;-)
    UpdateWindow(g_hDlg);
    return 1;
}
//-------------------------------------------------------------------------------
// Main render loop
int CDisplay::OnRender()
{
    // update possible animation
    if( g_pcAsset)
    {
        static double lastPlaying = 0.;

        ai_assert( g_pcAsset->mAnimator);
        if (g_bPlay) {
            g_dCurrent += clock()/ double( CLOCKS_PER_SEC)   -lastPlaying;

            double time = g_dCurrent;
            aiAnimation* mAnim = g_pcAsset->mAnimator->CurrentAnim();
            if(  mAnim && mAnim->mDuration > 0.0) {
                double tps = mAnim->mTicksPerSecond ? mAnim->mTicksPerSecond : 25.f;
                time = fmod( time, mAnim->mDuration/tps);
                SendDlgItemMessage(g_hDlg,IDC_SLIDERANIM,TBM_SETPOS,TRUE,LPARAM(10000 * (time/(mAnim->mDuration/tps))));
            }

            g_pcAsset->mAnimator->Calculate( time );
            lastPlaying = g_dCurrent;
        }
    }
    // begin the frame
    g_piDevice->BeginScene();

    switch (m_iViewMode)
    {
    case VIEWMODE_FULL:
    case VIEWMODE_NODE:
        RenderFullScene();
        break;
    case VIEWMODE_MATERIAL:
        RenderMaterialView();
        break;
    case VIEWMODE_TEXTURE:
        RenderTextureView();
        break;
    };

    // Now render the log display in the upper right corner of the window
    CLogDisplay::Instance().OnRender();

    // present the back-buffer
    g_piDevice->EndScene();
    g_piDevice->Present(nullptr,nullptr,nullptr,nullptr);

    // don't remove this, problems on some older machines (AMD timing bug)
    Sleep(10);
    return 1;
}
//-------------------------------------------------------------------------------
// Update UI
void UpdateColorFieldsInUI()
{
    InvalidateRect(GetDlgItem(g_hDlg,IDC_LCOLOR1),nullptr,TRUE);
    InvalidateRect(GetDlgItem(g_hDlg,IDC_LCOLOR2),nullptr,TRUE);
    InvalidateRect(GetDlgItem(g_hDlg,IDC_LCOLOR3),nullptr,TRUE);

    UpdateWindow(GetDlgItem(g_hDlg,IDC_LCOLOR1));
    UpdateWindow(GetDlgItem(g_hDlg,IDC_LCOLOR2));
    UpdateWindow(GetDlgItem(g_hDlg,IDC_LCOLOR3));
}
//-------------------------------------------------------------------------------
// FIll statistics UI
int CDisplay::FillDefaultStatistics(void)
{
    if (!g_pcAsset)
    {
        // clear all stats edit controls
        SetDlgItemText(g_hDlg,IDC_EVERT,"0");
        SetDlgItemText(g_hDlg,IDC_EFACE,"0");
        SetDlgItemText(g_hDlg,IDC_EMAT,"0");
        SetDlgItemText(g_hDlg,IDC_ENODE,"0");
        SetDlgItemText(g_hDlg,IDC_ESHADER,"0");
        SetDlgItemText(g_hDlg,IDC_ETEX,"0");
        return 1;
    }

    // get the number of vertices/faces in the model
    unsigned int iNumVert = 0;
    unsigned int iNumFaces = 0;
    for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
    {
        iNumVert += g_pcAsset->pcScene->mMeshes[i]->mNumVertices;
        iNumFaces += g_pcAsset->pcScene->mMeshes[i]->mNumFaces;
    }
    // and fill the statistic edit controls
    char szOut[1024];
    ai_snprintf(szOut,1024,"%i",(int)iNumVert);
    SetDlgItemText(g_hDlg,IDC_EVERT,szOut);
	ai_snprintf(szOut, 1024,"%i",(int)iNumFaces);
    SetDlgItemText(g_hDlg,IDC_EFACE,szOut);
	ai_snprintf(szOut, 1024,"%i",(int)g_pcAsset->pcScene->mNumMaterials);
    SetDlgItemText(g_hDlg,IDC_EMAT,szOut);
	ai_snprintf(szOut, 1024,"%i",(int)g_pcAsset->pcScene->mNumMeshes);
    SetDlgItemText(g_hDlg,IDC_EMESH,szOut);

    // need to get the number of nodes
    iNumVert = 0;
    GetNodeCount(g_pcAsset->pcScene->mRootNode,&iNumVert);
	ai_snprintf(szOut, 1024,"%i",(int)iNumVert);
    SetDlgItemText(g_hDlg,IDC_ENODEWND,szOut);

    // now get the number of unique shaders generated for the asset
    // (even if the environment changes this number won't change)
	ai_snprintf(szOut, 1024,"%i", CMaterialManager::Instance().GetShaderCount());
    SetDlgItemText(g_hDlg,IDC_ESHADER,szOut);

    sprintf(szOut,"%.5f",(float)g_fLoadTime);
    SetDlgItemText(g_hDlg,IDC_ELOAD,szOut);

    UpdateColorFieldsInUI();
    UpdateWindow(g_hDlg);
    return 1;
}
//-------------------------------------------------------------------------------
// Reset UI
int CDisplay::Reset(void)
{
    // clear all lists
    m_asMaterials.clear();
    m_asTextures.clear();
    m_asNodes.clear();
    m_asMeshes.clear();

    m_hRoot = nullptr;

    return OnSetupNormalView();
}
//-------------------------------------------------------------------------------
// reset to standard statistics view
void ShowNormalUIComponents()
{
    ShowWindow(GetDlgItem(g_hDlg,IDC_NUMNODES),SW_SHOW);
    ShowWindow(GetDlgItem(g_hDlg,IDC_ENODEWND),SW_SHOW);
    ShowWindow(GetDlgItem(g_hDlg,IDC_NUMSHADERS),SW_SHOW);
    ShowWindow(GetDlgItem(g_hDlg,IDC_LOADTIME),SW_SHOW);
    ShowWindow(GetDlgItem(g_hDlg,IDC_ESHADER),SW_SHOW);
    ShowWindow(GetDlgItem(g_hDlg,IDC_ELOAD),SW_SHOW);
    ShowWindow(GetDlgItem(g_hDlg,IDC_VIEWMATRIX),SW_HIDE);
}
//-------------------------------------------------------------------------------
int CDisplay::OnSetupNormalView()
{
    if (VIEWMODE_NODE == m_iViewMode)
    {
        ShowNormalUIComponents();
    }

    // now ... change the meaning of the statistics fields back
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMVERTS),"Vertices:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMNODES),"Nodes:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMFACES),"Faces:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMSHADERS),"Shaders:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMMATS),"Materials:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMMESHES),"Meshes:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_LOADTIME),"Time:");

    FillDefaultStatistics();
    SetViewMode(VIEWMODE_FULL);

    // for debugging
    m_pcCurrentMaterial = nullptr;
    m_pcCurrentTexture = nullptr;
    m_pcCurrentNode = nullptr;

    // redraw the color fields in the UI --- their purpose has possibly changed
    UpdateColorFieldsInUI();
    UpdateWindow(g_hDlg);
    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::OnSetupNodeView(NodeInfo* pcNew)
{
    ai_assert(nullptr != pcNew);

    if (m_pcCurrentNode == pcNew)return 2;

    // now ... change the meaning of the statistics fields back
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMVERTS),"Vertices:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMFACES),"Faces:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMMATS),"Materials:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMMESHES),"Meshes:");

    ShowWindow(GetDlgItem(g_hDlg,IDC_NUMNODES),SW_HIDE);
    ShowWindow(GetDlgItem(g_hDlg,IDC_ENODEWND),SW_HIDE);
    ShowWindow(GetDlgItem(g_hDlg,IDC_NUMSHADERS),SW_HIDE);
    ShowWindow(GetDlgItem(g_hDlg,IDC_LOADTIME),SW_HIDE);
    ShowWindow(GetDlgItem(g_hDlg,IDC_ESHADER),SW_HIDE);
    ShowWindow(GetDlgItem(g_hDlg,IDC_ELOAD),SW_HIDE);
    ShowWindow(GetDlgItem(g_hDlg,IDC_VIEWMATRIX),SW_SHOW);

    char szTemp[1024];
    sprintf(szTemp,
        "%.2f %.2f %.2f\r\n"
        "%.2f %.2f %.2f\r\n"
        "%.2f %.2f %.2f\r\n"
        "%.2f %.2f %.2f\r\n",
        pcNew->psNode->mTransformation.a1,
        pcNew->psNode->mTransformation.b1,
        pcNew->psNode->mTransformation.c1,
        pcNew->psNode->mTransformation.a2,
        pcNew->psNode->mTransformation.b2,
        pcNew->psNode->mTransformation.c2,
        pcNew->psNode->mTransformation.a3,
        pcNew->psNode->mTransformation.b3,
        pcNew->psNode->mTransformation.c3,
        pcNew->psNode->mTransformation.a4,
        pcNew->psNode->mTransformation.b4,
        pcNew->psNode->mTransformation.c4);
    SetWindowText(GetDlgItem(g_hDlg,IDC_VIEWMATRIX),szTemp);


    m_pcCurrentNode = pcNew;
    SetViewMode(VIEWMODE_NODE);

    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::OnSetupMaterialView(MaterialInfo* pcNew)
{
    ai_assert(nullptr != pcNew);

    if (m_pcCurrentMaterial == pcNew)return 2;

    if (VIEWMODE_NODE == m_iViewMode)
        ShowNormalUIComponents();

    m_pcCurrentMaterial = pcNew;
    SetViewMode(VIEWMODE_MATERIAL);

    // redraw the color fields in the UI --- their purpose has possibly changed
    UpdateColorFieldsInUI();
    UpdateWindow(g_hDlg);
    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::OnSetupTextureView(TextureInfo* pcNew)
{
    ai_assert(nullptr != pcNew);

    if (this->m_pcCurrentTexture == pcNew)return 2;

    if (VIEWMODE_NODE == this->m_iViewMode)
    {
        ShowNormalUIComponents();
    }

    if ((aiTextureType_OPACITY | 0x40000000) == pcNew->iType)
    {
        // for opacity textures display a warn message
        CLogDisplay::Instance().AddEntry("[INFO] This texture is not existing in the "
            "original mesh",D3DCOLOR_ARGB(0xFF,0xFF,0xFF,0));
        CLogDisplay::Instance().AddEntry("It is a copy of the alpha channel of the first "
            "diffuse texture",D3DCOLOR_ARGB(0xFF,0xFF,0xFF,0));
    }

    // check whether the pattern background effect is supported
    if (g_sCaps.PixelShaderVersion < D3DPS_VERSION(3,0))
    {
        CLogDisplay::Instance().AddEntry("[WARN] The background shader won't work "
            "on your system, it required PS 3.0 hardware. A default color is used ...",
            D3DCOLOR_ARGB(0xFF,0xFF,0x00,0));
    }

    this->m_fTextureZoom = 1000.0f;
    this->m_vTextureOffset.x = this->m_vTextureOffset.y = 0.0f;

    this->m_pcCurrentTexture = pcNew;
    this->SetViewMode(VIEWMODE_TEXTURE);

    // now ... change the meaning of the statistics fields
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMVERTS),"Width:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMNODES),"Height:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMFACES),"Format:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMSHADERS),"MIPs:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMMATS),"UV:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_NUMMESHES),"Blend:");
    SetWindowText(GetDlgItem(g_hDlg,IDC_LOADTIME),"Op:");

    // and fill them with data
    D3DSURFACE_DESC sDesc;
    if (pcNew->piTexture && *pcNew->piTexture) {
        (*pcNew->piTexture)->GetLevelDesc(0,&sDesc);
        char szTemp[128];

        sprintf(szTemp,"%i",sDesc.Width);
        SetWindowText(GetDlgItem(g_hDlg,IDC_EVERT),szTemp);

        sprintf(szTemp,"%i",sDesc.Height);
        SetWindowText(GetDlgItem(g_hDlg,IDC_ENODEWND),szTemp);

        sprintf(szTemp,"%i",(*pcNew->piTexture)->GetLevelCount());
        SetWindowText(GetDlgItem(g_hDlg,IDC_ESHADER),szTemp);

        sprintf(szTemp,"%u",pcNew->iUV);
        SetWindowText(GetDlgItem(g_hDlg,IDC_EMAT),szTemp);

        sprintf(szTemp,"%f",pcNew->fBlend);
        SetWindowText(GetDlgItem(g_hDlg,IDC_EMESH),szTemp);

        const char* szOp;
        switch (pcNew->eOp)
        {
        case aiTextureOp_Add:
            szOp = "add";
            break;
        case aiTextureOp_Subtract:
            szOp = "sub";
            break;
        case aiTextureOp_Divide:
            szOp = "div";
            break;
        case aiTextureOp_SignedAdd:
            szOp = "addsign";
            break;
        case aiTextureOp_SmoothAdd:
            szOp = "addsmooth";
            break;
        default:
            szOp = "mul";
            break;
        };
        SetWindowText(GetDlgItem(g_hDlg,IDC_ELOAD),szOp);

        // NOTE: Format is always ARGB8888 since other formats are
        // converted to this format ...
        SetWindowText(GetDlgItem(g_hDlg,IDC_EFACE),"ARGB8");

        // check whether this is the default texture
        if (pcNew->piTexture)
        {
            // {9785DA94-1D96-426b-B3CB-BADC36347F5E}
            static const GUID guidPrivateData =
            { 0x9785da94, 0x1d96, 0x426b,
            { 0xb3, 0xcb, 0xba, 0xdc, 0x36, 0x34, 0x7f, 0x5e } };

            uint32_t iData = 0;
            DWORD dwSize = 4;
            (*pcNew->piTexture)->GetPrivateData(guidPrivateData,&iData,&dwSize);

            if (0xFFFFFFFF == iData)
            {
                CLogDisplay::Instance().AddEntry("[ERROR] Texture could not be loaded. "
                    "The displayed texture is a default texture",
                    D3DCOLOR_ARGB(0xFF,0xFF,0,0));
                return 0;
            }
        }
    }
    // redraw the color fields in the UI --- their purpose has possibly changed
    UpdateColorFieldsInUI();
    UpdateWindow(g_hDlg);
    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::OnSetup(HTREEITEM p_hTreeItem)
{
    // search in our list for the item
    union   {
        TextureInfo* pcNew;
        NodeInfo* pcNew2;
        MaterialInfo* pcNew3;
    };

    pcNew = nullptr;
    for (std::vector<TextureInfo>::iterator i =  m_asTextures.begin();i != m_asTextures.end();++i){
        if (p_hTreeItem == (*i).hTreeItem)  {
            pcNew = &(*i);
            break;
        }
    }
    if (pcNew)  {
        return OnSetupTextureView(pcNew);
    }

    // search the node list
    for (std::vector<NodeInfo>::iterator i =  m_asNodes.begin(); i != m_asNodes.end();++i){
        if (p_hTreeItem == (*i).hTreeItem)  {
            pcNew2 = &(*i);
            break;
        }
    }
    if (pcNew2) {
        return OnSetupNodeView(pcNew2);
    }

    // search the material list
    for (std::vector<MaterialInfo>::iterator i =  m_asMaterials.begin();i != m_asMaterials.end();++i){
        if (p_hTreeItem == (*i).hTreeItem){
            pcNew3 = &(*i);
            break;
        }
    }
    if (pcNew3) {
        return OnSetupMaterialView(pcNew3);
    }
    return OnSetupNormalView();
}
//-------------------------------------------------------------------------------
int CDisplay::ShowTreeViewContextMenu(HTREEITEM hItem)
{
    ai_assert(nullptr != hItem);

    HMENU hDisplay = nullptr;

    // search in our list for the item
    TextureInfo* pcNew = nullptr;
    for (std::vector<TextureInfo>::iterator
        i =  m_asTextures.begin();
        i != m_asTextures.end();++i)
    {
        if (hItem == (*i).hTreeItem)    {
            pcNew = &(*i);
            break;
        }
    }
    if (pcNew)
    {
        HMENU hMenu = LoadMenu(g_hInstance,MAKEINTRESOURCE(IDR_TXPOPUP));
        hDisplay = GetSubMenu(hMenu,0);
    }

    // search in the material list for the item
    MaterialInfo* pcNew2 = nullptr;
    for (std::vector<MaterialInfo>::iterator
        i =  m_asMaterials.begin();
        i != m_asMaterials.end();++i)
    {
        if (hItem == (*i).hTreeItem)    {
            pcNew2 = &(*i);
            break;
        }
    }
    if (pcNew2)
    {
        HMENU hMenu = LoadMenu(g_hInstance,MAKEINTRESOURCE(IDR_MATPOPUP));
        hDisplay = GetSubMenu(hMenu,0);
    }
    if (nullptr != hDisplay)
    {
        // select this entry (this should all OnSetup())
        TreeView_Select(GetDlgItem(g_hDlg,IDC_TREE1),hItem,TVGN_CARET);

        // FIX: Render the scene once that the correct texture/material
        // is displayed while the context menu is active
        OnRender();

        POINT sPoint;
        GetCursorPos(&sPoint);
        TrackPopupMenu(hDisplay, TPM_LEFTALIGN, sPoint.x, sPoint.y, 0,
            g_hDlg,nullptr);
    }
    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::HandleTreeViewPopup(WPARAM wParam,LPARAM lParam)
{
    // get the current selected material
    std::vector<Info> apclrOut;
    const char* szMatKey = "";

    switch (LOWORD(wParam))
    {
    case ID_SOLONG_CLEARDIFFUSECOLOR:
        for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
        {
            if (this->m_pcCurrentMaterial->iIndex == g_pcAsset->pcScene->mMeshes[i]->mMaterialIndex)
            {
                apclrOut.push_back( Info( &g_pcAsset->apcMeshes[i]->vDiffuseColor,
                    g_pcAsset->apcMeshes[i],"DIFFUSE_COLOR"));
            }
        }
        szMatKey = "$clr.diffuse";
        break;
    case ID_SOLONG_CLEARSPECULARCOLOR:
        for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
        {
            if (this->m_pcCurrentMaterial->iIndex == g_pcAsset->pcScene->mMeshes[i]->mMaterialIndex)
            {
                apclrOut.push_back( Info( &g_pcAsset->apcMeshes[i]->vSpecularColor,
                    g_pcAsset->apcMeshes[i],"SPECULAR_COLOR"));
            }
        }
        szMatKey = "$clr.specular";
        break;
    case ID_SOLONG_CLEARAMBIENTCOLOR:
        for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
        {
            if (this->m_pcCurrentMaterial->iIndex == g_pcAsset->pcScene->mMeshes[i]->mMaterialIndex)
            {
                apclrOut.push_back( Info( &g_pcAsset->apcMeshes[i]->vAmbientColor,
                    g_pcAsset->apcMeshes[i],"AMBIENT_COLOR"));
            }
        }
        szMatKey = "$clr.ambient";
        break;
    case ID_SOLONG_CLEAREMISSIVECOLOR:
        for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
        {
            if (this->m_pcCurrentMaterial->iIndex == g_pcAsset->pcScene->mMeshes[i]->mMaterialIndex)
            {
                apclrOut.push_back( Info( &g_pcAsset->apcMeshes[i]->vEmissiveColor,
                    g_pcAsset->apcMeshes[i],"EMISSIVE_COLOR"));
            }
        }
        szMatKey = "$clr.emissive";
        break;
    default:

        // let the next function do this ... no spaghetti code ;-)
        HandleTreeViewPopup2(wParam,lParam);
    };
    if (!apclrOut.empty())
    {
        aiColor4D clrOld = *((aiColor4D*)(apclrOut.front().pclrColor));

        CHOOSECOLOR clr;
        clr.lStructSize = sizeof(CHOOSECOLOR);
        clr.hwndOwner = g_hDlg;
        clr.Flags = CC_RGBINIT | CC_FULLOPEN;
        clr.rgbResult = RGB(
            clamp<unsigned char>(clrOld.r * 255.0f),
            clamp<unsigned char>(clrOld.g * 255.0f),
            clamp<unsigned char>(clrOld.b * 255.0f));
        clr.lpCustColors = g_aclCustomColors;
        clr.lpfnHook = nullptr;
        clr.lpTemplateName = nullptr;
        clr.lCustData = 0;

        ChooseColor(&clr);

        clrOld.r = (float)(((unsigned int)clr.rgbResult)       & 0xFF) / 255.0f;
        clrOld.g = (float)(((unsigned int)clr.rgbResult >> 8)  & 0xFF) / 255.0f;
        clrOld.b = (float)(((unsigned int)clr.rgbResult >> 16) & 0xFF) / 255.0f;

        // update the color values in the mesh instances and
        // update all shaders ...
        for (std::vector<Info>::iterator
            i =  apclrOut.begin();
            i != apclrOut.end();++i)
        {
            *((*i).pclrColor) = *((D3DXVECTOR4*)&clrOld);
            if (!(*i).pMesh->bSharedFX)
            {
                (*i).pMesh->piEffect->SetVector((*i).szShaderParam,(*i).pclrColor);
            }
        }

        // change the material key ...
        aiMaterial* pcMat = (aiMaterial*)g_pcAsset->pcScene->mMaterials[
            this->m_pcCurrentMaterial->iIndex];
        pcMat->AddProperty<aiColor4D>(&clrOld,1,szMatKey,0,0);

        if (ID_SOLONG_CLEARSPECULARCOLOR == LOWORD(wParam) &&
            aiShadingMode_Gouraud == apclrOut.front().pMesh->eShadingMode)
        {
            CLogDisplay::Instance().AddEntry("[INFO] You have just changed the specular "
                "material color",D3DCOLOR_ARGB(0xFF,0xFF,0xFF,0));
            CLogDisplay::Instance().AddEntry(
                "This is great, especially since there is currently no specular shading",
                D3DCOLOR_ARGB(0xFF,0xFF,0xFF,0));
        }
    }
    return 1;
}
//-------------------------------------------------------------------------------
int CALLBACK TreeViewCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
    if (lParamSort == lParam1)return -1;
    if (lParamSort == lParam2)return 1;
    return 0;
}
//-------------------------------------------------------------------------------
int CDisplay::HandleTreeViewPopup2(WPARAM wParam,LPARAM /*lParam*/)
{
    char szFileName[MAX_PATH];
    DWORD dwTemp = MAX_PATH;

    switch (LOWORD(wParam))
    {
    case ID_HEY_REPLACE:
        {
        // get a path to a new texture
        if(ERROR_SUCCESS != RegQueryValueEx(g_hRegistry,"ReplaceTextureSrc",nullptr,nullptr,
            (BYTE*)szFileName,&dwTemp))
        {
            // Key was not found. Use C:
            strcpy(szFileName,"");
        }
        else
        {
            // need to remove the file name
            char* sz = strrchr(szFileName,'\\');
            if (!sz)
                sz = strrchr(szFileName,'/');
            if (sz)
                *sz = 0;
        }
        OPENFILENAME sFilename1 = {
            sizeof(OPENFILENAME),
            g_hDlg,GetModuleHandle(nullptr),
            "Textures\0*.png;*.dds;*.tga;*.bmp;*.tif;*.ppm;*.ppx;*.jpg;*.jpeg;*.exr\0*.*\0",
            nullptr, 0, 1,
            szFileName, MAX_PATH, nullptr, 0, nullptr,
            "Replace this texture",
            OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_NOCHANGEDIR,
            0, 1, ".jpg", 0, nullptr, nullptr
        };
        if(GetOpenFileName(&sFilename1) == 0) return 0;

        // Now store the file in the registry
        RegSetValueExA(g_hRegistry,"ReplaceTextureSrc",0,REG_SZ,(const BYTE*)szFileName,MAX_PATH);
        this->ReplaceCurrentTexture(szFileName);
        }
        return 1;

    case ID_HEY_EXPORT:
        {
        if(ERROR_SUCCESS != RegQueryValueEx(g_hRegistry,"TextureExportDest",nullptr,nullptr,
            (BYTE*)szFileName,&dwTemp))
        {
            // Key was not found. Use C:
            strcpy(szFileName,"");
        }
        else
        {
            // need to remove the file name
            char* sz = strrchr(szFileName,'\\');
            if (!sz)
                sz = strrchr(szFileName,'/');
            if (sz)
                *sz = 0;
        }
        OPENFILENAME sFilename1 = {
            sizeof(OPENFILENAME),
            g_hDlg,GetModuleHandle(nullptr),
            "Textures\0*.png;*.dds;*.bmp;*.tif;*.pfm;*.jpg;*.jpeg;*.hdr\0*.*\0", nullptr, 0, 1,
            szFileName, MAX_PATH, nullptr, 0, nullptr,
            "Export texture to file",
            OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_NOCHANGEDIR,
            0, 1, ".png", 0, nullptr, nullptr
        };
        if(GetSaveFileName(&sFilename1) == 0) return 0;

        // Now store the file in the registry
        RegSetValueExA(g_hRegistry,"TextureExportDest",0,REG_SZ,(const BYTE*)szFileName,MAX_PATH);

        // determine the file format ...
        D3DXIMAGE_FILEFORMAT eFormat = D3DXIFF_PNG;
        const char* sz = strrchr(szFileName,'.');
        if (sz)
        {
            ++sz;
            if (0 == Assimp::ASSIMP_stricmp(sz,"pfm"))eFormat = D3DXIFF_PFM;
            else if (0 == Assimp::ASSIMP_stricmp(sz,"dds"))eFormat = D3DXIFF_DDS;
            else if (0 == Assimp::ASSIMP_stricmp(sz,"jpg"))eFormat = D3DXIFF_JPG;
            else if (0 == Assimp::ASSIMP_stricmp(sz,"jpeg"))eFormat = D3DXIFF_JPG;
            else if (0 == Assimp::ASSIMP_stricmp(sz,"hdr"))eFormat = D3DXIFF_HDR;
            else if (0 == Assimp::ASSIMP_stricmp(sz,"bmp"))eFormat = D3DXIFF_BMP;
        }

        // get a pointer to the first surface of the current texture
        IDirect3DSurface9* pi = nullptr;
        (*this->m_pcCurrentTexture->piTexture)->GetSurfaceLevel(0,&pi);
        if(!pi || FAILED(D3DXSaveSurfaceToFile(szFileName,eFormat,pi,nullptr,nullptr)))
        {
            CLogDisplay::Instance().AddEntry("[ERROR] Unable to export texture",
                D3DCOLOR_ARGB(0xFF,0xFF,0,0));
        }
        else
        {
            CLogDisplay::Instance().AddEntry("[INFO] The texture has been exported",
                D3DCOLOR_ARGB(0xFF,0xFF,0xFF,0));
        }
        if(pi)pi->Release();
        }
        return 1;

    case ID_HEY_REMOVE:
        {

        if(IDYES != MessageBox(g_hDlg,"To recover the texture you need to reload the model. Do you wish to continue?",
            "Remove texture",MB_YESNO)) {
            return 1;
        }

        aiMaterial* pcMat = (aiMaterial*)g_pcAsset->pcScene->mMaterials[
            m_pcCurrentTexture->iMatIndex];

        unsigned int s;
        if (m_pcCurrentTexture->iType == (aiTextureType_OPACITY | 0x40000000))
        {
            // set a special property to indicate that no alpha channel is required
            int iVal = 1;
            pcMat->AddProperty<int>(&iVal,1,"no_a_from_d",0,0);
            s = aiTextureType_OPACITY;
        }
        else s = m_pcCurrentTexture->iType;
        pcMat->RemoveProperty(AI_MATKEY_TEXTURE(m_pcCurrentTexture->iType,0));

        // need to update all meshes associated with this material
        for (unsigned int i = 0;i < g_pcAsset->pcScene->mNumMeshes;++i)
        {
            if (m_pcCurrentTexture->iMatIndex == g_pcAsset->pcScene->mMeshes[i]->mMaterialIndex)
            {
                CMaterialManager::Instance().DeleteMaterial(g_pcAsset->apcMeshes[i]);
                CMaterialManager::Instance().CreateMaterial(g_pcAsset->apcMeshes[i],g_pcAsset->pcScene->mMeshes[i]);
            }
        }
        // find the corresponding MaterialInfo structure
        const unsigned int iMatIndex = m_pcCurrentTexture->iMatIndex;
        for (std::vector<MaterialInfo>::iterator
            a =  m_asMaterials.begin();
            a != m_asMaterials.end();++a)
        {
            if (iMatIndex == (*a).iIndex)
            {
                // good news. we will also need to find all other textures
                // associated with this item ...
                for (std::vector<TextureInfo>::iterator
                    n =  m_asTextures.begin();
                    n != m_asTextures.end();++n)
                {
                    if ((*n).iMatIndex == iMatIndex)
                    {
                        n =  m_asTextures.erase(n);
                        if (m_asTextures.end() == n)break;
                    }
                }
                // delete this material from all lists ...
                TreeView_DeleteItem(GetDlgItem(g_hDlg,IDC_TREE1),(*a).hTreeItem);
                this->m_asMaterials.erase(a);
                break;
            }
        }

        // add the new material to the list and make sure it will be fully expanded
        AddMaterialToDisplayList(m_hRoot,iMatIndex);
        HTREEITEM hNewItem = m_asMaterials.back().hTreeItem;
        TreeView_Expand(GetDlgItem(g_hDlg,IDC_TREE1),hNewItem,TVE_EXPAND);

        // we need to sort the list, materials come first, then nodes
        TVSORTCB sSort;
        sSort.hParent = m_hRoot;
        sSort.lParam = 10;
        sSort.lpfnCompare = &TreeViewCompareFunc;
        TreeView_SortChildrenCB(GetDlgItem(g_hDlg,IDC_TREE1),&sSort,0);

        // the texture was selected, but the silly user has just deleted it
        // ... go back to normal viewing mode
        TreeView_Select(GetDlgItem(g_hDlg,IDC_TREE1),m_hRoot,TVGN_CARET);
        return 1;
        }
    }
    return 0;
}
//-------------------------------------------------------------------------------
// Setup stereo view
int CDisplay::SetupStereoView()
{
    if (nullptr != g_pcAsset && nullptr != g_pcAsset->pcScene->mRootNode)
    {
        // enable the RED, GREEN and ALPHA channels
        g_piDevice->SetRenderState(D3DRS_COLORWRITEENABLE,
            D3DCOLORWRITEENABLE_RED |
            D3DCOLORWRITEENABLE_ALPHA |
            D3DCOLORWRITEENABLE_GREEN);

        // move the camera a little bit to the left
        g_sCamera.vPos -= g_sCamera.vRight * 0.03f;
    }
    return 1;
}
//-------------------------------------------------------------------------------
// Do the actual rendering pass for the stereo view
int CDisplay::RenderStereoView(const aiMatrix4x4& m)
{
    // and rerender the scene
    if (nullptr != g_pcAsset && nullptr != g_pcAsset->pcScene->mRootNode)
    {
        // enable the BLUE, GREEN and ALPHA channels
        g_piDevice->SetRenderState(D3DRS_COLORWRITEENABLE,
            D3DCOLORWRITEENABLE_GREEN |
            D3DCOLORWRITEENABLE_ALPHA |
            D3DCOLORWRITEENABLE_BLUE);

        // clear the z-buffer
        g_piDevice->Clear(0,nullptr,D3DCLEAR_ZBUFFER,0,1.0f,0);

        // move the camera a little bit to the right
        g_sCamera.vPos += g_sCamera.vRight * 0.06f;

        RenderNode(g_pcAsset->pcScene->mRootNode,m,false);
        g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,FALSE);
        RenderNode(g_pcAsset->pcScene->mRootNode,m,true);
        g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,TRUE);

        // (move back to the original position)
        g_sCamera.vPos -= g_sCamera.vRight * 0.03f;

        // re-enable all channels
        g_piDevice->SetRenderState(D3DRS_COLORWRITEENABLE,
            D3DCOLORWRITEENABLE_RED |
            D3DCOLORWRITEENABLE_GREEN |
            D3DCOLORWRITEENABLE_ALPHA |
            D3DCOLORWRITEENABLE_BLUE);
    }
    return 1;
}
//-------------------------------------------------------------------------------
// Process input for the texture view
int CDisplay::HandleInputTextureView()
{
    HandleMouseInputTextureView();
    HandleKeyboardInputTextureView();
    return 1;
}
//-------------------------------------------------------------------------------
// Get input for the current state
int CDisplay::HandleInput()
{
    if(CBackgroundPainter::TEXTURE_CUBE == CBackgroundPainter::Instance().GetMode())
        HandleMouseInputSkyBox();

    // handle input commands
    HandleMouseInputLightRotate();
    HandleMouseInputLightIntensityAndColor();
    if(g_bFPSView)
    {
        HandleMouseInputFPS();
        HandleKeyboardInputFPS();
    }
    else HandleMouseInputLocal();

    // compute auto rotation depending on the time which has passed
    if (g_sOptions.bRotate)
    {
        aiMatrix4x4 mMat;
        D3DXMatrixRotationYawPitchRoll((D3DXMATRIX*)&mMat,
            g_vRotateSpeed.x * g_fElpasedTime,
            g_vRotateSpeed.y * g_fElpasedTime,
            g_vRotateSpeed.z * g_fElpasedTime);
        g_mWorldRotate = g_mWorldRotate * mMat;
    }

    // Handle rotations of light source(s)
    if (g_sOptions.bLightRotate)
    {
        aiMatrix4x4 mMat;
        D3DXMatrixRotationYawPitchRoll((D3DXMATRIX*)&mMat,
            g_vRotateSpeed.x * g_fElpasedTime * 0.5f,
            g_vRotateSpeed.y * g_fElpasedTime * 0.5f,
            g_vRotateSpeed.z * g_fElpasedTime * 0.5f);

        D3DXVec3TransformNormal((D3DXVECTOR3*)&g_avLightDirs[0],
            (D3DXVECTOR3*)&g_avLightDirs[0],(D3DXMATRIX*)&mMat);

        g_avLightDirs[0].Normalize();
    }
    return 1;
}
//-------------------------------------------------------------------------------
// Process input for an empty scene view to allow for sky-box rotations
int CDisplay::HandleInputEmptyScene()
{
    if(CBackgroundPainter::TEXTURE_CUBE == CBackgroundPainter::Instance().GetMode())
    {
        if (g_bFPSView)
        {
            HandleMouseInputFPS();
            HandleKeyboardInputFPS();
        }
        HandleMouseInputSkyBox();

        // need to store the last mouse position in the global variable
        // HandleMouseInputFPS() is doing this internally
        if (!g_bFPSView)
        {
            g_LastmousePos.x = g_mousePos.x;
            g_LastmousePos.y = g_mousePos.y;
        }
    }
    return 1;
}
//-------------------------------------------------------------------------------
// Draw the HUD on top of the scene
int CDisplay::DrawHUD()
{
  // HACK: (thom) can't get the effect to work on non-shader cards, therefore deactivated for the moment
  if( g_sCaps.PixelShaderVersion < D3DPS_VERSION(2,0))
    return 1;

    // get the dimension of the back buffer
    RECT sRect;
    GetWindowRect(GetDlgItem(g_hDlg,IDC_RT),&sRect);
    sRect.right -= sRect.left;
    sRect.bottom -= sRect.top;

    // commit the texture to the shader
    // FIX: Necessary because the texture view is also using this shader
    g_piPassThroughEffect->SetTexture("TEXTURE_2D",g_pcTexture);

    // NOTE: The shader might be used for other purposes, too.
    // So ensure the right technique is there
    if( g_sCaps.PixelShaderVersion < D3DPS_VERSION(2,0))
        g_piPassThroughEffect->SetTechnique( "PassThrough_FF");
    else
        g_piPassThroughEffect->SetTechnique("PassThrough");

    // build vertices for drawing from system memory
    UINT dw;
    g_piPassThroughEffect->Begin(&dw,0);
    g_piPassThroughEffect->BeginPass(0);

    D3DSURFACE_DESC sDesc;
    g_pcTexture->GetLevelDesc(0,&sDesc);
    SVertex as[4];
    float fHalfX = ((float)sRect.right-(float)sDesc.Width) / 2.0f;
    float fHalfY = ((float)sRect.bottom-(float)sDesc.Height) / 2.0f;
    as[1].x = fHalfX;
    as[1].y = fHalfY;
    as[1].z = 0.2f;
    as[1].w = 1.0f;
    as[1].u = 0.0f;
    as[1].v = 0.0f;

    as[3].x = (float)sRect.right-fHalfX;
    as[3].y = fHalfY;
    as[3].z = 0.2f;
    as[3].w = 1.0f;
    as[3].u = 1.0f;
    as[3].v = 0.0f;

    as[0].x = fHalfX;
    as[0].y = (float)sRect.bottom-fHalfY;
    as[0].z = 0.2f;
    as[0].w = 1.0f;
    as[0].u = 0.0f;
    as[0].v = 1.0f;

    as[2].x = (float)sRect.right-fHalfX;
    as[2].y = (float)sRect.bottom-fHalfY;
    as[2].z = 0.2f;
    as[2].w = 1.0f;
    as[2].u = 1.0f;
    as[2].v = 1.0f;

    as[0].x -= 0.5f;as[1].x -= 0.5f;as[2].x -= 0.5f;as[3].x -= 0.5f;
    as[0].y -= 0.5f;as[1].y -= 0.5f;as[2].y -= 0.5f;as[3].y -= 0.5f;

    g_piDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
    g_piDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);

    // draw the screen-filling squad
    DWORD dw2;g_piDevice->GetFVF(&dw2);
    g_piDevice->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
    g_piDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP,2,
        &as,sizeof(SVertex));

    // end the effect and recover the old vertex format
    g_piPassThroughEffect->EndPass();
    g_piPassThroughEffect->End();

    g_piDevice->SetFVF(dw2);
    return 1;
}
//-------------------------------------------------------------------------------
// Render the full scene, all nodes
int CDisplay::RenderFullScene()
{
    // reset the color index used for drawing normals
    g_iCurrentColor = 0;

    aiMatrix4x4 pcProj;
    GetProjectionMatrix(pcProj);

    vPos = GetCameraMatrix(mViewProjection);
    mViewProjection = mViewProjection * pcProj;

    // setup wireframe/solid rendering mode
    if (g_sOptions.eDrawMode == RenderOptions::WIREFRAME)
        g_piDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
    else g_piDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);

    if (g_sOptions.bCulling)
        g_piDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);
    else g_piDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);

    // for high-quality mode, enable anisotropic texture filtering
    if (g_sOptions.bLowQuality) {
        for (DWORD d = 0; d < 8;++d) {
            g_piDevice->SetSamplerState(d,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
            g_piDevice->SetSamplerState(d,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
            g_piDevice->SetSamplerState(d,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
        }
    }
    else {
        for (DWORD d = 0; d < 8;++d) {
            g_piDevice->SetSamplerState(d,D3DSAMP_MAGFILTER,D3DTEXF_ANISOTROPIC);
            g_piDevice->SetSamplerState(d,D3DSAMP_MINFILTER,D3DTEXF_ANISOTROPIC);
            g_piDevice->SetSamplerState(d,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);

            g_piDevice->SetSamplerState(d,D3DSAMP_MAXANISOTROPY,g_sCaps.MaxAnisotropy);
        }
    }

    // draw the scene background (clear and texture 2d)
    CBackgroundPainter::Instance().OnPreRender();

    // setup the stereo view if necessary
    if (g_sOptions.bStereoView)
        SetupStereoView();


    // draw all opaque objects in the scene
    aiMatrix4x4 m;
    if (nullptr != g_pcAsset && nullptr != g_pcAsset->pcScene->mRootNode)
    {
        HandleInput();
        m =  g_mWorld * g_mWorldRotate;
        RenderNode(g_pcAsset->pcScene->mRootNode,m,false);
    }

    // if a cube texture is loaded as background image, the user
    // should be able to rotate it even if no asset is loaded
    HandleInputEmptyScene();

    // draw the scene background
    CBackgroundPainter::Instance().OnPostRender();

    // draw all non-opaque objects in the scene
    if (nullptr != g_pcAsset && nullptr != g_pcAsset->pcScene->mRootNode)
    {
        // disable the z-buffer
        if (!g_sOptions.bNoAlphaBlending) {
            g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,FALSE);
        }
        RenderNode(g_pcAsset->pcScene->mRootNode,m,true);

        if (!g_sOptions.bNoAlphaBlending) {
            g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,TRUE);
        }
    }

    // setup the stereo view if necessary
    if (g_sOptions.bStereoView)
        RenderStereoView(m);

    // render the skeleton if necessary
    if (g_sOptions.bSkeleton && nullptr != g_pcAsset && nullptr != g_pcAsset->pcScene->mRootNode) {
        // disable the z-buffer
        g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,FALSE);

        if (g_sOptions.eDrawMode != RenderOptions::WIREFRAME) {
            g_piDevice->SetRenderState(D3DRS_ZENABLE,FALSE);
        }

        g_piDevice->SetVertexDeclaration( gDefaultVertexDecl);
        // this is very similar to the code in SetupMaterial()
        ID3DXEffect* piEnd = g_piNormalsEffect;
        aiMatrix4x4 pcProj2 = m * mViewProjection;

        D3DXVECTOR4 vVector(1.f,0.f,0.f,1.f);
        piEnd->SetVector("OUTPUT_COLOR",&vVector);
        piEnd->SetMatrix("WorldViewProjection", (const D3DXMATRIX*)&pcProj2);

        UINT dwPasses = 0;
        piEnd->Begin(&dwPasses,0);
        piEnd->BeginPass(0);

        RenderSkeleton(g_pcAsset->pcScene->mRootNode,m,m);

        piEnd->EndPass();piEnd->End();
        g_piDevice->SetRenderState(D3DRS_ZWRITEENABLE,TRUE);
        g_piDevice->SetRenderState(D3DRS_ZENABLE,TRUE);
    }

    // draw the HUD texture on top of the rendered scene using
    // pre-projected vertices
    if (!g_bFPSView && g_pcAsset && g_pcTexture)
        DrawHUD();

    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::RenderMaterialView()
{
    return 1;
}
//-------------------------------------------------------------------------------
// Render animation skeleton
int CDisplay::RenderSkeleton (aiNode* piNode,const aiMatrix4x4& piMatrix, const aiMatrix4x4& parent)
{
    aiMatrix4x4 me = g_pcAsset->mAnimator->GetGlobalTransform( piNode);

    me.Transpose();
    //me *= piMatrix;

    if (piNode->mParent) {
        AssetHelper::LineVertex data[2];
        data[0].dColorDiffuse = data[1].dColorDiffuse = D3DCOLOR_ARGB(0xff,0xff,0,0);

        data[0].vPosition.x = parent.d1;
        data[0].vPosition.y = parent.d2;
        data[0].vPosition.z = parent.d3;

        data[1].vPosition.x = me.d1;
        data[1].vPosition.y = me.d2;
        data[1].vPosition.z = me.d3;

        g_piDevice->DrawPrimitiveUP(D3DPT_LINELIST,1,&data,sizeof(AssetHelper::LineVertex));
    }

    // render all child nodes
    for (unsigned int i = 0; i < piNode->mNumChildren;++i)
        RenderSkeleton(piNode->mChildren[i],piMatrix, me );

    return 1;
}
//-------------------------------------------------------------------------------
// Render a single node
int CDisplay::RenderNode (aiNode* piNode,const aiMatrix4x4& piMatrix,
    bool bAlpha /*= false*/)
{
    aiMatrix4x4 aiMe = g_pcAsset->mAnimator->GetGlobalTransform( piNode);

    aiMe.Transpose();
    aiMe *= piMatrix;

    bool bChangedVM = false;
    if (VIEWMODE_NODE == m_iViewMode && m_pcCurrentNode)
    {
        if (piNode != m_pcCurrentNode->psNode)
        {
            // directly call our children
            for (unsigned int i = 0; i < piNode->mNumChildren;++i)
                RenderNode(piNode->mChildren[i],piMatrix,bAlpha );

            return 1;
        }
        m_iViewMode = VIEWMODE_FULL;
        bChangedVM = true;
    }

    aiMatrix4x4 pcProj = aiMe * mViewProjection;

    aiMatrix4x4 pcCam = aiMe;
    pcCam.Inverse().Transpose();

    // VERY UNOPTIMIZED, much stuff is redundant. Who cares?
    if (!g_sOptions.bRenderMats && !bAlpha)
    {
        // this is very similar to the code in SetupMaterial()
        ID3DXEffect* piEnd = g_piDefaultEffect;

        // commit transformation matrices to the shader
        piEnd->SetMatrix("WorldViewProjection",
            (const D3DXMATRIX*)&pcProj);

        piEnd->SetMatrix("World",(const D3DXMATRIX*)&aiMe);
        piEnd->SetMatrix("WorldInverseTranspose",
            (const D3DXMATRIX*)&pcCam);

        if ( CBackgroundPainter::TEXTURE_CUBE == CBackgroundPainter::Instance().GetMode())
        {
            pcCam = pcCam * pcProj;
            piEnd->SetMatrix("ViewProj",(const D3DXMATRIX*)&pcCam);
            pcCam.Inverse();
            piEnd->SetMatrix("InvViewProj",(const D3DXMATRIX*)&pcCam);
        }

        // commit light colors and direction to the shader
        D3DXVECTOR4 apcVec[5];
        apcVec[0].x = g_avLightDirs[0].x;
        apcVec[0].y = g_avLightDirs[0].y;
        apcVec[0].z = g_avLightDirs[0].z;
        apcVec[0].w = 0.0f;
        apcVec[1].x = g_avLightDirs[0].x * -1.0f;
        apcVec[1].y = g_avLightDirs[0].y * -1.0f;
        apcVec[1].z = g_avLightDirs[0].z * -1.0f;
        apcVec[1].w = 0.0f;

        D3DXVec4Normalize(&apcVec[0],&apcVec[0]);
        D3DXVec4Normalize(&apcVec[1],&apcVec[1]);
        piEnd->SetVectorArray("afLightDir",apcVec,5);

        apcVec[0].x = ((g_avLightColors[0] >> 16) & 0xFF) / 255.0f;
        apcVec[0].y = ((g_avLightColors[0] >> 8) & 0xFF) / 255.0f;
        apcVec[0].z = ((g_avLightColors[0]) & 0xFF) / 255.0f;
        apcVec[0].w = 1.0f;

        if( g_sOptions.b3Lights)
        {
            apcVec[1].x = ((g_avLightColors[1] >> 16) & 0xFF) / 255.0f;
            apcVec[1].y = ((g_avLightColors[1] >> 8) & 0xFF) / 255.0f;
            apcVec[1].z = ((g_avLightColors[1]) & 0xFF) / 255.0f;
            apcVec[1].w = 0.0f;
        } else
        {
            apcVec[1].x = 0.0f;
            apcVec[1].y = 0.0f;
            apcVec[1].z = 0.0f;
            apcVec[1].w = 0.0f;
        }

        apcVec[0] *= g_fLightIntensity;
        apcVec[1] *= g_fLightIntensity;
        piEnd->SetVectorArray("afLightColor",apcVec,5);

        apcVec[0].x = vPos.x;
        apcVec[0].y = vPos.y;
        apcVec[0].z = vPos.z;
        piEnd->SetVector( "vCameraPos",&apcVec[0]);

        // setup the best technique
        if( g_sCaps.PixelShaderVersion < D3DPS_VERSION(2,0))
        {
            g_piDefaultEffect->SetTechnique( "DefaultFXSpecular_FF");
        } else
        if (g_sCaps.PixelShaderVersion < D3DPS_VERSION(3,0) || g_sOptions.bLowQuality)
        {
            if (g_sOptions.b3Lights)
                piEnd->SetTechnique("DefaultFXSpecular_PS20_D2");
            else piEnd->SetTechnique("DefaultFXSpecular_PS20_D1");
        }
        else
        {
            if (g_sOptions.b3Lights)
                piEnd->SetTechnique("DefaultFXSpecular_D2");
            else piEnd->SetTechnique("DefaultFXSpecular_D1");
        }

        // setup the default material
        UINT dwPasses = 0;
        piEnd->Begin(&dwPasses,0);
        piEnd->BeginPass(0);
    }
    D3DXVECTOR4 vVector = g_aclNormalColors[g_iCurrentColor];
    if (++g_iCurrentColor == 14)
    {
        g_iCurrentColor = 0;
    }
    if (! (!g_sOptions.bRenderMats && bAlpha  ))
    {
        for (unsigned int i = 0; i < piNode->mNumMeshes;++i)
        {
            const aiMesh* mesh = g_pcAsset->pcScene->mMeshes[piNode->mMeshes[i]];
            AssetHelper::MeshHelper* helper = g_pcAsset->apcMeshes[piNode->mMeshes[i]];

            // don't render the mesh if the render pass is incorrect
            if (g_sOptions.bRenderMats && (helper->piOpacityTexture || helper->fOpacity != 1.0f) && !mesh->HasBones())
            {
                if (!bAlpha)continue;
            }
            else if (bAlpha)continue;

            // Upload bone matrices. This maybe is the wrong place to do it, but for the heck of it I don't understand this code flow
            if( mesh->HasBones())
            {
                if( helper->piEffect)
                {
                    static float matrices[4*4*60];
                    float* tempmat = matrices;
                    const std::vector<aiMatrix4x4>& boneMats = g_pcAsset->mAnimator->GetBoneMatrices( piNode, i);
                    ai_assert( boneMats.size() == mesh->mNumBones);

                    for( unsigned int a = 0; a < mesh->mNumBones; a++)
                    {
                        const aiMatrix4x4& mat = boneMats[a];
                        *tempmat++ = mat.a1; *tempmat++ = mat.a2; *tempmat++ = mat.a3; *tempmat++ = mat.a4;
                        *tempmat++ = mat.b1; *tempmat++ = mat.b2; *tempmat++ = mat.b3; *tempmat++ = mat.b4;
                        *tempmat++ = mat.c1; *tempmat++ = mat.c2; *tempmat++ = mat.c3; *tempmat++ = mat.c4;
                        *tempmat++ = mat.d1; *tempmat++ = mat.d2; *tempmat++ = mat.d3; *tempmat++ = mat.d4;
                        //tempmat += 4;
                    }

                    if( g_sOptions.bRenderMats)
                    {
                        helper->piEffect->SetMatrixTransposeArray( "gBoneMatrix", (D3DXMATRIX*)matrices, 60);
                    } else
                    {
                        g_piDefaultEffect->SetMatrixTransposeArray( "gBoneMatrix", (D3DXMATRIX*)matrices, 60);
                        g_piDefaultEffect->CommitChanges();
                    }
                }
            } else
            {
                // upload identity matrices instead. Only the first is ever going to be used in meshes without bones
                if( !g_sOptions.bRenderMats)
                {
                    D3DXMATRIX identity( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
                    g_piDefaultEffect->SetMatrixTransposeArray( "gBoneMatrix", &identity, 1);
                    g_piDefaultEffect->CommitChanges();
                }
            }

            // now setup the material
            if (g_sOptions.bRenderMats)
            {
                CMaterialManager::Instance().SetupMaterial( helper, pcProj, aiMe, pcCam, vPos);
            }
            g_piDevice->SetVertexDeclaration( gDefaultVertexDecl);

            if (g_sOptions.bNoAlphaBlending) {
                // manually disable alpha-blending
                g_piDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
            }

            if (bAlpha)CMeshRenderer::Instance().DrawSorted(piNode->mMeshes[i],aiMe);
            else CMeshRenderer::Instance().DrawUnsorted(piNode->mMeshes[i]);

            // now end the material
            if (g_sOptions.bRenderMats)
            {
                CMaterialManager::Instance().EndMaterial( helper);
            }

            // render normal vectors?
            if (g_sOptions.bRenderNormals && helper->piVBNormals)
            {
                // this is very similar to the code in SetupMaterial()
                ID3DXEffect* piEnd = g_piNormalsEffect;

                piEnd->SetVector("OUTPUT_COLOR",&vVector);
                piEnd->SetMatrix("WorldViewProjection", (const D3DXMATRIX*)&pcProj);

                UINT dwPasses = 0;
                piEnd->Begin(&dwPasses,0);
                piEnd->BeginPass(0);

                g_piDevice->SetStreamSource(0, helper->piVBNormals, 0, sizeof(AssetHelper::LineVertex));
                g_piDevice->DrawPrimitive(D3DPT_LINELIST,0, g_pcAsset->pcScene->mMeshes[piNode->mMeshes[i]]->mNumVertices);

                piEnd->EndPass();
                piEnd->End();
            }
        }
        // end the default material
        if (!g_sOptions.bRenderMats)
        {
            g_piDefaultEffect->EndPass();
            g_piDefaultEffect->End();
        }
    }
    // render all child nodes
    for (unsigned int i = 0; i < piNode->mNumChildren;++i)
        RenderNode(piNode->mChildren[i],piMatrix,bAlpha );

    // need to reset the viewmode?
    if (bChangedVM)
        m_iViewMode = VIEWMODE_NODE;
    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::RenderPatternBG()
{
    if (!g_piPatternEffect)
    {
        // the pattern effect won't work on ps_2_0 cards
        if (g_sCaps.PixelShaderVersion >= D3DPS_VERSION(3,0))
        {
            // seems we have not yet compiled this shader.
            // and NOW is the best time to do that ...
            ID3DXBuffer* piBuffer = nullptr;
            if(FAILED( D3DXCreateEffect(g_piDevice,
                g_szCheckerBackgroundShader.c_str(),
                (UINT)g_szCheckerBackgroundShader.length(),
                nullptr,
                nullptr,
                D3DXSHADER_USE_LEGACY_D3DX9_31_DLL,
                nullptr,
                &g_piPatternEffect,&piBuffer)))
            {
                if( piBuffer)
                {
                    MessageBox(g_hDlg,(LPCSTR)piBuffer->GetBufferPointer(),"HLSL",MB_OK);
                    piBuffer->Release();
                }
                return 0;
            }
            if( piBuffer)
            {
                piBuffer->Release();
                piBuffer = nullptr;
            }
        }
        else
        {
            // clear the color buffer in magenta
            // (hopefully this is ugly enough that every ps_2_0 cards owner
            //  runs to the next shop to buy himself a new card ...)
            g_piDevice->Clear(0,nullptr,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                D3DCOLOR_ARGB(0xFF,0xFF,0,0xFF), 1.0f,0 );
            return 1;
        }
    }

    // clear the depth buffer only
    g_piDevice->Clear(0,nullptr,D3DCLEAR_ZBUFFER,
        D3DCOLOR_ARGB(0xFF,0xFF,0,0xFF), 1.0f,0 );

    // setup the colors to be used ...
    g_piPatternEffect->SetVector("COLOR_ONE",&m_avCheckerColors[0]);
    g_piPatternEffect->SetVector("COLOR_TWO",&m_avCheckerColors[1]);

    // setup the shader
    UINT dw;
    g_piPatternEffect->Begin(&dw,0);
    g_piPatternEffect->BeginPass(0);

    RECT sRect;
    GetWindowRect(GetDlgItem(g_hDlg,IDC_RT),&sRect);
    sRect.right -= sRect.left;
    sRect.bottom -= sRect.top;

    struct SVertex
    {
        float x,y,z,w;
    };
    // build the screen-filling rectangle
    SVertex as[4];
    as[1].x = 0.0f;
    as[1].y = 0.0f;
    as[1].z = 0.2f;
    as[3].x = (float)sRect.right;
    as[3].y = 0.0f;
    as[3].z = 0.2f;
    as[0].x = 0.0f;
    as[0].y = (float)sRect.bottom;
    as[0].z = 0.2f;
    as[2].x = (float)sRect.right;
    as[2].y = (float)sRect.bottom;
    as[2].z = 0.2f;

    as[0].w = 1.0f;
    as[1].w = 1.0f;
    as[2].w = 1.0f;
    as[3].w = 1.0f;

    as[0].x -= 0.5f;as[1].x -= 0.5f;as[2].x -= 0.5f;as[3].x -= 0.5f;
    as[0].y -= 0.5f;as[1].y -= 0.5f;as[2].y -= 0.5f;as[3].y -= 0.5f;

    // draw the rectangle
    DWORD dw2;g_piDevice->GetFVF(&dw2);
    g_piDevice->SetFVF(D3DFVF_XYZRHW);
    g_piDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP,2,
        &as,sizeof(SVertex));
    g_piDevice->SetFVF(dw2);

    // cleanup
    g_piPatternEffect->EndPass();
    g_piPatternEffect->End();
    return 1;
}
//-------------------------------------------------------------------------------
int CDisplay::RenderTextureView()
{
    if (!g_pcAsset || !g_pcAsset->pcScene)return 0;

    // handle input
    this->HandleInputTextureView();

    // render the background
    RenderPatternBG();

    // it might be that there is no texture ...
    if (!m_pcCurrentTexture->piTexture)
    {
        return 0;
    }


    RECT sRect;
    GetWindowRect(GetDlgItem(g_hDlg,IDC_RT),&sRect);
    sRect.right -= sRect.left;
    sRect.bottom -= sRect.top;

    // commit the texture to the shader
    g_piPassThroughEffect->SetTexture("TEXTURE_2D",*m_pcCurrentTexture->piTexture);

    if (aiTextureType_OPACITY == m_pcCurrentTexture->iType)
    {
        g_piPassThroughEffect->SetTechnique("PassThroughAlphaFromR");
    }
    else if ((aiTextureType_OPACITY | 0x40000000) == m_pcCurrentTexture->iType)
    {
        g_piPassThroughEffect->SetTechnique("PassThroughAlphaFromA");
    }
    else if( g_sCaps.PixelShaderVersion < D3DPS_VERSION(2,0))
        g_piPassThroughEffect->SetTechnique( "PassThrough_FF");
    else
        g_piPassThroughEffect->SetTechnique("PassThrough");

    UINT dw;
    g_piPassThroughEffect->Begin(&dw,0);
    g_piPassThroughEffect->BeginPass(0);

    if (aiTextureType_HEIGHT == m_pcCurrentTexture->iType ||
        aiTextureType_NORMALS == m_pcCurrentTexture->iType || g_sOptions.bNoAlphaBlending)
    {
        // manually disable alpha blending
        g_piDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
    }

    // build a rectangle which centers the texture
    // scaling is OK, but no stretching
    D3DSURFACE_DESC sDesc;
    if ( m_pcCurrentTexture->piTexture && *m_pcCurrentTexture->piTexture) { /* just a dirty fix */
        (*m_pcCurrentTexture->piTexture)->GetLevelDesc(0,&sDesc);

        struct SVertex{float x,y,z,w,u,v;};
        SVertex as[4];

        const float nx = (float)sRect.right;
        const float ny = (float)sRect.bottom;
        const float  x = (float)sDesc.Width;
        const float  y = (float)sDesc.Height;
        float f = std::min((nx-30) / x,(ny-30) / y) * (m_fTextureZoom/1000.0f);

        float fHalfX = (nx - (f * x)) / 2.0f;
        float fHalfY = (ny - (f * y)) / 2.0f;
        as[1].x = fHalfX + m_vTextureOffset.x;
        as[1].y = fHalfY + m_vTextureOffset.y;
        as[1].z = 0.2f;
        as[1].w = 1.0f;
        as[1].u = 0.0f;
        as[1].v = 0.0f;
        as[3].x = nx-fHalfX + m_vTextureOffset.x;
        as[3].y = fHalfY + m_vTextureOffset.y;
        as[3].z = 0.2f;
        as[3].w = 1.0f;
        as[3].u = 1.0f;
        as[3].v = 0.0f;
        as[0].x = fHalfX + m_vTextureOffset.x;
        as[0].y = ny-fHalfY + m_vTextureOffset.y;
        as[0].z = 0.2f;
        as[0].w = 1.0f;
        as[0].u = 0.0f;
        as[0].v = 1.0f;
        as[2].x = nx-fHalfX + m_vTextureOffset.x;
        as[2].y = ny-fHalfY + m_vTextureOffset.y;
        as[2].z = 0.2f;
        as[2].w = 1.0f;
        as[2].u = 1.0f;
        as[2].v = 1.0f;
        as[0].x -= 0.5f;as[1].x -= 0.5f;as[2].x -= 0.5f;as[3].x -= 0.5f;
        as[0].y -= 0.5f;as[1].y -= 0.5f;as[2].y -= 0.5f;as[3].y -= 0.5f;

        // draw the rectangle
        DWORD dw2;g_piDevice->GetFVF(&dw2);
        g_piDevice->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
        g_piDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP,2,
            &as,sizeof(SVertex));
        g_piDevice->SetFVF(dw2);
    }

    g_piPassThroughEffect->EndPass();
    g_piPassThroughEffect->End();

    // do we need to draw UV coordinates?
    return 1;
}

}

