UVAtlas/UVAtlasTool/Mesh.cpp

2688 lines
76 KiB
C++

//--------------------------------------------------------------------------------------
// File: Mesh.cpp
//
// Mesh processing helper class
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkID=324981
// http://go.microsoft.com/fwlink/?LinkID=512686
//--------------------------------------------------------------------------------------
#pragma warning(push)
#pragma warning(disable : 4005)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define NODRAWTEXT
#define NOGDI
#define NOMCX
#define NOSERVICE
#define NOHELP
#pragma warning(pop)
#include "Mesh.h"
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <iterator>
#include <new>
#include <utility>
#include "SDKMesh.h"
#include <DirectXPackedVector.h>
#include <DirectXCollision.h>
#include <UVAtlas.h>
using namespace DirectX;
namespace
{
struct handle_closer { void operator()(HANDLE h) noexcept { if (h) CloseHandle(h); } };
using ScopedHandle = std::unique_ptr<void, handle_closer>;
inline HANDLE safe_handle(HANDLE h) noexcept { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; }
template<typename T> inline HRESULT write_file(HANDLE hFile, const T& value)
{
DWORD bytesWritten;
if (!WriteFile(hFile, &value, static_cast<DWORD>(sizeof(T)), &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != sizeof(T))
return E_FAIL;
return S_OK;
}
inline HRESULT write_file_string(HANDLE hFile, const wchar_t* value)
{
const UINT length = (value) ? static_cast<UINT>(wcslen(value) + 1) : 1;
DWORD bytesWritten;
if (!WriteFile(hFile, &length, static_cast<DWORD>(sizeof(UINT)), &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != sizeof(UINT))
return E_FAIL;
if (length > 0)
{
auto const bytes = static_cast<DWORD>(sizeof(wchar_t) * length);
if (!WriteFile(hFile, value, bytes, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytes)
return E_FAIL;
}
else
{
constexpr wchar_t nul = 0;
if (!WriteFile(hFile, &nul, sizeof(wchar_t), &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != sizeof(wchar_t))
return E_FAIL;
}
return S_OK;
}
constexpr UINT64 roundup4k(UINT64 value)
{
return ((value + 4095) / 4096) * 4096;
}
static const uint8_t g_padding[4096] = {};
}
// Move constructor
Mesh::Mesh(Mesh&& moveFrom) noexcept : mnFaces(0), mnVerts(0)
{
*this = std::move(moveFrom);
}
// Move operator
Mesh& Mesh::operator= (Mesh&& moveFrom) noexcept
{
if (this != &moveFrom)
{
mnFaces = moveFrom.mnFaces;
mnVerts = moveFrom.mnVerts;
mIndices.swap(moveFrom.mIndices);
mAttributes.swap(moveFrom.mAttributes);
mAdjacency.swap(moveFrom.mAdjacency);
mPositions.swap(moveFrom.mPositions);
mNormals.swap(moveFrom.mNormals);
mTangents.swap(moveFrom.mTangents);
mBiTangents.swap(moveFrom.mBiTangents);
mTexCoords.swap(moveFrom.mTexCoords);
mTexCoords2.swap(moveFrom.mTexCoords2);
mColors.swap(moveFrom.mColors);
mBlendIndices.swap(moveFrom.mBlendIndices);
mBlendWeights.swap(moveFrom.mBlendWeights);
}
return *this;
}
//--------------------------------------------------------------------------------------
void Mesh::Clear() noexcept
{
mnFaces = mnVerts = 0;
// Release face data
mIndices.reset();
mAttributes.reset();
mAdjacency.reset();
// Release vertex data
mPositions.reset();
mNormals.reset();
mTangents.reset();
mBiTangents.reset();
mTexCoords.reset();
mTexCoords2.reset();
mColors.reset();
mBlendIndices.reset();
mBlendWeights.reset();
}
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::SetIndexData(size_t nFaces, const uint16_t* indices, const uint32_t* attributes) noexcept
{
if (!nFaces || !indices)
return E_INVALIDARG;
if ((uint64_t(nFaces) * 3) >= UINT32_MAX)
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
// Release face data
mnFaces = 0;
mIndices.reset();
mAttributes.reset();
std::unique_ptr<uint32_t[]> ib(new (std::nothrow) uint32_t[nFaces * 3]);
if (!ib)
return E_OUTOFMEMORY;
for (size_t j = 0; j < (nFaces * 3); ++j)
{
if (indices[j] == uint16_t(-1))
{
ib[j] = uint32_t(-1);
}
else
{
ib[j] = indices[j];
}
}
std::unique_ptr<uint32_t[]> attr;
if (attributes)
{
attr.reset(new (std::nothrow) uint32_t[nFaces]);
if (!attr)
return E_OUTOFMEMORY;
memcpy(attr.get(), attributes, sizeof(uint32_t) * nFaces);
}
mIndices.swap(ib);
mAttributes.swap(attr);
mnFaces = nFaces;
return S_OK;
}
_Use_decl_annotations_
HRESULT Mesh::SetIndexData(size_t nFaces, const uint32_t* indices, const uint32_t* attributes) noexcept
{
if (!nFaces || !indices)
return E_INVALIDARG;
if ((uint64_t(nFaces) * 3) >= UINT32_MAX)
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
mnFaces = 0;
mIndices.reset();
mAttributes.reset();
std::unique_ptr<uint32_t[]> ib(new (std::nothrow) uint32_t[nFaces * 3]);
if (!ib)
return E_OUTOFMEMORY;
memcpy(ib.get(), indices, sizeof(uint32_t) * nFaces * 3);
std::unique_ptr<uint32_t[]> attr;
if (attributes)
{
attr.reset(new (std::nothrow) uint32_t[nFaces]);
if (!attr)
return E_OUTOFMEMORY;
memcpy(attr.get(), attributes, sizeof(uint32_t) * nFaces);
}
mIndices.swap(ib);
mAttributes.swap(attr);
mnFaces = nFaces;
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::SetVertexData(const DirectX::VBReader& reader, _In_ size_t nVerts) noexcept
{
if (!nVerts)
return E_INVALIDARG;
// Release vertex data
mnVerts = 0;
mPositions.reset();
mNormals.reset();
mTangents.reset();
mBiTangents.reset();
mTexCoords.reset();
mTexCoords2.reset();
mColors.reset();
mBlendIndices.reset();
mBlendWeights.reset();
// Load positions (required)
std::unique_ptr<XMFLOAT3[]> pos(new (std::nothrow) XMFLOAT3[nVerts]);
if (!pos)
return E_OUTOFMEMORY;
HRESULT hr = reader.Read(pos.get(), "SV_Position", 0, nVerts);
if (FAILED(hr))
return hr;
// Load normals
std::unique_ptr<XMFLOAT3[]> norms;
auto e = reader.GetElement11("NORMAL", 0);
if (e)
{
norms.reset(new (std::nothrow) XMFLOAT3[nVerts]);
if (!norms)
return E_OUTOFMEMORY;
hr = reader.Read(norms.get(), "NORMAL", 0, nVerts);
if (FAILED(hr))
return hr;
}
// Load tangents
std::unique_ptr<XMFLOAT4[]> tans1;
e = reader.GetElement11("TANGENT", 0);
if (e)
{
tans1.reset(new (std::nothrow) XMFLOAT4[nVerts]);
if (!tans1)
return E_OUTOFMEMORY;
hr = reader.Read(tans1.get(), "TANGENT", 0, nVerts);
if (FAILED(hr))
return hr;
}
// Load bi-tangents
std::unique_ptr<XMFLOAT3[]> tans2;
e = reader.GetElement11("BINORMAL", 0);
if (e)
{
tans2.reset(new (std::nothrow) XMFLOAT3[nVerts]);
if (!tans2)
return E_OUTOFMEMORY;
hr = reader.Read(tans2.get(), "BINORMAL", 0, nVerts);
if (FAILED(hr))
return hr;
}
// Load texture coordinates
std::unique_ptr<XMFLOAT2[]> texcoord;
e = reader.GetElement11("TEXCOORD", 0);
if (e)
{
texcoord.reset(new (std::nothrow) XMFLOAT2[nVerts]);
if (!texcoord)
return E_OUTOFMEMORY;
hr = reader.Read(texcoord.get(), "TEXCOORD", 0, nVerts);
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT2[]> texcoord2;
e = reader.GetElement11("TEXCOORD", 1);
if (e)
{
texcoord2.reset(new (std::nothrow) XMFLOAT2[nVerts]);
if (!texcoord2)
return E_OUTOFMEMORY;
hr = reader.Read(texcoord2.get(), "TEXCOORD", 1, nVerts);
if (FAILED(hr))
return hr;
}
// Load vertex colors
std::unique_ptr<XMFLOAT4[]> colors;
e = reader.GetElement11("COLOR", 0);
if (e)
{
colors.reset(new (std::nothrow) XMFLOAT4[nVerts]);
if (!colors)
return E_OUTOFMEMORY;
hr = reader.Read(colors.get(), "COLOR", 0, nVerts);
if (FAILED(hr))
return hr;
}
// Load skinning bone indices
std::unique_ptr<XMFLOAT4[]> blendIndices;
e = reader.GetElement11("BLENDINDICES", 0);
if (e)
{
blendIndices.reset(new (std::nothrow) XMFLOAT4[nVerts]);
if (!blendIndices)
return E_OUTOFMEMORY;
hr = reader.Read(blendIndices.get(), "BLENDINDICES", 0, nVerts);
if (FAILED(hr))
return hr;
}
// Load skinning bone weights
std::unique_ptr<XMFLOAT4[]> blendWeights;
e = reader.GetElement11("BLENDWEIGHT", 0);
if (e)
{
blendWeights.reset(new (std::nothrow) XMFLOAT4[nVerts]);
if (!blendWeights)
return E_OUTOFMEMORY;
hr = reader.Read(blendWeights.get(), "BLENDWEIGHT", 0, nVerts);
if (FAILED(hr))
return hr;
}
// Return values
mPositions.swap(pos);
mNormals.swap(norms);
mTangents.swap(tans1);
mBiTangents.swap(tans2);
mTexCoords.swap(texcoord);
mTexCoords2.swap(texcoord2);
mColors.swap(colors);
mBlendIndices.swap(blendIndices);
mBlendWeights.swap(blendWeights);
mnVerts = nVerts;
return S_OK;
}
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::Validate(DirectX::VALIDATE_FLAGS flags, std::wstring* msgs) const noexcept
{
if (!mnFaces || !mIndices || !mnVerts)
return E_UNEXPECTED;
return DirectX::Validate(mIndices.get(), mnFaces, mnVerts, mAdjacency.get(), flags, msgs);
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::Clean(std::vector<uint32_t>& dups, _In_ bool breakBowties) noexcept
{
if (!mnFaces || !mIndices || !mnVerts || !mPositions)
return E_UNEXPECTED;
dups.clear();
HRESULT hr = DirectX::Clean(mIndices.get(), mnFaces, mnVerts, mAdjacency.get(), mAttributes.get(), dups, breakBowties);
if (FAILED(hr))
return hr;
if (dups.empty())
{
// No vertex duplication is needed for mesh clean
return S_OK;
}
const size_t nNewVerts = mnVerts + dups.size();
std::unique_ptr<XMFLOAT3[]> pos(new (std::nothrow) XMFLOAT3[nNewVerts]);
if (!pos)
return E_OUTOFMEMORY;
memcpy(pos.get(), mPositions.get(), sizeof(XMFLOAT3) * mnVerts);
std::unique_ptr<XMFLOAT3[]> norms;
if (mNormals)
{
norms.reset(new (std::nothrow) XMFLOAT3[nNewVerts]);
if (!norms)
return E_OUTOFMEMORY;
memcpy(norms.get(), mNormals.get(), sizeof(XMFLOAT3) * mnVerts);
}
std::unique_ptr<XMFLOAT4[]> tans1;
if (mTangents)
{
tans1.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!tans1)
return E_OUTOFMEMORY;
memcpy(tans1.get(), mTangents.get(), sizeof(XMFLOAT4) * mnVerts);
}
std::unique_ptr<XMFLOAT3[]> tans2;
if (mBiTangents)
{
tans2.reset(new (std::nothrow) XMFLOAT3[nNewVerts]);
if (!tans2)
return E_OUTOFMEMORY;
memcpy(tans2.get(), mBiTangents.get(), sizeof(XMFLOAT3) * mnVerts);
}
std::unique_ptr<XMFLOAT2[]> texcoord;
if (mTexCoords)
{
texcoord.reset(new (std::nothrow) XMFLOAT2[nNewVerts]);
if (!texcoord)
return E_OUTOFMEMORY;
memcpy(texcoord.get(), mTexCoords.get(), sizeof(XMFLOAT2) * mnVerts);
}
std::unique_ptr<XMFLOAT2[]> texcoord2;
if (mTexCoords2)
{
texcoord2.reset(new (std::nothrow) XMFLOAT2[nNewVerts]);
if (!texcoord2)
return E_OUTOFMEMORY;
memcpy(texcoord2.get(), mTexCoords2.get(), sizeof(XMFLOAT2) * mnVerts);
}
std::unique_ptr<XMFLOAT4[]> colors;
if (mColors)
{
colors.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!colors)
return E_OUTOFMEMORY;
memcpy(colors.get(), mColors.get(), sizeof(XMFLOAT4) * mnVerts);
}
std::unique_ptr<XMFLOAT4[]> blendIndices;
if (mBlendIndices)
{
blendIndices.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!blendIndices)
return E_OUTOFMEMORY;
memcpy(blendIndices.get(), mBlendIndices.get(), sizeof(XMFLOAT4) * mnVerts);
}
std::unique_ptr<XMFLOAT4[]> blendWeights;
if (mBlendWeights)
{
blendWeights.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!blendWeights)
return E_OUTOFMEMORY;
memcpy(blendWeights.get(), mBlendWeights.get(), sizeof(XMFLOAT4) * mnVerts);
}
size_t j = mnVerts;
for (auto it = dups.begin(); it != dups.end() && (j < nNewVerts); ++it, ++j)
{
assert(*it < mnVerts);
pos[j] = mPositions[*it];
if (norms)
{
norms[j] = mNormals[*it];
}
if (tans1)
{
tans1[j] = mTangents[*it];
}
if (tans2)
{
tans2[j] = mBiTangents[*it];
}
if (texcoord)
{
texcoord[j] = mTexCoords[*it];
}
if (texcoord2)
{
texcoord2[j] = mTexCoords2[*it];
}
if (colors)
{
colors[j] = mColors[*it];
}
if (blendIndices)
{
blendIndices[j] = mBlendIndices[*it];
}
if (blendWeights)
{
blendWeights[j] = mBlendWeights[*it];
}
}
mPositions.swap(pos);
mNormals.swap(norms);
mTangents.swap(tans1);
mBiTangents.swap(tans2);
mTexCoords.swap(texcoord);
mTexCoords2.swap(texcoord2);
mColors.swap(colors);
mBlendIndices.swap(blendIndices);
mBlendWeights.swap(blendWeights);
mnVerts = nNewVerts;
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::GenerateAdjacency(_In_ float epsilon) noexcept
{
if (!mnFaces || !mIndices || !mnVerts || !mPositions)
return E_UNEXPECTED;
if ((uint64_t(mnFaces) * 3) >= UINT32_MAX)
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
mAdjacency.reset(new (std::nothrow) uint32_t[mnFaces * 3]);
if (!mAdjacency)
return E_OUTOFMEMORY;
return DirectX::GenerateAdjacencyAndPointReps(mIndices.get(), mnFaces, mPositions.get(), mnVerts, epsilon, nullptr, mAdjacency.get());
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::ComputeNormals(_In_ DirectX::CNORM_FLAGS flags) noexcept
{
if (!mnFaces || !mIndices || !mnVerts || !mPositions)
return E_UNEXPECTED;
mNormals.reset(new (std::nothrow) XMFLOAT3[mnVerts]);
if (!mNormals)
return E_OUTOFMEMORY;
return DirectX::ComputeNormals(mIndices.get(), mnFaces, mPositions.get(), mnVerts, flags, mNormals.get());
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::ComputeTangentFrame(_In_ bool bitangents) noexcept
{
if (!mnFaces || !mIndices || !mnVerts || !mPositions || !mNormals || !mTexCoords)
return E_UNEXPECTED;
mTangents.reset();
mBiTangents.reset();
std::unique_ptr<XMFLOAT4[]> tan1(new (std::nothrow) XMFLOAT4[mnVerts]);
if (!tan1)
return E_OUTOFMEMORY;
std::unique_ptr<XMFLOAT3[]> tan2;
if (bitangents)
{
tan2.reset(new (std::nothrow) XMFLOAT3[mnVerts]);
if (!tan2)
return E_OUTOFMEMORY;
HRESULT hr = DirectX::ComputeTangentFrame(mIndices.get(), mnFaces, mPositions.get(), mNormals.get(), mTexCoords.get(), mnVerts,
tan1.get(), tan2.get());
if (FAILED(hr))
return hr;
}
else
{
mBiTangents.reset();
HRESULT hr = DirectX::ComputeTangentFrame(mIndices.get(), mnFaces, mPositions.get(), mNormals.get(), mTexCoords.get(), mnVerts,
tan1.get());
if (FAILED(hr))
return hr;
}
mTangents.swap(tan1);
mBiTangents.swap(tan2);
return S_OK;
}
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::UpdateFaces(size_t nFaces, const uint32_t* indices) noexcept
{
if (!nFaces || !indices)
return E_INVALIDARG;
if (!mnFaces || !mIndices)
return E_UNEXPECTED;
if (mnFaces != nFaces)
return E_FAIL;
if ((uint64_t(nFaces) * 3) >= UINT32_MAX)
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
memcpy(mIndices.get(), indices, sizeof(uint32_t) * 3 * nFaces);
return S_OK;
}
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::UpdateAttributes(size_t nFaces, const uint32_t* attributes) noexcept
{
if (!nFaces || !attributes)
return E_INVALIDARG;
if (!mnFaces || !mIndices || !mnVerts || !mPositions)
return E_UNEXPECTED;
if (mnFaces != nFaces)
return E_FAIL;
if (!mAttributes)
{
std::unique_ptr<uint32_t[]> attr(new (std::nothrow) uint32_t[nFaces]);
if (!attr)
return E_OUTOFMEMORY;
memcpy(attr.get(), attributes, sizeof(uint32_t) * nFaces);
mAttributes.swap(attr);
}
else
{
memcpy(mAttributes.get(), attributes, sizeof(uint32_t) * nFaces);
}
std::unique_ptr<uint32_t> remap(new (std::nothrow) uint32_t[mnFaces]);
if (!remap)
return E_OUTOFMEMORY;
HRESULT hr = AttributeSort(mnFaces, mAttributes.get(), remap.get());
if (FAILED(hr))
return hr;
if (mAdjacency)
{
hr = ReorderIBAndAdjacency(mIndices.get(), mnFaces, mAdjacency.get(), remap.get());
}
else
{
hr = ReorderIB(mIndices.get(), mnFaces, remap.get());
}
if (FAILED(hr))
return hr;
return S_OK;
}
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::UpdateUVs(size_t nVerts, const XMFLOAT2* uvs, bool keepOriginal) noexcept
{
if (!nVerts || !uvs)
return E_INVALIDARG;
if (!mnVerts || !mPositions)
return E_UNEXPECTED;
if (nVerts != mnVerts)
return E_FAIL;
if (keepOriginal && mTexCoords)
{
std::unique_ptr<XMFLOAT2[]> texcoord2;
texcoord2.reset(new (std::nothrow) XMFLOAT2[mnVerts]);
if (!texcoord2)
return E_OUTOFMEMORY;
memcpy(texcoord2.get(), uvs, sizeof(XMFLOAT2) * mnVerts);
mTexCoords2.swap(texcoord2);
}
else if (!mTexCoords)
{
std::unique_ptr<XMFLOAT2[]> texcoord;
texcoord.reset(new (std::nothrow) XMFLOAT2[mnVerts]);
if (!texcoord)
return E_OUTOFMEMORY;
memcpy(texcoord.get(), uvs, sizeof(XMFLOAT2) * mnVerts);
mTexCoords.swap(texcoord);
}
else
{
memcpy(mTexCoords.get(), uvs, sizeof(XMFLOAT2) * mnVerts);
}
return S_OK;
}
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::VertexRemap(const uint32_t* remap, size_t nNewVerts) noexcept
{
if (!remap || !nNewVerts)
return E_INVALIDARG;
if (!mnVerts || !mPositions)
return E_UNEXPECTED;
if (nNewVerts < mnVerts)
return E_FAIL;
std::unique_ptr<XMFLOAT3[]> pos(new (std::nothrow) XMFLOAT3[nNewVerts]);
if (!pos)
return E_OUTOFMEMORY;
HRESULT hr = UVAtlasApplyRemap(mPositions.get(), sizeof(XMFLOAT3), mnVerts, nNewVerts, remap, pos.get());
if (FAILED(hr))
return hr;
std::unique_ptr<XMFLOAT3[]> norms;
if (mNormals)
{
norms.reset(new (std::nothrow) XMFLOAT3[nNewVerts]);
if (!norms)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mNormals.get(), sizeof(XMFLOAT3), mnVerts, nNewVerts, remap, norms.get());
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT4[]> tans1;
if (mTangents)
{
tans1.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!tans1)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mTangents.get(), sizeof(XMFLOAT4), mnVerts, nNewVerts, remap, tans1.get());
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT3[]> tans2;
if (mBiTangents)
{
tans2.reset(new (std::nothrow) XMFLOAT3[nNewVerts]);
if (!tans2)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mBiTangents.get(), sizeof(XMFLOAT3), mnVerts, nNewVerts, remap, tans2.get());
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT2[]> texcoord;
if (mTexCoords)
{
texcoord.reset(new (std::nothrow) XMFLOAT2[nNewVerts]);
if (!texcoord)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mTexCoords.get(), sizeof(XMFLOAT2), mnVerts, nNewVerts, remap, texcoord.get());
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT2[]> texcoord2;
if (mTexCoords2)
{
texcoord2.reset(new (std::nothrow) XMFLOAT2[nNewVerts]);
if (!texcoord2)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mTexCoords2.get(), sizeof(XMFLOAT2), mnVerts, nNewVerts, remap, texcoord2.get());
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT4[]> colors;
if (mColors)
{
colors.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!colors)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mColors.get(), sizeof(XMFLOAT4), mnVerts, nNewVerts, remap, colors.get());
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT4[]> blendIndices;
if (mBlendIndices)
{
blendIndices.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!blendIndices)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mBlendIndices.get(), sizeof(XMFLOAT4), mnVerts, nNewVerts, remap, blendIndices.get());
if (FAILED(hr))
return hr;
}
std::unique_ptr<XMFLOAT4[]> blendWeights;
if (mBlendWeights)
{
blendWeights.reset(new (std::nothrow) XMFLOAT4[nNewVerts]);
if (!blendWeights)
return E_OUTOFMEMORY;
hr = UVAtlasApplyRemap(mBlendWeights.get(), sizeof(XMFLOAT4), mnVerts, nNewVerts, remap, blendWeights.get());
if (FAILED(hr))
return hr;
}
mPositions.swap(pos);
mNormals.swap(norms);
mTangents.swap(tans1);
mBiTangents.swap(tans2);
mTexCoords.swap(texcoord);
mTexCoords2.swap(texcoord2);
mColors.swap(colors);
mBlendIndices.swap(blendIndices);
mBlendWeights.swap(blendWeights);
mnVerts = nNewVerts;
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::ReverseWinding() noexcept
{
if (!mIndices || !mnFaces)
return E_UNEXPECTED;
auto iptr = mIndices.get();
for (size_t j = 0; j < mnFaces; ++j)
{
std::swap(*iptr, *(iptr + 2));
iptr += 3;
}
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::InvertUTexCoord() noexcept
{
if (!mTexCoords)
return E_UNEXPECTED;
auto tptr = mTexCoords.get();
for (size_t j = 0; j < mnVerts; ++j, ++tptr)
{
tptr->x = 1.f - tptr->x;
}
if (mTexCoords2)
{
tptr = mTexCoords2.get();
for (size_t j = 0; j < mnVerts; ++j, ++tptr)
{
tptr->x = 1.f - tptr->x;
}
}
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::InvertVTexCoord() noexcept
{
if (!mTexCoords)
return E_UNEXPECTED;
auto tptr = mTexCoords.get();
for (size_t j = 0; j < mnVerts; ++j, ++tptr)
{
tptr->y = 1.f - tptr->y;
}
if (mTexCoords2)
{
tptr = mTexCoords2.get();
for (size_t j = 0; j < mnVerts; ++j, ++tptr)
{
tptr->y = 1.f - tptr->y;
}
}
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::ReverseHandedness() noexcept
{
if (!mPositions)
return E_UNEXPECTED;
auto ptr = mPositions.get();
for (size_t j = 0; j < mnVerts; ++j, ++ptr)
{
ptr->z = -ptr->z;
}
if (mNormals)
{
auto nptr = mNormals.get();
for (size_t j = 0; j < mnVerts; ++j, ++nptr)
{
nptr->z = -nptr->z;
}
}
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::VisualizeUVs(bool useSecondUVs, bool vizNormals) noexcept
{
if (!mnVerts || !mPositions)
return E_UNEXPECTED;
const XMFLOAT2* sptr = nullptr;
if (useSecondUVs && mTexCoords2)
{
sptr = mTexCoords2.get();
}
else
{
sptr = mTexCoords.get();
}
if (!sptr)
return E_UNEXPECTED;
XMFLOAT3* dptr = mPositions.get();
for (size_t j = 0; j < mnVerts; ++j)
{
dptr->x = sptr->x;
dptr->y = sptr->y;
dptr->z = 0;
++sptr;
++dptr;
}
if (mNormals)
{
if (vizNormals)
{
std::unique_ptr<XMFLOAT4[]> colors;
colors.reset(new (std::nothrow) XMFLOAT4[mnVerts]);
if (!colors)
return E_OUTOFMEMORY;
const XMFLOAT3* nptr = mNormals.get();
XMFLOAT4* cptr = colors.get();
for (size_t j = 0; j < mnVerts; ++j)
{
// Remap -1..1 to 0..1
cptr->x = 0.5f * nptr->x + 0.5f;
cptr->y = 0.5f * nptr->y + 0.5f;
cptr->z = 0.5f * nptr->z + 0.5f;
cptr->w = 1.f;
++nptr;
++cptr;
}
mColors.swap(colors);
}
XMFLOAT3* nptr = mNormals.get();
for (size_t j = 0; j < mnVerts; ++j)
{
XMStoreFloat3(nptr, g_XMIdentityR2);
++nptr;
}
}
return S_OK;
}
//--------------------------------------------------------------------------------------
bool Mesh::Is16BitIndexBuffer() const noexcept
{
if (!mIndices || !mnFaces)
return false;
if ((uint64_t(mnFaces) * 3) >= UINT32_MAX)
return false;
const uint32_t* iptr = mIndices.get();
for (size_t j = 0; j < (mnFaces * 3); ++j)
{
const uint32_t index = *(iptr++);
if (index != uint32_t(-1)
&& (index >= UINT16_MAX))
{
return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------
std::unique_ptr<uint16_t[]> Mesh::GetIndexBuffer16() const noexcept
{
std::unique_ptr<uint16_t[]> ib;
if (!mIndices || !mnFaces)
return ib;
if ((uint64_t(mnFaces) * 3) >= UINT32_MAX)
return ib;
const size_t count = mnFaces * 3;
ib.reset(new (std::nothrow) uint16_t[count]);
if (!ib)
return ib;
const uint32_t* iptr = mIndices.get();
for (size_t j = 0; j < count; ++j)
{
uint32_t index = *(iptr++);
if (index == uint32_t(-1))
{
ib[j] = uint16_t(-1);
}
else if (index >= UINT16_MAX)
{
ib.reset();
return ib;
}
else
{
ib[j] = static_cast<uint16_t>(index);
}
}
return ib;
}
//--------------------------------------------------------------------------------------
HRESULT Mesh::GetVertexBuffer(const DirectX::VBWriter& writer) const noexcept
{
if (!mnVerts || !mPositions)
return E_UNEXPECTED;
HRESULT hr = writer.Write(mPositions.get(), "SV_Position", 0, mnVerts);
if (FAILED(hr))
return hr;
if (mNormals)
{
auto e = writer.GetElement11("NORMAL", 0);
if (e)
{
const bool x2bias = (e->Format == DXGI_FORMAT_R11G11B10_FLOAT);
hr = writer.Write(mNormals.get(), "NORMAL", 0, mnVerts, x2bias);
if (FAILED(hr))
return hr;
}
}
if (mTangents)
{
auto e = writer.GetElement11("TANGENT", 0);
if (e)
{
const bool x2bias = (e->Format == DXGI_FORMAT_R11G11B10_FLOAT);
hr = writer.Write(mTangents.get(), "TANGENT", 0, mnVerts, x2bias);
if (FAILED(hr))
return hr;
}
}
if (mBiTangents)
{
auto e = writer.GetElement11("BINORMAL", 0);
if (e)
{
const bool x2bias = (e->Format == DXGI_FORMAT_R11G11B10_FLOAT);
hr = writer.Write(mBiTangents.get(), "BINORMAL", 0, mnVerts, x2bias);
if (FAILED(hr))
return hr;
}
}
if (mTexCoords)
{
auto e = writer.GetElement11("TEXCOORD", 0);
if (e)
{
hr = writer.Write(mTexCoords.get(), "TEXCOORD", 0, mnVerts);
if (FAILED(hr))
return hr;
}
}
if (mTexCoords2)
{
auto e = writer.GetElement11("TEXCOORD", 1);
if (e)
{
hr = writer.Write(mTexCoords2.get(), "TEXCOORD", 1, mnVerts);
if (FAILED(hr))
return hr;
}
}
if (mColors)
{
auto e = writer.GetElement11("COLOR", 0);
if (e)
{
hr = writer.Write(mColors.get(), "COLOR", 0, mnVerts);
if (FAILED(hr))
return hr;
}
}
if (mBlendIndices)
{
auto e = writer.GetElement11("BLENDINDICES", 0);
if (e)
{
hr = writer.Write(mBlendIndices.get(), "BLENDINDICES", 0, mnVerts);
if (FAILED(hr))
return hr;
}
}
if (mBlendWeights)
{
auto e = writer.GetElement11("BLENDWEIGHT", 0);
if (e)
{
hr = writer.Write(mBlendWeights.get(), "BLENDWEIGHT", 0, mnVerts);
if (FAILED(hr))
return hr;
}
}
return S_OK;
}
//======================================================================================
// VBO
//======================================================================================
namespace VBO
{
#pragma pack(push,1)
struct header_t
{
uint32_t numVertices;
uint32_t numIndices;
};
struct vertex_t
{
DirectX::XMFLOAT3 position;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT2 textureCoordinate;
};
#pragma pack(pop)
static_assert(sizeof(header_t) == 8, "VBO header size mismatch");
static_assert(sizeof(vertex_t) == 32, "VBO vertex size mismatch");
} // namespace
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::ExportToVBO(const wchar_t* szFileName) const noexcept
{
using namespace VBO;
if (!szFileName)
return E_INVALIDARG;
if (!mnFaces || !mIndices || !mnVerts || !mPositions || !mNormals || !mTexCoords)
return E_UNEXPECTED;
if ((uint64_t(mnFaces) * 3) >= UINT32_MAX)
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
if (mnVerts >= UINT16_MAX)
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
// Setup VBO header
header_t header;
header.numVertices = static_cast<uint32_t>(mnVerts);
header.numIndices = static_cast<uint32_t>(mnFaces * 3);
// Setup vertices/indices for VBO
std::unique_ptr<vertex_t[]> vb(new (std::nothrow) vertex_t[mnVerts]);
std::unique_ptr<uint16_t[]> ib(new (std::nothrow) uint16_t[header.numIndices]);
if (!vb || !ib)
return E_OUTOFMEMORY;
// Copy to VB
auto vptr = vb.get();
for (size_t j = 0; j < mnVerts; ++j, ++vptr)
{
vptr->position = mPositions[j];
vptr->normal = mNormals[j];
vptr->textureCoordinate = mTexCoords[j];
}
// Copy to IB
auto iptr = ib.get();
for (size_t j = 0; j < header.numIndices; ++j, ++iptr)
{
uint32_t index = mIndices[j];
if (index == uint32_t(-1))
{
*iptr = uint16_t(-1);
}
else if (index >= UINT16_MAX)
{
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
}
else
{
*iptr = static_cast<uint16_t>(index);
}
}
// Write header and data
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
ScopedHandle hFile(safe_handle(CreateFile2(szFileName,
GENERIC_WRITE, 0, CREATE_ALWAYS, nullptr)));
#else
ScopedHandle hFile(safe_handle(CreateFileW(szFileName,
GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)));
#endif
if (!hFile)
return HRESULT_FROM_WIN32(GetLastError());
HRESULT hr = write_file(hFile.get(), header);
if (FAILED(hr))
return hr;
auto const vertSize = static_cast<DWORD>(sizeof(vertex_t) * header.numVertices);
DWORD bytesWritten;
if (!WriteFile(hFile.get(), vb.get(), vertSize, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != vertSize)
return E_FAIL;
auto const indexSize = static_cast<DWORD>(sizeof(uint16_t) * header.numIndices);
if (!WriteFile(hFile.get(), ib.get(), indexSize, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != indexSize)
return E_FAIL;
return S_OK;
}
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::CreateFromVBO(const wchar_t* szFileName, std::unique_ptr<Mesh>& result) noexcept
{
using namespace VBO;
if (!szFileName)
return E_INVALIDARG;
result.reset();
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
ScopedHandle hFile(safe_handle(CreateFile2(szFileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr)));
#else
ScopedHandle hFile(safe_handle(CreateFileW(szFileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)));
#endif
if (!hFile)
{
return HRESULT_FROM_WIN32(GetLastError());
}
// Get the file size
FILE_STANDARD_INFO fileInfo;
if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo)))
{
return HRESULT_FROM_WIN32(GetLastError());
}
// File is too big for 32-bit allocation, so reject read
if (fileInfo.EndOfFile.HighPart > 0)
return E_FAIL;
// Need at least enough data to read the header
if (fileInfo.EndOfFile.LowPart < sizeof(header_t))
return E_FAIL;
// Read VBO header
DWORD bytesRead = 0;
header_t header;
if (!ReadFile(hFile.get(), &header, sizeof(header_t), &bytesRead, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesRead != sizeof(header))
return E_FAIL;
if (!header.numVertices || !header.numIndices)
return E_FAIL;
result.reset(new (std::nothrow) Mesh);
if (!result)
return E_OUTOFMEMORY;
// Read vertices/indices from VBO
std::unique_ptr<vertex_t[]> vb(new (std::nothrow) vertex_t[header.numVertices]);
std::unique_ptr<uint16_t[]> ib(new (std::nothrow) uint16_t[header.numIndices]);
if (!vb || !ib)
return E_OUTOFMEMORY;
auto const vertSize = static_cast<DWORD>(sizeof(vertex_t) * header.numVertices);
if (!ReadFile(hFile.get(), vb.get(), vertSize, &bytesRead, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesRead != vertSize)
return E_FAIL;
auto const indexSize = static_cast<DWORD>(sizeof(uint16_t) * header.numIndices);
if (!ReadFile(hFile.get(), ib.get(), indexSize, &bytesRead, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesRead != indexSize)
return E_FAIL;
// Copy VB to result
std::unique_ptr<XMFLOAT3[]> pos(new (std::nothrow) XMFLOAT3[header.numVertices]);
std::unique_ptr<XMFLOAT3[]> norm(new (std::nothrow) XMFLOAT3[header.numVertices]);
std::unique_ptr<XMFLOAT2[]> texcoord(new (std::nothrow) XMFLOAT2[header.numVertices]);
if (!pos || !norm || !texcoord)
return E_OUTOFMEMORY;
auto vptr = vb.get();
for (size_t j = 0; j < header.numVertices; ++j, ++vptr)
{
pos[j] = vptr->position;
norm[j] = vptr->normal;
texcoord[j] = vptr->textureCoordinate;
}
// Copy IB to result
std::unique_ptr<uint32_t[]> indices(new (std::nothrow) uint32_t[header.numIndices]);
if (!indices)
return E_OUTOFMEMORY;
auto iptr = ib.get();
for (size_t j = 0; j < header.numIndices; ++j, ++iptr)
{
uint16_t index = *iptr;
if (index == uint16_t(-1))
indices[j] = uint32_t(-1);
else
indices[j] = index;
}
result->mPositions.swap(pos);
result->mNormals.swap(norm);
result->mTexCoords.swap(texcoord);
result->mIndices.swap(indices);
result->mnVerts = header.numVertices;
result->mnFaces = header.numIndices / 3;
return S_OK;
}
//======================================================================================
// Visual Studio CMO
//======================================================================================
//--------------------------------------------------------------------------------------
// .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;
};
constexpr uint32_t MAX_TEXTURE = 8;
struct SubMesh
{
UINT MaterialIndex;
UINT IndexBufferIndex;
UINT VertexBufferIndex;
UINT StartIndex;
UINT PrimCount;
};
constexpr uint32_t NUM_BONE_INFLUENCES = 4;
struct Vertex
{
DirectX::XMFLOAT3 Position;
DirectX::XMFLOAT3 Normal;
DirectX::XMFLOAT4 Tangent;
UINT color;
DirectX::XMFLOAT2 TextureCoordinates;
};
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)
} // 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::Vertex) == 52, "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");
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT Mesh::ExportToCMO(const wchar_t* szFileName, size_t nMaterials, const Material* materials) const noexcept
{
using namespace VSD3DStarter;
if (!szFileName)
return E_INVALIDARG;
if (nMaterials > 0 && !materials)
return E_INVALIDARG;
if (!mnFaces || !mIndices || !mnVerts || !mPositions || !mNormals || !mTexCoords || !mTangents)
return E_UNEXPECTED;
if ((uint64_t(mnFaces) * 3) >= UINT32_MAX)
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
if (mnVerts >= UINT16_MAX)
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
const UINT nIndices = static_cast<UINT>(mnFaces * 3);
// Setup vertices/indices for CMO
std::unique_ptr<Vertex[]> vb(new (std::nothrow) Vertex[mnVerts]);
std::unique_ptr<uint16_t[]> ib(new (std::nothrow) uint16_t[nIndices]);
if (!vb || !ib)
return E_OUTOFMEMORY;
std::unique_ptr<SkinningVertex[]> vbSkin;
if (mBlendIndices && mBlendWeights)
{
vbSkin.reset(new (std::nothrow) SkinningVertex[mnVerts]);
if (!vbSkin)
return E_OUTOFMEMORY;
}
// Copy to VB
auto vptr = vb.get();
for (size_t j = 0; j < mnVerts; ++j, ++vptr)
{
vptr->Position = mPositions[j];
vptr->Normal = mNormals[j];
vptr->Tangent = mTangents[j];
vptr->TextureCoordinates = mTexCoords[j];
if (mColors)
{
const XMVECTOR icolor = XMLoadFloat4(&mColors[j]);
PackedVector::XMUBYTEN4 rgba;
PackedVector::XMStoreUByteN4(&rgba, icolor);
vptr->color = rgba.v;
}
else
vptr->color = 0xFFFFFFFF;
}
// Copy to SkinVB
auto sptr = vbSkin.get();
if (sptr)
{
for (size_t j = 0; j < mnVerts; ++j, ++sptr)
{
const XMVECTOR v = XMLoadFloat4(&mBlendIndices[j]);
XMStoreUInt4(reinterpret_cast<XMUINT4*>(&sptr->boneIndex[0]), v);
const XMFLOAT4* w = &mBlendWeights[j];
sptr->boneWeight[0] = w->x;
sptr->boneWeight[1] = w->y;
sptr->boneWeight[2] = w->z;
sptr->boneWeight[3] = w->w;
}
}
// Copy to IB
auto iptr = ib.get();
for (size_t j = 0; j < nIndices; ++j, ++iptr)
{
uint32_t index = mIndices[j];
if (index == uint32_t(-1))
{
*iptr = uint16_t(-1);
}
else if (index >= UINT16_MAX)
{
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
}
else
{
*iptr = static_cast<uint16_t>(index);
}
}
// Create CMO file
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
ScopedHandle hFile(safe_handle(CreateFile2(szFileName,
GENERIC_WRITE, 0, CREATE_ALWAYS, nullptr)));
#else
ScopedHandle hFile(safe_handle(CreateFileW(szFileName,
GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)));
#endif
if (!hFile)
return HRESULT_FROM_WIN32(GetLastError());
// Write 1 mesh, name based on the filename
UINT n = 1;
HRESULT hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
{
wchar_t fname[_MAX_FNAME];
_wsplitpath_s(szFileName, nullptr, 0, nullptr, 0, fname, _MAX_FNAME, nullptr, 0);
hr = write_file_string(hFile.get(), fname);
if (FAILED(hr))
return hr;
}
// Write materials
static const Mesh::Material s_defMaterial = { L"default", false, 1.f, 1.f,
XMFLOAT3(0.2f, 0.2f, 0.2f), XMFLOAT3(0.8f, 0.8f, 0.8f),
XMFLOAT3(0.f, 0.f, 0.f), XMFLOAT3(0.f, 0.f, 0.f), L"" };
UINT materialCount = 1;
if (nMaterials > 0)
{
materialCount = static_cast<UINT>(nMaterials);
}
else
{
nMaterials = 1;
materials = &s_defMaterial;
}
hr = write_file(hFile.get(), materialCount);
if (FAILED(hr))
return hr;
for (UINT j = 0; j < materialCount; ++j)
{
auto& m = materials[j];
if (!m.name.empty())
{
hr = write_file_string(hFile.get(), m.name.c_str());
}
else
{
wchar_t name[64];
swprintf_s(name, L"material%03u\n", j);
hr = write_file_string(hFile.get(), name);
}
if (FAILED(hr))
return hr;
VSD3DStarter::Material mdata = {};
mdata.Ambient.x = m.ambientColor.x;
mdata.Ambient.y = m.ambientColor.y;
mdata.Ambient.z = m.ambientColor.z;
mdata.Ambient.w = 1.f;
mdata.Diffuse.x = m.diffuseColor.x;
mdata.Diffuse.y = m.diffuseColor.y;
mdata.Diffuse.z = m.diffuseColor.z;
mdata.Diffuse.w = m.alpha;
if (m.specularColor.x > 0.f || m.specularColor.y > 0.f || m.specularColor.z > 0.f)
{
mdata.Specular.x = m.specularColor.x;
mdata.Specular.y = m.specularColor.y;
mdata.Specular.z = m.specularColor.z;
mdata.SpecularPower = (m.specularPower <= 0.f) ? 16.f : m.specularPower;
}
else
{
mdata.SpecularPower = 1.f;
}
mdata.Specular.w = 1.f;
mdata.Emissive.x = m.emissiveColor.x;
mdata.Emissive.y = m.emissiveColor.y;
mdata.Emissive.z = m.emissiveColor.z;
mdata.Emissive.w = 1.f;
const XMMATRIX id = XMMatrixIdentity();
XMStoreFloat4x4(&mdata.UVTransform, id);
hr = write_file(hFile.get(), mdata);
if (FAILED(hr))
return hr;
if (m.specularColor.x > 0.f || m.specularColor.y > 0.f || m.specularColor.z > 0.f)
{
hr = write_file_string(hFile.get(), L"phong.dgsl");
}
else
{
hr = write_file_string(hFile.get(), L"lambert.dgsl");
}
if (FAILED(hr))
return hr;
hr = write_file_string(hFile.get(), m.texture.c_str());
if (FAILED(hr))
return hr;
for (size_t k = 1; k < MAX_TEXTURE; ++k)
{
hr = write_file_string(hFile.get(), L"");
if (FAILED(hr))
return hr;
}
}
constexpr BYTE sd = 0; // No skeleton/animation data
hr = write_file(hFile.get(), sd);
if (FAILED(hr))
return hr;
if (mAttributes)
{
auto subsets = ComputeSubsets(mAttributes.get(), mnFaces);
n = static_cast<UINT>(subsets.size());
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
size_t startIndex = 0;
for (const auto& it : subsets)
{
SubMesh smesh;
smesh.MaterialIndex = mAttributes[it.first];
if (smesh.MaterialIndex >= nMaterials)
smesh.MaterialIndex = 0;
smesh.IndexBufferIndex = 0;
smesh.VertexBufferIndex = 0;
smesh.StartIndex = static_cast<UINT>(startIndex);
smesh.PrimCount = static_cast<UINT>(it.second);
hr = write_file(hFile.get(), smesh);
if (FAILED(hr))
return hr;
if ((startIndex + (it.second * 3)) > mnFaces * 3)
return E_FAIL;
startIndex += static_cast<size_t>(uint64_t(smesh.PrimCount) * 3);
}
}
else
{
n = 1;
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
SubMesh smesh;
smesh.MaterialIndex = 0;
smesh.IndexBufferIndex = 0;
smesh.VertexBufferIndex = 0;
smesh.StartIndex = 0;
smesh.PrimCount = static_cast<UINT>(mnFaces);
hr = write_file(hFile.get(), smesh);
if (FAILED(hr))
return hr;
}
// Write indices (one IB shared across submeshes)
n = 1;
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
hr = write_file(hFile.get(), nIndices);
if (FAILED(hr))
return hr;
auto const indexSize = static_cast<DWORD>(sizeof(uint16_t) * nIndices);
DWORD bytesWritten;
if (!WriteFile(hFile.get(), ib.get(), indexSize, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != indexSize)
return E_FAIL;
// Write vertices (one VB shared across submeshes)
n = 1;
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
n = static_cast<UINT>(mnVerts);
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
auto const vertSize = static_cast<DWORD>(sizeof(Vertex) * mnVerts);
if (!WriteFile(hFile.get(), vb.get(), vertSize, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != vertSize)
return E_FAIL;
// Write skinning vertices (one SkinVB shared across submeshes)
if (vbSkin)
{
n = 1;
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
n = static_cast<UINT>(mnVerts);
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
auto const skinVertSize = static_cast<DWORD>(sizeof(SkinningVertex) * mnVerts);
if (!WriteFile(hFile.get(), vbSkin.get(), skinVertSize, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != skinVertSize)
return E_FAIL;
}
else
{
n = 0;
hr = write_file(hFile.get(), n);
if (FAILED(hr))
return hr;
}
// Write extents
{
BoundingSphere sphere;
BoundingSphere::CreateFromPoints(sphere, mnVerts, mPositions.get(), sizeof(XMFLOAT3));
BoundingBox box;
BoundingBox::CreateFromPoints(box, mnVerts, mPositions.get(), sizeof(XMFLOAT3));
MeshExtents extents;
extents.CenterX = sphere.Center.x;
extents.CenterY = sphere.Center.y;
extents.CenterZ = sphere.Center.z;
extents.Radius = sphere.Radius;
extents.MinX = box.Center.x - box.Extents.x;
extents.MinY = box.Center.y - box.Extents.y;
extents.MinZ = box.Center.z - box.Extents.z;
extents.MaxX = box.Center.x + box.Extents.x;
extents.MaxY = box.Center.y + box.Extents.y;
extents.MaxZ = box.Center.z + box.Extents.z;
hr = write_file(hFile.get(), extents);
if (FAILED(hr))
return hr;
}
// No skeleton data, so no animations
return S_OK;
}
//======================================================================================
// SDKMESH
//======================================================================================
_Use_decl_annotations_
HRESULT Mesh::ExportToSDKMESH(const wchar_t* szFileName,
size_t nMaterials, const Material* materials,
bool force32bit,
bool version2,
DXGI_FORMAT normalFormat,
DXGI_FORMAT uvFormat,
DXGI_FORMAT colorFormat) const noexcept
{
using namespace DXUT;
if (!szFileName)
return E_INVALIDARG;
if (nMaterials > 0 && !materials)
return E_INVALIDARG;
if (!mnFaces || !mIndices || !mnVerts || !mPositions)
return E_UNEXPECTED;
if ((uint64_t(mnFaces) * 3) >= UINT32_MAX)
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
// Build input layout/vertex decalaration
static const D3D11_INPUT_ELEMENT_DESC s_elements[] =
{
{ "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 0
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 1
{ "COLOR", 0, DXGI_FORMAT_B8G8R8A8_UNORM, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 2
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 3
{ "BINORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 4
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 5
{ "BLENDINDICES", 0, DXGI_FORMAT_R8G8B8A8_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 6
{ "BLENDWEIGHT", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 7
};
static const D3DVERTEXELEMENT9 s_decls[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, 0, D3DDECLUSAGE_POSITION, 0 }, // 0
{ 0, 0, D3DDECLTYPE_FLOAT3, 0, D3DDECLUSAGE_NORMAL, 0 }, // 1
{ 0, 0, D3DDECLTYPE_D3DCOLOR, 0, D3DDECLUSAGE_COLOR, 0 }, // 2
{ 0, 0, D3DDECLTYPE_FLOAT3, 0, D3DDECLUSAGE_TANGENT, 0 }, // 3
{ 0, 0, D3DDECLTYPE_FLOAT3, 0, D3DDECLUSAGE_BINORMAL, 0 }, // 4
{ 0, 0, D3DDECLTYPE_FLOAT2, 0, D3DDECLUSAGE_TEXCOORD, 0 }, // 5
{ 0, 0, D3DDECLTYPE_UBYTE4, 0, D3DDECLUSAGE_BLENDINDICES, 0 }, // 6
{ 0, 0, D3DDECLTYPE_UBYTE4N, 0, D3DDECLUSAGE_BLENDWEIGHT, 0 }, // 7
{ 0xFF, 0, D3DDECLTYPE_UNUSED, 0, 0, 0 },
};
static_assert((std::size(s_elements) + 1) == std::size(s_decls), "InputLayouts and Vertex Decls disagree");
uint8_t normalType;
size_t normalStride;
switch (normalFormat)
{
case DXGI_FORMAT_R16G16B16A16_FLOAT:
normalType = D3DDECLTYPE_FLOAT16_4; normalStride = sizeof(PackedVector::XMHALF4);
break;
case DXGI_FORMAT_R11G11B10_FLOAT: // Biased in GetVertexBuffer
normalType = D3DDECLTYPE_DXGI_R11G11B10_FLOAT; normalStride = sizeof(UINT);
break;
default:
normalFormat = DXGI_FORMAT_R32G32B32_FLOAT; normalType = D3DDECLTYPE_FLOAT3; normalStride = sizeof(XMFLOAT3);
break;
}
uint8_t uvType;
size_t uvStride;
switch (uvFormat)
{
case DXGI_FORMAT_R16G16_FLOAT:
uvType = D3DDECLTYPE_FLOAT16_2; uvStride = sizeof(PackedVector::XMHALF2);
break;
default:
uvFormat = DXGI_FORMAT_R32G32_FLOAT; uvType = D3DDECLTYPE_FLOAT2; uvStride = sizeof(XMFLOAT2);
break;
}
uint8_t colorType;
size_t colorStride;
switch (colorFormat)
{
case DXGI_FORMAT_R32G32B32A32_FLOAT:
colorType = D3DDECLTYPE_FLOAT4; colorStride = sizeof(XMFLOAT4);
break;
case DXGI_FORMAT_R16G16B16A16_FLOAT:
colorType = D3DDECLTYPE_FLOAT16_4; colorStride = sizeof(PackedVector::XMHALF4);
break;
case DXGI_FORMAT_R11G11B10_FLOAT:
colorType = D3DDECLTYPE_DXGI_R11G11B10_FLOAT; colorStride = sizeof(UINT);
break;
case DXGI_FORMAT_R10G10B10A2_UNORM:
colorType = D3DDECLTYPE_DXGI_R10G10B10A2_UNORM; colorStride = sizeof(UINT);
break;
case DXGI_FORMAT_R8G8B8A8_UNORM:
colorType = D3DDECLTYPE_UBYTE4N; colorStride = sizeof(UINT);
break;
default:
colorFormat = DXGI_FORMAT_B8G8R8A8_UNORM; colorType = D3DDECLTYPE_D3DCOLOR; colorStride = sizeof(UINT);
break;
}
SDKMESH_VERTEX_BUFFER_HEADER vbHeader = {};
vbHeader.NumVertices = mnVerts;
vbHeader.Decl[0] = s_decls[0];
D3D11_INPUT_ELEMENT_DESC inputLayout[MAX_VERTEX_ELEMENTS] = {};
inputLayout[0] = s_elements[0];
size_t nDecl = 1;
size_t stride = sizeof(XMFLOAT3);
if (mBlendIndices && mBlendWeights)
{
// BLENDWEIGHT
vbHeader.Decl[nDecl] = s_decls[7];
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
inputLayout[nDecl] = s_elements[7];
++nDecl;
stride += sizeof(UINT);
// BLENDINDICES
vbHeader.Decl[nDecl] = s_decls[6];
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
inputLayout[nDecl] = s_elements[6];
++nDecl;
stride += sizeof(UINT);
}
if (mNormals)
{
vbHeader.Decl[nDecl] = s_decls[1];
vbHeader.Decl[nDecl].Type = normalType;
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
inputLayout[nDecl] = s_elements[1];
inputLayout[nDecl].Format = normalFormat;
++nDecl;
stride += normalStride;
}
if (mColors)
{
vbHeader.Decl[nDecl] = s_decls[2];
vbHeader.Decl[nDecl].Type = colorType;
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
inputLayout[nDecl] = s_elements[2];
inputLayout[nDecl].Format = colorFormat;
++nDecl;
stride += colorStride;
}
if (mTexCoords)
{
vbHeader.Decl[nDecl] = s_decls[5];
vbHeader.Decl[nDecl].Type = uvType;
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
inputLayout[nDecl] = s_elements[5];
inputLayout[nDecl].Format = uvFormat;
++nDecl;
stride += uvStride;
}
if (mTexCoords2)
{
vbHeader.Decl[nDecl] = s_decls[5];
vbHeader.Decl[nDecl].Type = uvType;
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
vbHeader.Decl[nDecl].UsageIndex = 1;
inputLayout[nDecl] = s_elements[5];
inputLayout[nDecl].Format = uvFormat;
inputLayout[nDecl].SemanticIndex = 1;
++nDecl;
stride += uvStride;
}
if (mTangents)
{
vbHeader.Decl[nDecl] = s_decls[3];
vbHeader.Decl[nDecl].Type = normalType;
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
inputLayout[nDecl] = s_elements[3];
inputLayout[nDecl].Format = normalFormat;
++nDecl;
stride += normalStride;
}
if (mBiTangents)
{
vbHeader.Decl[nDecl] = s_decls[4];
vbHeader.Decl[nDecl].Type = normalType;
vbHeader.Decl[nDecl].Offset = static_cast<WORD>(stride);
inputLayout[nDecl] = s_elements[4];
inputLayout[nDecl].Format = normalFormat;
++nDecl;
stride += normalStride;
}
assert(nDecl < MAX_VERTEX_ELEMENTS);
vbHeader.Decl[nDecl] = s_decls[std::size(s_decls) - 1];
// Build vertex buffer
std::unique_ptr<uint8_t> vb(new (std::nothrow) uint8_t[mnVerts * stride]);
if (!vb)
return E_OUTOFMEMORY;
vbHeader.SizeBytes = uint64_t(mnVerts) * uint64_t(stride);
vbHeader.StrideBytes = stride;
{
VBWriter writer;
HRESULT hr = writer.Initialize(inputLayout, nDecl);
if (FAILED(hr))
return hr;
hr = writer.AddStream(vb.get(), mnVerts, 0, stride);
if (FAILED(hr))
return hr;
hr = GetVertexBuffer(writer);
if (FAILED(hr))
return hr;
}
// Build index buffer
SDKMESH_INDEX_BUFFER_HEADER ibHeader = {};
ibHeader.NumIndices = uint64_t(mnFaces) * 3;
std::unique_ptr<uint16_t[]> ib16;
if (!force32bit && Is16BitIndexBuffer())
{
ibHeader.SizeBytes = uint64_t(mnFaces) * 3 * sizeof(uint16_t);
ibHeader.IndexType = IT_16BIT;
ib16 = GetIndexBuffer16();
if (!ib16)
return E_OUTOFMEMORY;
}
else
{
ibHeader.SizeBytes = uint64_t(mnFaces) * 3 * sizeof(uint32_t);
ibHeader.IndexType = IT_32BIT;
}
// Build materials buffer
std::unique_ptr<SDKMESH_MATERIAL[]> mats;
if (version2)
{
if (!nMaterials)
{
mats.reset(new (std::nothrow) SDKMESH_MATERIAL[1]);
if (!mats)
return E_OUTOFMEMORY;
auto mat2 = reinterpret_cast<SDKMESH_MATERIAL_V2*>(mats.get());
memset(mat2, 0, sizeof(SDKMESH_MATERIAL_V2));
strcpy_s(mat2->Name, "default");
mat2->Alpha = 1.f;
}
else
{
mats.reset(new (std::nothrow) SDKMESH_MATERIAL[nMaterials]);
if (!mats)
return E_OUTOFMEMORY;
for (size_t j = 0; j < nMaterials; ++j)
{
auto m0 = &materials[j];
auto m2 = reinterpret_cast<SDKMESH_MATERIAL_V2*>(&mats[j]);
memset(m2, 0, sizeof(SDKMESH_MATERIAL_V2));
if (!m0->name.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->name.c_str(), -1,
m2->Name, MAX_MATERIAL_NAME, nullptr, FALSE);
if (!result)
{
*m2->Name = 0;
}
}
m2->Alpha = m0->alpha;
if (!m0->texture.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->texture.c_str(), -1,
m2->AlbedoTexture, MAX_TEXTURE_NAME, nullptr, FALSE);
if (!result)
{
*m2->AlbedoTexture = 0;
}
}
// Derive other PBR texture names from base texture
{
char drive[_MAX_DRIVE] = {};
char dir[MAX_PATH] = {};
char fname[_MAX_FNAME] = {};
char ext[_MAX_EXT] = {};
_splitpath_s(m2->AlbedoTexture, drive, dir, fname, ext);
std::string basename = fname;
const size_t pos = basename.find_last_of('_');
if (pos != std::string::npos)
{
basename = basename.substr(0, pos);
}
if (!basename.empty())
{
strcpy_s(fname, basename.c_str());
strcat_s(fname, "_normal");
_makepath_s(m2->NormalTexture, MAX_TEXTURE_NAME, drive, dir, fname, ext);
strcpy_s(fname, basename.c_str());
strcat_s(fname, "_occlusionRoughnessMetallic");
_makepath_s(m2->RMATexture, MAX_TEXTURE_NAME, drive, dir, fname, ext);
if (m0->emissiveColor.x > 0 || m0->emissiveColor.y > 0 || m0->emissiveColor.z > 0)
{
strcpy_s(fname, basename.c_str());
strcat_s(fname, "_emissive");
_makepath_s(m2->EmissiveTexture, MAX_TEXTURE_NAME, drive, dir, fname, ext);
}
}
}
// Allow normal texture material property to override derived name
if (!m0->normalTexture.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->normalTexture.c_str(), -1,
m2->NormalTexture, MAX_TEXTURE_NAME, nullptr, FALSE);
if (!result)
{
*m2->NormalTexture = 0;
}
}
// Allow emissive texture material property to override drived name
if (!m0->emissiveTexture.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->emissiveTexture.c_str(), -1,
m2->EmissiveTexture, MAX_TEXTURE_NAME, nullptr, FALSE);
if (!result)
{
*m2->EmissiveTexture = 0;
}
}
// Allow RMA texture material property to override drived name
if (!m0->rmaTexture.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->rmaTexture.c_str(), -1,
m2->RMATexture, MAX_TEXTURE_NAME, nullptr, FALSE);
if (!result)
{
*m2->RMATexture = 0;
}
}
}
}
}
else if (!nMaterials)
{
mats.reset(new (std::nothrow) SDKMESH_MATERIAL[1]);
if (!mats)
return E_OUTOFMEMORY;
memset(mats.get(), 0, sizeof(SDKMESH_MATERIAL));
strcpy_s(mats[0].Name, "default");
mats[0].Diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.f);
mats[0].Ambient = XMFLOAT4(0.2f, 02.f, 0.2f, 1.f);
mats[0].Power = 1.f;
}
else
{
mats.reset(new (std::nothrow) SDKMESH_MATERIAL[nMaterials]);
if (!mats)
return E_OUTOFMEMORY;
for (size_t j = 0; j < nMaterials; ++j)
{
auto m0 = &materials[j];
auto m = &mats[j];
memset(m, 0, sizeof(SDKMESH_MATERIAL));
if (!m0->name.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->name.c_str(), -1,
m->Name, MAX_MATERIAL_NAME, nullptr, FALSE);
if (!result)
{
*m->Name = 0;
}
}
if (!m0->texture.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->texture.c_str(), -1,
m->DiffuseTexture, MAX_TEXTURE_NAME, nullptr, FALSE);
if (!result)
{
*m->DiffuseTexture = 0;
}
}
if (!m0->normalTexture.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->normalTexture.c_str(), -1,
m->NormalTexture, MAX_TEXTURE_NAME, nullptr, FALSE);
if (!result)
{
*m->NormalTexture = 0;
}
}
if (!m0->specularTexture.empty())
{
const int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS,
m0->specularTexture.c_str(), -1,
m->SpecularTexture, MAX_TEXTURE_NAME, nullptr, FALSE);
if (!result)
{
*m->SpecularTexture = 0;
}
}
m->Diffuse.x = m0->diffuseColor.x;
m->Diffuse.y = m0->diffuseColor.y;
m->Diffuse.z = m0->diffuseColor.z;
m->Diffuse.w = m0->alpha;
m->Ambient.x = m0->ambientColor.x;
m->Ambient.y = m0->ambientColor.y;
m->Ambient.z = m0->ambientColor.z;
m->Ambient.w = 1.f;
if (m0->specularColor.x > 0.f || m0->specularColor.y > 0.f || m0->specularColor.z > 0.f)
{
m->Specular.x = m0->specularColor.x;
m->Specular.y = m0->specularColor.y;
m->Specular.z = m0->specularColor.z;
m->Power = (m0->specularPower <= 0.f) ? 16.f : m0->specularPower;
}
else
{
m->Power = 1.f;
}
m->Emissive.x = m0->emissiveColor.x;
m->Emissive.y = m0->emissiveColor.y;
m->Emissive.z = m0->emissiveColor.z;
}
}
// Build subsets
std::vector<SDKMESH_SUBSET> submeshes;
std::vector<UINT> subsetArray;
if (mAttributes)
{
auto subsets = ComputeSubsets(mAttributes.get(), mnFaces);
UINT64 startIndex = 0;
for (const auto& it : subsets)
{
subsetArray.push_back(static_cast<UINT>(submeshes.size()));
SDKMESH_SUBSET s = {};
s.MaterialID = mAttributes[it.first];
if (s.MaterialID >= nMaterials)
s.MaterialID = 0;
s.PrimitiveType = PT_TRIANGLE_LIST;
s.IndexStart = startIndex;
s.IndexCount = uint64_t(it.second) * 3;
s.VertexCount = mnVerts;
submeshes.push_back(s);
if ((startIndex + s.IndexCount) > uint64_t(mnFaces) * 3)
return E_FAIL;
startIndex += s.IndexCount;
}
}
else
{
SDKMESH_SUBSET s = {};
s.PrimitiveType = PT_TRIANGLE_LIST;
s.IndexCount = uint64_t(mnFaces) * 3;
s.VertexCount = mnVerts;
subsetArray.push_back(0);
submeshes.push_back(s);
}
// Create file
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
ScopedHandle hFile(safe_handle(CreateFile2(szFileName,
GENERIC_WRITE, 0, CREATE_ALWAYS, nullptr)));
#else
ScopedHandle hFile(safe_handle(CreateFileW(szFileName,
GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)));
#endif
if (!hFile)
return HRESULT_FROM_WIN32(GetLastError());
// Write file header
SDKMESH_HEADER header = {};
header.Version = (version2) ? SDKMESH_FILE_VERSION_V2 : SDKMESH_FILE_VERSION;
header.IsBigEndian = 0;
header.NumVertexBuffers = 1;
header.NumIndexBuffers = 1;
header.NumMeshes = 1;
header.NumTotalSubsets = static_cast<UINT>(submeshes.size());
header.NumFrames = 1;
header.NumMaterials = (nMaterials > 0) ? static_cast<UINT>(nMaterials) : 1;
header.HeaderSize = sizeof(SDKMESH_HEADER) + sizeof(SDKMESH_VERTEX_BUFFER_HEADER) + sizeof(SDKMESH_INDEX_BUFFER_HEADER);
const size_t staticDataSize = sizeof(SDKMESH_MESH)
+ header.NumTotalSubsets * sizeof(SDKMESH_SUBSET)
+ sizeof(SDKMESH_FRAME)
+ header.NumMaterials * sizeof(SDKMESH_MATERIAL);
header.NonBufferDataSize = uint64_t(staticDataSize) + uint64_t(subsetArray.size()) * sizeof(UINT) + sizeof(UINT);
header.BufferDataSize = roundup4k(vbHeader.SizeBytes) + roundup4k(ibHeader.SizeBytes);
header.VertexStreamHeadersOffset = sizeof(SDKMESH_HEADER);
header.IndexStreamHeadersOffset = header.VertexStreamHeadersOffset + sizeof(SDKMESH_VERTEX_BUFFER_HEADER);
header.MeshDataOffset = header.IndexStreamHeadersOffset + sizeof(SDKMESH_INDEX_BUFFER_HEADER);
header.SubsetDataOffset = header.MeshDataOffset + sizeof(SDKMESH_MESH);
header.FrameDataOffset = header.SubsetDataOffset + uint64_t(header.NumTotalSubsets) * sizeof(SDKMESH_SUBSET);
header.MaterialDataOffset = header.FrameDataOffset + sizeof(SDKMESH_FRAME);
HRESULT hr = write_file(hFile.get(), header);
if (FAILED(hr))
return hr;
// Write buffer headers
UINT64 offset = header.HeaderSize + header.NonBufferDataSize;
vbHeader.DataOffset = offset;
offset += roundup4k(vbHeader.SizeBytes);
hr = write_file(hFile.get(), vbHeader);
if (FAILED(hr))
return hr;
ibHeader.DataOffset = offset;
offset += roundup4k(ibHeader.SizeBytes);
hr = write_file(hFile.get(), ibHeader);
if (FAILED(hr))
return hr;
// Write mesh headers
assert(header.NumMeshes == 1);
offset = header.HeaderSize + staticDataSize;
SDKMESH_MESH meshHeader = {};
meshHeader.NumVertexBuffers = 1;
meshHeader.NumFrameInfluences = 1;
{
BoundingBox box;
BoundingBox::CreateFromPoints(box, mnVerts, mPositions.get(), sizeof(XMFLOAT3));
meshHeader.BoundingBoxCenter = box.Center;
meshHeader.BoundingBoxExtents = box.Extents;
}
meshHeader.NumSubsets = static_cast<UINT>(submeshes.size());
meshHeader.SubsetOffset = offset;
offset += uint64_t(meshHeader.NumSubsets) * sizeof(UINT);
meshHeader.FrameInfluenceOffset = offset;
offset += sizeof(UINT);
hr = write_file(hFile.get(), meshHeader);
if (FAILED(hr))
return hr;
// Write subsets
auto bytesToWrite = static_cast<DWORD>(sizeof(SDKMESH_SUBSET) * submeshes.size());
DWORD bytesWritten;
if (!WriteFile(hFile.get(), submeshes.data(), bytesToWrite, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytesToWrite)
return E_FAIL;
// Write frames
SDKMESH_FRAME frame = {};
strcpy_s(frame.Name, "root");
frame.ParentFrame = frame.ChildFrame = frame.SiblingFrame = DWORD(-1);
frame.AnimationDataIndex = INVALID_ANIMATION_DATA;
const XMMATRIX id = XMMatrixIdentity();
XMStoreFloat4x4(&frame.Matrix, id);
hr = write_file(hFile.get(), frame);
if (FAILED(hr))
return hr;
// Write materials
bytesToWrite = static_cast<DWORD>(sizeof(SDKMESH_MATERIAL) * ((nMaterials > 0) ? nMaterials : 1));
if (!WriteFile(hFile.get(), mats.get(), bytesToWrite, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytesToWrite)
return E_FAIL;
// Write subset index list
assert(meshHeader.NumSubsets == subsetArray.size());
bytesToWrite = meshHeader.NumSubsets * sizeof(UINT);
if (!WriteFile(hFile.get(), subsetArray.data(), bytesToWrite, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytesToWrite)
return E_FAIL;
// Write frame influence list
assert(meshHeader.NumFrameInfluences == 1);
constexpr UINT frameIndex = 0;
hr = write_file(hFile.get(), frameIndex);
if (FAILED(hr))
return hr;
// Write VB data
bytesToWrite = static_cast<DWORD>(vbHeader.SizeBytes);
if (!WriteFile(hFile.get(), vb.get(), bytesToWrite, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytesToWrite)
return E_FAIL;
bytesToWrite = static_cast<DWORD>(roundup4k(vbHeader.SizeBytes) - vbHeader.SizeBytes);
if (bytesToWrite > 0)
{
assert(bytesToWrite < sizeof(g_padding));
if (!WriteFile(hFile.get(), g_padding, bytesToWrite, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytesToWrite)
return E_FAIL;
}
// Write IB data
bytesToWrite = static_cast<DWORD>(ibHeader.SizeBytes);
if (!WriteFile(hFile.get(), (ib16) ? static_cast<void*>(ib16.get()) : static_cast<void*>(mIndices.get()),
bytesToWrite, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytesToWrite)
return E_FAIL;
bytesToWrite = static_cast<DWORD>(roundup4k(ibHeader.SizeBytes) - ibHeader.SizeBytes);
if (bytesToWrite > 0)
{
assert(bytesToWrite < sizeof(g_padding));
if (!WriteFile(hFile.get(), g_padding, bytesToWrite, &bytesWritten, nullptr))
return HRESULT_FROM_WIN32(GetLastError());
if (bytesWritten != bytesToWrite)
return E_FAIL;
}
return S_OK;
}