//--------------------------------------------------------------------------------------
// File: ModelLoadCMO.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------

#include "pch.h"
#include "Model.h"
#include "DDSTextureLoader.h"
#include "DirectXHelpers.h"
#include "Effects.h"
#include "VertexTypes.h"
#include "BinaryReader.h"
#include "PlatformHelpers.h"

using namespace DirectX;
using Microsoft::WRL::ComPtr;


//--------------------------------------------------------------------------------------
// .CMO files are built by Visual Studio 2012 and an example renderer is provided
// in the VS Direct3D Starter Kit
// http://code.msdn.microsoft.com/Visual-Studio-3D-Starter-455a15f1
//--------------------------------------------------------------------------------------

namespace VSD3DStarter
{
    // .CMO files

    // UINT - Mesh count
    // { [Mesh count]
    //      UINT - Length of name
    //      wchar_t[] - Name of mesh (if length > 0)
    //      UINT - Material count
    //      { [Material count]
    //          UINT - Length of material name
    //          wchar_t[] - Name of material (if length > 0)
    //          Material structure
    //          UINT - Length of pixel shader name
    //          wchar_t[] - Name of pixel shader (if length > 0)
    //          { [8]
    //              UINT - Length of texture name
    //              wchar_t[] - Name of texture (if length > 0)
    //          }
    //      }
    //      BYTE - 1 if there is skeletal animation data present
    //      UINT - SubMesh count
    //      { [SubMesh count]
    //          SubMesh structure
    //      }
    //      UINT - IB Count
    //      { [IB Count]
    //          UINT - Number of USHORTs in IB
    //          USHORT[] - Array of indices
    //      }
    //      UINT - VB Count
    //      { [VB Count]
    //          UINT - Number of verts in VB
    //          Vertex[] - Array of vertices
    //      }
    //      UINT - Skinning VB Count
    //      { [Skinning VB Count]
    //          UINT - Number of verts in Skinning VB
    //          SkinningVertex[] - Array of skinning verts
    //      }
    //      MeshExtents structure
    //      [If skeleton animation data is not present, file ends here]
    //      UINT - Bone count
    //      { [Bone count]
    //          UINT - Length of bone name
    //          wchar_t[] - Bone name (if length > 0)
    //          Bone structure
    //      }
    //      UINT - Animation clip count
    //      { [Animation clip count]
    //          UINT - Length of clip name
    //          wchar_t[] - Clip name (if length > 0)
    //          float - Start time
    //          float - End time
    //          UINT - Keyframe count
    //          { [Keyframe count]
    //              Keyframe structure
    //          }
    //      }
    // }

#pragma pack(push,1)

    struct Material
    {
        DirectX::XMFLOAT4   Ambient;
        DirectX::XMFLOAT4   Diffuse;
        DirectX::XMFLOAT4   Specular;
        float               SpecularPower;
        DirectX::XMFLOAT4   Emissive;
        DirectX::XMFLOAT4X4 UVTransform;
    };

    const uint32_t MAX_TEXTURE = 8;

    struct SubMesh
    {
        UINT MaterialIndex;
        UINT IndexBufferIndex;
        UINT VertexBufferIndex;
        UINT StartIndex;
        UINT PrimCount;
    };

    const uint32_t NUM_BONE_INFLUENCES = 4;

    static_assert(sizeof(VertexPositionNormalTangentColorTexture) == 52, "mismatch with CMO vertex type");

    struct SkinningVertex
    {
        UINT boneIndex[NUM_BONE_INFLUENCES];
        float boneWeight[NUM_BONE_INFLUENCES];
    };

    struct MeshExtents
    {
        float CenterX, CenterY, CenterZ;
        float Radius;

        float MinX, MinY, MinZ;
        float MaxX, MaxY, MaxZ;
    };

    struct Bone
    {
        INT ParentIndex;
        DirectX::XMFLOAT4X4 InvBindPos;
        DirectX::XMFLOAT4X4 BindPos;
        DirectX::XMFLOAT4X4 LocalTransform;
    };

