//**************************************************************************
//
//  Copyright (C) Microsoft Corporation, 1998 - 1999 All Rights Reserved.
//
//  File:   frmsave.cpp
//
//  Description:    Save LPDIRECT3DRMFRAME to an x file.
//
//  History:
//      011/06/98    CongpaY     Created
//
//**************************************************************************


#include <d3drm.h>
#include <dxfile.h>
#include <rmxftmpl.h>
#include <rmxfguid.h>
#include "frmsave.h"

extern HINSTANCE g_hInstD3DXOFDLL;

#define MyD3DRMColorGetAlpha(color)  ((float)((color & 0xFF000000)>>24)/(float)255)
#define MyD3DRMColorGetRed(color)  ((float)((color & 0x00FF0000)>>16)/(float)255)
#define MyD3DRMColorGetGreen(color)  ((float)((color & 0x0000FF00)>>8)/(float)255)
#define MyD3DRMColorGetBlue(color)  ((float)((color & 0x000000FF))/(float)255)


HRESULT FrameToXFile(LPDIRECT3DRMFRAME3 pFrame,
                     LPCSTR filename,
                     D3DRMXOFFORMAT d3dFormat,
                     D3DRMSAVEOPTIONS d3dSaveFlags)
{
    Saver saver;
    saver.Init(filename, d3dFormat, d3dSaveFlags);
    saver.SaveHeaderObject();
    saver.SaveFrame(pFrame);
    return S_OK;
}

Saver::Saver()
{
	pXFile=NULL;
	pSave=NULL;
}

Saver::~Saver()
{
    if (pSave) pSave->Release();
    if (pXFile) pXFile->Release();
}

HRESULT Saver::Init(LPCSTR filename,
                    D3DRMXOFFORMAT d3dFormatArg,
                    D3DRMSAVEOPTIONS d3dSaveFlagsArg)
{
    HRESULT hr;
    d3dFormat = d3dFormatArg;
    d3dSaveFlags = d3dSaveFlagsArg;


    CREATEXFILE pCreateXFile=(CREATEXFILE)GetProcAddress( g_hInstD3DXOFDLL, "DirectXFileCreate" );	
    if (!pCreateXFile) return E_NOTIMPL;
		
	
    DXFILEFORMAT xFormat;

    if (d3dFormat == D3DRMXOF_BINARY)
        xFormat = DXFILEFORMAT_BINARY;
    else if (d3dFormat == D3DRMXOF_TEXT)
        xFormat = DXFILEFORMAT_TEXT;
    else
        xFormat = DXFILEFORMAT_COMPRESSED;

    //DirectXFileCreate(&pXFile);
    pCreateXFile(&pXFile);
    if (!pXFile) return E_FAIL;
    
    hr=pXFile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES);
    if FAILED(hr) 
    {
	pXFile->Release();
	pXFile=NULL;
	return hr;
    }

    hr=pXFile->CreateSaveObject(filename, xFormat, &pSave);
    if FAILED(hr) 
    {
	pXFile->Release();
	pXFile=NULL;
	pSave=NULL;
	return hr;
    }

    return S_OK;
}

HRESULT Saver::SaveHeaderObject()
{
    LPDIRECTXFILEDATA pHeader=NULL;
    Header data;
    HRESULT hr;

    data.major = 1;
    data.minor = 0;
    data.flags = (d3dFormat == D3DRMXOF_TEXT)? 1 : 0;

    if (!pSave) return E_FAIL;

    hr=pSave->CreateDataObject(TID_DXFILEHeader,
                            NULL,
                            NULL,
                            sizeof(Header),
                            &data,
                            &pHeader);
    if FAILED(hr) return hr;
    if (!pHeader) return hr;	

    hr=pSave->SaveData(pHeader);
    pHeader->Release();
    return hr;
}

