//------------------------------------------------------------------------------------- // DirectXMeshValidate.cpp // // DirectX Mesh Geometry Library - Mesh validation // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkID=324981 //------------------------------------------------------------------------------------- #include "DirectXMeshP.h" using namespace DirectX; namespace { //------------------------------------------------------------------------------------- // Validates indices and optionally the adjacency information //------------------------------------------------------------------------------------- template HRESULT ValidateIndices( _In_reads_(nFaces*3) const index_t* indices, _In_ size_t nFaces, _In_ size_t nVerts, _In_reads_opt_(nFaces*3) const uint32_t* adjacency, _In_ DWORD flags, _In_opt_ std::wstring* msgs ) { bool result = true; if ( !adjacency ) { if ( flags & VALIDATE_BACKFACING ) { if ( msgs ) *msgs += L"Missing adjacency information required to check for BACKFACING\n"; result = false; } if ( flags & VALIDATE_ASYMMETRIC_ADJ ) { if ( msgs ) *msgs += L"Missing adjacency information required to check for ASYMMETRIC_ADJ\n"; result = false; } if ( !result ) return E_INVALIDARG; } for( size_t face = 0; face < nFaces; ++face ) { // Check for values in-range for( size_t point = 0; point < 3; ++point ) { index_t i = indices[ face*3 + point ]; if ( i >= nVerts && i != index_t(-1) ) { if ( !msgs ) return E_FAIL; result = false; wchar_t buff[ 128 ]; swprintf_s( buff, L"An invalid index value (%u) was found on face %Iu\n", i, face ); *msgs += buff; } if ( adjacency ) { uint32_t j = adjacency[ face*3 + point ]; if ( j >= nFaces && j != UNUSED32 ) { if ( !msgs ) return E_FAIL; result = false; wchar_t buff[ 128 ]; swprintf_s( buff, L"An invalid neighbor index value (%u) was found on face %Iu\n", j, face ); *msgs += buff; } } } // Check for unused faces index_t i0 = indices[ face*3 ]; index_t i1 = indices[ face*3 + 1 ]; index_t i2 = indices[ face*3 + 2 ]; if ( i0 == index_t(-1) || i1 == index_t(-1) || i2 == index_t(-1) ) { if ( flags & VALIDATE_UNUSED ) { if ( i0 != i1 || i0 != i2 || i1 != i2 ) { if ( !msgs ) return E_FAIL; result = false; wchar_t buff[ 128 ]; swprintf_s( buff, L"An unused face (%Iu) contains 'valid' but ignored vertices (%u,%u,%u)\n", face, i0, i1, i2 ); *msgs += buff; } if ( adjacency ) { for( size_t point = 0; point < 3; ++point ) { uint32_t k = adjacency[ face*3 + point ]; if ( k != UNUSED32 ) { if ( !msgs ) return E_FAIL; result = false; wchar_t buff[ 128 ]; swprintf_s( buff, L"An unused face (%Iu) has a neighbor %u\n", face, k ); *msgs += buff; } } } } // ignore unused triangles for remaining tests continue; } // Check for degenerate triangles if ( i0 == i1 || i0 == i2 || i1 == i2 ) { if ( flags & VALIDATE_DEGENERATE ) { if ( !msgs ) return E_FAIL; result = false; index_t bad; if ( i0 == i1 ) bad = i0; else if ( i1 == i2 ) bad = i2; else bad = i0; wchar_t buff[ 128 ]; swprintf_s( buff, L"A point (%u) was found more than once in triangle %Iu\n", bad, face ); *msgs += buff; if ( adjacency ) { for( size_t point = 0; point < 3; ++point ) { uint32_t k = adjacency[ face*3 + point ]; if ( k != UNUSED32 ) { result = false; swprintf_s( buff, L"A degenerate face (%Iu) has a neighbor %u\n", face, k ); *msgs += buff; } } } } // ignore degenerate triangles for remaining tests continue; } // Check for symmetric neighbors if ( ( flags & VALIDATE_ASYMMETRIC_ADJ ) && adjacency ) { for( size_t point = 0; point < 3; ++point ) { uint32_t k = adjacency[ face*3 + point ]; if ( k == UNUSED32 ) continue; assert( k < nFaces ); uint32_t edge = find_edge( &adjacency[ k * 3 ], uint32_t( face ) ); if ( edge >= 3 ) { if ( !msgs ) return E_FAIL; result = false; wchar_t buff[ 256 ]; swprintf_s( buff, L"A neighbor triangle (%u) does not reference back to this face (%Iu) as expected\n", k, face ); *msgs += buff; } } } // Check for duplicate neighbor if ( ( flags & VALIDATE_BACKFACING ) && adjacency ) { uint32_t j0 = adjacency[ face*3 ]; uint32_t j1 = adjacency[ face*3 + 1 ]; uint32_t j2 = adjacency[ face*3 + 2 ]; if ( ( j0 == j1 && j0 != UNUSED32 ) || ( j0 == j2 && j0 != UNUSED32 ) || ( j1 == j2 && j1 != UNUSED32 ) ) { if ( !msgs ) return E_FAIL; result = false; uint32_t bad; if ( j0 == j1 && j0 != UNUSED32 ) bad = j0; else if ( j0 == j2 && j0 != UNUSED32 ) bad = j0; else bad = j1; wchar_t buff[ 256 ]; swprintf_s( buff, L"A neighbor triangle (%u) was found more than once on triangle %Iu\n" L"\t(likley problem is that two triangles share same points with opposite direction)\n", bad, face ); *msgs += buff; } } } return result ? S_OK : E_FAIL; } //------------------------------------------------------------------------------------- // Validates mesh contains no bowties (i.e. a vertex is the apex of two separate triangle fans) //------------------------------------------------------------------------------------- template HRESULT ValidateNoBowties( _In_reads_(nFaces*3) const index_t* indices, _In_ size_t nFaces, _In_ size_t nVerts, _In_reads_opt_(nFaces*3) const uint32_t* adjacency, _In_opt_ std::wstring* msgs ) { if ( !adjacency ) { if ( msgs ) *msgs += L"Missing adjacency information required to check for BOWTIES\n"; return E_INVALIDARG; } size_t tsize = ( sizeof(bool) * nFaces * 3 ) + ( sizeof(index_t) * nVerts * 2 ) + ( sizeof(bool) * nVerts ); std::unique_ptr temp( new (std::nothrow) uint8_t[ tsize ] ); if ( !temp ) return E_OUTOFMEMORY; auto faceSeen = reinterpret_cast( temp.get() ); auto faceIds = reinterpret_cast( temp.get() + sizeof(bool) * nFaces * 3 ); auto faceUsing = reinterpret_cast( reinterpret_cast( faceIds ) + sizeof(index_t) * nVerts ); auto vertexBowtie = reinterpret_cast( reinterpret_cast( faceUsing ) + sizeof(index_t) * nVerts ); memset( faceSeen, 0, sizeof(bool) * nFaces * 3 ); memset( faceIds, 0xFF, sizeof(index_t) * nVerts ); memset( faceUsing, 0, sizeof(index_t) * nVerts ); memset( vertexBowtie, 0, sizeof(bool) * nVerts ); orbit_iterator ovi( adjacency, indices, nFaces ); bool result = true; for( uint32_t face = 0; face < nFaces; ++face ) { index_t i0 = indices[ face*3 ]; index_t i1 = indices[ face*3 + 1 ]; index_t i2 = indices[ face*3 + 2 ]; if ( i0 == i1 || i0 == i2 || i1 == i2 ) { // ignore degenerate faces faceSeen[ face * 3 ] = true; faceSeen[ face * 3 + 1 ] = true; faceSeen[ face * 3 + 2 ] = true; continue; } for( size_t point = 0; point < 3; ++point ) { if ( faceSeen[ face * 3 + point ] ) continue; faceSeen[ face * 3 + point ] = true; index_t i = indices[ face*3 + point ]; ovi.initialize( face, i, orbit_iterator::ALL ); ovi.moveToCCW(); while ( !ovi.done() ) { uint32_t curFace = ovi.nextFace(); if ( curFace >= nFaces ) return E_FAIL; uint32_t curPoint = ovi.getpoint(); if ( curPoint > 2 ) return E_FAIL; faceSeen[ curFace*3 + curPoint ] = true; uint32_t j = indices[ curFace * 3 + curPoint ]; if ( faceIds[ j ] == index_t(-1) ) { faceIds[ j ] = index_t( face ); faceUsing[ j ] = index_t( curFace ); } else if ( ( faceIds[ j ] != index_t( face ) ) && !vertexBowtie[ j ] ) { // We found a (unique) bowtie! if ( !msgs ) return E_FAIL; if ( result ) { // If this is the first bowtie found, add a quick explanation *msgs += L"A bowtie was found. Bowties can be fixed by calling Clean\n" L" A bowtie is the usage of a single vertex by two separate fans of triangles.\n" L" The fix is to duplicate the vertex so that each fan has its own vertex.\n"; result = false; } vertexBowtie[ j ] = true; wchar_t buff[ 256 ]; swprintf_s( buff, L"\nBowtie found around vertex %u shared by faces %u and %u\n", j, curFace, faceUsing[ j ] ); *msgs += buff; } } } } return result ? S_OK : E_FAIL; } }; namespace DirectX { //===================================================================================== // Entry-points //===================================================================================== //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT Validate( const uint16_t* indices, size_t nFaces, size_t nVerts, const uint32_t* adjacency, DWORD flags, std::wstring* msgs ) { if ( !indices || !nFaces || !nVerts ) return E_INVALIDARG; if ( nVerts >= UINT16_MAX ) return E_INVALIDARG; if ( ( uint64_t(nFaces) * 3 ) >= UINT32_MAX ) return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); if ( msgs ) msgs->clear(); HRESULT hr = ValidateIndices( indices, nFaces, nVerts, adjacency, flags, msgs ); if ( FAILED(hr) ) return hr; if ( flags & VALIDATE_BOWTIES ) { hr = ValidateNoBowties( indices, nFaces, nVerts, adjacency, msgs ); if ( FAILED(hr) ) return hr; } return S_OK; } //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT Validate( const uint32_t* indices, size_t nFaces, size_t nVerts, const uint32_t* adjacency, DWORD flags, std::wstring* msgs ) { if ( !indices || !nFaces || !nVerts ) return E_INVALIDARG; if ( nVerts >= UINT32_MAX ) return E_INVALIDARG; if ( ( uint64_t(nFaces) * 3 ) >= UINT32_MAX ) return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW ); if ( msgs ) msgs->clear(); HRESULT hr = ValidateIndices( indices, nFaces, nVerts, adjacency, flags, msgs ); if ( FAILED(hr) ) return hr; if ( flags & VALIDATE_BOWTIES ) { hr = ValidateNoBowties( indices, nFaces, nVerts, adjacency, msgs ); if ( FAILED(hr) ) return hr; } return S_OK; } } // namespace