    struct Clip
    {
        float StartTime;
        float EndTime;
        UINT  keys;
    };

    struct Keyframe
    {
        UINT BoneIndex;
        float Time;
        DirectX::XMFLOAT4X4 Transform;
    };

#pragma pack(pop)

    const Material s_defMaterial =
    {
        { 0.2f, 0.2f, 0.2f, 1.f },
        { 0.8f, 0.8f, 0.8f, 1.f },
        { 0.0f, 0.0f, 0.0f, 1.f },
        1.f,
        { 0.0f, 0.0f, 0.0f, 1.0f },
        { 1.f, 0.f, 0.f, 0.f,
          0.f, 1.f, 0.f, 0.f,
          0.f, 0.f, 1.f, 0.f,
          0.f, 0.f, 0.f, 1.f },
    };
} // namespace

static_assert(sizeof(VSD3DStarter::Material) == 132, "CMO Mesh structure size incorrect");
static_assert(sizeof(VSD3DStarter::SubMesh) == 20, "CMO Mesh structure size incorrect");
static_assert(sizeof(VSD3DStarter::SkinningVertex) == 32, "CMO Mesh structure size incorrect");
static_assert(sizeof(VSD3DStarter::MeshExtents) == 40, "CMO Mesh structure size incorrect");
static_assert(sizeof(VSD3DStarter::Bone) == 196, "CMO Mesh structure size incorrect");
static_assert(sizeof(VSD3DStarter::Clip) == 12, "CMO Mesh structure size incorrect");
static_assert(sizeof(VSD3DStarter::Keyframe) == 72, "CMO Mesh structure size incorrect");

namespace
{
    //----------------------------------------------------------------------------------
    struct MaterialRecordCMO
    {
        const VSD3DStarter::Material*   pMaterial;
        std::wstring                    name;
        std::wstring                    pixelShader;
        std::wstring                    texture[VSD3DStarter::MAX_TEXTURE];
        std::shared_ptr<IEffect>        effect;
        ComPtr<ID3D11InputLayout>       il;

        MaterialRecordCMO() noexcept :
            pMaterial(nullptr),
            texture{} {}
    };

    // Helper for creating a D3D input layout.
    void CreateCMOInputLayout(_In_ ID3D11Device* device, _In_ IEffect* effect, _Outptr_ ID3D11InputLayout** pInputLayout, bool skinning)
    {
        if (skinning)
        {
            ThrowIfFailed(
                CreateInputLayoutFromEffect<VertexPositionNormalTangentColorTextureSkinning>(device, effect, pInputLayout)
            );
        }
        else
        {
            ThrowIfFailed(
                CreateInputLayoutFromEffect<VertexPositionNormalTangentColorTexture>(device, effect, pInputLayout)
            );
        }

        assert(pInputLayout != nullptr && *pInputLayout != nullptr);
        _Analysis_assume_(pInputLayout != nullptr && *pInputLayout != nullptr);

        SetDebugObjectName(*pInputLayout, "ModelCMO");
    }

    // Shared VB input element description
    INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT;
    std::shared_ptr<std::vector<D3D11_INPUT_ELEMENT_DESC>> g_vbdecl;
    std::shared_ptr<std::vector<D3D11_INPUT_ELEMENT_DESC>> g_vbdeclSkinning;

    BOOL CALLBACK InitializeDecl(PINIT_ONCE initOnce, PVOID Parameter, PVOID *lpContext)
    {
        UNREFERENCED_PARAMETER(initOnce);
        UNREFERENCED_PARAMETER(Parameter);
        UNREFERENCED_PARAMETER(lpContext);

        g_vbdecl = std::make_shared<std::vector<D3D11_INPUT_ELEMENT_DESC>>(
            VertexPositionNormalTangentColorTexture::InputElements,
            VertexPositionNormalTangentColorTexture::InputElements + VertexPositionNormalTangentColorTexture::InputElementCount);

        g_vbdeclSkinning = std::make_shared<std::vector<D3D11_INPUT_ELEMENT_DESC>>(
            VertexPositionNormalTangentColorTextureSkinning::InputElements,
            VertexPositionNormalTangentColorTextureSkinning::InputElements + VertexPositionNormalTangentColorTextureSkinning::InputElementCount);
        return TRUE;
    }

