// Rf3DLoader.cpp : Implementation of CRf3DLoader

#include "stdafx.h"
#include "Rn3DLoader.h"

// Remember the 3DLoader node and check if it proper object.
// For this function set it is important only if the object is an Encapsulator too.
STDMETHODIMP CRf3DLoader::Assign(IUnknown *pNodeUnkn)
{
    CComQIPtr<IRsNode> spNode = pNodeUnkn;
    if (spNode == NULL)
        return E_INVALIDARG;

    spNode->QueryInterface(&m_spSpaceEnc);
    if (m_spSpaceEnc == NULL)
        return E_INVALIDARG;

    m_sp3DLoaderNode = spNode;

    // Find other necessary functions sets that we need
    CComPtr<IRpKernel> spKernelPackage;
    CRp3DLoader::GetKernelPackage(&spKernelPackage);
    if (spKernelPackage == NULL)
        return E_UNEXPECTED;

    CComPtr<IRsUnknownPackage> spPackage;
    spKernelPackage->FindPackage(DEF_GUIDNAME(CRpFileIOPck), &spPackage);
    if (spPackage == NULL)
        return E_FAIL;
    spPackage->QueryInterface(&m_spRpFileIOPck);
    if (m_spRpFileIOPck == NULL)
        return E_FAIL;

    spKernelPackage->CreateEncapsulatorFnSet(&m_spEncapsulatorSet);
    if (m_spEncapsulatorSet == NULL)
        return E_FAIL;

    return S_OK;
}

// Call to delete all the thumbnails.
// Due to fact all the thumbnails are stored as sub-objects of the IRn3DLoader
// node, it is enough to remove only the sub-objects
STDMETHODIMP CRf3DLoader::RemoveAllThumbnails()
{
    if (m_spSpaceEnc == NULL)   // Should never happen, but one don't know...
        return E_UNEXPECTED;

    // Get all the sub-objects
    CComPtr<IRsEnumNodes> spEnum;
    m_spSpaceEnc->EnumSubObjects(&spEnum);
    if (spEnum == NULL)
        return S_FALSE; // There are probably none objects

    spEnum->Reset();    // Reset the enumerator

    // Go through all the sub-objects, detect the thumbnail objects and remove them one by one
    CComPtr<IRsNode> spSubNode;
    while (S_OK == spEnum->Next(1, &spSubNode, NULL))
    {
        CComQIPtr<IRsBaseNode> spBaseSubNode = spSubNode;
        if (spBaseSubNode == NULL)
            return E_UNEXPECTED;

        // We added CON_ID_3DLOADER_STR_MARKTIME connector for each thumbnail object
        // so we can use it for identification
        if (spBaseSubNode->FindConById(CON_ID_3DLOADER_STR_MARKTIME, NULL) == S_OK)
            spSubNode->Destroy();

        spSubNode.Release();
    }

    return S_OK;
}

// The ThumbnailsCheckStart and ThumbnailsCheckFinish methods are necessary only
// to remove thumbnails of already deleted files.
// When the synchronization enumeration starts, we mark all updated or created thumbnail
// and in ThumbnailsCheckFinish method we delete all not updated thumbnails
STDMETHODIMP CRf3DLoader::ThumbnailsCheckStart()
{
    m_dwUpdateMarkCounter++;

    return S_OK;
}

// Method add new thumbnail object as sub-object of the 3DLoader node
// and then update it so the thumbnail corresponds real file thumbnail
// bitmap.
// Each sub-node have to have unique name. We use the fact that
// filename is unique in a directory too, so we use it as name of the
// newly created node.
STDMETHODIMP CRf3DLoader::AddThumbnailNode(IUnknown *pFileInfoUnkn)
{
    HRESULT hr;

    CComQIPtr<IRsFileInfo> spFileInfo = pFileInfoUnkn;
    if (spFileInfo == NULL)
        return E_INVALIDARG;

    CComBSTR bstrFileName;
    spFileInfo->get_Name(&bstrFileName);

    CComPtr<IRsNode> spThumbnailNode;
    if (FAILED(hr = AddThumbnailNodeObject(bstrFileName, &spThumbnailNode)))
        return hr;

    return UpdateThumbnailNode(spThumbnailNode, pFileInfoUnkn);
}

