//-----------------------------------------------------------------------------
// File: D3DFile.cpp
//
// Desc: Support code for loading DirectX .X files.
//
// Copyright (c) 1997-1999 Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------

#define STRICT
#define D3D_OVERLOADS

#include "StdAfx.h"

#include "guids.h"
#include "D3DUtil.h"
#include "D3DMath.h"
#include "D3DTextr.h"
#include "dxfile.h"
#include "rmxfguid.h"
#include "rmxftmpl.h"
#include "D3DFile.h"
#include "sealife.h"


//-----------------------------------------------------------------------------
// Name: GetFace
// Desc: Get the nth face
//-----------------------------------------------------------------------------
DWORD* GetFace(DWORD* pFaceData, DWORD dwFace)
{
    for(DWORD i=0; i<dwFace; i++)
        pFaceData += (*pFaceData) + 1;

    return pFaceData;
}




//-----------------------------------------------------------------------------
// Name: GetNumIndices
// Desc: Get number of indices from face data
//-----------------------------------------------------------------------------
DWORD GetNumIndices(DWORD* pFaceData, DWORD dwNumFaces)
{
    DWORD dwNumIndices = 0;
    while(dwNumFaces-- > 0)
    {
        dwNumIndices += (*pFaceData-2)*3;
        pFaceData += *pFaceData + 1;
    }

    return dwNumIndices;
}




