Files
UnrealEngineUWP/Engine/Source/Runtime/MeshDescription/Public/MeshElementContainer.h
Richard TalbotWatkin 4884bf9d9a First pass of MeshDescription API and format refactor.
- Removed hardcoded element type arrays (Vertices, Edges, Triangles etc.). Mesh element types can now be arbitrarily added, with any number of channels.
- Mesh element containers have a much leaner format; instead of sparse arrays, they are now represented by a simple bitarray, determining whether an index is used or not. Consequently, mesh topology is now entirely described with the attribute system, e.g. edge start and end vertices, triangle vertices, etc.
- Support added for attributes of arbitrary dimensions, e.g. float[4] or int[2].
- Support added for attributes which index into another mesh element container.
- Added FMeshElementIndexer: this is an efficient container for maintaining backward references from one element type to another; for example, edges have an attribute specifying which vertices are at each end (an attribute of type FVertexID[2]). With an indexer, it is possible to look up which edges contain a given vertex, even though this is not explicitly stored. Indexers are designed to do minimal allocations and update lazily and in batch when necessary.
- Added support for preserving UV topology in static meshes. UVs are now a first-class element type which may be indexed directly from triangles.
- Added the facility to access the underlying array in an attribute array directly.
- Triangles now directly reference their vertex, edge and UV IDs. Vertex instances are to be deprecated.
- Changed various systems to be triangle-centric rather than polygon-centric, as this is faster. Triangles are presumed to be the elementary face type in a MeshDescription, even if polygons are still supported. The concept of polygons will be somewhat shifted to mean a group of triangles which should be treated collectively for editing purposes.
- Optimised normal/tangent generation and FBX import.
- Deprecated EditableMesh, MeshEditor and StaticMeshEditorExtension plugins - these are to be removed, but they still have certain hooks in place which need removing.
#rb

[CL 13568702 by Richard TalbotWatkin in ue5-main branch]
2020-05-28 10:56:57 -04:00

