You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- 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]
467 lines
13 KiB
C++
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;
|
|
}; |