//-------------------------------------------------------------------------------------- // 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 #include #include #include #include #include #include "SDKMesh.h" #include #include #include using namespace DirectX; namespace { struct handle_closer { void operator()(HANDLE h) noexcept { if (h) CloseHandle(h); } }; using ScopedHandle = std::unique_ptr; inline HANDLE safe_handle(HANDLE h) noexcept { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; } template inline HRESULT write_file(HANDLE hFile, const T& value) { DWORD bytesWritten; if (!WriteFile(hFile, &value, static_cast(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(wcslen(value) + 1) : 1; DWORD bytesWritten; if (!WriteFile(hFile, &length, static_cast(sizeof(UINT)), &bytesWritten, nullptr)) return HRESULT_FROM_WIN32(GetLastError()); if (bytesWritten != sizeof(UINT)) return E_FAIL; if (length > 0) { auto const bytes = static_cast(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 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 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 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 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 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 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 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 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 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 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 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 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 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& 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 pos(new (std::nothrow) XMFLOAT3[nNewVerts]); if (!pos) return E_OUTOFMEMORY; memcpy(pos.get(), mPositions.get(), sizeof(XMFLOAT3) * mnVerts); std::unique_ptr 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 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 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 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 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 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 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 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 tan1(new (std::nothrow) XMFLOAT4[mnVerts]); if (!tan1) return E_OUTOFMEMORY; std::unique_ptr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Mesh::GetIndexBuffer16() const noexcept { std::unique_ptr 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(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(mnVerts); header.numIndices = static_cast(mnFaces * 3); // Setup vertices/indices for VBO std::unique_ptr vb(new (std::nothrow) vertex_t[mnVerts]); std::unique_ptr 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(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(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(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& 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 vb(new (std::nothrow) vertex_t[header.numVertices]); std::unique_ptr ib(new (std::nothrow) uint16_t[header.numIndices]); if (!vb || !ib) return E_OUTOFMEMORY; auto const vertSize = static_cast(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(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 pos(new (std::nothrow) XMFLOAT3[header.numVertices]); std::unique_ptr norm(new (std::nothrow) XMFLOAT3[header.numVertices]); std::unique_ptr 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 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(mnFaces * 3); // Setup vertices/indices for CMO std::unique_ptr vb(new (std::nothrow) Vertex[mnVerts]); std::unique_ptr ib(new (std::nothrow) uint16_t[nIndices]); if (!vb || !ib) return E_OUTOFMEMORY; std::unique_ptr 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(&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(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(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(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(startIndex); smesh.PrimCount = static_cast(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(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(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(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(mnVerts); hr = write_file(hFile.get(), n); if (FAILED(hr)) return hr; auto const vertSize = static_cast(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(mnVerts); hr = write_file(hFile.get(), n); if (FAILED(hr)) return hr; auto const skinVertSize = static_cast(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(stride); inputLayout[nDecl] = s_elements[7]; ++nDecl; stride += sizeof(UINT); // BLENDINDICES vbHeader.Decl[nDecl] = s_decls[6]; vbHeader.Decl[nDecl].Offset = static_cast(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(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(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(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(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(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(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 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 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 mats; if (version2) { if (!nMaterials) { mats.reset(new (std::nothrow) SDKMESH_MATERIAL[1]); if (!mats) return E_OUTOFMEMORY; auto mat2 = reinterpret_cast(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(&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 submeshes; std::vector subsetArray; if (mAttributes) { auto subsets = ComputeSubsets(mAttributes.get(), mnFaces); UINT64 startIndex = 0; for (const auto& it : subsets) { subsetArray.push_back(static_cast(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(submeshes.size()); header.NumFrames = 1; header.NumMaterials = (nMaterials > 0) ? static_cast(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(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(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(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(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(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(ibHeader.SizeBytes); if (!WriteFile(hFile.get(), (ib16) ? static_cast(ib16.get()) : static_cast(mIndices.get()), bytesToWrite, &bytesWritten, nullptr)) return HRESULT_FROM_WIN32(GetLastError()); if (bytesWritten != bytesToWrite) return E_FAIL; bytesToWrite = static_cast(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; }