// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "MeshTypes.h" /** * This defines the container used to hold mesh elements. * Its important properties are that it acts as an associative container (i.e. an element can be obtained from * a given index), and that insert/delete/find are cheap. * The current implementation is as a TSparseArray, but we abstract it so that this can be changed later if * required, e.g. a TMap might be desirable if we wished to maintain unique indices for the lifetime of the container. */ template class TMeshElementArrayBase { public: /** * Custom serialization for TMeshElementArrayBase. * The default TSparseArray serialization also compacts all the elements, removing the gaps and changing the indices. * The indices are significant in editable meshes, hence this is a custom serializer which preserves them. */ friend FArchive& operator<<( FArchive& Ar, TMeshElementArrayBase& Array ) { Array.Container.CountBytes( Ar ); if( Ar.IsLoading() ) { // Load array TBitArray<> AllocatedIndices; Ar << AllocatedIndices; Array.Container.Empty( AllocatedIndices.Num() ); for( auto It = TConstSetBitIterator<>( AllocatedIndices ); It; ++It ) { Array.Container.Insert( It.GetIndex(), ElementType() ); Ar << Array.Container[ It.GetIndex() ]; } } else { // Save array const int32 MaxIndex = Array.Container.GetMaxIndex(); // We have to build the TBitArray representing allocated indices by hand, as we don't have access to it from outside TSparseArray. // @todo core: consider replacing TSparseArray serialization with this format. TBitArray<> AllocatedIndices( false, MaxIndex ); int32 MaxAllocatedIndex = 0; for( int32 Index = 0; Index < MaxIndex; ++Index ) { if( Array.Container.IsAllocated( Index ) ) { AllocatedIndices[ Index ] = true; MaxAllocatedIndex = Index; } } // Cut off the trailing unallocated indices so that they are not serialized AllocatedIndices.SetNumUninitialized(MaxAllocatedIndex + 1); Ar << AllocatedIndices; for( auto It = Array.Container.CreateIterator(); It; ++It ) { Ar << *It; } } return Ar; } /** Compacts elements and returns a remapping table */ void Compact( TSparseArray& OutIndexRemap ); /** Remaps elements according to the passed remapping table */ void Remap( const TSparseArray& IndexRemap ); protected: /** The actual container, represented by a sparse array */ TSparseArray Container; }; /** * We prefer to access elements of the container via strongly-typed IDs. * This derived class imposes this type safety. */ template class TMeshElementArray final : public TMeshElementArrayBase { static_assert( TIsDerivedFrom::IsDerived, "ElementIDType must be derived from FElementID" ); using TMeshElementArrayBase::Container; public: /** Resets the container, optionally reserving space for elements to be added */ FORCEINLINE void Reset( const int32 Elements = 0 ) { Container.Reset(); Container.Reserve( Elements ); } /** Reserves space for the specified total number of elements */ FORCEINLINE void Reserve( const int32 Elements ) { Container.Reserve( Elements ); } /** Add a new element at the next available index, and return the new ID */ FORCEINLINE ElementIDType Add() { return ElementIDType( Container.Add( ElementType() ) ); } /** Add the provided element at the next available index, and return the new ID */ FORCEINLINE ElementIDType Add( typename TTypeTraits::ConstInitType Element ) { return ElementIDType( Container.Add( Element ) ); } /** Add the provided element at the next available index, and return the ID */ FORCEINLINE ElementIDType Add( ElementType&& Element ) { return ElementIDType( Container.Add( Forward( Element ) ) ); } /** Inserts a new element with the given ID */ FORCEINLINE ElementType& Insert( const ElementIDType ID ) { Container.Insert( ID.GetValue(), ElementType() ); return Container[ ID.GetValue() ]; } /** Inserts the provided element with the given ID */ FORCEINLINE ElementType& Insert( const ElementIDType ID, typename TTypeTraits::ConstInitType Element ) { Container.Insert( ID.GetValue(), Element ); return Container[ ID.GetValue() ]; } /** Inserts the provided element with the given ID */ FORCEINLINE ElementType& Insert( const ElementIDType ID, ElementType&& Element ) { Container.Insert( ID.GetValue(), Forward( Element ) ); return Container[ ID.GetValue() ]; } /** Removes the element with the given ID */ FORCEINLINE void Remove( const ElementIDType ID ) { checkSlow( Container.IsAllocated( ID.GetValue() ) ); Container.RemoveAt( ID.GetValue() ); } /** Returns the element with the given ID */ FORCEINLINE ElementType& operator[]( const ElementIDType ID ) { checkSlow( Container.IsAllocated( ID.GetValue() ) ); return Container[ ID.GetValue() ]; } FORCEINLINE const ElementType& operator[]( const ElementIDType ID ) const { checkSlow( Container.IsAllocated( ID.GetValue() ) ); return Container[ ID.GetValue() ]; } /** Returns the number of elements in the container */ FORCEINLINE int32 Num() const { return Container.Num(); } /** Returns the index after the last valid element */ FORCEINLINE int32 GetArraySize() const { return Container.GetMaxIndex(); } /** Returns the first valid ID */ FORCEINLINE ElementIDType GetFirstValidID() const { return Container.Num() > 0 ? ElementIDType( typename TSparseArray::TConstIterator( Container ).GetIndex() ) : INDEX_NONE; } /** Returns whether the given ID is valid or not */ FORCEINLINE bool IsValid( const ElementIDType ID ) const { return ID.GetValue() >= 0 && ID.GetValue() < Container.GetMaxIndex() && Container.IsAllocated( ID.GetValue() ); } /** Serializer */ FORCEINLINE friend FArchive& operator<<( FArchive& Ar, TMeshElementArray& Array ) { Ar << static_cast&>( Array ); return Ar; } /** * This is a special type of iterator which returns successive IDs of valid elements, rather than * the elements themselves. * It is designed to be used with a range-for: * * for( const FVertexID VertexID : GetVertices().GetElementIDs() ) * { * DoSomethingWith( VertexID ); * } */ class TElementIDs { public: explicit FORCEINLINE TElementIDs( const TSparseArray& InArray ) : Array( InArray ) {} class TConstIterator { public: explicit FORCEINLINE TConstIterator( typename TSparseArray::TConstIterator&& It ) : Iterator( MoveTemp( It ) ) {} FORCEINLINE TConstIterator& operator++() { ++Iterator; return *this; } FORCEINLINE ElementIDType operator*() const { return Iterator ? ElementIDType( Iterator.GetIndex() ) : INDEX_NONE; } friend FORCEINLINE bool operator==( const TConstIterator& Lhs, const TConstIterator& Rhs ) { return Lhs.Iterator == Rhs.Iterator; } friend FORCEINLINE bool operator!=( const TConstIterator& Lhs, const TConstIterator& Rhs ) { return Lhs.Iterator != Rhs.Iterator; } private: typename TSparseArray::TConstIterator Iterator; }; FORCEINLINE TConstIterator CreateConstIterator() const { return TConstIterator( typename TSparseArray::TConstIterator( Array ) ); } public: FORCEINLINE TConstIterator begin() const { return TConstIterator( Array.begin() ); } FORCEINLINE TConstIterator end() const { return TConstIterator( Array.end() ); } private: const TSparseArray& Array; }; /** Return iterable proxy object from container */ TElementIDs FORCEINLINE GetElementIDs() const { return TElementIDs( Container ); } }; template void TMeshElementArrayBase::Compact( TSparseArray& OutIndexRemap ) { TSparseArray NewContainer; NewContainer.Reserve( Container.Num() ); OutIndexRemap.Empty( Container.GetMaxIndex() ); // Add valid elements into a new contiguous sparse array. Note non-const iterator so we can move elements. for( typename TSparseArray::TIterator It( Container ); It; ++It ) { const int32 OldElementIndex = It.GetIndex(); // @todo mesheditor: implement TSparseArray::Add( ElementType&& ) to save this obscure approach const int32 NewElementIndex = NewContainer.Add( ElementType() ); NewContainer[ NewElementIndex ] = MoveTemp( *It ); // Provide an O(1) lookup from old index to new index, used when patching up vertex references afterwards OutIndexRemap.Insert( OldElementIndex, NewElementIndex ); } Container = MoveTemp( NewContainer ); } template void TMeshElementArrayBase::Remap( const TSparseArray& IndexRemap ) { TSparseArray NewContainer; NewContainer.Reserve( IndexRemap.GetMaxIndex() ); // Add valid elements into a new contiguous sparse array. Note non-const iterator so we can move elements. for( typename TSparseArray::TIterator It( Container ); It; ++It ) { const int32 OldElementIndex = It.GetIndex(); check( IndexRemap.IsAllocated( OldElementIndex ) ); const int32 NewElementIndex = IndexRemap[ OldElementIndex ]; // @todo mesheditor: implement TSparseArray::Insert( ElementType&& ) to save this obscure approach NewContainer.Insert( NewElementIndex, ElementType() ); NewContainer[ NewElementIndex ] = MoveTemp( *It ); } Container = MoveTemp( NewContainer ); }