HRESULT Saver::SaveFrame(LPDIRECT3DRMFRAME3 pFrame,
                         LPDIRECT3DRMFRAME3 pRefFrame,
                         LPDIRECTXFILEDATA pRefFrameObj)
{
    DWORD i;
    HRESULT hr;
    LPDIRECTXFILEDATA pFrameObj=NULL;

    if (!pSave) return E_FAIL;
    if (!pFrame) return E_FAIL;

    hr=pSave->CreateDataObject(TID_D3DRMFrame,
                            NULL, 
                            NULL,
                            0,
                            NULL,
                            &pFrameObj);
    if FAILED(hr) return hr;
    if (!pFrameObj) return E_FAIL;

    hr=SaveFrameTransform(pFrameObj, pFrame, pRefFrame);
    if FAILED(hr) {
	pFrameObj->Release();
	return hr;  
    }    

    // Enumerate visuals.

    DWORD cVisuals=0;

    hr=pFrame->GetVisuals(&cVisuals, NULL);
    if FAILED(hr) {
	pFrameObj->Release();
	return hr;  
    }    
	

    if (cVisuals)
    {
        LPUNKNOWN *ppUnk = new LPUNKNOWN[cVisuals];

        hr=pFrame->GetVisuals(&cVisuals, ppUnk);
	if SUCCEEDED(hr)
	{
            for (i = 0; i < cVisuals; i++)
            {
              LPDIRECT3DRMFRAME3 pChildFrame;
	      if (ppUnk[i]) 
              {
                 hr = ppUnk[i]->QueryInterface(IID_IDirect3DRMFrame3, (LPVOID *)&pChildFrame);

                 if (SUCCEEDED(hr))
                 {
                   SaveFrame(pChildFrame, pFrame, pFrameObj);
                   pChildFrame->Release();
                 }
                 else
                 {
                   LPDIRECT3DRMMESHBUILDER3 pMeshBuilder;
                   hr = ppUnk[i]->QueryInterface(IID_IDirect3DRMMeshBuilder3, (LPVOID *)&pMeshBuilder);
     
                   if (SUCCEEDED(hr))
                   {
                       SaveMeshBuilder(pFrameObj, pMeshBuilder);
                       pMeshBuilder->Release();
                   }
	        }         
                ppUnk[i]->Release();
             }
          }
	
          delete[] ppUnk;
       }
    }  //cVisuals

    // Enumerate child frames.

    LPDIRECT3DRMFRAMEARRAY pFrameArray=NULL;

    hr=pFrame->GetChildren(&pFrameArray);
    if SUCCEEDED(hr) 
    {
       for (i = 0; i < pFrameArray->GetSize(); i++)
       {
           LPDIRECT3DRMFRAME pTmpFrame;
           LPDIRECT3DRMFRAME3 pChildFrame;
           pFrameArray->GetElement(i, &pTmpFrame);
           pTmpFrame->QueryInterface(IID_IDirect3DRMFrame3, (LPVOID *)&pChildFrame);
           pTmpFrame->Release();
           SaveFrame(pChildFrame, pFrame, pFrameObj);
           pChildFrame->Release();
       }

       pFrameArray->Release();

       // Add frame object to the saved list.

       if (pRefFrameObj)
           pRefFrameObj->AddDataObject(pFrameObj);
       else
           pSave->SaveData(pFrameObj);

       pFrameObj->Release();
    }

    return hr;
}

HRESULT Saver::SaveFrameTransform(LPDIRECTXFILEDATA pFrameObj,
                                  LPDIRECT3DRMFRAME3 pFrame,
                                  LPDIRECT3DRMFRAME3 pRefFrame)
{
    LPDIRECTXFILEDATA pFrameTransformObj=NULL;
    D3DRMMATRIX4D rmMatrix;
    HRESULT hr;

    if (!pFrame) return E_INVALIDARG;

    pFrame->GetTransform(pRefFrame, rmMatrix);

    hr=pSave->CreateDataObject(TID_D3DRMFrameTransformMatrix,
                            NULL,
                            NULL,
                            sizeof(D3DRMMATRIX4D),
                            &rmMatrix,
                            &pFrameTransformObj);
    if FAILED(hr) return hr;

    hr=pFrameObj->AddDataObject(pFrameTransformObj);
    pFrameTransformObj->Release();
    return hr;
}

