Files
UnrealEngine/Engine/Source/Runtime/MeshDescription/Public/MeshElementArray.h
2025-05-18 13:04:45 +08:00

313 lines
9.5 KiB
C++

// 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 <typename ElementType>
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<int32>& OutIndexRemap );
/** Remaps elements according to the passed remapping table */
void Remap( const TSparseArray<int32>& IndexRemap );
protected:
/** The actual container, represented by a sparse array */
TSparseArray<ElementType> Container;
};
/**
* We prefer to access elements of the container via strongly-typed IDs.
* This derived class imposes this type safety.
*/
template <typename ElementType, typename ElementIDType>
class TMeshElementArray final : public TMeshElementArrayBase<ElementType>
{
static_assert( TIsDerivedFrom<ElementIDType, FElementID>::IsDerived, "ElementIDType must be derived from FElementID" );
using TMeshElementArrayBase<ElementType>::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<ElementType>::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<ElementType>( 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<ElementType>::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<ElementType>( 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<ElementType>::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<TMeshElementArrayBase<ElementType>&>( 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<ElementType>& InArray )
: Array( InArray )
{}
class TConstIterator
{
public:
explicit FORCEINLINE TConstIterator( typename TSparseArray<ElementType>::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<ElementType>::TConstIterator Iterator;
};
FORCEINLINE TConstIterator CreateConstIterator() const
{
return TConstIterator( typename TSparseArray<ElementType>::TConstIterator( Array ) );
}
public:
FORCEINLINE TConstIterator begin() const
{
return TConstIterator( Array.begin() );
}
FORCEINLINE TConstIterator end() const
{
return TConstIterator( Array.end() );
}
private:
const TSparseArray<ElementType>& Array;
};
/** Return iterable proxy object from container */
TElementIDs FORCEINLINE GetElementIDs() const { return TElementIDs( Container ); }
};
template <typename ElementType>
void TMeshElementArrayBase<ElementType>::Compact( TSparseArray<int32>& OutIndexRemap )
{
TSparseArray<ElementType> 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<ElementType>::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 <typename ElementType>
void TMeshElementArrayBase<ElementType>::Remap( const TSparseArray<int32>& IndexRemap )
{
TSparseArray<ElementType> 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<ElementType>::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 );
}