// As first this method check if the update is really necessary. We do it
// by the last file write time as it should be enough.
// The method returns S_FALSE if the objects is not necessary to update
STDMETHODIMP CRf3DLoader::UpdateThumbnailNode(IUnknown *pThumbnailNodeUnkn, IUnknown *pFileInfoUnkn)
{
    CComQIPtr<IRsNode> spThumbnailNode = pThumbnailNodeUnkn;

    if ((m_spRpFileIOPck == NULL) || (m_sp3DLoaderNode == NULL))
        return E_UNEXPECTED;

    CComQIPtr<IRsFileInfo> spFileInfo = pFileInfoUnkn;
    if ((spFileInfo == NULL) || (spThumbnailNode == NULL))
        return E_INVALIDARG;

    // First of all mark that we checked the object. Otherwise it would be deleted
    // when the ThumbnailsCheckFinish will be called
    // Mark the update into CON_ID_3DLOADER_STR_MARKTIME connector
    spThumbnailNode->SetValue(CON_ID_3DLOADER_STR_MARKTIME, CComVariant(m_dwUpdateMarkCounter));

    // Get the last write file time
    FILETIME fileTime;
    spFileInfo->get_LastWriteTime(&fileTime);
    ULARGE_INTEGER ulFileTime;
    ulFileTime.LowPart = fileTime.dwLowDateTime;
    ulFileTime.HighPart = fileTime.dwHighDateTime;

    // Read last write time of the thumbnail object
    // We store it into CON_ID_3DLOADER_STR_FILETIME connector of the object
    CComVariant vFileTimeObj;
    spThumbnailNode->GetValue(CON_ID_3DLOADER_STR_FILETIME, &vFileTimeObj);

    // If both the times are equal, we don't need update the thumbnail object
    if (SUCCEEDED(vFileTimeObj.ChangeType(VT_UI8)) && (ulFileTime.QuadPart == vFileTimeObj.ullVal))
        return S_FALSE;
    

    // The update is necessary ...
    // In such case read the thumbnail bitmap from the file and set it as texture
    // for the 3D thumbnail object

    // Save the file time
    CComVariant vNewFileTime;
    vNewFileTime.ChangeType(VT_UI8);
    vNewFileTime.ullVal = ulFileTime.QuadPart;
    spThumbnailNode->SetValue(CON_ID_3DLOADER_STR_FILETIME, vNewFileTime);

    // If both the times are equal, we don't need update the thumbnail object
    if (SUCCEEDED(vFileTimeObj.ChangeType(VT_UI8)) && (ulFileTime.QuadPart == vFileTimeObj.ullVal))
        return S_FALSE;

    // Get full path of the file
    CComBSTR bstrFileName;
    spFileInfo->get_Name(&bstrFileName);

    CComBSTR bstrFullPath;
    GetFullFilePath(bstrFileName, &bstrFullPath);

    // Create file handle
    CComPtr<IRsFileHandle> spFileHandle;
    m_spRpFileIOPck->CreateRsFileHandle(bstrFullPath, RSFHNDO_READ, &spFileHandle);
    if (spFileHandle == NULL)
        return E_FAIL;

    // Find chunk with the thumbnail and read it. May happen that the thumbnail
    // is not assigned. In such case we put empty bitmap for the thumbnail 3D object
    // If we want to, we could assign a default bitmap for such objects but this is only
    // sample plugin so it is enough.
    CComPtr<IRdBitmap> spThumbnailBitmap;

    RtDWORD nChunkIndex;
    if (SUCCEEDED(spFileHandle->FindChunk(DEF_GUIDNAME(IRdChunkThumbnail), -1, &nChunkIndex)))
    {
        CComPtr<IRsFileChunk> spChunk;
        spFileHandle->GetChunk(nChunkIndex, FHND_GET_CHUNK_FLG_NONE, &spChunk);
        CComQIPtr<IRdChunkThumbnail> spThumbnailChunk = spChunk;
        if (spThumbnailChunk != NULL)
            spThumbnailChunk->GetBitmap(&spThumbnailBitmap);
    }

    // Set the bitmap
    // As the thumbnail 3D object was constructed manually in 3D editor,
    // we don't know the bitmap connector ID. The only what we know is name
    // of the connector. So we try to find it and get the Id.
    CComQIPtr<IRsBaseNode> spThumbnailBase = spThumbnailNode;
    CComPtr<IRsConnector> spCon;
    spThumbnailBase->FindConByName(L"DiffuseTexture", &spCon);
    if (spCon == NULL) {
        ATLASSERT(!"The thumbnail object doesn't have connector for bitmap!");
        return E_FAIL; // In this case we need open ThumbnailPad3D.RsObj and check it
    }

    CONID conId;
    spCon->GetConId(&conId);
    return spThumbnailNode->SetValue(conId, CComVariant(spThumbnailBitmap));
}