HRESULT Saver::SaveMeshBuilder(LPDIRECTXFILEDATA pFrameObj,
                               LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
{
    LPDIRECTXFILEDATA pMeshObj;
    DWORD cVertices, cNormals, cFaces, dwFaceData, *pdwFaceData;
    LPDIRECT3DRMFACEARRAY pFaceArray = NULL;
    HRESULT hr;

    if (!pMeshBuilder) return E_INVALIDARG;
    //pFrameObj can be null

    hr=pMeshBuilder->GetGeometry(&cVertices, NULL,
                              &cNormals, NULL,
                              &dwFaceData, NULL);
    if FAILED(hr) return hr;

    cFaces = pMeshBuilder->GetFaceCount();

    if (!cVertices || !cNormals || !dwFaceData || !cFaces)
        return S_OK;

    pdwFaceData = new DWORD[dwFaceData];
    if FAILED(pdwFaceData) return E_OUTOFMEMORY;

    hr=pMeshBuilder->GetGeometry(NULL, NULL,
                              NULL, NULL,
                              &dwFaceData, pdwFaceData);
    if FAILED(hr) return hr;

    hr=CreateMeshObject(cVertices, cFaces, dwFaceData, pdwFaceData,
                     pMeshBuilder, &pMeshObj);
    if FAILED(hr) return hr;

    D3DRMCOLORSOURCE clrSrc = pMeshBuilder->GetColorSource();

    if (clrSrc == D3DRMCOLOR_FROMVERTEX)
    {
        CreateVertexColorsObject(pMeshObj, cVertices, pMeshBuilder);
    }

    if (d3dSaveFlags & D3DRMXOFSAVE_MATERIALS)
    {
        if (!pFaceArray)
            pMeshBuilder->GetFaces(&pFaceArray);
        CreateMaterialListObject(pMeshObj, pFaceArray);
    }

    if (d3dSaveFlags & D3DRMXOFSAVE_NORMALS)
    {
        CreateNormalsObject(pMeshObj,
                            cNormals, cFaces, dwFaceData, pdwFaceData,
                            pMeshBuilder);
    }
    

    if (d3dSaveFlags & D3DRMXOFSAVE_TEXTURETOPOLOGY)
    {
        if (!pFaceArray)
            pMeshBuilder->GetFaces(&pFaceArray);
        CreateTextureWrapsObject(pMeshObj, pFaceArray);
    }

    if (d3dSaveFlags & D3DRMXOFSAVE_TEXTURECOORDINATES)
    {
        CreateTextureCoordsObject(pMeshObj, cVertices, pMeshBuilder);
    }

    if (pFrameObj)
        pFrameObj->AddDataObject(pMeshObj);
    else
        pSave->SaveData(pMeshObj);

    pMeshObj->Release();
    delete[] pdwFaceData;
    if (pFaceArray)
        pFaceArray->Release();

    return S_OK;
}

HRESULT Saver::CreateMeshObject(DWORD cVertices,
                                DWORD cFaces,
                                DWORD dwFaceData,
                                LPDWORD pdwFaceData,
                                LPDIRECT3DRMMESHBUILDER3 pMeshBuilder,
                                LPDIRECTXFILEDATA *ppMeshObj)
{
    // mesh data is vertex_count + vertices + face_count + face_vertex_data;
    HRESULT hr;

    if (!pMeshBuilder) return E_INVALIDARG;
    if (!pSave) return E_INVALIDARG;

    DWORD cbSize, *data;
    cbSize = cVertices * sizeof(D3DVECTOR) +
        (1 + (dwFaceData + cFaces + 1)/2) * sizeof(DWORD);

    data = (LPDWORD) new BYTE[cbSize];
    if (!data) return E_OUTOFMEMORY;
    data[0] = cVertices;
    LPD3DVECTOR pVertices = (LPD3DVECTOR)&data[1];
    pMeshBuilder->GetGeometry(&cVertices, pVertices,
                              NULL, NULL,
                              NULL, NULL);

    LPDWORD pdwTmp = (LPDWORD)&pVertices[cVertices];
    *pdwTmp++ = cFaces;

    while (*pdwFaceData)
    {
        DWORD cFaceVertices = *pdwFaceData++;
        *pdwTmp++ = cFaceVertices;

        for (DWORD i = 0; i < cFaceVertices; i++)
        {
            *pdwTmp++ = *pdwFaceData++;
            pdwFaceData++; // skip normal index.
        }
    }

    DWORD dwSize;
    pMeshBuilder->GetName(&dwSize, NULL);
    
    LPSTR szName = NULL;
    if (dwSize)
    {
        szName = new char[dwSize];
        pMeshBuilder->GetName(&dwSize, szName);
    }

    hr=pSave->CreateDataObject(TID_D3DRMMesh,
                            szName,
                            NULL,
                            cbSize,
                            data,
                            ppMeshObj);

    if (szName) lNames.Add(szName);
    delete[] data;
    return S_OK;
}

HRESULT Saver::CreateNormalsObject(LPDIRECTXFILEDATA pMeshObj,
                                   DWORD cNormals,
                                   DWORD cFaces,
                                   DWORD dwFaceData,
                                   LPDWORD pdwFaceData,
                                   LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
{                                
    // normals data is normal_count + normals + face_count + face_normal_data;
    HRESULT hr;

    if (!pMeshObj) return E_INVALIDARG;

    DWORD cbSize, *data;
    cbSize = cNormals * sizeof(D3DVECTOR) +
        (1 + (dwFaceData + cFaces + 1)/2) * sizeof(DWORD);

    data = (LPDWORD) new BYTE[cbSize];
    if (!data) return E_OUTOFMEMORY;

    data[0] = cNormals;

    LPD3DVECTOR pNormals = (LPD3DVECTOR)&data[1];

    hr=pMeshBuilder->GetGeometry(NULL, NULL,
                              &cNormals, pNormals,      
	                        NULL, NULL);
    if FAILED(hr) return hr;

    LPDWORD pdwTmp = (LPDWORD)&pNormals[cNormals];
    *pdwTmp++ = cFaces;

    while (*pdwFaceData)
    {
        DWORD cFaceVertices = *pdwFaceData++;
        *pdwTmp++ = cFaceVertices;

        for (DWORD i = 0; i < cFaceVertices; i++)
        {
            pdwFaceData++; // skip vertex index.
            *pdwTmp++ = *pdwFaceData++;
        }
    }

    LPDIRECTXFILEDATA pNormalsObj=NULL;

    hr=pSave->CreateDataObject(TID_D3DRMMeshNormals,
                            NULL,
                            NULL,
                            cbSize,
                            data,
                            &pNormalsObj);
    if FAILED(hr) return hr;

    pMeshObj->AddDataObject(pNormalsObj);
    pNormalsObj->Release();
    delete[] data;

    return S_OK;
}

HRESULT Saver::CreateVertexColorsObject(LPDIRECTXFILEDATA pMeshObj,
                                        DWORD cVertices,
                                        LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
{
    DWORD cbSize;
    VertexColors *data;
    HRESULT hr;
    
    if (!pSave) return E_INVALIDARG;
    if (!pMeshBuilder) return E_INVALIDARG;

    cbSize = sizeof(DWORD) + cVertices * sizeof(IndexedColor);

    data = (VertexColors *) new BYTE[cbSize];
    if (!data) return E_OUTOFMEMORY;

    data->cVertices = cVertices;

    for (DWORD i = 0; i < cVertices; i++)
    {
        D3DCOLOR color = pMeshBuilder->GetVertexColor(i);
        data->vertexColors[i].index = i;
        data->vertexColors[i].color.r = MyD3DRMColorGetRed(color);
        data->vertexColors[i].color.g = MyD3DRMColorGetGreen(color);
        data->vertexColors[i].color.b = MyD3DRMColorGetBlue(color);
        data->vertexColors[i].color.a = MyD3DRMColorGetAlpha(color);
    }

    LPDIRECTXFILEDATA pVertexColorsObj=NULL;

    hr=pSave->CreateDataObject(TID_D3DRMMeshVertexColors,
                            NULL,
                            NULL,
                            cbSize,
                            data,
                            &pVertexColorsObj);
    if FAILED(hr) {
        delete[] data;                        
	return hr;
    }

    pMeshObj->AddDataObject(pVertexColorsObj);
    pVertexColorsObj->Release();
    delete[] data;                        

    return S_OK;
}

HRESULT Saver::CreateMaterialListObject(LPDIRECTXFILEDATA pMeshObj,
                                        LPDIRECT3DRMFACEARRAY pFaceArray)
{
    DWORD cbSize, cFaces;
    FaceMaterials *data;
    FaceMaterialList lMat;
    

    cFaces = pFaceArray->GetSize();
    cbSize = (2 + cFaces) * sizeof(DWORD);

    data = (FaceMaterials *) new BYTE[cbSize];
    if (!data) return E_OUTOFMEMORY;

    data->cFaceIndexes = cFaces;
    LPDWORD pdwIndex = data->faceIndexes;

    for (DWORD i = 0; i < cFaces; i++, pdwIndex++)
    {
        LPDIRECT3DRMFACE pFace;
        pFaceArray->GetElement(i, &pFace);

        D3DCOLOR faceColor;
        LPDIRECT3DRMMATERIAL pMaterial;
        LPDIRECT3DRMTEXTURE pTexture;

        faceColor = pFace->GetColor();
        pFace->GetMaterial(&pMaterial);
        pFace->GetTexture(&pTexture);
        
        *pdwIndex = lMat.Find(faceColor, pMaterial, pTexture);

        pMaterial->Release();
        if (pTexture) pTexture->Release();
        pFace->Release();
    }

    data->cMaterials = lMat.Count();

    if (data->cMaterials == 1)
    {
        data->cFaceIndexes = 1;
        data->faceIndexes[0] = 0;
        cbSize = 3 * sizeof(DWORD);
    }

    LPDIRECTXFILEDATA pMatListObj;

    pSave->CreateDataObject(TID_D3DRMMeshMaterialList,
                            NULL,
                            NULL,
                            cbSize,
                            data,
                            &pMatListObj);

    FaceMaterial *pMat;
    for (pMat = lMat.First(); pMat; pMat = pMat->pNext)
    {
        CreateMaterialObject(pMatListObj,
                             pMat);
    }

    pMeshObj->AddDataObject(pMatListObj);
    pMatListObj->Release();
    delete[] data;
    
    return S_OK;
}

HRESULT Saver::CreateMaterialObject(LPDIRECTXFILEDATA pMatListObj,
                                    FaceMaterial *pMat)
{
    BaseMaterial data;

    data.faceColor.r = MyD3DRMColorGetRed(pMat->faceColor);
    data.faceColor.g = MyD3DRMColorGetGreen(pMat->faceColor);
    data.faceColor.b = MyD3DRMColorGetBlue(pMat->faceColor);
    data.faceColor.a = MyD3DRMColorGetAlpha(pMat->faceColor);

    data.power = pMat->pMaterial->GetPower();

    pMat->pMaterial->GetSpecular(&data.specularColor.r,
                                 &data.specularColor.g,
                                 &data.specularColor.b);

    pMat->pMaterial->GetEmissive(&data.emissiveColor.r,
                                 &data.emissiveColor.g,
                                 &data.emissiveColor.b);

    LPDIRECTXFILEDATA pMaterialObj;

    pSave->CreateDataObject(TID_D3DRMMaterial,
                            NULL,
                            NULL,
                            sizeof(BaseMaterial),
                            &data,
                            &pMaterialObj);

    if (pMat->pTexture)
    {
        IDirectXFileData *pTextureObj;

        DWORD dwSize;
        pMat->pTexture->GetName(&dwSize, NULL);

        if (dwSize)
        {
            LPSTR szName = new char[dwSize];
            pMat->pTexture->GetName(&dwSize, szName);
    
            pSave->CreateDataObject(TID_D3DRMTextureFilename,
                                    NULL,
                                    NULL,
                                    sizeof(LPSTR),
                                    &szName,
                                    &pTextureObj);
    
            pMaterialObj->AddDataObject(pTextureObj);
            pTextureObj->Release();
            lNames.Add(szName);
        }
    }

    pMatListObj->AddDataObject(pMaterialObj);
    pMaterialObj->Release();

    return S_OK;
}

HRESULT Saver::CreateTextureWrapsObject(LPDIRECTXFILEDATA pMeshObj,
                                        LPDIRECT3DRMFACEARRAY pFaceArray)
{
    DWORD cbSize, cFaces;
    FaceWraps *data;

    cFaces = pFaceArray->GetSize();
    cbSize = sizeof(DWORD) + cFaces * sizeof(Boolean2d);

    data = (FaceWraps *) new BYTE[cbSize];
    if (!data) return E_OUTOFMEMORY;

    data->cFaces = cFaces;
    Boolean2d *pWrap = data->faceWraps;

    for (DWORD i = 0; i < cFaces; i++, pWrap++)
    {
        LPDIRECT3DRMFACE pFace;
        pFaceArray->GetElement(i, &pFace);
        pFace->GetTextureTopology(&pWrap->u, &pWrap->v);
        pFace->Release();
    }

    LPDIRECTXFILEDATA pTextureWrapsObj;

    pSave->CreateDataObject(TID_D3DRMMeshFaceWraps,
                            NULL,
                            NULL,
                            cbSize,
                            data,
                            &pTextureWrapsObj);

    pMeshObj->AddDataObject(pTextureWrapsObj);
    pTextureWrapsObj->Release();
    delete[] data;                        

    return S_OK;
}

HRESULT Saver::CreateTextureCoordsObject(LPDIRECTXFILEDATA pMeshObj,
                                         DWORD cVertices,
                                         LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
{
    DWORD cbSize;
    TextureCoords *data;

    cbSize = sizeof(DWORD) + cVertices * sizeof(Coords2d);

    data = (TextureCoords *) new BYTE[cbSize];
    if (!data) return E_OUTOFMEMORY;

    data->cVertices = cVertices;
    Coords2d *pCoords = data->textureCoords;

    for (DWORD i = 0; i < cVertices; i++, pCoords++)
    {
        pMeshBuilder->GetTextureCoordinates(i, &pCoords->u, &pCoords->v);
    }

    LPDIRECTXFILEDATA pTexCoordsObj;

    pSave->CreateDataObject(TID_D3DRMMeshTextureCoords,
                            NULL,
                            NULL,
                            cbSize,
                            data,
                            &pTexCoordsObj);

    pMeshObj->AddDataObject(pTexCoordsObj);
    pTexCoordsObj->Release();
    delete[] data;                        

    return S_OK;
}

FaceMaterialList::FaceMaterialList()
  : cElements(0), pFirst(NULL)
{
}

FaceMaterialList::~FaceMaterialList()
{
    FaceMaterial *pMat = pFirst;
    while (pMat)
    {
        FaceMaterial *pNext = pMat->pNext;
        pMat->pMaterial->Release();
        if (pMat->pTexture) pMat->pTexture->Release();
        delete pMat;
        pMat = pNext;
    }
}

DWORD FaceMaterialList::Find(D3DCOLOR faceColor,
                             LPDIRECT3DRMMATERIAL pMaterial,
                             LPDIRECT3DRMTEXTURE pTexture)
{
    FaceMaterial *pTmp = pFirst;
    FaceMaterial **ppNew = &pFirst;

    for (DWORD i = 0; pTmp; i++, pTmp = pTmp->pNext)
    {
        if (pTmp->faceColor == faceColor &&
            pTmp->pMaterial == pMaterial &&
            pTmp->pTexture == pTexture)
            return i;

        if (!pTmp->pNext)
            ppNew = &pTmp->pNext;
    }

    FaceMaterial *pNew = new FaceMaterial;
    if (!pNew) return 0;

    pNew->faceColor = faceColor;
    pNew->pMaterial = pMaterial;
    pNew->pTexture = pTexture;
    pNew->pNext = NULL;
    pMaterial->AddRef();
    if (pTexture) pTexture->AddRef();

    *ppNew = pNew;
    cElements++;
    return i;
}

NameList::NameList()
 : pFirst(NULL),
   ppLast(NULL)
{
}

NameList::~NameList()
{
    NameEntry *pEntry = pFirst;

    while (pEntry)
    {
        NameEntry *pNext = pEntry->pNext;
        delete[] pEntry->pName;
        delete pEntry;
        pEntry = pNext;
    }
}

void NameList::Add(LPSTR pName)
{
    NameEntry *pNew = new NameEntry;
    if (!pNew) return;
	
    pNew->pName = pName;
    pNew->pNext = NULL;

    if (ppLast)
        *ppLast = pNew;
    else
        pFirst = pNew;

    ppLast = &pNew->pNext;
}