467 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "MeshAttributeArray.h"
/**
* Class representing a collection of mesh elements, such as vertices or triangles.
* It has two purposes:
* 1) To generate new element IDs on demand, and recycle those which have been discarded.
* 2) To hold a map of attributes for a given element type and their values.
*/
class FMeshElementContainer
{
public:
FMeshElementContainer() = default;
FMeshElementContainer(const FMeshElementContainer&) = default;
FMeshElementContainer& operator=(const FMeshElementContainer&) = default;
/** Move constructor which ensures that NumHoles is set correctly in the moved object */
FMeshElementContainer(FMeshElementContainer&& Other)
{
BitArray = MoveTemp(Other.BitArray);
Attributes = MoveTemp(Other.Attributes);
NumHoles = Other.NumHoles;
Other.NumHoles = 0;
}
/** Move assignment operator which ensures that NumHoles is set correctly in the moved object */
FMeshElementContainer& operator=(FMeshElementContainer&& Other)
{
if (this != &Other)
{
BitArray = MoveTemp(Other.BitArray);
Attributes = MoveTemp(Other.Attributes);
NumHoles = Other.NumHoles;
Other.NumHoles = 0;
}
return *this;
}
/** Resets the container, optionally reserving space for elements to be added */
void Reset(const int32 Elements = 0)
{
BitArray.Empty(Elements);
Attributes.Initialize(0);
NumHoles = 0;
}
/** Reserves space for the specified total number of elements */
void Reserve(const int32 Elements) { BitArray.Reserve(Elements); }
/** Add a new element at the next available index, and return the new ID */
int32 Add()
{
if (NumHoles > 0)
{
// If there are holes, use those up first.
const int32 Index = BitArray.FindAndSetFirstZeroBit();
check(Index != INDEX_NONE);
NumHoles--;
return Index;
}
else
{
// Otherwise add a new element, and insert a corresponding attribute slot.
const int32 Index = BitArray.Add(true);
Attributes.Insert(Index);
return Index;
}
}
/** Inserts a new element with the given index */
void Insert(const int32 Index)
{
checkSlow(Index >= 0);
if (Index >= BitArray.Num())
{
// If the index is beyond the current bit array size, create a new one, padded out with zeroes.
const int32 FirstNewIndex = BitArray.Num();
const int32 NumNewHoles = Index - FirstNewIndex;
BitArray.SetNumUninitialized(Index + 1);
BitArray.SetRange(FirstNewIndex, NumNewHoles, false);
BitArray[Index] = true;
NumHoles += NumNewHoles;
Attributes.Insert(Index);
}
else
{
// Can't insert an index over an existing one.
// If we get here, we assume this is a hole, so decrement the number of holes.
checkSlow(!BitArray[Index]);
BitArray[Index] = true;
NumHoles--;
}
}
/** Removes the element with the given ID */
void Remove(const int32 Index)
{
// We can't remove an element which is already a hole.
checkSlow(BitArray[Index]);
BitArray[Index] = false;
NumHoles++;
Attributes.Remove(Index);
}
/** Returns the number of elements in the container */
int32 Num() const { return BitArray.Num() - NumHoles; }
/** Returns the index after the last valid element */
int32 GetArraySize() const { return BitArray.Num(); }
/** Returns the first valid ID */
int32 GetFirstValidID() const
{
return BitArray.Find(true);
}
/** Returns whether the given ID is valid or not */
bool IsValid(const int32 Index) const
{
return Index >= 0 && Index < BitArray.Num() && BitArray[Index];
}
/** Accessor for attributes */
FORCEINLINE FAttributesSetBase& GetAttributes() { return Attributes; }
FORCEINLINE const FAttributesSetBase& GetAttributes() const { return Attributes; }
/** 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);
/** Serializer */
friend FArchive& operator<<(FArchive& Ar, FMeshElementContainer& Container)
{
Ar << Container.BitArray;
// We could count the number of holes in the BitArray, but it is quicker for serialization purposes (particularly in transactions) to just store it.
Ar << Container.NumHoles;
Ar << Container.Attributes;
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 int32 VertexIndex : GetVertices().GetElementIDs())
* {
* DoSomethingWith(VertexIndex);
* }
*/
class FElementIDs
{
public:
explicit FORCEINLINE FElementIDs(const TBitArray<>& InArray)
: Array(InArray)
{}
class FConstIterator
{
public:
explicit FORCEINLINE FConstIterator(TConstSetBitIterator<>&& It)
: Iterator(MoveTemp(It))
{}
FORCEINLINE FConstIterator& operator++()
{
++Iterator;
return *this;
}
FORCEINLINE int32 operator*() const
{
return Iterator ? Iterator.GetIndex() : INDEX_NONE;
}
friend FORCEINLINE bool operator==(const FConstIterator& Lhs, const FConstIterator& Rhs)
{
return Lhs.Iterator == Rhs.Iterator;
}
friend FORCEINLINE bool operator!=(const FConstIterator& Lhs, const FConstIterator& Rhs)
{
return Lhs.Iterator != Rhs.Iterator;
}
private:
TConstSetBitIterator<> Iterator;
};
FORCEINLINE FConstIterator CreateConstIterator() const
{
return FConstIterator(TConstSetBitIterator<>(Array));
}
public:
FORCEINLINE FConstIterator begin() const
{
return FConstIterator(TConstSetBitIterator<>(Array));
}
FORCEINLINE FConstIterator end() const
{
return FConstIterator(TConstSetBitIterator<>(Array, Array.Num()));
}
private:
const TBitArray<>& Array;
};
/** Return iterable proxy object from container */
FElementIDs FORCEINLINE GetElementIDs() const { return FElementIDs(BitArray); }
protected:
/** A bit array of all indices currently in use */
TBitArray<> BitArray;
/** Attributes associated with this element container */
FAttributesSetBase Attributes;
/** A count of the number of unused indices in the bit array. IMPORTANT: This must always correspond to the number of zeroes in the BitArray. */
int32 NumHoles = 0;
};
/**
* Templated specialization for type-safety.
*/
template <typename ElementIDType>
class TMeshElementContainer : public FMeshElementContainer
{
public:
/** Add a new element at the next available index, and return the new ID */
ElementIDType Add()
{
return ElementIDType(FMeshElementContainer::Add());
}
/** Inserts a new element with the given index */
void Insert(const ElementIDType Index)
{
FMeshElementContainer::Insert(Index.GetValue());
}
/** Removes the element with the given ID */
void Remove(const ElementIDType Index)
{
FMeshElementContainer::Remove(Index.GetValue());
}
/** Returns the first valid ID */
ElementIDType GetFirstValidID() const
{
return ElementIDType(FMeshElementContainer::GetFirstValidID());
}
/** Returns whether the given ID is valid or not */
bool IsValid(const ElementIDType Index) const
{
return FMeshElementContainer::IsValid(Index.GetValue());
}
/** Accessor for attributes */
FORCEINLINE TAttributesSet<ElementIDType>& GetAttributes() { return static_cast<TAttributesSet<ElementIDType>&>(Attributes); }
FORCEINLINE const TAttributesSet<ElementIDType>& GetAttributes() const { return static_cast<const TAttributesSet<ElementIDType>&>(Attributes); }
/** Serializer */
friend FArchive& operator<<(FArchive& Ar, TMeshElementContainer& Container)
{
Ar << static_cast<FMeshElementContainer&>(Container);
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 int32 VertexIndex : GetVertices().GetElementIDs())
* {
* DoSomethingWith(VertexIndex);
* }
*/
class TElementIDs
{
public:
explicit FORCEINLINE TElementIDs(const TBitArray<>& InArray)
: Array(InArray)
{}
class TConstIterator
{
public:
explicit FORCEINLINE TConstIterator(TConstSetBitIterator<>&& 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:
TConstSetBitIterator<> Iterator;
};
FORCEINLINE TConstIterator CreateConstIterator() const
{
return TConstIterator(TConstSetBitIterator<>(Array));
}
public:
FORCEINLINE TConstIterator begin() const
{
return TConstIterator(TConstSetBitIterator<>(Array));
}
FORCEINLINE TConstIterator end() const
{
return TConstIterator(TConstSetBitIterator<>(Array, Array.Num()));
}
private:
const TBitArray<>& Array;
};
/** Return iterable proxy object from container */
TElementIDs FORCEINLINE GetElementIDs() const { return TElementIDs(BitArray); }
};
/**
* This is a wrapper for an array of allocated MeshElementContainers.
*/
class FMeshElementChannels
{
public:
/** Default constructor creates a single element */
explicit FMeshElementChannels(const int32 NumberOfIndices = 1)
{
Channels.SetNum(NumberOfIndices);
}
/** Transparent access through the array */
FORCEINLINE const FMeshElementContainer& Get(const int32 Index = 0) const { return Channels[Index]; }
FORCEINLINE const FMeshElementContainer& operator[](const int32 Index) const { return Channels[Index]; }
FORCEINLINE const FMeshElementContainer& operator*() const { return Channels[0]; }
FORCEINLINE const FMeshElementContainer* operator->() const { return &Channels[0]; }
FORCEINLINE FMeshElementContainer& Get(const int32 Index = 0) { return Channels[Index]; }
FORCEINLINE FMeshElementContainer& operator[](const int32 Index) { return Channels[Index]; }
FORCEINLINE FMeshElementContainer& operator*() { return Channels[0]; }
FORCEINLINE FMeshElementContainer* operator->() { return &Channels[0]; }
/** Change the number of indices */
void SetNumChannels(const int32 NumIndices) { Channels.SetNum(NumIndices); }
/** Get the number of indices */
int32 GetNumChannels() const { return Channels.Num(); }
/** Resets the containers */
void Reset()
{
for (FMeshElementContainer& Channel : Channels)
{
Channel.Reset();
}
}
/** Determines whether the mesh element type is empty or not */
bool IsEmpty() const
{
for (const FMeshElementContainer& Channel : Channels)
{
if (Channel.GetArraySize() != 0) { return false; }
}
return true;
}
/** Serializer */
friend FArchive& operator<<(FArchive& Ar, FMeshElementChannels& ElementType)
{
Ar << ElementType.Channels;
return Ar;
}
private:
// The usual thing is that a MeshElementType has exactly one channel, so we specially reserve a single element inline to save extra allocations
TArray<FMeshElementContainer, TInlineAllocator<1>> Channels;
};
/**
* This is a wrapper for a FMeshElementChannels.
* It holds a TUniquePtr pointing to the actual FMeshElementChannels, so that its address is constant and can be cached.
*/
class FMeshElementTypeWrapper
{
public:
/** Default constructor - construct a MeshElementType, optionally with more than one channel */
explicit FMeshElementTypeWrapper(const int32 NumberOfChannels = 1)
{
check(NumberOfChannels > 0);
Ptr = MakeUnique<FMeshElementChannels>(NumberOfChannels);
}
/** Copy constructor - make a deep copy of the MeshElementType through the TUniquePtr */
FMeshElementTypeWrapper(const FMeshElementTypeWrapper& Other)
{
check(Other.Ptr.IsValid());
Ptr = MakeUnique<FMeshElementChannels>(*Other.Ptr);
}
/** Copy assignment */
FMeshElementTypeWrapper& operator=(const FMeshElementTypeWrapper& Other)
{
FMeshElementTypeWrapper Temp(Other);
Swap(*this, Temp);
return *this;
}
/** Default move constructor */
FMeshElementTypeWrapper(FMeshElementTypeWrapper&&) = default;
/** Default move assignment */
FMeshElementTypeWrapper& operator=(FMeshElementTypeWrapper&&) = default;
/** Transparent access through the TUniquePtr */
const FMeshElementChannels* Get() const { return Ptr.Get(); }
const FMeshElementChannels* operator->() const { return Ptr.Get(); }
const FMeshElementChannels& operator*() const { return *Ptr; }
FMeshElementChannels* Get() { return Ptr.Get(); }
FMeshElementChannels* operator->() { return Ptr.Get(); }
FMeshElementChannels& operator*() { return *Ptr; }
/** Serializer */
friend FArchive& operator<<(FArchive& Ar, FMeshElementTypeWrapper& Wrapper)
{
Ar << *Wrapper.Ptr;
return Ar;
}
private:
TUniquePtr<FMeshElementChannels> Ptr;
};