//-----------------------------------------------------------------------------
// Name: CD3DFileBaseObject()
// Desc: 
//-----------------------------------------------------------------------------
CD3DFileObject::CD3DFileObject(TCHAR* strName)
{
    m_pNext        = NULL;
    m_pChild       = NULL;
    m_strName[0]   = 0;
    m_bHasMeshData = FALSE;

    if (strName)
        StrCpy(m_strName, strName);

    // Set a default matrix
    D3DUtil_SetIdentityMatrix(m_mat);

    // Set a default material
    D3DUtil_InitMaterial(m_Material[0].m_mtrl, 1.0f, 1.0f, 1.0f);
    ZeroMemory(m_Material, sizeof(m_Material));
    m_dwNumMaterials = 0;
    m_bHasAlpha      = FALSE;

    // Clear out vertex data
    m_dwNumVertices = 0L;
    m_pVertices     = NULL;
    m_dwNumIndices  = 0L;
    m_pIndices      = NULL;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
CD3DFileObject::~CD3DFileObject()
{
    SAFE_DELETE(m_pChild);
    SAFE_DELETE(m_pNext);

    for(DWORD i=0; i<m_dwNumMaterials; i++)
        D3DTextr_DestroyTexture(m_Material[i].m_strTexture);

    SAFE_DELETE(m_pVertices);
    SAFE_DELETE(m_pIndices);
}




//-----------------------------------------------------------------------------
// Name: SetMeshGeometry()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT CD3DFileObject::SetMeshGeometry(D3DVECTOR* pVertexData,
                                     DWORD dwNumVertices, DWORD* pFaceData,
                                     DWORD dwNumFaces)
{
    // Set up vertices
    m_dwNumVertices = dwNumVertices;
    m_pVertices     = new D3DVERTEX[m_dwNumVertices];
    if (NULL == m_pVertices)
        return E_FAIL;

    for(DWORD i=0; i< m_dwNumVertices; i++)
    {
        ZeroMemory(&m_pVertices[i], sizeof(D3DVERTEX));
        m_pVertices[i].x = pVertexData[i].x;
        m_pVertices[i].y = pVertexData[i].y;
        m_pVertices[i].z = pVertexData[i].z;
    }

    // Count the number of indices (converting n-sided faces to triangles)
    m_dwNumIndices = GetNumIndices(pFaceData, dwNumFaces);

    // Allocate memory for the indices, you must call AddFace() to set the vertices
    m_pIndices = new WORD[m_dwNumIndices];

    if (NULL == m_pIndices)
        return E_FAIL;

    m_bHasMeshData  = TRUE;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DFileObject::AddChild(CD3DFileObject* pChild)
{
    if (m_pChild)
        m_pChild->AddNext(pChild);
    else
        m_pChild = pChild;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DFileObject::AddNext(CD3DFileObject* pNext)
{
    if (m_pNext)
        m_pNext->AddNext(pNext);
    else
        m_pNext = pNext;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DFileObject::Render(LPDIRECT3DDEVICE7 pd3dDevice, BOOL bAlpha)
{
    if (m_bHasMeshData)
    {
        // Render the mesh
        WORD* pIndices = m_pIndices;
        for(DWORD i=0; i <= m_dwNumMaterials; i++)
        {
            // Skip materials with no references
            if (0L == m_Material[i].m_dwNumIndices)
                continue;

            // Render opaque and transparent meshes during separate passes
            if (bAlpha == m_bHasAlpha)
            {
                TCHAR* strTexture   = m_Material[i].m_strTexture;
                DWORD  dwNumIndices = m_Material[i].m_dwNumIndices;

                if (strTexture[0])
                    pd3dDevice->SetTexture(0, D3DTextr_GetSurface(strTexture));
                pd3dDevice->SetMaterial(&m_Material[i].m_mtrl);
                pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, D3DFVF_VERTEX,
                                                  m_pVertices, m_dwNumVertices,
                                                  pIndices, dwNumIndices, NULL);
            }

            pIndices += m_Material[i].m_dwNumIndices;
        }
    }
    else
    {
        if (m_pChild)
        {
            // Save the old matrix sate
            D3DMATRIX matWorldOld, matWorldNew;
            pd3dDevice->GetTransform(D3DTRANSFORMSTATE_WORLD, &matWorldOld);

            // Concat the frame matrix with the current world matrix
            matWorldNew = m_mat * matWorldOld;
            pd3dDevice->SetTransform(D3DTRANSFORMSTATE_WORLD, &matWorldNew);

            // Render the child nodes
            m_pChild->Render(pd3dDevice, bAlpha);

            // Restore the old matrix state
            pd3dDevice->SetTransform(D3DTRANSFORMSTATE_WORLD, &matWorldOld);
        }
    }

    // Render the remaining sibling nodes
    if (m_pNext)
        m_pNext->Render(pd3dDevice, bAlpha);
}




//-----------------------------------------------------------------------------
// Name: SetMaterialData()
// Desc: Sets the material structure for the mesh
//-----------------------------------------------------------------------------
VOID CD3DFileObject::SetMaterialData(DWORD dwMaterial, D3DMATERIAL7* pmtrl,
                                      TCHAR* strName)
{
    if (dwMaterial < MAX_MATERIAL)
    {
        m_Material[dwMaterial].m_mtrl = *pmtrl;
        StrCpyN(m_Material[dwMaterial].m_strTexture, strName, MAX_TEXTURE_NAME);

        if (pmtrl->diffuse.a < 1.0f)
            m_bHasAlpha = TRUE;
    }
}




//-----------------------------------------------------------------------------
// Name: AddFace()
// Desc: Adds one or more faces to a material slot in a Mesh. Note: this must
//       be called in order (material 0 first, then 1, ...)
//-----------------------------------------------------------------------------
VOID CD3DFileObject::AddFace(DWORD dwMaterial, DWORD* pFaceData,
                              DWORD dwNumFaces)
{
    // Make sure dwMaterial is in range
    if (dwMaterial >= MAX_MATERIAL)
        return;

    // Update the material count
    if (m_dwNumMaterials < dwMaterial+1)
        m_dwNumMaterials = dwMaterial+1;

    // add indices to the end
    WORD* pIndices = m_pIndices;
    for(DWORD i=0; i<=dwMaterial; i++)
        pIndices += m_Material[i].m_dwNumIndices;

    // Assign the indices (build a triangle fan for high-order polygons)
    while(dwNumFaces--)
    {
        DWORD dwNumVerticesPerFace = *pFaceData++;

        for(DWORD i=2; i<dwNumVerticesPerFace; i++)
        {
            m_Material[dwMaterial].m_dwNumIndices += 3;
            *pIndices++ = (WORD)pFaceData[0];
            *pIndices++ = (WORD)pFaceData[i-1];
            *pIndices++ = (WORD)pFaceData[i];
        }

        pFaceData += dwNumVerticesPerFace;
    }
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
HRESULT CD3DFileObject::GetMeshGeometry(D3DVERTEX** ppVertices, DWORD* pdwNumVertices,
                                         WORD** ppIndices, DWORD* pdwNumIndices)
{
    if (ppVertices)     *ppVertices     = m_pVertices;
    if (pdwNumVertices) *pdwNumVertices = m_dwNumVertices;
    if (ppIndices)      *ppIndices      = m_pIndices;
    if (pdwNumIndices)  *pdwNumIndices  = m_dwNumIndices;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
HRESULT CD3DFileObject::ComputeNormals()
{
    D3DVECTOR* pNormals = new D3DVECTOR[m_dwNumVertices];
    ZeroMemory(pNormals, sizeof(D3DVECTOR)*m_dwNumVertices);

    for(DWORD i=0; i<m_dwNumIndices; i+=3)
    {
        WORD a = m_pIndices[i+0];
        WORD b = m_pIndices[i+1];
        WORD c = m_pIndices[i+2];

        D3DVECTOR* v1 = (D3DVECTOR*)&m_pVertices[a];
        D3DVECTOR* v2 = (D3DVECTOR*)&m_pVertices[b];
        D3DVECTOR* v3 = (D3DVECTOR*)&m_pVertices[c];

        D3DVECTOR n = Normalize(CrossProduct(*v2-*v1, *v3-*v2));

        pNormals[a] += n;
        pNormals[b] += n;
        pNormals[c] += n;
    }

    // Assign the newly computed normals back to the vertices
    for(i=0; i<m_dwNumVertices; i++)
    {
        // Provide some relief to bogus normals
        if (Magnitude(pNormals[i]) < 0.1f) 
            pNormals[i] = D3DVECTOR(0.0f, 0.0f, 1.0f);
        
        pNormals[i] = Normalize(pNormals[i]);
        m_pVertices[i].nx = pNormals[i].x;
        m_pVertices[i].ny = pNormals[i].y;
        m_pVertices[i].nz = pNormals[i].z;
    }

    delete pNormals;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DFileObject::SetNormals(D3DVECTOR* pNormals)
{
    for(DWORD i=0; i<m_dwNumVertices; i++)
    {
        m_pVertices[i].nx = pNormals[i].x;
        m_pVertices[i].ny = pNormals[i].y;
        m_pVertices[i].nz = pNormals[i].z;
    }
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DFileObject::SetTextureCoords(FLOAT* pTexCoords)
{
    for(DWORD i=0; i<m_dwNumVertices; i++)
    {
        m_pVertices[i].tu = pTexCoords[2*i+0];
        m_pVertices[i].tv = pTexCoords[2*i+1];
    }
}




//-----------------------------------------------------------------------------
// Name: ParseXXXX()
// Desc: The following routines implement the DirectX .X file loader.
//-----------------------------------------------------------------------------




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
HRESULT ParseMaterial(LPDIRECTXFILEDATA pFileData, CD3DFileObject* pMesh,
                       DWORD dwMaterial)
{
    // Read data from the file
    LONG_PTR  pData;
    DWORD dwSize;
    TCHAR strTexture[128];

    if (FAILED(pFileData->GetData(NULL, &dwSize, (VOID**)&pData)))
        return NULL;

    // Set the material properties for the mesh
    D3DMATERIAL7 mtrl;
    ZeroMemory(&mtrl, sizeof(mtrl));
    memcpy(&mtrl.diffuse,  (VOID*)(pData+0),  sizeof(FLOAT)*4);
    memcpy(&mtrl.ambient,  (VOID*)(pData+0),  sizeof(FLOAT)*4);
    memcpy(&mtrl.power,    (VOID*)(pData+16), sizeof(FLOAT)*1);
    memcpy(&mtrl.specular, (VOID*)(pData+20), sizeof(FLOAT)*3);
    memcpy(&mtrl.emissive, (VOID*)(pData+32), sizeof(FLOAT)*3);
    strTexture[0] = 0;

    LPDIRECTXFILEOBJECT pChildObj;
    if (SUCCEEDED(pFileData->GetNextObject(&pChildObj)))
    {
        LPDIRECTXFILEDATA pChildData;

        if (SUCCEEDED(pChildObj->QueryInterface(IID_IDirectXFileData,
                                                    (VOID**)&pChildData)))
        {
            const GUID* pguid;
            pChildData->GetType(&pguid);

            if (TID_D3DRMTextureFilename == *pguid)
            {
                TCHAR** string;

                if (FAILED(pChildData->GetData(NULL, &dwSize, (VOID**)&string)))
                    return NULL;

                D3DTextr_CreateTextureFromFile(*string);
                StrCpyN(strTexture, *string, 128);
            }

            pChildData->Release();
        }

        pChildObj->Release();
    }

    pMesh->SetMaterialData(dwMaterial, &mtrl, strTexture);

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
HRESULT ParseMeshMaterialList(LPDIRECTXFILEDATA pFileData,
                               CD3DFileObject* pMesh)
{
    LPDIRECTXFILEOBJECT        pChildObj;
    LPDIRECTXFILEDATA          pChildData;
    LPDIRECTXFILEDATAREFERENCE pChildDataRef;
    DWORD                      dwMaterial = 0;

    while(SUCCEEDED(pFileData->GetNextObject(&pChildObj)))
    {
        if (SUCCEEDED(pChildObj->QueryInterface(IID_IDirectXFileData,
                                                    (VOID**)&pChildData)))
        {
            const GUID* pguid;
            pChildData->GetType(&pguid);

            if (TID_D3DRMMaterial == *pguid)
            {
                ParseMaterial(pChildData, pMesh, dwMaterial++);
            }

            pChildData->Release();
        }

        if (SUCCEEDED(pChildObj->QueryInterface(IID_IDirectXFileDataReference,
                                                    (VOID**)&pChildDataRef)))
        {
            if (SUCCEEDED(pChildDataRef->Resolve(&pChildData)))
            {
                const GUID* pguid;
                pChildData->GetType(&pguid);

                if (TID_D3DRMMaterial == *pguid)
                {
                    ParseMaterial(pChildData, pMesh, dwMaterial++);
                }

                pChildData->Release();
            }
            pChildDataRef->Release();
        }

        pChildObj->Release();
    }
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
HRESULT ParseMesh(LPDIRECTXFILEDATA pFileData, CD3DFileObject* pParentFrame)
{
    DWORD dwNameLen=80;
    CHAR strName[80];
    TCHAR szName[80];

    if (FAILED(pFileData->GetName(strName, &dwNameLen)))
        return E_FAIL;

    // Read the Mesh data from the file
    LONG_PTR  pData;
    DWORD dwSize;

    SHAnsiToUnicode(strName, szName, ARRAYSIZE(szName));
    if (FAILED(pFileData->GetData(NULL, &dwSize, (VOID**)&pData)))
        return E_FAIL;

    DWORD      dwNumVertices =    *((DWORD*)pData); pData += 4;
    D3DVECTOR* pVertices     = ((D3DVECTOR*)pData); pData += 12*dwNumVertices;
    DWORD      dwNumFaces    =    *((DWORD*)pData); pData += 4;
    DWORD*     pFaceData     =      (DWORD*)pData;

    // Create the Mesh object
    CD3DFileObject* pMesh = new CD3DFileObject(szName);
    pMesh->SetMeshGeometry(pVertices, dwNumVertices, pFaceData, dwNumFaces);

    BOOL bHasNormals = FALSE;
    BOOL bHasMaterials = FALSE;

    // Enumerate child objects.
    LPDIRECTXFILEOBJECT pChildObj;
    while(SUCCEEDED(pFileData->GetNextObject(&pChildObj)))
    {
        LPDIRECTXFILEDATA pChildData;

        if (SUCCEEDED(pChildObj->QueryInterface(IID_IDirectXFileData,
                                                  (VOID**)&pChildData)))
        {
            const GUID* pGUID;
            LONG_PTR    pData;
            DWORD       dwSize;

            pChildData->GetType(&pGUID);
            if (FAILED(pChildData->GetData(NULL, &dwSize, (VOID**)&pData)))
            {
                delete pMesh;
                return NULL;
            }

            if (TID_D3DRMMeshMaterialList == *pGUID)
            {
                DWORD  dwNumMaterials = *((DWORD*)pData);   pData += 4;
                DWORD  dwNumMatFaces  = *((DWORD*)pData);   pData += 4;
                DWORD* pMatFace       =   (DWORD*)pData;

                if (dwNumMaterials == 1 || dwNumMatFaces != dwNumFaces)
                {
                    // Only one material add all faces at once
                    pMesh->AddFace(0, pFaceData, dwNumFaces);
                }
                else
                {
                    // Multiple materials, add in sorted order
                    for(DWORD mat=0; mat<dwNumMaterials; mat++)
                    {
                        for(DWORD face=0; face<dwNumMatFaces; face++)
                        {
                            if (pMatFace[face] == mat)
                                pMesh->AddFace(mat, GetFace(pFaceData, face), 1);
                        }
                    }
                }

                ParseMeshMaterialList(pChildData, pMesh);
                bHasMaterials = TRUE;
            }

            if (TID_D3DRMMeshNormals == *pGUID)
            {
                DWORD      dwNumNormals = *((DWORD*)pData);
                D3DVECTOR* pNormals     = (D3DVECTOR*)(pData+4);

                if (dwNumNormals == dwNumVertices)
                {
                    pMesh->SetNormals(pNormals);
                    bHasNormals = TRUE;
                }
            }

            if (TID_D3DRMMeshTextureCoords == *pGUID)
            {
                // Copy the texture coords into the mesh's vertices
                DWORD  dwNumTexCoords = *((DWORD*)pData);
                FLOAT* pTexCoords     = (FLOAT*)(((FLOAT*)pData)+4);

                if (dwNumTexCoords == dwNumVertices)
                    pMesh->SetTextureCoords(pTexCoords);
            }

            pChildData->Release();
        }

        pChildObj->Release();
    }

    if (FALSE == bHasMaterials)
        pMesh->AddFace(0, pFaceData, dwNumFaces);

    if (FALSE == bHasNormals)
        pMesh->ComputeNormals();

    pParentFrame->AddChild(pMesh);
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: 
// Desc:
//-----------------------------------------------------------------------------
HRESULT ParseFrame(LPDIRECTXFILEDATA pFileData, CD3DFileObject* pParentFrame)
{
    DWORD dwNameLen=80;
    CHAR strName[80];
    TCHAR szName[80];

    if (FAILED(pFileData->GetName(strName, &dwNameLen)))
        return E_FAIL;

    SHAnsiToUnicode(strName, szName, ARRAYSIZE(szName));
    CD3DFileObject* pFrame = new CD3DFileObject(szName);

    // Enumerate child objects.
    LPDIRECTXFILEOBJECT pChildObj;
    while(SUCCEEDED(pFileData->GetNextObject(&pChildObj)))
    {
        LPDIRECTXFILEDATA pChildData;
        if (SUCCEEDED(pChildObj->QueryInterface(IID_IDirectXFileData,
                            (VOID**)&pChildData)))
        {
            const GUID* pGUID;
            pChildData->GetType(&pGUID);

            if (TID_D3DRMFrame == *pGUID)
                ParseFrame(pChildData, pFrame);

            if (TID_D3DRMMesh == *pGUID)
                ParseMesh(pChildData, pFrame);

            if (TID_D3DRMFrameTransformMatrix == *pGUID)
            {
                DWORD dwSize;
                VOID* pData;
                if (FAILED(pChildData->GetData(NULL, &dwSize, &pData)))
                {
                    delete pFrame;
                    return NULL;
                }

                if (dwSize == sizeof(D3DMATRIX))
                {
                    // Convert from a left- to a right-handed cordinate system
                    D3DMATRIX* pmatFrame = (D3DMATRIX*)pData;
                    pmatFrame->_13 *= -1.0f;
                    pmatFrame->_31 *= -1.0f;
                    pmatFrame->_23 *= -1.0f;
                    pmatFrame->_32 *= -1.0f;
                    pmatFrame->_43 *= -1.0f;
                    pFrame->SetMatrix(pmatFrame);
                }
            }

            pChildData->Release();
        }

        pChildObj->Release();
    }

    pParentFrame->AddChild(pFrame);
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CD3DFile()
// Desc: Class constructor
//-----------------------------------------------------------------------------
CD3DFile::CD3DFile()
{
    m_pRoot = NULL;
}




//-----------------------------------------------------------------------------
// Name: ~CD3DFile()
// Desc: Class destructor
//-----------------------------------------------------------------------------
CD3DFile::~CD3DFile()
{
    SAFE_DELETE(m_pRoot);
}




//-----------------------------------------------------------------------------
// Name: Load()
// Desc: Loads a .X geometry file, and creates a hierarchy of frames and meshes
//       to represent the geometry in that file.
//-----------------------------------------------------------------------------
HRESULT CD3DFile::Load(LPCTSTR pszFilename)
{
    HRESULT                 hr;
    LPDIRECTXFILE           pDXFile;
    LPDIRECTXFILEENUMOBJECT pEnumObj = NULL;
    LPDIRECTXFILEDATA       pFileData;
    const GUID*             pGUID;
    CD3DFileObject*         pFrame = NULL;
    CHAR szFilename[MAX_PATH];

    SHTCharToAnsi(pszFilename, szFilename, ARRAYSIZE(szFilename));
    // Cleanup any existing object
    SAFE_DELETE(m_pRoot);

    // Create the file object, and register the D3DRM templates for .X files
    if (FAILED(DirectXFileCreate(&pDXFile)))
        return E_FAIL;
    if (FAILED(pDXFile->RegisterTemplates((VOID*)D3DRM_XTEMPLATES,
                                            D3DRM_XTEMPLATE_BYTES)))
    {
        pDXFile->Release();
        return E_FAIL;
    }

    // Create an enumerator object, to enumerate through the .X file objects.
    // This will open the file in the current directory.
    hr = pDXFile->CreateEnumObject(szFilename, DXFILELOAD_FROMFILE, &pEnumObj);
    if (FAILED(hr))
    {
        CHAR szPath[MAX_PATH];

        GetCurrentDirectoryA(ARRAYSIZE(szPath), szPath);
        PathAppendA(szPath, szFilename);
        hr = pDXFile->CreateEnumObject(szPath, DXFILELOAD_FROMFILE, &pEnumObj);
        if (FAILED(hr))
        {
            pDXFile->Release();
            return hr;
        }
    }

    // Create a root object for the X file object
    m_pRoot = new CD3DFileObject(TEXT("D3DFile_Root"));

    // Cycle through each object. Parse meshes and frames as appropriate
    while(SUCCEEDED(hr = pEnumObj->GetNextDataObject(&pFileData)))
    {
        pFileData->GetType(&pGUID);

        if (*pGUID == TID_D3DRMFrame)
            ParseFrame(pFileData, m_pRoot);

        if (*pGUID == TID_D3DRMMesh)
            ParseMesh(pFileData, m_pRoot);

        pFileData->Release();
    }

    // Success will result in hr == DXFILEERR_NOMOREOBJECTS
    if (DXFILEERR_NOMOREOBJECTS == hr) 
        hr = S_OK;
    else
        SAFE_DELETE(m_pRoot);

    pEnumObj->Release();
    pDXFile->Release();

    return hr;
}




//-----------------------------------------------------------------------------
// Name: GetMeshVertices()
// Desc: Traverse the hierarchy of frames and meshes that make up the file
//       object, and retrieves the vertices for the specified mesh.
//-----------------------------------------------------------------------------
HRESULT CD3DFile::GetMeshVertices(TCHAR* strName, D3DVERTEX** ppVertices,
                                   DWORD* pdwNumVertices)
{
    CD3DFileObject* pObject = FindObject(strName);
    if (pObject)
        return pObject->GetMeshGeometry(ppVertices, pdwNumVertices, NULL, NULL);

    return E_FAIL;
}




//-----------------------------------------------------------------------------
// Name: GetMeshVertices()
// Desc: Traverse the hierarchy of frames and meshes that make up the file
//       object, and retrieves the vertices for the specified mesh.
//-----------------------------------------------------------------------------
HRESULT CD3DFile::GetMeshIndices(TCHAR* strName, WORD** ppIndices,
                                  DWORD* pdwNumIndices)
{
    CD3DFileObject* pObject = FindObject(strName);
    if (pObject)
        return pObject->GetMeshGeometry(NULL, NULL, ppIndices, pdwNumIndices);

    return E_FAIL;
}




//-----------------------------------------------------------------------------
// Name: EnumObjects()
// Desc: Enumerates all objects in the file.
//-----------------------------------------------------------------------------
BOOL CD3DFileObject::EnumObjects(BOOL (*fnCallback)(CD3DFileObject*,D3DMATRIX*,VOID*),
                                  D3DMATRIX* pmat, VOID* pContext)
{
    if (fnCallback(this, pmat, pContext) == TRUE)
        return TRUE;

    if (m_pChild)
    {
        // Concat matrix set
        D3DMATRIX matSave = (*pmat);
        (*pmat) = (*pmat) * m_mat;
    
        if (m_pChild->EnumObjects(fnCallback, pmat, pContext) == TRUE)
            return TRUE;

        // Restore matrix set
        (*pmat) = matSave;
    }

    if (m_pNext)
        if (m_pNext->EnumObjects(fnCallback, pmat, pContext) == TRUE)
            return TRUE;

    return FALSE;
}




//-----------------------------------------------------------------------------
// Name: EnumObjects()
// Desc: Enumerates all objects in the file.
//-----------------------------------------------------------------------------
VOID CD3DFile::EnumObjects(BOOL (*fnCallback)(CD3DFileObject*,D3DMATRIX*,VOID*),
                            D3DMATRIX* pmat, VOID* pContext)
{
    if (m_pRoot)
    {
        D3DMATRIX mat;

        if (pmat)
            mat = *pmat;
        else
            D3DUtil_SetIdentityMatrix(mat);

        m_pRoot->EnumObjects(fnCallback, &mat, pContext);
    }
}




//-----------------------------------------------------------------------------
// Name: ScaleMeshCB()
// Desc: Callback to scale a mesh
//-----------------------------------------------------------------------------
BOOL ScaleMeshCB(CD3DFileObject* pFileObject, D3DMATRIX*, VOID* pContext)
{
    D3DVERTEX* pVertices;
    DWORD      dwNumVertices;

    if (SUCCEEDED(pFileObject->GetMeshGeometry(&pVertices, &dwNumVertices,
                                                 NULL, NULL)))
    {
        for(DWORD i=0; i<dwNumVertices; i++)
        {
            pVertices[i].x *= (*((FLOAT*)pContext));
            pVertices[i].y *= (*((FLOAT*)pContext));
            pVertices[i].z *= (*((FLOAT*)pContext));
        }
    }

    // Keep enumerating
    return FALSE;
}



//-----------------------------------------------------------------------------
// Name: FindMeshCB()
// Desc: Callback to scale a mesh
//-----------------------------------------------------------------------------
BOOL FindMeshCB(CD3DFileObject* pFileObject, D3DMATRIX*, VOID* pContext)
{
    struct FINDMESHRECORD
    {
        TCHAR*          strName;
        CD3DFileObject* pObject;
    };

    FINDMESHRECORD* data = (FINDMESHRECORD*)pContext;
    
    if (0 == lstrcmpi(data->strName, pFileObject->GetName()))
    {
        data->pObject = pFileObject;
        return TRUE;
    }

    // Keep enumerating
    return FALSE;
}




//-----------------------------------------------------------------------------
// Name: Scale()
// Desc: Scales all meshes in the file
//-----------------------------------------------------------------------------
VOID CD3DFile::Scale(FLOAT fScale)
{
    EnumObjects(ScaleMeshCB, NULL, (VOID*)&fScale);
}




//-----------------------------------------------------------------------------
// Name: FindObject()
// Desc: Searches all meshes in file object and returns named mesh
//-----------------------------------------------------------------------------
CD3DFileObject* CD3DFile::FindObject(TCHAR* strName)
{
    if (NULL == strName)
        return m_pRoot;

    struct FINDMESHRECORD
    {
        TCHAR*          strName;
        CD3DFileObject* pObject;
    };

    FINDMESHRECORD data = { strName, NULL };

    EnumObjects(FindMeshCB, NULL, (VOID*)&data);
    return data.pObject;
}




//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Renders the hierarchy of frames and meshes that make up the file
//       object
//-----------------------------------------------------------------------------
HRESULT CD3DFile::Render(LPDIRECT3DDEVICE7 pd3dDevice)
{
    LPDIRECTDRAWSURFACE7 pddsSavedTexture;
    D3DMATRIX    matSaved;
    D3DMATERIAL7 mtrlSaved;
    DWORD        dwAlphaState, dwSrcBlendState, dwDestBlendState;

    if (m_pRoot)
    {
        // State render states that will be overwritten
        pd3dDevice->GetMaterial(&mtrlSaved);
        pd3dDevice->GetTexture(0, &pddsSavedTexture);
        pd3dDevice->GetTransform(D3DTRANSFORMSTATE_WORLD, &matSaved);
        pd3dDevice->GetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, &dwAlphaState);
        pd3dDevice->GetRenderState(D3DRENDERSTATE_SRCBLEND,  &dwSrcBlendState);
        pd3dDevice->GetRenderState(D3DRENDERSTATE_DESTBLEND, &dwDestBlendState);

        // Render the opaque file object's hierarchy of frames and meshes
        m_pRoot->Render(pd3dDevice, FALSE);

        // Render the transparent file object's hierarchy of frames and meshes
//        pd3dDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, TRUE);
//        pd3dDevice->SetRenderState(D3DRENDERSTATE_SRCBLEND,  D3DBLEND_SRCALPHA);
//        pd3dDevice->SetRenderState(D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA);
//        m_pRoot->Render(pd3dDevice, TRUE);

        // Restore the render states
        pd3dDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, dwAlphaState);
        pd3dDevice->SetRenderState(D3DRENDERSTATE_SRCBLEND,  dwSrcBlendState);
        pd3dDevice->SetRenderState(D3DRENDERSTATE_DESTBLEND, dwDestBlendState);
        pd3dDevice->SetTransform(D3DTRANSFORMSTATE_WORLD, &matSaved);
        pd3dDevice->SetTexture(0, pddsSavedTexture);
        pd3dDevice->SetMaterial(&mtrlSaved);

        // Keep the ref count of the texture consistent
        if (pddsSavedTexture)
            pddsSavedTexture->Release();
    }

    return S_OK;
}