// Remove all not updated thumbnails 3D object
// return S_OK if an object was deleted or S_FALSE is all is without a change
STDMETHODIMP CRf3DLoader::ThumbnailsCheckFinish()
{
    if (m_spSpaceEnc == NULL)   // Should never happen, but one don't know...
        return E_UNEXPECTED;

    // Get all the sub-objects
    CComPtr<IRsEnumNodes> spEnum;
    m_spSpaceEnc->EnumSubObjects(&spEnum);
    if (spEnum == NULL)
        return S_FALSE; // There are probably none objects

    spEnum->Reset();    // Reset the enumerator

    // Go through all the sub-objects and check them
    BOOL bSomethingDeleted = FALSE;
    CComPtr<IRsNode> spSubNode;
    while (S_OK == spEnum->Next(1, &spSubNode, NULL))
    {
        CComQIPtr<IRsBaseNode> spBaseSubNode = spSubNode;
        if (spBaseSubNode == NULL)
            return E_UNEXPECTED;

        if (spBaseSubNode->FindConById(CON_ID_3DLOADER_STR_MARKTIME, NULL) != S_OK) {
            spSubNode.Release();
            continue;   // Don't touch the object as it is not thumbnail object
        }

        CComVariant vMarkTime;
        spSubNode->GetValue(CON_ID_3DLOADER_STR_MARKTIME, &vMarkTime);
        if (FAILED(vMarkTime.ChangeType(VT_UI4)) || (vMarkTime.uintVal != m_dwUpdateMarkCounter)) {
            spSubNode->Destroy();
            bSomethingDeleted = TRUE;
        }

        spSubNode.Release();
    }

    return bSomethingDeleted ? S_OK : S_FALSE;
}

//////////////////////////////////////////////////////////////////////////
// Functions bellow are used to construct and maintain the geometry