    inline XMFLOAT3 GetMaterialColor(float r, float g, float b, bool srgb)
    {
        if (srgb)
        {
            XMVECTOR v = XMVectorSet(r, g, b, 1.f);
            v = XMColorSRGBToRGB(v);

            XMFLOAT3 result;
            XMStoreFloat3(&result, v);
            return result;
        }
        else
        {
            return XMFLOAT3(r, g, b);
        }
    }
}


//======================================================================================
// Model Loader
//======================================================================================

_Use_decl_annotations_
std::unique_ptr<Model> DirectX::Model::CreateFromCMO(
    ID3D11Device* device,
    const uint8_t* meshData, size_t dataSize,
    IEffectFactory& fxFactory,
    ModelLoaderFlags flags)
{
    if (!InitOnceExecuteOnce(&g_InitOnce, InitializeDecl, nullptr, nullptr))
        throw std::exception("One-time initialization failed");

    if (!device || !meshData)
        throw std::exception("Device and meshData cannot be null");

    auto fxFactoryDGSL = dynamic_cast<DGSLEffectFactory*>(&fxFactory);

    // Meshes
    auto nMesh = reinterpret_cast<const UINT*>(meshData);
    size_t usedSize = sizeof(UINT);
    if (dataSize < usedSize)
        throw std::exception("End of file");

    if (!*nMesh)
        throw std::exception("No meshes found");

    auto model = std::make_unique<Model>();

    for (UINT meshIndex = 0; meshIndex < *nMesh; ++meshIndex)
    {
        // Mesh name
        auto nName = reinterpret_cast<const UINT*>(meshData + usedSize);
        usedSize += sizeof(UINT);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        auto meshName = reinterpret_cast<const wchar_t*>(meshData + usedSize);

        usedSize += sizeof(wchar_t)*(*nName);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        auto mesh = std::make_shared<ModelMesh>();
        mesh->name.assign(meshName, *nName);
        mesh->ccw = (flags & ModelLoader_CounterClockwise) != 0;
        mesh->pmalpha = (flags & ModelLoader_PremultipledAlpha) != 0;

        // Materials
        auto nMats = reinterpret_cast<const UINT*>(meshData + usedSize);
        usedSize += sizeof(UINT);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        std::vector<MaterialRecordCMO> materials;
        materials.reserve(*nMats);
        for (UINT j = 0; j < *nMats; ++j)
        {
            MaterialRecordCMO m;

            // Material name
            nName = reinterpret_cast<const UINT*>(meshData + usedSize);
            usedSize += sizeof(UINT);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            auto matName = reinterpret_cast<const wchar_t*>(meshData + usedSize);

            usedSize += sizeof(wchar_t)*(*nName);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            m.name.assign(matName, *nName);

            // Material settings
            auto matSetting = reinterpret_cast<const VSD3DStarter::Material*>(meshData + usedSize);
            usedSize += sizeof(VSD3DStarter::Material);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            m.pMaterial = matSetting;

            // Pixel shader name
            nName = reinterpret_cast<const UINT*>(meshData + usedSize);
            usedSize += sizeof(UINT);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            auto psName = reinterpret_cast<const wchar_t*>(meshData + usedSize);

            usedSize += sizeof(wchar_t)*(*nName);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            m.pixelShader.assign(psName, *nName);

            for (UINT t = 0; t < VSD3DStarter::MAX_TEXTURE; ++t)
            {
                nName = reinterpret_cast<const UINT*>(meshData + usedSize);
                usedSize += sizeof(UINT);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                auto txtName = reinterpret_cast<const wchar_t*>(meshData + usedSize);

                usedSize += sizeof(wchar_t)*(*nName);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                m.texture[t].assign(txtName, *nName);
            }

            materials.emplace_back(m);
        }

        assert(materials.size() == *nMats);

        if (materials.empty())
        {
            // Add default material if none defined
            MaterialRecordCMO m;
            m.pMaterial = &VSD3DStarter::s_defMaterial;
            m.name = L"Default";
            materials.emplace_back(m);
        }

        // Skeletal data?
        const BYTE* bSkeleton = meshData + usedSize;
        usedSize += sizeof(BYTE);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        // Submeshes
        auto nSubmesh = reinterpret_cast<const UINT*>(meshData + usedSize);
        usedSize += sizeof(UINT);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        if (!*nSubmesh)
            throw std::exception("No submeshes found\n");

        auto subMesh = reinterpret_cast<const VSD3DStarter::SubMesh*>(meshData + usedSize);
        usedSize += sizeof(VSD3DStarter::SubMesh) * (*nSubmesh);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        // Index buffers
        auto nIBs = reinterpret_cast<const UINT*>(meshData + usedSize);
        usedSize += sizeof(UINT);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        if (!*nIBs)
            throw std::exception("No index buffers found\n");

        struct IBData
        {
            size_t          nIndices;
            const USHORT*   ptr;
        };

        std::vector<IBData> ibData;
        ibData.reserve(*nIBs);

        std::vector<ComPtr<ID3D11Buffer>> ibs;
        ibs.resize(*nIBs);

        for (UINT j = 0; j < *nIBs; ++j)
        {
            auto nIndexes = reinterpret_cast<const UINT*>(meshData + usedSize);
            usedSize += sizeof(UINT);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            if (!*nIndexes)
                throw std::exception("Empty index buffer found\n");

            uint64_t sizeInBytes = uint64_t(*(nIndexes)) * sizeof(USHORT);

            if (sizeInBytes > UINT32_MAX)
                throw std::exception("IB too large");

            if (!(flags & ModelLoader_AllowLargeModels))
            {
                if (sizeInBytes > (D3D11_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u))
                    throw std::exception("IB too large for DirectX 11");
            }

            auto ibBytes = static_cast<size_t>(sizeInBytes);

            auto indexes = reinterpret_cast<const USHORT*>(meshData + usedSize);
            usedSize += ibBytes;
            if (dataSize < usedSize)
                throw std::exception("End of file");

            IBData ib;
            ib.nIndices = *nIndexes;
            ib.ptr = indexes;
            ibData.emplace_back(ib);

            D3D11_BUFFER_DESC desc = {};
            desc.Usage = D3D11_USAGE_DEFAULT;
            desc.ByteWidth = static_cast<UINT>(ibBytes);
            desc.BindFlags = D3D11_BIND_INDEX_BUFFER;

            D3D11_SUBRESOURCE_DATA initData = { indexes, 0, 0 };

            ThrowIfFailed(
                device->CreateBuffer(&desc, &initData, &ibs[j])
            );

            SetDebugObjectName(ibs[j].Get(), "ModelCMO");
        }

        assert(ibData.size() == *nIBs);
        assert(ibs.size() == *nIBs);

        // Vertex buffers
        auto nVBs = reinterpret_cast<const UINT*>(meshData + usedSize);
        usedSize += sizeof(UINT);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        if (!*nVBs)
            throw std::exception("No vertex buffers found\n");

        struct VBData
        {
            size_t                                          nVerts;
            const VertexPositionNormalTangentColorTexture*  ptr;
            const VSD3DStarter::SkinningVertex*             skinPtr;
        };

        std::vector<VBData> vbData;
        vbData.reserve(*nVBs);
        for (UINT j = 0; j < *nVBs; ++j)
        {
            auto nVerts = reinterpret_cast<const UINT*>(meshData + usedSize);
            usedSize += sizeof(UINT);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            if (!*nVerts)
                throw std::exception("Empty vertex buffer found\n");

            size_t vbBytes = sizeof(VertexPositionNormalTangentColorTexture) * (*(nVerts));

            auto verts = reinterpret_cast<const VertexPositionNormalTangentColorTexture*>(meshData + usedSize);
            usedSize += vbBytes;
            if (dataSize < usedSize)
                throw std::exception("End of file");

            VBData vb;
            vb.nVerts = *nVerts;
            vb.ptr = verts;
            vb.skinPtr = nullptr;
            vbData.emplace_back(vb);
        }

        assert(vbData.size() == *nVBs);

        // Skinning vertex buffers
        auto nSkinVBs = reinterpret_cast<const UINT*>(meshData + usedSize);
        usedSize += sizeof(UINT);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        if (*nSkinVBs)
        {
            if (*nSkinVBs != *nVBs)
                throw std::exception("Number of VBs not equal to number of skin VBs");

            for (UINT j = 0; j < *nSkinVBs; ++j)
            {
                auto nVerts = reinterpret_cast<const UINT*>(meshData + usedSize);
                usedSize += sizeof(UINT);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                if (!*nVerts)
                    throw std::exception("Empty skinning vertex buffer found\n");

                if (vbData[j].nVerts != *nVerts)
                    throw std::exception("Mismatched number of verts for skin VBs");

                size_t vbBytes = sizeof(VSD3DStarter::SkinningVertex) * (*(nVerts));

                auto verts = reinterpret_cast<const VSD3DStarter::SkinningVertex*>(meshData + usedSize);
                usedSize += vbBytes;
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                vbData[j].skinPtr = verts;
            }
        }

        // Extents
        auto extents = reinterpret_cast<const VSD3DStarter::MeshExtents*>(meshData + usedSize);
        usedSize += sizeof(VSD3DStarter::MeshExtents);
        if (dataSize < usedSize)
            throw std::exception("End of file");

        mesh->boundingSphere.Center.x = extents->CenterX;
        mesh->boundingSphere.Center.y = extents->CenterY;
        mesh->boundingSphere.Center.z = extents->CenterZ;
        mesh->boundingSphere.Radius = extents->Radius;

        XMVECTOR min = XMVectorSet(extents->MinX, extents->MinY, extents->MinZ, 0.f);
        XMVECTOR max = XMVectorSet(extents->MaxX, extents->MaxY, extents->MaxZ, 0.f);
        BoundingBox::CreateFromPoints(mesh->boundingBox, min, max);

    #if 0
            // Animation data
        if (*bSkeleton)
        {
            // Bones
            auto nBones = reinterpret_cast<const UINT*>(meshData + usedSize);
            usedSize += sizeof(UINT);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            if (!*nBones)
                throw std::exception("Animation bone data is missing\n");

            for (UINT j = 0; j < *nBones; ++j)
            {
                // Bone name
                nName = reinterpret_cast<const UINT*>(meshData + usedSize);
                usedSize += sizeof(UINT);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                auto boneName = reinterpret_cast<const wchar_t*>(meshData + usedSize);

                usedSize += sizeof(wchar_t)*(*nName);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                // TODO - What to do with bone name?
                boneName;

                // Bone settings
                auto bones = reinterpret_cast<const VSD3DStarter::Bone*>(meshData + usedSize);
                usedSize += sizeof(VSD3DStarter::Bone);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                // TODO - What to do with bone data?
                bones;
            }

            // Animation Clips
            auto nClips = reinterpret_cast<const UINT*>(meshData + usedSize);
            usedSize += sizeof(UINT);
            if (dataSize < usedSize)
                throw std::exception("End of file");

            for (UINT j = 0; j < *nClips; ++j)
            {
                // Clip name
                nName = reinterpret_cast<const UINT*>(meshData + usedSize);
                usedSize += sizeof(UINT);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                auto clipName = reinterpret_cast<const wchar_t*>(meshData + usedSize);

                usedSize += sizeof(wchar_t)*(*nName);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                // TODO - What to do with clip name?
                clipName;

                auto clip = reinterpret_cast<const VSD3DStarter::Clip*>(meshData + usedSize);
                usedSize += sizeof(VSD3DStarter::Clip);
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                if (!clip->keys)
                    throw std::exception("Keyframes missing in clip");

                auto keys = reinterpret_cast<const VSD3DStarter::Keyframe*>(meshData + usedSize);
                usedSize += sizeof(VSD3DStarter::Keyframe) * clip->keys;
                if (dataSize < usedSize)
                    throw std::exception("End of file");

                // TODO - What to do with keys and clip->StartTime, clip->EndTime?
                keys;
            }
        }
    #else
        UNREFERENCED_PARAMETER(bSkeleton);
    #endif

        bool enableSkinning = (*nSkinVBs) != 0;

        // Build vertex buffers
        std::vector<ComPtr<ID3D11Buffer>> vbs;
        vbs.resize(*nVBs);

        const size_t stride = enableSkinning ? sizeof(VertexPositionNormalTangentColorTextureSkinning)
            : sizeof(VertexPositionNormalTangentColorTexture);

        for (UINT j = 0; j < *nVBs; ++j)
        {
            size_t nVerts = vbData[j].nVerts;

            uint64_t sizeInBytes = uint64_t(stride) * uint64_t(nVerts);

            if (sizeInBytes > UINT32_MAX)
                throw std::exception("VB too large");

            if (!(flags & ModelLoader_AllowLargeModels))
            {
                if (sizeInBytes > uint64_t(D3D11_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u))
                    throw std::exception("VB too large for DirectX 11");
            }

            size_t bytes = static_cast<size_t>(sizeInBytes);

            D3D11_BUFFER_DESC desc = {};
            desc.Usage = D3D11_USAGE_DEFAULT;
            desc.ByteWidth = static_cast<UINT>(bytes);
            desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

            if (fxFactoryDGSL && !enableSkinning)
            {
                // Can use CMO vertex data directly
                D3D11_SUBRESOURCE_DATA initData = { vbData[j].ptr, 0, 0 };

                ThrowIfFailed(
                    device->CreateBuffer(&desc, &initData, &vbs[j])
                );
            }
            else
            {
                auto temp = std::make_unique<uint8_t[]>(bytes + (sizeof(UINT) * nVerts));

                auto visited = reinterpret_cast<UINT*>(temp.get() + bytes);
                memset(visited, 0xff, sizeof(UINT) * nVerts);

                assert(vbData[j].ptr != nullptr);

                if (enableSkinning)
                {
                    // Combine CMO multi-stream data into a single stream
                    auto skinptr = vbData[j].skinPtr;
                    assert(skinptr != nullptr);

                    uint8_t* ptr = temp.get();

                    auto sptr = vbData[j].ptr;

                    for (size_t v = 0; v < nVerts; ++v)
                    {
                        *reinterpret_cast<VertexPositionNormalTangentColorTexture*>(ptr) = sptr[v];

                        auto skinv = reinterpret_cast<VertexPositionNormalTangentColorTextureSkinning*>(ptr);
                        skinv->SetBlendIndices(*reinterpret_cast<const XMUINT4*>(skinptr[v].boneIndex));
                        skinv->SetBlendWeights(*reinterpret_cast<const XMFLOAT4*>(skinptr[v].boneWeight));

                        ptr += stride;
                    }
                }
                else
                {
                    memcpy(temp.get(), vbData[j].ptr, bytes);
                }

                if (!fxFactoryDGSL)
                {
                    // Need to fix up VB tex coords for UV transform which is not supported by basic effects
                    for (UINT k = 0; k < *nSubmesh; ++k)
                    {
                        auto& sm = subMesh[k];

                        if (sm.VertexBufferIndex != j)
                            continue;

                        if ((sm.IndexBufferIndex >= *nIBs)
                            || (sm.MaterialIndex >= materials.size()))
                            throw std::exception("Invalid submesh found\n");

                        XMMATRIX uvTransform = XMLoadFloat4x4(&materials[sm.MaterialIndex].pMaterial->UVTransform);

                        auto ib = ibData[sm.IndexBufferIndex].ptr;

                        size_t count = ibData[sm.IndexBufferIndex].nIndices;

                        for (size_t q = 0; q < count; ++q)
                        {
                            size_t v = ib[q];

                            if (v >= nVerts)
                                throw std::exception("Invalid index found\n");

                            auto verts = reinterpret_cast<VertexPositionNormalTangentColorTexture*>(temp.get() + (v * stride));
                            if (visited[v] == UINT(-1))
                            {
                                visited[v] = sm.MaterialIndex;

                                XMVECTOR t = XMLoadFloat2(&verts->textureCoordinate);

                                t = XMVectorSelect(g_XMIdentityR3, t, g_XMSelect1110);

                                t = XMVector4Transform(t, uvTransform);

                                XMStoreFloat2(&verts->textureCoordinate, t);
                            }
                            else if (visited[v] != sm.MaterialIndex)
                            {
                            #ifdef _DEBUG
                                XMMATRIX uv2 = XMLoadFloat4x4(&materials[visited[v]].pMaterial->UVTransform);

                                if (XMVector4NotEqual(uvTransform.r[0], uv2.r[0])
                                    || XMVector4NotEqual(uvTransform.r[1], uv2.r[1])
                                    || XMVector4NotEqual(uvTransform.r[2], uv2.r[2])
                                    || XMVector4NotEqual(uvTransform.r[3], uv2.r[3]))
                                {
                                    DebugTrace("WARNING: %ls - mismatched UV transforms for the same vertex; texture coordinates may not be correct\n", mesh->name.c_str());
                                }
                            #endif
                            }
                        }
                    }
                }

                // Create vertex buffer from temporary buffer
                D3D11_SUBRESOURCE_DATA initData = { temp.get(), 0, 0 };

                ThrowIfFailed(
                    device->CreateBuffer(&desc, &initData, &vbs[j])
                );
            }

            SetDebugObjectName(vbs[j].Get(), "ModelCMO");
        }

        assert(vbs.size() == *nVBs);

        // Create Effects
        bool srgb = (flags & ModelLoader_MaterialColorsSRGB) != 0;

        for (size_t j = 0; j < materials.size(); ++j)
        {
            auto& m = materials[j];

            if (fxFactoryDGSL)
            {
                DGSLEffectFactory::DGSLEffectInfo info;
                info.name = m.name.c_str();
                info.specularPower = m.pMaterial->SpecularPower;
                info.perVertexColor = true;
                info.enableSkinning = enableSkinning;
                info.alpha = m.pMaterial->Diffuse.w;
                info.ambientColor = GetMaterialColor(m.pMaterial->Ambient.x, m.pMaterial->Ambient.y, m.pMaterial->Ambient.z, srgb);
                info.diffuseColor = GetMaterialColor(m.pMaterial->Diffuse.x, m.pMaterial->Diffuse.y, m.pMaterial->Diffuse.z, srgb);
                info.specularColor = GetMaterialColor(m.pMaterial->Specular.x, m.pMaterial->Specular.y, m.pMaterial->Specular.z, srgb);
                info.emissiveColor = GetMaterialColor(m.pMaterial->Emissive.x, m.pMaterial->Emissive.y, m.pMaterial->Emissive.z, srgb);
                info.diffuseTexture = m.texture[0].empty() ? nullptr : m.texture[0].c_str();
                info.specularTexture = m.texture[1].empty() ? nullptr : m.texture[1].c_str();
                info.normalTexture = m.texture[2].empty() ? nullptr : m.texture[2].c_str();
                info.emissiveTexture = m.texture[3].empty() ? nullptr : m.texture[3].c_str();
                info.pixelShader = m.pixelShader.c_str();

                constexpr int offset = DGSLEffectFactory::DGSLEffectInfo::BaseTextureOffset;
                for (int i = 0; i < (DGSLEffect::MaxTextures - offset); ++i)
                {
                    info.textures[i] = m.texture[i + offset].empty() ? nullptr : m.texture[i + offset].c_str();
                }

                m.effect = fxFactoryDGSL->CreateDGSLEffect(info, nullptr);

                auto dgslEffect = static_cast<DGSLEffect*>(m.effect.get());
                dgslEffect->SetUVTransform(XMLoadFloat4x4(&m.pMaterial->UVTransform));
            }
            else
            {
                EffectFactory::EffectInfo info;
                info.name = m.name.c_str();
                info.specularPower = m.pMaterial->SpecularPower;
                info.perVertexColor = true;
                info.enableSkinning = enableSkinning;
                info.alpha = m.pMaterial->Diffuse.w;
                info.ambientColor = GetMaterialColor(m.pMaterial->Ambient.x, m.pMaterial->Ambient.y, m.pMaterial->Ambient.z, srgb);
                info.diffuseColor = GetMaterialColor(m.pMaterial->Diffuse.x, m.pMaterial->Diffuse.y, m.pMaterial->Diffuse.z, srgb);
                info.specularColor = GetMaterialColor(m.pMaterial->Specular.x, m.pMaterial->Specular.y, m.pMaterial->Specular.z, srgb);
                info.emissiveColor = GetMaterialColor(m.pMaterial->Emissive.x, m.pMaterial->Emissive.y, m.pMaterial->Emissive.z, srgb);
                info.diffuseTexture = m.texture[0].c_str();

                m.effect = fxFactory.CreateEffect(info, nullptr);
            }

            CreateCMOInputLayout(device, m.effect.get(), &m.il, enableSkinning);
        }

        // Build mesh parts
        for (UINT j = 0; j < *nSubmesh; ++j)
        {
            auto& sm = subMesh[j];

            if ((sm.IndexBufferIndex >= *nIBs)
                || (sm.VertexBufferIndex >= *nVBs)
                || (sm.MaterialIndex >= materials.size()))
                throw std::exception("Invalid submesh found\n");

            auto& mat = materials[sm.MaterialIndex];

            auto part = new ModelMeshPart();

            if (mat.pMaterial->Diffuse.w < 1)
                part->isAlpha = true;

            part->indexCount = sm.PrimCount * 3;
            part->startIndex = sm.StartIndex;
            part->vertexStride = static_cast<UINT>(stride);
            part->inputLayout = mat.il;
            part->indexBuffer = ibs[sm.IndexBufferIndex];
            part->vertexBuffer = vbs[sm.VertexBufferIndex];
            part->effect = mat.effect;
            part->vbDecl = enableSkinning ? g_vbdeclSkinning : g_vbdecl;

            mesh->meshParts.emplace_back(part);
        }

        model->meshes.emplace_back(mesh);
    }

    return model;
}


//--------------------------------------------------------------------------------------
_Use_decl_annotations_
std::unique_ptr<Model> DirectX::Model::CreateFromCMO(
    ID3D11Device* device,
    const wchar_t* szFileName,
    IEffectFactory& fxFactory,
    ModelLoaderFlags flags)
{
    size_t dataSize = 0;
    std::unique_ptr<uint8_t[]> data;
    HRESULT hr = BinaryReader::ReadEntireFile(szFileName, data, &dataSize);
    if (FAILED(hr))
    {
        DebugTrace("ERROR: CreateFromCMO failed (%08X) loading '%ls'\n",
            static_cast<unsigned int>(hr), szFileName);
        throw std::exception("CreateFromCMO");
    }

    auto model = CreateFromCMO(device, data.get(), dataSize, fxFactory, flags);

    model->name = szFileName;

    return model;
}