// Function loads the 3D thumbnail object from resource and add it into 3DLoader space
HRESULT CRf3DLoader::AddThumbnailNodeObject(BSTR bstrName, IRsNode **ppThumbnailNodeObject)
{
    HRESULT hr;

    // Get file IO helper and load the 3D object from resource
    if ((m_spRpFileIOPck == NULL) || (m_spSpaceEnc == NULL))
        return E_UNEXPECTED;

    CComPtr<IRfFileIOHelper> spFileIOhelper;
    m_spRpFileIOPck->GetFileIOHelper(&spFileIOhelper);
    if (spFileIOhelper == NULL)
        return E_FAIL;

    CComPtr<IRsNode> spThumbnailNode;
    spFileIOhelper->LoadNodeFromResource(_AtlBaseModule.GetResourceInstance(), IDR_BIN_THUMBNAIL_PAD, &spThumbnailNode);
    if (spThumbnailNode == NULL)
        return E_FAIL;

    // Rename the object
    if (FAILED(hr = spThumbnailNode->put_Name(bstrName))) {
        spThumbnailNode->Destroy(); // Release the object
        return hr;
    }

    // ConnectTo the node with transform node to be its position properly set
    if (FAILED(hr = ConnectWithTransformNode(spThumbnailNode))) {
        spThumbnailNode->Destroy(); // Release the object
        return hr;
    }

    if (FAILED(hr = m_spSpaceEnc->AddSubObject(spThumbnailNode))) {
        spThumbnailNode->Destroy(); // Release the object
        return hr;
    }

    // When the object was successfully added, add 2 more connectors that we will use
    // to note an info about the object.
    // Into CON_ID_3DLOADER_STR_FILETIME we will store the last write time of the file that
    // the node represents
    // CON_ID_3DLOADER_STR_MARKTIME connector we will use to mark last update time so we can
    // decide whether the object should be deleted or not.
    // It is not necessary to see the connectors in LinkEditor, thus set CONFLG_HIDDEN flag
    // for them too.

    RtConDesc sFileTimeDesc = {&CON_ID_3DLOADER_STR_FILETIME, L"FileTime", L"Stored last file write time", VT_UI8, NULL, CONFLG_IN|CONFLG_HIDDEN};
    RtConDesc sMarkTimeDesc = {&CON_ID_3DLOADER_STR_MARKTIME, L"MarkTime", L"Stored last update time", VT_UI8, NULL, CONFLG_IN|CONFLG_HIDDEN};

    CComQIPtr<IRsBaseNode> spThumbnailBase = spThumbnailNode;
    if (spThumbnailBase == NULL)
        return E_UNEXPECTED;

    spThumbnailBase->AddNewConnector(&sFileTimeDesc, NULL); // Nothing tragic happen if the connector
    spThumbnailBase->AddNewConnector(&sMarkTimeDesc, NULL); // fail to add, it will not just work ...

    // Set the file path for the script that we will run when
    // when you click on a 3D Thumbnail object
    // First we will find the script node. We know it is called "ScriptCtrl"
    CComPtr<IRsNode> spClickCtrl;
    CComQIPtr<IRnEncapsulator> spThumbnailEnc = spThumbnailNode;
    if (spThumbnailEnc != NULL)
        spThumbnailEnc->FindNodeByName(L"ClickCtrl", &spClickCtrl);

    // If we have the script object, then we have to find the FilePath connector
    CComPtr<IRsConnector> spPathCon;
    CComQIPtr<IRsBaseNode> spClickCtrlBase = spClickCtrl;
    if (spClickCtrlBase != NULL)
        spClickCtrlBase->FindConByName(L"FilePath", &spPathCon);

    // If the connector is found, store the file path
    if (spPathCon != NULL)
    {
        CONID conId;
        spPathCon->GetConId(&conId);

        CComBSTR bstrFullPath;
        GetFullFilePath(bstrName, &bstrFullPath);
        spClickCtrl->SetValue(conId, CComVariant(bstrFullPath));
    }

    if (ppThumbnailNodeObject != NULL)
        *ppThumbnailNodeObject = spThumbnailNode.Detach();

    return S_OK;
};

// Function add the most bottom object. Its purpose is to enable select
// the loader in 3D space and drag it.
// We load it from resource.
STDMETHODIMP CRf3DLoader::Add3DLoaderBase()
{
    HRESULT hr;

    // Get file IO helper and load the 3D object from resource
    if ((m_spRpFileIOPck == NULL) || (m_spSpaceEnc == NULL))
        return E_UNEXPECTED;

    // Check if such object already exist
    if (m_spSpaceEnc->FindNodeByName(RF3DLOADER_3DLOADER_PAD_NAME, NULL) == S_OK)
        return S_OK;

    CComPtr<IRfFileIOHelper> spFileIOhelper;
    m_spRpFileIOPck->GetFileIOHelper(&spFileIOhelper);
    if (spFileIOhelper == NULL)
        return E_FAIL;

    CComPtr<IRsNode> sp3DLoaderBasePadNode;
    spFileIOhelper->LoadNodeFromResource(_AtlBaseModule.GetResourceInstance(), IDR_BIN_3DLOADER_BASE, &sp3DLoaderBasePadNode);
    if (sp3DLoaderBasePadNode == NULL)
        return E_FAIL;

    sp3DLoaderBasePadNode->put_Name(RF3DLOADER_3DLOADER_PAD_NAME);

    // Create identical matrix Data Object
    CComPtr<IRsDataObject> spMatrixDO;
    CComQIPtr<IRdMatrixf> spMatrix;
    CRp3DLoader::CreateDataObj(DEF_GUIDNAME_PTR(IRdMatrixf), &spMatrixDO);
    if ((spMatrix = spMatrixDO) == NULL)
        return E_FAIL;

    // Set transform matrix for the thumbnail base pad
    sp3DLoaderBasePadNode->SetValue(CON_OBJ_TRANSFORM_INP_MATRIX, CComVariant(spMatrix));

    // ConnectTo the node with transform node to be its position properly set
    if (FAILED(hr = ConnectWithTransformNode(sp3DLoaderBasePadNode))) {
        sp3DLoaderBasePadNode->Destroy(); // Release the object
        return hr;
    }

    if (FAILED(hr = m_spSpaceEnc->AddSubObject(sp3DLoaderBasePadNode))) {
        sp3DLoaderBasePadNode->Destroy(); // Release the object
        return hr;
    }

    return S_OK;
}


// Function check if a Transform node is present in 3DLoader space. If isn't
// it will be created here. Finally the spThumbnailNode will be connected
// with the transform node object.
// The transform object allows you to create hierarchy in 3D space. In another
// words, if you move the 3DLoader object in 3D space, all its sub-objects will
// be moved too
HRESULT CRf3DLoader::ConnectWithTransformNode(IRsNode *pThumbnailNode)
{
    HRESULT hr;

    // Find the Transform node first
    // Rosetta supports registered connectors that has except value and type
    // of the value a purpose too. In our case we are trying to find
    // CON_TRANSFORM_MATRIX connector. If you find such connector, then
    // you know the object can be located in 3D space. Furthermore, all
    // registered connectors are unique so it can be only one per object

    CComQIPtr<IRsBaseNode> spDestBase = m_sp3DLoaderNode;
    if (spDestBase == NULL)
        return E_UNEXPECTED;
    CComPtr<IRsConnector> spOutMCon;
    spDestBase->FindConById(CON_TRANSFORM_MATRIX, &spOutMCon);

    // CON_TRANSFORM_MATRIX is exported output connector (the blue one in LinkEditor)
    // We will find source of the connector. It should be the Transform node
    CComPtr<IRsConnector> spSrcCon;
    if (spOutMCon != NULL)
        spOutMCon->GetInputConnection(0, &spSrcCon);

    CComQIPtr<IRsBaseNode> spSrcBase;
    if (spSrcCon != NULL)
    {
        CComPtr<IRsNode> spSrcNode;
        spSrcCon->GetNode(&spSrcNode);
        spSrcBase = spSrcNode;
    }

    if (spSrcBase == NULL)
    {
        CComPtr<IRsNode> spTransformNode;
        if (FAILED(hr = AddTransformNode(&spTransformNode)))
            return hr;

        spTransformNode->QueryInterface(&spSrcBase);
    }

    if (pThumbnailNode == NULL) // You can call this function even with input parameter NULL
        return hr;            // In such case only the transform node will be added

    // Finally we connect the transform node world matrix connector with
    // 3D thumbnail object owner's matrix connector
    return spSrcBase->ConnectToConnector(CON_TRANSFORM_MATRIX, CComQIPtr<IRsBaseNode>(pThumbnailNode), CON_TRANSFORM_PARENT_MATRIX);
}

// This is only simple wrapper function for export connector to owner object
HRESULT CRf3DLoader::ExportConnector(IRsNode *pSrcNode, const CONID *pConId, RtBool bSetValue)
{
    HRESULT hr;

    CComQIPtr<IRsBaseNode> spSrcBase = pSrcNode;
    if (spSrcBase == NULL)
        return E_FAIL;

    CComPtr<IRsConnector> spCon;
    spSrcBase->FindConById(*pConId, &spCon);

    if (m_spEncapsulatorSet == NULL)
        return E_UNEXPECTED;

    m_spEncapsulatorSet->Attach(m_sp3DLoaderNode);
    hr = m_spEncapsulatorSet->ExportConnector(spCon, true, bSetValue, false, NULL);
    m_spEncapsulatorSet->Attach(NULL);

    return hr;
}

// Function add transform node into 3DLoader node and export necessary connectors
HRESULT CRf3DLoader::AddTransformNode(IRsNode **ppTransformNode)
{
    HRESULT hr;

    if (m_spSpaceEnc == NULL)
        return E_UNEXPECTED;

    CComPtr<IRsNode> spTransformNode;
    if (FAILED(hr = CRp3DLoader::CreateNode(&DEF_GUIDNAME(IRnTransform), &spTransformNode)))
        return hr;

    if (FAILED(hr = m_spSpaceEnc->AddSubObject(spTransformNode))) {
        spTransformNode->Destroy();
        return hr;
    }

    // Now check the Transform node's connectors. If they are not exported, then
    // export them. Check the connections too.
    if (FAILED(hr = ExportConnector(spTransformNode, &CON_TRANSFORM_MATRIX, TRUE)))
        return hr;

    if (FAILED(hr = ExportConnector(spTransformNode, &CON_OBJ_TRANSFORM_MATRIX, TRUE)))
        return hr;

    if (FAILED(hr = ExportConnector(spTransformNode, &CON_TRANSFORM_PARENT_MATRIX, TRUE)))
        return hr;

    if (FAILED(hr = ExportConnector(spTransformNode, &CON_OBJ_TRANSFORM_INP_MATRIX, TRUE)))
        return hr;

    if (ppTransformNode != NULL)
        *ppTransformNode = spTransformNode.Detach();

    return S_OK;
}

// Go through all the thumbnails object and arrange them along Z axis
// Due to fact the we want animate the arranging, we only compute the
// Z values and store them into the m_afZPosition array.
STDMETHODIMP CRf3DLoader::ArrangeThumbnails()
{
    if (m_spSpaceEnc == NULL)   // Should never happen, but one don't know...
        return E_UNEXPECTED;

    // Get all the sub-objects
    CComPtr<IRsEnumNodes> spEnum;
    m_spSpaceEnc->EnumSubObjects(&spEnum);
    if (spEnum == NULL)
        return S_FALSE; // There are probably none objects

    spEnum->Reset();    // Reset the enumerator

    m_afZPosition.RemoveAll();  // Reset temporary array for new Z positions

    // Go through all the sub-objects and remove them one by one
    CComPtr<IRsNode> spSubNode;
    RtFloat fZPos = RF3DLOADER_BOTTOM_Z_POS;
    while (S_OK == spEnum->Next(1, &spSubNode, NULL))
    {
        CComQIPtr<IRsBaseNode> spSubBaseNode = spSubNode;
        if (spSubBaseNode == NULL)
            return E_UNEXPECTED;

        // Count only thumbnails objects. If the object contain attribute MarkTime
        // then we know it is our object
        if (spSubBaseNode->FindConById(CON_ID_3DLOADER_STR_MARKTIME, NULL) == S_OK)
        {
            // Add new position to temporary array
            m_afZPosition.Add(fZPos);
            // Compute new position
            fZPos += RF3DLOADER_Z_DISTANCE;
        }

        spSubNode.Release();
    }

    // Set number animation steps
    m_dwAnimationFrame = RF3DLOADER_NUM_ANIM_STEPS;

    return S_OK;
}

// Go through all the thumbnails object and animate them by the values
// we stored into m_afZPosition array.
// The function returns S_OK if the animation step was done and next steps are necessary
// Returns S_FALSE if the animation already finished. This information is then
// used by caller to know when the animation is finished
STDMETHODIMP CRf3DLoader::AnimateThumbnails()
{
    if (m_spSpaceEnc == NULL)   // Should never happen, but one don't know...
        return E_UNEXPECTED;

    if (m_dwAnimationFrame == 0)
        return S_FALSE; // Animation finished

    RtFloat fAnimVal = RtFloat(RF3DLOADER_NUM_ANIM_STEPS - m_dwAnimationFrame)/RtFloat(RF3DLOADER_NUM_ANIM_STEPS);

    // Get all the sub-objects
    CComPtr<IRsEnumNodes> spEnum;
    m_spSpaceEnc->EnumSubObjects(&spEnum);
    if (spEnum == NULL)
        return S_FALSE; // There are probably none objects

    spEnum->Reset();    // Reset the enumerator

    // Go through all the sub-objects and remove them one by one
    CComPtr<IRsNode> spSubNode;
    DWORD dwIndex = 0;
    while (S_OK == spEnum->Next(1, &spSubNode, NULL))
    {
        CComQIPtr<IRsBaseNode> spSubBaseNode = spSubNode;
        if (spSubBaseNode == NULL)
            return E_UNEXPECTED;

        // Count only thumbnails objects. If the object contain attribute MarkTime
        // then we know it is our object
        if (spSubBaseNode->FindConById(CON_ID_3DLOADER_STR_MARKTIME, NULL) != S_OK) {
            spSubNode.Release();
            continue;
        }

        CComPtr<IRsDataObject> spMatrixDO;
        CComQIPtr<IRdMatrixf> spMatrix;

        // Get the current transform matrix of the thumbnail 3D object
        CComVariant vCurrMatrix;
        spSubNode->GetValue(CON_OBJ_TRANSFORM_INP_MATRIX, &vCurrMatrix);

        // If the matrix doesn't exist, create one
        if ((vCurrMatrix.vt != VT_UNKNOWN) || ((spMatrix = vCurrMatrix.punkVal) == NULL))
        {
            // Create identical matrix Data Object
            CComPtr<IRsDataObject> spMatrixDO;
            CRp3DLoader::CreateDataObj(DEF_GUIDNAME_PTR(IRdMatrixf), &spMatrixDO);
            if ((spMatrix = spMatrixDO) == NULL)
                return E_FAIL;
        }

        // Animate only if the thumbnail is not on proper place
        RtFloat fCurrTransZ = 0.0f;
        spMatrix->GetTransZ(&fCurrTransZ);
        if (fCurrTransZ != m_afZPosition[dwIndex])
        {
            // Set the matrix and proper Z value
            spMatrix->SetScaleZ(RF3DLOADER_THUMB_Z_SCALE);
            spMatrix->SetTransZ(fCurrTransZ + (m_afZPosition[dwIndex] - fCurrTransZ)*fAnimVal);

            // Set the transform matrix to the thumbnail 3D object
            spSubNode->SetValue(CON_OBJ_TRANSFORM_INP_MATRIX, CComVariant(spMatrix));
        }

        spSubNode.Release();
        dwIndex++;
    }

    m_dwAnimationFrame--;

    return (m_dwAnimationFrame > 0) ? S_OK : S_FALSE;
}

// Assemble full file path by the file name and 3DLoader focus directory
HRESULT CRf3DLoader::GetFullFilePath(BSTR bstrFileName, BSTR *pbstrPath)
{
    if (m_sp3DLoaderNode == NULL)
        return E_UNEXPECTED;

    // Get the focused directory path
    CComVariant vDirPath;
    m_sp3DLoaderNode->GetValue(CON_3DLOADER_NODE_DIR_PATH, &vDirPath);
    CComBSTR bstrDirPath;
    if (FAILED(vDirPath.ChangeType(VT_BSTR)))
        return E_FAIL;

    // Add the filename
    CComBSTR bstrFullPath = vDirPath.bstrVal;
    bstrFullPath.Append(L"\\");
    bstrFullPath.Append(bstrFileName);

    if (pbstrPath != NULL)
        *pbstrPath = bstrFullPath.Detach();

    return S_OK;
}
